From b309c25619cf7427faa81ce59e1ba2931e7bfa45 Mon Sep 17 00:00:00 2001 From: FlightControl Date: Sun, 15 Sep 2019 18:02:45 +0200 Subject: [PATCH 1/7] Speech Engine Prototype --- .../Moose/AI/AI_A2G_Dispatcher.lua | 26 +- Moose Development/Moose/Core/RadioSpeech.lua | 310 ++++++++++++++++++ Moose Development/Moose/Modules.lua | 1 + .../Moose/Tasking/DetectionManager.lua | 38 ++- 4 files changed, 345 insertions(+), 30 deletions(-) create mode 100644 Moose Development/Moose/Core/RadioSpeech.lua diff --git a/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua b/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua index 0d74a86d2..0c6450c2c 100644 --- a/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua +++ b/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua @@ -3511,12 +3511,12 @@ do -- AI_A2G_DISPATCHER self:F({"Defender Takeoff", DefenderGroup:GetName()}) --self:GetParent(self).onafterBirth( self, Defender, From, Event, To ) - local DefenderName = DefenderGroup:GetCallsign() + local DefenderName = DefenderGroup:GetCallsign() -- #string local Dispatcher = AI_A2G_Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) if Squadron then - Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " airborne.", "Wheels_up.wav", 3, "A2G/", DefenderGroup ) + Dispatcher:MessageToPlayers( DefenderName .. ", wheels up.", DefenderGroup ) AI_A2G_Fsm:Patrol() -- Engage on the TargetSetUnit end end @@ -3529,7 +3529,7 @@ do -- AI_A2G_DISPATCHER local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) if Squadron then - Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " patrolling.", "Patrolling.wav", 3, "A2G/", DefenderGroup ) + Dispatcher:MessageToPlayers( DefenderName .. ", patrolling.", DefenderGroup ) end Dispatcher:ClearDefenderTaskTarget( DefenderGroup ) @@ -3548,7 +3548,7 @@ do -- AI_A2G_DISPATCHER local FirstUnit = AttackSetUnit:GetFirst() local Coordinate = FirstUnit:GetCoordinate() -- Core.Point#COORDINATE - Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " on route to ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), "Moving_on_to_ground_target.wav", 3, "A2G/", DefenderGroup ) + Dispatcher:MessageToPlayers( DefenderName .. ", moving on to ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), DefenderGroup ) end end @@ -3563,7 +3563,7 @@ do -- AI_A2G_DISPATCHER if FirstUnit then local Coordinate = FirstUnit:GetCoordinate() - Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " engaging ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), "Engaging_ground_target.wav", 3, "A2G/", DefenderGroup ) + Dispatcher:MessageToPlayers( DefenderName .. ", engaging ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), DefenderGroup ) end end @@ -3574,7 +3574,7 @@ do -- AI_A2G_DISPATCHER local DefenderName = DefenderGroup:GetCallsign() local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " returning.", "Returning_to_base.wav", 3, "A2G/", DefenderGroup ) + Dispatcher:MessageToPlayers( DefenderName .. ", returning to base.", DefenderGroup ) Dispatcher:ClearDefenderTaskTarget( DefenderGroup ) end @@ -3587,7 +3587,7 @@ do -- AI_A2G_DISPATCHER local DefenderName = DefenderGroup:GetCallsign() local Dispatcher = AI_A2G_Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " lost control." ) + Dispatcher:MessageToPlayers( DefenderName .. ", lost control." ) if DefenderGroup:IsAboveRunway() then Dispatcher:RemoveDefenderFromSquadron( Squadron, DefenderGroup ) DefenderGroup:Destroy() @@ -3602,7 +3602,7 @@ do -- AI_A2G_DISPATCHER local DefenderName = DefenderGroup:GetCallsign() local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " landing.", "Landing_at_base.wav", 3, "A2G/", DefenderGroup ) + Dispatcher:MessageToPlayers( DefenderName .. ", landing at base.", DefenderGroup ) if Action and Action == "Destroy" then Dispatcher:RemoveDefenderFromSquadron( Squadron, DefenderGroup ) @@ -3659,7 +3659,7 @@ do -- AI_A2G_DISPATCHER self:F( { DefenderTarget = DefenderTarget } ) if DefenderTarget then - Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " airborne. Engaging!", "Wheels_up.wav", 3, "A2G/", DefenderGroup ) + Dispatcher:MessageToPlayers( DefenderName .. ", wheels up.", DefenderGroup ) AI_A2G_Fsm:EngageRoute( DefenderTarget.Set ) -- Engage on the TargetSetUnit end end @@ -3675,7 +3675,7 @@ do -- AI_A2G_DISPATCHER local FirstUnit = AttackSetUnit:GetFirst() local Coordinate = FirstUnit:GetCoordinate() -- Core.Point#COORDINATE - Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " on route to ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), "Moving_on_to_ground_target.wav", 3, "A2G/", DefenderGroup ) + Dispatcher:MessageToPlayers( DefenderName .. ", on route to ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), DefenderGroup ) end self:GetParent(self).onafterEngageRoute( self, DefenderGroup, From, Event, To, AttackSetUnit ) end @@ -3691,7 +3691,7 @@ do -- AI_A2G_DISPATCHER if FirstUnit then local Coordinate = FirstUnit:GetCoordinate() - Dispatcher:MessageToPlayers( Squadron, "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " engaging ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), "Engaging_ground_target.wav", 3, "A2G/", DefenderGroup ) + Dispatcher:MessageToPlayers( DefenderName .. ", engaging ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), DefenderGroup ) end end @@ -3701,7 +3701,7 @@ do -- AI_A2G_DISPATCHER local DefenderName = DefenderGroup:GetCallsign() local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " returning.", "Returning_to_base.wav", 3, "A2G/", DefenderGroup ) + Dispatcher:MessageToPlayers( DefenderName .. ", returning to base.", DefenderGroup ) self:GetParent(self).onafterRTB( self, DefenderGroup, From, Event, To ) @@ -3732,7 +3732,7 @@ do -- AI_A2G_DISPATCHER local DefenderName = DefenderGroup:GetCallsign() local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " landing.", "Landing_at_base.wav", 3, "A2G/", DefenderGroup ) + Dispatcher:MessageToPlayers( DefenderName .. ", landing at base.", DefenderGroup ) if Action and Action == "Destroy" then Dispatcher:RemoveDefenderFromSquadron( Squadron, DefenderGroup ) diff --git a/Moose Development/Moose/Core/RadioSpeech.lua b/Moose Development/Moose/Core/RadioSpeech.lua new file mode 100644 index 000000000..618cb5161 --- /dev/null +++ b/Moose Development/Moose/Core/RadioSpeech.lua @@ -0,0 +1,310 @@ +--- **Core** - Makes the radio talk. +-- +-- === +-- +-- ## Features: +-- +-- * Send text strings using a vocabulary that is converted in spoken language. +-- * Possiblity to implement multiple language. +-- +-- === +-- +-- ### Authors: FlightControl +-- +-- @module Core.Speech +-- @image Core_Radio.JPG + +--- Makes the radio speak. +-- +-- # RADIOSPEECH usage +-- +-- +-- @type RADIOSPEECH +-- @extends Core.RadioQueue#RADIOQUEUE +RADIOSPEECH = { + ClassName = "RADIOSPEECH", + Vocabulary = { + EN = {}, + } +} + + +RADIOSPEECH.Vocabulary.EN = { + ["1"] = { "1", 0.25 }, + ["2"] = { "2", 0.25 }, + ["3"] = { "3", 0.30 }, + ["4"] = { "4", 0.35 }, + ["5"] = { "5", 0.35 }, + ["6"] = { "6", 0.42 }, + ["7"] = { "7", 0.38 }, + ["8"] = { "8", 0.20 }, + ["9"] = { "9", 0.32 }, + ["10"] = { "10", 0.35 }, + ["11"] = { "11", 0.40 }, + ["12"] = { "12", 0.42 }, + ["13"] = { "13", 0.38 }, + ["14"] = { "14", 0.42 }, + ["15"] = { "15", 0.42 }, + ["16"] = { "16", 0.52 }, + ["17"] = { "17", 0.59 }, + ["18"] = { "18", 0.40 }, + ["19"] = { "19", 0.47 }, + ["20"] = { "20", 0.38 }, + ["30"] = { "30", 0.29 }, + ["40"] = { "40", 0.35 }, + ["50"] = { "50", 0.32 }, + ["60"] = { "60", 0.44 }, + ["70"] = { "70", 0.48 }, + ["80"] = { "80", 0.26 }, + ["90"] = { "90", 0.36 }, + ["100"] = { "100", 0.55 }, + ["1000"] = { "1000", 1 }, + + ["chevy"] = { "chevy", 0.35 }, + ["colt"] = { "colt", 0.35 }, + ["springfield"] = { "springfield", 0.65 }, + ["dodge"] = { "dodge", 0.35 }, + ["enfield"] = { "enfield", 0.5 }, + ["ford"] = { "ford", 0.32 }, + ["pontiac"] = { "pontiac", 0.55 }, + ["uzi"] = { "uzi", 0.28 }, + + ["degrees"] = { "degrees", 0.5 }, + ["�"] = { "degrees", 0.5 }, + ["kilometers"] = { "kilometers", 0.65 }, + ["km"] = { "kilometers", 0.65 }, + ["miles"] = { "miles", 0.45 }, + ["mi"] = { "miles", 0.45 }, + + ["br"] = { "br", 1.1 }, + ["bra"] = { "bra", 0.3 }, + + + ["returning to base"] = { "returning_to_base", 0.85 }, + ["moving on to ground target"] = { "moving_on_to_ground_target", 1.20 }, + ["engaging ground target"] = { "engaging_ground_target", 1.20 }, + ["wheels up"] = { "wheels_up", 0.42 }, + ["landing at base"] = { "landing at base", 0.8 }, + ["patrolling"] = { "patrolling", 0.55 }, + + ["for"] = { "for", 0.31 }, + ["and"] = { "and", 0.31 }, + ["at"] = { "at", 0.3 }, + ["dot"] = { "dot", 0.26 }, + ["defender"] = { "defender", 0.45 }, +} + + + +--- Create a new RADIOSPEECH object for a given radio frequency/modulation. +-- @param #RADIOSPEECH self +-- @param #number frequency The radio frequency in MHz. +-- @param #number modulation (Optional) The radio modulation. Default radio.modulation.AM. +-- @return #RADIOSPEECH self The RADIOSPEECH object. +function RADIOSPEECH:New(frequency, modulation) + + -- Inherit base + local self = BASE:Inherit( self, RADIOQUEUE:New( frequency, modulation ) ) -- #RADIOSPEECH + + self.Language = "EN" + + self:BuildTree() + + return self +end + + +--- Add Sentence to the Speech collection. +-- @param #RADIOSPEECH self +-- @param #string RemainingSentence The remaining sentence during recursion. +-- @param #table Speech The speech node. +-- @param #string Sentence The full sentence. +-- @param #string Data The speech data. +-- @return #RADIOSPEECH self The RADIOSPEECH object. +function RADIOSPEECH:AddSentenceToSpeech( RemainingSentence, Speech, Sentence, Data ) + + self:I( { RemainingSentence, Speech, Sentence, Data } ) + + local Token, RemainingSentence = RemainingSentence:match( "^ *(%w+)(.*)" ) + self:I( { Token = Token, RemainingSentence = RemainingSentence } ) + + -- Is there a Token? + if Token then + + -- We check if the Token is already in the Speech collection. + if not Speech[Token] then + + -- There is not yet a vocabulary registered for this. + Speech[Token] = {} + + if RemainingSentence and RemainingSentence ~= "" then + -- We use recursion to iterate through the complete Sentence, and make a chain of Tokens. + -- The last Speech node in the collection contains the Sentence and the Data to be spoken. + -- This to ensure that during the actual speech: + -- - Complete sentences are being understood. + -- - Words without speech are ignored. + -- - Incorrect sequence of words are ignored. + Speech[Token].Next = {} + self:AddSentenceToSpeech( RemainingSentence, Speech[Token].Next, Sentence, Data ) + else + -- There is no remaining sentence, so we add speech to the Sentence. + -- The recursion stops here. + Speech[Token].Sentence = Sentence + Speech[Token].Data = Data + end + end + end +end + +--- Build the tree structure based on the language words, in order to find the correct sentences and to ignore incomprehensible words. +-- @param #RADIOSPEECH self +-- @return #RADIOSPEECH self The RADIOSPEECH object. +function RADIOSPEECH:BuildTree() + + self.Speech = {} + + for Language, Sentences in pairs( self.Vocabulary ) do + self:I( { Language = Language, Sentences = Sentences }) + self.Speech[Language] = {} + for Sentence, Data in pairs( Sentences ) do + self:I( { Sentence = Sentence, Data = Data } ) + self:AddSentenceToSpeech( Sentence, self.Speech[Language], Sentence, Data ) + end + end + + self:I( { Speech = self.Speech } ) + + return self +end + +--- Speak a sentence. +-- @param #RADIOSPEECH self +-- @param #string Sentence The sentence to be spoken. +function RADIOSPEECH:SpeakWords( Sentence, Speech ) + + local Word, RemainderSentence = Sentence:match( "^[^%d%a]*(%a*)(.*)" ) + + self:I( { Word = Word, Speech = Speech[Word], RemainderSentence = RemainderSentence } ) + + if Word and Word ~= "" then + + -- Construct of words + Word = Word:lower() + if Speech[Word] then + -- The end of the sentence has been reached. Now Speech.Next should be nil, otherwise there is an error. + if Speech[Word].Next == nil then + self:I( { Sentence = Speech[Word].Sentence, Data = Speech[Word].Data } ) + self:NewTransmission( Speech[Word].Data[1] .. ".wav", Speech[Word].Data[2], "EN/" ) + else + if RemainderSentence and RemainderSentence ~= "" then + RemainderSentence = self:SpeakWords( RemainderSentence, Speech[Word].Next ) + end + end + end + end + + return RemainderSentence + +end + +--- Speak a sentence. +-- @param #RADIOSPEECH self +-- @param #string Sentence The sentence to be spoken. +function RADIOSPEECH:SpeakDigits( Sentence, Speech ) + + local Digits, RemainderSentence = Sentence:match( "^[^%a%d]*(%d*)(.*)" ) + + self:I( { Digits = Digits, Speech = Speech[Digits], RemainderSentence = RemainderSentence } ) + + if Digits and Digits ~= "" then + + -- Construct numbers + local Number = tonumber( Digits ) + local Multiple = nil + while Number >= 0 do + if Number > 1000 then + Multiple = math.floor( Number / 1000 ) * 1000 + elseif Number > 100 then + Multiple = math.floor( Number / 100 ) * 100 + elseif Number > 20 then + Multiple = math.floor( Number / 10 ) * 10 + elseif Number >= 0 then + Multiple = Number + end + Sentence = tostring( Multiple ) + if Speech[Sentence] then + self:I( { Speech = Speech[Sentence].Sentence, Data = Speech[Sentence].Data } ) + self:NewTransmission( Speech[Sentence].Data[1] .. ".wav", Speech[Sentence].Data[2], "EN/" ) + end + Number = Number - Multiple + Number = ( Number == 0 ) and -1 or Number + end + end + + return RemainderSentence +end + + +--- Speak a sentence. +-- @param #RADIOSPEECH self +-- @param #string Sentence The sentence to be spoken. +function RADIOSPEECH:SpeakSymbols( Sentence, Speech ) + + local Symbol, RemainderSentence = Sentence:match( "^[^%a%d]*(°*)(.*)" ) + + self:I( { Sentence = Sentence, Symbol = Symbol, Speech = Speech[Symbol], RemainderSentence = RemainderSentence } ) + + if Symbol and Symbol ~= "" then + local Word = nil + if Symbol == "°" then + Word = "degrees" + end + if Word then + if Speech[Word] then + self:I( { Speech = Speech[Word].Sentence, Data = Speech[Word].Data } ) + self:NewTransmission( Speech[Word].Data[1] .. ".wav", Speech[Word].Data[2], "EN/" ) + end + end + end + + return RemainderSentence +end + + +--- Speak a sentence. +-- @param #RADIOSPEECH self +-- @param #string Sentence The sentence to be spoken. +function RADIOSPEECH:Speak( Sentence, Speech ) + + self:I( { Sentence, Speech } ) + + local Language = self.Language + + -- If there is no node for Speech, then we start at the first nodes of the language. + if not Speech then + Speech = self.Speech[Language] + end + + self:I( { Speech = Speech, Language = Language } ) + + self:NewTransmission( "_In.wav", 0.52, "EN/" ) + + repeat + + Sentence = self:SpeakWords( Sentence, Speech ) + + self:I( { Sentence = Sentence } ) + + Sentence = self:SpeakDigits( Sentence, Speech ) + + self:I( { Sentence = Sentence } ) + +-- Sentence = self:SpeakSymbols( Sentence, Speech ) +-- +-- self:I( { Sentence = Sentence } ) + + until not Sentence or Sentence == "" + + self:NewTransmission( "_Out.wav", 0.28, "EN/" ) + +end diff --git a/Moose Development/Moose/Modules.lua b/Moose Development/Moose/Modules.lua index eb0d8f61e..0a499fe25 100644 --- a/Moose Development/Moose/Modules.lua +++ b/Moose Development/Moose/Modules.lua @@ -21,6 +21,7 @@ __Moose.Include( 'Scripts/Moose/Core/Message.lua' ) __Moose.Include( 'Scripts/Moose/Core/Fsm.lua' ) __Moose.Include( 'Scripts/Moose/Core/Radio.lua' ) __Moose.Include( 'Scripts/Moose/Core/RadioQueue.lua' ) +__Moose.Include( 'Scripts/Moose/Core/RadioSpeech.lua' ) __Moose.Include( 'Scripts/Moose/Core/Spawn.lua' ) __Moose.Include( 'Scripts/Moose/Core/SpawnStatic.lua' ) __Moose.Include( 'Scripts/Moose/Core/Goal.lua' ) diff --git a/Moose Development/Moose/Tasking/DetectionManager.lua b/Moose Development/Moose/Tasking/DetectionManager.lua index 8bec92065..de21616bd 100644 --- a/Moose Development/Moose/Tasking/DetectionManager.lua +++ b/Moose Development/Moose/Tasking/DetectionManager.lua @@ -225,7 +225,6 @@ do -- DETECTION MANAGER --- Set a command center to communicate actions to the players reporting to the command center. -- @param #DETECTION_MANAGER self - -- @param Tasking.CommandCenter#COMMANDCENTER CommandCenter The command center. -- @return #DETECTION_MANGER self function DETECTION_MANAGER:SetTacticalMenu( DispatcherMainMenuText, DispatcherMenuText ) @@ -279,7 +278,7 @@ do -- DETECTION MANAGER self.RadioQueue = nil - self.RadioQueue = RADIOQUEUE:New( self.RadioFrequency, self.RadioModulation ) + self.RadioQueue = RADIOSPEECH:New( self.RadioFrequency, self.RadioModulation ) self.RadioQueue.power = self.RadioPower self.RadioQueue:Start( 0.5 ) end @@ -292,27 +291,32 @@ do -- DETECTION MANAGER -- @param #string SoundPath The path pointing to the folder in the mission file. -- @param Wrapper.Group#GROUP DefenderGroup The defender group sending the message. -- @return #DETECTION_MANGER self - function DETECTION_MANAGER:MessageToPlayers( Message, SoundFile, SoundDuration, SoundPath, DefenderGroup ) + function DETECTION_MANAGER:MessageToPlayers( Message, DefenderGroup ) self:F( { Message = Message } ) - if not self.PreviousMessage or self.PreviousMessage ~= Message then - self.PreviousMessage = Message - if self.CC then - self.CC:MessageToCoalition( Message ) - end +-- if not self.PreviousMessage or self.PreviousMessage ~= Message then +-- self.PreviousMessage = Message +-- if self.CC then +-- self.CC:MessageToCoalition( Message ) +-- end +-- end + + if self.CC then + self.CC:MessageToCoalition( Message ) end - -- Here we handle the transmission of the voice over. - -- If for a certain reason the Defender does not exist, we use the coordinate of the airbase to send the message from. - if SoundFile then - local RadioQueue = self.RadioQueue -- Core.RadioQueue#RADIOQUEUE - local DefenderUnit = DefenderGroup:GetUnit(1) - if DefenderUnit and DefenderUnit:IsAlive() then - RadioQueue:SetSenderUnitName( DefenderUnit:GetName() ) - end - RadioQueue:NewTransmission( SoundFile, SoundDuration, SoundPath ) + Message = Message:gsub( "°", "degrees" ) + Message = Message:gsub( "(%d)%.(%d)", "%1dot%2" ) + + -- Here we handle the transmission of the voice over. + -- If for a certain reason the Defender does not exist, we use the coordinate of the airbase to send the message from. + local RadioQueue = self.RadioQueue -- Core.RadioQueue#RADIOQUEUE + local DefenderUnit = DefenderGroup:GetUnit(1) + if DefenderUnit and DefenderUnit:IsAlive() then + RadioQueue:SetSenderUnitName( DefenderUnit:GetName() ) end + RadioQueue:Speak( Message ) return self end From 8c54b7298d28b55a5586286f223cfc27c7a1c828 Mon Sep 17 00:00:00 2001 From: FlightControl Date: Sun, 15 Sep 2019 18:36:27 +0200 Subject: [PATCH 2/7] Tuning A2A AI dispatcher to work with new sound speech system. --- .../Moose/AI/AI_A2A_Dispatcher.lua | 70 +++++++++---------- .../Moose/Tasking/DetectionManager.lua | 10 +-- 2 files changed, 41 insertions(+), 39 deletions(-) diff --git a/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua b/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua index eb7c8d004..cf59ef0fa 100644 --- a/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua +++ b/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua @@ -3100,31 +3100,31 @@ do -- AI_A2A_DISPATCHER self:SetDefenderTask( SquadronName, DefenderCAP, "CAP", Fsm ) - function Fsm:onafterTakeoff( Defender, From, Event, To ) - self:F({"CAP Birth", Defender:GetName()}) + function Fsm:onafterTakeoff( DefenderGroup, From, Event, To ) + self:F({"CAP Birth", DefenderGroup:GetName()}) --self:GetParent(self).onafterBirth( self, Defender, From, Event, To ) - local DefenderName = Defender:GetCallsign() + local DefenderName = DefenderGroup:GetCallsign() local Dispatcher = Fsm:GetDispatcher() -- #AI_A2A_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) + local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) if Squadron then - Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " airborne. Starting Patrol." ) + Dispatcher:MessageToPlayers( DefenderName .. " Wheels up.", DefenderGroup ) Fsm:__Patrol( 2 ) -- Start Patrolling end end - function Fsm:onafterRTB( Defender, From, Event, To ) + function Fsm:onafterRTB( DefenderGroup, From, Event, To ) - self:F({"CAP RTB", Defender:GetName()}) + self:F({"CAP RTB", DefenderGroup:GetName()}) - self:GetParent(self).onafterRTB( self, Defender, From, Event, To ) + self:GetParent(self).onafterRTB( self, DefenderGroup, From, Event, To ) - local DefenderName = Defender:GetCallsign() + local DefenderName = DefenderGroup:GetCallsign() local Dispatcher = self:GetDispatcher() -- #AI_A2A_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) - Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " returning." ) - Dispatcher:ClearDefenderTaskTarget( Defender ) + local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) + Dispatcher:MessageToPlayers( DefenderName .. " returning to base.", DefenderGroup ) + Dispatcher:ClearDefenderTaskTarget( DefenderGroup ) end --- @param #AI_A2A_DISPATCHER self @@ -3295,30 +3295,30 @@ do -- AI_A2A_DISPATCHER self:SetDefenderTask( ClosestDefenderSquadronName, DefenderGCI, "GCI", Fsm, AttackerDetection ) - function Fsm:onafterTakeoff( Defender, From, Event, To ) - self:F({"GCI Birth", Defender:GetName()}) + function Fsm:onafterTakeoff( DefenderGroup, From, Event, To ) + self:F({"GCI Birth", DefenderGroup:GetName()}) --self:GetParent(self).onafterBirth( self, Defender, From, Event, To ) - local DefenderName = Defender:GetCallsign() + local DefenderName = DefenderGroup:GetCallsign() local Dispatcher = Fsm:GetDispatcher() -- #AI_A2A_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) - local DefenderTarget = Dispatcher:GetDefenderTaskTarget( Defender ) + local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) + local DefenderTarget = Dispatcher:GetDefenderTaskTarget( DefenderGroup ) if DefenderTarget then - Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " airborne. Engaging target!" ) + Dispatcher:MessageToPlayers( DefenderName .. " wheels up.", DefenderGroup ) Fsm:__Engage( 2, DefenderTarget.Set ) -- Engage on the TargetSetUnit end end - function Fsm:onafterRTB( Defender, From, Event, To ) - self:F({"GCI RTB", Defender:GetName()}) - self:GetParent(self).onafterRTB( self, Defender, From, Event, To ) + function Fsm:onafterRTB( DefenderGroup, From, Event, To ) + self:F({"GCI RTB", DefenderGroup:GetName()}) + self:GetParent(self).onafterRTB( self, DefenderGroup, From, Event, To ) - local DefenderName = Defender:GetCallsign() + local DefenderName = DefenderGroup:GetCallsign() local Dispatcher = self:GetDispatcher() -- #AI_A2A_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) - Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " returning." ) - Dispatcher:ClearDefenderTaskTarget( Defender ) + local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) + Dispatcher:MessageToPlayers( DefenderName .. " returning to base.", DefenderGroup ) + Dispatcher:ClearDefenderTaskTarget( DefenderGroup ) end --- @param #AI_A2A_DISPATCHER self @@ -3335,24 +3335,24 @@ do -- AI_A2A_DISPATCHER end --- @param #AI_A2A_DISPATCHER self - function Fsm:onafterHome( Defender, From, Event, To, Action ) - self:F({"GCI Home", Defender:GetName()}) - self:GetParent(self).onafterHome( self, Defender, From, Event, To ) + function Fsm:onafterHome( DefenderGroup, From, Event, To, Action ) + self:F({"GCI Home", DefenderGroup:GetName()}) + self:GetParent(self).onafterHome( self, DefenderGroup, From, Event, To ) - local DefenderName = Defender:GetCallsign() + local DefenderName = DefenderGroup:GetCallsign() local Dispatcher = self:GetDispatcher() -- #AI_A2A_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) + local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " landing." ) + Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " landing at base.", DefenderGroup ) if Action and Action == "Destroy" then - Dispatcher:RemoveDefenderFromSquadron( Squadron, Defender ) - Defender:Destroy() + Dispatcher:RemoveDefenderFromSquadron( Squadron, DefenderGroup ) + DefenderGroup:Destroy() end if Dispatcher:GetSquadronLanding( Squadron.Name ) == AI_A2A_DISPATCHER.Landing.NearAirbase then - Dispatcher:RemoveDefenderFromSquadron( Squadron, Defender ) - Defender:Destroy() + Dispatcher:RemoveDefenderFromSquadron( Squadron, DefenderGroup ) + DefenderGroup:Destroy() Dispatcher:ParkDefender( Squadron ) end end diff --git a/Moose Development/Moose/Tasking/DetectionManager.lua b/Moose Development/Moose/Tasking/DetectionManager.lua index de21616bd..e40121654 100644 --- a/Moose Development/Moose/Tasking/DetectionManager.lua +++ b/Moose Development/Moose/Tasking/DetectionManager.lua @@ -312,11 +312,13 @@ do -- DETECTION MANAGER -- Here we handle the transmission of the voice over. -- If for a certain reason the Defender does not exist, we use the coordinate of the airbase to send the message from. local RadioQueue = self.RadioQueue -- Core.RadioQueue#RADIOQUEUE - local DefenderUnit = DefenderGroup:GetUnit(1) - if DefenderUnit and DefenderUnit:IsAlive() then - RadioQueue:SetSenderUnitName( DefenderUnit:GetName() ) + if RadioQueue then + local DefenderUnit = DefenderGroup:GetUnit(1) + if DefenderUnit and DefenderUnit:IsAlive() then + RadioQueue:SetSenderUnitName( DefenderUnit:GetName() ) + end + RadioQueue:Speak( Message ) end - RadioQueue:Speak( Message ) return self end From c579aad6060130e8d9b79b1785fbfc6dc6db452f Mon Sep 17 00:00:00 2001 From: FlightControl Date: Sun, 22 Sep 2019 08:23:28 +0200 Subject: [PATCH 3/7] API V2 of the A2A and A2G dispatchers. Now a more fine grained attack is possible. You can now tweak the altitude and speed when units engage the target. Especially useful for ground attacks when SEAD, CAS or BAI.You can model low altitude approaches. Added 2 new test missions under the IAD - A2G API v2. Please report any bugs. --- Moose Development/Moose/AI/AI_A2A.lua | 737 ------------------ Moose Development/Moose/AI/AI_A2A_Cap.lua | 379 ++------- .../Moose/AI/AI_A2A_Dispatcher.lua | 178 ++++- Moose Development/Moose/AI/AI_A2A_Gci.lua | 372 +-------- Moose Development/Moose/AI/AI_A2A_Patrol.lua | 49 +- Moose Development/Moose/AI/AI_A2G.lua | 69 -- Moose Development/Moose/AI/AI_A2G_BAI.lua | 149 +--- Moose Development/Moose/AI/AI_A2G_CAS.lua | 148 ++-- .../Moose/AI/AI_A2G_Dispatcher.lua | 342 +++++--- Moose Development/Moose/AI/AI_A2G_SEAD.lua | 158 ++-- .../Moose/AI/AI_Air_Dispatcher.lua | 2 +- .../{AI_A2G_Engage.lua => AI_Air_Engage.lua} | 346 +++++--- .../{AI_A2G_Patrol.lua => AI_Air_Patrol.lua} | 234 ++++-- Moose Development/Moose/Core/RadioSpeech.lua | 11 +- Moose Development/Moose/Modules.lua | 6 +- 15 files changed, 1012 insertions(+), 2168 deletions(-) delete mode 100644 Moose Development/Moose/AI/AI_A2A.lua delete mode 100644 Moose Development/Moose/AI/AI_A2G.lua rename Moose Development/Moose/AI/{AI_A2G_Engage.lua => AI_Air_Engage.lua} (64%) rename Moose Development/Moose/AI/{AI_A2G_Patrol.lua => AI_Air_Patrol.lua} (56%) diff --git a/Moose Development/Moose/AI/AI_A2A.lua b/Moose Development/Moose/AI/AI_A2A.lua deleted file mode 100644 index f23fc006e..000000000 --- a/Moose Development/Moose/AI/AI_A2A.lua +++ /dev/null @@ -1,737 +0,0 @@ ---- **AI** -- (R2.2) - Models the process of air operations for airplanes. --- --- === --- --- ### Author: **FlightControl** --- --- === --- --- @module AI.AI_A2A --- @image MOOSE.JPG - ---BASE:TraceClass("AI_A2A") - - ---- @type AI_A2A --- @extends Core.Fsm#FSM_CONTROLLABLE - ---- The AI_A2A class implements the core functions to operate an AI @{Wrapper.Group} A2A tasking. --- --- --- ## AI_A2A constructor --- --- * @{#AI_A2A.New}(): Creates a new AI_A2A object. --- --- ## 2. AI_A2A is a FSM --- --- ![Process](..\Presentations\AI_PATROL\Dia2.JPG) --- --- ### 2.1. AI_A2A States --- --- * **None** ( Group ): The process is not started yet. --- * **Patrolling** ( Group ): The AI is patrolling the Patrol Zone. --- * **Returning** ( Group ): The AI is returning to Base. --- * **Stopped** ( Group ): The process is stopped. --- * **Crashed** ( Group ): The AI has crashed or is dead. --- --- ### 2.2. AI_A2A Events --- --- * **Start** ( Group ): Start the process. --- * **Stop** ( Group ): Stop the process. --- * **Route** ( Group ): Route the AI to a new random 3D point within the Patrol Zone. --- * **RTB** ( Group ): Route the AI to the home base. --- * **Detect** ( Group ): The AI is detecting targets. --- * **Detected** ( Group ): The AI has detected new targets. --- * **Status** ( Group ): The AI is checking status (fuel and damage). When the tresholds have been reached, the AI will RTB. --- --- ## 3. Set or Get the AI controllable --- --- * @{#AI_A2A.SetControllable}(): Set the AIControllable. --- * @{#AI_A2A.GetControllable}(): Get the AIControllable. --- --- @field #AI_A2A -AI_A2A = { - ClassName = "AI_A2A", -} - ---- Creates a new AI_A2A object --- @param #AI_A2A self --- @param Wrapper.Group#GROUP AIGroup The GROUP object to receive the A2A Process. --- @return #AI_A2A -function AI_A2A:New( AIGroup ) - - -- Inherits from BASE - local self = BASE:Inherit( self, FSM_CONTROLLABLE:New() ) -- #AI_A2A - - self:SetControllable( AIGroup ) - - self:SetFuelThreshold( .2, 60 ) - self:SetDamageThreshold( 0.4 ) - self:SetDisengageRadius( 70000 ) - - self:SetStartState( "Stopped" ) - - self:AddTransition( "*", "Start", "Started" ) - - --- Start Handler OnBefore for AI_A2A - -- @function [parent=#AI_A2A] OnBeforeStart - -- @param #AI_A2A self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @return #boolean - - --- Start Handler OnAfter for AI_A2A - -- @function [parent=#AI_A2A] OnAfterStart - -- @param #AI_A2A self - -- @param #string From - -- @param #string Event - -- @param #string To - - --- Start Trigger for AI_A2A - -- @function [parent=#AI_A2A] Start - -- @param #AI_A2A self - - --- Start Asynchronous Trigger for AI_A2A - -- @function [parent=#AI_A2A] __Start - -- @param #AI_A2A self - -- @param #number Delay - - self:AddTransition( "*", "Stop", "Stopped" ) - ---- OnLeave Transition Handler for State Stopped. --- @function [parent=#AI_A2A] OnLeaveStopped --- @param #AI_A2A self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. --- @return #boolean Return false to cancel Transition. - ---- OnEnter Transition Handler for State Stopped. --- @function [parent=#AI_A2A] OnEnterStopped --- @param #AI_A2A self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. - ---- OnBefore Transition Handler for Event Stop. --- @function [parent=#AI_A2A] OnBeforeStop --- @param #AI_A2A self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. --- @return #boolean Return false to cancel Transition. - ---- OnAfter Transition Handler for Event Stop. --- @function [parent=#AI_A2A] OnAfterStop --- @param #AI_A2A self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. - ---- Synchronous Event Trigger for Event Stop. --- @function [parent=#AI_A2A] Stop --- @param #AI_A2A self - ---- Asynchronous Event Trigger for Event Stop. --- @function [parent=#AI_A2A] __Stop --- @param #AI_A2A self --- @param #number Delay The delay in seconds. - - self:AddTransition( "*", "Status", "*" ) -- FSM_CONTROLLABLE Transition for type #AI_A2A. - ---- OnBefore Transition Handler for Event Status. --- @function [parent=#AI_A2A] OnBeforeStatus --- @param #AI_A2A self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. --- @return #boolean Return false to cancel Transition. - ---- OnAfter Transition Handler for Event Status. --- @function [parent=#AI_A2A] OnAfterStatus --- @param #AI_A2A self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. - ---- Synchronous Event Trigger for Event Status. --- @function [parent=#AI_A2A] Status --- @param #AI_A2A self - ---- Asynchronous Event Trigger for Event Status. --- @function [parent=#AI_A2A] __Status --- @param #AI_A2A self --- @param #number Delay The delay in seconds. - - self:AddTransition( "*", "RTB", "*" ) -- FSM_CONTROLLABLE Transition for type #AI_A2A. - ---- OnBefore Transition Handler for Event RTB. --- @function [parent=#AI_A2A] OnBeforeRTB --- @param #AI_A2A self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. --- @return #boolean Return false to cancel Transition. - ---- OnAfter Transition Handler for Event RTB. --- @function [parent=#AI_A2A] OnAfterRTB --- @param #AI_A2A self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. - ---- Synchronous Event Trigger for Event RTB. --- @function [parent=#AI_A2A] RTB --- @param #AI_A2A self - ---- Asynchronous Event Trigger for Event RTB. --- @function [parent=#AI_A2A] __RTB --- @param #AI_A2A self --- @param #number Delay The delay in seconds. - ---- OnLeave Transition Handler for State Returning. --- @function [parent=#AI_A2A] OnLeaveReturning --- @param #AI_A2A self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. --- @return #boolean Return false to cancel Transition. - ---- OnEnter Transition Handler for State Returning. --- @function [parent=#AI_A2A] OnEnterReturning --- @param #AI_A2A self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. - - self:AddTransition( "Patrolling", "Refuel", "Refuelling" ) - - --- Refuel Handler OnBefore for AI_A2A - -- @function [parent=#AI_A2A] OnBeforeRefuel - -- @param #AI_A2A self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From - -- @param #string Event - -- @param #string To - -- @return #boolean - - --- Refuel Handler OnAfter for AI_A2A - -- @function [parent=#AI_A2A] OnAfterRefuel - -- @param #AI_A2A self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. - -- @param #string From - -- @param #string Event - -- @param #string To - - --- Refuel Trigger for AI_A2A - -- @function [parent=#AI_A2A] Refuel - -- @param #AI_A2A self - - --- Refuel Asynchronous Trigger for AI_A2A - -- @function [parent=#AI_A2A] __Refuel - -- @param #AI_A2A self - -- @param #number Delay - - self:AddTransition( "*", "Takeoff", "Airborne" ) - self:AddTransition( "*", "Return", "Returning" ) - self:AddTransition( "*", "Hold", "Holding" ) - self:AddTransition( "*", "Home", "Home" ) - self:AddTransition( "*", "LostControl", "LostControl" ) - self:AddTransition( "*", "Fuel", "Fuel" ) - self:AddTransition( "*", "Damaged", "Damaged" ) - self:AddTransition( "*", "Eject", "*" ) - self:AddTransition( "*", "Crash", "Crashed" ) - self:AddTransition( "*", "PilotDead", "*" ) - - self.IdleCount = 0 - - return self -end - ---- @param Wrapper.Group#GROUP self --- @param Core.Event#EVENTDATA EventData -function GROUP:OnEventTakeoff( EventData, Fsm ) - Fsm:Takeoff() - self:UnHandleEvent( EVENTS.Takeoff ) -end - -function AI_A2A:SetDispatcher( Dispatcher ) - self.Dispatcher = Dispatcher -end - -function AI_A2A:GetDispatcher() - return self.Dispatcher -end - -function AI_A2A:SetTargetDistance( Coordinate ) - - local CurrentCoord = self.Controllable:GetCoordinate() - self.TargetDistance = CurrentCoord:Get2DDistance( Coordinate ) - - self.ClosestTargetDistance = ( not self.ClosestTargetDistance or self.ClosestTargetDistance > self.TargetDistance ) and self.TargetDistance or self.ClosestTargetDistance -end - - -function AI_A2A:ClearTargetDistance() - - self.TargetDistance = nil - self.ClosestTargetDistance = nil -end - - ---- Sets (modifies) the minimum and maximum speed of the patrol. --- @param #AI_A2A self --- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Controllable} in km/h. --- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Controllable} in km/h. --- @return #AI_A2A self -function AI_A2A:SetSpeed( PatrolMinSpeed, PatrolMaxSpeed ) - self:F2( { PatrolMinSpeed, PatrolMaxSpeed } ) - - self.PatrolMinSpeed = PatrolMinSpeed - self.PatrolMaxSpeed = PatrolMaxSpeed -end - - ---- Sets the floor and ceiling altitude of the patrol. --- @param #AI_A2A self --- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol. --- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol. --- @return #AI_A2A self -function AI_A2A:SetAltitude( PatrolFloorAltitude, PatrolCeilingAltitude ) - self:F2( { PatrolFloorAltitude, PatrolCeilingAltitude } ) - - self.PatrolFloorAltitude = PatrolFloorAltitude - self.PatrolCeilingAltitude = PatrolCeilingAltitude -end - - ---- Sets the home airbase. --- @param #AI_A2A self --- @param Wrapper.Airbase#AIRBASE HomeAirbase --- @return #AI_A2A self -function AI_A2A:SetHomeAirbase( HomeAirbase ) - self:F2( { HomeAirbase } ) - - self.HomeAirbase = HomeAirbase -end - ---- Sets to refuel at the given tanker. --- @param #AI_A2A self --- @param Wrapper.Group#GROUP TankerName The group name of the tanker as defined within the Mission Editor or spawned. --- @return #AI_A2A self -function AI_A2A:SetTanker( TankerName ) - self:F2( { TankerName } ) - - self.TankerName = TankerName -end - - ---- Sets the disengage range, that when engaging a target beyond the specified range, the engagement will be cancelled and the plane will RTB. --- @param #AI_A2A self --- @param #number DisengageRadius The disengage range. --- @return #AI_A2A self -function AI_A2A:SetDisengageRadius( DisengageRadius ) - self:F2( { DisengageRadius } ) - - self.DisengageRadius = DisengageRadius -end - ---- Set the status checking off. --- @param #AI_A2A self --- @return #AI_A2A self -function AI_A2A:SetStatusOff() - self:F2() - - self.CheckStatus = false -end - - ---- When the AI is out of fuel, it is required that a new AI is started, before the old AI can return to the home base. --- Therefore, with a parameter and a calculation of the distance to the home base, the fuel treshold is calculated. --- When the fuel treshold is reached, the AI will continue for a given time its patrol task in orbit, while a new AIControllable is targetted to the AI_A2A. --- Once the time is finished, the old AI will return to the base. --- @param #AI_A2A self --- @param #number PatrolFuelThresholdPercentage The treshold in percentage (between 0 and 1) when the AIControllable is considered to get out of fuel. --- @param #number PatrolOutOfFuelOrbitTime The amount of seconds the out of fuel AIControllable will orbit before returning to the base. --- @return #AI_A2A self -function AI_A2A:SetFuelThreshold( PatrolFuelThresholdPercentage, PatrolOutOfFuelOrbitTime ) - - self.PatrolFuelThresholdPercentage = PatrolFuelThresholdPercentage - self.PatrolOutOfFuelOrbitTime = PatrolOutOfFuelOrbitTime - - self.Controllable:OptionRTBBingoFuel( false ) - - return self -end - ---- When the AI is damaged beyond a certain treshold, it is required that the AI returns to the home base. --- However, damage cannot be foreseen early on. --- Therefore, when the damage treshold is reached, --- the AI will return immediately to the home base (RTB). --- Note that for groups, the average damage of the complete group will be calculated. --- So, in a group of 4 airplanes, 2 lost and 2 with damage 0.2, the damage treshold will be 0.25. --- @param #AI_A2A self --- @param #number PatrolDamageThreshold The treshold in percentage (between 0 and 1) when the AI is considered to be damaged. --- @return #AI_A2A self -function AI_A2A:SetDamageThreshold( PatrolDamageThreshold ) - - self.PatrolManageDamage = true - self.PatrolDamageThreshold = PatrolDamageThreshold - - return self -end - ---- Defines a new patrol route using the @{Process_PatrolZone} parameters and settings. --- @param #AI_A2A self --- @return #AI_A2A self --- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. -function AI_A2A:onafterStart( Controllable, From, Event, To ) - - self:__Status( 10 ) -- Check status status every 30 seconds. - - self:HandleEvent( EVENTS.PilotDead, self.OnPilotDead ) - self:HandleEvent( EVENTS.Crash, self.OnCrash ) - self:HandleEvent( EVENTS.Ejection, self.OnEjection ) - - Controllable:OptionROEHoldFire() - Controllable:OptionROTVertical() -end - - - ---- @param #AI_A2A self -function AI_A2A:onbeforeStatus() - - return self.CheckStatus -end - ---- @param #AI_A2A self -function AI_A2A:onafterStatus() - - if self.Controllable and self.Controllable:IsAlive() then - - local RTB = false - - local DistanceFromHomeBase = self.HomeAirbase:GetCoordinate():Get2DDistance( self.Controllable:GetCoordinate() ) - - if not self:Is( "Holding" ) and not self:Is( "Returning" ) then - local DistanceFromHomeBase = self.HomeAirbase:GetCoordinate():Get2DDistance( self.Controllable:GetCoordinate() ) - self:F({DistanceFromHomeBase=DistanceFromHomeBase}) - - if DistanceFromHomeBase > self.DisengageRadius then - self:I( self.Controllable:GetName() .. " is too far from home base, RTB!" ) - self:Hold( 300 ) - RTB = false - end - end - --- I think this code is not requirement anymore after release 2.5. --- if self:Is( "Fuel" ) or self:Is( "Damaged" ) or self:Is( "LostControl" ) then --- if DistanceFromHomeBase < 5000 then --- self:E( self.Controllable:GetName() .. " is near the home base, RTB!" ) --- self:Home( "Destroy" ) --- end --- end - - - if not self:Is( "Fuel" ) and not self:Is( "Home" ) then - local Fuel = self.Controllable:GetFuelMin() - self:F({Fuel=Fuel, PatrolFuelThresholdPercentage=self.PatrolFuelThresholdPercentage}) - if Fuel < self.PatrolFuelThresholdPercentage then - if self.TankerName then - self:I( self.Controllable:GetName() .. " is out of fuel: " .. Fuel .. " ... Refuelling at Tanker!" ) - self:Refuel() - else - self:I( self.Controllable:GetName() .. " is out of fuel: " .. Fuel .. " ... RTB!" ) - local OldAIControllable = self.Controllable - - local OrbitTask = OldAIControllable:TaskOrbitCircle( math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ), self.PatrolMinSpeed ) - local TimedOrbitTask = OldAIControllable:TaskControlled( OrbitTask, OldAIControllable:TaskCondition(nil,nil,nil,nil,self.PatrolOutOfFuelOrbitTime,nil ) ) - OldAIControllable:SetTask( TimedOrbitTask, 10 ) - - self:Fuel() - RTB = true - end - else - end - end - - -- TODO: Check GROUP damage function. - local Damage = self.Controllable:GetLife() - local InitialLife = self.Controllable:GetLife0() - self:F( { Damage = Damage, InitialLife = InitialLife, DamageThreshold = self.PatrolDamageThreshold } ) - if ( Damage / InitialLife ) < self.PatrolDamageThreshold then - self:I( self.Controllable:GetName() .. " is damaged: " .. Damage .. " ... RTB!" ) - self:Damaged() - RTB = true - self:SetStatusOff() - end - - -- Check if planes went RTB and are out of control. - -- We only check if planes are out of control, when they are in duty. - if self.Controllable:HasTask() == false then - if not self:Is( "Started" ) and - not self:Is( "Stopped" ) and - not self:Is( "Fuel" ) and - not self:Is( "Damaged" ) and - not self:Is( "Home" ) then - if self.IdleCount >= 3 then - if Damage ~= InitialLife then - self:Damaged() - else - self:I( self.Controllable:GetName() .. " control lost! " ) - self:LostControl() - end - else - self.IdleCount = self.IdleCount + 1 - end - end - else - self.IdleCount = 0 - end - - if RTB == true then - self:__RTB( 0.5 ) - end - - if not self:Is("Home") then - self:__Status( 10 ) - end - - end -end - - ---- @param Wrapper.Group#GROUP AIGroup -function AI_A2A.RTBRoute( AIGroup, Fsm ) - - AIGroup:F( { "AI_A2A.RTBRoute:", AIGroup:GetName() } ) - - if AIGroup:IsAlive() then - Fsm:__RTB( 0.5 ) - end - -end - ---- @param Wrapper.Group#GROUP AIGroup -function AI_A2A.RTBHold( AIGroup, Fsm ) - - AIGroup:F( { "AI_A2A.RTBHold:", AIGroup:GetName() } ) - if AIGroup:IsAlive() then - Fsm:__RTB( 0.5 ) - Fsm:Return() - local Task = AIGroup:TaskOrbitCircle( 4000, 400 ) - AIGroup:SetTask( Task ) - end - -end - - ---- @param #AI_A2A self --- @param Wrapper.Group#GROUP AIGroup -function AI_A2A:onafterRTB( AIGroup, From, Event, To ) - self:F( { AIGroup, From, Event, To } ) - - - if AIGroup and AIGroup:IsAlive() then - - self:I( "Group " .. AIGroup:GetName() .. " ... RTB! ( " .. self:GetState() .. " )" ) - - self:ClearTargetDistance() - AIGroup:ClearTasks() - - local EngageRoute = {} - - --- Calculate the target route point. - - local CurrentCoord = AIGroup:GetCoordinate() - local ToTargetCoord = self.HomeAirbase:GetCoordinate() - local ToTargetSpeed = math.random( self.PatrolMinSpeed, self.PatrolMaxSpeed ) - local ToAirbaseAngle = CurrentCoord:GetAngleDegrees( CurrentCoord:GetDirectionVec3( ToTargetCoord ) ) - - local Distance = CurrentCoord:Get2DDistance( ToTargetCoord ) - - local ToAirbaseCoord = CurrentCoord:Translate( 5000, ToAirbaseAngle ) - if Distance < 5000 then - self:I( "RTB and near the airbase!" ) - self:Home() - return - end - --- Create a route point of type air. - local ToRTBRoutePoint = ToAirbaseCoord:WaypointAir( - self.PatrolAltType, - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, - ToTargetSpeed, - true - ) - - self:F( { Angle = ToAirbaseAngle, ToTargetSpeed = ToTargetSpeed } ) - self:T2( { self.MinSpeed, self.MaxSpeed, ToTargetSpeed } ) - - EngageRoute[#EngageRoute+1] = ToRTBRoutePoint - EngageRoute[#EngageRoute+1] = ToRTBRoutePoint - - AIGroup:OptionROEHoldFire() - AIGroup:OptionROTEvadeFire() - - --- Now we're going to do something special, we're going to call a function from a waypoint action at the AIControllable... - AIGroup:WayPointInitialize( EngageRoute ) - - local Tasks = {} - Tasks[#Tasks+1] = AIGroup:TaskFunction( "AI_A2A.RTBRoute", self ) - EngageRoute[#EngageRoute].task = AIGroup:TaskCombo( Tasks ) - - --- NOW ROUTE THE GROUP! - AIGroup:Route( EngageRoute, 0.5 ) - - end - -end - ---- @param #AI_A2A self --- @param Wrapper.Group#GROUP AIGroup -function AI_A2A:onafterHome( AIGroup, From, Event, To ) - self:F( { AIGroup, From, Event, To } ) - - self:I( "Group " .. self.Controllable:GetName() .. " ... Home! ( " .. self:GetState() .. " )" ) - - if AIGroup and AIGroup:IsAlive() then - end - -end - - - ---- @param #AI_A2A self --- @param Wrapper.Group#GROUP AIGroup -function AI_A2A:onafterHold( AIGroup, From, Event, To, HoldTime ) - self:F( { AIGroup, From, Event, To } ) - - self:I( "Group " .. self.Controllable:GetName() .. " ... Holding! ( " .. self:GetState() .. " )" ) - - if AIGroup and AIGroup:IsAlive() then - local OrbitTask = AIGroup:TaskOrbitCircle( math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ), self.PatrolMinSpeed ) - local TimedOrbitTask = AIGroup:TaskControlled( OrbitTask, AIGroup:TaskCondition( nil, nil, nil, nil, HoldTime , nil ) ) - - local RTBTask = AIGroup:TaskFunction( "AI_A2A.RTBHold", self ) - - local OrbitHoldTask = AIGroup:TaskOrbitCircle( 4000, self.PatrolMinSpeed ) - - --AIGroup:SetState( AIGroup, "AI_A2A", self ) - - AIGroup:SetTask( AIGroup:TaskCombo( { TimedOrbitTask, RTBTask, OrbitHoldTask } ), 1 ) - end - -end - ---- @param Wrapper.Group#GROUP AIGroup -function AI_A2A.Resume( AIGroup, Fsm ) - - AIGroup:I( { "AI_A2A.Resume:", AIGroup:GetName() } ) - if AIGroup:IsAlive() then - Fsm:__RTB( 0.5 ) - end - -end - ---- @param #AI_A2A self --- @param Wrapper.Group#GROUP AIGroup -function AI_A2A:onafterRefuel( AIGroup, From, Event, To ) - self:F( { AIGroup, From, Event, To } ) - - self:I( "Group " .. self.Controllable:GetName() .. " ... Refuelling! ( " .. self:GetState() .. " )" ) - - if AIGroup and AIGroup:IsAlive() then - local Tanker = GROUP:FindByName( self.TankerName ) - if Tanker:IsAlive() and Tanker:IsAirPlane() then - - local RefuelRoute = {} - - --- Calculate the target route point. - - local CurrentCoord = AIGroup:GetCoordinate() - local ToRefuelCoord = Tanker:GetCoordinate() - local ToRefuelSpeed = math.random( self.PatrolMinSpeed, self.PatrolMaxSpeed ) - - --- Create a route point of type air. - local ToRefuelRoutePoint = CurrentCoord:WaypointAir( - self.PatrolAltType, - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, - ToRefuelSpeed, - true - ) - - self:F( { ToRefuelSpeed = ToRefuelSpeed } ) - - RefuelRoute[#RefuelRoute+1] = ToRefuelRoutePoint - RefuelRoute[#RefuelRoute+1] = ToRefuelRoutePoint - - AIGroup:OptionROEHoldFire() - AIGroup:OptionROTEvadeFire() - - local Tasks = {} - Tasks[#Tasks+1] = AIGroup:TaskRefueling() - Tasks[#Tasks+1] = AIGroup:TaskFunction( self:GetClassName() .. ".Resume", self ) - RefuelRoute[#RefuelRoute].task = AIGroup:TaskCombo( Tasks ) - - AIGroup:Route( RefuelRoute, 0.5 ) - else - self:RTB() - end - end - -end - - - ---- @param #AI_A2A self -function AI_A2A:onafterDead() - self:SetStatusOff() -end - - ---- @param #AI_A2A self --- @param Core.Event#EVENTDATA EventData -function AI_A2A:OnCrash( EventData ) - - if self.Controllable:IsAlive() and EventData.IniDCSGroupName == self.Controllable:GetName() then - self:I( self.Controllable:GetUnits() ) - if #self.Controllable:GetUnits() == 1 then - self:__Crash( 1, EventData ) - end - end -end - ---- @param #AI_A2A self --- @param Core.Event#EVENTDATA EventData -function AI_A2A:OnEjection( EventData ) - - if self.Controllable:IsAlive() and EventData.IniDCSGroupName == self.Controllable:GetName() then - self:__Eject( 1, EventData ) - end -end - ---- @param #AI_A2A self --- @param Core.Event#EVENTDATA EventData -function AI_A2A:OnPilotDead( EventData ) - - if self.Controllable:IsAlive() and EventData.IniDCSGroupName == self.Controllable:GetName() then - self:__PilotDead( 1, EventData ) - end -end diff --git a/Moose Development/Moose/AI/AI_A2A_Cap.lua b/Moose Development/Moose/AI/AI_A2A_Cap.lua index ba41a3bab..460d5d614 100644 --- a/Moose Development/Moose/AI/AI_A2A_Cap.lua +++ b/Moose Development/Moose/AI/AI_A2A_Cap.lua @@ -10,7 +10,8 @@ -- @image AI_Combat_Air_Patrol.JPG --- @type AI_A2A_CAP --- @extends AI.AI_A2A_Patrol#AI_A2A_PATROL +-- @extends AI.AI_Air_Patrol#AI_AIR_PATROL +-- @extends AI.AI_Air_Engage#AI_AIR_ENGAGE --- The AI_A2A_CAP class implements the core functions to patrol a @{Zone} by an AI @{Wrapper.Group} or @{Wrapper.Group} @@ -97,6 +98,36 @@ AI_A2A_CAP = { ClassName = "AI_A2A_CAP", } +--- Creates a new AI_A2A_CAP object +-- @param #AI_A2A_CAP self +-- @param Wrapper.Group#GROUP AICap +-- @param DCS#Speed EngageMinSpeed The minimum speed of the @{Wrapper.Group} in km/h when engaging a target. +-- @param DCS#Speed EngageMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h when engaging a target. +-- @param DCS#Altitude EngageFloorAltitude The lowest altitude in meters where to execute the engagement. +-- @param DCS#Altitude EngageCeilingAltitude The highest altitude in meters where to execute the engagement. +-- @param DCS#AltitudeType EngageAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to "RADIO". +-- @param Core.Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed. +-- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Group} in km/h. +-- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h. +-- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol. +-- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol. +-- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to "RADIO". +-- @return #AI_A2A_CAP +function AI_A2A_CAP:New2( AICap, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType, PatrolZone, PatrolMinSpeed, PatrolMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType ) + + -- Multiple inheritance ... :-) + local AI_Air = AI_AIR:New( AICap ) + local AI_Air_Patrol = AI_AIR_PATROL:New( AI_Air, AICap, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) -- #AI_AIR_PATROL + local AI_Air_Engage = AI_AIR_ENGAGE:New( AI_Air_Patrol, AICap, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType ) + local self = BASE:Inherit( self, AI_Air_Engage ) + + self:SetFuelThreshold( .2, 60 ) + self:SetDamageThreshold( 0.4 ) + self:SetDisengageRadius( 70000 ) + + return self +end + --- Creates a new AI_A2A_CAP object -- @param #AI_A2A_CAP self -- @param Wrapper.Group#GROUP AICap @@ -111,176 +142,8 @@ AI_A2A_CAP = { -- @return #AI_A2A_CAP function AI_A2A_CAP:New( AICap, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, EngageMinSpeed, EngageMaxSpeed, PatrolAltType ) - -- Inherits from BASE - local self = BASE:Inherit( self, AI_A2A_PATROL:New( AICap, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) ) -- #AI_A2A_CAP + return self:New2( AICap, EngageMinSpeed, EngageMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolZone, PatrolMinSpeed, PatrolMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType, PatrolAltType ) - self.Accomplished = false - self.Engaging = false - - self.EngageMinSpeed = EngageMinSpeed - self.EngageMaxSpeed = EngageMaxSpeed - - self:AddTransition( { "Patrolling", "Engaging", "Returning", "Airborne" }, "Engage", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_A2A_CAP. - - --- OnBefore Transition Handler for Event Engage. - -- @function [parent=#AI_A2A_CAP] OnBeforeEngage - -- @param #AI_A2A_CAP self - -- @param Wrapper.Group#GROUP AICap The Group Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event Engage. - -- @function [parent=#AI_A2A_CAP] OnAfterEngage - -- @param #AI_A2A_CAP self - -- @param Wrapper.Group#GROUP AICap The Group Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- Synchronous Event Trigger for Event Engage. - -- @function [parent=#AI_A2A_CAP] Engage - -- @param #AI_A2A_CAP self - - --- Asynchronous Event Trigger for Event Engage. - -- @function [parent=#AI_A2A_CAP] __Engage - -- @param #AI_A2A_CAP self - -- @param #number Delay The delay in seconds. - ---- OnLeave Transition Handler for State Engaging. --- @function [parent=#AI_A2A_CAP] OnLeaveEngaging --- @param #AI_A2A_CAP self --- @param Wrapper.Group#GROUP AICap The Group Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. --- @return #boolean Return false to cancel Transition. - ---- OnEnter Transition Handler for State Engaging. --- @function [parent=#AI_A2A_CAP] OnEnterEngaging --- @param #AI_A2A_CAP self --- @param Wrapper.Group#GROUP AICap The Group Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. - - self:AddTransition( "Engaging", "Fired", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_A2A_CAP. - - --- OnBefore Transition Handler for Event Fired. - -- @function [parent=#AI_A2A_CAP] OnBeforeFired - -- @param #AI_A2A_CAP self - -- @param Wrapper.Group#GROUP AICap The Group Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event Fired. - -- @function [parent=#AI_A2A_CAP] OnAfterFired - -- @param #AI_A2A_CAP self - -- @param Wrapper.Group#GROUP AICap The Group Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- Synchronous Event Trigger for Event Fired. - -- @function [parent=#AI_A2A_CAP] Fired - -- @param #AI_A2A_CAP self - - --- Asynchronous Event Trigger for Event Fired. - -- @function [parent=#AI_A2A_CAP] __Fired - -- @param #AI_A2A_CAP self - -- @param #number Delay The delay in seconds. - - self:AddTransition( "*", "Destroy", "*" ) -- FSM_CONTROLLABLE Transition for type #AI_A2A_CAP. - - --- OnBefore Transition Handler for Event Destroy. - -- @function [parent=#AI_A2A_CAP] OnBeforeDestroy - -- @param #AI_A2A_CAP self - -- @param Wrapper.Group#GROUP AICap The Group Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event Destroy. - -- @function [parent=#AI_A2A_CAP] OnAfterDestroy - -- @param #AI_A2A_CAP self - -- @param Wrapper.Group#GROUP AICap The Group Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- Synchronous Event Trigger for Event Destroy. - -- @function [parent=#AI_A2A_CAP] Destroy - -- @param #AI_A2A_CAP self - - --- Asynchronous Event Trigger for Event Destroy. - -- @function [parent=#AI_A2A_CAP] __Destroy - -- @param #AI_A2A_CAP self - -- @param #number Delay The delay in seconds. - - - self:AddTransition( "Engaging", "Abort", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_A2A_CAP. - - --- OnBefore Transition Handler for Event Abort. - -- @function [parent=#AI_A2A_CAP] OnBeforeAbort - -- @param #AI_A2A_CAP self - -- @param Wrapper.Group#GROUP AICap The Group Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event Abort. - -- @function [parent=#AI_A2A_CAP] OnAfterAbort - -- @param #AI_A2A_CAP self - -- @param Wrapper.Group#GROUP AICap The Group Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- Synchronous Event Trigger for Event Abort. - -- @function [parent=#AI_A2A_CAP] Abort - -- @param #AI_A2A_CAP self - - --- Asynchronous Event Trigger for Event Abort. - -- @function [parent=#AI_A2A_CAP] __Abort - -- @param #AI_A2A_CAP self - -- @param #number Delay The delay in seconds. - - self:AddTransition( "Engaging", "Accomplish", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_A2A_CAP. - - --- OnBefore Transition Handler for Event Accomplish. - -- @function [parent=#AI_A2A_CAP] OnBeforeAccomplish - -- @param #AI_A2A_CAP self - -- @param Wrapper.Group#GROUP AICap The Group Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event Accomplish. - -- @function [parent=#AI_A2A_CAP] OnAfterAccomplish - -- @param #AI_A2A_CAP self - -- @param Wrapper.Group#GROUP AICap The Group Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- Synchronous Event Trigger for Event Accomplish. - -- @function [parent=#AI_A2A_CAP] Accomplish - -- @param #AI_A2A_CAP self - - --- Asynchronous Event Trigger for Event Accomplish. - -- @function [parent=#AI_A2A_CAP] __Accomplish - -- @param #AI_A2A_CAP self - -- @param #number Delay The delay in seconds. - - self:AddTransition( { "Patrolling", "Engaging" }, "Refuel", "Refuelling" ) - - return self end --- onafter State Transition for Event Patrol. @@ -291,7 +154,7 @@ end -- @param #string To The To State string. function AI_A2A_CAP:onafterStart( AICap, From, Event, To ) - self:GetParent( self ).onafterStart( self, AICap, From, Event, To ) + self:GetParent( self, AI_A2A_CAP ).onafterStart( self, AICap, From, Event, To ) AICap:HandleEvent( EVENTS.Takeoff, nil, self ) end @@ -324,168 +187,26 @@ function AI_A2A_CAP:SetEngageRange( EngageRange ) end end ---- onafter State Transition for Event Patrol. +--- Evaluate the attack and create an AttackUnitTask list. -- @param #AI_A2A_CAP self --- @param Wrapper.Group#GROUP AICap The AI Group managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. -function AI_A2A_CAP:onafterPatrol( AICap, From, Event, To ) +-- @param Core.Set#SET_UNIT AttackSetUnit The set of units to attack. +-- @param Wrappper.Group#GROUP DefenderGroup The group of defenders. +-- @param #number EngageAltitude The altitude to engage the targets. +-- @return #AI_A2A_CAP self +function AI_A2A_CAP:CreateAttackUnitTasks( AttackSetUnit, DefenderGroup, EngageAltitude ) - -- Call the parent Start event handler - self:GetParent(self).onafterPatrol( self, AICap, From, Event, To ) - self:HandleEvent( EVENTS.Dead ) + local AttackUnitTasks = {} -end - --- todo: need to fix this global function - ---- @param Wrapper.Group#GROUP AICap -function AI_A2A_CAP.AttackRoute( AICap, Fsm ) - - AICap:F( { "AI_A2A_CAP.AttackRoute:", AICap:GetName() } ) - - if AICap:IsAlive() then - Fsm:__Engage( 0.5 ) - end -end - ---- @param #AI_A2A_CAP self --- @param Wrapper.Group#GROUP AICap The Group Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. -function AI_A2A_CAP:onbeforeEngage( AICap, From, Event, To ) - - if self.Accomplished == true then - return false - end -end - ---- @param #AI_A2A_CAP self --- @param Wrapper.Group#GROUP AICap The AI Group managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. -function AI_A2A_CAP:onafterAbort( AICap, From, Event, To ) - AICap:ClearTasks() - self:__Route( 0.5 ) -end - - ---- @param #AI_A2A_CAP self --- @param Wrapper.Group#GROUP AICap The AICap Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. -function AI_A2A_CAP:onafterEngage( AICap, From, Event, To, AttackSetUnit ) - - self:F( { AICap, From, Event, To, AttackSetUnit} ) - - self.AttackSetUnit = AttackSetUnit or self.AttackSetUnit -- Core.Set#SET_UNIT - - local FirstAttackUnit = self.AttackSetUnit:GetFirst() -- Wrapper.Unit#UNIT - - if FirstAttackUnit and FirstAttackUnit:IsAlive() then -- If there is no attacker anymore, stop the engagement. - - if AICap and AICap:IsAlive() then - - local EngageRoute = {} - - --- Calculate the target route point. - local CurrentCoord = AICap:GetCoordinate() - local ToTargetCoord = self.AttackSetUnit:GetFirst():GetCoordinate() - local ToTargetSpeed = math.random( self.EngageMinSpeed, self.EngageMaxSpeed ) - local ToInterceptAngle = CurrentCoord:GetAngleDegrees( CurrentCoord:GetDirectionVec3( ToTargetCoord ) ) - - --- Create a route point of type air. - local ToPatrolRoutePoint = CurrentCoord:Translate( 5000, ToInterceptAngle ):WaypointAir( - self.PatrolAltType, - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, - ToTargetSpeed, - true - ) - - self:F( { Angle = ToInterceptAngle, ToTargetSpeed = ToTargetSpeed } ) - self:T2( { self.MinSpeed, self.MaxSpeed, ToTargetSpeed } ) - - EngageRoute[#EngageRoute+1] = ToPatrolRoutePoint - EngageRoute[#EngageRoute+1] = ToPatrolRoutePoint - - local AttackTasks = {} - - for AttackUnitID, AttackUnit in pairs( self.AttackSetUnit:GetSet() ) do - local AttackUnit = AttackUnit -- Wrapper.Unit#UNIT - self:T( { "Attacking Unit:", AttackUnit:GetName(), AttackUnit:IsAlive(), AttackUnit:IsAir() } ) - if AttackUnit:IsAlive() and AttackUnit:IsAir() then - -- TODO: Add coalition check? Only attack units of if AttackUnit:GetCoalition()~=AICap:GetCoalition() - -- Maybe the detected set also contains - AttackTasks[#AttackTasks+1] = AICap:TaskAttackUnit( AttackUnit ) - end - end - - if #AttackTasks == 0 then - self:E("No targets found -> Going back to Patrolling") - self:__Abort( 0.5 ) - else - AICap:OptionROEOpenFire() - AICap:OptionROTEvadeFire() - - AttackTasks[#AttackTasks+1] = AICap:TaskFunction( "AI_A2A_CAP.AttackRoute", self ) - EngageRoute[#EngageRoute].task = AICap:TaskCombo( AttackTasks ) - end - - AICap:Route( EngageRoute, 0.5 ) + for AttackUnitID, AttackUnit in pairs( self.AttackSetUnit:GetSet() ) do + local AttackUnit = AttackUnit -- Wrapper.Unit#UNIT + self:T( { "Attacking Unit:", AttackUnit:GetName(), AttackUnit:IsAlive(), AttackUnit:IsAir() } ) + if AttackUnit:IsAlive() and AttackUnit:IsAir() then + -- TODO: Add coalition check? Only attack units of if AttackUnit:GetCoalition()~=AICap:GetCoalition() + -- Maybe the detected set also contains + AttackUnitTasks[#AttackUnitTasks+1] = DefenderGroup:TaskAttackUnit( AttackUnit ) end - else - self:E("No targets found -> Going back to Patrolling") - self:__Abort( 0.5 ) - end -end - ---- @param #AI_A2A_CAP self --- @param Wrapper.Group#GROUP AICap The Group Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. -function AI_A2A_CAP:onafterAccomplish( AICap, From, Event, To ) - self.Accomplished = true - self:SetDetectionOff() -end - ---- @param #AI_A2A_CAP self --- @param Wrapper.Group#GROUP AICap The Group Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. --- @param Core.Event#EVENTDATA EventData -function AI_A2A_CAP:onafterDestroy( AICap, From, Event, To, EventData ) - - if EventData.IniUnit then - self.AttackUnits[EventData.IniUnit] = nil - end -end - ---- @param #AI_A2A_CAP self --- @param Core.Event#EVENTDATA EventData -function AI_A2A_CAP:OnEventDead( EventData ) - self:F( { "EventDead", EventData } ) - - if EventData.IniDCSUnit then - if self.AttackUnits and self.AttackUnits[EventData.IniUnit] then - self:__Destroy( 1, EventData ) - end - end -end - ---- @param Wrapper.Group#GROUP AICap -function AI_A2A_CAP.Resume( AICap, Fsm ) - - AICap:I( { "AI_A2A_CAP.Resume:", AICap:GetName() } ) - if AICap:IsAlive() then - Fsm:__Reset( 1 ) - Fsm:__Route( 5 ) end + return AttackUnitTasks end + diff --git a/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua b/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua index cf59ef0fa..43d74920d 100644 --- a/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua +++ b/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua @@ -1770,13 +1770,87 @@ do -- AI_A2A_DISPATCHER --- Set a CAP for a Squadron. -- @param #AI_A2A_DISPATCHER self -- @param #string SquadronName The squadron name. - -- @param Core.Zone#ZONE_BASE Zone The @{Zone} object derived from @{Core.Zone#ZONE_BASE} that defines the zone wherein the CAP will be executed. - -- @param #number FloorAltitude The minimum altitude at which the cap can be executed. - -- @param #number CeilingAltitude the maximum altitude at which the cap can be executed. - -- @param #number PatrolMinSpeed The minimum speed at which the cap can be executed. - -- @param #number PatrolMaxSpeed The maximum speed at which the cap can be executed. -- @param #number EngageMinSpeed The minimum speed at which the engage can be executed. -- @param #number EngageMaxSpeed The maximum speed at which the engage can be executed. + -- @param DCS#Altitude EngageFloorAltitude The lowest altitude in meters where to execute the engagement. + -- @param DCS#Altitude EngageCeilingAltitude The highest altitude in meters where to execute the engagement. + -- @param #number EngageAltType The altitude type to engage, which is a string "BARO" defining Barometric or "RADIO" defining radio controlled altitude. + -- @param Core.Zone#ZONE_BASE Zone The @{Zone} object derived from @{Core.Zone#ZONE_BASE} that defines the zone wherein the CAP will be executed. + -- @param #number PatrolMinSpeed The minimum speed at which the cap can be executed. + -- @param #number PatrolMaxSpeed The maximum speed at which the cap can be executed. + -- @param #number PatrolFloorAltitude The minimum altitude at which the cap can be executed. + -- @param #number PatrolCeilingAltitude the maximum altitude at which the cap can be executed. + -- @param #number PatrolAltType The altitude type to patrol, which is a string "BARO" defining Barometric or "RADIO" defining radio controlled altitude. + -- @return #AI_A2A_DISPATCHER + -- @usage + -- + -- -- CAP Squadron execution. + -- CAPZoneEast = ZONE_POLYGON:New( "CAP Zone East", GROUP:FindByName( "CAP Zone East" ) ) + -- -- Setup a CAP, engaging between 800 and 900 km/h, altitude 30 (above the sea), radio altitude measurement, + -- -- patrolling speed between 500 and 600 km/h, altitude between 4000 and 10000 meters, barometric altitude measurement. + -- A2ADispatcher:SetSquadronCapV2( "Mineralnye", 800, 900, 30, 30, "RADIO", CAPZoneEast, 500, 600, 4000, 10000, "BARO" ) + -- A2ADispatcher:SetSquadronCapInterval( "Mineralnye", 2, 30, 60, 1 ) + -- + -- CAPZoneWest = ZONE_POLYGON:New( "CAP Zone West", GROUP:FindByName( "CAP Zone West" ) ) + -- -- Setup a CAP, engaging between 800 and 1200 km/h, altitude between 4000 and 10000 meters, radio altitude measurement, + -- -- patrolling speed between 600 and 800 km/h, altitude between 4000 and 8000, barometric altitude measurement. + -- A2ADispatcher:SetSquadronCapV2( "Sochi", 800, 1200, 2000, 3000, "RADIO", CAPZoneWest, 600, 800, 4000, 8000, "BARO" ) + -- A2ADispatcher:SetSquadronCapInterval( "Sochi", 2, 30, 120, 1 ) + -- + -- CAPZoneMiddle = ZONE:New( "CAP Zone Middle") + -- -- Setup a CAP, engaging between 800 and 1200 km/h, altitude between 5000 and 8000 meters, barometric altitude measurement, + -- -- patrolling speed between 600 and 800 km/h, altitude between 4000 and 8000, radio altitude. + -- A2ADispatcher:SetSquadronCapV2( "Maykop", 800, 1200, 5000, 8000, "BARO", CAPZoneMiddle, 600, 800, 4000, 8000, "RADIO" ) + -- A2ADispatcher:SetSquadronCapInterval( "Maykop", 2, 30, 120, 1 ) + -- + function AI_A2A_DISPATCHER:SetSquadronCap2( SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType, Zone, PatrolMinSpeed, PatrolMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType ) + + self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {} + self.DefenderSquadrons[SquadronName].Cap = self.DefenderSquadrons[SquadronName].Cap or {} + + local DefenderSquadron = self:GetSquadron( SquadronName ) + + local Cap = self.DefenderSquadrons[SquadronName].Cap + Cap.Name = SquadronName + Cap.EngageMinSpeed = EngageMinSpeed + Cap.EngageMaxSpeed = EngageMaxSpeed + Cap.EngageFloorAltitude = EngageFloorAltitude + Cap.EngageCeilingAltitude = EngageCeilingAltitude + Cap.Zone = Zone + Cap.PatrolMinSpeed = PatrolMinSpeed + Cap.PatrolMaxSpeed = PatrolMaxSpeed + Cap.PatrolFloorAltitude = PatrolFloorAltitude + Cap.PatrolCeilingAltitude = PatrolCeilingAltitude + Cap.PatrolAltType = PatrolAltType + Cap.EngageAltType = EngageAltType + + self:SetSquadronCapInterval( SquadronName, self.DefenderDefault.CapLimit, self.DefenderDefault.CapMinSeconds, self.DefenderDefault.CapMaxSeconds, 1 ) + + self:I( { CAP = { SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, Zone, PatrolMinSpeed, PatrolMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType, EngageAltType } } ) + + -- Add the CAP to the EWR network. + + local RecceSet = self.Detection:GetDetectionSet() + RecceSet:FilterPrefixes( DefenderSquadron.TemplatePrefixes ) + RecceSet:FilterStart() + + self.Detection:SetFriendlyPrefixes( DefenderSquadron.TemplatePrefixes ) + + return self + end + + --- Set a CAP for a Squadron. + -- @param #AI_A2A_DISPATCHER self + -- @param #string SquadronName The squadron name. + -- @param #number EngageMinSpeed The minimum speed at which the engage can be executed. + -- @param #number EngageMaxSpeed The maximum speed at which the engage can be executed. + -- @param DCS#Altitude EngageFloorAltitude The lowest altitude in meters where to execute the engagement. + -- @param DCS#Altitude EngageCeilingAltitude The highest altitude in meters where to execute the engagement. + -- @param Core.Zone#ZONE_BASE Zone The @{Zone} object derived from @{Core.Zone#ZONE_BASE} that defines the zone wherein the CAP will be executed. + -- @param #number PatrolFloorAltitude The minimum altitude at which the cap can be executed. + -- @param #number PatrolCeilingAltitude the maximum altitude at which the cap can be executed. + -- @param #number PatrolMinSpeed The minimum speed at which the cap can be executed. + -- @param #number PatrolMaxSpeed The maximum speed at which the cap can be executed. -- @param #number AltType The altitude type, which is a string "BARO" defining Barometric or "RADIO" defining radio controlled altitude. -- @return #AI_A2A_DISPATCHER -- @usage @@ -1794,37 +1868,9 @@ do -- AI_A2A_DISPATCHER -- A2ADispatcher:SetSquadronCap( "Maykop", CAPZoneMiddle, 4000, 8000, 600, 800, 800, 1200, "RADIO" ) -- A2ADispatcher:SetSquadronCapInterval( "Sochi", 2, 30, 120, 1 ) -- - function AI_A2A_DISPATCHER:SetSquadronCap( SquadronName, Zone, FloorAltitude, CeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, EngageMinSpeed, EngageMaxSpeed, AltType ) - - self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {} - self.DefenderSquadrons[SquadronName].Cap = self.DefenderSquadrons[SquadronName].Cap or {} - - local DefenderSquadron = self:GetSquadron( SquadronName ) + function AI_A2A_DISPATCHER:SetSquadronCap( SquadronName, Zone, PatrolMinSpeed, PatrolMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, EngageMinSpeed, EngageMaxSpeed, AltType ) - local Cap = self.DefenderSquadrons[SquadronName].Cap - Cap.Name = SquadronName - Cap.Zone = Zone - Cap.FloorAltitude = FloorAltitude - Cap.CeilingAltitude = CeilingAltitude - Cap.PatrolMinSpeed = PatrolMinSpeed - Cap.PatrolMaxSpeed = PatrolMaxSpeed - Cap.EngageMinSpeed = EngageMinSpeed - Cap.EngageMaxSpeed = EngageMaxSpeed - Cap.AltType = AltType - - self:SetSquadronCapInterval( SquadronName, self.DefenderDefault.CapLimit, self.DefenderDefault.CapMinSeconds, self.DefenderDefault.CapMaxSeconds, 1 ) - - self:F( { CAP = { SquadronName, Zone, FloorAltitude, CeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, EngageMinSpeed, EngageMaxSpeed, AltType } } ) - - -- Add the CAP to the EWR network. - - local RecceSet = self.Detection:GetDetectionSet() - RecceSet:FilterPrefixes( DefenderSquadron.TemplatePrefixes ) - RecceSet:FilterStart() - - self.Detection:SetFriendlyPrefixes( DefenderSquadron.TemplatePrefixes ) - - return self + return self:SetSquadronCap2( SquadronName, EngageMinSpeed, EngageMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, AltType, Zone, PatrolMinSpeed, PatrolMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, AltType ) end --- Set the squadron CAP parameters. @@ -2012,6 +2058,37 @@ do -- AI_A2A_DISPATCHER return nil end + --- Set squadron GCI. + -- @param #AI_A2A_DISPATCHER self + -- @param #string SquadronName The squadron name. + -- @param #number EngageMinSpeed The minimum speed [km/h] at which the GCI can be executed. + -- @param #number EngageMaxSpeed The maximum speed [km/h] at which the GCI can be executed. + -- @param DCS#Altitude EngageFloorAltitude The lowest altitude in meters where to execute the engagement. + -- @param DCS#Altitude EngageCeilingAltitude The highest altitude in meters where to execute the engagement. + -- @param DCS#AltitudeType EngageAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to "RADIO". + -- @usage + -- + -- -- GCI Squadron execution. + -- A2ADispatcher:SetSquadronGci2( "Mozdok", 900, 1200, 5000, 5000, "BARO" ) + -- A2ADispatcher:SetSquadronGci2( "Novo", 900, 2100, 30, 30, "RADIO" ) + -- A2ADispatcher:SetSquadronGci2( "Maykop", 900, 1200, 100, 300, "RADIO" ) + -- + -- @return #AI_A2A_DISPATCHER + function AI_A2A_DISPATCHER:SetSquadronGci2( SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType ) + + self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {} + self.DefenderSquadrons[SquadronName].Gci = self.DefenderSquadrons[SquadronName].Gci or {} + + local Intercept = self.DefenderSquadrons[SquadronName].Gci + Intercept.Name = SquadronName + Intercept.EngageMinSpeed = EngageMinSpeed + Intercept.EngageMaxSpeed = EngageMaxSpeed + Intercept.EngageFloorAltitude = EngageFloorAltitude + Intercept.EngageCeilingAltitude = EngageCeilingAltitude + Intercept.EngageAltType = EngageAltType + + self:I( { GCI = { SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType } } ) + end --- Set squadron GCI. -- @param #AI_A2A_DISPATCHER self @@ -3080,7 +3157,7 @@ do -- AI_A2A_DISPATCHER if DefenderCAP then - local Fsm = AI_A2A_CAP:New( DefenderCAP, Cap.Zone, Cap.FloorAltitude, Cap.CeilingAltitude, Cap.PatrolMinSpeed, Cap.PatrolMaxSpeed, Cap.EngageMinSpeed, Cap.EngageMaxSpeed, Cap.AltType ) + local Fsm = AI_A2A_CAP:New2( DefenderCAP, Cap.EngageMinSpeed, Cap.EngageMaxSpeed, Cap.EngageFloorAltitude, Cap.EngageCeilingAltitude, Cap.EngageAltType, Cap.Zone, Cap.PatrolMinSpeed, Cap.PatrolMaxSpeed, Cap.PatrolFloorAltitude, Cap.PatrolCeilingAltitude, Cap.PatrolAltType ) Fsm:SetDispatcher( self ) Fsm:SetHomeAirbase( DefenderSquadron.Airbase ) Fsm:SetFuelThreshold( DefenderSquadron.FuelThreshold or self.DefenderDefault.FuelThreshold, 60 ) @@ -3101,7 +3178,7 @@ do -- AI_A2A_DISPATCHER self:SetDefenderTask( SquadronName, DefenderCAP, "CAP", Fsm ) function Fsm:onafterTakeoff( DefenderGroup, From, Event, To ) - self:F({"CAP Birth", DefenderGroup:GetName()}) + self:F({"CAP Takeoff", DefenderGroup:GetName()}) --self:GetParent(self).onafterBirth( self, Defender, From, Event, To ) local DefenderName = DefenderGroup:GetCallsign() @@ -3113,7 +3190,7 @@ do -- AI_A2A_DISPATCHER Fsm:__Patrol( 2 ) -- Start Patrolling end end - + function Fsm:onafterRTB( DefenderGroup, From, Event, To ) self:F({"CAP RTB", DefenderGroup:GetName()}) @@ -3169,7 +3246,7 @@ do -- AI_A2A_DISPATCHER for DefenderID, Defender in pairs( Defenders ) do local Fsm = self:GetDefenderTaskFsm( Defender ) - Fsm:__Engage( 1, AttackerDetection.Set ) -- Engage on the TargetSetUnit + Fsm:__EngageRoute( 1, AttackerDetection.Set ) -- Engage on the TargetSetUnit self:SetDefenderTaskTarget( Defender, AttackerDetection ) @@ -3201,7 +3278,7 @@ do -- AI_A2A_DISPATCHER for DefenderID, DefenderGroup in pairs( DefenderFriendlies or {} ) do local Fsm = self:GetDefenderTaskFsm( DefenderGroup ) - Fsm:__Engage( 1, AttackerSet ) -- Engage on the TargetSetUnit + Fsm:__EngageRoute( 1, AttackerSet ) -- Engage on the TargetSetUnit self:SetDefenderTaskTarget( DefenderGroup, AttackerDetection ) @@ -3283,7 +3360,7 @@ do -- AI_A2A_DISPATCHER DefenderCount = DefenderCount - DefenderGrouping / DefenderOverhead - local Fsm = AI_A2A_GCI:New( DefenderGCI, Gci.EngageMinSpeed, Gci.EngageMaxSpeed ) + local Fsm = AI_A2A_GCI:New2( DefenderGCI, Gci.EngageMinSpeed, Gci.EngageMaxSpeed, Gci.EngageFloorAltitude, Gci.EngageCeilingAltitude, Gci.EngageAltType ) Fsm:SetDispatcher( self ) Fsm:SetHomeAirbase( DefenderSquadron.Airbase ) Fsm:SetFuelThreshold( DefenderSquadron.FuelThreshold or self.DefenderDefault.FuelThreshold, 60 ) @@ -3306,10 +3383,27 @@ do -- AI_A2A_DISPATCHER if DefenderTarget then Dispatcher:MessageToPlayers( DefenderName .. " wheels up.", DefenderGroup ) - Fsm:__Engage( 2, DefenderTarget.Set ) -- Engage on the TargetSetUnit + --Fsm:__Engage( 2, DefenderTarget.Set ) -- Engage on the TargetSetUnit + Fsm:EngageRoute( DefenderTarget.Set ) -- Engage on the TargetSetUnit end end + function Fsm:onafterEngageRoute( DefenderGroup, From, Event, To, AttackSetUnit ) + self:F({"GCI Route", DefenderGroup:GetName()}) + + local DefenderName = DefenderGroup:GetCallsign() + local Dispatcher = Fsm:GetDispatcher() -- #AI_A2A_DISPATCHER + local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) + + if Squadron then + local FirstUnit = AttackSetUnit:GetFirst() + local Coordinate = FirstUnit:GetCoordinate() -- Core.Point#COORDINATE + + Dispatcher:MessageToPlayers( DefenderName .. ", intercepting bogeys at " .. Coordinate:ToStringA2A( DefenderGroup ), DefenderGroup ) + end + self:GetParent( Fsm ).onafterEngageRoute( self, DefenderGroup, From, Event, To, AttackSetUnit ) + end + function Fsm:onafterRTB( DefenderGroup, From, Event, To ) self:F({"GCI RTB", DefenderGroup:GetName()}) self:GetParent(self).onafterRTB( self, DefenderGroup, From, Event, To ) diff --git a/Moose Development/Moose/AI/AI_A2A_Gci.lua b/Moose Development/Moose/AI/AI_A2A_Gci.lua index a6642a9ce..f0ddb1d13 100644 --- a/Moose Development/Moose/AI/AI_A2A_Gci.lua +++ b/Moose Development/Moose/AI/AI_A2A_Gci.lua @@ -105,185 +105,39 @@ AI_A2A_GCI = { --- Creates a new AI_A2A_GCI object -- @param #AI_A2A_GCI self -- @param Wrapper.Group#GROUP AIIntercept +-- @param DCS#Speed EngageMinSpeed The minimum speed of the @{Wrapper.Group} in km/h when engaging a target. +-- @param DCS#Speed EngageMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h when engaging a target. +-- @param DCS#Altitude EngageFloorAltitude The lowest altitude in meters where to execute the engagement. +-- @param DCS#Altitude EngageCeilingAltitude The highest altitude in meters where to execute the engagement. +-- @param DCS#AltitudeType EngageAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to "RADIO". -- @return #AI_A2A_GCI -function AI_A2A_GCI:New( AIIntercept, EngageMinSpeed, EngageMaxSpeed ) +function AI_A2A_GCI:New2( AIIntercept, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType ) - -- Inherits from BASE - local self = BASE:Inherit( self, AI_A2A:New( AIIntercept ) ) -- #AI_A2A_GCI - - self.Accomplished = false - self.Engaging = false + local AI_Air = AI_AIR:New( AIIntercept ) + local AI_Air_Engage = AI_AIR_ENGAGE:New( AI_Air, AIIntercept, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType ) + local self = BASE:Inherit( self, AI_Air_Engage ) -- #AI_A2A_GCI - self.EngageMinSpeed = EngageMinSpeed - self.EngageMaxSpeed = EngageMaxSpeed - self.PatrolMinSpeed = EngageMinSpeed - self.PatrolMaxSpeed = EngageMaxSpeed - - self.PatrolAltType = "RADIO" - - self:AddTransition( { "Started", "Engaging", "Returning", "Airborne" }, "Engage", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_A2A_GCI. - - --- OnBefore Transition Handler for Event Engage. - -- @function [parent=#AI_A2A_GCI] OnBeforeEngage - -- @param #AI_A2A_GCI self - -- @param Wrapper.Group#GROUP AIIntercept The Group Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event Engage. - -- @function [parent=#AI_A2A_GCI] OnAfterEngage - -- @param #AI_A2A_GCI self - -- @param Wrapper.Group#GROUP AIIntercept The Group Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- Synchronous Event Trigger for Event Engage. - -- @function [parent=#AI_A2A_GCI] Engage - -- @param #AI_A2A_GCI self - - --- Asynchronous Event Trigger for Event Engage. - -- @function [parent=#AI_A2A_GCI] __Engage - -- @param #AI_A2A_GCI self - -- @param #number Delay The delay in seconds. - ---- OnLeave Transition Handler for State Engaging. --- @function [parent=#AI_A2A_GCI] OnLeaveEngaging --- @param #AI_A2A_GCI self --- @param Wrapper.Group#GROUP AIIntercept The Group Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. --- @return #boolean Return false to cancel Transition. - ---- OnEnter Transition Handler for State Engaging. --- @function [parent=#AI_A2A_GCI] OnEnterEngaging --- @param #AI_A2A_GCI self --- @param Wrapper.Group#GROUP AIIntercept The Group Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. - - self:AddTransition( "Engaging", "Fired", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_A2A_GCI. - - --- OnBefore Transition Handler for Event Fired. - -- @function [parent=#AI_A2A_GCI] OnBeforeFired - -- @param #AI_A2A_GCI self - -- @param Wrapper.Group#GROUP AIIntercept The Group Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event Fired. - -- @function [parent=#AI_A2A_GCI] OnAfterFired - -- @param #AI_A2A_GCI self - -- @param Wrapper.Group#GROUP AIIntercept The Group Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- Synchronous Event Trigger for Event Fired. - -- @function [parent=#AI_A2A_GCI] Fired - -- @param #AI_A2A_GCI self - - --- Asynchronous Event Trigger for Event Fired. - -- @function [parent=#AI_A2A_GCI] __Fired - -- @param #AI_A2A_GCI self - -- @param #number Delay The delay in seconds. - - self:AddTransition( "*", "Destroy", "*" ) -- FSM_CONTROLLABLE Transition for type #AI_A2A_GCI. - - --- OnBefore Transition Handler for Event Destroy. - -- @function [parent=#AI_A2A_GCI] OnBeforeDestroy - -- @param #AI_A2A_GCI self - -- @param Wrapper.Group#GROUP AIIntercept The Group Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event Destroy. - -- @function [parent=#AI_A2A_GCI] OnAfterDestroy - -- @param #AI_A2A_GCI self - -- @param Wrapper.Group#GROUP AIIntercept The Group Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- Synchronous Event Trigger for Event Destroy. - -- @function [parent=#AI_A2A_GCI] Destroy - -- @param #AI_A2A_GCI self - - --- Asynchronous Event Trigger for Event Destroy. - -- @function [parent=#AI_A2A_GCI] __Destroy - -- @param #AI_A2A_GCI self - -- @param #number Delay The delay in seconds. - - - self:AddTransition( "Engaging", "Abort", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_A2A_GCI. - - --- OnBefore Transition Handler for Event Abort. - -- @function [parent=#AI_A2A_GCI] OnBeforeAbort - -- @param #AI_A2A_GCI self - -- @param Wrapper.Group#GROUP AIIntercept The Group Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event Abort. - -- @function [parent=#AI_A2A_GCI] OnAfterAbort - -- @param #AI_A2A_GCI self - -- @param Wrapper.Group#GROUP AIIntercept The Group Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- Synchronous Event Trigger for Event Abort. - -- @function [parent=#AI_A2A_GCI] Abort - -- @param #AI_A2A_GCI self - - --- Asynchronous Event Trigger for Event Abort. - -- @function [parent=#AI_A2A_GCI] __Abort - -- @param #AI_A2A_GCI self - -- @param #number Delay The delay in seconds. - - self:AddTransition( "Engaging", "Accomplish", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_A2A_GCI. - - --- OnBefore Transition Handler for Event Accomplish. - -- @function [parent=#AI_A2A_GCI] OnBeforeAccomplish - -- @param #AI_A2A_GCI self - -- @param Wrapper.Group#GROUP AIIntercept The Group Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - -- @return #boolean Return false to cancel Transition. - - --- OnAfter Transition Handler for Event Accomplish. - -- @function [parent=#AI_A2A_GCI] OnAfterAccomplish - -- @param #AI_A2A_GCI self - -- @param Wrapper.Group#GROUP AIIntercept The Group Object managed by the FSM. - -- @param #string From The From State string. - -- @param #string Event The Event string. - -- @param #string To The To State string. - - --- Synchronous Event Trigger for Event Accomplish. - -- @function [parent=#AI_A2A_GCI] Accomplish - -- @param #AI_A2A_GCI self - - --- Asynchronous Event Trigger for Event Accomplish. - -- @function [parent=#AI_A2A_GCI] __Accomplish - -- @param #AI_A2A_GCI self - -- @param #number Delay The delay in seconds. - - self:AddTransition( { "Patrolling", "Engaging" }, "Refuel", "Refuelling" ) + self:SetFuelThreshold( .2, 60 ) + self:SetDamageThreshold( 0.4 ) + self:SetDisengageRadius( 70000 ) return self end +--- Creates a new AI_A2A_GCI object +-- @param #AI_A2A_GCI self +-- @param Wrapper.Group#GROUP AIIntercept +-- @param DCS#Speed EngageMinSpeed The minimum speed of the @{Wrapper.Group} in km/h when engaging a target. +-- @param DCS#Speed EngageMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h when engaging a target. +-- @param DCS#Altitude EngageFloorAltitude The lowest altitude in meters where to execute the engagement. +-- @param DCS#Altitude EngageCeilingAltitude The highest altitude in meters where to execute the engagement. +-- @param DCS#AltitudeType EngageAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to "RADIO". +-- @return #AI_A2A_GCI +function AI_A2A_GCI:New( AIIntercept, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType ) + + return self:New2( AIIntercept, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType ) +end + --- onafter State Transition for Event Patrol. -- @param #AI_A2A_GCI self -- @param Wrapper.Group#GROUP AIIntercept The AI Group managed by the FSM. @@ -292,173 +146,33 @@ end -- @param #string To The To State string. function AI_A2A_GCI:onafterStart( AIIntercept, From, Event, To ) - self:GetParent( self ).onafterStart( self, AIIntercept, From, Event, To ) - AIIntercept:HandleEvent( EVENTS.Takeoff, nil, self ) - + self:GetParent( self, AI_A2A_GCI ).onafterStart( self, AIIntercept, From, Event, To ) end - ---- onafter State Transition for Event Patrol. +--- Evaluate the attack and create an AttackUnitTask list. -- @param #AI_A2A_GCI self --- @param Wrapper.Group#GROUP AIIntercept The AI Group managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. -function AI_A2A_GCI:onafterEngage( AIIntercept, From, Event, To ) +-- @param Core.Set#SET_UNIT AttackSetUnit The set of units to attack. +-- @param Wrappper.Group#GROUP DefenderGroup The group of defenders. +-- @param #number EngageAltitude The altitude to engage the targets. +-- @return #AI_A2A_GCI self +function AI_A2A_GCI:CreateAttackUnitTasks( AttackSetUnit, DefenderGroup, EngageAltitude ) - self:HandleEvent( EVENTS.Dead ) + local AttackUnitTasks = {} -end - --- todo: need to fix this global function - ---- @param Wrapper.Group#GROUP AIControllable -function AI_A2A_GCI.InterceptRoute( AIIntercept, Fsm ) - - AIIntercept:F( { "AI_A2A_GCI.InterceptRoute:", AIIntercept:GetName() } ) - - if AIIntercept:IsAlive() then - Fsm:__Engage( 0.5 ) - - --local Task = AIIntercept:TaskOrbitCircle( 4000, 400 ) - --AIIntercept:SetTask( Task ) - end -end - ---- @param #AI_A2A_GCI self --- @param Wrapper.Group#GROUP AIIntercept The Group Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. -function AI_A2A_GCI:onbeforeEngage( AIIntercept, From, Event, To ) - - if self.Accomplished == true then - return false - end -end - ---- @param #AI_A2A_GCI self --- @param Wrapper.Group#GROUP AIIntercept The AI Group managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. -function AI_A2A_GCI:onafterAbort( AIIntercept, From, Event, To ) - AIIntercept:ClearTasks() - self:Return() - self:__RTB( 0.5 ) -end - - ---- @param #AI_A2A_GCI self --- @param Wrapper.Group#GROUP AIIntercept The GroupGroup managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. -function AI_A2A_GCI:onafterEngage( AIIntercept, From, Event, To, AttackSetUnit ) - - self:F( { AIIntercept, From, Event, To, AttackSetUnit} ) - - self.AttackSetUnit = AttackSetUnit or self.AttackSetUnit -- Core.Set#SET_UNIT - - local FirstAttackUnit = self.AttackSetUnit:GetFirst() - - if FirstAttackUnit and FirstAttackUnit:IsAlive() then - - if AIIntercept:IsAlive() then - - local EngageRoute = {} - - local CurrentCoord = AIIntercept:GetCoordinate() - - --- Calculate the target route point. - - local CurrentCoord = AIIntercept:GetCoordinate() - - local ToTargetCoord = self.AttackSetUnit:GetFirst():GetCoordinate() - self:SetTargetDistance( ToTargetCoord ) -- For RTB status check - - local ToTargetSpeed = math.random( self.EngageMinSpeed, self.EngageMaxSpeed ) - local ToInterceptAngle = CurrentCoord:GetAngleDegrees( CurrentCoord:GetDirectionVec3( ToTargetCoord ) ) - - --- Create a route point of type air. - local ToPatrolRoutePoint = CurrentCoord:Translate( 15000, ToInterceptAngle ):WaypointAir( - self.PatrolAltType, - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, - ToTargetSpeed, - true - ) - - self:F( { Angle = ToInterceptAngle, ToTargetSpeed = ToTargetSpeed } ) - self:F( { self.EngageMinSpeed, self.EngageMaxSpeed, ToTargetSpeed } ) - - EngageRoute[#EngageRoute+1] = ToPatrolRoutePoint - EngageRoute[#EngageRoute+1] = ToPatrolRoutePoint - - local AttackTasks = {} - - for AttackUnitID, AttackUnit in pairs( self.AttackSetUnit:GetSet() ) do - local AttackUnit = AttackUnit -- Wrapper.Unit#UNIT - if AttackUnit:IsAlive() and AttackUnit:IsAir() then - self:T( { "Intercepting Unit:", AttackUnit:GetName(), AttackUnit:IsAlive(), AttackUnit:IsAir() } ) - AttackTasks[#AttackTasks+1] = AIIntercept:TaskAttackUnit( AttackUnit ) - end - end - - if #AttackTasks == 0 then - self:E("No targets found -> Going RTB") - self:Return() - self:__RTB( 0.5 ) - else - AIIntercept:OptionROEOpenFire() - AIIntercept:OptionROTEvadeFire() - - AttackTasks[#AttackTasks+1] = AIIntercept:TaskFunction( "AI_A2A_GCI.InterceptRoute", self ) - EngageRoute[#EngageRoute].task = AIIntercept:TaskCombo( AttackTasks ) - end - - AIIntercept:Route( EngageRoute, 0.5 ) - + for AttackUnitID, AttackUnit in pairs( self.AttackSetUnit:GetSet() ) do + local AttackUnit = AttackUnit -- Wrapper.Unit#UNIT + self:T( { "Attacking Unit:", AttackUnit:GetName(), AttackUnit:IsAlive(), AttackUnit:IsAir() } ) + if AttackUnit:IsAlive() and AttackUnit:IsAir() then + -- TODO: Add coalition check? Only attack units of if AttackUnit:GetCoalition()~=AICap:GetCoalition() + -- Maybe the detected set also contains + AttackUnitTasks[#AttackUnitTasks+1] = DefenderGroup:TaskAttackUnit( AttackUnit ) end - else - self:E("No targets found -> Going RTB") - self:Return() - self:__RTB( 0.5 ) end + + return AttackUnitTasks end ---- @param #AI_A2A_GCI self --- @param Wrapper.Group#GROUP AIIntercept The Group Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. -function AI_A2A_GCI:onafterAccomplish( AIIntercept, From, Event, To ) - self.Accomplished = true - self:SetDetectionOff() -end ---- @param #AI_A2A_GCI self --- @param Wrapper.Group#GROUP AIIntercept The Group Object managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. --- @param Core.Event#EVENTDATA EventData -function AI_A2A_GCI:onafterDestroy( AIIntercept, From, Event, To, EventData ) - if EventData.IniUnit then - self.AttackUnits[EventData.IniUnit] = nil - end -end ---- @param #AI_A2A_GCI self --- @param Core.Event#EVENTDATA EventData -function AI_A2A_GCI:OnEventDead( EventData ) - self:F( { "EventDead", EventData } ) - - if EventData.IniDCSUnit then - if self.AttackUnits and self.AttackUnits[EventData.IniUnit] then - self:__Destroy( 1, EventData ) - end - end -end diff --git a/Moose Development/Moose/AI/AI_A2A_Patrol.lua b/Moose Development/Moose/AI/AI_A2A_Patrol.lua index 6b1315440..9eedd29cc 100644 --- a/Moose Development/Moose/AI/AI_A2A_Patrol.lua +++ b/Moose Development/Moose/AI/AI_A2A_Patrol.lua @@ -136,8 +136,14 @@ AI_A2A_PATROL = { -- PatrolArea = AI_A2A_PATROL:New( PatrolZone, 3000, 6000, 600, 900 ) function AI_A2A_PATROL:New( AIPatrol, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) - -- Inherits from BASE - local self = BASE:Inherit( self, AI_A2A:New( AIPatrol ) ) -- #AI_A2A_PATROL + local AI_Air = AI_AIR:New( AIPatrol ) + local AI_Air_Patrol = AI_A2A_PATROL:New( AI_Air, AIPatrol, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) + local self = BASE:Inherit( self, AI_Air_Patrol ) -- #AI_A2A_PATROL + + self:SetFuelThreshold( .2, 60 ) + self:SetDamageThreshold( 0.4 ) + self:SetDisengageRadius( 70000 ) + self.PatrolZone = PatrolZone self.PatrolFloorAltitude = PatrolFloorAltitude @@ -257,35 +263,6 @@ function AI_A2A_PATROL:SetAltitude( PatrolFloorAltitude, PatrolCeilingAltitude ) self.PatrolCeilingAltitude = PatrolCeilingAltitude end ---- Set race track parameters. CAP flights will perform race track patterns rather than randomly patrolling the zone. --- @param #AI_A2A_PATROL self --- @param #number LegMin Min Length of the race track leg in meters. Default 10,000 m. --- @param #number LegMax Max length of the race track leg in meters. Default 15,000 m. --- @param #number HeadingMin Min heading of the race track in degrees. Default 0 deg, i.e. from South to North. --- @param #number HeadingMax Max heading of the race track in degrees. Default 180 deg, i.e. from South to North. --- @param #number DurationMin (Optional) Min duration before switching the orbit position. Default is keep same orbit until RTB or engage. --- @param #number DurationMax (Optional) Max duration before switching the orbit position. Default is keep same orbit until RTB or engage. --- @param #table CapCoordinates Table of coordinates of first race track point. Second point is determined by leg length and heading. --- @return #AI_A2A_PATROL self -function AI_A2A_PATROL:SetRaceTrackPattern(LegMin, LegMax, HeadingMin, HeadingMax, DurationMin, DurationMax, CapCoordinates) - self:F2({leglength, duration}) - - self.racetrack=true - self.racetracklegmin=LegMin or 10000 - self.racetracklegmax=LegMax or 15000 - self.racetrackheadingmin=HeadingMin or 0 - self.racetrackheadingmax=HeadingMax or 180 - self.racetrackdurationmin=DurationMin - self.racetrackdurationmax=DurationMax - - if self.racetrackdurationmax and not self.racetrackdurationmin then - self.racetrackdurationmin=self.racetrackdurationmax - end - - self.racetrackcapcoordinates=CapCoordinates - -end - --- Defines a new patrol route using the @{Process_PatrolZone} parameters and settings. -- @param #AI_A2A_PATROL self @@ -426,13 +403,3 @@ function AI_A2A_PATROL:onafterRoute( AIPatrol, From, Event, To ) end ---- @param Wrapper.Group#GROUP AIPatrol -function AI_A2A_PATROL.Resume( AIPatrol, Fsm ) - - AIPatrol:I( { "AI_A2A_PATROL.Resume:", AIPatrol:GetName() } ) - if AIPatrol:IsAlive() then - Fsm:__Reset( 1 ) - Fsm:__Route( 5 ) - end - -end diff --git a/Moose Development/Moose/AI/AI_A2G.lua b/Moose Development/Moose/AI/AI_A2G.lua deleted file mode 100644 index b80eb8796..000000000 --- a/Moose Development/Moose/AI/AI_A2G.lua +++ /dev/null @@ -1,69 +0,0 @@ ---- **AI** -- Models the process of air to ground operations for airplanes and helicopters. --- --- === --- --- ### Author: **FlightControl** --- --- === --- --- @module AI.AI_A2G --- @image AI_Air_To_Ground_Dispatching.JPG - ---- @type AI_A2G --- @extends AI.AI_Air#AI_AIR - ---- The AI_A2G class implements the core functions to operate an AI @{Wrapper.Group} A2G tasking. --- --- --- # 1) AI_A2G constructor --- --- * @{#AI_A2G.New}(): Creates a new AI_A2G object. --- --- # 2) AI_A2G is a Finite State Machine. --- --- This section must be read as follows. Each of the rows indicate a state transition, triggered through an event, and with an ending state of the event was executed. --- The first column is the **From** state, the second column the **Event**, and the third column the **To** state. --- --- So, each of the rows have the following structure. --- --- * **From** => **Event** => **To** --- --- Important to know is that an event can only be executed if the **current state** is the **From** state. --- This, when an **Event** that is being triggered has a **From** state that is equal to the **Current** state of the state machine, the event will be executed, --- and the resulting state will be the **To** state. --- --- These are the different possible state transitions of this state machine implementation: --- --- * Idle => Start => Monitoring --- --- ## 2.1) AI_A2G States. --- --- * **Idle**: The process is idle. --- --- ## 2.2) AI_A2G Events. --- --- * **Start**: Start the transport process. --- * **Stop**: Stop the transport process. --- * **Monitor**: Monitor and take action. --- --- @field #AI_A2G -AI_A2G = { - ClassName = "AI_A2G", -} - ---- Creates a new AI_A2G process. --- @param #AI_A2G self --- @param Wrapper.Group#GROUP AIGroup The group object to receive the A2G Process. --- @return #AI_A2G -function AI_A2G:New( AIGroup ) - - -- Inherits from BASE - local self = BASE:Inherit( self, AI_AIR:New( AIGroup ) ) -- #AI_A2G - - self:SetFuelThreshold( .2, 60 ) - self:SetDamageThreshold( 0.95 ) - self:SetDisengageRadius( 70000 ) - - return self -end - diff --git a/Moose Development/Moose/AI/AI_A2G_BAI.lua b/Moose Development/Moose/AI/AI_A2G_BAI.lua index ac8efbb4f..8b8b8e880 100644 --- a/Moose Development/Moose/AI/AI_A2G_BAI.lua +++ b/Moose Development/Moose/AI/AI_A2G_BAI.lua @@ -35,6 +35,7 @@ AI_A2G_BAI = { -- @param DCS#Speed EngageMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h when engaging a target. -- @param DCS#Altitude EngageFloorAltitude The lowest altitude in meters where to execute the engagement. -- @param DCS#Altitude EngageCeilingAltitude The highest altitude in meters where to execute the engagement. +-- @param DCS#AltitudeType EngageAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to "RADIO". -- @param Core.Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed. -- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol. -- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol. @@ -42,10 +43,12 @@ AI_A2G_BAI = { -- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h. -- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO -- @return #AI_A2G_BAI -function AI_A2G_BAI:New( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) +function AI_A2G_BAI:New2( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) - -- Inherits from BASE - local self = BASE:Inherit( self, AI_A2G_PATROL:New( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) ) -- #AI_A2G_BAI + local AI_Air = AI_AIR:New( AIGroup ) + local AI_Air_Patrol = AI_AIR_PATROL:New( AI_Air, AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) -- #AI_AIR_PATROL + local AI_Air_Engage = AI_AIR_ENGAGE:New( AI_Air_Patrol, AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType ) + local self = BASE:Inherit( self, AI_Air_Engage ) local RTBSpeedMax = AIGroup:GetSpeedMax() or 9999 @@ -55,112 +58,46 @@ function AI_A2G_BAI:New( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAlt end ---- @param #AI_A2G_BAI self --- @param Wrapper.Group#GROUP DefenderGroup The GroupGroup managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. -function AI_A2G_BAI:onafterEngage( DefenderGroup, From, Event, To, AttackSetUnit ) +--- Creates a new AI_A2G_BAI object +-- @param #AI_A2G_BAI self +-- @param Wrapper.Group#GROUP AIGroup +-- @param DCS#Speed EngageMinSpeed The minimum speed of the @{Wrapper.Group} in km/h when engaging a target. +-- @param DCS#Speed EngageMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h when engaging a target. +-- @param DCS#Altitude EngageFloorAltitude The lowest altitude in meters where to execute the engagement. +-- @param DCS#Altitude EngageCeilingAltitude The highest altitude in meters where to execute the engagement. +-- @param Core.Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed. +-- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol. +-- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol. +-- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Group} in km/h. +-- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h. +-- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO +-- @return #AI_A2G_BAI +function AI_A2G_BAI:New( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) - self:I( { DefenderGroup, From, Event, To, AttackSetUnit} ) - - local DefenderGroupName = DefenderGroup:GetName() - - local AttackCount = AttackSetUnit:Count() - - self.AttackSetUnit = AttackSetUnit -- Kept in memory in case of resume from refuel in air! - - if AttackCount > 0 then - - if DefenderGroup:IsAlive() then - - local EngageAltitude = math.random( self.EngageFloorAltitude, self.EngageCeilingAltitude ) - local EngageSpeed = math.random( self.EngageMinSpeed, self.EngageMaxSpeed ) - - -- Determine the distance to the target. - -- If it is less than 10km, then attack without a route. - -- Otherwise perform a route attack. - - local DefenderCoord = DefenderGroup:GetPointVec3() - DefenderCoord:SetY( EngageAltitude ) -- Ground targets don't have an altitude. - - local TargetCoord = AttackSetUnit:GetFirst():GetPointVec3() - TargetCoord:SetY( EngageAltitude ) -- Ground targets don't have an altitude. - - local TargetDistance = DefenderCoord:Get2DDistance( TargetCoord ) - local EngageDistance = ( DefenderGroup:IsHelicopter() and 5000 ) or ( DefenderGroup:IsAirPlane() and 10000 ) - - local EngageRoute = {} - local AttackTasks = {} - - --- Calculate the target route point. - - local FromWP = DefenderCoord:WaypointAir( - self.PatrolAltType or "RADIO", - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, - EngageSpeed, - true - ) - - EngageRoute[#EngageRoute+1] = FromWP - - self:SetTargetDistance( TargetCoord ) -- For RTB status check - - local FromEngageAngle = TargetCoord:GetAngleDegrees( TargetCoord:GetDirectionVec3( DefenderCoord ) ) - local ToWP = TargetCoord:Translate( EngageDistance, FromEngageAngle, true ):WaypointAir( - self.PatrolAltType or "RADIO", - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, - EngageSpeed, - true - ) - - EngageRoute[#EngageRoute+1] = ToWP - - local AttackUnitTasks = {} - - local AttackSetUnitPerThreatLevel = AttackSetUnit:GetSetPerThreatLevel( 10, 0 ) - for AttackUnitIndex, AttackUnit in ipairs( AttackSetUnitPerThreatLevel or {} ) do - if AttackUnit then - if AttackUnit:IsAlive() and AttackUnit:IsGround() then - self:T( { "BAI Unit:", AttackUnit:GetName() } ) - AttackUnitTasks[#AttackUnitTasks+1] = DefenderGroup:TaskAttackUnit( AttackUnit, true, false, nil, nil, EngageAltitude ) - end - end - end - - if #AttackUnitTasks == 0 then - self:I( DefenderGroupName .. ": No targets found -> Going RTB") - self:Return() - self:__RTB( self.TaskDelay ) - else - DefenderGroup:OptionROEOpenFire() - DefenderGroup:OptionROTEvadeFire() - DefenderGroup:OptionKeepWeaponsOnThreat() - - AttackTasks[#AttackTasks+1] = DefenderGroup:TaskCombo( AttackUnitTasks ) - end - - AttackTasks[#AttackTasks+1] = DefenderGroup:TaskFunction( "AI_A2G_ENGAGE.___Engage", self, AttackSetUnit ) - EngageRoute[#EngageRoute].task = DefenderGroup:TaskCombo( AttackTasks ) - - DefenderGroup:Route( EngageRoute, self.TaskDelay ) - end - else - self:I( DefenderGroupName .. ": No targets found -> Going RTB") - self:Return() - self:__RTB( self.TaskDelay ) - end + return self:New2( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, PatrolAltType, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType) end ---- @param Wrapper.Group#GROUP AIEngage -function AI_A2G_BAI.Resume( AIEngage, Fsm ) +--- Evaluate the attack and create an AttackUnitTask list. +-- @param #AI_A2G_BAI self +-- @param Core.Set#SET_UNIT AttackSetUnit The set of units to attack. +-- @param Wrappper.Group#GROUP DefenderGroup The group of defenders. +-- @param #number EngageAltitude The altitude to engage the targets. +-- @return #AI_A2G_BAI self +function AI_A2G_BAI:CreateAttackUnitTasks( AttackSetUnit, DefenderGroup, EngageAltitude ) - AIEngage:F( { "AI_A2G_BAI.Resume:", AIEngage:GetName() } ) - if AIEngage:IsAlive() then - Fsm:__Reset( Fsm.TaskDelay ) - Fsm:__EngageRoute( Fsm.TaskDelay, Fsm.AttackSetUnit ) + local AttackUnitTasks = {} + + local AttackSetUnitPerThreatLevel = AttackSetUnit:GetSetPerThreatLevel( 10, 0 ) + for AttackUnitIndex, AttackUnit in ipairs( AttackSetUnitPerThreatLevel or {} ) do + if AttackUnit then + if AttackUnit:IsAlive() and AttackUnit:IsGround() then + self:T( { "BAI Unit:", AttackUnit:GetName() } ) + AttackUnitTasks[#AttackUnitTasks+1] = DefenderGroup:TaskAttackUnit( AttackUnit, true, false, nil, nil, EngageAltitude ) + end + end end -end \ No newline at end of file + return AttackUnitTasks +end + + diff --git a/Moose Development/Moose/AI/AI_A2G_CAS.lua b/Moose Development/Moose/AI/AI_A2G_CAS.lua index 8ed9ee29d..00cd73383 100644 --- a/Moose Development/Moose/AI/AI_A2G_CAS.lua +++ b/Moose Development/Moose/AI/AI_A2G_CAS.lua @@ -14,7 +14,7 @@ --- @type AI_A2G_CAS --- @extends AI.AI_A2G_Patrol#AI_A2G_PATROL +-- @extends AI.AI_A2G_Patrol#AI_AIR_PATROL --- Implements the core functions to intercept intruders. Use the Engage trigger to intercept intruders. @@ -35,6 +35,7 @@ AI_A2G_CAS = { -- @param DCS#Speed EngageMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h when engaging a target. -- @param DCS#Altitude EngageFloorAltitude The lowest altitude in meters where to execute the engagement. -- @param DCS#Altitude EngageCeilingAltitude The highest altitude in meters where to execute the engagement. +-- @param DCS#AltitudeType EngageAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to "RADIO". -- @param Core.Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed. -- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol. -- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol. @@ -42,10 +43,12 @@ AI_A2G_CAS = { -- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h. -- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO -- @return #AI_A2G_CAS -function AI_A2G_CAS:New( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) +function AI_A2G_CAS:New2( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) - -- Inherits from BASE - local self = BASE:Inherit( self, AI_A2G_PATROL:New( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) ) -- #AI_A2G_CAS + local AI_Air = AI_AIR:New( AIGroup ) + local AI_Air_Patrol = AI_AIR_PATROL:New( AI_Air, AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) -- #AI_AIR_PATROL + local AI_Air_Engage = AI_AIR_ENGAGE:New( AI_Air_Patrol, AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType ) + local self = BASE:Inherit( self, AI_Air_Engage ) local RTBSpeedMax = AIGroup:GetSpeedMax() or 9999 @@ -55,108 +58,47 @@ function AI_A2G_CAS:New( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAlt end ---- @param #AI_A2G_CAS self --- @param Wrapper.Group#GROUP DefenderGroup The GroupGroup managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. -function AI_A2G_CAS:onafterEngage( DefenderGroup, From, Event, To, AttackSetUnit ) +--- Creates a new AI_A2G_CAS object +-- @param #AI_A2G_CAS self +-- @param Wrapper.Group#GROUP AIGroup +-- @param DCS#Speed EngageMinSpeed The minimum speed of the @{Wrapper.Group} in km/h when engaging a target. +-- @param DCS#Speed EngageMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h when engaging a target. +-- @param DCS#Altitude EngageFloorAltitude The lowest altitude in meters where to execute the engagement. +-- @param DCS#Altitude EngageCeilingAltitude The highest altitude in meters where to execute the engagement. +-- @param Core.Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed. +-- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol. +-- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol. +-- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Group} in km/h. +-- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h. +-- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO +-- @return #AI_A2G_CAS +function AI_A2G_CAS:New( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) - self:F( { DefenderGroup, From, Event, To, AttackSetUnit} ) - - local DefenderGroupName = DefenderGroup:GetName() - - self.AttackSetUnit = AttackSetUnit -- Kept in memory in case of resume from refuel in air! - - local AttackCount = AttackSetUnit:Count() - - if AttackCount > 0 then - - if DefenderGroup:IsAlive() then - - local EngageAltitude = math.random( self.EngageFloorAltitude or 500, self.EngageCeilingAltitude or 1000 ) - local EngageSpeed = math.random( self.EngageMinSpeed, self.EngageMaxSpeed ) - - local DefenderCoord = DefenderGroup:GetPointVec3() - DefenderCoord:SetY( EngageAltitude ) -- Ground targets don't have an altitude. - - local TargetCoord = AttackSetUnit:GetFirst():GetPointVec3() - TargetCoord:SetY( EngageAltitude ) -- Ground targets don't have an altitude. - - local TargetDistance = DefenderCoord:Get2DDistance( TargetCoord ) - local EngageDistance = ( DefenderGroup:IsHelicopter() and 5000 ) or ( DefenderGroup:IsAirPlane() and 10000 ) - - local EngageRoute = {} - local AttackTasks = {} - - local FromWP = DefenderCoord:WaypointAir( - self.PatrolAltType or "RADIO", - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, - EngageSpeed, - true - ) - EngageRoute[#EngageRoute+1] = FromWP - - self:SetTargetDistance( TargetCoord ) -- For RTB status check - - local FromEngageAngle = DefenderCoord:GetAngleDegrees( DefenderCoord:GetDirectionVec3( TargetCoord ) ) - local ToWP = DefenderCoord:Translate( EngageDistance, FromEngageAngle, true ):WaypointAir( - self.PatrolAltType or "RADIO", - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, - EngageSpeed, - true - ) - EngageRoute[#EngageRoute+1] = ToWP - - if TargetDistance <= EngageDistance * 3 then - - local AttackUnitTasks = {} - - local AttackSetUnitPerThreatLevel = AttackSetUnit:GetSetPerThreatLevel( 10, 0 ) - for AttackUnitIndex, AttackUnit in ipairs( AttackSetUnitPerThreatLevel or {} ) do - if AttackUnit then - if AttackUnit:IsAlive() and AttackUnit:IsGround() then - self:T( { "CAS Unit:", AttackUnit:GetName() } ) - AttackUnitTasks[#AttackUnitTasks+1] = DefenderGroup:TaskAttackUnit( AttackUnit, true, false, nil, nil, EngageAltitude ) - end - end - end - - - if #AttackUnitTasks == 0 then - self:I( DefenderGroupName .. ": No targets found -> Going RTB") - self:Return() - self:__RTB( self.TaskDelay ) - else - DefenderGroup:OptionROEOpenFire() - DefenderGroup:OptionROTEvadeFire() - DefenderGroup:OptionKeepWeaponsOnThreat() - - AttackTasks[#AttackTasks+1] = DefenderGroup:TaskCombo( AttackUnitTasks ) - end - end - - AttackTasks[#AttackTasks+1] = DefenderGroup:TaskFunction( "AI_A2G_ENGAGE.___Engage", self, AttackSetUnit ) - EngageRoute[#EngageRoute].task = DefenderGroup:TaskCombo( AttackTasks ) - - DefenderGroup:Route( EngageRoute, self.TaskDelay ) - end - else - self:I( DefenderGroupName .. ": No targets found -> Going RTB") - self:Return() - self:__RTB( self.TaskDelay ) - end + return self:New2( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, PatrolAltType, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType) end ---- @param Wrapper.Group#GROUP AIEngage -function AI_A2G_CAS.Resume( AIEngage, Fsm ) +--- Evaluate the attack and create an AttackUnitTask list. +-- @param #AI_A2G_CAS self +-- @param Core.Set#SET_UNIT AttackSetUnit The set of units to attack. +-- @param Wrappper.Group#GROUP DefenderGroup The group of defenders. +-- @param #number EngageAltitude The altitude to engage the targets. +-- @return #AI_A2G_CAS self +function AI_A2G_CAS:CreateAttackUnitTasks( AttackSetUnit, DefenderGroup, EngageAltitude ) - AIEngage:F( { "AI_A2G_CAS.Resume:", AIEngage:GetName() } ) - if AIEngage:IsAlive() then - Fsm:__Reset( Fsm.TaskDelay ) - Fsm:__EngageRoute( Fsm.TaskDelay, Fsm.AttackSetUnit ) + local AttackUnitTasks = {} + + local AttackSetUnitPerThreatLevel = AttackSetUnit:GetSetPerThreatLevel( 10, 0 ) + for AttackUnitIndex, AttackUnit in ipairs( AttackSetUnitPerThreatLevel or {} ) do + if AttackUnit then + if AttackUnit:IsAlive() and AttackUnit:IsGround() then + self:T( { "CAS Unit:", AttackUnit:GetName() } ) + AttackUnitTasks[#AttackUnitTasks+1] = DefenderGroup:TaskAttackUnit( AttackUnit, true, false, nil, nil, EngageAltitude ) + end + end end -end \ No newline at end of file + return AttackUnitTasks +end + + + diff --git a/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua b/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua index 0c6450c2c..3628fca69 100644 --- a/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua +++ b/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua @@ -2088,22 +2088,23 @@ do -- AI_A2G_DISPATCHER - --- + --- Set a squadron to engage for suppression of air defenses, when a defense point is under attack. -- @param #AI_A2G_DISPATCHER self -- @param #string SquadronName The squadron name. -- @param #number EngageMinSpeed (optional, default = 50% of max speed) The minimum speed at which the SEAD task can be executed. -- @param #number EngageMaxSpeed (optional, default = 75% of max speed) The maximum speed at which the SEAD task can be executed. -- @param DCS#Altitude EngageFloorAltitude (optional, default = 1000m ) The lowest altitude in meters where to execute the engagement. -- @param DCS#Altitude EngageCeilingAltitude (optional, default = 1500m ) The highest altitude in meters where to execute the engagement. + -- @param #number EngageAltType The altitude type when engaging, which is a string "BARO" defining Barometric or "RADIO" defining radio controlled altitude. -- @usage -- -- -- SEAD Squadron execution. - -- A2GDispatcher:SetSquadronSead( "Mozdok", 900, 1200 ) - -- A2GDispatcher:SetSquadronSead( "Novo", 900, 2100 ) - -- A2GDispatcher:SetSquadronSead( "Maykop", 900, 1200 ) + -- A2GDispatcher:SetSquadronSead( "Mozdok", 900, 1200, 4000, 5000, "BARO" ) + -- A2GDispatcher:SetSquadronSead( "Novo", 900, 2100, 6000, 9000, "BARO" ) + -- A2GDispatcher:SetSquadronSead( "Maykop", 900, 1200, 30, 100, "RADIO" ) -- -- @return #AI_A2G_DISPATCHER - function AI_A2G_DISPATCHER:SetSquadronSead( SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude ) + function AI_A2G_DISPATCHER:SetSquadronSead2( SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType ) local DefenderSquadron = self:GetSquadron( SquadronName ) @@ -2115,9 +2116,32 @@ do -- AI_A2G_DISPATCHER Sead.EngageMaxSpeed = EngageMaxSpeed Sead.EngageFloorAltitude = EngageFloorAltitude or 500 Sead.EngageCeilingAltitude = EngageCeilingAltitude or 1000 + Sead.EngageAltType = EngageAltType Sead.Defend = true - self:F( { Sead = Sead } ) + self:I( { SEAD = { SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType } } ) + + return self + end + + --- Set a squadron to engage for suppression of air defenses, when a defense point is under attack. + -- @param #AI_A2G_DISPATCHER self + -- @param #string SquadronName The squadron name. + -- @param #number EngageMinSpeed (optional, default = 50% of max speed) The minimum speed at which the SEAD task can be executed. + -- @param #number EngageMaxSpeed (optional, default = 75% of max speed) The maximum speed at which the SEAD task can be executed. + -- @param DCS#Altitude EngageFloorAltitude (optional, default = 1000m ) The lowest altitude in meters where to execute the engagement. + -- @param DCS#Altitude EngageCeilingAltitude (optional, default = 1500m ) The highest altitude in meters where to execute the engagement. + -- @usage + -- + -- -- SEAD Squadron execution. + -- A2GDispatcher:SetSquadronSead( "Mozdok", 900, 1200, 4000, 5000 ) + -- A2GDispatcher:SetSquadronSead( "Novo", 900, 2100, 6000, 8000 ) + -- A2GDispatcher:SetSquadronSead( "Maykop", 900, 1200, 6000, 10000 ) + -- + -- @return #AI_A2G_DISPATCHER + function AI_A2G_DISPATCHER:SetSquadronSead( SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude ) + + return self:SetSquadronSead2( SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, "RADIO" ) end --- Set the squadron SEAD engage limit. @@ -2140,6 +2164,55 @@ do -- AI_A2G_DISPATCHER + --- Set a Sead patrol for a Squadron. + -- The Sead patrol will start a patrol of the aircraft at a specified zone, and will engage when commanded. + -- @param #AI_A2G_DISPATCHER self + -- @param #string SquadronName The squadron name. + -- @param Core.Zone#ZONE_BASE Zone The @{Zone} object derived from @{Core.Zone#ZONE_BASE} that defines the zone wherein the Patrol will be executed. + -- @param #number PatrolMinSpeed (optional, default = 50% of max speed) The minimum speed at which the cap can be executed. + -- @param #number PatrolMaxSpeed (optional, default = 75% of max speed) The maximum speed at which the cap can be executed. + -- @param #number PatrolFloorAltitude (optional, default = 1000m ) The minimum altitude at which the cap can be executed. + -- @param #number PatrolCeilingAltitude (optional, default = 1500m ) The maximum altitude at which the cap can be executed. + -- @param #number PatrolAltType The altitude type when patrolling, which is a string "BARO" defining Barometric or "RADIO" defining radio controlled altitude. + -- @param #number EngageMinSpeed (optional, default = 50% of max speed) The minimum speed at which the engage can be executed. + -- @param #number EngageMaxSpeed (optional, default = 75% of max speed) The maximum speed at which the engage can be executed. + -- @param #number EngageFloorAltitude (optional, default = 1000m ) The minimum altitude at which the engage can be executed. + -- @param #number EngageCeilingAltitude (optional, default = 1500m ) The maximum altitude at which the engage can be executed. + -- @param #number EngageAltType The altitude type when engaging, which is a string "BARO" defining Barometric or "RADIO" defining radio controlled altitude. + -- @return #AI_A2G_DISPATCHER + -- @usage + -- + -- -- Sead Patrol Squadron execution. + -- PatrolZoneEast = ZONE_POLYGON:New( "Patrol Zone East", GROUP:FindByName( "Patrol Zone East" ) ) + -- A2GDispatcher:SetSquadronSeadPatrol2( "Mineralnye", PatrolZoneEast, 500, 600, 4000, 10000, "BARO", 800, 900, 2000, 3000, "RADIO", ) + -- + function AI_A2G_DISPATCHER:SetSquadronSeadPatrol2( SquadronName, Zone, PatrolMinSpeed, PatrolMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType ) + + local DefenderSquadron = self:GetSquadron( SquadronName ) + + DefenderSquadron.SEAD = DefenderSquadron.SEAD or {} + + local SeadPatrol = DefenderSquadron.SEAD + SeadPatrol.Name = SquadronName + SeadPatrol.Zone = Zone + SeadPatrol.PatrolFloorAltitude = PatrolFloorAltitude + SeadPatrol.PatrolCeilingAltitude = PatrolCeilingAltitude + SeadPatrol.EngageFloorAltitude = EngageFloorAltitude + SeadPatrol.EngageCeilingAltitude = EngageCeilingAltitude + SeadPatrol.PatrolMinSpeed = PatrolMinSpeed + SeadPatrol.PatrolMaxSpeed = PatrolMaxSpeed + SeadPatrol.EngageMinSpeed = EngageMinSpeed + SeadPatrol.EngageMaxSpeed = EngageMaxSpeed + SeadPatrol.PatrolAltType = PatrolAltType + SeadPatrol.EngageAltType = EngageAltType + SeadPatrol.Patrol = true + + self:SetSquadronPatrolInterval( SquadronName, self.DefenderDefault.PatrolLimit, self.DefenderDefault.PatrolMinSeconds, self.DefenderDefault.PatrolMaxSeconds, 1, "SEAD" ) + + self:I( { SEAD = { Zone:GetName(), PatrolMinSpeed, PatrolMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType } } ) + end + + --- Set a Sead patrol for a Squadron. -- The Sead patrol will start a patrol of the aircraft at a specified zone, and will engage when commanded. -- @param #AI_A2G_DISPATCHER self @@ -2160,47 +2233,29 @@ do -- AI_A2G_DISPATCHER -- A2GDispatcher:SetSquadronSeadPatrol( "Mineralnye", PatrolZoneEast, 4000, 10000, 500, 600, 800, 900 ) -- function AI_A2G_DISPATCHER:SetSquadronSeadPatrol( SquadronName, Zone, FloorAltitude, CeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, EngageMinSpeed, EngageMaxSpeed, AltType ) + + self:SetSquadronSeadPatrol2( SquadronName, Zone, PatrolMinSpeed, PatrolMaxSpeed, FloorAltitude, CeilingAltitude, AltType, EngageMinSpeed, EngageMaxSpeed, FloorAltitude, CeilingAltitude, AltType ) - local DefenderSquadron = self:GetSquadron( SquadronName ) - - DefenderSquadron.SEAD = DefenderSquadron.SEAD or {} - - local SeadPatrol = DefenderSquadron.SEAD - SeadPatrol.Name = SquadronName - SeadPatrol.Zone = Zone - SeadPatrol.PatrolFloorAltitude = FloorAltitude - SeadPatrol.PatrolCeilingAltitude = CeilingAltitude - SeadPatrol.EngageFloorAltitude = FloorAltitude - SeadPatrol.EngageCeilingAltitude = CeilingAltitude - SeadPatrol.PatrolMinSpeed = PatrolMinSpeed - SeadPatrol.PatrolMaxSpeed = PatrolMaxSpeed - SeadPatrol.EngageMinSpeed = EngageMinSpeed - SeadPatrol.EngageMaxSpeed = EngageMaxSpeed - SeadPatrol.AltType = AltType - SeadPatrol.Patrol = true - - self:SetSquadronPatrolInterval( SquadronName, self.DefenderDefault.PatrolLimit, self.DefenderDefault.PatrolMinSeconds, self.DefenderDefault.PatrolMaxSeconds, 1, "SEAD" ) - - self:F( { Sead = SeadPatrol } ) end - + - --- + --- Set a squadron to engage for close air support, when a defense point is under attack. -- @param #AI_A2G_DISPATCHER self -- @param #string SquadronName The squadron name. -- @param #number EngageMinSpeed (optional, default = 50% of max speed) The minimum speed at which the CAS task can be executed. -- @param #number EngageMaxSpeed (optional, default = 75% of max speed) The maximum speed at which the CAS task can be executed. -- @param DCS#Altitude EngageFloorAltitude (optional, default = 1000m ) The lowest altitude in meters where to execute the engagement. -- @param DCS#Altitude EngageCeilingAltitude (optional, default = 1500m ) The highest altitude in meters where to execute the engagement. + -- @param #number EngageAltType The altitude type when engaging, which is a string "BARO" defining Barometric or "RADIO" defining radio controlled altitude. -- @usage -- -- -- CAS Squadron execution. - -- A2GDispatcher:SetSquadronCas( "Mozdok", 900, 1200 ) - -- A2GDispatcher:SetSquadronCas( "Novo", 900, 2100 ) - -- A2GDispatcher:SetSquadronCas( "Maykop", 900, 1200 ) + -- A2GDispatcher:SetSquadronCas( "Mozdok", 900, 1200, 4000, 5000, "BARO" ) + -- A2GDispatcher:SetSquadronCas( "Novo", 900, 2100, 6000, 9000, "BARO" ) + -- A2GDispatcher:SetSquadronCas( "Maykop", 900, 1200, 30, 100, "RADIO" ) -- -- @return #AI_A2G_DISPATCHER - function AI_A2G_DISPATCHER:SetSquadronCas( SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude ) + function AI_A2G_DISPATCHER:SetSquadronCas2( SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType ) local DefenderSquadron = self:GetSquadron( SquadronName ) @@ -2212,9 +2267,32 @@ do -- AI_A2G_DISPATCHER Cas.EngageMaxSpeed = EngageMaxSpeed Cas.EngageFloorAltitude = EngageFloorAltitude or 500 Cas.EngageCeilingAltitude = EngageCeilingAltitude or 1000 + Cas.EngageAltType = EngageAltType Cas.Defend = true - self:F( { Cas = Cas } ) + self:I( { CAS = { SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType } } ) + + return self + end + + --- Set a squadron to engage for close air support, when a defense point is under attack. + -- @param #AI_A2G_DISPATCHER self + -- @param #string SquadronName The squadron name. + -- @param #number EngageMinSpeed (optional, default = 50% of max speed) The minimum speed at which the CAS task can be executed. + -- @param #number EngageMaxSpeed (optional, default = 75% of max speed) The maximum speed at which the CAS task can be executed. + -- @param DCS#Altitude EngageFloorAltitude (optional, default = 1000m ) The lowest altitude in meters where to execute the engagement. + -- @param DCS#Altitude EngageCeilingAltitude (optional, default = 1500m ) The highest altitude in meters where to execute the engagement. + -- @usage + -- + -- -- CAS Squadron execution. + -- A2GDispatcher:SetSquadronCas( "Mozdok", 900, 1200, 4000, 5000 ) + -- A2GDispatcher:SetSquadronCas( "Novo", 900, 2100, 6000, 8000 ) + -- A2GDispatcher:SetSquadronCas( "Maykop", 900, 1200, 6000, 10000 ) + -- + -- @return #AI_A2G_DISPATCHER + function AI_A2G_DISPATCHER:SetSquadronCas( SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude ) + + return self:SetSquadronCas2( SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, "RADIO" ) end @@ -2236,6 +2314,53 @@ do -- AI_A2G_DISPATCHER end + --- Set a Cas patrol for a Squadron. + -- The Cas patrol will start a patrol of the aircraft at a specified zone, and will engage when commanded. + -- @param #AI_A2G_DISPATCHER self + -- @param #string SquadronName The squadron name. + -- @param Core.Zone#ZONE_BASE Zone The @{Zone} object derived from @{Core.Zone#ZONE_BASE} that defines the zone wherein the Patrol will be executed. + -- @param #number PatrolMinSpeed (optional, default = 50% of max speed) The minimum speed at which the cap can be executed. + -- @param #number PatrolMaxSpeed (optional, default = 75% of max speed) The maximum speed at which the cap can be executed. + -- @param #number PatrolFloorAltitude (optional, default = 1000m ) The minimum altitude at which the cap can be executed. + -- @param #number PatrolCeilingAltitude (optional, default = 1500m ) The maximum altitude at which the cap can be executed. + -- @param #number PatrolAltType The altitude type when patrolling, which is a string "BARO" defining Barometric or "RADIO" defining radio controlled altitude. + -- @param #number EngageMinSpeed (optional, default = 50% of max speed) The minimum speed at which the engage can be executed. + -- @param #number EngageMaxSpeed (optional, default = 75% of max speed) The maximum speed at which the engage can be executed. + -- @param #number EngageFloorAltitude (optional, default = 1000m ) The minimum altitude at which the engage can be executed. + -- @param #number EngageCeilingAltitude (optional, default = 1500m ) The maximum altitude at which the engage can be executed. + -- @param #number EngageAltType The altitude type when engaging, which is a string "BARO" defining Barometric or "RADIO" defining radio controlled altitude. + -- @return #AI_A2G_DISPATCHER + -- @usage + -- + -- -- Cas Patrol Squadron execution. + -- PatrolZoneEast = ZONE_POLYGON:New( "Patrol Zone East", GROUP:FindByName( "Patrol Zone East" ) ) + -- A2GDispatcher:SetSquadronCasPatrol2( "Mineralnye", PatrolZoneEast, 500, 600, 4000, 10000, "BARO", 800, 900, 2000, 3000, "RADIO", ) + -- + function AI_A2G_DISPATCHER:SetSquadronCasPatrol2( SquadronName, Zone, PatrolMinSpeed, PatrolMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType ) + + local DefenderSquadron = self:GetSquadron( SquadronName ) + + DefenderSquadron.CAS = DefenderSquadron.CAS or {} + + local CasPatrol = DefenderSquadron.CAS + CasPatrol.Name = SquadronName + CasPatrol.Zone = Zone + CasPatrol.PatrolFloorAltitude = PatrolFloorAltitude + CasPatrol.PatrolCeilingAltitude = PatrolCeilingAltitude + CasPatrol.EngageFloorAltitude = EngageFloorAltitude + CasPatrol.EngageCeilingAltitude = EngageCeilingAltitude + CasPatrol.PatrolMinSpeed = PatrolMinSpeed + CasPatrol.PatrolMaxSpeed = PatrolMaxSpeed + CasPatrol.EngageMinSpeed = EngageMinSpeed + CasPatrol.EngageMaxSpeed = EngageMaxSpeed + CasPatrol.PatrolAltType = PatrolAltType + CasPatrol.EngageAltType = EngageAltType + CasPatrol.Patrol = true + + self:SetSquadronPatrolInterval( SquadronName, self.DefenderDefault.PatrolLimit, self.DefenderDefault.PatrolMinSeconds, self.DefenderDefault.PatrolMaxSeconds, 1, "CAS" ) + + self:I( { CAS = { Zone:GetName(), PatrolMinSpeed, PatrolMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType } } ) + end --- Set a Cas patrol for a Squadron. @@ -2258,47 +2383,28 @@ do -- AI_A2G_DISPATCHER -- A2GDispatcher:SetSquadronCasPatrol( "Mineralnye", PatrolZoneEast, 4000, 10000, 500, 600, 800, 900 ) -- function AI_A2G_DISPATCHER:SetSquadronCasPatrol( SquadronName, Zone, FloorAltitude, CeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, EngageMinSpeed, EngageMaxSpeed, AltType ) + + self:SetSquadronCasPatrol2( SquadronName, Zone, PatrolMinSpeed, PatrolMaxSpeed, FloorAltitude, CeilingAltitude, AltType, EngageMinSpeed, EngageMaxSpeed, FloorAltitude, CeilingAltitude, AltType ) - local DefenderSquadron = self:GetSquadron( SquadronName ) - - DefenderSquadron.CAS = DefenderSquadron.CAS or {} - - local CasPatrol = DefenderSquadron.CAS - CasPatrol.Name = SquadronName - CasPatrol.Zone = Zone - CasPatrol.PatrolFloorAltitude = FloorAltitude - CasPatrol.PatrolCeilingAltitude = CeilingAltitude - CasPatrol.EngageFloorAltitude = FloorAltitude - CasPatrol.EngageCeilingAltitude = CeilingAltitude - CasPatrol.PatrolMinSpeed = PatrolMinSpeed - CasPatrol.PatrolMaxSpeed = PatrolMaxSpeed - CasPatrol.EngageMinSpeed = EngageMinSpeed - CasPatrol.EngageMaxSpeed = EngageMaxSpeed - CasPatrol.AltType = AltType - CasPatrol.Patrol = true - - self:SetSquadronPatrolInterval( SquadronName, self.DefenderDefault.PatrolLimit, self.DefenderDefault.PatrolMinSeconds, self.DefenderDefault.PatrolMaxSeconds, 1, "CAS" ) - - self:F( { Cas = CasPatrol } ) end - - --- + --- Set a squadron to engage for a battlefield area interdiction, when a defense point is under attack. -- @param #AI_A2G_DISPATCHER self -- @param #string SquadronName The squadron name. -- @param #number EngageMinSpeed (optional, default = 50% of max speed) The minimum speed at which the BAI task can be executed. -- @param #number EngageMaxSpeed (optional, default = 75% of max speed) The maximum speed at which the BAI task can be executed. -- @param DCS#Altitude EngageFloorAltitude (optional, default = 1000m ) The lowest altitude in meters where to execute the engagement. -- @param DCS#Altitude EngageCeilingAltitude (optional, default = 1500m ) The highest altitude in meters where to execute the engagement. + -- @param #number EngageAltType The altitude type when engaging, which is a string "BARO" defining Barometric or "RADIO" defining radio controlled altitude. -- @usage -- -- -- BAI Squadron execution. - -- A2GDispatcher:SetSquadronBai( "Mozdok", 900, 1200 ) - -- A2GDispatcher:SetSquadronBai( "Novo", 900, 2100 ) - -- A2GDispatcher:SetSquadronBai( "Maykop", 900, 1200 ) + -- A2GDispatcher:SetSquadronBai( "Mozdok", 900, 1200, 4000, 5000, "BARO" ) + -- A2GDispatcher:SetSquadronBai( "Novo", 900, 2100, 6000, 9000, "BARO" ) + -- A2GDispatcher:SetSquadronBai( "Maykop", 900, 1200, 30, 100, "RADIO" ) -- -- @return #AI_A2G_DISPATCHER - function AI_A2G_DISPATCHER:SetSquadronBai( SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude ) + function AI_A2G_DISPATCHER:SetSquadronBai2( SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType ) local DefenderSquadron = self:GetSquadron( SquadronName ) @@ -2310,9 +2416,32 @@ do -- AI_A2G_DISPATCHER Bai.EngageMaxSpeed = EngageMaxSpeed Bai.EngageFloorAltitude = EngageFloorAltitude or 500 Bai.EngageCeilingAltitude = EngageCeilingAltitude or 1000 + Bai.EngageAltType = EngageAltType Bai.Defend = true - self:F( { Bai = Bai } ) + self:I( { BAI = { SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType } } ) + + return self + end + + --- Set a squadron to engage for a battlefield area interdiction, when a defense point is under attack. + -- @param #AI_A2G_DISPATCHER self + -- @param #string SquadronName The squadron name. + -- @param #number EngageMinSpeed (optional, default = 50% of max speed) The minimum speed at which the BAI task can be executed. + -- @param #number EngageMaxSpeed (optional, default = 75% of max speed) The maximum speed at which the BAI task can be executed. + -- @param DCS#Altitude EngageFloorAltitude (optional, default = 1000m ) The lowest altitude in meters where to execute the engagement. + -- @param DCS#Altitude EngageCeilingAltitude (optional, default = 1500m ) The highest altitude in meters where to execute the engagement. + -- @usage + -- + -- -- BAI Squadron execution. + -- A2GDispatcher:SetSquadronBai( "Mozdok", 900, 1200, 4000, 5000 ) + -- A2GDispatcher:SetSquadronBai( "Novo", 900, 2100, 6000, 8000 ) + -- A2GDispatcher:SetSquadronBai( "Maykop", 900, 1200, 6000, 10000 ) + -- + -- @return #AI_A2G_DISPATCHER + function AI_A2G_DISPATCHER:SetSquadronBai( SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude ) + + return self:SetSquadronBai2( SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, "RADIO" ) end @@ -2333,6 +2462,54 @@ do -- AI_A2G_DISPATCHER end + + --- Set a Bai patrol for a Squadron. + -- The Bai patrol will start a patrol of the aircraft at a specified zone, and will engage when commanded. + -- @param #AI_A2G_DISPATCHER self + -- @param #string SquadronName The squadron name. + -- @param Core.Zone#ZONE_BASE Zone The @{Zone} object derived from @{Core.Zone#ZONE_BASE} that defines the zone wherein the Patrol will be executed. + -- @param #number PatrolMinSpeed (optional, default = 50% of max speed) The minimum speed at which the cap can be executed. + -- @param #number PatrolMaxSpeed (optional, default = 75% of max speed) The maximum speed at which the cap can be executed. + -- @param #number PatrolFloorAltitude (optional, default = 1000m ) The minimum altitude at which the cap can be executed. + -- @param #number PatrolCeilingAltitude (optional, default = 1500m ) The maximum altitude at which the cap can be executed. + -- @param #number PatrolAltType The altitude type when patrolling, which is a string "BARO" defining Barometric or "RADIO" defining radio controlled altitude. + -- @param #number EngageMinSpeed (optional, default = 50% of max speed) The minimum speed at which the engage can be executed. + -- @param #number EngageMaxSpeed (optional, default = 75% of max speed) The maximum speed at which the engage can be executed. + -- @param #number EngageFloorAltitude (optional, default = 1000m ) The minimum altitude at which the engage can be executed. + -- @param #number EngageCeilingAltitude (optional, default = 1500m ) The maximum altitude at which the engage can be executed. + -- @param #number EngageAltType The altitude type when engaging, which is a string "BARO" defining Barometric or "RADIO" defining radio controlled altitude. + -- @return #AI_A2G_DISPATCHER + -- @usage + -- + -- -- Bai Patrol Squadron execution. + -- PatrolZoneEast = ZONE_POLYGON:New( "Patrol Zone East", GROUP:FindByName( "Patrol Zone East" ) ) + -- A2GDispatcher:SetSquadronBaiPatrol2( "Mineralnye", PatrolZoneEast, 500, 600, 4000, 10000, "BARO", 800, 900, 2000, 3000, "RADIO", ) + -- + function AI_A2G_DISPATCHER:SetSquadronBaiPatrol2( SquadronName, Zone, PatrolMinSpeed, PatrolMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType ) + + local DefenderSquadron = self:GetSquadron( SquadronName ) + + DefenderSquadron.BAI = DefenderSquadron.BAI or {} + + local BaiPatrol = DefenderSquadron.BAI + BaiPatrol.Name = SquadronName + BaiPatrol.Zone = Zone + BaiPatrol.PatrolFloorAltitude = PatrolFloorAltitude + BaiPatrol.PatrolCeilingAltitude = PatrolCeilingAltitude + BaiPatrol.EngageFloorAltitude = EngageFloorAltitude + BaiPatrol.EngageCeilingAltitude = EngageCeilingAltitude + BaiPatrol.PatrolMinSpeed = PatrolMinSpeed + BaiPatrol.PatrolMaxSpeed = PatrolMaxSpeed + BaiPatrol.EngageMinSpeed = EngageMinSpeed + BaiPatrol.EngageMaxSpeed = EngageMaxSpeed + BaiPatrol.PatrolAltType = PatrolAltType + BaiPatrol.EngageAltType = EngageAltType + BaiPatrol.Patrol = true + + self:SetSquadronPatrolInterval( SquadronName, self.DefenderDefault.PatrolLimit, self.DefenderDefault.PatrolMinSeconds, self.DefenderDefault.PatrolMaxSeconds, 1, "BAI" ) + + self:I( { BAI = { Zone:GetName(), PatrolMinSpeed, PatrolMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolAltType, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType } } ) + end --- Set a Bai patrol for a Squadron. -- The Bai patrol will start a patrol of the aircraft at a specified zone, and will engage when commanded. @@ -2354,28 +2531,9 @@ do -- AI_A2G_DISPATCHER -- A2GDispatcher:SetSquadronBaiPatrol( "Mineralnye", PatrolZoneEast, 4000, 10000, 500, 600, 800, 900 ) -- function AI_A2G_DISPATCHER:SetSquadronBaiPatrol( SquadronName, Zone, FloorAltitude, CeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, EngageMinSpeed, EngageMaxSpeed, AltType ) + + self:SetSquadronBaiPatrol2( SquadronName, Zone, PatrolMinSpeed, PatrolMaxSpeed, FloorAltitude, CeilingAltitude, AltType, EngageMinSpeed, EngageMaxSpeed, FloorAltitude, CeilingAltitude, AltType ) - local DefenderSquadron = self:GetSquadron( SquadronName ) - - DefenderSquadron.BAI = DefenderSquadron.BAI or {} - - local BaiPatrol = DefenderSquadron.BAI - BaiPatrol.Name = SquadronName - BaiPatrol.Zone = Zone - BaiPatrol.PatrolFloorAltitude = FloorAltitude - BaiPatrol.PatrolCeilingAltitude = CeilingAltitude - BaiPatrol.EngageFloorAltitude = FloorAltitude - BaiPatrol.EngageCeilingAltitude = CeilingAltitude - BaiPatrol.PatrolMinSpeed = PatrolMinSpeed - BaiPatrol.PatrolMaxSpeed = PatrolMaxSpeed - BaiPatrol.EngageMinSpeed = EngageMinSpeed - BaiPatrol.EngageMaxSpeed = EngageMaxSpeed - BaiPatrol.AltType = AltType - BaiPatrol.Patrol = true - - self:SetSquadronPatrolInterval( SquadronName, self.DefenderDefault.PatrolLimit, self.DefenderDefault.PatrolMinSeconds, self.DefenderDefault.PatrolMaxSeconds, 1, "BAI" ) - - self:F( { Bai = BaiPatrol } ) end @@ -3496,7 +3654,7 @@ do -- AI_A2G_DISPATCHER local AI_A2G_PATROL = { SEAD = AI_A2G_SEAD, BAI = AI_A2G_BAI, CAS = AI_A2G_CAS } - local AI_A2G_Fsm = AI_A2G_PATROL[DefenseTaskType]:New( DefenderGroup, Patrol.EngageMinSpeed, Patrol.EngageMaxSpeed, Patrol.EngageFloorAltitude, Patrol.EngageCeilingAltitude, Patrol.Zone, Patrol.PatrolFloorAltitude, Patrol.PatrolCeilingAltitude, Patrol.PatrolMinSpeed, Patrol.PatrolMaxSpeed, Patrol.AltType ) + local AI_A2G_Fsm = AI_A2G_PATROL[DefenseTaskType]:New2( DefenderGroup, Patrol.EngageMinSpeed, Patrol.EngageMaxSpeed, Patrol.EngageFloorAltitude, Patrol.EngageCeilingAltitude, Patrol.EngageAltType, Patrol.Zone, Patrol.PatrolFloorAltitude, Patrol.PatrolCeilingAltitude, Patrol.PatrolMinSpeed, Patrol.PatrolMaxSpeed, Patrol.PatrolAltType ) AI_A2G_Fsm:SetDispatcher( self ) AI_A2G_Fsm:SetHomeAirbase( DefenderSquadron.Airbase ) AI_A2G_Fsm:SetFuelThreshold( DefenderSquadron.FuelThreshold or self.DefenderDefault.FuelThreshold, 60 ) @@ -3508,7 +3666,7 @@ do -- AI_A2G_DISPATCHER self:SetDefenderTask( SquadronName, DefenderGroup, DefenseTaskType, AI_A2G_Fsm, nil, DefenderGrouping ) function AI_A2G_Fsm:onafterTakeoff( DefenderGroup, From, Event, To ) - self:F({"Defender Takeoff", DefenderGroup:GetName()}) + self:F({"Takeoff", DefenderGroup:GetName()}) --self:GetParent(self).onafterBirth( self, Defender, From, Event, To ) local DefenderName = DefenderGroup:GetCallsign() -- #string @@ -3522,7 +3680,7 @@ do -- AI_A2G_DISPATCHER end function AI_A2G_Fsm:onafterPatrolRoute( DefenderGroup, From, Event, To ) - self:F({"Defender PatrolRoute", DefenderGroup:GetName()}) + self:F({"PatrolRoute", DefenderGroup:GetName()}) self:GetParent(self).onafterPatrolRoute( self, DefenderGroup, From, Event, To ) local DefenderName = DefenderGroup:GetCallsign() @@ -3544,7 +3702,7 @@ do -- AI_A2G_DISPATCHER local Dispatcher = AI_A2G_Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - if Squadron then + if Squadron and AttackSetUnit:Count() > 0 then local FirstUnit = AttackSetUnit:GetFirst() local Coordinate = FirstUnit:GetCoordinate() -- Core.Point#COORDINATE @@ -3568,7 +3726,7 @@ do -- AI_A2G_DISPATCHER end function AI_A2G_Fsm:onafterRTB( DefenderGroup, From, Event, To ) - self:F({"Defender RTB", DefenderGroup:GetName()}) + self:F({"RTB", DefenderGroup:GetName()}) self:GetParent(self).onafterRTB( self, DefenderGroup, From, Event, To ) local DefenderName = DefenderGroup:GetCallsign() @@ -3581,7 +3739,7 @@ do -- AI_A2G_DISPATCHER --- @param #AI_A2G_DISPATCHER self function AI_A2G_Fsm:onafterLostControl( DefenderGroup, From, Event, To ) - self:F({"Defender LostControl", DefenderGroup:GetName()}) + self:F({"LostControl", DefenderGroup:GetName()}) self:GetParent(self).onafterHome( self, DefenderGroup, From, Event, To ) local DefenderName = DefenderGroup:GetCallsign() @@ -3596,7 +3754,7 @@ do -- AI_A2G_DISPATCHER --- @param #AI_A2G_DISPATCHER self function AI_A2G_Fsm:onafterHome( DefenderGroup, From, Event, To, Action ) - self:F({"Defender Home", DefenderGroup:GetName()}) + self:F({"Home", DefenderGroup:GetName()}) self:GetParent(self).onafterHome( self, DefenderGroup, From, Event, To ) local DefenderName = DefenderGroup:GetCallsign() @@ -3635,9 +3793,9 @@ do -- AI_A2G_DISPATCHER if DefenderGroup then - local AI_A2G = { SEAD = AI_A2G_SEAD, BAI = AI_A2G_BAI, CAS = AI_A2G_CAS } + local AI_A2G_ENGAGE = { SEAD = AI_A2G_SEAD, BAI = AI_A2G_BAI, CAS = AI_A2G_CAS } - local AI_A2G_Fsm = AI_A2G[DefenseTaskType]:New( DefenderGroup, Defense.EngageMinSpeed, Defense.EngageMaxSpeed, Defense.EngageFloorAltitude, Defense.EngageCeilingAltitude ) -- AI.AI_A2G_ENGAGE + local AI_A2G_Fsm = AI_A2G_ENGAGE[DefenseTaskType]:New( DefenderGroup, Defense.EngageMinSpeed, Defense.EngageMaxSpeed, Defense.EngageFloorAltitude, Defense.EngageCeilingAltitude, Defense.EngageAltType ) -- AI.AI_AIR_ENGAGE AI_A2G_Fsm:SetDispatcher( self ) AI_A2G_Fsm:SetHomeAirbase( DefenderSquadron.Airbase ) AI_A2G_Fsm:SetFuelThreshold( DefenderSquadron.FuelThreshold or self.DefenderDefault.FuelThreshold, 60 ) diff --git a/Moose Development/Moose/AI/AI_A2G_SEAD.lua b/Moose Development/Moose/AI/AI_A2G_SEAD.lua index cd2883772..b72080558 100644 --- a/Moose Development/Moose/AI/AI_A2G_SEAD.lua +++ b/Moose Development/Moose/AI/AI_A2G_SEAD.lua @@ -14,7 +14,7 @@ --- @type AI_A2G_SEAD --- @extends AI.AI_A2G_Patrol#AI_A2G_PATROL +-- @extends AI.AI_A2G_Patrol#AI_AIR_PATROL --- Implements the core functions to SEAD intruders. Use the Engage trigger to intercept intruders. @@ -85,6 +85,7 @@ AI_A2G_SEAD = { -- @param DCS#Speed EngageMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h when engaging a target. -- @param DCS#Altitude EngageFloorAltitude The lowest altitude in meters where to execute the engagement. -- @param DCS#Altitude EngageCeilingAltitude The highest altitude in meters where to execute the engagement. +-- @param DCS#AltitudeType EngageAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to "RADIO". -- @param Core.Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed. -- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol. -- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol. @@ -92,10 +93,12 @@ AI_A2G_SEAD = { -- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h. -- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO -- @return #AI_A2G_SEAD -function AI_A2G_SEAD:New( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) +function AI_A2G_SEAD:New2( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) - -- Inherits from BASE - local self = BASE:Inherit( self, AI_A2G_PATROL:New( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) ) -- #AI_A2G_SEAD + local AI_Air = AI_AIR:New( AIGroup ) + local AI_Air_Patrol = AI_AIR_PATROL:New( AI_Air, AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) -- #AI_AIR_PATROL + local AI_Air_Engage = AI_AIR_ENGAGE:New( AI_Air_Patrol, AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType ) + local self = BASE:Inherit( self, AI_Air_Engage ) local RTBSpeedMax = AIGroup:GetSpeedMax() or 9999 @@ -105,116 +108,49 @@ function AI_A2G_SEAD:New( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAl end +--- Creates a new AI_A2G_SEAD object +-- @param #AI_A2G_SEAD self +-- @param Wrapper.Group#GROUP AIGroup +-- @param DCS#Speed EngageMinSpeed The minimum speed of the @{Wrapper.Group} in km/h when engaging a target. +-- @param DCS#Speed EngageMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h when engaging a target. +-- @param DCS#Altitude EngageFloorAltitude The lowest altitude in meters where to execute the engagement. +-- @param DCS#Altitude EngageCeilingAltitude The highest altitude in meters where to execute the engagement. +-- @param Core.Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed. +-- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol. +-- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol. +-- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Group} in km/h. +-- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h. +-- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO +-- @return #AI_A2G_SEAD +function AI_A2G_SEAD:New( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) ---- @param #AI_A2G_SEAD self --- @param Wrapper.Group#GROUP DefenderGroup The GroupGroup managed by the FSM. --- @param #string From The From State string. --- @param #string Event The Event string. --- @param #string To The To State string. -function AI_A2G_SEAD:onafterEngage( DefenderGroup, From, Event, To, AttackSetUnit ) - - self:F( { DefenderGroup, From, Event, To, AttackSetUnit} ) - - local DefenderGroupName = DefenderGroup:GetName() - - self.AttackSetUnit = AttackSetUnit -- Kept in memory in case of resume from refuel in air! - - local AttackCount = AttackSetUnit:Count() - - if AttackCount > 0 then - - if DefenderGroup:IsAlive() then - - -- Determine the distance to the target. - -- If it is less than 50km, then attack without a route. - -- Otherwise perform a route attack. - - local EngageAltitude = math.random( self.EngageFloorAltitude or 500, self.EngageCeilingAltitude or 1000 ) - local EngageSpeed = math.random( self.EngageMinSpeed, self.EngageMaxSpeed ) - - local DefenderCoord = DefenderGroup:GetPointVec3() - DefenderCoord:SetY( EngageAltitude ) -- Ground targets don't have an altitude. - - local TargetCoord = AttackSetUnit:GetFirst():GetPointVec3() - TargetCoord:SetY( EngageAltitude ) -- Ground targets don't have an altitude. - - local TargetDistance = DefenderCoord:Get2DDistance( TargetCoord ) - local EngageDistance = ( DefenderGroup:IsHelicopter() and 5000 ) or ( DefenderGroup:IsAirPlane() and 25000 ) - - local EngageRoute = {} - local AttackTasks = {} - - local FromWP = DefenderCoord:WaypointAir( - self.PatrolAltType or "RADIO", - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, - EngageSpeed, - false - ) - EngageRoute[#EngageRoute+1] = FromWP - - self:SetTargetDistance( TargetCoord ) -- For RTB status check - - local FromEngageAngle = DefenderCoord:GetAngleDegrees( DefenderCoord:GetDirectionVec3( TargetCoord ) ) - local ToWP = DefenderCoord:Translate( EngageDistance, FromEngageAngle, true ):WaypointAir( - self.PatrolAltType or "RADIO", - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, - EngageSpeed, - true - ) - EngageRoute[#EngageRoute+1] = ToWP - - if TargetDistance <= EngageDistance * 3 then - - local AttackUnitTasks = {} - - local AttackSetUnitPerThreatLevel = AttackSetUnit:GetSetPerThreatLevel( 10, 0 ) - for AttackUnitID, AttackUnit in ipairs( AttackSetUnitPerThreatLevel ) do - if AttackUnit then - if AttackUnit:IsAlive() and AttackUnit:IsGround() then - local HasRadar = AttackUnit:HasSEAD() - if HasRadar then - self:F( { "SEAD Unit:", AttackUnit:GetName() } ) - AttackUnitTasks[#AttackUnitTasks+1] = DefenderGroup:TaskAttackUnit( AttackUnit, true, false, nil, nil, EngageAltitude ) - end - end - end - end - - if #AttackUnitTasks == 0 then - self:I( DefenderGroupName .. ": No targets found -> Going RTB") - self:Return() - self:__RTB( self.TaskDelay ) - else - DefenderGroup:OptionROEOpenFire() - DefenderGroup:OptionROTVertical() - DefenderGroup:OptionKeepWeaponsOnThreat() - --DefenderGroup:OptionRTBAmmo( Weapon.flag.AnyASM ) - - AttackTasks[#AttackTasks+1] = DefenderGroup:TaskCombo( AttackUnitTasks ) - end - end - - AttackTasks[#AttackTasks+1] = DefenderGroup:TaskFunction( "AI_A2G_ENGAGE.___Engage", self, AttackSetUnit ) - EngageRoute[#EngageRoute].task = DefenderGroup:TaskCombo( AttackTasks ) - - DefenderGroup:Route( EngageRoute, self.TaskDelay ) - end - else - self:I( DefenderGroupName .. ": No targets found -> Going RTB") - self:Return() - self:__RTB( self.TaskDelay ) - end + return self:New2( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, PatrolAltType, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) end ---- @param Wrapper.Group#GROUP AIEngage -function AI_A2G_SEAD.Resume( AIEngage, Fsm ) - AIEngage:F( { "AI_A2G_SEAD.Resume:", AIEngage:GetName() } ) - if AIEngage:IsAlive() then - Fsm:__Reset( Fsm.TaskDelay ) - Fsm:__EngageRoute( Fsm.TaskDelay, Fsm.AttackSetUnit ) +--- Evaluate the attack and create an AttackUnitTask list. +-- @param #AI_A2G_SEAD self +-- @param Core.Set#SET_UNIT AttackSetUnit The set of units to attack. +-- @param Wrappper.Group#GROUP DefenderGroup The group of defenders. +-- @param #number EngageAltitude The altitude to engage the targets. +-- @return #AI_A2G_SEAD self +function AI_A2G_SEAD:CreateAttackUnitTasks( AttackSetUnit, DefenderGroup, EngageAltitude ) + + local AttackUnitTasks = {} + + local AttackSetUnitPerThreatLevel = AttackSetUnit:GetSetPerThreatLevel( 10, 0 ) + for AttackUnitID, AttackUnit in ipairs( AttackSetUnitPerThreatLevel ) do + if AttackUnit then + if AttackUnit:IsAlive() and AttackUnit:IsGround() then + local HasRadar = AttackUnit:HasSEAD() + if HasRadar then + self:F( { "SEAD Unit:", AttackUnit:GetName() } ) + AttackUnitTasks[#AttackUnitTasks+1] = DefenderGroup:TaskAttackUnit( AttackUnit, true, false, nil, nil, EngageAltitude ) + end + end + end end -end \ No newline at end of file + return AttackUnitTasks +end + diff --git a/Moose Development/Moose/AI/AI_Air_Dispatcher.lua b/Moose Development/Moose/AI/AI_Air_Dispatcher.lua index ac912f819..23531f689 100644 --- a/Moose Development/Moose/AI/AI_Air_Dispatcher.lua +++ b/Moose Development/Moose/AI/AI_Air_Dispatcher.lua @@ -2577,7 +2577,7 @@ do -- AI_AIR_DISPATCHER local AI_A2G = { SEAD = AI_A2G_SEAD, BAI = AI_A2G_BAI, CAS = AI_A2G_CAS } - local Fsm = AI_A2G[DefenseTaskType]:New( DefenderGroup, Defense.EngageMinSpeed, Defense.EngageMaxSpeed, Defense.EngageFloorAltitude, Defense.EngageCeilingAltitude ) -- AI.AI_A2G_ENGAGE + local Fsm = AI_A2G[DefenseTaskType]:New( DefenderGroup, Defense.EngageMinSpeed, Defense.EngageMaxSpeed, Defense.EngageFloorAltitude, Defense.EngageCeilingAltitude ) -- AI.AI_AIR_ENGAGE Fsm:SetDispatcher( self ) Fsm:SetHomeAirbase( DefenderSquadron.Airbase ) Fsm:SetFuelThreshold( DefenderSquadron.FuelThreshold or self.DefenderDefault.FuelThreshold, 60 ) diff --git a/Moose Development/Moose/AI/AI_A2G_Engage.lua b/Moose Development/Moose/AI/AI_Air_Engage.lua similarity index 64% rename from Moose Development/Moose/AI/AI_A2G_Engage.lua rename to Moose Development/Moose/AI/AI_Air_Engage.lua index f74f82f80..a14d604aa 100644 --- a/Moose Development/Moose/AI/AI_A2G_Engage.lua +++ b/Moose Development/Moose/AI/AI_Air_Engage.lua @@ -8,20 +8,20 @@ -- -- === -- --- @module AI.AI_A2G_Engage +-- @module AI.AI_Air_Engage -- @image AI_Air_To_Ground_Engage.JPG ---- @type AI_A2G_ENGAGE --- @extends AI.AI_A2G#AI_A2G +--- @type AI_AIR_ENGAGE +-- @extends AI.AI_AIR#AI_AIR --- Implements the core functions to intercept intruders. Use the Engage trigger to intercept intruders. -- -- ![Process](..\Presentations\AI_GCI\Dia3.JPG) -- --- The AI_A2G_ENGAGE is assigned a @{Wrapper.Group} and this must be done before the AI_A2G_ENGAGE process can be started using the **Start** event. +-- The AI_AIR_ENGAGE is assigned a @{Wrapper.Group} and this must be done before the AI_AIR_ENGAGE process can be started using the **Start** event. -- -- ![Process](..\Presentations\AI_GCI\Dia4.JPG) -- @@ -47,9 +47,9 @@ -- -- ![Process](..\Presentations\AI_GCI\Dia13.JPG) -- --- ## 1. AI_A2G_ENGAGE constructor +-- ## 1. AI_AIR_ENGAGE constructor -- --- * @{#AI_A2G_ENGAGE.New}(): Creates a new AI_A2G_ENGAGE object. +-- * @{#AI_AIR_ENGAGE.New}(): Creates a new AI_AIR_ENGAGE object. -- -- ## 3. Set the Range of Engagement -- @@ -59,7 +59,7 @@ -- that will define when the AI will engage with the detected airborne enemy targets. -- The range can be beyond or smaller than the range of the Patrol Zone. -- The range is applied at the position of the AI. --- Use the method @{AI.AI_GCI#AI_A2G_ENGAGE.SetEngageRange}() to define that range. +-- Use the method @{AI.AI_GCI#AI_AIR_ENGAGE.SetEngageRange}() to define that range. -- -- ## 4. Set the Zone of Engagement -- @@ -67,29 +67,30 @@ -- -- An optional @{Zone} can be set, -- that will define when the AI will engage with the detected airborne enemy targets. --- Use the method @{AI.AI_Cap#AI_A2G_ENGAGE.SetEngageZone}() to define that Zone. +-- Use the method @{AI.AI_Cap#AI_AIR_ENGAGE.SetEngageZone}() to define that Zone. -- -- === -- --- @field #AI_A2G_ENGAGE -AI_A2G_ENGAGE = { - ClassName = "AI_A2G_ENGAGE", +-- @field #AI_AIR_ENGAGE +AI_AIR_ENGAGE = { + ClassName = "AI_AIR_ENGAGE", } ---- Creates a new AI_A2G_ENGAGE object --- @param #AI_A2G_ENGAGE self +--- Creates a new AI_AIR_ENGAGE object +-- @param #AI_AIR_ENGAGE self -- @param Wrapper.Group#GROUP AIGroup -- @param DCS#Speed EngageMinSpeed (optional, default = 50% of max speed) The minimum speed of the @{Wrapper.Group} in km/h when engaging a target. -- @param DCS#Speed EngageMaxSpeed (optional, default = 75% of max speed) The maximum speed of the @{Wrapper.Group} in km/h when engaging a target. -- @param DCS#Altitude EngageFloorAltitude (optional, default = 1000m ) The lowest altitude in meters where to execute the engagement. -- @param DCS#Altitude EngageCeilingAltitude (optional, default = 1500m ) The highest altitude in meters where to execute the engagement. --- @return #AI_A2G_ENGAGE -function AI_A2G_ENGAGE:New( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude ) +-- @param DCS#AltitudeType EngageAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to "RADIO". +-- @return #AI_AIR_ENGAGE +function AI_AIR_ENGAGE:New( AI_Air, AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType ) -- Inherits from BASE - local self = BASE:Inherit( self, AI_A2G:New( AIGroup ) ) -- #AI_A2G_ENGAGE + local self = BASE:Inherit( self, AI_Air ) -- #AI_AIR_ENGAGE self.Accomplished = false self.Engaging = false @@ -100,12 +101,13 @@ function AI_A2G_ENGAGE:New( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloor self.EngageMaxSpeed = EngageMaxSpeed or SpeedMax * 0.75 self.EngageFloorAltitude = EngageFloorAltitude or 1000 self.EngageCeilingAltitude = EngageCeilingAltitude or 1500 + self.EngageAltType = EngageAltType or "RADIO" - self:AddTransition( { "Started", "Engaging", "Returning", "Airborne", "Patrolling" }, "EngageRoute", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_A2G_ENGAGE. + self:AddTransition( { "Started", "Engaging", "Returning", "Airborne", "Patrolling" }, "EngageRoute", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_AIR_ENGAGE. --- OnBefore Transition Handler for Event EngageRoute. - -- @function [parent=#AI_A2G_ENGAGE] OnBeforeEngageRoute - -- @param #AI_A2G_ENGAGE self + -- @function [parent=#AI_AIR_ENGAGE] OnBeforeEngageRoute + -- @param #AI_AIR_ENGAGE self -- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM. -- @param #string From The From State string. -- @param #string Event The Event string. @@ -113,25 +115,25 @@ function AI_A2G_ENGAGE:New( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloor -- @return #boolean Return false to cancel Transition. --- OnAfter Transition Handler for Event EngageRoute. - -- @function [parent=#AI_A2G_ENGAGE] OnAfterEngageRoute - -- @param #AI_A2G_ENGAGE self + -- @function [parent=#AI_AIR_ENGAGE] OnAfterEngageRoute + -- @param #AI_AIR_ENGAGE self -- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM. -- @param #string From The From State string. -- @param #string Event The Event string. -- @param #string To The To State string. --- Synchronous Event Trigger for Event EngageRoute. - -- @function [parent=#AI_A2G_ENGAGE] EngageRoute - -- @param #AI_A2G_ENGAGE self + -- @function [parent=#AI_AIR_ENGAGE] EngageRoute + -- @param #AI_AIR_ENGAGE self --- Asynchronous Event Trigger for Event EngageRoute. - -- @function [parent=#AI_A2G_ENGAGE] __EngageRoute - -- @param #AI_A2G_ENGAGE self + -- @function [parent=#AI_AIR_ENGAGE] __EngageRoute + -- @param #AI_AIR_ENGAGE self -- @param #number Delay The delay in seconds. --- OnLeave Transition Handler for State Engaging. --- @function [parent=#AI_A2G_ENGAGE] OnLeaveEngaging --- @param #AI_A2G_ENGAGE self +-- @function [parent=#AI_AIR_ENGAGE] OnLeaveEngaging +-- @param #AI_AIR_ENGAGE self -- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM. -- @param #string From The From State string. -- @param #string Event The Event string. @@ -139,18 +141,18 @@ function AI_A2G_ENGAGE:New( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloor -- @return #boolean Return false to cancel Transition. --- OnEnter Transition Handler for State Engaging. --- @function [parent=#AI_A2G_ENGAGE] OnEnterEngaging --- @param #AI_A2G_ENGAGE self +-- @function [parent=#AI_AIR_ENGAGE] OnEnterEngaging +-- @param #AI_AIR_ENGAGE self -- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM. -- @param #string From The From State string. -- @param #string Event The Event string. -- @param #string To The To State string. - self:AddTransition( { "Started", "Engaging", "Returning", "Airborne", "Patrolling" }, "Engage", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_A2G_ENGAGE. + self:AddTransition( { "Started", "Engaging", "Returning", "Airborne", "Patrolling" }, "Engage", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_AIR_ENGAGE. --- OnBefore Transition Handler for Event Engage. - -- @function [parent=#AI_A2G_ENGAGE] OnBeforeEngage - -- @param #AI_A2G_ENGAGE self + -- @function [parent=#AI_AIR_ENGAGE] OnBeforeEngage + -- @param #AI_AIR_ENGAGE self -- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM. -- @param #string From The From State string. -- @param #string Event The Event string. @@ -158,25 +160,25 @@ function AI_A2G_ENGAGE:New( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloor -- @return #boolean Return false to cancel Transition. --- OnAfter Transition Handler for Event Engage. - -- @function [parent=#AI_A2G_ENGAGE] OnAfterEngage - -- @param #AI_A2G_ENGAGE self + -- @function [parent=#AI_AIR_ENGAGE] OnAfterEngage + -- @param #AI_AIR_ENGAGE self -- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM. -- @param #string From The From State string. -- @param #string Event The Event string. -- @param #string To The To State string. --- Synchronous Event Trigger for Event Engage. - -- @function [parent=#AI_A2G_ENGAGE] Engage - -- @param #AI_A2G_ENGAGE self + -- @function [parent=#AI_AIR_ENGAGE] Engage + -- @param #AI_AIR_ENGAGE self --- Asynchronous Event Trigger for Event Engage. - -- @function [parent=#AI_A2G_ENGAGE] __Engage - -- @param #AI_A2G_ENGAGE self + -- @function [parent=#AI_AIR_ENGAGE] __Engage + -- @param #AI_AIR_ENGAGE self -- @param #number Delay The delay in seconds. --- OnLeave Transition Handler for State Engaging. --- @function [parent=#AI_A2G_ENGAGE] OnLeaveEngaging --- @param #AI_A2G_ENGAGE self +-- @function [parent=#AI_AIR_ENGAGE] OnLeaveEngaging +-- @param #AI_AIR_ENGAGE self -- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM. -- @param #string From The From State string. -- @param #string Event The Event string. @@ -184,18 +186,18 @@ function AI_A2G_ENGAGE:New( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloor -- @return #boolean Return false to cancel Transition. --- OnEnter Transition Handler for State Engaging. --- @function [parent=#AI_A2G_ENGAGE] OnEnterEngaging --- @param #AI_A2G_ENGAGE self +-- @function [parent=#AI_AIR_ENGAGE] OnEnterEngaging +-- @param #AI_AIR_ENGAGE self -- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM. -- @param #string From The From State string. -- @param #string Event The Event string. -- @param #string To The To State string. - self:AddTransition( "Engaging", "Fired", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_A2G_ENGAGE. + self:AddTransition( "Engaging", "Fired", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_AIR_ENGAGE. --- OnBefore Transition Handler for Event Fired. - -- @function [parent=#AI_A2G_ENGAGE] OnBeforeFired - -- @param #AI_A2G_ENGAGE self + -- @function [parent=#AI_AIR_ENGAGE] OnBeforeFired + -- @param #AI_AIR_ENGAGE self -- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM. -- @param #string From The From State string. -- @param #string Event The Event string. @@ -203,27 +205,27 @@ function AI_A2G_ENGAGE:New( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloor -- @return #boolean Return false to cancel Transition. --- OnAfter Transition Handler for Event Fired. - -- @function [parent=#AI_A2G_ENGAGE] OnAfterFired - -- @param #AI_A2G_ENGAGE self + -- @function [parent=#AI_AIR_ENGAGE] OnAfterFired + -- @param #AI_AIR_ENGAGE self -- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM. -- @param #string From The From State string. -- @param #string Event The Event string. -- @param #string To The To State string. --- Synchronous Event Trigger for Event Fired. - -- @function [parent=#AI_A2G_ENGAGE] Fired - -- @param #AI_A2G_ENGAGE self + -- @function [parent=#AI_AIR_ENGAGE] Fired + -- @param #AI_AIR_ENGAGE self --- Asynchronous Event Trigger for Event Fired. - -- @function [parent=#AI_A2G_ENGAGE] __Fired - -- @param #AI_A2G_ENGAGE self + -- @function [parent=#AI_AIR_ENGAGE] __Fired + -- @param #AI_AIR_ENGAGE self -- @param #number Delay The delay in seconds. - self:AddTransition( "*", "Destroy", "*" ) -- FSM_CONTROLLABLE Transition for type #AI_A2G_ENGAGE. + self:AddTransition( "*", "Destroy", "*" ) -- FSM_CONTROLLABLE Transition for type #AI_AIR_ENGAGE. --- OnBefore Transition Handler for Event Destroy. - -- @function [parent=#AI_A2G_ENGAGE] OnBeforeDestroy - -- @param #AI_A2G_ENGAGE self + -- @function [parent=#AI_AIR_ENGAGE] OnBeforeDestroy + -- @param #AI_AIR_ENGAGE self -- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM. -- @param #string From The From State string. -- @param #string Event The Event string. @@ -231,28 +233,28 @@ function AI_A2G_ENGAGE:New( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloor -- @return #boolean Return false to cancel Transition. --- OnAfter Transition Handler for Event Destroy. - -- @function [parent=#AI_A2G_ENGAGE] OnAfterDestroy - -- @param #AI_A2G_ENGAGE self + -- @function [parent=#AI_AIR_ENGAGE] OnAfterDestroy + -- @param #AI_AIR_ENGAGE self -- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM. -- @param #string From The From State string. -- @param #string Event The Event string. -- @param #string To The To State string. --- Synchronous Event Trigger for Event Destroy. - -- @function [parent=#AI_A2G_ENGAGE] Destroy - -- @param #AI_A2G_ENGAGE self + -- @function [parent=#AI_AIR_ENGAGE] Destroy + -- @param #AI_AIR_ENGAGE self --- Asynchronous Event Trigger for Event Destroy. - -- @function [parent=#AI_A2G_ENGAGE] __Destroy - -- @param #AI_A2G_ENGAGE self + -- @function [parent=#AI_AIR_ENGAGE] __Destroy + -- @param #AI_AIR_ENGAGE self -- @param #number Delay The delay in seconds. - self:AddTransition( "Engaging", "Abort", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_A2G_ENGAGE. + self:AddTransition( "Engaging", "Abort", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_AIR_ENGAGE. --- OnBefore Transition Handler for Event Abort. - -- @function [parent=#AI_A2G_ENGAGE] OnBeforeAbort - -- @param #AI_A2G_ENGAGE self + -- @function [parent=#AI_AIR_ENGAGE] OnBeforeAbort + -- @param #AI_AIR_ENGAGE self -- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM. -- @param #string From The From State string. -- @param #string Event The Event string. @@ -260,27 +262,27 @@ function AI_A2G_ENGAGE:New( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloor -- @return #boolean Return false to cancel Transition. --- OnAfter Transition Handler for Event Abort. - -- @function [parent=#AI_A2G_ENGAGE] OnAfterAbort - -- @param #AI_A2G_ENGAGE self + -- @function [parent=#AI_AIR_ENGAGE] OnAfterAbort + -- @param #AI_AIR_ENGAGE self -- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM. -- @param #string From The From State string. -- @param #string Event The Event string. -- @param #string To The To State string. --- Synchronous Event Trigger for Event Abort. - -- @function [parent=#AI_A2G_ENGAGE] Abort - -- @param #AI_A2G_ENGAGE self + -- @function [parent=#AI_AIR_ENGAGE] Abort + -- @param #AI_AIR_ENGAGE self --- Asynchronous Event Trigger for Event Abort. - -- @function [parent=#AI_A2G_ENGAGE] __Abort - -- @param #AI_A2G_ENGAGE self + -- @function [parent=#AI_AIR_ENGAGE] __Abort + -- @param #AI_AIR_ENGAGE self -- @param #number Delay The delay in seconds. - self:AddTransition( "Engaging", "Accomplish", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_A2G_ENGAGE. + self:AddTransition( "Engaging", "Accomplish", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_AIR_ENGAGE. --- OnBefore Transition Handler for Event Accomplish. - -- @function [parent=#AI_A2G_ENGAGE] OnBeforeAccomplish - -- @param #AI_A2G_ENGAGE self + -- @function [parent=#AI_AIR_ENGAGE] OnBeforeAccomplish + -- @param #AI_AIR_ENGAGE self -- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM. -- @param #string From The From State string. -- @param #string Event The Event string. @@ -288,20 +290,20 @@ function AI_A2G_ENGAGE:New( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloor -- @return #boolean Return false to cancel Transition. --- OnAfter Transition Handler for Event Accomplish. - -- @function [parent=#AI_A2G_ENGAGE] OnAfterAccomplish - -- @param #AI_A2G_ENGAGE self + -- @function [parent=#AI_AIR_ENGAGE] OnAfterAccomplish + -- @param #AI_AIR_ENGAGE self -- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM. -- @param #string From The From State string. -- @param #string Event The Event string. -- @param #string To The To State string. --- Synchronous Event Trigger for Event Accomplish. - -- @function [parent=#AI_A2G_ENGAGE] Accomplish - -- @param #AI_A2G_ENGAGE self + -- @function [parent=#AI_AIR_ENGAGE] Accomplish + -- @param #AI_AIR_ENGAGE self --- Asynchronous Event Trigger for Event Accomplish. - -- @function [parent=#AI_A2G_ENGAGE] __Accomplish - -- @param #AI_A2G_ENGAGE self + -- @function [parent=#AI_AIR_ENGAGE] __Accomplish + -- @param #AI_AIR_ENGAGE self -- @param #number Delay The delay in seconds. self:AddTransition( { "Patrolling", "Engaging" }, "Refuel", "Refuelling" ) @@ -310,14 +312,14 @@ function AI_A2G_ENGAGE:New( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloor end --- onafter event handler for Start event. --- @param #AI_A2G_ENGAGE self +-- @param #AI_AIR_ENGAGE self -- @param Wrapper.Group#GROUP AIGroup The AI group managed by the FSM. -- @param #string From The From State string. -- @param #string Event The Event string. -- @param #string To The To State string. -function AI_A2G_ENGAGE:onafterStart( AIGroup, From, Event, To ) +function AI_AIR_ENGAGE:onafterStart( AIGroup, From, Event, To ) - self:GetParent( self, AI_A2G_ENGAGE ).onafterStart( self, AIGroup, From, Event, To ) + self:GetParent( self, AI_AIR_ENGAGE ).onafterStart( self, AIGroup, From, Event, To ) AIGroup:HandleEvent( EVENTS.Takeoff, nil, self ) @@ -326,12 +328,12 @@ end --- onafter event handler for Engage event. --- @param #AI_A2G_ENGAGE self +-- @param #AI_AIR_ENGAGE self -- @param Wrapper.Group#GROUP AIGroup The AI Group managed by the FSM. -- @param #string From The From State string. -- @param #string Event The Event string. -- @param #string To The To State string. -function AI_A2G_ENGAGE:onafterEngage( AIGroup, From, Event, To ) +function AI_AIR_ENGAGE:onafterEngage( AIGroup, From, Event, To ) self:HandleEvent( EVENTS.Dead ) @@ -339,39 +341,14 @@ end -- todo: need to fix this global function ---- @param Wrapper.Group#GROUP AIControllable -function AI_A2G_ENGAGE.___EngageRoute( AIGroup, Fsm, AttackSetUnit ) - - AIGroup:I( { "AI_A2G_ENGAGE.___EngageRoute:", AIGroup:GetName() } ) - - if AIGroup:IsAlive() then - Fsm:__EngageRoute( Fsm.TaskDelay, AttackSetUnit ) - - --local Task = AIGroup:TaskOrbitCircle( 4000, 400 ) - --AIGroup:SetTask( Task ) - end -end - ---- @param Wrapper.Group#GROUP AIControllable -function AI_A2G_ENGAGE.___Engage( AIGroup, Fsm, AttackSetUnit ) - - AIGroup:I( { "AI_A2G_ENGAGE.___Engage:", AIGroup:GetName() } ) - - if AIGroup:IsAlive() then - Fsm:__Engage( Fsm.TaskDelay, AttackSetUnit ) - - --local Task = AIGroup:TaskOrbitCircle( 4000, 400 ) - --AIGroup:SetTask( Task ) - end -end --- onbefore event handler for Engage event. --- @param #AI_A2G_ENGAGE self +-- @param #AI_AIR_ENGAGE self -- @param Wrapper.Group#GROUP AIGroup The group Object managed by the FSM. -- @param #string From The From State string. -- @param #string Event The Event string. -- @param #string To The To State string. -function AI_A2G_ENGAGE:onbeforeEngage( AIGroup, From, Event, To ) +function AI_AIR_ENGAGE:onbeforeEngage( AIGroup, From, Event, To ) if self.Accomplished == true then return false @@ -379,44 +356,44 @@ function AI_A2G_ENGAGE:onbeforeEngage( AIGroup, From, Event, To ) end --- onafter event handler for Abort event. --- @param #AI_A2G_ENGAGE self +-- @param #AI_AIR_ENGAGE self -- @param Wrapper.Group#GROUP AIGroup The AI Group managed by the FSM. -- @param #string From The From State string. -- @param #string Event The Event string. -- @param #string To The To State string. -function AI_A2G_ENGAGE:onafterAbort( AIGroup, From, Event, To ) +function AI_AIR_ENGAGE:onafterAbort( AIGroup, From, Event, To ) AIGroup:ClearTasks() self:Return() self:__RTB( self.TaskDelay ) end ---- @param #AI_A2G_ENGAGE self +--- @param #AI_AIR_ENGAGE self -- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM. -- @param #string From The From State string. -- @param #string Event The Event string. -- @param #string To The To State string. -function AI_A2G_ENGAGE:onafterAccomplish( AIGroup, From, Event, To ) +function AI_AIR_ENGAGE:onafterAccomplish( AIGroup, From, Event, To ) self.Accomplished = true self:SetDetectionOff() end ---- @param #AI_A2G_ENGAGE self +--- @param #AI_AIR_ENGAGE self -- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM. -- @param #string From The From State string. -- @param #string Event The Event string. -- @param #string To The To State string. -- @param Core.Event#EVENTDATA EventData -function AI_A2G_ENGAGE:onafterDestroy( AIGroup, From, Event, To, EventData ) +function AI_AIR_ENGAGE:onafterDestroy( AIGroup, From, Event, To, EventData ) if EventData.IniUnit then self.AttackUnits[EventData.IniUnit] = nil end end ---- @param #AI_A2G_ENGAGE self +--- @param #AI_AIR_ENGAGE self -- @param Core.Event#EVENTDATA EventData -function AI_A2G_ENGAGE:OnEventDead( EventData ) +function AI_AIR_ENGAGE:OnEventDead( EventData ) self:F( { "EventDead", EventData } ) if EventData.IniDCSUnit then @@ -426,14 +403,29 @@ function AI_A2G_ENGAGE:OnEventDead( EventData ) end end ---- @param #AI_A2G_ENGAGE self + +--- @param Wrapper.Group#GROUP AIControllable +function AI_AIR_ENGAGE.___EngageRoute( AIGroup, Fsm, AttackSetUnit ) + + Fsm:I( { "AI_AIR_ENGAGE.___EngageRoute:", AIGroup:GetName() } ) + + if AIGroup:IsAlive() then + Fsm:__EngageRoute( Fsm.TaskDelay, AttackSetUnit ) + + --local Task = AIGroup:TaskOrbitCircle( 4000, 400 ) + --AIGroup:SetTask( Task ) + end +end + + +--- @param #AI_AIR_ENGAGE self -- @param Wrapper.Group#GROUP DefenderGroup The GroupGroup managed by the FSM. -- @param #string From The From State string. -- @param #string Event The Event string. -- @param #string To The To State string. -function AI_A2G_ENGAGE:onafterEngageRoute( DefenderGroup, From, Event, To, AttackSetUnit ) +function AI_AIR_ENGAGE:onafterEngageRoute( DefenderGroup, From, Event, To, AttackSetUnit ) - self:F( { DefenderGroup, From, Event, To, AttackSetUnit} ) + self:I( { DefenderGroup, From, Event, To, AttackSetUnit } ) local DefenderGroupName = DefenderGroup:GetName() @@ -495,7 +487,7 @@ function AI_A2G_ENGAGE:onafterEngageRoute( DefenderGroup, From, Event, To, Attac EngageRoute[#EngageRoute+1] = ToWP - AttackTasks[#AttackTasks+1] = DefenderGroup:TaskFunction( "AI_A2G_ENGAGE.___EngageRoute", self, AttackSetUnit ) + AttackTasks[#AttackTasks+1] = DefenderGroup:TaskFunction( "AI_AIR_ENGAGE.___EngageRoute", self, AttackSetUnit ) EngageRoute[#EngageRoute].task = DefenderGroup:TaskCombo( AttackTasks ) DefenderGroup:OptionROEReturnFire() @@ -512,3 +504,113 @@ function AI_A2G_ENGAGE:onafterEngageRoute( DefenderGroup, From, Event, To, Attac end end + +--- @param Wrapper.Group#GROUP AIControllable +function AI_AIR_ENGAGE.___Engage( AIGroup, Fsm, AttackSetUnit ) + + Fsm:I( { "AI_AIR_ENGAGE.___Engage:", AIGroup:GetName() } ) + + if AIGroup:IsAlive() then + Fsm:__Engage( Fsm.TaskDelay, AttackSetUnit ) + end +end + + +--- @param #AI_AIR_ENGAGE self +-- @param Wrapper.Group#GROUP DefenderGroup The GroupGroup managed by the FSM. +-- @param #string From The From State string. +-- @param #string Event The Event string. +-- @param #string To The To State string. +function AI_AIR_ENGAGE:onafterEngage( DefenderGroup, From, Event, To, AttackSetUnit ) + + self:F( { DefenderGroup, From, Event, To, AttackSetUnit} ) + + local DefenderGroupName = DefenderGroup:GetName() + + self.AttackSetUnit = AttackSetUnit -- Kept in memory in case of resume from refuel in air! + + local AttackCount = AttackSetUnit:Count() + self:I({AttackCount = AttackCount}) + + if AttackCount > 0 then + + if DefenderGroup:IsAlive() then + + local EngageAltitude = math.random( self.EngageFloorAltitude or 500, self.EngageCeilingAltitude or 1000 ) + local EngageSpeed = math.random( self.EngageMinSpeed, self.EngageMaxSpeed ) + + local DefenderCoord = DefenderGroup:GetPointVec3() + DefenderCoord:SetY( EngageAltitude ) -- Ground targets don't have an altitude. + + local TargetCoord = AttackSetUnit:GetFirst():GetPointVec3() + TargetCoord:SetY( EngageAltitude ) -- Ground targets don't have an altitude. + + local TargetDistance = DefenderCoord:Get2DDistance( TargetCoord ) + local EngageDistance = ( DefenderGroup:IsHelicopter() and 5000 ) or ( DefenderGroup:IsAirPlane() and 10000 ) + + local EngageRoute = {} + local AttackTasks = {} + + local FromWP = DefenderCoord:WaypointAir( + self.EngageAltType or "RADIO", + POINT_VEC3.RoutePointType.TurningPoint, + POINT_VEC3.RoutePointAction.TurningPoint, + EngageSpeed, + true + ) + EngageRoute[#EngageRoute+1] = FromWP + + self:SetTargetDistance( TargetCoord ) -- For RTB status check + + local FromEngageAngle = DefenderCoord:GetAngleDegrees( DefenderCoord:GetDirectionVec3( TargetCoord ) ) + local ToWP = DefenderCoord:Translate( EngageDistance, FromEngageAngle, true ):WaypointAir( + self.EngageAltType or "RADIO", + POINT_VEC3.RoutePointType.TurningPoint, + POINT_VEC3.RoutePointAction.TurningPoint, + EngageSpeed, + true + ) + EngageRoute[#EngageRoute+1] = ToWP + + if TargetDistance <= EngageDistance * 3 then + + local AttackUnitTasks = self:CreateAttackUnitTasks( AttackSetUnit, DefenderGroup, EngageAltitude ) + + if #AttackUnitTasks == 0 then + self:I( DefenderGroupName .. ": No targets found -> Going RTB") + self:Return() + self:__RTB( self.TaskDelay ) + return + else + DefenderGroup:OptionROEOpenFire() + DefenderGroup:OptionROTEvadeFire() + DefenderGroup:OptionKeepWeaponsOnThreat() + + AttackTasks[#AttackTasks+1] = DefenderGroup:TaskCombo( AttackUnitTasks ) + end + end + + AttackTasks[#AttackTasks+1] = DefenderGroup:TaskFunction( "AI_AIR_ENGAGE.___Engage", self, AttackSetUnit ) + EngageRoute[#EngageRoute].task = DefenderGroup:TaskCombo( AttackTasks ) + + DefenderGroup:Route( EngageRoute, self.TaskDelay ) + + end + else + self:I( DefenderGroupName .. ": No targets found -> Going RTB") + self:Return() + self:__RTB( self.TaskDelay ) + return + end +end + +--- @param Wrapper.Group#GROUP AIEngage +function AI_AIR_ENGAGE.Resume( AIEngage, Fsm ) + + AIEngage:F( { "Resume:", AIEngage:GetName() } ) + if AIEngage:IsAlive() then + Fsm:__Reset( Fsm.TaskDelay ) + Fsm:__EngageRoute( Fsm.TaskDelay, Fsm.AttackSetUnit ) + end + +end diff --git a/Moose Development/Moose/AI/AI_A2G_Patrol.lua b/Moose Development/Moose/AI/AI_Air_Patrol.lua similarity index 56% rename from Moose Development/Moose/AI/AI_A2G_Patrol.lua rename to Moose Development/Moose/AI/AI_Air_Patrol.lua index 68e1954ca..53e439f8a 100644 --- a/Moose Development/Moose/AI/AI_A2G_Patrol.lua +++ b/Moose Development/Moose/AI/AI_Air_Patrol.lua @@ -6,19 +6,19 @@ -- -- === -- --- @module AI.AI_A2G_Patrol +-- @module AI.AI_Air_Patrol -- @image AI_Air_To_Ground_Patrol.JPG ---- @type AI_A2G_PATROL --- @extends AI.AI_A2G_Engage#AI_A2G_ENGAGE +--- @type AI_AIR_PATROL +-- @extends AI.AI_Air#AI_AIR ---- The AI_A2G_PATROL class implements the core functions to patrol a @{Zone} by an AI @{Wrapper.Group} or @{Wrapper.Group} +--- The AI_AIR_PATROL class implements the core functions to patrol a @{Zone} by an AI @{Wrapper.Group} or @{Wrapper.Group} -- and automatically engage any airborne enemies that are within a certain range or within a certain zone. -- -- ![Process](..\Presentations\AI_CAP\Dia3.JPG) -- --- The AI_A2G_PATROL is assigned a @{Wrapper.Group} and this must be done before the AI_A2G_PATROL process can be started using the **Start** event. +-- The AI_AIR_PATROL is assigned a @{Wrapper.Group} and this must be done before the AI_AIR_PATROL process can be started using the **Start** event. -- -- ![Process](..\Presentations\AI_CAP\Dia4.JPG) -- @@ -44,32 +44,32 @@ -- -- ![Process](..\Presentations\AI_CAP\Dia13.JPG) -- --- ## 1. AI_A2G_PATROL constructor +-- ## 1. AI_AIR_PATROL constructor -- --- * @{#AI_A2G_PATROL.New}(): Creates a new AI_A2G_PATROL object. +-- * @{#AI_AIR_PATROL.New}(): Creates a new AI_AIR_PATROL object. -- --- ## 2. AI_A2G_PATROL is a FSM +-- ## 2. AI_AIR_PATROL is a FSM -- -- ![Process](..\Presentations\AI_CAP\Dia2.JPG) -- --- ### 2.1 AI_A2G_PATROL States +-- ### 2.1 AI_AIR_PATROL States -- -- * **None** ( Group ): The process is not started yet. -- * **Patrolling** ( Group ): The AI is patrolling the Patrol Zone. -- * **Engaging** ( Group ): The AI is engaging the bogeys. -- * **Returning** ( Group ): The AI is returning to Base.. -- --- ### 2.2 AI_A2G_PATROL Events +-- ### 2.2 AI_AIR_PATROL Events -- -- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Start}**: Start the process. -- * **@{AI.AI_Patrol#AI_PATROL_ZONE.PatrolRoute}**: Route the AI to a new random 3D point within the Patrol Zone. --- * **@{#AI_A2G_PATROL.Engage}**: Let the AI engage the bogeys. --- * **@{#AI_A2G_PATROL.Abort}**: Aborts the engagement and return patrolling in the patrol zone. +-- * **@{#AI_AIR_PATROL.Engage}**: Let the AI engage the bogeys. +-- * **@{#AI_AIR_PATROL.Abort}**: Aborts the engagement and return patrolling in the patrol zone. -- * **@{AI.AI_Patrol#AI_PATROL_ZONE.RTB}**: Route the AI to the home base. -- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detect}**: The AI is detecting targets. -- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detected}**: The AI has detected new targets. --- * **@{#AI_A2G_PATROL.Destroy}**: The AI has destroyed a bogey @{Wrapper.Unit}. --- * **@{#AI_A2G_PATROL.Destroyed}**: The AI has destroyed all bogeys @{Wrapper.Unit}s assigned in the CAS task. +-- * **@{#AI_AIR_PATROL.Destroy}**: The AI has destroyed a bogey @{Wrapper.Unit}. +-- * **@{#AI_AIR_PATROL.Destroyed}**: The AI has destroyed all bogeys @{Wrapper.Unit}s assigned in the CAS task. -- * **Status** ( Group ): The AI is checking status (fuel and damage). When the tresholds have been reached, the AI will RTB. -- -- ## 3. Set the Range of Engagement @@ -80,7 +80,7 @@ -- that will define when the AI will engage with the detected airborne enemy targets. -- The range can be beyond or smaller than the range of the Patrol Zone. -- The range is applied at the position of the AI. --- Use the method @{AI.AI_CAP#AI_A2G_PATROL.SetEngageRange}() to define that range. +-- Use the method @{AI.AI_CAP#AI_AIR_PATROL.SetEngageRange}() to define that range. -- -- ## 4. Set the Zone of Engagement -- @@ -88,33 +88,29 @@ -- -- An optional @{Zone} can be set, -- that will define when the AI will engage with the detected airborne enemy targets. --- Use the method @{AI.AI_Cap#AI_A2G_PATROL.SetEngageZone}() to define that Zone. +-- Use the method @{AI.AI_Cap#AI_AIR_PATROL.SetEngageZone}() to define that Zone. -- -- === -- --- @field #AI_A2G_PATROL -AI_A2G_PATROL = { - ClassName = "AI_A2G_PATROL", +-- @field #AI_AIR_PATROL +AI_AIR_PATROL = { + ClassName = "AI_AIR_PATROL", } ---- Creates a new AI_A2G_PATROL object --- @param #AI_A2G_PATROL self +--- Creates a new AI_AIR_PATROL object +-- @param #AI_AIR_PATROL self -- @param Wrapper.Group#GROUP AIGroup --- @param DCS#Speed EngageMinSpeed (optional, default = 50% of max speed) The minimum speed of the @{Wrapper.Group} in km/h when engaging a target. --- @param DCS#Speed EngageMaxSpeed (optional, default = 75% of max speed) The maximum speed of the @{Wrapper.Group} in km/h when engaging a target. --- @param DCS#Altitude EngageFloorAltitude (optional, default = 1000m ) The lowest altitude in meters where to execute the engagement. --- @param DCS#Altitude EngageCeilingAltitude (optional, default = 1500m ) The highest altitude in meters where to execute the engagement. -- @param Core.Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed. -- @param DCS#Altitude PatrolFloorAltitude (optional, default = 1000m ) The lowest altitude in meters where to execute the patrol. -- @param DCS#Altitude PatrolCeilingAltitude (optional, default = 1500m ) The highest altitude in meters where to execute the patrol. -- @param DCS#Speed PatrolMinSpeed (optional, default = 50% of max speed) The minimum speed of the @{Wrapper.Group} in km/h. -- @param DCS#Speed PatrolMaxSpeed (optional, default = 75% of max speed) The maximum speed of the @{Wrapper.Group} in km/h. -- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO. --- @return #AI_A2G_PATROL -function AI_A2G_PATROL:New( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) +-- @return #AI_AIR_PATROL +function AI_AIR_PATROL:New( AI_Air, AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) -- Inherits from BASE - local self = BASE:Inherit( self, AI_A2G_ENGAGE:New( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude ) ) -- #AI_A2G_PATROL + local self = BASE:Inherit( self, AI_Air ) -- #AI_AIR_PATROL local SpeedMax = AIGroup:GetSpeedMax() @@ -131,8 +127,8 @@ function AI_A2G_PATROL:New( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloor self:AddTransition( { "Started", "Airborne", "Refuelling" }, "Patrol", "Patrolling" ) --- OnBefore Transition Handler for Event Patrol. - -- @function [parent=#AI_A2G_PATROL] OnBeforePatrol - -- @param #AI_A2G_PATROL self + -- @function [parent=#AI_AIR_PATROL] OnBeforePatrol + -- @param #AI_AIR_PATROL self -- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM. -- @param #string From The From State string. -- @param #string Event The Event string. @@ -140,25 +136,25 @@ function AI_A2G_PATROL:New( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloor -- @return #boolean Return false to cancel Transition. --- OnAfter Transition Handler for Event Patrol. - -- @function [parent=#AI_A2G_PATROL] OnAfterPatrol - -- @param #AI_A2G_PATROL self + -- @function [parent=#AI_AIR_PATROL] OnAfterPatrol + -- @param #AI_AIR_PATROL self -- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM. -- @param #string From The From State string. -- @param #string Event The Event string. -- @param #string To The To State string. --- Synchronous Event Trigger for Event Patrol. - -- @function [parent=#AI_A2G_PATROL] Patrol - -- @param #AI_A2G_PATROL self + -- @function [parent=#AI_AIR_PATROL] Patrol + -- @param #AI_AIR_PATROL self --- Asynchronous Event Trigger for Event Patrol. - -- @function [parent=#AI_A2G_PATROL] __Patrol - -- @param #AI_A2G_PATROL self + -- @function [parent=#AI_AIR_PATROL] __Patrol + -- @param #AI_AIR_PATROL self -- @param #number Delay The delay in seconds. --- OnLeave Transition Handler for State Patrolling. - -- @function [parent=#AI_A2G_PATROL] OnLeavePatrolling - -- @param #AI_A2G_PATROL self + -- @function [parent=#AI_AIR_PATROL] OnLeavePatrolling + -- @param #AI_AIR_PATROL self -- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM. -- @param #string From The From State string. -- @param #string Event The Event string. @@ -166,18 +162,18 @@ function AI_A2G_PATROL:New( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloor -- @return #boolean Return false to cancel Transition. --- OnEnter Transition Handler for State Patrolling. - -- @function [parent=#AI_A2G_PATROL] OnEnterPatrolling - -- @param #AI_A2G_PATROL self + -- @function [parent=#AI_AIR_PATROL] OnEnterPatrolling + -- @param #AI_AIR_PATROL self -- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM. -- @param #string From The From State string. -- @param #string Event The Event string. -- @param #string To The To State string. - self:AddTransition( "Patrolling", "PatrolRoute", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_A2G_PATROL. + self:AddTransition( "Patrolling", "PatrolRoute", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_AIR_PATROL. --- OnBefore Transition Handler for Event PatrolRoute. - -- @function [parent=#AI_A2G_PATROL] OnBeforePatrolRoute - -- @param #AI_A2G_PATROL self + -- @function [parent=#AI_AIR_PATROL] OnBeforePatrolRoute + -- @param #AI_AIR_PATROL self -- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM. -- @param #string From The From State string. -- @param #string Event The Event string. @@ -185,34 +181,34 @@ function AI_A2G_PATROL:New( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloor -- @return #boolean Return false to cancel Transition. --- OnAfter Transition Handler for Event PatrolRoute. - -- @function [parent=#AI_A2G_PATROL] OnAfterPatrolRoute - -- @param #AI_A2G_PATROL self + -- @function [parent=#AI_AIR_PATROL] OnAfterPatrolRoute + -- @param #AI_AIR_PATROL self -- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM. -- @param #string From The From State string. -- @param #string Event The Event string. -- @param #string To The To State string. --- Synchronous Event Trigger for Event PatrolRoute. - -- @function [parent=#AI_A2G_PATROL] PatrolRoute - -- @param #AI_A2G_PATROL self + -- @function [parent=#AI_AIR_PATROL] PatrolRoute + -- @param #AI_AIR_PATROL self --- Asynchronous Event Trigger for Event PatrolRoute. - -- @function [parent=#AI_A2G_PATROL] __PatrolRoute - -- @param #AI_A2G_PATROL self + -- @function [parent=#AI_AIR_PATROL] __PatrolRoute + -- @param #AI_AIR_PATROL self -- @param #number Delay The delay in seconds. - self:AddTransition( "*", "Reset", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_A2G_PATROL. + self:AddTransition( "*", "Reset", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_AIR_PATROL. return self end --- Set the Engage Range when the AI will engage with airborne enemies. --- @param #AI_A2G_PATROL self +-- @param #AI_AIR_PATROL self -- @param #number EngageRange The Engage Range. --- @return #AI_A2G_PATROL self -function AI_A2G_PATROL:SetEngageRange( EngageRange ) +-- @return #AI_AIR_PATROL self +function AI_AIR_PATROL:SetEngageRange( EngageRange ) self:F2() if EngageRange then @@ -222,14 +218,44 @@ function AI_A2G_PATROL:SetEngageRange( EngageRange ) end end +--- Set race track parameters. CAP flights will perform race track patterns rather than randomly patrolling the zone. +-- @param #AI_AIR_PATROL self +-- @param #number LegMin Min Length of the race track leg in meters. Default 10,000 m. +-- @param #number LegMax Max length of the race track leg in meters. Default 15,000 m. +-- @param #number HeadingMin Min heading of the race track in degrees. Default 0 deg, i.e. from South to North. +-- @param #number HeadingMax Max heading of the race track in degrees. Default 180 deg, i.e. from South to North. +-- @param #number DurationMin (Optional) Min duration before switching the orbit position. Default is keep same orbit until RTB or engage. +-- @param #number DurationMax (Optional) Max duration before switching the orbit position. Default is keep same orbit until RTB or engage. +-- @param #table CapCoordinates Table of coordinates of first race track point. Second point is determined by leg length and heading. +-- @return #AI_AIR_PATROL self +function AI_AIR_PATROL:SetRaceTrackPattern(LegMin, LegMax, HeadingMin, HeadingMax, DurationMin, DurationMax, CapCoordinates) + + self.racetrack=true + self.racetracklegmin=LegMin or 10000 + self.racetracklegmax=LegMax or 15000 + self.racetrackheadingmin=HeadingMin or 0 + self.racetrackheadingmax=HeadingMax or 180 + self.racetrackdurationmin=DurationMin + self.racetrackdurationmax=DurationMax + + if self.racetrackdurationmax and not self.racetrackdurationmin then + self.racetrackdurationmin=self.racetrackdurationmax + end + + self.racetrackcapcoordinates=CapCoordinates + +end + + + --- Defines a new patrol route using the @{Process_PatrolZone} parameters and settings. --- @param #AI_A2G_PATROL self --- @return #AI_A2G_PATROL self +-- @param #AI_AIR_PATROL self +-- @return #AI_AIR_PATROL self -- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM. -- @param #string From The From State string. -- @param #string Event The Event string. -- @param #string To The To State string. -function AI_A2G_PATROL:onafterPatrol( AIPatrol, From, Event, To ) +function AI_AIR_PATROL:onafterPatrol( AIPatrol, From, Event, To ) self:F2() self:ClearTargetDistance() @@ -247,9 +273,9 @@ end --- @param Wrapper.Group#GROUP AIPatrol -- This statis method is called from the route path within the last task at the last waaypoint of the AIPatrol. -- Note that this method is required, as triggers the next route when patrolling for the AIPatrol. -function AI_A2G_PATROL.___PatrolRoute( AIPatrol, Fsm ) +function AI_AIR_PATROL.___PatrolRoute( AIPatrol, Fsm ) - AIPatrol:F( { "AI_A2G_PATROL.___PatrolRoute:", AIPatrol:GetName() } ) + AIPatrol:F( { "AI_AIR_PATROL.___PatrolRoute:", AIPatrol:GetName() } ) if AIPatrol:IsAlive() then Fsm:PatrolRoute() @@ -258,12 +284,12 @@ function AI_A2G_PATROL.___PatrolRoute( AIPatrol, Fsm ) end --- Defines a new patrol route using the @{Process_PatrolZone} parameters and settings. --- @param #AI_A2G_PATROL self +-- @param #AI_AIR_PATROL self -- @param Wrapper.Group#GROUP AIPatrol The Group managed by the FSM. -- @param #string From The From State string. -- @param #string Event The Event string. -- @param #string To The To State string. -function AI_A2G_PATROL:onafterPatrolRoute( AIPatrol, From, Event, To ) +function AI_AIR_PATROL:onafterPatrolRoute( AIPatrol, From, Event, To ) self:F2() @@ -295,34 +321,82 @@ function AI_A2G_PATROL:onafterPatrolRoute( AIPatrol, From, Event, To ) true ) PatrolRoute[#PatrolRoute+1] = FromWP - - --- Create a route point of type air. - local ToWP = ToTargetCoord:WaypointAir( - self.PatrolAltType, - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, - ToTargetSpeed, - true - ) - PatrolRoute[#PatrolRoute+1] = ToWP - - local Tasks = {} - Tasks[#Tasks+1] = AIPatrol:TaskFunction( "AI_A2G_PATROL.___PatrolRoute", self ) - PatrolRoute[#PatrolRoute].task = AIPatrol:TaskCombo( Tasks ) - - AIPatrol:OptionROEReturnFire() - AIPatrol:OptionROTEvadeFire() + if self.racetrack then + + -- Random heading. + local heading = math.random(self.racetrackheadingmin, self.racetrackheadingmax) + + -- Random leg length. + local leg=math.random(self.racetracklegmin, self.racetracklegmax) + + -- Random duration if any. + local duration = self.racetrackdurationmin + if self.racetrackdurationmax then + duration=math.random(self.racetrackdurationmin, self.racetrackdurationmax) + end + + -- CAP coordinate. + local c0=self.PatrolZone:GetRandomCoordinate() + if self.racetrackcapcoordinates and #self.racetrackcapcoordinates>0 then + c0=self.racetrackcapcoordinates[math.random(#self.racetrackcapcoordinates)] + end + + -- Race track points. + local c1=c0:SetAltitude(altitude) --Core.Point#COORDINATE + local c2=c1:Translate(leg, heading):SetAltitude(altitude) + + self:SetTargetDistance(c0) -- For RTB status check + + -- Debug: + self:T(string.format("Patrol zone race track: v=%.1f knots, h=%.1f ft, heading=%03d, leg=%d m, t=%s sec", UTILS.KmphToKnots(speedkmh), UTILS.MetersToFeet(altitude), heading, leg, tostring(duration))) + --c1:MarkToAll("Race track c1") + --c2:MarkToAll("Race track c2") - AIPatrol:Route( PatrolRoute, self.TaskDelay ) + -- Task to orbit. + local taskOrbit=AIPatrol:TaskOrbit(c1, altitude, UTILS.KmphToMps(speedkmh), c2) + + -- Task function to redo the patrol at other random position. + local taskPatrol=AIPatrol:TaskFunction("AI_A2A_PATROL.PatrolRoute", self) + + -- Controlled task with task condition. + local taskCond=AIPatrol:TaskCondition(nil, nil, nil, nil, duration, nil) + local taskCont=AIPatrol:TaskControlled(taskOrbit, taskCond) + + -- Second waypoint + PatrolRoute[2]=c1:WaypointAirTurningPoint(self.PatrolAltType, speedkmh, {taskCont, taskPatrol}, "CAP Orbit") + + else + + --- Create a route point of type air. + local ToWP = ToTargetCoord:WaypointAir( + self.PatrolAltType, + POINT_VEC3.RoutePointType.TurningPoint, + POINT_VEC3.RoutePointAction.TurningPoint, + ToTargetSpeed, + true + ) + + PatrolRoute[#PatrolRoute+1] = ToWP + + local Tasks = {} + Tasks[#Tasks+1] = AIPatrol:TaskFunction( "AI_AIR_PATROL.___PatrolRoute", self ) + PatrolRoute[#PatrolRoute].task = AIPatrol:TaskCombo( Tasks ) + + AIPatrol:OptionROEReturnFire() + AIPatrol:OptionROTEvadeFire() + + AIPatrol:Route( PatrolRoute, self.TaskDelay ) + end + end end --- @param Wrapper.Group#GROUP AIPatrol -function AI_A2G_PATROL.Resume( AIPatrol, Fsm ) +function AI_AIR_PATROL.Resume( AIPatrol, Fsm ) - AIPatrol:F( { "AI_A2G_PATROL.Resume:", AIPatrol:GetName() } ) + AIPatrol:F( { "AI_AIR_PATROL.Resume:", AIPatrol:GetName() } ) if AIPatrol:IsAlive() then Fsm:__Reset( Fsm.TaskDelay ) Fsm:__PatrolRoute( Fsm.TaskDelay ) diff --git a/Moose Development/Moose/Core/RadioSpeech.lua b/Moose Development/Moose/Core/RadioSpeech.lua index 618cb5161..cb8ce9cfd 100644 --- a/Moose Development/Moose/Core/RadioSpeech.lua +++ b/Moose Development/Moose/Core/RadioSpeech.lua @@ -58,6 +58,14 @@ RADIOSPEECH.Vocabulary.EN = { ["80"] = { "80", 0.26 }, ["90"] = { "90", 0.36 }, ["100"] = { "100", 0.55 }, + ["200"] = { "200", 0.55 }, + ["300"] = { "300", 0.61 }, + ["400"] = { "400", 0.60 }, + ["500"] = { "500", 0.61 }, + ["600"] = { "600", 0.65 }, + ["700"] = { "700", 0.70 }, + ["800"] = { "800", 0.54 }, + ["900"] = { "900", 0.60 }, ["1000"] = { "1000", 1 }, ["chevy"] = { "chevy", 0.35 }, @@ -70,7 +78,6 @@ RADIOSPEECH.Vocabulary.EN = { ["uzi"] = { "uzi", 0.28 }, ["degrees"] = { "degrees", 0.5 }, - ["�"] = { "degrees", 0.5 }, ["kilometers"] = { "kilometers", 0.65 }, ["km"] = { "kilometers", 0.65 }, ["miles"] = { "miles", 0.45 }, @@ -81,7 +88,7 @@ RADIOSPEECH.Vocabulary.EN = { ["returning to base"] = { "returning_to_base", 0.85 }, - ["moving on to ground target"] = { "moving_on_to_ground_target", 1.20 }, + ["on route to ground target"] = { "on_route_to_ground_target", 1.05 }, ["engaging ground target"] = { "engaging_ground_target", 1.20 }, ["wheels up"] = { "wheels_up", 0.42 }, ["landing at base"] = { "landing at base", 0.8 }, diff --git a/Moose Development/Moose/Modules.lua b/Moose Development/Moose/Modules.lua index 0a499fe25..da8a13e96 100644 --- a/Moose Development/Moose/Modules.lua +++ b/Moose Development/Moose/Modules.lua @@ -71,17 +71,15 @@ __Moose.Include( 'Scripts/Moose/Ops/RescueHelo.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_Balancer.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_Air.lua' ) -__Moose.Include( 'Scripts/Moose/AI/AI_A2A.lua' ) +__Moose.Include( 'Scripts/Moose/AI/AI_Air_Patrol.lua' ) +__Moose.Include( 'Scripts/Moose/AI/AI_Air_Engage.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_A2A_Patrol.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_A2A_Cap.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_A2A_Gci.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_A2A_Dispatcher.lua' ) -__Moose.Include( 'Scripts/Moose/AI/AI_A2G.lua' ) -__Moose.Include( 'Scripts/Moose/AI/AI_A2G_Engage.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_A2G_BAI.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_A2G_CAS.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_A2G_SEAD.lua' ) -__Moose.Include( 'Scripts/Moose/AI/AI_A2G_Patrol.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_A2G_Dispatcher.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_Patrol.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_Cap.lua' ) From fb875077d76a58ad85da3d8bbf96ec9176db954d Mon Sep 17 00:00:00 2001 From: FlightControl Date: Sun, 22 Sep 2019 20:30:59 +0200 Subject: [PATCH 4/7] Speech optimizations for A2A dispatcher --- .../Moose/AI/AI_A2A_Dispatcher.lua | 66 ++++++++++++++----- Moose Development/Moose/Core/RadioQueue.lua | 2 +- Moose Development/Moose/Core/RadioSpeech.lua | 13 +++- 3 files changed, 61 insertions(+), 20 deletions(-) diff --git a/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua b/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua index 43d74920d..b03fec008 100644 --- a/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua +++ b/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua @@ -3157,15 +3157,15 @@ do -- AI_A2A_DISPATCHER if DefenderCAP then - local Fsm = AI_A2A_CAP:New2( DefenderCAP, Cap.EngageMinSpeed, Cap.EngageMaxSpeed, Cap.EngageFloorAltitude, Cap.EngageCeilingAltitude, Cap.EngageAltType, Cap.Zone, Cap.PatrolMinSpeed, Cap.PatrolMaxSpeed, Cap.PatrolFloorAltitude, Cap.PatrolCeilingAltitude, Cap.PatrolAltType ) - Fsm:SetDispatcher( self ) - Fsm:SetHomeAirbase( DefenderSquadron.Airbase ) - Fsm:SetFuelThreshold( DefenderSquadron.FuelThreshold or self.DefenderDefault.FuelThreshold, 60 ) - Fsm:SetDamageThreshold( self.DefenderDefault.DamageThreshold ) - Fsm:SetDisengageRadius( self.DisengageRadius ) - Fsm:SetTanker( DefenderSquadron.TankerName or self.DefenderDefault.TankerName ) + local AI_A2A_Fsm = AI_A2A_CAP:New2( DefenderCAP, Cap.EngageMinSpeed, Cap.EngageMaxSpeed, Cap.EngageFloorAltitude, Cap.EngageCeilingAltitude, Cap.EngageAltType, Cap.Zone, Cap.PatrolMinSpeed, Cap.PatrolMaxSpeed, Cap.PatrolFloorAltitude, Cap.PatrolCeilingAltitude, Cap.PatrolAltType ) + AI_A2A_Fsm:SetDispatcher( self ) + AI_A2A_Fsm:SetHomeAirbase( DefenderSquadron.Airbase ) + AI_A2A_Fsm:SetFuelThreshold( DefenderSquadron.FuelThreshold or self.DefenderDefault.FuelThreshold, 60 ) + AI_A2A_Fsm:SetDamageThreshold( self.DefenderDefault.DamageThreshold ) + AI_A2A_Fsm:SetDisengageRadius( self.DisengageRadius ) + AI_A2A_Fsm:SetTanker( DefenderSquadron.TankerName or self.DefenderDefault.TankerName ) if DefenderSquadron.Racetrack or self.DefenderDefault.Racetrack then - Fsm:SetRaceTrackPattern(DefenderSquadron.RacetrackLengthMin or self.DefenderDefault.RacetrackLengthMin, + AI_A2A_Fsm:SetRaceTrackPattern(DefenderSquadron.RacetrackLengthMin or self.DefenderDefault.RacetrackLengthMin, DefenderSquadron.RacetrackLengthMax or self.DefenderDefault.RacetrackLengthMax, DefenderSquadron.RacetrackHeadingMin or self.DefenderDefault.RacetrackHeadingMin, DefenderSquadron.RacetrackHeadingMax or self.DefenderDefault.RacetrackHeadingMax, @@ -3173,25 +3173,39 @@ do -- AI_A2A_DISPATCHER DefenderSquadron.RacetrackDurationMax or self.DefenderDefault.RacetrackDurationMax, DefenderSquadron.RacetrackCoordinates or self.DefenderDefault.RacetrackCoordinates) end - Fsm:Start() + AI_A2A_Fsm:Start() - self:SetDefenderTask( SquadronName, DefenderCAP, "CAP", Fsm ) + self:SetDefenderTask( SquadronName, DefenderCAP, "CAP", AI_A2A_Fsm ) - function Fsm:onafterTakeoff( DefenderGroup, From, Event, To ) + function AI_A2A_Fsm:onafterTakeoff( DefenderGroup, From, Event, To ) self:F({"CAP Takeoff", DefenderGroup:GetName()}) --self:GetParent(self).onafterBirth( self, Defender, From, Event, To ) local DefenderName = DefenderGroup:GetCallsign() - local Dispatcher = Fsm:GetDispatcher() -- #AI_A2A_DISPATCHER + local Dispatcher = AI_A2A_Fsm:GetDispatcher() -- #AI_A2A_DISPATCHER local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) if Squadron then Dispatcher:MessageToPlayers( DefenderName .. " Wheels up.", DefenderGroup ) - Fsm:__Patrol( 2 ) -- Start Patrolling + AI_A2A_Fsm:__Patrol( 2 ) -- Start Patrolling end end - function Fsm:onafterRTB( DefenderGroup, From, Event, To ) + function AI_A2A_Fsm:onafterPatrolRoute( DefenderGroup, From, Event, To ) + self:F({"CAP PatrolRoute", DefenderGroup:GetName()}) + self:GetParent(self).onafterPatrolRoute( self, DefenderGroup, From, Event, To ) + + local DefenderName = DefenderGroup:GetCallsign() + local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER + local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) + if Squadron then + Dispatcher:MessageToPlayers( DefenderName .. ", patrolling.", DefenderGroup ) + end + + Dispatcher:ClearDefenderTaskTarget( DefenderGroup ) + end + + function AI_A2A_Fsm:onafterRTB( DefenderGroup, From, Event, To ) self:F({"CAP RTB", DefenderGroup:GetName()}) @@ -3205,7 +3219,7 @@ do -- AI_A2A_DISPATCHER end --- @param #AI_A2A_DISPATCHER self - function Fsm:onafterHome( Defender, From, Event, To, Action ) + function AI_A2A_Fsm:onafterHome( Defender, From, Event, To, Action ) self:F({"CAP Home", Defender:GetName()}) self:GetParent(self).onafterHome( self, Defender, From, Event, To ) @@ -3278,7 +3292,7 @@ do -- AI_A2A_DISPATCHER for DefenderID, DefenderGroup in pairs( DefenderFriendlies or {} ) do local Fsm = self:GetDefenderTaskFsm( DefenderGroup ) - Fsm:__EngageRoute( 1, AttackerSet ) -- Engage on the TargetSetUnit + Fsm:__EngageRoute( 0.1, AttackerSet ) -- Engage on the TargetSetUnit self:SetDefenderTaskTarget( DefenderGroup, AttackerDetection ) @@ -3395,7 +3409,7 @@ do -- AI_A2A_DISPATCHER local Dispatcher = Fsm:GetDispatcher() -- #AI_A2A_DISPATCHER local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - if Squadron then + if Squadron and AttackSetUnit:Count() > 0 then local FirstUnit = AttackSetUnit:GetFirst() local Coordinate = FirstUnit:GetCoordinate() -- Core.Point#COORDINATE @@ -3404,6 +3418,22 @@ do -- AI_A2A_DISPATCHER self:GetParent( Fsm ).onafterEngageRoute( self, DefenderGroup, From, Event, To, AttackSetUnit ) end + function Fsm:onafterEngage( DefenderGroup, From, Event, To, AttackSetUnit ) + self:F({"GCI Engage", DefenderGroup:GetName()}) + + local DefenderName = DefenderGroup:GetCallsign() + local Dispatcher = Fsm:GetDispatcher() -- #AI_A2A_DISPATCHER + local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) + + if Squadron and AttackSetUnit:Count() > 0 then + local FirstUnit = AttackSetUnit:GetFirst() + local Coordinate = FirstUnit:GetCoordinate() -- Core.Point#COORDINATE + + Dispatcher:MessageToPlayers( DefenderName .. ", engaging bogeys at " .. Coordinate:ToStringA2A( DefenderGroup ), DefenderGroup ) + end + self:GetParent( Fsm ).onafterEngage( self, DefenderGroup, From, Event, To, AttackSetUnit ) + end + function Fsm:onafterRTB( DefenderGroup, From, Event, To ) self:F({"GCI RTB", DefenderGroup:GetName()}) self:GetParent(self).onafterRTB( self, DefenderGroup, From, Event, To ) @@ -3437,7 +3467,7 @@ do -- AI_A2A_DISPATCHER local Dispatcher = self:GetDispatcher() -- #AI_A2A_DISPATCHER local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " landing at base.", DefenderGroup ) + Dispatcher:MessageToPlayers( DefenderName .. " landing at base.", DefenderGroup ) if Action and Action == "Destroy" then Dispatcher:RemoveDefenderFromSquadron( Squadron, DefenderGroup ) diff --git a/Moose Development/Moose/Core/RadioQueue.lua b/Moose Development/Moose/Core/RadioQueue.lua index 8ee0d7749..f1980102c 100644 --- a/Moose Development/Moose/Core/RadioQueue.lua +++ b/Moose Development/Moose/Core/RadioQueue.lua @@ -470,7 +470,7 @@ function RADIOQUEUE:_GetRadioSenderCoord() end -- Now try a static. - local sender=STATIC:FindByName(self.sendername) + local sender=STATIC:FindByName( self.sendername, false ) -- Check that sender is alive and an aircraft. if sender then diff --git a/Moose Development/Moose/Core/RadioSpeech.lua b/Moose Development/Moose/Core/RadioSpeech.lua index cb8ce9cfd..e54068c3c 100644 --- a/Moose Development/Moose/Core/RadioSpeech.lua +++ b/Moose Development/Moose/Core/RadioSpeech.lua @@ -66,7 +66,15 @@ RADIOSPEECH.Vocabulary.EN = { ["700"] = { "700", 0.70 }, ["800"] = { "800", 0.54 }, ["900"] = { "900", 0.60 }, - ["1000"] = { "1000", 1 }, + ["1000"] = { "1000", 0.60 }, + ["2000"] = { "2000", 0.61 }, + ["3000"] = { "3000", 0.64 }, + ["4000"] = { "4000", 0.62 }, + ["5000"] = { "5000", 0.69 }, + ["6000"] = { "6000", 0.69 }, + ["7000"] = { "7000", 0.75 }, + ["8000"] = { "8000", 0.59 }, + ["9000"] = { "9000", 0.65 }, ["chevy"] = { "chevy", 0.35 }, ["colt"] = { "colt", 0.35 }, @@ -81,6 +89,7 @@ RADIOSPEECH.Vocabulary.EN = { ["kilometers"] = { "kilometers", 0.65 }, ["km"] = { "kilometers", 0.65 }, ["miles"] = { "miles", 0.45 }, + ["meters"] = { "meters", 0.41 }, ["mi"] = { "miles", 0.45 }, ["br"] = { "br", 1.1 }, @@ -89,7 +98,9 @@ RADIOSPEECH.Vocabulary.EN = { ["returning to base"] = { "returning_to_base", 0.85 }, ["on route to ground target"] = { "on_route_to_ground_target", 1.05 }, + ["intercepting bogeys"] = { "intercepting_bogeys", 1.00 }, ["engaging ground target"] = { "engaging_ground_target", 1.20 }, + ["engaging bogeys"] = { "engaging_bogeys", 0.81 }, ["wheels up"] = { "wheels_up", 0.42 }, ["landing at base"] = { "landing at base", 0.8 }, ["patrolling"] = { "patrolling", 0.55 }, From 5cdaf53727e5931bd4fa56e10b11e3a85c445e73 Mon Sep 17 00:00:00 2001 From: FlightControl Date: Wed, 25 Sep 2019 17:53:11 +0300 Subject: [PATCH 5/7] - Added a NoTrace option to the scheduler, so for those schedulers that have a schedule in microseconds, you may wanna use the function NoTrace(), to avoid spamming the dcs.log. - Started with the implementation of multiple languages of speech. Got now a Russian and English prototype working. - Moved radio frequency settings to a squadron, so multiple squadrons can communicate in their own radio frequency. --- .../Moose/AI/AI_A2A_Dispatcher.lua | 98 +++++++- .../Moose/AI/AI_A2G_Dispatcher.lua | 51 +++- .../Moose/AI/AI_Air_Dispatcher.lua | 20 +- Moose Development/Moose/Core/Point.lua | 65 +++-- Moose Development/Moose/Core/RadioQueue.lua | 1 + Moose Development/Moose/Core/RadioSpeech.lua | 229 ++++++++++++------ .../Moose/Core/ScheduleDispatcher.lua | 13 +- Moose Development/Moose/Core/Scheduler.lua | 8 + .../Moose/Tasking/DetectionManager.lua | 32 +-- Moose Development/Moose/Wrapper/Client.lua | 1 + 10 files changed, 359 insertions(+), 159 deletions(-) diff --git a/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua b/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua index b03fec008..ddf3192f3 100644 --- a/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua +++ b/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua @@ -1685,6 +1685,8 @@ do -- AI_A2A_DISPATCHER DefenderSquadron.TemplatePrefixes = TemplatePrefixes DefenderSquadron.Captured = false -- Not captured. This flag will be set to true, when the airbase where the squadron is located, is captured. + self:SetSquadronLanguage( SquadronName, "EN" ) -- Squadrons speak English by default. + self:F( { Squadron = {SquadronName, AirbaseName, TemplatePrefixes, ResourceCount } } ) return self @@ -2830,6 +2832,59 @@ do -- AI_A2A_DISPATCHER end + --- Set the squadron language. + -- @param #AI_A2A_DISPATCHER self + -- @param #string SquadronName The name of the squadron. + -- @param #string Language A string defining the language to be embedded within the miz file. + -- @return #AI_A2A_DISPATCHER + -- @usage + -- + -- -- Now Setup the A2A dispatcher, and initialize it using the Detection object. + -- A2ADispatcher = AI_A2A_DISPATCHER:New( Detection ) + -- + -- -- Set for English. + -- A2ADispatcher:SetSquadronLanguage( "SquadronName", "EN" ) -- This squadron speaks English. + -- + -- -- Set for Russian. + -- A2ADispatcher:SetSquadronLanguage( "SquadronName", "RU" ) -- This squadron speaks Russian. + function AI_A2A_DISPATCHER:SetSquadronLanguage( SquadronName, Language ) + + local DefenderSquadron = self:GetSquadron( SquadronName ) + DefenderSquadron.Language = Language + + if DefenderSquadron.RadioQueue then + DefenderSquadron.RadioQueue:SetLanguage( Language ) + end + + return self + end + + + --- Set the frequency of communication and the mode of communication for voice overs. + -- @param #AI_A2A_DISPATCHER self + -- @param #string SquadronName The name of the squadron. + -- @param #number RadioFrequency The frequency of communication. + -- @param #number RadioModulation The modulation of communication. + -- @param #number RadioPower The power in Watts of communication. + function AI_A2A_DISPATCHER:SetSquadronRadioFrequency( SquadronName, RadioFrequency, RadioModulation, RadioPower ) + + local DefenderSquadron = self:GetSquadron( SquadronName ) + DefenderSquadron.RadioFrequency = RadioFrequency + DefenderSquadron.RadioModulation = RadioModulation or radio.modulation.AM + DefenderSquadron.RadioPower = RadioPower or 100 + + if DefenderSquadron.RadioQueue then + DefenderSquadron.RadioQueue:Stop() + end + + DefenderSquadron.RadioQueue = nil + + DefenderSquadron.RadioQueue = RADIOSPEECH:New( DefenderSquadron.RadioFrequency, DefenderSquadron.RadioModulation ) + DefenderSquadron.RadioQueue.power = DefenderSquadron.RadioPower + DefenderSquadron.RadioQueue:Start( 0.5 ) + + DefenderSquadron.RadioQueue:SetLanguage( DefenderSquadron.Language ) + end --- Add defender to squadron. Resource count will get smaller. -- @param #AI_A2A_DISPATCHER self @@ -3186,7 +3241,7 @@ do -- AI_A2A_DISPATCHER local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) if Squadron then - Dispatcher:MessageToPlayers( DefenderName .. " Wheels up.", DefenderGroup ) + Dispatcher:MessageToPlayers( Squadron, DefenderName .. " Wheels up.", DefenderGroup ) AI_A2A_Fsm:__Patrol( 2 ) -- Start Patrolling end end @@ -3199,7 +3254,7 @@ do -- AI_A2A_DISPATCHER local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) if Squadron then - Dispatcher:MessageToPlayers( DefenderName .. ", patrolling.", DefenderGroup ) + Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", patrolling.", DefenderGroup ) end Dispatcher:ClearDefenderTaskTarget( DefenderGroup ) @@ -3214,7 +3269,7 @@ do -- AI_A2A_DISPATCHER local DefenderName = DefenderGroup:GetCallsign() local Dispatcher = self:GetDispatcher() -- #AI_A2A_DISPATCHER local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - Dispatcher:MessageToPlayers( DefenderName .. " returning to base.", DefenderGroup ) + Dispatcher:MessageToPlayers( Squadron, DefenderName .. " returning to base.", DefenderGroup ) Dispatcher:ClearDefenderTaskTarget( DefenderGroup ) end @@ -3396,7 +3451,11 @@ do -- AI_A2A_DISPATCHER local DefenderTarget = Dispatcher:GetDefenderTaskTarget( DefenderGroup ) if DefenderTarget then - Dispatcher:MessageToPlayers( DefenderName .. " wheels up.", DefenderGroup ) + if Squadron.Language == "EN" then + Dispatcher:MessageToPlayers( Squadron, DefenderName .. " wheels up.", DefenderGroup ) + elseif Squadron.Language == "RU" then + Dispatcher:MessageToPlayers( Squadron, DefenderName .. " колеса вверх.", DefenderGroup ) + end --Fsm:__Engage( 2, DefenderTarget.Set ) -- Engage on the TargetSetUnit Fsm:EngageRoute( DefenderTarget.Set ) -- Engage on the TargetSetUnit end @@ -3412,8 +3471,14 @@ do -- AI_A2A_DISPATCHER if Squadron and AttackSetUnit:Count() > 0 then local FirstUnit = AttackSetUnit:GetFirst() local Coordinate = FirstUnit:GetCoordinate() -- Core.Point#COORDINATE - - Dispatcher:MessageToPlayers( DefenderName .. ", intercepting bogeys at " .. Coordinate:ToStringA2A( DefenderGroup ), DefenderGroup ) + + if Squadron.Language == "EN" then + Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", intercepting bogeys at " .. Coordinate:ToStringA2A( DefenderGroup, nil, Squadron.Language ), DefenderGroup ) + elseif Squadron.Language == "RU" then + Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", перехват самолетов в " .. Coordinate:ToStringA2A( DefenderGroup, nil, Squadron.Language ), DefenderGroup ) + elseif Squadron.Language == "DE" then + Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", Eindringlinge abfangen bei" .. Coordinate:ToStringA2A( DefenderGroup, nil, Squadron.Language ), DefenderGroup ) + end end self:GetParent( Fsm ).onafterEngageRoute( self, DefenderGroup, From, Event, To, AttackSetUnit ) end @@ -3429,7 +3494,11 @@ do -- AI_A2A_DISPATCHER local FirstUnit = AttackSetUnit:GetFirst() local Coordinate = FirstUnit:GetCoordinate() -- Core.Point#COORDINATE - Dispatcher:MessageToPlayers( DefenderName .. ", engaging bogeys at " .. Coordinate:ToStringA2A( DefenderGroup ), DefenderGroup ) + if Squadron.Language == "EN" then + Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", engaging bogeys at " .. Coordinate:ToStringA2A( DefenderGroup, nil, Squadron.Language ), DefenderGroup ) + elseif Squadron.Language == "RU" then + Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", захватывающие самолеты в " .. Coordinate:ToStringA2A( DefenderGroup, nil, Squadron.Language ), DefenderGroup ) + end end self:GetParent( Fsm ).onafterEngage( self, DefenderGroup, From, Event, To, AttackSetUnit ) end @@ -3441,7 +3510,14 @@ do -- AI_A2A_DISPATCHER local DefenderName = DefenderGroup:GetCallsign() local Dispatcher = self:GetDispatcher() -- #AI_A2A_DISPATCHER local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - Dispatcher:MessageToPlayers( DefenderName .. " returning to base.", DefenderGroup ) + + if Squadron then + if Squadron.Language == "EN" then + Dispatcher:MessageToPlayers( Squadron, DefenderName .. " returning to base.", DefenderGroup ) + elseif Squadron.Language == "RU" then + Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", возвращаясь на базу.", DefenderGroup ) + end + end Dispatcher:ClearDefenderTaskTarget( DefenderGroup ) end @@ -3467,7 +3543,11 @@ do -- AI_A2A_DISPATCHER local Dispatcher = self:GetDispatcher() -- #AI_A2A_DISPATCHER local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - Dispatcher:MessageToPlayers( DefenderName .. " landing at base.", DefenderGroup ) + if Squadron.Language == "EN" then + Dispatcher:MessageToPlayers( Squadron, DefenderName .. " landing at base.", DefenderGroup ) + elseif Squadron.Language == "RU" then + Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", захватывающие самолеты в посадка на базу.", DefenderGroup ) + end if Action and Action == "Destroy" then Dispatcher:RemoveDefenderFromSquadron( Squadron, DefenderGroup ) diff --git a/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua b/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua index 3628fca69..d747bcf58 100644 --- a/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua +++ b/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua @@ -3313,6 +3313,31 @@ do -- AI_A2G_DISPATCHER end + --- Set the frequency of communication and the mode of communication for voice overs. + -- @param #AI_A2G_DISPATCHER self + -- @param #string SquadronName The name of the squadron. + -- @param #number RadioFrequency The frequency of communication. + -- @param #number RadioModulation The modulation of communication. + -- @param #number RadioPower The power in Watts of communication. + function AI_A2G_DISPATCHER:SetSquadronRadioFrequency( SquadronName, RadioFrequency, RadioModulation, RadioPower ) + + local DefenderSquadron = self:GetSquadron( SquadronName ) + DefenderSquadron.RadioFrequency = RadioFrequency + DefenderSquadron.RadioModulation = RadioModulation or radio.modulation.AM + DefenderSquadron.RadioPower = RadioPower or 100 + + if DefenderSquadron.RadioQueue then + DefenderSquadron.RadioQueue:Stop() + end + + DefenderSquadron.RadioQueue = nil + + DefenderSquadron.RadioQueue = RADIOSPEECH:New( DefenderSquadron.RadioFrequency, DefenderSquadron.RadioModulation ) + DefenderSquadron.RadioQueue.power = DefenderSquadron.RadioPower + DefenderSquadron.RadioQueue:Start( 0.5 ) + + DefenderSquadron.RadioQueue:SetLanguage( DefenderSquadron.Language ) + end --- @param #AI_A2G_DISPATCHER self @@ -3674,7 +3699,7 @@ do -- AI_A2G_DISPATCHER local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) if Squadron then - Dispatcher:MessageToPlayers( DefenderName .. ", wheels up.", DefenderGroup ) + Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", wheels up.", DefenderGroup ) AI_A2G_Fsm:Patrol() -- Engage on the TargetSetUnit end end @@ -3687,7 +3712,7 @@ do -- AI_A2G_DISPATCHER local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) if Squadron then - Dispatcher:MessageToPlayers( DefenderName .. ", patrolling.", DefenderGroup ) + Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", patrolling.", DefenderGroup ) end Dispatcher:ClearDefenderTaskTarget( DefenderGroup ) @@ -3706,7 +3731,7 @@ do -- AI_A2G_DISPATCHER local FirstUnit = AttackSetUnit:GetFirst() local Coordinate = FirstUnit:GetCoordinate() -- Core.Point#COORDINATE - Dispatcher:MessageToPlayers( DefenderName .. ", moving on to ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), DefenderGroup ) + Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", moving on to ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), DefenderGroup ) end end @@ -3721,7 +3746,7 @@ do -- AI_A2G_DISPATCHER if FirstUnit then local Coordinate = FirstUnit:GetCoordinate() - Dispatcher:MessageToPlayers( DefenderName .. ", engaging ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), DefenderGroup ) + Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", engaging ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), DefenderGroup ) end end @@ -3732,7 +3757,7 @@ do -- AI_A2G_DISPATCHER local DefenderName = DefenderGroup:GetCallsign() local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - Dispatcher:MessageToPlayers( DefenderName .. ", returning to base.", DefenderGroup ) + Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", returning to base.", DefenderGroup ) Dispatcher:ClearDefenderTaskTarget( DefenderGroup ) end @@ -3745,7 +3770,7 @@ do -- AI_A2G_DISPATCHER local DefenderName = DefenderGroup:GetCallsign() local Dispatcher = AI_A2G_Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - Dispatcher:MessageToPlayers( DefenderName .. ", lost control." ) + Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", lost control." ) if DefenderGroup:IsAboveRunway() then Dispatcher:RemoveDefenderFromSquadron( Squadron, DefenderGroup ) DefenderGroup:Destroy() @@ -3760,7 +3785,7 @@ do -- AI_A2G_DISPATCHER local DefenderName = DefenderGroup:GetCallsign() local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - Dispatcher:MessageToPlayers( DefenderName .. ", landing at base.", DefenderGroup ) + Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", landing at base.", DefenderGroup ) if Action and Action == "Destroy" then Dispatcher:RemoveDefenderFromSquadron( Squadron, DefenderGroup ) @@ -3817,7 +3842,7 @@ do -- AI_A2G_DISPATCHER self:F( { DefenderTarget = DefenderTarget } ) if DefenderTarget then - Dispatcher:MessageToPlayers( DefenderName .. ", wheels up.", DefenderGroup ) + Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", wheels up.", DefenderGroup ) AI_A2G_Fsm:EngageRoute( DefenderTarget.Set ) -- Engage on the TargetSetUnit end end @@ -3833,7 +3858,7 @@ do -- AI_A2G_DISPATCHER local FirstUnit = AttackSetUnit:GetFirst() local Coordinate = FirstUnit:GetCoordinate() -- Core.Point#COORDINATE - Dispatcher:MessageToPlayers( DefenderName .. ", on route to ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), DefenderGroup ) + Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", on route to ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), DefenderGroup ) end self:GetParent(self).onafterEngageRoute( self, DefenderGroup, From, Event, To, AttackSetUnit ) end @@ -3849,7 +3874,7 @@ do -- AI_A2G_DISPATCHER if FirstUnit then local Coordinate = FirstUnit:GetCoordinate() - Dispatcher:MessageToPlayers( DefenderName .. ", engaging ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), DefenderGroup ) + Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", engaging ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), DefenderGroup ) end end @@ -3859,7 +3884,7 @@ do -- AI_A2G_DISPATCHER local DefenderName = DefenderGroup:GetCallsign() local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - Dispatcher:MessageToPlayers( DefenderName .. ", returning to base.", DefenderGroup ) + Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", returning to base.", DefenderGroup ) self:GetParent(self).onafterRTB( self, DefenderGroup, From, Event, To ) @@ -3874,7 +3899,7 @@ do -- AI_A2G_DISPATCHER local DefenderName = DefenderGroup:GetCallsign() local Dispatcher = AI_A2G_Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - --Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " lost control." ) + --Dispatcher:MessageToPlayers( Squadron, "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " lost control." ) if DefenderGroup:IsAboveRunway() then Dispatcher:RemoveDefenderFromSquadron( Squadron, DefenderGroup ) @@ -3890,7 +3915,7 @@ do -- AI_A2G_DISPATCHER local DefenderName = DefenderGroup:GetCallsign() local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - Dispatcher:MessageToPlayers( DefenderName .. ", landing at base.", DefenderGroup ) + Dispatcher:MessageToPlayers( Squadron, DefenderName .. ", landing at base.", DefenderGroup ) if Action and Action == "Destroy" then Dispatcher:RemoveDefenderFromSquadron( Squadron, DefenderGroup ) diff --git a/Moose Development/Moose/AI/AI_Air_Dispatcher.lua b/Moose Development/Moose/AI/AI_Air_Dispatcher.lua index 23531f689..459a97aab 100644 --- a/Moose Development/Moose/AI/AI_Air_Dispatcher.lua +++ b/Moose Development/Moose/AI/AI_Air_Dispatcher.lua @@ -2502,7 +2502,7 @@ do -- AI_AIR_DISPATCHER local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) if Squadron then - Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " airborne." ) + Dispatcher:MessageToPlayers( Squadron, "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " airborne." ) Fsm:Patrol() -- Engage on the TargetSetUnit end end @@ -2514,7 +2514,7 @@ do -- AI_AIR_DISPATCHER local DefenderName = Defender:GetName() local Dispatcher = self:GetDispatcher() -- #AI_AIR_DISPATCHER local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) - Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " returning." ) + Dispatcher:MessageToPlayers( Squadron, "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " returning." ) Dispatcher:ClearDefenderTaskTarget( Defender ) end @@ -2527,7 +2527,7 @@ do -- AI_AIR_DISPATCHER local DefenderName = Defender:GetName() local Dispatcher = Fsm:GetDispatcher() -- #AI_AIR_DISPATCHER local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) - Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " lost control." ) + Dispatcher:MessageToPlayers( Squadron, "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " lost control." ) if Defender:IsAboveRunway() then Dispatcher:RemoveDefenderFromSquadron( Squadron, Defender ) Defender:Destroy() @@ -2542,7 +2542,7 @@ do -- AI_AIR_DISPATCHER local DefenderName = Defender:GetName() local Dispatcher = self:GetDispatcher() -- #AI_AIR_DISPATCHER local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) - Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " landing." ) + Dispatcher:MessageToPlayers( Squadron, "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " landing." ) if Action and Action == "Destroy" then Dispatcher:RemoveDefenderFromSquadron( Squadron, Defender ) @@ -2599,7 +2599,7 @@ do -- AI_AIR_DISPATCHER self:F( { DefenderTarget = DefenderTarget } ) if DefenderTarget then - Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " airborne." ) + Dispatcher:MessageToPlayers( Squadron, "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " airborne." ) Fsm:EngageRoute( DefenderTarget.Set ) -- Engage on the TargetSetUnit end end @@ -2614,7 +2614,7 @@ do -- AI_AIR_DISPATCHER local FirstUnit = AttackSetUnit:GetFirst() local Coordinate = FirstUnit:GetCoordinate() -- Core.Point#COORDINATE - Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " on route, bearing " .. Coordinate:ToString( Defender ) ) + Dispatcher:MessageToPlayers( Squadron, "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " on route, bearing " .. Coordinate:ToString( Defender ) ) end function Fsm:OnAfterEngage( Defender, From, Event, To, AttackSetUnit ) @@ -2627,7 +2627,7 @@ do -- AI_AIR_DISPATCHER local FirstUnit = AttackSetUnit:GetFirst() local Coordinate = FirstUnit:GetCoordinate() - Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " engaging target, bearing " .. Coordinate:ToString( Defender ) ) + Dispatcher:MessageToPlayers( Squadron, "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " engaging target, bearing " .. Coordinate:ToString( Defender ) ) end function Fsm:onafterRTB( Defender, From, Event, To ) @@ -2636,7 +2636,7 @@ do -- AI_AIR_DISPATCHER local DefenderName = Defender:GetName() local Dispatcher = self:GetDispatcher() -- #AI_AIR_DISPATCHER local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) - Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " RTB." ) + Dispatcher:MessageToPlayers( Squadron, "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " RTB." ) self:GetParent(self).onafterRTB( self, Defender, From, Event, To ) @@ -2651,7 +2651,7 @@ do -- AI_AIR_DISPATCHER local DefenderName = Defender:GetName() local Dispatcher = Fsm:GetDispatcher() -- #AI_AIR_DISPATCHER local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) - Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " lost control." ) + Dispatcher:MessageToPlayers( Squadron, "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " lost control." ) if Defender:IsAboveRunway() then Dispatcher:RemoveDefenderFromSquadron( Squadron, Defender ) @@ -2667,7 +2667,7 @@ do -- AI_AIR_DISPATCHER local DefenderName = Defender:GetName() local Dispatcher = self:GetDispatcher() -- #AI_AIR_DISPATCHER local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) - Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " landing." ) + Dispatcher:MessageToPlayers( Squadron, "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " landing." ) if Action and Action == "Destroy" then Dispatcher:RemoveDefenderFromSquadron( Squadron, Defender ) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index 3dce6e9a9..8e2dc7648 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -867,7 +867,7 @@ do -- COORDINATE -- @param #number Precision The precision. -- @param Core.Settings#SETTINGS Settings -- @return #string The bearing text in degrees. - function COORDINATE:GetBearingText( AngleRadians, Precision, Settings ) + function COORDINATE:GetBearingText( AngleRadians, Precision, Settings, Language ) local Settings = Settings or _SETTINGS -- Core.Settings#SETTINGS @@ -883,16 +883,25 @@ do -- COORDINATE -- @param #number Distance The distance in meters. -- @param Core.Settings#SETTINGS Settings -- @return #string The distance text expressed in the units of measurement. - function COORDINATE:GetDistanceText( Distance, Settings ) + function COORDINATE:GetDistanceText( Distance, Settings, Language ) local Settings = Settings or _SETTINGS -- Core.Settings#SETTINGS + local Language = Language or "EN" local DistanceText if Settings:IsMetric() then - DistanceText = " for " .. UTILS.Round( Distance / 1000, 2 ) .. " km" + if Language == "EN" then + DistanceText = " for " .. UTILS.Round( Distance / 1000, 2 ) .. " km" + elseif Language == "RU" then + DistanceText = " за " .. UTILS.Round( Distance / 1000, 2 ) .. " километров" + end else - DistanceText = " for " .. UTILS.Round( UTILS.MetersToNM( Distance ), 2 ) .. " miles" + if Language == "EN" then + DistanceText = " for " .. UTILS.Round( UTILS.MetersToNM( Distance ), 2 ) .. " miles" + elseif Language == "RU" then + DistanceText = " за " .. UTILS.Round( UTILS.MetersToNM( Distance ), 2 ) .. " миль" + end end return DistanceText @@ -901,14 +910,24 @@ do -- COORDINATE --- Return the altitude text of the COORDINATE. -- @param #COORDINATE self -- @return #string Altitude text. - function COORDINATE:GetAltitudeText( Settings ) + function COORDINATE:GetAltitudeText( Settings, Language ) local Altitude = self.y local Settings = Settings or _SETTINGS + local Language = Language or "EN" + if Altitude ~= 0 then if Settings:IsMetric() then - return " at " .. UTILS.Round( self.y, -3 ) .. " meters" + if Language == "EN" then + return " at " .. UTILS.Round( self.y, -3 ) .. " meters" + elseif Language == "RU" then + return " в " .. UTILS.Round( self.y, -3 ) .. " метры" + end else - return " at " .. UTILS.Round( UTILS.MetersToFeet( self.y ), -3 ) .. " feet" + if Language == "EN" then + return " at " .. UTILS.Round( UTILS.MetersToFeet( self.y ), -3 ) .. " feet" + elseif Language == "RU" then + return " в " .. UTILS.Round( self.y, -3 ) .. " ноги" + end end else return "" @@ -954,12 +973,12 @@ do -- COORDINATE -- @param #number Distance The distance -- @param Core.Settings#SETTINGS Settings -- @return #string The BR Text - function COORDINATE:GetBRText( AngleRadians, Distance, Settings ) + function COORDINATE:GetBRText( AngleRadians, Distance, Settings, Language ) local Settings = Settings or _SETTINGS -- Core.Settings#SETTINGS - local BearingText = self:GetBearingText( AngleRadians, 0, Settings ) - local DistanceText = self:GetDistanceText( Distance, Settings ) + local BearingText = self:GetBearingText( AngleRadians, 0, Settings, Language ) + local DistanceText = self:GetDistanceText( Distance, Settings, Language ) local BRText = BearingText .. DistanceText @@ -972,13 +991,13 @@ do -- COORDINATE -- @param #number Distance The distance -- @param Core.Settings#SETTINGS Settings -- @return #string The BRA Text - function COORDINATE:GetBRAText( AngleRadians, Distance, Settings ) + function COORDINATE:GetBRAText( AngleRadians, Distance, Settings, Language ) local Settings = Settings or _SETTINGS -- Core.Settings#SETTINGS - local BearingText = self:GetBearingText( AngleRadians, 0, Settings ) - local DistanceText = self:GetDistanceText( Distance, Settings ) - local AltitudeText = self:GetAltitudeText( Settings ) + local BearingText = self:GetBearingText( AngleRadians, 0, Settings, Language ) + local DistanceText = self:GetDistanceText( Distance, Settings, Language ) + local AltitudeText = self:GetAltitudeText( Settings, Language ) local BRAText = BearingText .. DistanceText .. AltitudeText -- When the POINT is a VEC2, there will be no altitude shown. @@ -1867,12 +1886,12 @@ do -- COORDINATE -- @param #COORDINATE FromCoordinate The coordinate to measure the distance and the bearing from. -- @param Core.Settings#SETTINGS Settings (optional) The settings. Can be nil, and in this case the default settings are used. If you want to specify your own settings, use the _SETTINGS object. -- @return #string The BR text. - function COORDINATE:ToStringBRA( FromCoordinate, Settings ) + function COORDINATE:ToStringBRA( FromCoordinate, Settings, Language ) local DirectionVec3 = FromCoordinate:GetDirectionVec3( self ) local AngleRadians = self:GetAngleRadians( DirectionVec3 ) local Distance = FromCoordinate:Get2DDistance( self ) local Altitude = self:GetAltitudeText() - return "BRA, " .. self:GetBRAText( AngleRadians, Distance, Settings ) + return "BRA, " .. self:GetBRAText( AngleRadians, Distance, Settings, Language ) end --- Return a BULLS string out of the BULLS of the coalition to the COORDINATE. @@ -2023,7 +2042,7 @@ do -- COORDINATE -- @param Wrapper.Controllable#CONTROLLABLE Controllable -- @param Core.Settings#SETTINGS Settings (optional) The settings. Can be nil, and in this case the default settings are used. If you want to specify your own settings, use the _SETTINGS object. -- @return #string The coordinate Text in the configured coordinate system. - function COORDINATE:ToStringA2A( Controllable, Settings ) -- R2.2 + function COORDINATE:ToStringA2A( Controllable, Settings, Language ) -- R2.2 self:F2( { Controllable = Controllable and Controllable:GetName() } ) @@ -2032,23 +2051,23 @@ do -- COORDINATE if Settings:IsA2A_BRAA() then if Controllable then local Coordinate = Controllable:GetCoordinate() - return self:ToStringBRA( Coordinate, Settings ) + return self:ToStringBRA( Coordinate, Settings, Language ) else - return self:ToStringMGRS( Settings ) + return self:ToStringMGRS( Settings, Language ) end end if Settings:IsA2A_BULLS() then local Coalition = Controllable:GetCoalition() - return self:ToStringBULLS( Coalition, Settings ) + return self:ToStringBULLS( Coalition, Settings, Language ) end if Settings:IsA2A_LL_DMS() then - return self:ToStringLLDMS( Settings ) + return self:ToStringLLDMS( Settings, Language ) end if Settings:IsA2A_LL_DDM() then - return self:ToStringLLDDM( Settings ) + return self:ToStringLLDDM( Settings, Language ) end if Settings:IsA2A_MGRS() then - return self:ToStringMGRS( Settings ) + return self:ToStringMGRS( Settings, Language ) end return nil diff --git a/Moose Development/Moose/Core/RadioQueue.lua b/Moose Development/Moose/Core/RadioQueue.lua index f1980102c..302b3fd4c 100644 --- a/Moose Development/Moose/Core/RadioQueue.lua +++ b/Moose Development/Moose/Core/RadioQueue.lua @@ -81,6 +81,7 @@ function RADIOQUEUE:New(frequency, modulation) -- Scheduler self.scheduler=SCHEDULER:New() + self.scheduler:NoTrace() return self end diff --git a/Moose Development/Moose/Core/RadioSpeech.lua b/Moose Development/Moose/Core/RadioSpeech.lua index e54068c3c..d4cf22af1 100644 --- a/Moose Development/Moose/Core/RadioSpeech.lua +++ b/Moose Development/Moose/Core/RadioSpeech.lua @@ -11,7 +11,7 @@ -- -- ### Authors: FlightControl -- --- @module Core.Speech +-- @module Core.RadioSpeech -- @image Core_Radio.JPG --- Makes the radio speak. @@ -25,6 +25,8 @@ RADIOSPEECH = { ClassName = "RADIOSPEECH", Vocabulary = { EN = {}, + DE = {}, + RU = {}, } } @@ -91,6 +93,7 @@ RADIOSPEECH.Vocabulary.EN = { ["miles"] = { "miles", 0.45 }, ["meters"] = { "meters", 0.41 }, ["mi"] = { "miles", 0.45 }, + ["feet"] = { "feet", 0.29 }, ["br"] = { "br", 1.1 }, ["bra"] = { "bra", 0.3 }, @@ -112,7 +115,81 @@ RADIOSPEECH.Vocabulary.EN = { ["defender"] = { "defender", 0.45 }, } +RADIOSPEECH.Vocabulary.RU = { + ["1"] = { "1", 0.34 }, + ["2"] = { "2", 0.30 }, + ["3"] = { "3", 0.23 }, + ["4"] = { "4", 0.51 }, + ["5"] = { "5", 0.31 }, + ["6"] = { "6", 0.44 }, + ["7"] = { "7", 0.25 }, + ["8"] = { "8", 0.43 }, + ["9"] = { "9", 0.45 }, + ["10"] = { "10", 0.53 }, + ["11"] = { "11", 0.66 }, + ["12"] = { "12", 0.70 }, + ["13"] = { "13", 0.66 }, + ["14"] = { "14", 0.80 }, + ["15"] = { "15", 0.65 }, + ["16"] = { "16", 0.75 }, + ["17"] = { "17", 0.74 }, + ["18"] = { "18", 0.85 }, + ["19"] = { "19", 0.80 }, + ["20"] = { "20", 0.58 }, + ["30"] = { "30", 0.51 }, + ["40"] = { "40", 0.51 }, + ["50"] = { "50", 0.67 }, + ["60"] = { "60", 0.76 }, + ["70"] = { "70", 0.68 }, + ["80"] = { "80", 0.84 }, + ["90"] = { "90", 0.71 }, + ["100"] = { "100", 0.35 }, + ["200"] = { "200", 0.59 }, + ["300"] = { "300", 0.53 }, + ["400"] = { "400", 0.70 }, + ["500"] = { "500", 0.50 }, + ["600"] = { "600", 0.58 }, + ["700"] = { "700", 0.64 }, + ["800"] = { "800", 0.77 }, + ["900"] = { "900", 0.75 }, + ["1000"] = { "1000", 0.87 }, + ["2000"] = { "2000", 0.83 }, + ["3000"] = { "3000", 0.84 }, + ["4000"] = { "4000", 1.00 }, + ["5000"] = { "5000", 0.77 }, + ["6000"] = { "6000", 0.90 }, + ["7000"] = { "7000", 0.77 }, + ["8000"] = { "8000", 0.92 }, + ["9000"] = { "9000", 0.87 }, + ["степени"] = { "degrees", 0.5 }, + ["километров"] = { "kilometers", 0.65 }, + ["km"] = { "kilometers", 0.65 }, + ["миль"] = { "miles", 0.45 }, + ["mi"] = { "miles", 0.45 }, + ["метры"] = { "meters", 0.41 }, + ["m"] = { "meters", 0.41 }, + ["ноги"] = { "feet", 0.37 }, + + ["br"] = { "br", 1.1 }, + ["bra"] = { "bra", 0.3 }, + + + ["возвращаясь на базу"] = { "returning_to_base", 1.40 }, + ["на пути к наземной цели"] = { "on_route_to_ground_target", 1.45 }, + ["перехват самолетов"] = { "intercepting_bogeys", 1.22 }, + ["поражение наземной цели"] = { "engaging_ground_target", 1.53 }, + ["захватывающие самолеты"] = { "engaging_bogeys", 1.68 }, + ["колеса вверх"] = { "wheels_up", 0.92 }, + ["посадка на базу"] = { "landing at base", 1.04 }, + ["патрулирующий"] = { "patrolling", 0.96 }, + + ["за"] = { "for", 0.27 }, + ["и"] = { "and", 0.17 }, + ["в"] = { "at", 0.19 }, + ["dot"] = { "dot", 0.51 }, + ["defender"] = { "defender", 0.45 }, +} --- Create a new RADIOSPEECH object for a given radio frequency/modulation. -- @param #RADIOSPEECH self @@ -131,6 +208,11 @@ function RADIOSPEECH:New(frequency, modulation) return self end +function RADIOSPEECH:SetLanguage( Langauge ) + + self.Language = Langauge +end + --- Add Sentence to the Speech collection. -- @param #RADIOSPEECH self @@ -143,7 +225,7 @@ function RADIOSPEECH:AddSentenceToSpeech( RemainingSentence, Speech, Sentence, D self:I( { RemainingSentence, Speech, Sentence, Data } ) - local Token, RemainingSentence = RemainingSentence:match( "^ *(%w+)(.*)" ) + local Token, RemainingSentence = RemainingSentence:match( "^ *([^ ]+)(.*)" ) self:I( { Token = Token, RemainingSentence = RemainingSentence } ) -- Is there a Token? @@ -198,122 +280,117 @@ end --- Speak a sentence. -- @param #RADIOSPEECH self -- @param #string Sentence The sentence to be spoken. -function RADIOSPEECH:SpeakWords( Sentence, Speech ) +function RADIOSPEECH:SpeakWords( Sentence, Speech, Language ) - local Word, RemainderSentence = Sentence:match( "^[^%d%a]*(%a*)(.*)" ) + local OriginalSentence = Sentence + + -- lua does not parse UTF-8, so the match statement will fail on cyrillic using %a. + -- therefore, the only way to parse the statement is to use blank, comma or dot as a delimiter. + -- and then check if the character can be converted to a number or not. + local Word, RemainderSentence = Sentence:match( "^[., ]*([^ .,]+)(.*)" ) self:I( { Word = Word, Speech = Speech[Word], RemainderSentence = RemainderSentence } ) + - if Word and Word ~= "" then + if Word then + if Word ~= "" and tonumber(Word) == nil then - -- Construct of words - Word = Word:lower() - if Speech[Word] then - -- The end of the sentence has been reached. Now Speech.Next should be nil, otherwise there is an error. - if Speech[Word].Next == nil then - self:I( { Sentence = Speech[Word].Sentence, Data = Speech[Word].Data } ) - self:NewTransmission( Speech[Word].Data[1] .. ".wav", Speech[Word].Data[2], "EN/" ) - else - if RemainderSentence and RemainderSentence ~= "" then - RemainderSentence = self:SpeakWords( RemainderSentence, Speech[Word].Next ) + -- Construct of words + Word = Word:lower() + if Speech[Word] then + -- The end of the sentence has been reached. Now Speech.Next should be nil, otherwise there is an error. + if Speech[Word].Next == nil then + self:I( { Sentence = Speech[Word].Sentence, Data = Speech[Word].Data } ) + self:NewTransmission( Speech[Word].Data[1] .. ".wav", Speech[Word].Data[2], Language .. "/" ) + else + if RemainderSentence and RemainderSentence ~= "" then + return self:SpeakWords( RemainderSentence, Speech[Word].Next, Language ) + end end end + return RemainderSentence end + return OriginalSentence + else + return "" end - return RemainderSentence - end --- Speak a sentence. -- @param #RADIOSPEECH self -- @param #string Sentence The sentence to be spoken. -function RADIOSPEECH:SpeakDigits( Sentence, Speech ) +function RADIOSPEECH:SpeakDigits( Sentence, Speech, Langauge ) - local Digits, RemainderSentence = Sentence:match( "^[^%a%d]*(%d*)(.*)" ) + local OriginalSentence = Sentence + + -- lua does not parse UTF-8, so the match statement will fail on cyrillic using %a. + -- therefore, the only way to parse the statement is to use blank, comma or dot as a delimiter. + -- and then check if the character can be converted to a number or not. + local Digits, RemainderSentence = Sentence:match( "^[., ]*([^ .,]+)(.*)" ) self:I( { Digits = Digits, Speech = Speech[Digits], RemainderSentence = RemainderSentence } ) - if Digits and Digits ~= "" then + if Digits then + if Digits ~= "" and tonumber( Digits ) ~= nil then - -- Construct numbers - local Number = tonumber( Digits ) - local Multiple = nil - while Number >= 0 do - if Number > 1000 then - Multiple = math.floor( Number / 1000 ) * 1000 - elseif Number > 100 then - Multiple = math.floor( Number / 100 ) * 100 - elseif Number > 20 then - Multiple = math.floor( Number / 10 ) * 10 - elseif Number >= 0 then - Multiple = Number + -- Construct numbers + local Number = tonumber( Digits ) + local Multiple = nil + while Number >= 0 do + if Number > 1000 then + Multiple = math.floor( Number / 1000 ) * 1000 + elseif Number > 100 then + Multiple = math.floor( Number / 100 ) * 100 + elseif Number > 20 then + Multiple = math.floor( Number / 10 ) * 10 + elseif Number >= 0 then + Multiple = Number + end + Sentence = tostring( Multiple ) + if Speech[Sentence] then + self:I( { Speech = Speech[Sentence].Sentence, Data = Speech[Sentence].Data } ) + self:NewTransmission( Speech[Sentence].Data[1] .. ".wav", Speech[Sentence].Data[2], Langauge .. "/" ) + end + Number = Number - Multiple + Number = ( Number == 0 ) and -1 or Number end - Sentence = tostring( Multiple ) - if Speech[Sentence] then - self:I( { Speech = Speech[Sentence].Sentence, Data = Speech[Sentence].Data } ) - self:NewTransmission( Speech[Sentence].Data[1] .. ".wav", Speech[Sentence].Data[2], "EN/" ) - end - Number = Number - Multiple - Number = ( Number == 0 ) and -1 or Number + return RemainderSentence end + return OriginalSentence + else + return "" end - return RemainderSentence end + --- Speak a sentence. -- @param #RADIOSPEECH self -- @param #string Sentence The sentence to be spoken. -function RADIOSPEECH:SpeakSymbols( Sentence, Speech ) +function RADIOSPEECH:Speak( Sentence, Language ) - local Symbol, RemainderSentence = Sentence:match( "^[^%a%d]*(°*)(.*)" ) + self:I( { Sentence, Language } ) - self:I( { Sentence = Sentence, Symbol = Symbol, Speech = Speech[Symbol], RemainderSentence = RemainderSentence } ) - - if Symbol and Symbol ~= "" then - local Word = nil - if Symbol == "°" then - Word = "degrees" - end - if Word then - if Speech[Word] then - self:I( { Speech = Speech[Word].Sentence, Data = Speech[Word].Data } ) - self:NewTransmission( Speech[Word].Data[1] .. ".wav", Speech[Word].Data[2], "EN/" ) - end - end - end - - return RemainderSentence -end - - ---- Speak a sentence. --- @param #RADIOSPEECH self --- @param #string Sentence The sentence to be spoken. -function RADIOSPEECH:Speak( Sentence, Speech ) - - self:I( { Sentence, Speech } ) - - local Language = self.Language + local Language = Language or "EN" + + self:I( { Language = Language } ) -- If there is no node for Speech, then we start at the first nodes of the language. - if not Speech then - Speech = self.Speech[Language] - end + local Speech = self.Speech[Language] self:I( { Speech = Speech, Language = Language } ) - self:NewTransmission( "_In.wav", 0.52, "EN/" ) + self:NewTransmission( "_In.wav", 0.52, Language .. "/" ) repeat - Sentence = self:SpeakWords( Sentence, Speech ) + Sentence = self:SpeakWords( Sentence, Speech, Language ) self:I( { Sentence = Sentence } ) - Sentence = self:SpeakDigits( Sentence, Speech ) + Sentence = self:SpeakDigits( Sentence, Speech, Language ) self:I( { Sentence = Sentence } ) @@ -323,6 +400,6 @@ function RADIOSPEECH:Speak( Sentence, Speech ) until not Sentence or Sentence == "" - self:NewTransmission( "_Out.wav", 0.28, "EN/" ) + self:NewTransmission( "_Out.wav", 0.28, Language .. "/" ) end diff --git a/Moose Development/Moose/Core/ScheduleDispatcher.lua b/Moose Development/Moose/Core/ScheduleDispatcher.lua index b87b0685c..2fb596fd8 100644 --- a/Moose Development/Moose/Core/ScheduleDispatcher.lua +++ b/Moose Development/Moose/Core/ScheduleDispatcher.lua @@ -162,6 +162,7 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr local Randomize = Schedule.Randomize or 0 local Stop = Schedule.Stop or 0 local ScheduleID = Schedule.ScheduleID + local ShowTrace = Scheduler.ShowTrace local Prefix = ( Repeat == 0 ) and "--->" or "+++>" @@ -169,13 +170,17 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr --self:E( { SchedulerObject = SchedulerObject } ) if SchedulerObject then local function Timer() - SchedulerObject:T( Prefix .. Name .. ":" .. Line .. " (" .. Source .. ")" ) + if ShowTrace then + SchedulerObject:T( Prefix .. Name .. ":" .. Line .. " (" .. Source .. ")" ) + end return ScheduleFunction( SchedulerObject, unpack( ScheduleArguments ) ) end Status, Result = xpcall( Timer, ErrorHandler ) else local function Timer() - self:T( Prefix .. Name .. ":" .. Line .. " (" .. Source .. ")" ) + if ShowTrace then + self:T( Prefix .. Name .. ":" .. Line .. " (" .. Source .. ")" ) + end return ScheduleFunction( unpack( ScheduleArguments ) ) end Status, Result = xpcall( Timer, ErrorHandler ) @@ -274,5 +279,9 @@ function SCHEDULEDISPATCHER:Clear( Scheduler ) end end +function SCHEDULEDISPATCHER:NoTrace( Scheduler ) + self:F2( { Scheduler = Scheduler } ) + Scheduler.ShowTrace = nil +end diff --git a/Moose Development/Moose/Core/Scheduler.lua b/Moose Development/Moose/Core/Scheduler.lua index df54ced90..a0541c440 100644 --- a/Moose Development/Moose/Core/Scheduler.lua +++ b/Moose Development/Moose/Core/Scheduler.lua @@ -301,6 +301,14 @@ function SCHEDULER:Clear() _SCHEDULEDISPATCHER:Clear( self ) end +--- No tracing for this scheduler. +-- @param #SCHEDULER self +-- @param #number ScheduleID (optional) The ScheduleID of the planned (repeating) schedule. +function SCHEDULER:NoTrace() + + _SCHEDULEDISPATCHER:NoTrace( self ) +end + diff --git a/Moose Development/Moose/Tasking/DetectionManager.lua b/Moose Development/Moose/Tasking/DetectionManager.lua index e40121654..3e16fad29 100644 --- a/Moose Development/Moose/Tasking/DetectionManager.lua +++ b/Moose Development/Moose/Tasking/DetectionManager.lua @@ -261,37 +261,17 @@ do -- DETECTION MANAGER return self.CC end - --- Set the frequency of communication and the mode of communication for voice overs. - -- @param #DETECTION_MANAGER self - -- @param #number RadioFrequency The frequency of communication. - -- @param #number RadioModulation The modulation of communication. - -- @param #number RadioPower The power in Watts of communication. - function DETECTION_MANAGER:SetRadioFrequency( RadioFrequency, RadioModulation, RadioPower ) - - self.RadioFrequency = RadioFrequency - self.RadioModulation = RadioModulation or radio.modulation.AM - self.RadioPower = RadioPower or 100 - - if self.RadioQueue then - self.RadioQueue:Stop() - end - - self.RadioQueue = nil - - self.RadioQueue = RADIOSPEECH:New( self.RadioFrequency, self.RadioModulation ) - self.RadioQueue.power = self.RadioPower - self.RadioQueue:Start( 0.5 ) - end --- Send an information message to the players reporting to the command center. -- @param #DETECTION_MANAGER self + -- @param #table Squadron The squadron table. -- @param #string Message The message to be sent. -- @param #string SoundFile The name of the sound file .wav or .ogg. -- @param #number SoundDuration The duration of the sound. -- @param #string SoundPath The path pointing to the folder in the mission file. -- @param Wrapper.Group#GROUP DefenderGroup The defender group sending the message. -- @return #DETECTION_MANGER self - function DETECTION_MANAGER:MessageToPlayers( Message, DefenderGroup ) + function DETECTION_MANAGER:MessageToPlayers( Squadron, Message, DefenderGroup ) self:F( { Message = Message } ) @@ -306,18 +286,18 @@ do -- DETECTION MANAGER self.CC:MessageToCoalition( Message ) end - Message = Message:gsub( "°", "degrees" ) - Message = Message:gsub( "(%d)%.(%d)", "%1dot%2" ) + Message = Message:gsub( "°", " degrees " ) + Message = Message:gsub( "(%d)%.(%d)", "%1 dot %2" ) -- Here we handle the transmission of the voice over. -- If for a certain reason the Defender does not exist, we use the coordinate of the airbase to send the message from. - local RadioQueue = self.RadioQueue -- Core.RadioQueue#RADIOQUEUE + local RadioQueue = Squadron.RadioQueue -- Core.RadioSpeech#RADIOSPEECH if RadioQueue then local DefenderUnit = DefenderGroup:GetUnit(1) if DefenderUnit and DefenderUnit:IsAlive() then RadioQueue:SetSenderUnitName( DefenderUnit:GetName() ) end - RadioQueue:Speak( Message ) + RadioQueue:Speak( Message, Squadron.Language ) end return self diff --git a/Moose Development/Moose/Wrapper/Client.lua b/Moose Development/Moose/Wrapper/Client.lua index cef00651d..4e6cce0de 100644 --- a/Moose Development/Moose/Wrapper/Client.lua +++ b/Moose Development/Moose/Wrapper/Client.lua @@ -143,6 +143,7 @@ function CLIENT:Register( ClientName ) --self.AliveCheckScheduler = routines.scheduleFunction( self._AliveCheckScheduler, { self }, timer.getTime() + 1, 5 ) self.AliveCheckScheduler = SCHEDULER:New( self, self._AliveCheckScheduler, { "Client Alive " .. ClientName }, 1, 5, 0.5 ) + self.AliveCheckScheduler:NoTrace() self:F( self ) return self From 4ea44308bc110f06d757ac7a4e7c77a6186335dd Mon Sep 17 00:00:00 2001 From: FlightControl Date: Thu, 26 Sep 2019 19:36:42 +0300 Subject: [PATCH 6/7] Fix for older A2A dispatcher CAP api altitude problem during takeoff ... --- Moose Development/Moose/AI/AI_A2A_Dispatcher.lua | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua b/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua index ddf3192f3..5647c4dab 100644 --- a/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua +++ b/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua @@ -1844,15 +1844,13 @@ do -- AI_A2A_DISPATCHER --- Set a CAP for a Squadron. -- @param #AI_A2A_DISPATCHER self -- @param #string SquadronName The squadron name. - -- @param #number EngageMinSpeed The minimum speed at which the engage can be executed. - -- @param #number EngageMaxSpeed The maximum speed at which the engage can be executed. - -- @param DCS#Altitude EngageFloorAltitude The lowest altitude in meters where to execute the engagement. - -- @param DCS#Altitude EngageCeilingAltitude The highest altitude in meters where to execute the engagement. -- @param Core.Zone#ZONE_BASE Zone The @{Zone} object derived from @{Core.Zone#ZONE_BASE} that defines the zone wherein the CAP will be executed. -- @param #number PatrolFloorAltitude The minimum altitude at which the cap can be executed. -- @param #number PatrolCeilingAltitude the maximum altitude at which the cap can be executed. -- @param #number PatrolMinSpeed The minimum speed at which the cap can be executed. -- @param #number PatrolMaxSpeed The maximum speed at which the cap can be executed. + -- @param #number EngageMinSpeed The minimum speed at which the engage can be executed. + -- @param #number EngageMaxSpeed The maximum speed at which the engage can be executed. -- @param #number AltType The altitude type, which is a string "BARO" defining Barometric or "RADIO" defining radio controlled altitude. -- @return #AI_A2A_DISPATCHER -- @usage @@ -1870,7 +1868,7 @@ do -- AI_A2A_DISPATCHER -- A2ADispatcher:SetSquadronCap( "Maykop", CAPZoneMiddle, 4000, 8000, 600, 800, 800, 1200, "RADIO" ) -- A2ADispatcher:SetSquadronCapInterval( "Sochi", 2, 30, 120, 1 ) -- - function AI_A2A_DISPATCHER:SetSquadronCap( SquadronName, Zone, PatrolMinSpeed, PatrolMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, EngageMinSpeed, EngageMaxSpeed, AltType ) + function AI_A2A_DISPATCHER:SetSquadronCap( SquadronName, Zone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, EngageMinSpeed, EngageMaxSpeed, AltType ) return self:SetSquadronCap2( SquadronName, EngageMinSpeed, EngageMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, AltType, Zone, PatrolMinSpeed, PatrolMaxSpeed, PatrolFloorAltitude, PatrolCeilingAltitude, AltType ) end From ad2ce8df78d175855128574677b7d6a0c5d105d1 Mon Sep 17 00:00:00 2001 From: FlightControl Date: Mon, 30 Sep 2019 10:35:29 +0200 Subject: [PATCH 7/7] Improvements to A2A and A2G Dispatchers --- Moose Development/Moose/AI/AI_A2A_Cap.lua | 2 + .../Moose/AI/AI_A2A_Dispatcher.lua | 93 +- Moose Development/Moose/AI/AI_A2G_BAI.lua | 4 - Moose Development/Moose/AI/AI_A2G_CAS.lua | 4 - Moose Development/Moose/AI/AI_A2G_SEAD.lua | 4 - Moose Development/Moose/AI/AI_Air.lua | 37 +- .../Moose/AI/AI_Air_Dispatcher.lua | 2685 ++++++++--------- Moose Development/Moose/AI/AI_Air_Engage.lua | 11 +- .../Moose/AI/AI_Air_Squadron.lua | 289 ++ Moose Development/Moose/Core/Scheduler.lua | 1 + 10 files changed, 1682 insertions(+), 1448 deletions(-) create mode 100644 Moose Development/Moose/AI/AI_Air_Squadron.lua diff --git a/Moose Development/Moose/AI/AI_A2A_Cap.lua b/Moose Development/Moose/AI/AI_A2A_Cap.lua index 460d5d614..d44d9763c 100644 --- a/Moose Development/Moose/AI/AI_A2A_Cap.lua +++ b/Moose Development/Moose/AI/AI_A2A_Cap.lua @@ -125,6 +125,7 @@ function AI_A2A_CAP:New2( AICap, EngageMinSpeed, EngageMaxSpeed, EngageFloorAlti self:SetDamageThreshold( 0.4 ) self:SetDisengageRadius( 70000 ) + return self end @@ -203,6 +204,7 @@ function AI_A2A_CAP:CreateAttackUnitTasks( AttackSetUnit, DefenderGroup, EngageA if AttackUnit:IsAlive() and AttackUnit:IsAir() then -- TODO: Add coalition check? Only attack units of if AttackUnit:GetCoalition()~=AICap:GetCoalition() -- Maybe the detected set also contains + self:T( { "Attacking Task:", AttackUnit:GetName(), AttackUnit:IsAlive(), AttackUnit:IsAir() } ) AttackUnitTasks[#AttackUnitTasks+1] = DefenderGroup:TaskAttackUnit( AttackUnit ) end end diff --git a/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua b/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua index 5647c4dab..d25dc2ce9 100644 --- a/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua +++ b/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua @@ -3267,7 +3267,9 @@ do -- AI_A2A_DISPATCHER local DefenderName = DefenderGroup:GetCallsign() local Dispatcher = self:GetDispatcher() -- #AI_A2A_DISPATCHER local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) - Dispatcher:MessageToPlayers( Squadron, DefenderName .. " returning to base.", DefenderGroup ) + if Squadron then + Dispatcher:MessageToPlayers( Squadron, DefenderName .. " returning to base.", DefenderGroup ) + end Dispatcher:ClearDefenderTaskTarget( DefenderGroup ) end @@ -3313,7 +3315,7 @@ do -- AI_A2A_DISPATCHER for DefenderID, Defender in pairs( Defenders ) do local Fsm = self:GetDefenderTaskFsm( Defender ) - Fsm:__EngageRoute( 1, AttackerDetection.Set ) -- Engage on the TargetSetUnit + Fsm:EngageRoute( AttackerDetection.Set ) -- Engage on the TargetSetUnit self:SetDefenderTaskTarget( Defender, AttackerDetection ) @@ -3630,6 +3632,32 @@ do -- AI_A2A_DISPATCHER return nil, nil end + + --- Assigns A2G AI Tasks in relation to the detected items. + -- @param #AI_A2G_DISPATCHER self + function AI_A2A_DISPATCHER:Order( DetectedItem ) + local AttackCoordinate = self.Detection:GetDetectedItemCoordinate( DetectedItem ) + + local ShortestDistance = 999999999 + + for DefenderSquadronName, DefenderSquadron in pairs( self.DefenderSquadrons ) do + + self:I( { DefenderSquadron = DefenderSquadron.Name } ) + + local Airbase = DefenderSquadron.Airbase + local AirbaseCoordinate = Airbase:GetCoordinate() + + local EvaluateDistance = AttackCoordinate:Get2DDistance( AirbaseCoordinate ) + + if EvaluateDistance <= ShortestDistance then + ShortestDistance = EvaluateDistance + end + end + + return ShortestDistance + end + + --- Shows the tactical display. -- @param #AI_A2A_DISPATCHER self function AI_A2A_DISPATCHER:ShowTacticalDisplay( Detection ) @@ -3645,7 +3673,8 @@ do -- AI_A2A_DISPATCHER local DefenderGroupCount = 0 -- Now that all obsolete tasks are removed, loop through the detected targets. - for DetectedItemID, DetectedItem in pairs( Detection:GetDetectedItems() ) do + --for DetectedItemID, DetectedItem in pairs( Detection:GetDetectedItems() ) do + for DetectedItemID, DetectedItem in UTILS.spairs( Detection:GetDetectedItems(), function( t, a, b ) return self:Order(t[a]) < self:Order(t[b]) end ) do local DetectedItem = DetectedItem -- Functional.Detection#DETECTION_BASE.DetectedItem local DetectedSet = DetectedItem.Set -- Core.Set#SET_UNIT @@ -3761,7 +3790,9 @@ do -- AI_A2A_DISPATCHER local DefenderGroupCount = 0 -- Now that all obsolete tasks are removed, loop through the detected targets. - for DetectedItemID, DetectedItem in pairs( Detection:GetDetectedItems() ) do + -- Closest detected targets to be considered first! + --for DetectedItemID, DetectedItem in pairs( Detection:GetDetectedItems() ) do + for DetectedItemID, DetectedItem in UTILS.spairs( Detection:GetDetectedItems(), function( t, a, b ) return self:Order(t[a]) < self:Order(t[b]) end ) do local DetectedItem = DetectedItem -- Functional.Detection#DETECTION_BASE.DetectedItem local DetectedSet = DetectedItem.Set -- Core.Set#SET_UNIT @@ -3790,62 +3821,10 @@ do -- AI_A2A_DISPATCHER self:GCI( DetectedItem, DefendersMissing, Friendlies ) end end - - if self.TacticalDisplay then - -- Show tactical situation - Report:Add( string.format( "\n- Target %s (%s): (#%d) %s" , DetectedItem.ItemID, DetectedItem.Index, DetectedItem.Set:Count(), DetectedItem.Set:GetObjectNames() ) ) - for Defender, DefenderTask in pairs( self:GetDefenderTasks() ) do - local Defender = Defender -- Wrapper.Group#GROUP - if DefenderTask.Target and DefenderTask.Target.Index == DetectedItem.Index then - if Defender and Defender:IsAlive() then - DefenderGroupCount = DefenderGroupCount + 1 - local Fuel = Defender:GetFuelMin() * 100 - local Damage = Defender:GetLife() / Defender:GetLife0() * 100 - Report:Add( string.format( " - %s*%d/%d (%s - %s): (#%d) F: %3d, D:%3d - %s", - Defender:GetName(), - Defender:GetSize(), - Defender:GetInitialSize(), - DefenderTask.Type, - DefenderTask.Fsm:GetState(), - Defender:GetSize(), - Fuel, - Damage, - Defender:HasTask() == true and "Executing" or "Idle" ) ) - end - end - end - end end if self.TacticalDisplay then - Report:Add( "\n- No Targets:") - local TaskCount = 0 - for Defender, DefenderTask in pairs( self:GetDefenderTasks() ) do - TaskCount = TaskCount + 1 - local Defender = Defender -- Wrapper.Group#GROUP - if not DefenderTask.Target then - if Defender:IsAlive() then - local DefenderHasTask = Defender:HasTask() - local Fuel = Defender:GetFuelMin() * 100 - local Damage = Defender:GetLife() / Defender:GetLife0() * 100 - DefenderGroupCount = DefenderGroupCount + 1 - Report:Add( string.format( " - %s*%d/%d (%s - %s): (#%d) F: %3d, D:%3d - %s", - Defender:GetName(), - Defender:GetSize(), - Defender:GetInitialSize(), - DefenderTask.Type, - DefenderTask.Fsm:GetState(), - Defender:GetSize(), - Fuel, - Damage, - Defender:HasTask() == true and "Executing" or "Idle" ) ) - end - end - end - Report:Add( string.format( "\n- %d Tasks - %d Defender Groups", TaskCount, DefenderGroupCount ) ) - - self:F( Report:Text( "\n" ) ) - trigger.action.outText( Report:Text( "\n" ), 25 ) + self:ShowTacticalDisplay( Detection ) end return true diff --git a/Moose Development/Moose/AI/AI_A2G_BAI.lua b/Moose Development/Moose/AI/AI_A2G_BAI.lua index 8b8b8e880..f35009033 100644 --- a/Moose Development/Moose/AI/AI_A2G_BAI.lua +++ b/Moose Development/Moose/AI/AI_A2G_BAI.lua @@ -50,10 +50,6 @@ function AI_A2G_BAI:New2( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAl local AI_Air_Engage = AI_AIR_ENGAGE:New( AI_Air_Patrol, AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType ) local self = BASE:Inherit( self, AI_Air_Engage ) - local RTBSpeedMax = AIGroup:GetSpeedMax() or 9999 - - self:SetRTBSpeed( RTBSpeedMax * 0.50, RTBSpeedMax * 0.75 ) - return self end diff --git a/Moose Development/Moose/AI/AI_A2G_CAS.lua b/Moose Development/Moose/AI/AI_A2G_CAS.lua index 00cd73383..de24056c9 100644 --- a/Moose Development/Moose/AI/AI_A2G_CAS.lua +++ b/Moose Development/Moose/AI/AI_A2G_CAS.lua @@ -50,10 +50,6 @@ function AI_A2G_CAS:New2( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAl local AI_Air_Engage = AI_AIR_ENGAGE:New( AI_Air_Patrol, AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType ) local self = BASE:Inherit( self, AI_Air_Engage ) - local RTBSpeedMax = AIGroup:GetSpeedMax() or 9999 - - self:SetRTBSpeed( RTBSpeedMax * 0.50, RTBSpeedMax * 0.75 ) - return self end diff --git a/Moose Development/Moose/AI/AI_A2G_SEAD.lua b/Moose Development/Moose/AI/AI_A2G_SEAD.lua index b72080558..6a0a32e3c 100644 --- a/Moose Development/Moose/AI/AI_A2G_SEAD.lua +++ b/Moose Development/Moose/AI/AI_A2G_SEAD.lua @@ -100,10 +100,6 @@ function AI_A2G_SEAD:New2( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorA local AI_Air_Engage = AI_AIR_ENGAGE:New( AI_Air_Patrol, AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude, EngageAltType ) local self = BASE:Inherit( self, AI_Air_Engage ) - local RTBSpeedMax = AIGroup:GetSpeedMax() or 9999 - - self:SetRTBSpeed( RTBSpeedMax * 0.50, RTBSpeedMax * 0.75 ) - return self end diff --git a/Moose Development/Moose/AI/AI_Air.lua b/Moose Development/Moose/AI/AI_Air.lua index 8c6dbb9e0..3477a3d2a 100644 --- a/Moose Development/Moose/AI/AI_Air.lua +++ b/Moose Development/Moose/AI/AI_Air.lua @@ -308,7 +308,7 @@ end -- @param DCS#Speed RTBMaxSpeed The maximum speed of the @{Wrapper.Controllable} in km/h. -- @return #AI_AIR self function AI_AIR:SetRTBSpeed( RTBMinSpeed, RTBMaxSpeed ) - self:F2( { RTBMinSpeed, RTBMaxSpeed } ) + self:F( { RTBMinSpeed, RTBMaxSpeed } ) self.RTBMinSpeed = RTBMinSpeed self.RTBMaxSpeed = RTBMaxSpeed @@ -425,7 +425,18 @@ function AI_AIR:onafterStart( Controllable, From, Event, To ) Controllable:OptionROTVertical() end +--- Coordinates the approriate returning action. +-- @param #AI_AIR self +-- @return #AI_AIR self +-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM. +-- @param #string From The From State string. +-- @param #string Event The Event string. +-- @param #string To The To State string. +function AI_AIR:onafterReturn( Controllable, From, Event, To ) + self:__RTB( self.TaskDelay ) + +end --- @param #AI_AIR self function AI_AIR:onbeforeStatus() @@ -481,11 +492,14 @@ function AI_AIR:onafterStatus() OldAIControllable:SetTask( TimedOrbitTask, 10 ) self:Fuel() - RTB = true end else end end + + if self:Is( "Fuel" ) and not self:Is( "Home" ) and not self:is( "Refuelling" ) then + RTB = true + end -- TODO: Check GROUP damage function. local Damage = self.Controllable:GetLife() @@ -581,7 +595,13 @@ function AI_AIR:onafterRTB( AIGroup, From, Event, To ) local FromCoord = AIGroup:GetCoordinate() local ToTargetCoord = self.HomeAirbase:GetCoordinate() - local ToTargetSpeed = math.random( self.RTBMinSpeed, self.RTBMaxSpeed ) + + if not self.RTBMinSpeed and not self.RTBMaxSpeed then + local RTBSpeedMax = AIGroup:GetSpeedMax() + self:SetRTBSpeed( RTBSpeedMax * 0.25, RTBSpeedMax * 0.25 ) + end + + local RTBSpeed = math.random( self.RTBMinSpeed, self.RTBMaxSpeed ) local ToAirbaseAngle = FromCoord:GetAngleDegrees( FromCoord:GetDirectionVec3( ToTargetCoord ) ) local Distance = FromCoord:Get2DDistance( ToTargetCoord ) @@ -593,12 +613,19 @@ function AI_AIR:onafterRTB( AIGroup, From, Event, To ) return end + if not AIGroup:InAir() == true then + self:I( "Not anymore in the air, considered Home." ) + self:Home() + return + end + + --- Create a route point of type air. local FromRTBRoutePoint = FromCoord:WaypointAir( self.PatrolAltType, POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, - ToTargetSpeed, + RTBSpeed, true ) @@ -607,7 +634,7 @@ function AI_AIR:onafterRTB( AIGroup, From, Event, To ) self.PatrolAltType, POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, - ToTargetSpeed, + RTBSpeed, true ) diff --git a/Moose Development/Moose/AI/AI_Air_Dispatcher.lua b/Moose Development/Moose/AI/AI_Air_Dispatcher.lua index 459a97aab..7565f7a64 100644 --- a/Moose Development/Moose/AI/AI_Air_Dispatcher.lua +++ b/Moose Development/Moose/AI/AI_Air_Dispatcher.lua @@ -1,16 +1,265 @@ ---- **AI** - Intermediary class that realized the common methods for the A2G and A2A dispatchers. +--- **AI** - Create an automated AIR defense system based on a detection network of reconnaissance vehicles and air units, coordinating SEAD, BAI and CAP operations. -- -- === -- -- Features: -- --- * Common methods for A2G and A2A dispatchers. +-- * Setup quickly an AIR defense system for a coalition. +-- * Setup multiple defense zones to defend specific coordinates in your battlefield. +-- * Setup (SEAD) Suppression of Air Defense squadrons, to gain control in the air of enemy grounds. +-- * Setup (CAS) Controlled Air Support squadrons, to attack closeby enemy ground units near friendly installations. +-- * Setup (BAI) Battleground Air Interdiction squadrons to attack remote enemy ground units and targets. +-- * Define and use a detection network controlled by recce. +-- * Define AIR defense squadrons at airbases, farps and carriers. +-- * Enable airbases for AIR defenses. +-- * Add different planes and helicopter templates to squadrons. +-- * Assign squadrons to execute a specific engagement type depending on threat level of the detected ground enemy unit composition. +-- * Add multiple squadrons to different airbases, farps or carriers. +-- * Define different ranges to engage upon. +-- * Establish an automatic in air refuel process for planes using refuel tankers. +-- * Setup default settings for all squadrons and AIR defenses. +-- * Setup specific settings for specific squadrons. +-- +-- === +-- +-- ## Missions: +-- +-- [AID-AIR - AI AIR Dispatching](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/AID%20-%20AI%20Dispatching/AID-AIR%20-%20AI%20AIR%20Dispatching) +-- +-- === +-- +-- ## YouTube Channel: +-- +-- [DCS WORLD - MOOSE - AIR GCICAP - Build an automatic AIR Defense System](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl0S4KMNUUJpaUs6zZHjLKNx) +-- +-- === +-- +-- # QUICK START GUIDE +-- +-- The following class is available to model an AIR defense system. +-- +-- AI_AIR_DISPATCHER is the main AIR defense class that models the AIR defense system. +-- +-- Before you start using the AI_AIR_DISPATCHER, ask youself the following questions. +-- +-- +-- ## 1. Which coalition am I modeling an AIR defense system for? blue or red? +-- +-- One AI_AIR_DISPATCHER object can create a defense system for **one coalition**, which is blue or red. +-- If you want to create a **mutual defense system**, for both blue and red, then you need to create **two** AI_AIR_DISPATCHER **objects**, +-- each governing their defense system for one coalition. +-- +-- +-- ## 2. Which type of detection will I setup? Grouping based per AREA, per TYPE or per UNIT? (Later others will follow). +-- +-- The MOOSE framework leverages the @{Functional.Detection} classes to perform the reconnaissance, detecting enemy units +-- and reporting them to the head quarters. +-- Several types of @{Functional.Detection} classes exist, and the most common characteristics of these classes is that they: +-- +-- * Perform detections from multiple recce as one co-operating entity. +-- * Communicate with a @{Tasking.CommandCenter}, which consolidates each detection. +-- * Groups detections based on a method (per area, per type or per unit). +-- * Communicates detections. +-- +-- +-- ## 3. Which recce units can be used as part of the detection system? Only ground based, or also airborne? +-- +-- Depending on the type of mission you want to achieve, different types of units can be engaged to perform ground enemy targets reconnaissance. +-- Ground recce (FAC) are very useful units to determine the position of enemy ground targets when they spread out over the battlefield at strategic positions. +-- Using their varying detection technology, and especially those ground units which have spotting technology, can be extremely effective at +-- detecting targets at great range. The terrain elevation characteristics are a big tool in making ground recce to be more effective. +-- Unfortunately, they lack sometimes the visibility to detect targets at greater range, or when scenery is preventing line of sight. +-- If you succeed to position recce at higher level terrain providing a broad and far overview of the lower terrain in the distance, then +-- the recce will be very effective at detecting approaching enemy targets. Therefore, always use the terrain very carefully! +-- +-- Airborne recce (AFAC) are also very effective. The are capable of patrolling at a functional detection altitude, +-- having an overview of the whole battlefield. However, airborne recce can be vulnerable to air to ground attacks, +-- so you need air superiority to make them effective. +-- Airborne recce will also have varying ground detection technology, which plays a big role in the effectiveness of the reconnaissance. +-- Certain helicopter or plane types have ground searching radars or advanced ground scanning technology, and are very effective +-- compared to air units having only visual detection capabilities. +-- For example, for the red coalition, the Mi-28N and the Su-34; and for the blue side, the reaper, are such effective airborne recce units. +-- +-- Typically, don't want these recce units to engage with the enemy, you want to keep them at position. Therefore, it is a good practice +-- to set the ROE for these recce to hold weapons, and make them invisible from the enemy. +-- +-- It is not possible to perform a recce function as a player (unit). +-- +-- +-- ## 4. How do the defenses decide **when and where to engage** on approaching enemy units? +-- +-- The AIR dispacher needs you to setup (various) defense coordinates, which are strategic positions in the battle field to be defended. +-- Any ground based enemy approaching within the proximity of such a defense point, may trigger for a defensive action by friendly air units. +-- +-- There are 2 important parameters that play a role in the defensive decision making: defensiveness and reactivity. +-- +-- The AIR dispatcher provides various parameters to setup the **defensiveness**, +-- which models the decision **when** a defender will engage with the approaching enemy. +-- Defensiveness is calculated by a probability distribution model when to trigger a defense action, +-- depending on the distance of the enemy unit from the defense coordinates, and a **defensiveness factor**. +-- +-- The other parameter considered for defensive action is **where the enemy is located**, thus the distance from a defense coordinate, +-- which we call the **reactive distance**. By default, the reactive distance is set to 60km, but can be changed by the mission designer +-- using the available method explained further below. +-- The combination of the defensiveness and reactivity results in a model that, the closer the attacker is to the defense point, +-- the higher the probability will be that a defense action will be launched! +-- +-- +-- ## 5. Are defense coordinates and defense reactivity the only parameters? +-- +-- No, depending on the target type, and the threat level of the target, the probability of defense will be higher. +-- In other words, when a SAM-10 radar emitter is detected, its probabilty for defense will be much higher than when a BMP-1 vehicle is +-- detected, even when both enemies are at the same distance from a defense coordinate. +-- This will ensure optimal defenses, SEAD tasks will be launched much more quicker against engaging radar emitters, to ensure air superiority. +-- Approaching main battle tanks will be engaged much faster, than a group of approaching trucks. +-- +-- +-- ## 6. Which Squadrons will I create and which name will I give each Squadron? +-- +-- The AIR defense system works with **Squadrons**. Each Squadron must be given a unique name, that forms the **key** to the squadron. +-- Several options and activities can be set per Squadron. A free format name can be given, but always ensure that the name is meaningfull +-- for your mission, and remember that squadron names are used for communication to the players of your mission. +-- +-- There are mainly 3 types of defenses: **SEAD**, **CAS** and **BAI**. +-- +-- Suppression of Air Defenses (SEAD) are effective agains radar emitters. Close Air Support (CAS) is launched when the enemy is close near friendly units. +-- Battleground Air Interdiction (BAI) tasks are launched when there are no friendlies around. +-- +-- Depending on the defense type, different payloads will be needed. See further points on squadron definition. +-- +-- +-- ## 7. Where will the Squadrons be located? On Airbases? On Carrier Ships? On Farps? +-- +-- Squadrons are placed at the **home base** on an **airfield**, **carrier** or **farp**. +-- Carefully plan where each Squadron will be located as part of the defense system required for mission effective defenses. +-- If the home base of the squadron is too far from assumed enemy positions, then the defenses will be too late. +-- The home bases must be **behind** enemy lines, you want to prevent your home bases to be engaged by enemies! +-- Depending on the units applied for defenses, the home base can be further or closer to the enemies. +-- Any airbase, farp or carrier can act as the launching platform for AIR defenses. +-- Carefully plan which airbases will take part in the coalition. Color each airbase **in the color of the coalition**, using the mission editor, +-- or your air units will not return for landing at the airbase! +-- +-- +-- ## 8. Which helicopter or plane models will I assign for each Squadron? Do I need one plane model or more plane models per squadron? +-- +-- Per Squadron, one or multiple helicopter or plane models can be allocated as **Templates**. +-- These are late activated groups with one airplane or helicopter that start with a specific name, called the **template prefix**. +-- The AIR defense system will select from the given templates a random template to spawn a new plane (group). +-- +-- A squadron will perform specific task types (SEAD, CAS or BAI). So, squadrons will require specific templates for the +-- task types it will perform. A squadron executing SEAD defenses, will require a payload with long range anti-radar seeking missiles. +-- +-- +-- ## 9. Which payloads, skills and skins will these plane models have? +-- +-- Per Squadron, even if you have one plane model, you can still allocate multiple templates of one plane model, +-- each having different payloads, skills and skins. +-- The AIR defense system will select from the given templates a random template to spawn a new plane (group). +-- +-- +-- ## 10. How to squadrons engage in a defensive action? +-- +-- There are two ways how squadrons engage and execute your AIR defenses. +-- Squadrons can start the defense directly from the airbase, farp or carrier. When a squadron launches a defensive group, that group +-- will start directly from the airbase. The other way is to launch early on in the mission a patrolling mechanism. +-- Squadrons will launch air units to patrol in specific zone(s), so that when ground enemy targets are detected, that the airborne +-- AIR defenses can come immediately into action. +-- +-- +-- ## 11. For each Squadron doing a patrol, which zone types will I create? +-- +-- Per zone, evaluate whether you want: +-- +-- * simple trigger zones +-- * polygon zones +-- * moving zones +-- +-- Depending on the type of zone selected, a different @{Zone} object needs to be created from a ZONE_ class. +-- +-- +-- ## 12. Are moving defense coordinates possible? +-- +-- Yes, different COORDINATE types are possible to be used. +-- The COORDINATE_UNIT will help you to specify a defense coodinate that is attached to a moving unit. +-- +-- +-- ## 13. How much defense coordinates do I need to create? +-- +-- It depends, but the idea is to define only the necessary defense points that drive your mission. +-- If you define too much defense points, the performance of your mission may decrease. Per defense point defined, +-- all the possible enemies are evaluated. Note that each defense coordinate has a reach depending on the size of the defense radius. +-- The default defense radius is about 60km, and depending on the defense reactivity, defenses will be launched when the enemy is at +-- close or greater distance from the defense coordinate. +-- +-- +-- ## 14. For each Squadron doing patrols, what are the time intervals and patrol amounts to be performed? +-- +-- For each patrol: +-- +-- * **How many** patrol you want to have airborne at the same time? +-- * **How frequent** you want the defense mechanism to check whether to start a new patrol? +-- +-- other considerations: +-- +-- * **How far** is the patrol area from the engagement "hot zone". You want to ensure that the enemy is reached on time! +-- * **How safe** is the patrol area taking into account air superiority. Is it well defended, are there nearby A2A bases? +-- +-- +-- ## 15. For each Squadron, which takeoff method will I use? +-- +-- For each Squadron, evaluate which takeoff method will be used: +-- +-- * Straight from the air +-- * From the runway +-- * From a parking spot with running engines +-- * From a parking spot with cold engines +-- +-- **The default takeoff method is staight in the air.** +-- This takeoff method is the most useful if you want to avoid airplane clutter at airbases! +-- But it is the least realistic one! +-- +-- +-- ## 16. For each Squadron, which landing method will I use? +-- +-- For each Squadron, evaluate which landing method will be used: +-- +-- * Despawn near the airbase when returning +-- * Despawn after landing on the runway +-- * Despawn after engine shutdown after landing +-- +-- **The default landing method is despawn when near the airbase when returning.** +-- This landing method is the most useful if you want to avoid airplane clutter at airbases! +-- But it is the least realistic one! +-- +-- +-- ## 19. For each Squadron, which **defense overhead** will I use? +-- +-- For each Squadron, depending on the helicopter or airplane type (modern, old) and payload, which overhead is required to provide any defense? +-- +-- In other words, if **X** enemy ground units are detected, how many **Y** defense helicpters or airplanes need to engage (per squadron)? +-- The **Y** is dependent on the type of airplane (era), payload, fuel levels, skills etc. +-- But the most important factor is the payload, which is the amount of AIR weapons the defense can carry to attack the enemy ground units. +-- For example, a Ka-50 can carry 16 vikrs, that means, that it potentially can destroy at least 8 ground units without a reload of ammunication. +-- That means, that one defender can destroy more enemy ground units. +-- Thus, the overhead is a **factor** that will calculate dynamically how many **Y** defenses will be required based on **X** attackers detected. +-- +-- **The default overhead is 1. A smaller value than 1, like 0.25 will decrease the overhead to a 1 / 4 ratio, meaning, +-- one defender for each 4 detected ground enemy units. ** +-- +-- +-- ## 19. For each Squadron, which grouping will I use? +-- +-- When multiple targets are detected, how will defenses be grouped when multiple defense air units are spawned for multiple enemy ground units? +-- Per one, two, three, four? +-- +-- **The default grouping is 1. That means, that each spawned defender will act individually.** +-- But you can specify a number between 1 and 4, so that the defenders will act as a group. -- -- === -- -- ### Author: **FlightControl** rework of GCICAP + introduction of new concepts (squadrons). -- --- @module AI.AI_Air_Dispatcher +-- @module AI.AI_AIR_Dispatcher -- @image AI_Air_To_Ground_Dispatching.JPG @@ -21,10 +270,622 @@ do -- AI_AIR_DISPATCHER -- @type AI_AIR_DISPATCHER -- @extends Tasking.DetectionManager#DETECTION_MANAGER - --- Intermediary class that realized the common methods for the A2G and A2A dispatchers. + --- Create an automated AIR defense system based on a detection network of reconnaissance vehicles and air units, coordinating SEAD, BAI and CAP operations. -- -- === -- + -- When your mission is in the need to take control of the AI to automate and setup a process of air to ground defenses, this is the module you need. + -- The defense system work through the definition of defense coordinates, which are points in your friendly area within the battle field, that your mission need to have defended. + -- Multiple defense coordinates can be setup. Defense coordinates can be strategic or tactical positions or references to strategic units or scenery. + -- The AIR dispatcher will evaluate every x seconds the tactical situation around each defense coordinate. When a defense coordinate + -- is under threat, it will communicate through the command center that defensive actions need to be taken and will launch groups of air units for defense. + -- The level of threat to the defense coordinate varyies upon the strength and types of the enemy units, the distance to the defense point, and the defensiveness parameters. + -- Defensive actions are taken through probability, but the closer and the more threat the enemy poses to the defense coordinate, the faster it will be attacked by friendly AIR units. + -- + -- Please study carefully the underlying explanations how to setup and use this module, as it has many features. + -- It also requires a little study to ensure that you get a good understanding of the defense mechanisms, to ensure a strong + -- defense for your missions. + -- + -- === + -- + -- # USAGE GUIDE + -- + -- ## 1. AI\_AIR\_DISPATCHER constructor: + -- + -- ![Banner Image](..\Presentations\AI_AIR_DISPATCHER\AI_AIR_DISPATCHER-ME_1.JPG) + -- + -- + -- The @{#AI_AIR_DISPATCHER.New}() method creates a new AI_AIR_DISPATCHER instance. + -- + -- ### 1.1. Define the **reconnaissance network**: + -- + -- As part of the AI_AIR_DISPATCHER :New() constructor, a reconnaissance network must be given as the first parameter. + -- A reconnaissance network is provided by passing a @{Functional.Detection} object. + -- The most effective reconnaissance for the AIR dispatcher would be to use the @{Functional.Detection#DETECTION_AREAS} object. + -- + -- A reconnaissance network, is used to detect enemy ground targets, + -- potentially group them into areas, and to understand the position, level of threat of the enemy. + -- + -- ![Banner Image](..\Presentations\AI_AIR_DISPATCHER\Dia5.JPG) + -- + -- As explained in the introduction, depending on the type of mission you want to achieve, different types of units can be applied to detect ground enemy targets. + -- Ground based units are very useful to act as a reconnaissance, but they lack sometimes the visibility to detect targets at greater range. + -- Recce are very useful to acquire the position of enemy ground targets when spread out over the battlefield at strategic positions. + -- Ground units also have varying detectors, and especially the ground units which have laser guiding missiles can be extremely effective at + -- detecting targets at great range. The terrain elevation characteristics are a big tool in making ground recce to be more effective. + -- If you succeed to position recce at higher level terrain providing a broad and far overview of the lower terrain in the distance, then + -- the recce will be very effective at detecting approaching enemy targets. Therefore, always use the terrain very carefully! + -- + -- Beside ground level units to use for reconnaissance, air units are also very effective. The are capable of patrolling at great speed + -- covering a large terrain. However, airborne recce can be vulnerable to air to ground attacks, and you need air superiority to make then + -- effective. Also the instruments available at the air units play a big role in the effectiveness of the reconnaissance. + -- Air units which have ground detection capabilities will be much more effective than air units with only visual detection capabilities. + -- For the red coalition, the Mi-28N and for the blue side, the reaper are such effective reconnaissance airborne units. + -- + -- Reconnaissance networks are **dynamically constructed**, that is, they form part of the @{Functional.Detection} instance that is given as the first parameter to the AIR dispatcher. + -- By defining in a **smart way the names or name prefixes of the reconnaissance groups**, these groups will be **automatically added or removed** to or from the reconnaissance network, + -- when these groups are spawned in or destroyed during the ongoing battle. + -- By spawning in dynamically additional recce, you can ensure that there is sufficient reconnaissance coverage so the defense mechanism is continuously + -- alerted of new enemy ground targets. + -- + -- The following example defens a new reconnaissance network using a @{Functional.Detection#DETECTION_AREAS} object. + -- + -- -- Define a SET_GROUP object that builds a collection of groups that define the recce network. + -- -- Here we build the network with all the groups that have a name starting with CCCP Recce. + -- DetectionSetGroup = SET_GROUP:New() -- Defene a set of group objects, caled DetectionSetGroup. + -- + -- DetectionSetGroup:FilterPrefixes( { "CCCP Recce" } ) -- The DetectionSetGroup will search for groups that start with the name "CCCP Recce". + -- + -- -- This command will start the dynamic filtering, so when groups spawn in or are destroyed, + -- -- which have a group name starting with "CCCP Recce", then these will be automatically added or removed from the set. + -- DetectionSetGroup:FilterStart() + -- + -- -- This command defines the reconnaissance network. + -- -- It will group any detected ground enemy targets within a radius of 1km. + -- -- It uses the DetectionSetGroup, which defines the set of reconnaissance groups to detect for enemy ground targets. + -- Detection = DETECTION_AREAS:New( DetectionSetGroup, 1000 ) + -- + -- -- Setup the A2A dispatcher, and initialize it. + -- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection ) + -- + -- + -- The above example creates a SET_GROUP instance, and stores this in the variable (object) **DetectionSetGroup**. + -- **DetectionSetGroup** is then being configured to filter all active groups with a group name starting with `"CCCP Recce"` to be included in the set. + -- **DetectionSetGroup** is then calling `FilterStart()`, which is starting the dynamic filtering or inclusion of these groups. + -- Note that any destroy or new spawn of a group having a name, starting with the above prefix, will be removed or added to the set. + -- + -- Then a new detection object is created from the class `DETECTION_AREAS`. A grouping radius of 1000 meters (1km) is choosen. + -- + -- The `Detection` object is then passed to the @{#AI_AIR_DISPATCHER.New}() method to indicate the reconnaissance network + -- configuration and setup the AIR defense detection mechanism. + -- + -- ### 1.2. Setup the AIR dispatcher for both a red and blue coalition. + -- + -- Following the above described procedure, you'll need to create for each coalition an separate detection network, and a separate AIR dispatcher. + -- Ensure that while doing so, that you name the objects differently both for red and blue coalition. + -- + -- For example like this for the red coalition: + -- + -- DetectionRed = DETECTION_AREAS:New( DetectionSetGroupRed, 1000 ) + -- AIRDispatcherRed = AI_AIR_DISPATCHER:New( DetectionRed ) + -- + -- And for the blue coalition: + -- + -- DetectionBlue = DETECTION_AREAS:New( DetectionSetGroupBlue, 1000 ) + -- AIRDispatcherBlue = AI_AIR_DISPATCHER:New( DetectionBlue ) + -- + -- + -- Note: Also the SET_GROUP objects should be created for each coalition separately, containing each red and blue recce respectively! + -- + -- ### 1.3. Define the enemy ground target **grouping radius**, in case you use DETECTION_AREAS: + -- + -- The target grouping radius is a property of the DETECTION_AREAS class, that was passed to the AI_AIR_DISPATCHER:New() method, + -- but can be changed. The grouping radius should not be too small, but also depends on the types of ground forces and the way you want your mission to evolve. + -- A large radius will mean large groups of enemy ground targets, while making smaller groups will result in a more fragmented defense system. + -- Typically I suggest a grouping radius of 1km. This is the right balance to create efficient defenses. + -- + -- Note that detected targets are constantly re-grouped, that is, when certain detected enemy ground units are moving further than the group radius, + -- then these units will become a separate area being detected. This may result in additional defenses being started by the dispatcher! + -- So don't make this value too small! Again, I advise about 1km or 1000 meters. + -- + -- ## 2. Setup (a) **Defense Coordinate(s)**. + -- + -- As explained above, defense coordinates are the center of your defense operations. + -- The more threat to the defense coordinate, the higher it is likely a defensive action will be launched. + -- + -- Find below an example how to add defense coordinates: + -- + -- -- Add defense coordinates. + -- AIRDispatcher:AddDefenseCoordinate( "HQ", GROUP:FindByName( "HQ" ):GetCoordinate() ) + -- + -- In this example, the coordinate of a group called `"HQ"` is retrieved, using `:GetCoordinate()` + -- This returns a COORDINATE object, pointing to the first unit within the GROUP object. + -- + -- The method @{#AI_AIR_DISPATCHER.AddDefenseCoordinate}() adds a new defense coordinate to the `AIRDispatcher` object. + -- The first parameter is the key of the defense coordinate, the second the coordinate itself. + -- + -- Later, a COORDINATE_UNIT will be added to the framework, which can be used to assign "moving" coordinates to an AIR dispatcher. + -- + -- **REMEMBER!** + -- + -- - **Defense coordinates are the center of the AIR dispatcher defense system!** + -- - **You can define more defense coordinates to defend a larger area.** + -- - **Detected enemy ground targets are not immediately engaged, but are engaged with a reactivity or probability calculation!** + -- + -- But, there is more to it ... + -- + -- + -- ### 2.1. The **Defense Radius**. + -- + -- The defense radius defines the maximum radius that a defense will be initiated around each defense coordinate. + -- So even when there are targets further away than the defense radius, then these targets won't be engaged upon. + -- By default, the defense radius is set to 100km (100.000 meters), but can be changed using the @{#AI_AIR_DISPATCHER.SetDefenseRadius}() method. + -- Note that the defense radius influences the defense reactivity also! The larger the defense radius, the more reactive the defenses will be. + -- + -- For example: + -- + -- AIRDispatcher:SetDefenseRadius( 30000 ) + -- + -- This defines an AIR dispatcher which will engage on enemy ground targets within 30km radius around the defense coordinate. + -- Note that the defense radius **applies to all defense coordinates** defined within the AIR dispatcher. + -- + -- ### 2.2. The **Defense Reactivity**. + -- + -- There are 5 levels that can be configured to tweak the defense reactivity. As explained above, the threat to a defense coordinate is + -- also determined by the distance of the enemy ground target to the defense coordinate. + -- If you want to have a **low** defense reactivity, that is, the probability that an AIR defense will engage to the enemy ground target, then + -- use the @{#AI_AIR_DISPATCHER.SetDefenseReactivityLow}() method. For medium and high reactivity, use the methods + -- @{#AI_AIR_DISPATCHER.SetDefenseReactivityMedium}() and @{#AI_AIR_DISPATCHER.SetDefenseReactivityHigh}() respectively. + -- + -- Note that the reactivity of defenses is always in relation to the Defense Radius! the shorter the distance, + -- the less reactive the defenses will be in terms of distance to enemy ground targets! + -- + -- For example: + -- + -- AIRDispatcher:SetDefenseReactivityHigh() + -- + -- This defines an AIR dispatcher with high defense reactivity. + -- + -- ## 3. **Squadrons**. + -- + -- The AIR dispatcher works with **Squadrons**, that need to be defined using the different methods available. + -- + -- Use the method @{#AI_AIR_DISPATCHER.SetSquadron}() to **setup a new squadron** active at an airfield, farp or carrier, + -- while defining which helicopter or plane **templates** are being used by the squadron and how many **resources** are available. + -- + -- **Multiple squadrons** can be defined within one AIR dispatcher, each having specific defense tasks and defense parameter settings! + -- + -- Squadrons: + -- + -- * Have name (string) that is the identifier or **key** of the squadron. + -- * Have specific helicopter or plane **templates**. + -- * Are located at **one** airbase, farp or carrier. + -- * Optionally have a **limited set of resources**. The default is that squadrons have **unlimited resources**. + -- + -- The name of the squadron given acts as the **squadron key** in all `AIRDispatcher:SetSquadron...()` or `AIRDispatcher:GetSquadron...()` methods. + -- + -- Additionally, squadrons have specific configuration options to: + -- + -- * Control how new helicopters or aircraft are taking off from the airfield, farp or carrier (in the air, cold, hot, at the runway). + -- * Control how returning helicopters or aircraft are landing at the airfield, farp or carrier (in the air near the airbase, after landing, after engine shutdown). + -- * Control the **grouping** of new helicopters or aircraft spawned at the airfield, farp or carrier. If there is more than one helicopter or aircraft to be spawned, these may be grouped. + -- * Control the **overhead** or defensive strength of the squadron. Depending on the types of helicopters, planes, amount of resources and payload (weapon configuration) chosen, + -- the mission designer can choose to increase or reduce the amount of planes spawned. + -- + -- The method @{#AI_AIR_DISPATCHER.SetSquadron}() defines for you a new squadron. + -- The provided parameters are the squadron name, airbase name and a list of template prefixe, and a number that indicates the amount of resources. + -- + -- For example, this defines 3 new squadrons: + -- + -- AIRDispatcher:SetSquadron( "Maykop SEAD", AIRBASE.Caucasus.Maykop_Khanskaya, { "CCCP KA-50" }, 10 ) + -- AIRDispatcher:SetSquadron( "Maykop CAS", "CAS", { "CCCP KA-50" }, 10 ) + -- AIRDispatcher:SetSquadron( "Maykop BAI", "BAI", { "CCCP KA-50" }, 10 ) + -- + -- The latter 2 will depart from FARPs, which bare the name `"CAS"` and `"BAI"`. + -- + -- + -- ### 3.1. Squadrons **Tasking**. + -- + -- Squadrons can be commanded to execute 3 types of tasks, as explained above: + -- + -- - SEAD: Suppression of Air Defenses, which are ground targets that have medium or long range radar emitters. + -- - CAS : Close Air Support, when there are enemy ground targets close to friendly units. + -- - BAI : Battlefield Air Interdiction, which are targets further away from the frond-line. + -- + -- You need to configure each squadron which task types you want it to perform. Read on ... + -- + -- ### 3.2. Squadrons enemy ground target **engagement types**. + -- + -- There are two ways how targets can be engaged: directly **on call** from the airfield, farp or carrier, or through a **patrol**. + -- + -- Patrols are extremely handy, as these will airborne your helicopters or airplanes in advance. They will patrol in defined zones outlined, + -- and will engage with the targets once commanded. If the patrol zone is close enough to the enemy ground targets, then the time required + -- to engage is heavily minimized! + -- + -- However; patrols come with a side effect: since your resources are airborne, they will be vulnerable to incoming air attacks from the enemy. + -- + -- The mission designer needs to carefully balance the need for patrols or the need for engagement on call from the airfields. + -- + -- ### 3.3. Squadron **on call** engagement. + -- + -- So to make squadrons engage targets from the airfields, use the following methods: + -- + -- - For SEAD, use the @{#AI_AIR_DISPATCHER.SetSquadronSead}() method. + -- - For CAS, use the @{#AI_AIR_DISPATCHER.SetSquadronCas}() method. + -- - For BAI, use the @{#AI_AIR_DISPATCHER.SetSquadronBai}() method. + -- + -- Note that for the tasks, specific helicopter or airplane templates are required to be used, which you can configure using your mission editor. + -- Especially the payload (weapons configuration) is important to get right. + -- + -- For example, the following will define for the squadrons different tasks: + -- + -- AIRDispatcher:SetSquadron( "Maykop SEAD", AIRBASE.Caucasus.Maykop_Khanskaya, { "CCCP KA-50 SEAD" }, 10 ) + -- AIRDispatcher:SetSquadronSead( "Maykop SEAD", 120, 250 ) + -- + -- AIRDispatcher:SetSquadron( "Maykop CAS", "CAS", { "CCCP KA-50 CAS" }, 10 ) + -- AIRDispatcher:SetSquadronCas( "Maykop CAS", 120, 250 ) + -- + -- AIRDispatcher:SetSquadron( "Maykop BAI", "BAI", { "CCCP KA-50 BAI" }, 10 ) + -- AIRDispatcher:SetSquadronBai( "Maykop BAI", 120, 250 ) + -- + -- ### 3.4. Squadron **on patrol engagement**. + -- + -- Squadrons can be setup to patrol in the air near the engagement hot zone. + -- When needed, the AIR defense units will be close to the battle area, and can engage quickly. + -- + -- So to make squadrons engage targets from a patrol zone, use the following methods: + -- + -- - For SEAD, use the @{#AI_AIR_DISPATCHER.SetSquadronSeadPatrol}() method. + -- - For CAS, use the @{#AI_AIR_DISPATCHER.SetSquadronCasPatrol}() method. + -- - For BAI, use the @{#AI_AIR_DISPATCHER.SetSquadronBaiPatrol}() method. + -- + -- Because a patrol requires more parameters, the following methods must be used to fine-tune the patrols for each squadron. + -- + -- - For SEAD, use the @{#AI_AIR_DISPATCHER.SetSquadronSeadPatrolInterval}() method. + -- - For CAS, use the @{#AI_AIR_DISPATCHER.SetSquadronCasPatrolInterval}() method. + -- - For BAI, use the @{#AI_AIR_DISPATCHER.SetSquadronBaiPatrolInterval}() method. + -- + -- Here an example to setup patrols of various task types: + -- + -- AIRDispatcher:SetSquadron( "Maykop SEAD", AIRBASE.Caucasus.Maykop_Khanskaya, { "CCCP KA-50 SEAD" }, 10 ) + -- AIRDispatcher:SetSquadronSeadPatrol( "Maykop SEAD", PatrolZone, 300, 500, 50, 80, 250, 300 ) + -- AIRDispatcher:SetSquadronPatrolInterval( "Maykop SEAD", 2, 30, 60, 1, "SEAD" ) + -- + -- AIRDispatcher:SetSquadron( "Maykop CAS", "CAS", { "CCCP KA-50 CAS" }, 10 ) + -- AIRDispatcher:SetSquadronCasPatrol( "Maykop CAS", PatrolZone, 600, 700, 50, 80, 250, 300 ) + -- AIRDispatcher:SetSquadronPatrolInterval( "Maykop CAS", 2, 30, 60, 1, "CAS" ) + -- + -- AIRDispatcher:SetSquadron( "Maykop BAI", "BAI", { "CCCP KA-50 BAI" }, 10 ) + -- AIRDispatcher:SetSquadronBaiPatrol( "Maykop BAI", PatrolZone, 800, 900, 50, 80, 250, 300 ) + -- AIRDispatcher:SetSquadronPatrolInterval( "Maykop BAI", 2, 30, 60, 1, "BAI" ) + -- + -- + -- ### 3.5. Set squadron take-off methods + -- + -- Use the various SetSquadronTakeoff... methods to control how squadrons are taking-off from the home airfield, FARP or ship. + -- + -- * @{#AI_AIR_DISPATCHER.SetSquadronTakeoff}() is the generic configuration method to control takeoff from the air, hot, cold or from the runway. See the method for further details. + -- * @{#AI_AIR_DISPATCHER.SetSquadronTakeoffInAir}() will spawn new aircraft from the squadron directly in the air. + -- * @{#AI_AIR_DISPATCHER.SetSquadronTakeoffFromParkingCold}() will spawn new aircraft in without running engines at a parking spot at the airfield. + -- * @{#AI_AIR_DISPATCHER.SetSquadronTakeoffFromParkingHot}() will spawn new aircraft in with running engines at a parking spot at the airfield. + -- * @{#AI_AIR_DISPATCHER.SetSquadronTakeoffFromRunway}() will spawn new aircraft at the runway at the airfield. + -- + -- **The default landing method is to spawn new aircraft directly in the air.** + -- + -- Use these methods to fine-tune for specific airfields that are known to create bottlenecks, or have reduced airbase efficiency. + -- The more and the longer aircraft need to taxi at an airfield, the more risk there is that: + -- + -- * aircraft will stop waiting for each other or for a landing aircraft before takeoff. + -- * aircraft may get into a "dead-lock" situation, where two aircraft are blocking each other. + -- * aircraft may collide at the airbase. + -- * aircraft may be awaiting the landing of a plane currently in the air, but never lands ... + -- + -- Currently within the DCS engine, the airfield traffic coordination is erroneous and contains a lot of bugs. + -- If you experience while testing problems with aircraft take-off or landing, please use one of the above methods as a solution to workaround these issues! + -- + -- This example sets the default takeoff method to be from the runway. + -- And for a couple of squadrons overrides this default method. + -- + -- -- Setup the Takeoff methods + -- + -- -- The default takeoff + -- A2ADispatcher:SetDefaultTakeOffFromRunway() + -- + -- -- The individual takeoff per squadron + -- A2ADispatcher:SetSquadronTakeoff( "Mineralnye", AI_AIR_DISPATCHER.Takeoff.Air ) + -- A2ADispatcher:SetSquadronTakeoffInAir( "Sochi" ) + -- A2ADispatcher:SetSquadronTakeoffFromRunway( "Mozdok" ) + -- A2ADispatcher:SetSquadronTakeoffFromParkingCold( "Maykop" ) + -- A2ADispatcher:SetSquadronTakeoffFromParkingHot( "Novo" ) + -- + -- + -- ### 3.5.1. Set Squadron takeoff altitude when spawning new aircraft in the air. + -- + -- In the case of the @{#AI_AIR_DISPATCHER.SetSquadronTakeoffInAir}() there is also an other parameter that can be applied. + -- That is modifying or setting the **altitude** from where planes spawn in the air. + -- Use the method @{#AI_AIR_DISPATCHER.SetSquadronTakeoffInAirAltitude}() to set the altitude for a specific squadron. + -- The default takeoff altitude can be modified or set using the method @{#AI_AIR_DISPATCHER.SetSquadronTakeoffInAirAltitude}(). + -- As part of the method @{#AI_AIR_DISPATCHER.SetSquadronTakeoffInAir}() a parameter can be specified to set the takeoff altitude. + -- If this parameter is not specified, then the default altitude will be used for the squadron. + -- + -- ### 3.5.2. Set Squadron takeoff interval. + -- + -- The different types of available airfields have different amounts of available launching platforms: + -- + -- - Airbases typically have a lot of platforms. + -- - FARPs have 4 platforms. + -- - Ships have 2 to 4 platforms. + -- + -- Depending on the demand of requested takeoffs by the AIR dispatcher, an airfield can become overloaded. Too many aircraft need to be taken + -- off at the same time, which will result in clutter as described above. In order to better control this behaviour, a takeoff scheduler is implemented, + -- which can be used to control how many aircraft are ordered for takeoff between specific time intervals. + -- The takeff intervals can be specified per squadron, which make sense, as each squadron have a "home" airfield. + -- + -- For this purpose, the method @{#AI_AIR_DISPATCHER.SetSquadronTakeOffInterval}() can be used to specify the takeoff intervals of + -- aircraft groups per squadron to avoid cluttering of aircraft at airbases. + -- This is especially useful for FARPs and ships. Each takeoff dispatch is queued by the dispatcher and when the interval time + -- has been reached, a new group will be spawned or activated for takeoff. + -- + -- The interval needs to be estimated, and depends on the time needed for the aircraft group to actually depart from the launch platform, and + -- the way how the aircraft are starting up. Cold starts take the longest duration, hot starts a few seconds, and runway takeoff also a few seconds for FARPs and ships. + -- + -- See the underlying example: + -- + -- -- Imagine a squadron launched from a FARP, with a grouping of 4. + -- -- Aircraft will cold start from the FARP, and thus, a maximum of 4 aircraft can be launched at the same time. + -- -- Additionally, depending on the group composition of the aircraft, defending units will be ordered for takeoff together. + -- -- It takes about 3 to 4 minutes to takeoff helicopters from FARPs in cold start. + -- A2ADispatcher:SetSquadronTakeOffInterval( "Mineralnye", 60 * 4 ) + -- + -- + -- ### 3.6. Set squadron landing methods + -- + -- In analogy with takeoff, the landing methods are to control how squadrons land at the airfield: + -- + -- * @{#AI_AIR_DISPATCHER.SetSquadronLanding}() is the generic configuration method to control landing, namely despawn the aircraft near the airfield in the air, right after landing, or at engine shutdown. + -- * @{#AI_AIR_DISPATCHER.SetSquadronLandingNearAirbase}() will despawn the returning aircraft in the air when near the airfield. + -- * @{#AI_AIR_DISPATCHER.SetSquadronLandingAtRunway}() will despawn the returning aircraft directly after landing at the runway. + -- * @{#AI_AIR_DISPATCHER.SetSquadronLandingAtEngineShutdown}() will despawn the returning aircraft when the aircraft has returned to its parking spot and has turned off its engines. + -- + -- You can use these methods to minimize the airbase coodination overhead and to increase the airbase efficiency. + -- When there are lots of aircraft returning for landing, at the same airbase, the takeoff process will be halted, which can cause a complete failure of the + -- A2A defense system, as no new CAP or GCI planes can takeoff. + -- Note that the method @{#AI_AIR_DISPATCHER.SetSquadronLandingNearAirbase}() will only work for returning aircraft, not for damaged or out of fuel aircraft. + -- Damaged or out-of-fuel aircraft are returning to the nearest friendly airbase and will land, and are out of control from ground control. + -- + -- This example defines the default landing method to be at the runway. + -- And for a couple of squadrons overrides this default method. + -- + -- -- Setup the Landing methods + -- + -- -- The default landing method + -- A2ADispatcher:SetDefaultLandingAtRunway() + -- + -- -- The individual landing per squadron + -- A2ADispatcher:SetSquadronLandingAtRunway( "Mineralnye" ) + -- A2ADispatcher:SetSquadronLandingNearAirbase( "Sochi" ) + -- A2ADispatcher:SetSquadronLandingAtEngineShutdown( "Mozdok" ) + -- A2ADispatcher:SetSquadronLandingNearAirbase( "Maykop" ) + -- A2ADispatcher:SetSquadronLanding( "Novo", AI_AIR_DISPATCHER.Landing.AtRunway ) + -- + -- + -- ### 3.7. Set squadron **grouping**. + -- + -- Use the method @{#AI_AIR_DISPATCHER.SetSquadronGrouping}() to set the grouping of aircraft when spawned in. + -- + -- ![Banner Image](..\Presentations\AI_AIR_DISPATCHER\Dia12.JPG) + -- + -- In the case of **on call** engagement, the @{#AI_AIR_DISPATCHER.SetSquadronGrouping}() method has additional behaviour. + -- When there aren't enough patrol flights airborne, a on call will be initiated for the remaining + -- targets to be engaged. Depending on the grouping parameter, the spawned flights for on call aircraft are grouped into this setting. + -- For example with a group setting of 2, if 3 targets are detected and cannot be engaged by the available patrols or any airborne flight, + -- an additional on call flight needs to be started. + -- + -- The **grouping value is set for a Squadron**, and can be **dynamically adjusted** during mission execution, so to adjust the defense flights grouping when the tactical situation changes. + -- + -- ### 3.8. Set the squadron **overhead** to balance the effectiveness of the AIR defenses. + -- + -- The effectiveness can be set with the **overhead parameter**. This is a number that is used to calculate the amount of Units that dispatching command will allocate to GCI in surplus of detected amount of units. + -- The **default value** of the overhead parameter is 1.0, which means **equal balance**. + -- + -- ![Banner Image](..\Presentations\AI_AIR_DISPATCHER\Dia11.JPG) + -- + -- However, depending on the (type of) aircraft (strength and payload) in the squadron and the amount of resources available, this parameter can be changed. + -- + -- The @{#AI_AIR_DISPATCHER.SetSquadronOverhead}() method can be used to tweak the defense strength, + -- taking into account the plane types of the squadron. + -- + -- For example, a A-10C with full long-distance AIR missiles payload, may still be less effective than a Su-23 with short range AIR missiles... + -- So in this case, one may want to use the @{#AI_AIR_DISPATCHER.SetOverhead}() method to allocate more defending planes as the amount of detected attacking ground units. + -- The overhead must be given as a decimal value with 1 as the neutral value, which means that overhead values: + -- + -- * Higher than 1.0, for example 1.5, will increase the defense unit amounts. For 4 attacking ground units detected, 6 aircraft will be spawned. + -- * Lower than 1, for example 0.75, will decrease the defense unit amounts. For 4 attacking ground units detected, only 3 aircraft will be spawned. + -- + -- The amount of defending units is calculated by multiplying the amount of detected attacking ground units as part of the detected group + -- multiplied by the overhead parameter, and rounded up to the smallest integer. + -- + -- Typically, for AIR defenses, values small than 1 will be used. Here are some good values for a couple of aircraft to support CAS operations: + -- + -- - A-10C: 0.15 + -- - Su-34: 0.15 + -- - A-10A: 0.25 + -- - SU-25T: 0.10 + -- + -- So generically, the amount of missiles that an aircraft can take will determine its attacking effectiveness. The longer the range of the missiles, + -- the less risk that the defender may be destroyed by the enemy, thus, the less aircraft needs to be activated in a defense. + -- + -- The **overhead value is set for a Squadron**, and can be **dynamically adjusted** during mission execution, so to adjust the defense overhead when the tactical situation changes. + -- + -- ### 3.8. Set the squadron **engage limit**. + -- + -- To limit the amount of aircraft to defend against a large group of intruders, an **engage limit** can be defined per squadron. + -- This limit will avoid an extensive amount of aircraft to engage with the enemy if the attacking ground forces are enormous. + -- + -- Use the method @{#AI_AIR_DISPATCHER.SetSquadronEngageLimit}() to limit the amount of aircraft that will engage with the enemy, per squadron. + -- + -- ## 4. Set the **fuel treshold**. + -- + -- When aircraft get **out of fuel** to a certain %-tage, which is by default **15% (0.15)**, there are two possible actions that can be taken: + -- - The aircraft will go RTB, and will be replaced with a new aircraft if possible. + -- - The aircraft will refuel at a tanker, if a tanker has been specified for the squadron. + -- + -- Use the method @{#AI_AIR_DISPATCHER.SetSquadronFuelThreshold}() to set the **squadron fuel treshold** of the aircraft for all squadrons. + -- + -- ## 6. Other configuration options + -- + -- ### 6.1. Set a tactical display panel. + -- + -- Every 30 seconds, a tactical display panel can be shown that illustrates what the status is of the different groups controlled by AI_AIR_DISPATCHER. + -- Use the method @{#AI_AIR_DISPATCHER.SetTacticalDisplay}() to switch on the tactical display panel. The default will not show this panel. + -- Note that there may be some performance impact if this panel is shown. + -- + -- ## 10. Default settings. + -- + -- Default settings configure the standard behaviour of the squadrons. + -- This section a good overview of the different parameters that setup the behaviour of **ALL** the squadrons by default. + -- Note that default behaviour can be tweaked, and thus, this will change the behaviour of all the squadrons. + -- Unless there is a specific behaviour set for a specific squadron, the default configured behaviour will be followed. + -- + -- ## 10.1. Default **takeoff** behaviour. + -- + -- The default takeoff behaviour is set to **in the air**, which means that new spawned aircraft will be spawned directly in the air above the airbase by default. + -- + -- **The default takeoff method can be set for ALL squadrons that don't have an individual takeoff method configured.** + -- + -- * @{#AI_AIR_DISPATCHER.SetDefaultTakeoff}() is the generic configuration method to control takeoff by default from the air, hot, cold or from the runway. See the method for further details. + -- * @{#AI_AIR_DISPATCHER.SetDefaultTakeoffInAir}() will spawn by default new aircraft from the squadron directly in the air. + -- * @{#AI_AIR_DISPATCHER.SetDefaultTakeoffFromParkingCold}() will spawn by default new aircraft in without running engines at a parking spot at the airfield. + -- * @{#AI_AIR_DISPATCHER.SetDefaultTakeoffFromParkingHot}() will spawn by default new aircraft in with running engines at a parking spot at the airfield. + -- * @{#AI_AIR_DISPATCHER.SetDefaultTakeoffFromRunway}() will spawn by default new aircraft at the runway at the airfield. + -- + -- ## 10.2. Default landing behaviour. + -- + -- The default landing behaviour is set to **near the airbase**, which means that returning airplanes will be despawned directly in the air by default. + -- + -- The default landing method can be set for ALL squadrons that don't have an individual landing method configured. + -- + -- * @{#AI_AIR_DISPATCHER.SetDefaultLanding}() is the generic configuration method to control by default landing, namely despawn the aircraft near the airfield in the air, right after landing, or at engine shutdown. + -- * @{#AI_AIR_DISPATCHER.SetDefaultLandingNearAirbase}() will despawn by default the returning aircraft in the air when near the airfield. + -- * @{#AI_AIR_DISPATCHER.SetDefaultLandingAtRunway}() will despawn by default the returning aircraft directly after landing at the runway. + -- * @{#AI_AIR_DISPATCHER.SetDefaultLandingAtEngineShutdown}() will despawn by default the returning aircraft when the aircraft has returned to its parking spot and has turned off its engines. + -- + -- ## 10.3. Default **overhead**. + -- + -- The default overhead is set to **0.25**. That essentially means that for each 4 ground enemies there will be 1 aircraft dispatched. + -- + -- The default overhead value can be set for ALL squadrons that don't have an individual overhead value configured. + -- + -- Use the @{#AI_AIR_DISPATCHER.SetDefaultOverhead}() method can be used to set the default overhead or defense strength for ALL squadrons. + -- + -- ## 10.4. Default **grouping**. + -- + -- The default grouping is set to **one airplane**. That essentially means that there won't be any grouping applied by default. + -- + -- The default grouping value can be set for ALL squadrons that don't have an individual grouping value configured. + -- + -- Use the method @{#AI_AIR_DISPATCHER.SetDefaultGrouping}() to set the **default grouping** of spawned airplanes for all squadrons. + -- + -- ## 10.5. Default RTB fuel treshold. + -- + -- When an airplane gets **out of fuel** to a certain %-tage, which is **15% (0.15)**, it will go RTB, and will be replaced with a new airplane when applicable. + -- + -- Use the method @{#AI_AIR_DISPATCHER.SetDefaultFuelThreshold}() to set the **default fuel treshold** of spawned airplanes for all squadrons. + -- + -- ## 10.6. Default RTB damage treshold. + -- + -- When an airplane is **damaged** to a certain %-tage, which is **40% (0.40)**, it will go RTB, and will be replaced with a new airplane when applicable. + -- + -- Use the method @{#AI_AIR_DISPATCHER.SetDefaultDamageThreshold}() to set the **default damage treshold** of spawned airplanes for all squadrons. + -- + -- ## 10.7. Default settings for **patrol**. + -- + -- ### 10.7.1. Default **patrol time Interval**. + -- + -- Patrol dispatching is time event driven, and will evaluate in random time intervals if a new patrol needs to be dispatched. + -- + -- The default patrol time interval is between **180** and **600** seconds. + -- + -- Use the method @{#AI_AIR_DISPATCHER.SetDefaultPatrolTimeInterval}() to set the **default patrol time interval** of dispatched aircraft for ALL squadrons. + -- + -- Note that you can still change the patrol limit and patrol time intervals for each patrol individually using + -- the @{#AI_AIR_DISPATCHER.SetSquadronPatrolTimeInterval}() method. + -- + -- ### 10.7.2. Default **patrol limit**. + -- + -- Multiple patrol can be airborne at the same time for one squadron, which is controlled by the **patrol limit**. + -- The **default patrol limit** is 1 patrol per squadron to be airborne at the same time. + -- Note that the default patrol limit is used when a squadron patrol is defined, and cannot be changed afterwards. + -- So, ensure that you set the default patrol limit **before** you define or setup the squadron patrol. + -- + -- Use the method @{#AI_AIR_DISPATCHER.SetDefaultPatrolTimeInterval}() to set the **default patrol time interval** of dispatched aircraft patrols for all squadrons. + -- Note that you can still change the patrol limit and patrol time intervals for each patrol individually using + -- the @{#AI_AIR_DISPATCHER.SetSquadronPatrolTimeInterval}() method. + -- + -- ## 10.7.3. Default tanker for refuelling when executing CAP. + -- + -- Instead of sending CAP to RTB when out of fuel, you can let CAP refuel in mid air using a tanker. + -- This greatly increases the efficiency of your CAP operations. + -- + -- In the mission editor, setup a group with task Refuelling. A tanker unit of the correct coalition will be automatically selected. + -- Then, use the method @{#AI_AIR_DISPATCHER.SetDefaultTanker}() to set the tanker for the dispatcher. + -- Use the method @{#AI_AIR_DISPATCHER.SetDefaultFuelThreshold}() to set the %-tage left in the defender airplane tanks when a refuel action is needed. + -- + -- When the tanker specified is alive and in the air, the tanker will be used for refuelling. + -- + -- For example, the following setup will set the default refuel tanker to "Tanker": + -- + -- ![Banner Image](..\Presentations\AI_AIR_DISPATCHER\AI_AIR_DISPATCHER-ME_11.JPG) + -- + -- -- Define the CAP + -- A2ADispatcher:SetSquadron( "Sochi", AIRBASE.Caucasus.Sochi_Adler, { "SQ CCCP SU-34" }, 20 ) + -- A2ADispatcher:SetSquadronCap( "Sochi", ZONE:New( "PatrolZone" ), 4000, 8000, 600, 800, 1000, 1300 ) + -- A2ADispatcher:SetSquadronCapInterval("Sochi", 2, 30, 600, 1 ) + -- A2ADispatcher:SetSquadronGci( "Sochi", 900, 1200 ) + -- + -- -- Set the default tanker for refuelling to "Tanker", when the default fuel treshold has reached 90% fuel left. + -- A2ADispatcher:SetDefaultFuelThreshold( 0.9 ) + -- A2ADispatcher:SetDefaultTanker( "Tanker" ) + -- + -- ## 10.8. Default settings for GCI. + -- + -- ## 10.8.1. Optimal intercept point calculation. + -- + -- When intruders are detected, the intrusion path of the attackers can be monitored by the EWR. + -- Although defender planes might be on standby at the airbase, it can still take some time to get the defenses up in the air if there aren't any defenses airborne. + -- This time can easily take 2 to 3 minutes, and even then the defenders still need to fly towards the target, which takes also time. + -- + -- Therefore, an optimal **intercept point** is calculated which takes a couple of parameters: + -- + -- * The average bearing of the intruders for an amount of seconds. + -- * The average speed of the intruders for an amount of seconds. + -- * An assumed time it takes to get planes operational at the airbase. + -- + -- The **intercept point** will determine: + -- + -- * If there are any friendlies close to engage the target. These can be defenders performing CAP or defenders in RTB. + -- * The optimal airbase from where defenders will takeoff for GCI. + -- + -- Use the method @{#AI_AIR_DISPATCHER.SetIntercept}() to modify the assumed intercept delay time to calculate a valid interception. + -- + -- ## 10.8.2. Default Disengage Radius. + -- + -- The radius to **disengage any target** when the **distance** of the defender to the **home base** is larger than the specified meters. + -- The default Disengage Radius is **300km** (300000 meters). Note that the Disengage Radius is applicable to ALL squadrons! + -- + -- Use the method @{#AI_AIR_DISPATCHER.SetDisengageRadius}() to modify the default Disengage Radius to another distance setting. + -- + -- ## 11. Airbase capture: + -- + -- Different squadrons can be located at one airbase. + -- If the airbase gets captured, that is, when there is an enemy unit near the airbase, and there aren't anymore friendlies at the airbase, the airbase will change coalition ownership. + -- As a result, the GCI and CAP will stop! + -- However, the squadron will still stay alive. Any airplane that is airborne will continue its operations until all airborne airplanes + -- of the squadron will be destroyed. This to keep consistency of air operations not to confuse the players. + -- + -- + -- + -- -- @field #AI_AIR_DISPATCHER AI_AIR_DISPATCHER = { ClassName = "AI_AIR_DISPATCHER", @@ -47,6 +908,9 @@ do -- AI_AIR_DISPATCHER -- @type AI_AIR_DISPATCHER.DefenseCoordinates -- @map <#string,Core.Point#COORDINATE> A list of all defense coordinates mapped per defense coordinate name. + --- @field #AI_AIR_DISPATCHER.DefenseCoordinates DefenseCoordinates + AI_AIR_DISPATCHER.DefenseCoordinates = {} + --- Enumerator for spawns at airbases -- @type AI_AIR_DISPATCHER.Takeoff -- @extends Wrapper.Group#GROUP.Takeoff @@ -55,7 +919,7 @@ do -- AI_AIR_DISPATCHER AI_AIR_DISPATCHER.Takeoff = GROUP.Takeoff --- Defnes Landing location. - -- @field Landing + -- @field #AI_AIR_DISPATCHER.Landing AI_AIR_DISPATCHER.Landing = { NearAirbase = 1, AtRunway = 2, @@ -86,12 +950,35 @@ do -- AI_AIR_DISPATCHER AI_AIR_DISPATCHER.DefenseQueue = {} - + --- Defense approach types + -- @type #AI_AIR_DISPATCHER.DefenseApproach + AI_AIR_DISPATCHER.DefenseApproach = { + Random = 1, + Distance = 2, + } --- AI_AIR_DISPATCHER constructor. + -- This is defining the AIR DISPATCHER for one coaliton. + -- The Dispatcher works with a @{Functional.Detection#DETECTION_BASE} object that is taking of the detection of targets using the EWR units. + -- The Detection object is polymorphic, depending on the type of detection object choosen, the detection will work differently. -- @param #AI_AIR_DISPATCHER self -- @param Functional.Detection#DETECTION_BASE Detection The DETECTION object that will detects targets using the the Early Warning Radar network. -- @return #AI_AIR_DISPATCHER self + -- @usage + -- + -- -- Setup the Detection, using DETECTION_AREAS. + -- -- First define the SET of GROUPs that are defining the EWR network. + -- -- Here with prefixes DF CCCP AWACS, DF CCCP EWR. + -- DetectionSetGroup = SET_GROUP:New() + -- DetectionSetGroup:FilterPrefixes( { "DF CCCP AWACS", "DF CCCP EWR" } ) + -- DetectionSetGroup:FilterStart() + -- + -- -- Define the DETECTION_AREAS, using the DetectionSetGroup, with a 30km grouping radius. + -- Detection = DETECTION_AREAS:New( DetectionSetGroup, 30000 ) + -- + -- -- Now Setup the AIR dispatcher, and initialize it using the Detection object. + -- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection ) -- + -- function AI_AIR_DISPATCHER:New( Detection ) -- Inherits from DETECTION_MANAGER @@ -99,23 +986,31 @@ do -- AI_AIR_DISPATCHER self.Detection = Detection -- Functional.Detection#DETECTION_AREAS + self.Detection:FilterCategories( Unit.Category.GROUND_UNIT ) + -- This table models the DefenderSquadron templates. self.DefenderSquadrons = {} -- The Defender Squadrons. self.DefenderSpawns = {} self.DefenderTasks = {} -- The Defenders Tasks. self.DefenderDefault = {} -- The Defender Default Settings over all Squadrons. + -- TODO: Check detection through radar. +-- self.Detection:FilterCategories( { Unit.Category.GROUND } ) +-- self.Detection:InitDetectRadar( false ) +-- self.Detection:InitDetectVisual( true ) +-- self.Detection:SetRefreshTimeInterval( 30 ) + self:SetDefenseRadius() + self:SetDefenseLimit( nil ) + self:SetDefenseApproach( AI_AIR_DISPATCHER.DefenseApproach.Random ) self:SetIntercept( 300 ) -- A default intercept delay time of 300 seconds. + self:SetDisengageRadius( 300000 ) -- The default Disengage Radius is 300 km. self:SetDefaultTakeoff( AI_AIR_DISPATCHER.Takeoff.Air ) self:SetDefaultTakeoffInAirAltitude( 500 ) -- Default takeoff is 500 meters above the ground. self:SetDefaultLanding( AI_AIR_DISPATCHER.Landing.NearAirbase ) - self:SetDefaultEngageRadius( 150000 ) - self:SetDefaultOverhead( 1 ) self:SetDefaultGrouping( 1 ) - self:SetDefaultFuelThreshold( 0.15, 0 ) -- 15% of fuel remaining in the tank will trigger the airplane to return to base or refuel. self:SetDefaultDamageThreshold( 0.4 ) -- When 40% of damage, go RTB. self:SetDefaultPatrolTimeInterval( 180, 600 ) -- Between 180 and 600 seconds. @@ -130,7 +1025,7 @@ do -- AI_AIR_DISPATCHER -- @param #string From The From State string. -- @param #string Event The Event string. -- @param #string To The To State string. - -- @param Tasking.Task_A2G#AI_A2G Task + -- @param Tasking.Task_AIR#AI_AIR Task -- @param Wrapper.Unit#UNIT TaskUnit -- @param #string PlayerName @@ -234,9 +1129,11 @@ do -- AI_AIR_DISPATCHER self.DefenderPatrolIndex = 0 + self:SetDefenseReactivityMedium() + self.TakeoffScheduleID = self:ScheduleRepeat( 10, 10, 0, nil, self.ResourceTakeoff, self ) - self:__Start( 5 ) + self:__Start( 1 ) return self end @@ -253,7 +1150,56 @@ do -- AI_AIR_DISPATCHER for Resource = 1, DefenderSquadron.ResourceCount or 0 do self:ResourcePark( DefenderSquadron ) end + self:I( "Parked resources for squadron " .. DefenderSquadron.Name ) end + + end + + --- Locks the DefenseItem from being defended. + -- @param #AI_AIR_DISPATCHER self + -- @param #string DetectedItemIndex The index of the detected item. + function AI_AIR_DISPATCHER:Lock( DetectedItemIndex ) + self:F( { DetectedItemIndex = DetectedItemIndex } ) + local DetectedItem = self.Detection:GetDetectedItemByIndex( DetectedItemIndex ) + if DetectedItem then + self:F( { Locked = DetectedItem } ) + self.Detection:LockDetectedItem( DetectedItem ) + end + end + + + --- Unlocks the DefenseItem from being defended. + -- @param #AI_AIR_DISPATCHER self + -- @param #string DetectedItemIndex The index of the detected item. + function AI_AIR_DISPATCHER:Unlock( DetectedItemIndex ) + self:F( { DetectedItemIndex = DetectedItemIndex } ) + self:F( { Index = self.Detection.DetectedItemsByIndex } ) + local DetectedItem = self.Detection:GetDetectedItemByIndex( DetectedItemIndex ) + if DetectedItem then + self:F( { Unlocked = DetectedItem } ) + self.Detection:UnlockDetectedItem( DetectedItem ) + end + end + + + --- Sets maximum zones to be engaged at one time by defenders. + -- @param #AI_AIR_DISPATCHER self + -- @param #number DefenseLimit The maximum amount of detected items to be engaged at the same time. + function AI_AIR_DISPATCHER:SetDefenseLimit( DefenseLimit ) + self:F( { DefenseLimit = DefenseLimit } ) + + self.DefenseLimit = DefenseLimit + end + + + --- Sets the method of the tactical approach of the defenses. + -- @param #AI_AIR_DISPATCHER self + -- @param #number DefenseApproach Use the structure AI_AIR_DISPATCHER.DefenseApproach to set the defense approach. + -- The default defense approach is AI_AIR_DISPATCHER.DefenseApproach.Random. + function AI_AIR_DISPATCHER:SetDefenseApproach( DefenseApproach ) + self:F( { DefenseApproach = DefenseApproach } ) + + self._DefenseApproach = DefenseApproach end @@ -347,9 +1293,55 @@ do -- AI_AIR_DISPATCHER end end + do -- Manage the defensive behaviour - --- Define the defense radius to check if a target can be engaged by a squadron group. - -- When targets are detected that are still really far off, you don't want the dispatcher to launch defenders, as they might need to travel too far. + --- @param #AI_AIR_DISPATCHER self + -- @param #string DefenseCoordinateName The name of the coordinate to be defended by AIR defenses. + -- @param Core.Point#COORDINATE DefenseCoordinate The coordinate to be defended by AIR defenses. + function AI_AIR_DISPATCHER:AddDefenseCoordinate( DefenseCoordinateName, DefenseCoordinate ) + self.DefenseCoordinates[DefenseCoordinateName] = DefenseCoordinate + end + + --- @param #AI_AIR_DISPATCHER self + function AI_AIR_DISPATCHER:SetDefenseReactivityLow() + self.DefenseReactivity = 0.05 + end + + --- @param #AI_AIR_DISPATCHER self + function AI_AIR_DISPATCHER:SetDefenseReactivityMedium() + self.DefenseReactivity = 0.15 + end + + --- @param #AI_AIR_DISPATCHER self + function AI_AIR_DISPATCHER:SetDefenseReactivityHigh() + self.DefenseReactivity = 0.5 + end + + end + + + --- Define the radius to disengage any target when the distance to the home base is larger than the specified meters. + -- @param #AI_AIR_DISPATCHER self + -- @param #number DisengageRadius (Optional, Default = 300000) The radius to disengage a target when too far from the home base. + -- @return #AI_AIR_DISPATCHER + -- @usage + -- + -- -- Set 50km as the Disengage Radius. + -- AIRDispatcher:SetDisengageRadius( 50000 ) + -- + -- -- Set 100km as the Disengage Radius. + -- AIRDispatcher:SetDisngageRadius() -- 300000 is the default value. + -- + function AI_AIR_DISPATCHER:SetDisengageRadius( DisengageRadius ) + + self.DisengageRadius = DisengageRadius or 300000 + + return self + end + + + --- Define the defense radius to check if a target can be engaged by a squadron group for SEAD, CAS or BAI for defense. + -- When targets are detected that are still really far off, you don't want the AI_AIR_DISPATCHER to launch defenders, as they might need to travel too far. -- You want it to wait until a certain defend radius is reached, which is calculated as: -- 1. the **distance of the closest airbase to target**, being smaller than the **Defend Radius**. -- 2. the **distance to any defense reference point**. @@ -365,14 +1357,14 @@ do -- AI_AIR_DISPATCHER -- @return #AI_AIR_DISPATCHER -- @usage -- - -- -- Now Setup the A2G dispatcher, and initialize it using the Detection object. - -- A2GDispatcher = AI_AIR_DISPATCHER:New( Detection ) + -- -- Now Setup the AIR dispatcher, and initialize it using the Detection object. + -- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection ) -- -- -- Set 100km as the radius to defend from detected targets from the nearest airbase. - -- A2GDispatcher:SetDefendRadius( 100000 ) + -- AIRDispatcher:SetDefendRadius( 100000 ) -- -- -- Set 200km as the radius to defend. - -- A2GDispatcher:SetDefendRadius() -- 200000 is the default value. + -- AIRDispatcher:SetDefendRadius() -- 200000 is the default value. -- function AI_AIR_DISPATCHER:SetDefenseRadius( DefenseRadius ) @@ -383,7 +1375,6 @@ do -- AI_AIR_DISPATCHER return self end - --- Define a border area to simulate a **cold war** scenario. @@ -396,19 +1387,19 @@ do -- AI_AIR_DISPATCHER -- @return #AI_AIR_DISPATCHER -- @usage -- - -- -- Now Setup the A2G dispatcher, and initialize it using the Detection object. - -- A2GDispatcher = AI_AIR_DISPATCHER:New( Detection ) + -- -- Now Setup the AIR dispatcher, and initialize it using the Detection object. + -- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection ) -- - -- -- Set one ZONE_POLYGON object as the border for the A2G dispatcher. + -- -- Set one ZONE_POLYGON object as the border for the AIR dispatcher. -- local BorderZone = ZONE_POLYGON( "CCCP Border", GROUP:FindByName( "CCCP Border" ) ) -- The GROUP object is a late activate helicopter unit. - -- A2GDispatcher:SetBorderZone( BorderZone ) + -- AIRDispatcher:SetBorderZone( BorderZone ) -- -- or -- - -- -- Set two ZONE_POLYGON objects as the border for the A2G dispatcher. + -- -- Set two ZONE_POLYGON objects as the border for the AIR dispatcher. -- local BorderZone1 = ZONE_POLYGON( "CCCP Border1", GROUP:FindByName( "CCCP Border1" ) ) -- The GROUP object is a late activate helicopter unit. -- local BorderZone2 = ZONE_POLYGON( "CCCP Border2", GROUP:FindByName( "CCCP Border2" ) ) -- The GROUP object is a late activate helicopter unit. - -- A2GDispatcher:SetBorderZone( { BorderZone1, BorderZone2 } ) + -- AIRDispatcher:SetBorderZone( { BorderZone1, BorderZone2 } ) -- -- function AI_AIR_DISPATCHER:SetBorderZone( BorderZone ) @@ -430,11 +1421,11 @@ do -- AI_AIR_DISPATCHER -- @return #AI_AIR_DISPATCHER -- @usage -- - -- -- Now Setup the A2G dispatcher, and initialize it using the Detection object. - -- A2GDispatcher = AI_AIR_DISPATCHER:New( Detection ) + -- -- Now Setup the AIR dispatcher, and initialize it using the Detection object. + -- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection ) -- -- -- Now Setup the Tactical Display for debug mode. - -- A2GDispatcher:SetTacticalDisplay( true ) + -- AIRDispatcher:SetTacticalDisplay( true ) -- function AI_AIR_DISPATCHER:SetTacticalDisplay( TacticalDisplay ) @@ -451,11 +1442,11 @@ do -- AI_AIR_DISPATCHER -- @return #AI_AIR_DISPATCHER -- @usage -- - -- -- Now Setup the A2G dispatcher, and initialize it using the Detection object. - -- A2GDispatcher = AI_AIR_DISPATCHER:New( Detection ) + -- -- Now Setup the AIR dispatcher, and initialize it using the Detection object. + -- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection ) -- -- -- Now Setup the default damage treshold. - -- A2GDispatcher:SetDefaultDamageThreshold( 0.90 ) -- Go RTB when the airplane 90% damaged. + -- AIRDispatcher:SetDefaultDamageThreshold( 0.90 ) -- Go RTB when the airplane 90% damaged. -- function AI_AIR_DISPATCHER:SetDefaultDamageThreshold( DamageThreshold ) @@ -473,11 +1464,11 @@ do -- AI_AIR_DISPATCHER -- @return #AI_AIR_DISPATCHER -- @usage -- - -- -- Now Setup the A2G dispatcher, and initialize it using the Detection object. - -- A2GDispatcher = AI_AIR_DISPATCHER:New( Detection ) + -- -- Now Setup the AIR dispatcher, and initialize it using the Detection object. + -- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection ) -- -- -- Now Setup the default Patrol time interval. - -- A2GDispatcher:SetDefaultPatrolTimeInterval( 300, 1200 ) -- Between 300 and 1200 seconds. + -- AIRDispatcher:SetDefaultPatrolTimeInterval( 300, 1200 ) -- Between 300 and 1200 seconds. -- function AI_AIR_DISPATCHER:SetDefaultPatrolTimeInterval( PatrolMinSeconds, PatrolMaxSeconds ) @@ -495,11 +1486,11 @@ do -- AI_AIR_DISPATCHER -- @return #AI_AIR_DISPATCHER -- @usage -- - -- -- Now Setup the A2G dispatcher, and initialize it using the Detection object. - -- A2GDispatcher = AI_AIR_DISPATCHER:New( Detection ) + -- -- Now Setup the AIR dispatcher, and initialize it using the Detection object. + -- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection ) -- -- -- Now Setup the default Patrol limit. - -- A2GDispatcher:SetDefaultPatrolLimit( 2 ) -- Maximum 2 Patrol per squadron. + -- AIRDispatcher:SetDefaultPatrolLimit( 2 ) -- Maximum 2 Patrol per squadron. -- function AI_AIR_DISPATCHER:SetDefaultPatrolLimit( PatrolLimit ) @@ -510,17 +1501,17 @@ do -- AI_AIR_DISPATCHER --- Set the default engage limit for squadrons, which will be used to determine how many air units will engage at the same time with the enemy. - -- The default eatrol limit is 1, which means one patrol group maximum per squadron. + -- The default eatrol limit is 1, which means one eatrol group maximum per squadron. -- @param #AI_AIR_DISPATCHER self -- @param #number EngageLimit The maximum engages that can be done at the same time per squadron. -- @return #AI_AIR_DISPATCHER -- @usage -- - -- -- Now Setup the A2G dispatcher, and initialize it using the Detection object. - -- A2GDispatcher = AI_AIR_DISPATCHER:New( Detection ) + -- -- Now Setup the AIR dispatcher, and initialize it using the Detection object. + -- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection ) -- -- -- Now Setup the default Patrol limit. - -- A2GDispatcher:SetDefaultEngageLimit( 2 ) -- Maximum 2 engagements with the enemy per squadron. + -- AIRDispatcher:SetDefaultEngageLimit( 2 ) -- Maximum 2 engagements with the enemy per squadron. -- function AI_AIR_DISPATCHER:SetDefaultEngageLimit( EngageLimit ) @@ -530,7 +1521,6 @@ do -- AI_AIR_DISPATCHER end - function AI_AIR_DISPATCHER:SetIntercept( InterceptDelay ) self.DefenderDefault.InterceptDelay = InterceptDelay @@ -688,7 +1678,7 @@ do -- AI_AIR_DISPATCHER -- * Are **located at one specific airbase**. Multiple squadrons can be located at one airbase through. -- * Optionally have a limited set of **resources**. The default is that squadrons have unlimited resources. -- - -- The name of the squadron given acts as the **squadron key** in the AI\_A2G\_DISPATCHER:Squadron...() methods. + -- The name of the squadron given acts as the **squadron key** in the AI\_AIR\_DISPATCHER:Squadron...() methods. -- -- Additionally, squadrons have specific configuration options to: -- @@ -724,76 +1714,111 @@ do -- AI_AIR_DISPATCHER -- @param #number ResourceCount (optional) A number that specifies how many resources are in stock of the squadron. If not specified, the squadron will have infinite resources available. -- -- @usage - -- -- Now Setup the A2G dispatcher, and initialize it using the Detection object. - -- A2GDispatcher = AI_AIR_DISPATCHER:New( Detection ) + -- -- Now Setup the AIR dispatcher, and initialize it using the Detection object. + -- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection ) -- -- @usage -- -- This will create squadron "Squadron1" at "Batumi" airbase, and will use plane types "SQ1" and has 40 planes in stock... - -- A2GDispatcher:SetSquadron( "Squadron1", "Batumi", "SQ1", 40 ) + -- AIRDispatcher:SetSquadron( "Squadron1", "Batumi", "SQ1", 40 ) -- -- @usage -- -- This will create squadron "Sq 1" at "Batumi" airbase, and will use plane types "Mig-29" and "Su-27" and has 20 planes in stock... - -- -- Note that in this implementation, the A2G dispatcher will select a random plane type when a new plane (group) needs to be spawned for defenses. + -- -- Note that in this implementation, the AIR dispatcher will select a random plane type when a new plane (group) needs to be spawned for defenses. -- -- Note the usage of the {} for the airplane templates list. - -- A2GDispatcher:SetSquadron( "Sq 1", "Batumi", { "Mig-29", "Su-27" }, 40 ) + -- AIRDispatcher:SetSquadron( "Sq 1", "Batumi", { "Mig-29", "Su-27" }, 40 ) -- -- @usage -- -- This will create 2 squadrons "104th" and "23th" at "Batumi" airbase, and will use plane types "Mig-29" and "Su-27" respectively and each squadron has 10 planes in stock... - -- A2GDispatcher:SetSquadron( "104th", "Batumi", "Mig-29", 10 ) - -- A2GDispatcher:SetSquadron( "23th", "Batumi", "Su-27", 10 ) + -- AIRDispatcher:SetSquadron( "104th", "Batumi", "Mig-29", 10 ) + -- AIRDispatcher:SetSquadron( "23th", "Batumi", "Su-27", 10 ) -- -- @usage -- -- This is an example like the previous, but now with infinite resources. -- -- The ResourceCount parameter is not given in the SetSquadron method. - -- A2GDispatcher:SetSquadron( "104th", "Batumi", "Mig-29" ) - -- A2GDispatcher:SetSquadron( "23th", "Batumi", "Su-27" ) + -- AIRDispatcher:SetSquadron( "104th", "Batumi", "Mig-29" ) + -- AIRDispatcher:SetSquadron( "23th", "Batumi", "Su-27" ) -- -- -- @return #AI_AIR_DISPATCHER function AI_AIR_DISPATCHER:SetSquadron( SquadronName, AirbaseName, TemplatePrefixes, ResourceCount ) + + local Squadron = AI_AIR_SQUADRON:New( SquadronName, AirbaseName, TemplatePrefixes, ResourceCount ) + + return self:SetSquadron2( Squadron ) + end + + --- This is the new method to define Squadrons programmatically. + -- + -- Define a squadron using the AI_AIR_SQUADRON class. + -- + -- Squadrons: + -- + -- * Have a **name or key** that is the identifier or key of the squadron. + -- * Have **specific plane types** defined by **templates**. + -- * Are **located at one specific airbase**. Multiple squadrons can be located at one airbase through. + -- * Optionally have a limited set of **resources**. The default is that squadrons have unlimited resources. + -- + -- The name of the squadron given acts as the **squadron key** in the AI\_AIR\_DISPATCHER:Squadron...() methods. + -- + -- Additionally, squadrons have specific configuration options to: + -- + -- * Control how new aircraft are **taking off** from the airfield (in the air, cold, hot, at the runway). + -- * Control how returning aircraft are **landing** at the airfield (in the air near the airbase, after landing, after engine shutdown). + -- * Control the **grouping** of new aircraft spawned at the airfield. If there is more than one aircraft to be spawned, these may be grouped. + -- * Control the **overhead** or defensive strength of the squadron. Depending on the types of planes and amount of resources, the mission designer can choose to increase or reduce the amount of planes spawned. + -- + -- For performance and bug workaround reasons within DCS, squadrons have different methods to spawn new aircraft or land returning or damaged aircraft. + -- + -- @param #AI_AIR_DISPATCHER self + -- @param AI.AI_Air_Squadron#AI_AIR_SQUADRON Squadron The Air Squadron to be set active for the Air Dispatcher. + -- + -- @usage + -- -- Now Setup the AIR dispatcher, and initialize it using the Detection object. + -- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection ) + -- + -- @usage + -- -- This will create squadron "Squadron1" at "Batumi" airbase, and will use plane types "SQ1" and has 40 planes in stock... + -- AIRDispatcher:SetSquadron( "Squadron1", "Batumi", "SQ1", 40 ) + -- + -- @usage + -- -- This will create squadron "Sq 1" at "Batumi" airbase, and will use plane types "Mig-29" and "Su-27" and has 20 planes in stock... + -- -- Note that in this implementation, the AIR dispatcher will select a random plane type when a new plane (group) needs to be spawned for defenses. + -- -- Note the usage of the {} for the airplane templates list. + -- AIRDispatcher:SetSquadron( "Sq 1", "Batumi", { "Mig-29", "Su-27" }, 40 ) + -- + -- @usage + -- -- This will create 2 squadrons "104th" and "23th" at "Batumi" airbase, and will use plane types "Mig-29" and "Su-27" respectively and each squadron has 10 planes in stock... + -- AIRDispatcher:SetSquadron( "104th", "Batumi", "Mig-29", 10 ) + -- AIRDispatcher:SetSquadron( "23th", "Batumi", "Su-27", 10 ) + -- + -- @usage + -- -- This is an example like the previous, but now with infinite resources. + -- -- The ResourceCount parameter is not given in the SetSquadron method. + -- AIRDispatcher:SetSquadron( "104th", "Batumi", "Mig-29" ) + -- AIRDispatcher:SetSquadron( "23th", "Batumi", "Su-27" ) + -- + -- + -- @return #AI_AIR_DISPATCHER + function AI_AIR_DISPATCHER:SetSquadron2( Squadron ) + local SquadronName = Squadron:GetName() -- Retrieves the Squadron Name. self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {} local DefenderSquadron = self.DefenderSquadrons[SquadronName] - DefenderSquadron.Name = SquadronName - DefenderSquadron.Airbase = AIRBASE:FindByName( AirbaseName ) - DefenderSquadron.AirbaseName = DefenderSquadron.Airbase:GetName() - if not DefenderSquadron.Airbase then - error( "Cannot find airbase with name:" .. AirbaseName ) - end - - DefenderSquadron.Spawn = {} - if type( TemplatePrefixes ) == "string" then - local SpawnTemplate = TemplatePrefixes - self.DefenderSpawns[SpawnTemplate] = self.DefenderSpawns[SpawnTemplate] or SPAWN:New( SpawnTemplate ) -- :InitCleanUp( 180 ) - DefenderSquadron.Spawn[1] = self.DefenderSpawns[SpawnTemplate] - else - for TemplateID, SpawnTemplate in pairs( TemplatePrefixes ) do - self.DefenderSpawns[SpawnTemplate] = self.DefenderSpawns[SpawnTemplate] or SPAWN:New( SpawnTemplate ) -- :InitCleanUp( 180 ) - DefenderSquadron.Spawn[#DefenderSquadron.Spawn+1] = self.DefenderSpawns[SpawnTemplate] - end - end - DefenderSquadron.ResourceCount = ResourceCount - DefenderSquadron.TemplatePrefixes = TemplatePrefixes - DefenderSquadron.Captured = false -- Not captured. This flag will be set to true, when the airbase where the squadron is located, is captured. - - self:SetSquadronTakeoffInterval( SquadronName, 0 ) - - self:F( { Squadron = {SquadronName, AirbaseName, TemplatePrefixes, ResourceCount } } ) - return self end - --- Get an item from the Squadron table. + --- Get the @{AI.AI_Air_Squadron} object from the Squadron Name given. -- @param #AI_AIR_DISPATCHER self - -- @return #table + -- @param #string SquadronName The Squadron Name to search the Squadron object. + -- @return AI.AI_Air_Squadron#AI_AIR_SQUADRON The Squadron object. function AI_AIR_DISPATCHER:GetSquadron( SquadronName ) local DefenderSquadron = self.DefenderSquadrons[SquadronName] if not DefenderSquadron then - error( "Unknown Squadron:" .. SquadronName ) + error( "Unknown Squadron for Dispatcher:" .. SquadronName ) end return DefenderSquadron @@ -809,7 +1834,7 @@ do -- AI_AIR_DISPATCHER -- @usage -- -- -- Set the Squadron visible before startup of dispatcher. - -- A2GDispatcher:SetSquadronVisible( "Mineralnye" ) + -- AIRDispatcher:SetSquadronVisible( "Mineralnye" ) -- -- TODO: disabling because of bug in queueing. -- function AI_AIR_DISPATCHER:SetSquadronVisible( SquadronName ) @@ -835,7 +1860,7 @@ do -- AI_AIR_DISPATCHER -- @usage -- -- -- Set the Squadron visible before startup of dispatcher. - -- local IsVisible = A2GDispatcher:IsSquadronVisible( "Mineralnye" ) + -- local IsVisible = AIRDispatcher:IsSquadronVisible( "Mineralnye" ) -- function AI_AIR_DISPATCHER:IsSquadronVisible( SquadronName ) @@ -857,7 +1882,7 @@ do -- AI_AIR_DISPATCHER -- @usage -- -- -- Set the Squadron Takeoff interval every 60 seconds for squadron "SQ50", which is good for a FARP cold start. - -- A2GDispatcher:SetSquadronTakeoffInterval( "SQ50", 60 ) + -- AIRDispatcher:SetSquadronTakeoffInterval( "SQ50", 60 ) -- function AI_AIR_DISPATCHER:SetSquadronTakeoffInterval( SquadronName, TakeoffInterval ) @@ -893,8 +1918,8 @@ do -- AI_AIR_DISPATCHER -- -- -- Patrol Squadron execution. -- PatrolZoneEast = ZONE_POLYGON:New( "Patrol Zone East", GROUP:FindByName( "Patrol Zone East" ) ) - -- A2GDispatcher:SetSquadronSeadPatrol( "Mineralnye", PatrolZoneEast, 4000, 10000, 500, 600, 800, 900 ) - -- A2GDispatcher:SetSquadronPatrolInterval( "Mineralnye", 2, 30, 60, 1, "SEAD" ) + -- AIRDispatcher:SetSquadronSeadPatrol( "Mineralnye", PatrolZoneEast, 4000, 10000, 500, 600, 800, 900 ) + -- AIRDispatcher:SetSquadronPatrolInterval( "Mineralnye", 2, 30, 60, 1, "SEAD" ) -- function AI_AIR_DISPATCHER:SetSquadronPatrolInterval( SquadronName, PatrolLimit, LowInterval, HighInterval, Probability, DefenseTaskType ) @@ -926,144 +1951,7 @@ do -- AI_AIR_DISPATCHER end - - --- Set the squadron Patrol parameters for SEAD tasks. - -- @param #AI_AIR_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @param #number PatrolLimit (optional) The maximum amount of Patrol groups to be spawned. Note that a Patrol is a group, so can consist out of 1 to 4 airplanes. The default is 1 Patrol group. - -- @param #number LowInterval (optional) The minimum time boundary in seconds when a new Patrol will be spawned. The default is 180 seconds. - -- @param #number HighInterval (optional) The maximum time boundary in seconds when a new Patrol will be spawned. The default is 600 seconds. - -- @param #number Probability Is not in use, you can skip this parameter. - -- @return #AI_AIR_DISPATCHER - -- @usage - -- - -- -- Patrol Squadron execution. - -- PatrolZoneEast = ZONE_POLYGON:New( "Patrol Zone East", GROUP:FindByName( "Patrol Zone East" ) ) - -- A2GDispatcher:SetSquadronSeadPatrol( "Mineralnye", PatrolZoneEast, 4000, 10000, 500, 600, 800, 900 ) - -- A2GDispatcher:SetSquadronSeadPatrolInterval( "Mineralnye", 2, 30, 60, 1 ) - -- - function AI_AIR_DISPATCHER:SetSquadronSeadPatrolInterval( SquadronName, PatrolLimit, LowInterval, HighInterval, Probability ) - - self:SetSquadronPatrolInterval( SquadronName, PatrolLimit, LowInterval, HighInterval, Probability, "SEAD" ) - - end - - --- Set the squadron Patrol parameters for CAS tasks. - -- @param #AI_AIR_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @param #number PatrolLimit (optional) The maximum amount of Patrol groups to be spawned. Note that a Patrol is a group, so can consist out of 1 to 4 airplanes. The default is 1 Patrol group. - -- @param #number LowInterval (optional) The minimum time boundary in seconds when a new Patrol will be spawned. The default is 180 seconds. - -- @param #number HighInterval (optional) The maximum time boundary in seconds when a new Patrol will be spawned. The default is 600 seconds. - -- @param #number Probability Is not in use, you can skip this parameter. - -- @return #AI_AIR_DISPATCHER - -- @usage - -- - -- -- Patrol Squadron execution. - -- PatrolZoneEast = ZONE_POLYGON:New( "Patrol Zone East", GROUP:FindByName( "Patrol Zone East" ) ) - -- A2GDispatcher:SetSquadronCasPatrol( "Mineralnye", PatrolZoneEast, 4000, 10000, 500, 600, 800, 900 ) - -- A2GDispatcher:SetSquadronCasPatrolInterval( "Mineralnye", 2, 30, 60, 1 ) - -- - function AI_AIR_DISPATCHER:SetSquadronCasPatrolInterval( SquadronName, PatrolLimit, LowInterval, HighInterval, Probability ) - - self:SetSquadronPatrolInterval( SquadronName, PatrolLimit, LowInterval, HighInterval, Probability, "CAS" ) - - end - - - --- Set the squadron Patrol parameters for BAI tasks. - -- @param #AI_AIR_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @param #number PatrolLimit (optional) The maximum amount of Patrol groups to be spawned. Note that a Patrol is a group, so can consist out of 1 to 4 airplanes. The default is 1 Patrol group. - -- @param #number LowInterval (optional) The minimum time boundary in seconds when a new Patrol will be spawned. The default is 180 seconds. - -- @param #number HighInterval (optional) The maximum time boundary in seconds when a new Patrol will be spawned. The default is 600 seconds. - -- @param #number Probability Is not in use, you can skip this parameter. - -- @return #AI_AIR_DISPATCHER - -- @usage - -- - -- -- Patrol Squadron execution. - -- PatrolZoneEast = ZONE_POLYGON:New( "Patrol Zone East", GROUP:FindByName( "Patrol Zone East" ) ) - -- A2GDispatcher:SetSquadronBaiPatrol( "Mineralnye", PatrolZoneEast, 4000, 10000, 500, 600, 800, 900 ) - -- A2GDispatcher:SetSquadronBaiPatrolInterval( "Mineralnye", 2, 30, 60, 1 ) - -- - function AI_AIR_DISPATCHER:SetSquadronBaiPatrolInterval( SquadronName, PatrolLimit, LowInterval, HighInterval, Probability ) - - self:SetSquadronPatrolInterval( SquadronName, PatrolLimit, LowInterval, HighInterval, Probability, "BAI" ) - - end - - - --- - -- @param #AI_AIR_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @return #AI_AIR_DISPATCHER - function AI_AIR_DISPATCHER:GetPatrolDelay( SquadronName ) - - self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {} - self.DefenderSquadrons[SquadronName].Patrol = self.DefenderSquadrons[SquadronName].Patrol or {} - - local DefenderSquadron = self:GetSquadron( SquadronName ) - - local Patrol = self.DefenderSquadrons[SquadronName].Patrol - if Patrol then - return math.random( Patrol.LowInterval, Patrol.HighInterval ) - else - error( "This squadron does not exist:" .. SquadronName ) - end - end - - --- - -- @param #AI_AIR_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @return #table DefenderSquadron - function AI_AIR_DISPATCHER:CanPatrol( SquadronName, DefenseTaskType ) - self:F({SquadronName = SquadronName}) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - - if DefenderSquadron.Captured == false then -- We can only spawn new Patrol if the base has not been captured. - - if ( not DefenderSquadron.ResourceCount ) or ( DefenderSquadron.ResourceCount and DefenderSquadron.ResourceCount > 0 ) then -- And, if there are sufficient resources. - - local Patrol = DefenderSquadron[DefenseTaskType] - if Patrol and Patrol.Patrol == true then - local PatrolCount = self:CountPatrolAirborne( SquadronName, DefenseTaskType ) - self:F( { PatrolCount = PatrolCount, PatrolLimit = Patrol.PatrolLimit, PatrolProbability = Patrol.Probability } ) - if PatrolCount < Patrol.PatrolLimit then - local Probability = math.random() - if Probability <= Patrol.Probability then - return DefenderSquadron, Patrol - end - end - else - self:F( "No patrol for " .. SquadronName ) - end - end - end - return nil - end - - - --- - -- @param #AI_AIR_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @return #table DefenderSquadron - function AI_AIR_DISPATCHER:CanDefend( SquadronName, DefenseTaskType ) - self:F({SquadronName = SquadronName, DefenseTaskType}) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - - if DefenderSquadron.Captured == false then -- We can only spawn new defense if the home airbase has not been captured. - - if ( not DefenderSquadron.ResourceCount ) or ( DefenderSquadron.ResourceCount and DefenderSquadron.ResourceCount > 0 ) then -- And, if there are sufficient resources. - if DefenderSquadron[DefenseTaskType] and ( DefenderSquadron[DefenseTaskType].Defend == true ) then - return DefenderSquadron, DefenderSquadron[DefenseTaskType] - end - end - end - return nil - end - --- Set the squadron engage limit for a specific task type. -- Mission designers should not use this method, instead use the below methods. This method is used by the below methods. -- @@ -1080,7 +1968,7 @@ do -- AI_AIR_DISPATCHER -- -- -- Patrol Squadron execution. -- PatrolZoneEast = ZONE_POLYGON:New( "Patrol Zone East", GROUP:FindByName( "Patrol Zone East" ) ) - -- A2GDispatcher:SetSquadronEngageLimit( "Mineralnye", 2, "SEAD" ) -- Engage maximum 2 groups with the enemy for SEAD defense. + -- AIRDispatcher:SetSquadronEngageLimit( "Mineralnye", 2, "SEAD" ) -- Engage maximum 2 groups with the enemy for SEAD defense. -- function AI_AIR_DISPATCHER:SetSquadronEngageLimit( SquadronName, EngageLimit, DefenseTaskType ) @@ -1098,302 +1986,12 @@ do -- AI_AIR_DISPATCHER - --- - -- @param #AI_AIR_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @param #number EngageMinSpeed (optional, default = 50% of max speed) The minimum speed at which the SEAD task can be executed. - -- @param #number EngageMaxSpeed (optional, default = 75% of max speed) The maximum speed at which the SEAD task can be executed. - -- @param DCS#Altitude EngageFloorAltitude (optional, default = 1000m ) The lowest altitude in meters where to execute the engagement. - -- @param DCS#Altitude EngageCeilingAltitude (optional, default = 1500m ) The highest altitude in meters where to execute the engagement. - -- @usage - -- - -- -- SEAD Squadron execution. - -- A2GDispatcher:SetSquadronSead( "Mozdok", 900, 1200 ) - -- A2GDispatcher:SetSquadronSead( "Novo", 900, 2100 ) - -- A2GDispatcher:SetSquadronSead( "Maykop", 900, 1200 ) - -- - -- @return #AI_AIR_DISPATCHER - function AI_AIR_DISPATCHER:SetSquadronSead( SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude ) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - - DefenderSquadron.SEAD = DefenderSquadron.SEAD or {} - - local Sead = DefenderSquadron.SEAD - Sead.Name = SquadronName - Sead.EngageMinSpeed = EngageMinSpeed - Sead.EngageMaxSpeed = EngageMaxSpeed - Sead.EngageFloorAltitude = EngageFloorAltitude or 500 - Sead.EngageCeilingAltitude = EngageCeilingAltitude or 1000 - Sead.Defend = true - - self:F( { Sead = Sead } ) - end - - --- Set the squadron SEAD engage limit. - -- @param #AI_AIR_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @param #number EngageLimit The maximum amount of groups to engage with the enemy for this squadron. - -- @return #AI_AIR_DISPATCHER - -- @usage - -- - -- -- Patrol Squadron execution. - -- PatrolZoneEast = ZONE_POLYGON:New( "Patrol Zone East", GROUP:FindByName( "Patrol Zone East" ) ) - -- A2GDispatcher:SetSquadronSeadEngageLimit( "Mineralnye", 2 ) -- Engage maximum 2 groups with the enemy for SEAD defense. - -- - function AI_AIR_DISPATCHER:SetSquadronSeadEngageLimit( SquadronName, EngageLimit ) - - self:SetSquadronEngageLimit( SquadronName, EngageLimit, "SEAD" ) - - end - - - - - --- Set a Sead patrol for a Squadron. - -- The Sead patrol will start a patrol of the aircraft at a specified zone, and will engage when commanded. - -- @param #AI_AIR_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @param Core.Zone#ZONE_BASE Zone The @{Zone} object derived from @{Core.Zone#ZONE_BASE} that defines the zone wherein the Patrol will be executed. - -- @param #number FloorAltitude (optional, default = 1000m ) The minimum altitude at which the cap can be executed. - -- @param #number CeilingAltitude (optional, default = 1500m ) The maximum altitude at which the cap can be executed. - -- @param #number PatrolMinSpeed (optional, default = 50% of max speed) The minimum speed at which the cap can be executed. - -- @param #number PatrolMaxSpeed (optional, default = 75% of max speed) The maximum speed at which the cap can be executed. - -- @param #number EngageMinSpeed (optional, default = 50% of max speed) The minimum speed at which the engage can be executed. - -- @param #number EngageMaxSpeed (optional, default = 75% of max speed) The maximum speed at which the engage can be executed. - -- @param #number AltType The altitude type, which is a string "BARO" defining Barometric or "RADIO" defining radio controlled altitude. - -- @return #AI_AIR_DISPATCHER - -- @usage - -- - -- -- Sead Patrol Squadron execution. - -- PatrolZoneEast = ZONE_POLYGON:New( "Patrol Zone East", GROUP:FindByName( "Patrol Zone East" ) ) - -- A2GDispatcher:SetSquadronSeadPatrol( "Mineralnye", PatrolZoneEast, 4000, 10000, 500, 600, 800, 900 ) - -- - function AI_AIR_DISPATCHER:SetSquadronSeadPatrol( SquadronName, Zone, FloorAltitude, CeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, EngageMinSpeed, EngageMaxSpeed, AltType ) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - - DefenderSquadron.SEAD = DefenderSquadron.SEAD or {} - - local SeadPatrol = DefenderSquadron.SEAD - SeadPatrol.Name = SquadronName - SeadPatrol.Zone = Zone - SeadPatrol.PatrolFloorAltitude = FloorAltitude - SeadPatrol.PatrolCeilingAltitude = CeilingAltitude - SeadPatrol.EngageFloorAltitude = FloorAltitude - SeadPatrol.EngageCeilingAltitude = CeilingAltitude - SeadPatrol.PatrolMinSpeed = PatrolMinSpeed - SeadPatrol.PatrolMaxSpeed = PatrolMaxSpeed - SeadPatrol.EngageMinSpeed = EngageMinSpeed - SeadPatrol.EngageMaxSpeed = EngageMaxSpeed - SeadPatrol.AltType = AltType - SeadPatrol.Patrol = true - - self:SetSquadronPatrolInterval( SquadronName, self.DefenderDefault.PatrolLimit, self.DefenderDefault.PatrolMinSeconds, self.DefenderDefault.PatrolMaxSeconds, 1, "SEAD" ) - - self:F( { Sead = SeadPatrol } ) - end - - - --- - -- @param #AI_AIR_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @param #number EngageMinSpeed (optional, default = 50% of max speed) The minimum speed at which the CAS task can be executed. - -- @param #number EngageMaxSpeed (optional, default = 75% of max speed) The maximum speed at which the CAS task can be executed. - -- @param DCS#Altitude EngageFloorAltitude (optional, default = 1000m ) The lowest altitude in meters where to execute the engagement. - -- @param DCS#Altitude EngageCeilingAltitude (optional, default = 1500m ) The highest altitude in meters where to execute the engagement. - -- @usage - -- - -- -- CAS Squadron execution. - -- A2GDispatcher:SetSquadronCas( "Mozdok", 900, 1200 ) - -- A2GDispatcher:SetSquadronCas( "Novo", 900, 2100 ) - -- A2GDispatcher:SetSquadronCas( "Maykop", 900, 1200 ) - -- - -- @return #AI_AIR_DISPATCHER - function AI_AIR_DISPATCHER:SetSquadronCas( SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude ) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - - DefenderSquadron.CAS = DefenderSquadron.CAS or {} - - local Cas = DefenderSquadron.CAS - Cas.Name = SquadronName - Cas.EngageMinSpeed = EngageMinSpeed - Cas.EngageMaxSpeed = EngageMaxSpeed - Cas.EngageFloorAltitude = EngageFloorAltitude or 500 - Cas.EngageCeilingAltitude = EngageCeilingAltitude or 1000 - Cas.Defend = true - - self:F( { Cas = Cas } ) - end - - - --- Set the squadron CAS engage limit. - -- @param #AI_AIR_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @param #number EngageLimit The maximum amount of groups to engage with the enemy for this squadron. - -- @return #AI_AIR_DISPATCHER - -- @usage - -- - -- -- Patrol Squadron execution. - -- PatrolZoneEast = ZONE_POLYGON:New( "Patrol Zone East", GROUP:FindByName( "Patrol Zone East" ) ) - -- A2GDispatcher:SetSquadronCasEngageLimit( "Mineralnye", 2 ) -- Engage maximum 2 groups with the enemy for CAS defense. - -- - function AI_AIR_DISPATCHER:SetSquadronCasEngageLimit( SquadronName, EngageLimit ) - - self:SetSquadronEngageLimit( SquadronName, EngageLimit, "CAS" ) - - end - - - - - --- Set a Cas patrol for a Squadron. - -- The Cas patrol will start a patrol of the aircraft at a specified zone, and will engage when commanded. - -- @param #AI_AIR_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @param Core.Zone#ZONE_BASE Zone The @{Zone} object derived from @{Core.Zone#ZONE_BASE} that defines the zone wherein the Patrol will be executed. - -- @param #number FloorAltitude (optional, default = 1000m ) The minimum altitude at which the cap can be executed. - -- @param #number CeilingAltitude (optional, default = 1500m ) The maximum altitude at which the cap can be executed. - -- @param #number PatrolMinSpeed (optional, default = 50% of max speed) The minimum speed at which the cap can be executed. - -- @param #number PatrolMaxSpeed (optional, default = 75% of max speed) The maximum speed at which the cap can be executed. - -- @param #number EngageMinSpeed (optional, default = 50% of max speed) The minimum speed at which the engage can be executed. - -- @param #number EngageMaxSpeed (optional, default = 75% of max speed) The maximum speed at which the engage can be executed. - -- @param #number AltType The altitude type, which is a string "BARO" defining Barometric or "RADIO" defining radio controlled altitude. - -- @return #AI_AIR_DISPATCHER - -- @usage - -- - -- -- Cas Patrol Squadron execution. - -- PatrolZoneEast = ZONE_POLYGON:New( "Patrol Zone East", GROUP:FindByName( "Patrol Zone East" ) ) - -- A2GDispatcher:SetSquadronCasPatrol( "Mineralnye", PatrolZoneEast, 4000, 10000, 500, 600, 800, 900 ) - -- - function AI_AIR_DISPATCHER:SetSquadronCasPatrol( SquadronName, Zone, FloorAltitude, CeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, EngageMinSpeed, EngageMaxSpeed, AltType ) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - - DefenderSquadron.CAS = DefenderSquadron.CAS or {} - - local CasPatrol = DefenderSquadron.CAS - CasPatrol.Name = SquadronName - CasPatrol.Zone = Zone - CasPatrol.PatrolFloorAltitude = FloorAltitude - CasPatrol.PatrolCeilingAltitude = CeilingAltitude - CasPatrol.EngageFloorAltitude = FloorAltitude - CasPatrol.EngageCeilingAltitude = CeilingAltitude - CasPatrol.PatrolMinSpeed = PatrolMinSpeed - CasPatrol.PatrolMaxSpeed = PatrolMaxSpeed - CasPatrol.EngageMinSpeed = EngageMinSpeed - CasPatrol.EngageMaxSpeed = EngageMaxSpeed - CasPatrol.AltType = AltType - CasPatrol.Patrol = true - - self:SetSquadronPatrolInterval( SquadronName, self.DefenderDefault.PatrolLimit, self.DefenderDefault.PatrolMinSeconds, self.DefenderDefault.PatrolMaxSeconds, 1, "CAS" ) - - self:F( { Cas = CasPatrol } ) - end - - - --- - -- @param #AI_AIR_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @param #number EngageMinSpeed (optional, default = 50% of max speed) The minimum speed at which the BAI task can be executed. - -- @param #number EngageMaxSpeed (optional, default = 75% of max speed) The maximum speed at which the BAI task can be executed. - -- @param DCS#Altitude EngageFloorAltitude (optional, default = 1000m ) The lowest altitude in meters where to execute the engagement. - -- @param DCS#Altitude EngageCeilingAltitude (optional, default = 1500m ) The highest altitude in meters where to execute the engagement. - -- @usage - -- - -- -- BAI Squadron execution. - -- A2GDispatcher:SetSquadronBai( "Mozdok", 900, 1200 ) - -- A2GDispatcher:SetSquadronBai( "Novo", 900, 2100 ) - -- A2GDispatcher:SetSquadronBai( "Maykop", 900, 1200 ) - -- - -- @return #AI_AIR_DISPATCHER - function AI_AIR_DISPATCHER:SetSquadronBai( SquadronName, EngageMinSpeed, EngageMaxSpeed, EngageFloorAltitude, EngageCeilingAltitude ) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - - DefenderSquadron.BAI = DefenderSquadron.BAI or {} - - local Bai = DefenderSquadron.BAI - Bai.Name = SquadronName - Bai.EngageMinSpeed = EngageMinSpeed - Bai.EngageMaxSpeed = EngageMaxSpeed - Bai.EngageFloorAltitude = EngageFloorAltitude or 500 - Bai.EngageCeilingAltitude = EngageCeilingAltitude or 1000 - Bai.Defend = true - - self:F( { Bai = Bai } ) - end - - - --- Set the squadron BAI engage limit. - -- @param #AI_AIR_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @param #number EngageLimit The maximum amount of groups to engage with the enemy for this squadron. - -- @return #AI_AIR_DISPATCHER - -- @usage - -- - -- -- Patrol Squadron execution. - -- PatrolZoneEast = ZONE_POLYGON:New( "Patrol Zone East", GROUP:FindByName( "Patrol Zone East" ) ) - -- A2GDispatcher:SetSquadronBaiEngageLimit( "Mineralnye", 2 ) -- Engage maximum 2 groups with the enemy for BAI defense. - -- - function AI_AIR_DISPATCHER:SetSquadronBaiEngageLimit( SquadronName, EngageLimit ) - - self:SetSquadronEngageLimit( SquadronName, EngageLimit, "BAI" ) - - end - - - --- Set a Bai patrol for a Squadron. - -- The Bai patrol will start a patrol of the aircraft at a specified zone, and will engage when commanded. - -- @param #AI_AIR_DISPATCHER self - -- @param #string SquadronName The squadron name. - -- @param Core.Zone#ZONE_BASE Zone The @{Zone} object derived from @{Core.Zone#ZONE_BASE} that defines the zone wherein the Patrol will be executed. - -- @param #number FloorAltitude (optional, default = 1000m ) The minimum altitude at which the cap can be executed. - -- @param #number CeilingAltitude (optional, default = 1500m ) The maximum altitude at which the cap can be executed. - -- @param #number PatrolMinSpeed (optional, default = 50% of max speed) The minimum speed at which the cap can be executed. - -- @param #number PatrolMaxSpeed (optional, default = 75% of max speed) The maximum speed at which the cap can be executed. - -- @param #number EngageMinSpeed (optional, default = 50% of max speed) The minimum speed at which the engage can be executed. - -- @param #number EngageMaxSpeed (optional, default = 75% of max speed) The maximum speed at which the engage can be executed. - -- @param #number AltType The altitude type, which is a string "BARO" defining Barometric or "RADIO" defining radio controlled altitude. - -- @return #AI_AIR_DISPATCHER - -- @usage - -- - -- -- Bai Patrol Squadron execution. - -- PatrolZoneEast = ZONE_POLYGON:New( "Patrol Zone East", GROUP:FindByName( "Patrol Zone East" ) ) - -- A2GDispatcher:SetSquadronBaiPatrol( "Mineralnye", PatrolZoneEast, 4000, 10000, 500, 600, 800, 900 ) - -- - function AI_AIR_DISPATCHER:SetSquadronBaiPatrol( SquadronName, Zone, FloorAltitude, CeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, EngageMinSpeed, EngageMaxSpeed, AltType ) - - local DefenderSquadron = self:GetSquadron( SquadronName ) - - DefenderSquadron.BAI = DefenderSquadron.BAI or {} - - local BaiPatrol = DefenderSquadron.BAI - BaiPatrol.Name = SquadronName - BaiPatrol.Zone = Zone - BaiPatrol.PatrolFloorAltitude = FloorAltitude - BaiPatrol.PatrolCeilingAltitude = CeilingAltitude - BaiPatrol.EngageFloorAltitude = FloorAltitude - BaiPatrol.EngageCeilingAltitude = CeilingAltitude - BaiPatrol.PatrolMinSpeed = PatrolMinSpeed - BaiPatrol.PatrolMaxSpeed = PatrolMaxSpeed - BaiPatrol.EngageMinSpeed = EngageMinSpeed - BaiPatrol.EngageMaxSpeed = EngageMaxSpeed - BaiPatrol.AltType = AltType - BaiPatrol.Patrol = true - - self:SetSquadronPatrolInterval( SquadronName, self.DefenderDefault.PatrolLimit, self.DefenderDefault.PatrolMinSeconds, self.DefenderDefault.PatrolMaxSeconds, 1, "BAI" ) - - self:F( { Bai = BaiPatrol } ) - end - --- Defines the default amount of extra planes that will take-off as part of the defense system. -- @param #AI_AIR_DISPATCHER self -- @param #number Overhead The %-tage of Units that dispatching command will allocate to intercept in surplus of detected amount of units. -- The default overhead is 1, so equal balance. The @{#AI_AIR_DISPATCHER.SetOverhead}() method can be used to tweak the defense strength, - -- taking into account the plane types of the squadron. For example, a MIG-31 with full long-distance A2G missiles payload, may still be less effective than a F-15C with short missiles... + -- taking into account the plane types of the squadron. For example, a MIG-31 with full long-distance AIR missiles payload, may still be less effective than a F-15C with short missiles... -- So in this case, one may want to use the Overhead method to allocate more defending planes as the amount of detected attacking planes. -- The overhead must be given as a decimal value with 1 as the neutral value, which means that Overhead values: -- @@ -1409,14 +2007,14 @@ do -- AI_AIR_DISPATCHER -- -- @usage: -- - -- local A2GDispatcher = AI_AIR_DISPATCHER:New( ... ) + -- local AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) -- -- -- An overhead of 1,5 with 1 planes detected, will allocate 2 planes ( 1 * 1,5 ) = 1,5 => rounded up gives 2. -- -- An overhead of 1,5 with 2 planes detected, will allocate 3 planes ( 2 * 1,5 ) = 3 => rounded up gives 3. -- -- An overhead of 1,5 with 3 planes detected, will allocate 5 planes ( 3 * 1,5 ) = 4,5 => rounded up gives 5 planes. -- -- An overhead of 1,5 with 4 planes detected, will allocate 6 planes ( 4 * 1,5 ) = 6 => rounded up gives 6 planes. -- - -- A2GDispatcher:SetDefaultOverhead( 1.5 ) + -- AIRDispatcher:SetDefaultOverhead( 1.5 ) -- -- @return #AI_AIR_DISPATCHER function AI_AIR_DISPATCHER:SetDefaultOverhead( Overhead ) @@ -1432,7 +2030,7 @@ do -- AI_AIR_DISPATCHER -- @param #string SquadronName The name of the squadron. -- @param #number Overhead The %-tage of Units that dispatching command will allocate to intercept in surplus of detected amount of units. -- The default overhead is 1, so equal balance. The @{#AI_AIR_DISPATCHER.SetOverhead}() method can be used to tweak the defense strength, - -- taking into account the plane types of the squadron. For example, a MIG-31 with full long-distance A2G missiles payload, may still be less effective than a F-15C with short missiles... + -- taking into account the plane types of the squadron. For example, a MIG-31 with full long-distance AIR missiles payload, may still be less effective than a F-15C with short missiles... -- So in this case, one may want to use the Overhead method to allocate more defending planes as the amount of detected attacking planes. -- The overhead must be given as a decimal value with 1 as the neutral value, which means that Overhead values: -- @@ -1448,20 +2046,20 @@ do -- AI_AIR_DISPATCHER -- -- @usage: -- - -- local A2GDispatcher = AI_AIR_DISPATCHER:New( ... ) + -- local AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) -- -- -- An overhead of 1,5 with 1 planes detected, will allocate 2 planes ( 1 * 1,5 ) = 1,5 => rounded up gives 2. -- -- An overhead of 1,5 with 2 planes detected, will allocate 3 planes ( 2 * 1,5 ) = 3 => rounded up gives 3. -- -- An overhead of 1,5 with 3 planes detected, will allocate 5 planes ( 3 * 1,5 ) = 4,5 => rounded up gives 5 planes. -- -- An overhead of 1,5 with 4 planes detected, will allocate 6 planes ( 4 * 1,5 ) = 6 => rounded up gives 6 planes. -- - -- A2GDispatcher:SetSquadronOverhead( "SquadronName", 1.5 ) + -- AIRDispatcher:SetSquadronOverhead( "SquadronName", 1.5 ) -- -- @return #AI_AIR_DISPATCHER function AI_AIR_DISPATCHER:SetSquadronOverhead( SquadronName, Overhead ) local DefenderSquadron = self:GetSquadron( SquadronName ) - DefenderSquadron.Overhead = Overhead + DefenderSquadron:SetOverhead( Overhead ) return self end @@ -1472,7 +2070,7 @@ do -- AI_AIR_DISPATCHER -- @param #string SquadronName The name of the squadron. -- @return #number The %-tage of Units that dispatching command will allocate to intercept in surplus of detected amount of units. -- The default overhead is 1, so equal balance. The @{#AI_AIR_DISPATCHER.SetOverhead}() method can be used to tweak the defense strength, - -- taking into account the plane types of the squadron. For example, a MIG-31 with full long-distance A2G missiles payload, may still be less effective than a F-15C with short missiles... + -- taking into account the plane types of the squadron. For example, a MIG-31 with full long-distance AIR missiles payload, may still be less effective than a F-15C with short missiles... -- So in this case, one may want to use the Overhead method to allocate more defending planes as the amount of detected attacking planes. -- The overhead must be given as a decimal value with 1 as the neutral value, which means that Overhead values: -- @@ -1488,20 +2086,20 @@ do -- AI_AIR_DISPATCHER -- -- @usage: -- - -- local A2GDispatcher = AI_AIR_DISPATCHER:New( ... ) + -- local AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) -- -- -- An overhead of 1,5 with 1 planes detected, will allocate 2 planes ( 1 * 1,5 ) = 1,5 => rounded up gives 2. -- -- An overhead of 1,5 with 2 planes detected, will allocate 3 planes ( 2 * 1,5 ) = 3 => rounded up gives 3. -- -- An overhead of 1,5 with 3 planes detected, will allocate 5 planes ( 3 * 1,5 ) = 4,5 => rounded up gives 5 planes. -- -- An overhead of 1,5 with 4 planes detected, will allocate 6 planes ( 4 * 1,5 ) = 6 => rounded up gives 6 planes. -- - -- local SquadronOverhead = A2GDispatcher:GetSquadronOverhead( "SquadronName" ) + -- local SquadronOverhead = AIRDispatcher:GetSquadronOverhead( "SquadronName" ) -- -- @return #AI_AIR_DISPATCHER function AI_AIR_DISPATCHER:GetSquadronOverhead( SquadronName ) local DefenderSquadron = self:GetSquadron( SquadronName ) - return DefenderSquadron.Overhead or self.DefenderDefault.Overhead + return DefenderSquadron:GetOverhead() or self.DefenderDefault.Overhead end @@ -1511,10 +2109,10 @@ do -- AI_AIR_DISPATCHER -- @param #number Grouping The level of grouping that will be applied of the Patrol or GCI defenders. -- @usage: -- - -- local A2GDispatcher = AI_AIR_DISPATCHER:New( ... ) + -- local AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) -- -- -- Set a grouping by default per 2 airplanes. - -- A2GDispatcher:SetDefaultGrouping( 2 ) + -- AIRDispatcher:SetDefaultGrouping( 2 ) -- -- -- @return #AI_AIR_DISPATCHER @@ -1533,17 +2131,41 @@ do -- AI_AIR_DISPATCHER -- @param #number Grouping The level of grouping that will be applied of the Patrol or GCI defenders. -- @usage: -- - -- local A2GDispatcher = AI_AIR_DISPATCHER:New( ... ) + -- local AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) -- -- -- Set a grouping per 2 airplanes. - -- A2GDispatcher:SetSquadronGrouping( "SquadronName", 2 ) + -- AIRDispatcher:SetSquadronGrouping( "SquadronName", 2 ) -- -- -- @return #AI_AIR_DISPATCHER function AI_AIR_DISPATCHER:SetSquadronGrouping( SquadronName, Grouping ) local DefenderSquadron = self:GetSquadron( SquadronName ) - DefenderSquadron.Grouping = Grouping + DefenderSquadron:SetGrouping( Grouping ) + + return self + end + + + --- Sets the engage probability if the squadron will engage on a detected target. + -- This can be configured per squadron, to ensure that each squadron as a specific defensive probability setting. + -- @param #AI_AIR_DISPATCHER self + -- @param #string SquadronName The name of the squadron. + -- @param #number EngageProbability The probability when the squadron will consider to engage the detected target. + -- @usage: + -- + -- local AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) + -- + -- -- Set an defense probability for squadron SquadronName of 50%. + -- -- This will result that this squadron has 50% chance to engage on a detected target. + -- AIRDispatcher:SetSquadronEngageProbability( "SquadronName", 0.5 ) + -- + -- + -- @return #AI_AIR_DISPATCHER + function AI_AIR_DISPATCHER:SetSquadronEngageProbability( SquadronName, EngageProbability ) + + local DefenderSquadron = self:GetSquadron( SquadronName ) + DefenderSquadron:SetEngageProbability( EngageProbability ) return self end @@ -1554,19 +2176,19 @@ do -- AI_AIR_DISPATCHER -- @param #number Takeoff From the airbase hot, from the airbase cold, in the air, from the runway. -- @usage: -- - -- local A2GDispatcher = AI_AIR_DISPATCHER:New( ... ) + -- local AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) -- -- -- Let new flights by default take-off in the air. - -- A2GDispatcher:SetDefaultTakeoff( AI_A2G_Dispatcher.Takeoff.Air ) + -- AIRDispatcher:SetDefaultTakeoff( AI_AIR_Dispatcher.Takeoff.Air ) -- -- -- Let new flights by default take-off from the runway. - -- A2GDispatcher:SetDefaultTakeoff( AI_A2G_Dispatcher.Takeoff.Runway ) + -- AIRDispatcher:SetDefaultTakeoff( AI_AIR_Dispatcher.Takeoff.Runway ) -- -- -- Let new flights by default take-off from the airbase hot. - -- A2GDispatcher:SetDefaultTakeoff( AI_A2G_Dispatcher.Takeoff.Hot ) + -- AIRDispatcher:SetDefaultTakeoff( AI_AIR_Dispatcher.Takeoff.Hot ) -- -- -- Let new flights by default take-off from the airbase cold. - -- A2GDispatcher:SetDefaultTakeoff( AI_A2G_Dispatcher.Takeoff.Cold ) + -- AIRDispatcher:SetDefaultTakeoff( AI_AIR_Dispatcher.Takeoff.Cold ) -- -- -- @return #AI_AIR_DISPATCHER @@ -1584,19 +2206,19 @@ do -- AI_AIR_DISPATCHER -- @param #number Takeoff From the airbase hot, from the airbase cold, in the air, from the runway. -- @usage: -- - -- local A2GDispatcher = AI_AIR_DISPATCHER:New( ... ) + -- local AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) -- -- -- Let new flights take-off in the air. - -- A2GDispatcher:SetSquadronTakeoff( "SquadronName", AI_A2G_Dispatcher.Takeoff.Air ) + -- AIRDispatcher:SetSquadronTakeoff( "SquadronName", AI_AIR_Dispatcher.Takeoff.Air ) -- -- -- Let new flights take-off from the runway. - -- A2GDispatcher:SetSquadronTakeoff( "SquadronName", AI_A2G_Dispatcher.Takeoff.Runway ) + -- AIRDispatcher:SetSquadronTakeoff( "SquadronName", AI_AIR_Dispatcher.Takeoff.Runway ) -- -- -- Let new flights take-off from the airbase hot. - -- A2GDispatcher:SetSquadronTakeoff( "SquadronName", AI_A2G_Dispatcher.Takeoff.Hot ) + -- AIRDispatcher:SetSquadronTakeoff( "SquadronName", AI_AIR_Dispatcher.Takeoff.Hot ) -- -- -- Let new flights take-off from the airbase cold. - -- A2GDispatcher:SetSquadronTakeoff( "SquadronName", AI_A2G_Dispatcher.Takeoff.Cold ) + -- AIRDispatcher:SetSquadronTakeoff( "SquadronName", AI_AIR_Dispatcher.Takeoff.Cold ) -- -- -- @return #AI_AIR_DISPATCHER @@ -1604,7 +2226,7 @@ do -- AI_AIR_DISPATCHER function AI_AIR_DISPATCHER:SetSquadronTakeoff( SquadronName, Takeoff ) local DefenderSquadron = self:GetSquadron( SquadronName ) - DefenderSquadron.Takeoff = Takeoff + DefenderSquadron:SetTakeoff( Takeoff ) return self end @@ -1615,11 +2237,11 @@ do -- AI_AIR_DISPATCHER -- @return #number Takeoff From the airbase hot, from the airbase cold, in the air, from the runway. -- @usage: -- - -- local A2GDispatcher = AI_AIR_DISPATCHER:New( ... ) + -- local AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) -- -- -- Let new flights by default take-off in the air. - -- local TakeoffMethod = A2GDispatcher:GetDefaultTakeoff() - -- if TakeOffMethod == , AI_A2G_Dispatcher.Takeoff.InAir then + -- local TakeoffMethod = AIRDispatcher:GetDefaultTakeoff() + -- if TakeOffMethod == , AI_AIR_Dispatcher.Takeoff.InAir then -- ... -- end -- @@ -1634,18 +2256,18 @@ do -- AI_AIR_DISPATCHER -- @return #number Takeoff From the airbase hot, from the airbase cold, in the air, from the runway. -- @usage: -- - -- local A2GDispatcher = AI_AIR_DISPATCHER:New( ... ) + -- local AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) -- -- -- Let new flights take-off in the air. - -- local TakeoffMethod = A2GDispatcher:GetSquadronTakeoff( "SquadronName" ) - -- if TakeOffMethod == , AI_A2G_Dispatcher.Takeoff.InAir then + -- local TakeoffMethod = AIRDispatcher:GetSquadronTakeoff( "SquadronName" ) + -- if TakeOffMethod == , AI_AIR_Dispatcher.Takeoff.InAir then -- ... -- end -- function AI_AIR_DISPATCHER:GetSquadronTakeoff( SquadronName ) local DefenderSquadron = self:GetSquadron( SquadronName ) - return DefenderSquadron.Takeoff or self.DefenderDefault.Takeoff + return DefenderSquadron:GetTakeoff() or self.DefenderDefault.Takeoff end @@ -1653,10 +2275,10 @@ do -- AI_AIR_DISPATCHER -- @param #AI_AIR_DISPATCHER self -- @usage: -- - -- local A2GDispatcher = AI_AIR_DISPATCHER:New( ... ) + -- local AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) -- -- -- Let new flights by default take-off in the air. - -- A2GDispatcher:SetDefaultTakeoffInAir() + -- AIRDispatcher:SetDefaultTakeoffInAir() -- -- @return #AI_AIR_DISPATCHER -- @@ -1674,10 +2296,10 @@ do -- AI_AIR_DISPATCHER -- @param #number TakeoffAltitude (optional) The altitude in meters above the ground. If not given, the default takeoff altitude will be used. -- @usage: -- - -- local A2GDispatcher = AI_AIR_DISPATCHER:New( ... ) + -- local AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) -- -- -- Let new flights take-off in the air. - -- A2GDispatcher:SetSquadronTakeoffInAir( "SquadronName" ) + -- AIRDispatcher:SetSquadronTakeoffInAir( "SquadronName" ) -- -- @return #AI_AIR_DISPATCHER -- @@ -1697,10 +2319,10 @@ do -- AI_AIR_DISPATCHER -- @param #AI_AIR_DISPATCHER self -- @usage: -- - -- local A2GDispatcher = AI_AIR_DISPATCHER:New( ... ) + -- local AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) -- -- -- Let new flights by default take-off from the runway. - -- A2GDispatcher:SetDefaultTakeoffFromRunway() + -- AIRDispatcher:SetDefaultTakeoffFromRunway() -- -- @return #AI_AIR_DISPATCHER -- @@ -1717,10 +2339,10 @@ do -- AI_AIR_DISPATCHER -- @param #string SquadronName The name of the squadron. -- @usage: -- - -- local A2GDispatcher = AI_AIR_DISPATCHER:New( ... ) + -- local AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) -- -- -- Let new flights take-off from the runway. - -- A2GDispatcher:SetSquadronTakeoffFromRunway( "SquadronName" ) + -- AIRDispatcher:SetSquadronTakeoffFromRunway( "SquadronName" ) -- -- @return #AI_AIR_DISPATCHER -- @@ -1736,10 +2358,10 @@ do -- AI_AIR_DISPATCHER -- @param #AI_AIR_DISPATCHER self -- @usage: -- - -- local A2GDispatcher = AI_AIR_DISPATCHER:New( ... ) + -- local AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) -- -- -- Let new flights by default take-off at a hot parking spot. - -- A2GDispatcher:SetDefaultTakeoffFromParkingHot() + -- AIRDispatcher:SetDefaultTakeoffFromParkingHot() -- -- @return #AI_AIR_DISPATCHER -- @@ -1755,10 +2377,10 @@ do -- AI_AIR_DISPATCHER -- @param #string SquadronName The name of the squadron. -- @usage: -- - -- local A2GDispatcher = AI_AIR_DISPATCHER:New( ... ) + -- local AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) -- -- -- Let new flights take-off in the air. - -- A2GDispatcher:SetSquadronTakeoffFromParkingHot( "SquadronName" ) + -- AIRDispatcher:SetSquadronTakeoffFromParkingHot( "SquadronName" ) -- -- @return #AI_AIR_DISPATCHER -- @@ -1774,10 +2396,10 @@ do -- AI_AIR_DISPATCHER -- @param #AI_AIR_DISPATCHER self -- @usage: -- - -- local A2GDispatcher = AI_AIR_DISPATCHER:New( ... ) + -- local AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) -- -- -- Let new flights take-off from a cold parking spot. - -- A2GDispatcher:SetDefaultTakeoffFromParkingCold() + -- AIRDispatcher:SetDefaultTakeoffFromParkingCold() -- -- @return #AI_AIR_DISPATCHER -- @@ -1794,10 +2416,10 @@ do -- AI_AIR_DISPATCHER -- @param #string SquadronName The name of the squadron. -- @usage: -- - -- local A2GDispatcher = AI_AIR_DISPATCHER:New( ... ) + -- local AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) -- -- -- Let new flights take-off from a cold parking spot. - -- A2GDispatcher:SetSquadronTakeoffFromParkingCold( "SquadronName" ) + -- AIRDispatcher:SetSquadronTakeoffFromParkingCold( "SquadronName" ) -- -- @return #AI_AIR_DISPATCHER -- @@ -1814,10 +2436,10 @@ do -- AI_AIR_DISPATCHER -- @param #number TakeoffAltitude The altitude in meters above the ground. -- @usage: -- - -- local A2GDispatcher = AI_AIR_DISPATCHER:New( ... ) + -- local AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) -- -- -- Set the default takeoff altitude when taking off in the air. - -- A2GDispatcher:SetDefaultTakeoffInAirAltitude( 2000 ) -- This makes planes start at 2000 meters above the ground. + -- AIRDispatcher:SetDefaultTakeoffInAirAltitude( 2000 ) -- This makes planes start at 2000 meters above the ground. -- -- @return #AI_AIR_DISPATCHER -- @@ -1834,10 +2456,10 @@ do -- AI_AIR_DISPATCHER -- @param #number TakeoffAltitude The altitude in meters above the ground. -- @usage: -- - -- local A2GDispatcher = AI_AIR_DISPATCHER:New( ... ) + -- local AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) -- -- -- Set the default takeoff altitude when taking off in the air. - -- A2GDispatcher:SetSquadronTakeoffInAirAltitude( "SquadronName", 2000 ) -- This makes planes start at 2000 meters above the ground. + -- AIRDispatcher:SetSquadronTakeoffInAirAltitude( "SquadronName", 2000 ) -- This makes planes start at 2000 meters above the ground. -- -- @return #AI_AIR_DISPATCHER -- @@ -1855,16 +2477,16 @@ do -- AI_AIR_DISPATCHER -- @param #number Landing The landing method which can be NearAirbase, AtRunway, AtEngineShutdown -- @usage: -- - -- local A2GDispatcher = AI_AIR_DISPATCHER:New( ... ) + -- local AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) -- -- -- Let new flights by default despawn near the airbase when returning. - -- A2GDispatcher:SetDefaultLanding( AI_A2G_Dispatcher.Landing.NearAirbase ) + -- AIRDispatcher:SetDefaultLanding( AI_AIR_Dispatcher.Landing.NearAirbase ) -- -- -- Let new flights by default despawn after landing land at the runway. - -- A2GDispatcher:SetDefaultLanding( AI_A2G_Dispatcher.Landing.AtRunway ) + -- AIRDispatcher:SetDefaultLanding( AI_AIR_Dispatcher.Landing.AtRunway ) -- -- -- Let new flights by default despawn after landing and parking, and after engine shutdown. - -- A2GDispatcher:SetDefaultLanding( AI_A2G_Dispatcher.Landing.AtEngineShutdown ) + -- AIRDispatcher:SetDefaultLanding( AI_AIR_Dispatcher.Landing.AtEngineShutdown ) -- -- @return #AI_AIR_DISPATCHER function AI_AIR_DISPATCHER:SetDefaultLanding( Landing ) @@ -1881,22 +2503,22 @@ do -- AI_AIR_DISPATCHER -- @param #number Landing The landing method which can be NearAirbase, AtRunway, AtEngineShutdown -- @usage: -- - -- local A2GDispatcher = AI_AIR_DISPATCHER:New( ... ) + -- local AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) -- -- -- Let new flights despawn near the airbase when returning. - -- A2GDispatcher:SetSquadronLanding( "SquadronName", AI_A2G_Dispatcher.Landing.NearAirbase ) + -- AIRDispatcher:SetSquadronLanding( "SquadronName", AI_AIR_Dispatcher.Landing.NearAirbase ) -- -- -- Let new flights despawn after landing land at the runway. - -- A2GDispatcher:SetSquadronLanding( "SquadronName", AI_A2G_Dispatcher.Landing.AtRunway ) + -- AIRDispatcher:SetSquadronLanding( "SquadronName", AI_AIR_Dispatcher.Landing.AtRunway ) -- -- -- Let new flights despawn after landing and parking, and after engine shutdown. - -- A2GDispatcher:SetSquadronLanding( "SquadronName", AI_A2G_Dispatcher.Landing.AtEngineShutdown ) + -- AIRDispatcher:SetSquadronLanding( "SquadronName", AI_AIR_Dispatcher.Landing.AtEngineShutdown ) -- -- @return #AI_AIR_DISPATCHER function AI_AIR_DISPATCHER:SetSquadronLanding( SquadronName, Landing ) local DefenderSquadron = self:GetSquadron( SquadronName ) - DefenderSquadron.Landing = Landing + DefenderSquadron:SetLanding( Landing ) return self end @@ -1907,11 +2529,11 @@ do -- AI_AIR_DISPATCHER -- @return #number Landing The landing method which can be NearAirbase, AtRunway, AtEngineShutdown -- @usage: -- - -- local A2GDispatcher = AI_AIR_DISPATCHER:New( ... ) + -- local AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) -- -- -- Let new flights by default despawn near the airbase when returning. - -- local LandingMethod = A2GDispatcher:GetDefaultLanding( AI_A2G_Dispatcher.Landing.NearAirbase ) - -- if LandingMethod == AI_A2G_Dispatcher.Landing.NearAirbase then + -- local LandingMethod = AIRDispatcher:GetDefaultLanding( AI_AIR_Dispatcher.Landing.NearAirbase ) + -- if LandingMethod == AI_AIR_Dispatcher.Landing.NearAirbase then -- ... -- end -- @@ -1927,18 +2549,18 @@ do -- AI_AIR_DISPATCHER -- @return #number Landing The landing method which can be NearAirbase, AtRunway, AtEngineShutdown -- @usage: -- - -- local A2GDispatcher = AI_AIR_DISPATCHER:New( ... ) + -- local AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) -- -- -- Let new flights despawn near the airbase when returning. - -- local LandingMethod = A2GDispatcher:GetSquadronLanding( "SquadronName", AI_A2G_Dispatcher.Landing.NearAirbase ) - -- if LandingMethod == AI_A2G_Dispatcher.Landing.NearAirbase then + -- local LandingMethod = AIRDispatcher:GetSquadronLanding( "SquadronName", AI_AIR_Dispatcher.Landing.NearAirbase ) + -- if LandingMethod == AI_AIR_Dispatcher.Landing.NearAirbase then -- ... -- end -- function AI_AIR_DISPATCHER:GetSquadronLanding( SquadronName ) local DefenderSquadron = self:GetSquadron( SquadronName ) - return DefenderSquadron.Landing or self.DefenderDefault.Landing + return DefenderSquadron:GetLanding() or self.DefenderDefault.Landing end @@ -1946,10 +2568,10 @@ do -- AI_AIR_DISPATCHER -- @param #AI_AIR_DISPATCHER self -- @usage: -- - -- local A2GDispatcher = AI_AIR_DISPATCHER:New( ... ) + -- local AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) -- -- -- Let flights by default to land near the airbase and despawn. - -- A2GDispatcher:SetDefaultLandingNearAirbase() + -- AIRDispatcher:SetDefaultLandingNearAirbase() -- -- @return #AI_AIR_DISPATCHER function AI_AIR_DISPATCHER:SetDefaultLandingNearAirbase() @@ -1965,10 +2587,10 @@ do -- AI_AIR_DISPATCHER -- @param #string SquadronName The name of the squadron. -- @usage: -- - -- local A2GDispatcher = AI_AIR_DISPATCHER:New( ... ) + -- local AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) -- -- -- Let flights to land near the airbase and despawn. - -- A2GDispatcher:SetSquadronLandingNearAirbase( "SquadronName" ) + -- AIRDispatcher:SetSquadronLandingNearAirbase( "SquadronName" ) -- -- @return #AI_AIR_DISPATCHER function AI_AIR_DISPATCHER:SetSquadronLandingNearAirbase( SquadronName ) @@ -1983,10 +2605,10 @@ do -- AI_AIR_DISPATCHER -- @param #AI_AIR_DISPATCHER self -- @usage: -- - -- local A2GDispatcher = AI_AIR_DISPATCHER:New( ... ) + -- local AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) -- -- -- Let flights by default land at the runway and despawn. - -- A2GDispatcher:SetDefaultLandingAtRunway() + -- AIRDispatcher:SetDefaultLandingAtRunway() -- -- @return #AI_AIR_DISPATCHER function AI_AIR_DISPATCHER:SetDefaultLandingAtRunway() @@ -2002,10 +2624,10 @@ do -- AI_AIR_DISPATCHER -- @param #string SquadronName The name of the squadron. -- @usage: -- - -- local A2GDispatcher = AI_AIR_DISPATCHER:New( ... ) + -- local AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) -- -- -- Let flights land at the runway and despawn. - -- A2GDispatcher:SetSquadronLandingAtRunway( "SquadronName" ) + -- AIRDispatcher:SetSquadronLandingAtRunway( "SquadronName" ) -- -- @return #AI_AIR_DISPATCHER function AI_AIR_DISPATCHER:SetSquadronLandingAtRunway( SquadronName ) @@ -2020,10 +2642,10 @@ do -- AI_AIR_DISPATCHER -- @param #AI_AIR_DISPATCHER self -- @usage: -- - -- local A2GDispatcher = AI_AIR_DISPATCHER:New( ... ) + -- local AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) -- -- -- Let flights by default land and despawn at engine shutdown. - -- A2GDispatcher:SetDefaultLandingAtEngineShutdown() + -- AIRDispatcher:SetDefaultLandingAtEngineShutdown() -- -- @return #AI_AIR_DISPATCHER function AI_AIR_DISPATCHER:SetDefaultLandingAtEngineShutdown() @@ -2039,10 +2661,10 @@ do -- AI_AIR_DISPATCHER -- @param #string SquadronName The name of the squadron. -- @usage: -- - -- local A2GDispatcher = AI_AIR_DISPATCHER:New( ... ) + -- local AIRDispatcher = AI_AIR_DISPATCHER:New( ... ) -- -- -- Let flights land and despawn at engine shutdown. - -- A2GDispatcher:SetSquadronLandingAtEngineShutdown( "SquadronName" ) + -- AIRDispatcher:SetSquadronLandingAtEngineShutdown( "SquadronName" ) -- -- @return #AI_AIR_DISPATCHER function AI_AIR_DISPATCHER:SetSquadronLandingAtEngineShutdown( SquadronName ) @@ -2059,11 +2681,11 @@ do -- AI_AIR_DISPATCHER -- @return #AI_AIR_DISPATCHER -- @usage -- - -- -- Now Setup the A2G dispatcher, and initialize it using the Detection object. - -- A2GDispatcher = AI_AIR_DISPATCHER:New( Detection ) + -- -- Now Setup the AIR dispatcher, and initialize it using the Detection object. + -- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection ) -- -- -- Now Setup the default fuel treshold. - -- A2GDispatcher:SetDefaultFuelThreshold( 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank. + -- AIRDispatcher:SetDefaultFuelThreshold( 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank. -- function AI_AIR_DISPATCHER:SetDefaultFuelThreshold( FuelThreshold ) @@ -2081,16 +2703,16 @@ do -- AI_AIR_DISPATCHER -- @return #AI_AIR_DISPATCHER -- @usage -- - -- -- Now Setup the A2G dispatcher, and initialize it using the Detection object. - -- A2GDispatcher = AI_AIR_DISPATCHER:New( Detection ) + -- -- Now Setup the AIR dispatcher, and initialize it using the Detection object. + -- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection ) -- -- -- Now Setup the default fuel treshold. - -- A2GDispatcher:SetSquadronRefuelThreshold( "SquadronName", 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank. + -- AIRDispatcher:SetSquadronRefuelThreshold( "SquadronName", 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank. -- function AI_AIR_DISPATCHER:SetSquadronFuelThreshold( SquadronName, FuelThreshold ) local DefenderSquadron = self:GetSquadron( SquadronName ) - DefenderSquadron.FuelThreshold = FuelThreshold + DefenderSquadron:SetFuelThreshold( FuelThreshold ) return self end @@ -2101,14 +2723,14 @@ do -- AI_AIR_DISPATCHER -- @return #AI_AIR_DISPATCHER -- @usage -- - -- -- Now Setup the A2G dispatcher, and initialize it using the Detection object. - -- A2GDispatcher = AI_AIR_DISPATCHER:New( Detection ) + -- -- Now Setup the AIR dispatcher, and initialize it using the Detection object. + -- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection ) -- -- -- Now Setup the default fuel treshold. - -- A2GDispatcher:SetDefaultFuelThreshold( 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank. + -- AIRDispatcher:SetDefaultFuelThreshold( 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank. -- -- -- Now Setup the default tanker. - -- A2GDispatcher:SetDefaultTanker( "Tanker" ) -- The group name of the tanker is "Tanker" in the Mission Editor. + -- AIRDispatcher:SetDefaultTanker( "Tanker" ) -- The group name of the tanker is "Tanker" in the Mission Editor. function AI_AIR_DISPATCHER:SetDefaultTanker( TankerName ) self.DefenderDefault.TankerName = TankerName @@ -2124,47 +2746,67 @@ do -- AI_AIR_DISPATCHER -- @return #AI_AIR_DISPATCHER -- @usage -- - -- -- Now Setup the A2G dispatcher, and initialize it using the Detection object. - -- A2GDispatcher = AI_AIR_DISPATCHER:New( Detection ) + -- -- Now Setup the AIR dispatcher, and initialize it using the Detection object. + -- AIRDispatcher = AI_AIR_DISPATCHER:New( Detection ) -- -- -- Now Setup the squadron fuel treshold. - -- A2GDispatcher:SetSquadronRefuelThreshold( "SquadronName", 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank. + -- AIRDispatcher:SetSquadronRefuelThreshold( "SquadronName", 0.30 ) -- Go RTB when only 30% of fuel remaining in the tank. -- -- -- Now Setup the squadron tanker. - -- A2GDispatcher:SetSquadronTanker( "SquadronName", "Tanker" ) -- The group name of the tanker is "Tanker" in the Mission Editor. + -- AIRDispatcher:SetSquadronTanker( "SquadronName", "Tanker" ) -- The group name of the tanker is "Tanker" in the Mission Editor. function AI_AIR_DISPATCHER:SetSquadronTanker( SquadronName, TankerName ) local DefenderSquadron = self:GetSquadron( SquadronName ) - DefenderSquadron.TankerName = TankerName + DefenderSquadron:SetTankerName( TankerName ) return self end + --- Set the frequency of communication and the mode of communication for voice overs. + -- @param #AI_AIR_DISPATCHER self + -- @param #string SquadronName The name of the squadron. + -- @param #number RadioFrequency The frequency of communication. + -- @param #number RadioModulation The modulation of communication. + -- @param #number RadioPower The power in Watts of communication. + function AI_AIR_DISPATCHER:SetSquadronRadioFrequency( SquadronName, RadioFrequency, RadioModulation, RadioPower, Language ) + + local DefenderSquadron = self:GetSquadron( SquadronName ) + DefenderSquadron:SetRadio( RadioFrequency, RadioModulation, RadioPower, DefenderSquadron.Language ) + end + -- TODO: Need to model the resources in a squadron. + --- @param #AI_AIR_DISPATCHER self + -- @param AI.AI_Air_Squadron#AI_AIR_SQUADRON Squadron function AI_AIR_DISPATCHER:AddDefenderToSquadron( Squadron, Defender, Size ) self.Defenders = self.Defenders or {} local DefenderName = Defender:GetName() self.Defenders[ DefenderName ] = Squadron - if Squadron.ResourceCount then - Squadron.ResourceCount = Squadron.ResourceCount - Size + local SquadronResourceCount = Squadron:GetResourceCount() + if SquadronResourceCount then + Squadron:RemoveResources( Size ) end self:F( { DefenderName = DefenderName, SquadronResourceCount = Squadron.ResourceCount } ) end --- @param #AI_AIR_DISPATCHER self + -- @param AI.AI_Air_Squadron#AI_AIR_SQUADRON Squadron function AI_AIR_DISPATCHER:RemoveDefenderFromSquadron( Squadron, Defender ) self.Defenders = self.Defenders or {} local DefenderName = Defender:GetName() - if Squadron.ResourceCount then - Squadron.ResourceCount = Squadron.ResourceCount + Defender:GetSize() + local SquadronResourceCount = Squadron:GetResourceCount() + if SquadronResourceCount then + Squadron:AddResources( Defender:GetSize() ) end self.Defenders[ DefenderName ] = nil - self:F( { DefenderName = DefenderName, SquadronResourceCount = Squadron.ResourceCount } ) + self:F( { DefenderName = DefenderName, SquadronResourceCount = SquadronResourceCount } ) end + --- @param #AI_AIR_DISPATCHER self + -- @param Wrapper.Group#GROUP Defender + -- @return AI.AI_Air_Squadron#AI_AIR_SQUADRON The Squadron. function AI_AIR_DISPATCHER:GetSquadronFromDefender( Defender ) self.Defenders = self.Defenders or {} local DefenderName = Defender:GetName() @@ -2379,32 +3021,6 @@ do -- AI_AIR_DISPATCHER return nil, nil end - --- - -- @param #AI_AIR_DISPATCHER self - function AI_AIR_DISPATCHER:onafterPatrol( From, Event, To, SquadronName, DefenseTaskType ) - - local DefenderSquadron, Patrol = self:CanPatrol( SquadronName, DefenseTaskType ) - - -- Determine if there are sufficient resources to form a complete group for patrol. - if DefenderSquadron then - local DefendersNeeded - local DefendersGrouping = ( DefenderSquadron.Grouping or self.DefenderDefault.Grouping ) - if DefenderSquadron.ResourceCount == nil then - DefendersNeeded = DefendersGrouping - else - if DefenderSquadron.ResourceCount >= DefendersGrouping then - DefendersNeeded = DefendersGrouping - else - DefendersNeeded = DefenderSquadron.ResourceCount - end - end - - if Patrol then - self:ResourceQueue( true, DefenderSquadron, DefendersNeeded, Patrol, DefenseTaskType, nil, SquadronName ) - end - end - - end --- -- @param #AI_AIR_DISPATCHER self @@ -2428,699 +3044,48 @@ do -- AI_AIR_DISPATCHER end - --- + + + + --- Shows the tactical display. -- @param #AI_AIR_DISPATCHER self - function AI_AIR_DISPATCHER:ResourceTakeoff() + function AI_AIR_DISPATCHER:ShowTacticalDisplay( Detection ) - for DefenseQueueID, DefenseQueueItem in pairs( self.DefenseQueue ) do - self:F( { DefenseQueueID } ) - end - - for SquadronName, Squadron in pairs( self.DefenderSquadrons ) do - - if #self.DefenseQueue > 0 then - - self:F( { SquadronName, Squadron.Name, Squadron.TakeoffTime, Squadron.TakeoffInterval, timer.getTime() } ) - - local DefenseQueueItem = self.DefenseQueue[1] - self:F( {DefenderSquadron=DefenseQueueItem.DefenderSquadron} ) - - if DefenseQueueItem.SquadronName == SquadronName then - - if Squadron.TakeoffTime + Squadron.TakeoffInterval < timer.getTime() then - Squadron.TakeoffTime = timer.getTime() - - if DefenseQueueItem.Patrol == true then - self:ResourcePatrol( DefenseQueueItem.DefenderSquadron, DefenseQueueItem.DefendersNeeded, DefenseQueueItem.Defense, DefenseQueueItem.DefenseTaskType, DefenseQueueItem.AttackerDetection, DefenseQueueItem.SquadronName ) - else - self:ResourceEngage( DefenseQueueItem.DefenderSquadron, DefenseQueueItem.DefendersNeeded, DefenseQueueItem.Defense, DefenseQueueItem.DefenseTaskType, DefenseQueueItem.AttackerDetection, DefenseQueueItem.SquadronName ) - end - table.remove( self.DefenseQueue, 1 ) - end - end - end - - end - - end - - --- - -- @param #AI_AIR_DISPATCHER self - function AI_AIR_DISPATCHER:ResourcePatrol( DefenderSquadron, DefendersNeeded, Patrol, DefenseTaskType, AttackerDetection, SquadronName ) - - - self:F({DefenderSquadron=DefenderSquadron}) - self:F({DefendersNeeded=DefendersNeeded}) - self:F({Patrol=Patrol}) - self:F({DefenseTaskType=DefenseTaskType}) - self:F({AttackerDetection=AttackerDetection}) - self:F({SquadronName=SquadronName}) - - local DefenderGroup, DefenderGrouping = self:ResourceActivate( DefenderSquadron, DefendersNeeded ) - - if DefenderGroup then - - local AI_A2G_PATROL = { SEAD = AI_A2G_SEAD, BAI = AI_A2G_BAI, CAS = AI_A2G_CAS } - - local Fsm = AI_A2G_PATROL[DefenseTaskType]:New( DefenderGroup, Patrol.EngageMinSpeed, Patrol.EngageMaxSpeed, Patrol.EngageFloorAltitude, Patrol.EngageCeilingAltitude, Patrol.Zone, Patrol.PatrolFloorAltitude, Patrol.PatrolCeilingAltitude, Patrol.PatrolMinSpeed, Patrol.PatrolMaxSpeed, Patrol.AltType ) - Fsm:SetDispatcher( self ) - Fsm:SetHomeAirbase( DefenderSquadron.Airbase ) - Fsm:SetFuelThreshold( DefenderSquadron.FuelThreshold or self.DefenderDefault.FuelThreshold, 60 ) - Fsm:SetDamageThreshold( self.DefenderDefault.DamageThreshold ) - Fsm:SetDisengageRadius( self.DisengageRadius ) - Fsm:SetTanker( DefenderSquadron.TankerName or self.DefenderDefault.TankerName ) - Fsm:Start() - - self:SetDefenderTask( SquadronName, DefenderGroup, DefenseTaskType, Fsm, nil, DefenderGrouping ) - - function Fsm:onafterTakeoff( Defender, From, Event, To ) - self:F({"Defender Birth", Defender:GetName()}) - --self:GetParent(self).onafterBirth( self, Defender, From, Event, To ) - - local DefenderName = Defender:GetName() - local Dispatcher = Fsm:GetDispatcher() -- #AI_AIR_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) - - if Squadron then - Dispatcher:MessageToPlayers( Squadron, "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " airborne." ) - Fsm:Patrol() -- Engage on the TargetSetUnit - end - end - - function Fsm:onafterRTB( Defender, From, Event, To ) - self:F({"Defender RTB", Defender:GetName()}) - self:GetParent(self).onafterRTB( self, Defender, From, Event, To ) - - local DefenderName = Defender:GetName() - local Dispatcher = self:GetDispatcher() -- #AI_AIR_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) - Dispatcher:MessageToPlayers( Squadron, "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " returning." ) - - Dispatcher:ClearDefenderTaskTarget( Defender ) - end - - --- @param #AI_AIR_DISPATCHER self - function Fsm:onafterLostControl( Defender, From, Event, To ) - self:F({"Defender LostControl", Defender:GetName()}) - self:GetParent(self).onafterHome( self, Defender, From, Event, To ) - - local DefenderName = Defender:GetName() - local Dispatcher = Fsm:GetDispatcher() -- #AI_AIR_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) - Dispatcher:MessageToPlayers( Squadron, "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " lost control." ) - if Defender:IsAboveRunway() then - Dispatcher:RemoveDefenderFromSquadron( Squadron, Defender ) - Defender:Destroy() - end - end - - --- @param #AI_AIR_DISPATCHER self - function Fsm:onafterHome( Defender, From, Event, To, Action ) - self:F({"Defender Home", Defender:GetName()}) - self:GetParent(self).onafterHome( self, Defender, From, Event, To ) - - local DefenderName = Defender:GetName() - local Dispatcher = self:GetDispatcher() -- #AI_AIR_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) - Dispatcher:MessageToPlayers( Squadron, "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " landing." ) - - if Action and Action == "Destroy" then - Dispatcher:RemoveDefenderFromSquadron( Squadron, Defender ) - Defender:Destroy() - end - - if Dispatcher:GetSquadronLanding( Squadron.Name ) == AI_AIR_DISPATCHER.Landing.NearAirbase then - Dispatcher:RemoveDefenderFromSquadron( Squadron, Defender ) - Defender:Destroy() - Dispatcher:ResourcePark( Squadron, Defender ) - end - end - end - - end - - - --- - -- @param #AI_AIR_DISPATCHER self - function AI_AIR_DISPATCHER:ResourceEngage( DefenderSquadron, DefendersNeeded, Defense, DefenseTaskType, AttackerDetection, SquadronName ) - - self:F({DefenderSquadron=DefenderSquadron}) - self:F({DefendersNeeded=DefendersNeeded}) - self:F({Defense=Defense}) - self:F({DefenseTaskType=DefenseTaskType}) - self:F({AttackerDetection=AttackerDetection}) - self:F({SquadronName=SquadronName}) - - local DefenderGroup, DefenderGrouping = self:ResourceActivate( DefenderSquadron, DefendersNeeded ) - - if DefenderGroup then - - local AI_A2G = { SEAD = AI_A2G_SEAD, BAI = AI_A2G_BAI, CAS = AI_A2G_CAS } - - local Fsm = AI_A2G[DefenseTaskType]:New( DefenderGroup, Defense.EngageMinSpeed, Defense.EngageMaxSpeed, Defense.EngageFloorAltitude, Defense.EngageCeilingAltitude ) -- AI.AI_AIR_ENGAGE - Fsm:SetDispatcher( self ) - Fsm:SetHomeAirbase( DefenderSquadron.Airbase ) - Fsm:SetFuelThreshold( DefenderSquadron.FuelThreshold or self.DefenderDefault.FuelThreshold, 60 ) - Fsm:SetDamageThreshold( self.DefenderDefault.DamageThreshold ) - Fsm:SetDisengageRadius( self.DisengageRadius ) - Fsm:Start() - - self:SetDefenderTask( SquadronName, DefenderGroup, DefenseTaskType, Fsm, AttackerDetection, DefenderGrouping ) - - function Fsm:onafterTakeoff( Defender, From, Event, To ) - self:F({"Defender Birth", Defender:GetName()}) - --self:GetParent(self).onafterBirth( self, Defender, From, Event, To ) - - local DefenderName = Defender:GetName() - local Dispatcher = Fsm:GetDispatcher() -- #AI_AIR_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) - local DefenderTarget = Dispatcher:GetDefenderTaskTarget( Defender ) - - self:F( { DefenderTarget = DefenderTarget } ) - - if DefenderTarget then - Dispatcher:MessageToPlayers( Squadron, "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " airborne." ) - Fsm:EngageRoute( DefenderTarget.Set ) -- Engage on the TargetSetUnit - end - end - - function Fsm:OnAfterEngageRoute( Defender, From, Event, To, AttackSetUnit ) - self:F({"Engage Route", Defender:GetName()}) - --self:GetParent(self).onafterBirth( self, Defender, From, Event, To ) - - local DefenderName = Defender:GetName() - local Dispatcher = Fsm:GetDispatcher() -- #AI_AIR_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) - local FirstUnit = AttackSetUnit:GetFirst() - local Coordinate = FirstUnit:GetCoordinate() -- Core.Point#COORDINATE - - Dispatcher:MessageToPlayers( Squadron, "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " on route, bearing " .. Coordinate:ToString( Defender ) ) - end - - function Fsm:OnAfterEngage( Defender, From, Event, To, AttackSetUnit ) - self:F({"Engage Route", Defender:GetName()}) - --self:GetParent(self).onafterBirth( self, Defender, From, Event, To ) - - local DefenderName = Defender:GetName() - local Dispatcher = Fsm:GetDispatcher() -- #AI_AIR_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) - local FirstUnit = AttackSetUnit:GetFirst() - local Coordinate = FirstUnit:GetCoordinate() - - Dispatcher:MessageToPlayers( Squadron, "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " engaging target, bearing " .. Coordinate:ToString( Defender ) ) - end - - function Fsm:onafterRTB( Defender, From, Event, To ) - self:F({"Defender RTB", Defender:GetName()}) - - local DefenderName = Defender:GetName() - local Dispatcher = self:GetDispatcher() -- #AI_AIR_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) - Dispatcher:MessageToPlayers( Squadron, "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " RTB." ) - - self:GetParent(self).onafterRTB( self, Defender, From, Event, To ) - - Dispatcher:ClearDefenderTaskTarget( Defender ) - end - - --- @param #AI_AIR_DISPATCHER self - function Fsm:onafterLostControl( Defender, From, Event, To ) - self:F({"Defender LostControl", Defender:GetName()}) - self:GetParent(self).onafterHome( self, Defender, From, Event, To ) - - local DefenderName = Defender:GetName() - local Dispatcher = Fsm:GetDispatcher() -- #AI_AIR_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) - Dispatcher:MessageToPlayers( Squadron, "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " lost control." ) - - if Defender:IsAboveRunway() then - Dispatcher:RemoveDefenderFromSquadron( Squadron, Defender ) - Defender:Destroy() - end - end - - --- @param #AI_AIR_DISPATCHER self - function Fsm:onafterHome( Defender, From, Event, To, Action ) - self:F({"Defender Home", Defender:GetName()}) - self:GetParent(self).onafterHome( self, Defender, From, Event, To ) - - local DefenderName = Defender:GetName() - local Dispatcher = self:GetDispatcher() -- #AI_AIR_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) - Dispatcher:MessageToPlayers( Squadron, "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " landing." ) - - if Action and Action == "Destroy" then - Dispatcher:RemoveDefenderFromSquadron( Squadron, Defender ) - Defender:Destroy() - end - - if Dispatcher:GetSquadronLanding( Squadron.Name ) == AI_AIR_DISPATCHER.Landing.NearAirbase then - Dispatcher:RemoveDefenderFromSquadron( Squadron, Defender ) - Defender:Destroy() - Dispatcher:ResourcePark( Squadron, Defender ) - end - end - end - end - - --- - -- @param #AI_AIR_DISPATCHER self - function AI_AIR_DISPATCHER:onafterEngage( From, Event, To, AttackerDetection, Defenders ) - - if Defenders then - - for DefenderID, Defender in pairs( Defenders or {} ) do - - local Fsm = self:GetDefenderTaskFsm( Defender ) - Fsm:Engage( AttackerDetection.Set ) -- Engage on the TargetSetUnit - - self:SetDefenderTaskTarget( Defender, AttackerDetection ) - - end - end - end - - --- - -- @param #AI_AIR_DISPATCHER self - function AI_AIR_DISPATCHER:HasDefenseLine( DefenseCoordinate, DetectedItem ) - - local AttackCoordinate = self.Detection:GetDetectedItemCoordinate( DetectedItem ) - local EvaluateDistance = AttackCoordinate:Get2DDistance( DefenseCoordinate ) - - -- Now check if this coordinate is not in a danger zone, meaning, that the attack line is not crossing other coordinates. - -- (y1 – y2)x + (x2 – x1)y + (x1y2 – x2y1) = 0 - - local c1 = DefenseCoordinate - local c2 = AttackCoordinate - - local a = c1.z - c2.z -- Calculate a - local b = c2.x - c1.x -- Calculate b - local c = c1.x * c2.z - c2.x * c1.z -- calculate c - - local ok = true - - -- Now we check if each coordinate radius of about 30km of each attack is crossing a defense line. If yes, then this is not a good attack! - for AttackItemID, CheckAttackItem in pairs( self.Detection:GetDetectedItems() ) do - - -- Only compare other detected coordinates. - if AttackItemID ~= DetectedItem.ID then - - local CheckAttackCoordinate = self.Detection:GetDetectedItemCoordinate( CheckAttackItem ) - - local x = CheckAttackCoordinate.x - local y = CheckAttackCoordinate.z - local r = 5000 - - -- now we check if the coordinate is intersecting with the defense line. - - local IntersectDistance = ( math.abs( a * x + b * y + c ) ) / math.sqrt( a * a + b * b ) - self:F( { IntersectDistance = IntersectDistance, x = x, y = y } ) - - local IntersectAttackDistance = CheckAttackCoordinate:Get2DDistance( DefenseCoordinate ) - - self:F( { IntersectAttackDistance=IntersectAttackDistance, EvaluateDistance=EvaluateDistance } ) - - -- If the distance of the attack coordinate is larger than the test radius; then the line intersects, and this is not a good coordinate. - if IntersectDistance < r and IntersectAttackDistance < EvaluateDistance then - ok = false - break - end - end - end - - return ok - end - - --- - -- @param #AI_AIR_DISPATCHER self - function AI_AIR_DISPATCHER:onafterDefend( From, Event, To, DetectedItem, DefendersTotal, DefendersEngaged, DefendersMissing, DefenderFriendlies, DefenseTaskType ) - - self:F( { From, Event, To, DetectedItem.Index, DefendersEngaged = DefendersEngaged, DefendersMissing = DefendersMissing, DefenderFriendlies = DefenderFriendlies } ) - - DetectedItem.Type = DefenseTaskType -- This is set to report the task type in the status panel. - - local AttackerSet = DetectedItem.Set - local AttackerUnit = AttackerSet:GetFirst() - - if AttackerUnit and AttackerUnit:IsAlive() then - local AttackerCount = AttackerSet:Count() - local DefenderCount = 0 - - for DefenderID, DefenderGroup in pairs( DefenderFriendlies or {} ) do - - -- Here we check if the defenders have a defense line to the attackers. - -- If the attackers are behind enemy lines or too close to an other defense line; then don´t engage. - local DefenseCoordinate = DefenderGroup:GetCoordinate() - local HasDefenseLine = self:HasDefenseLine( DefenseCoordinate, DetectedItem ) - - if HasDefenseLine == true then - local SquadronName = self:GetDefenderTask( DefenderGroup ).SquadronName - local SquadronOverhead = self:GetSquadronOverhead( SquadronName ) - - local Fsm = self:GetDefenderTaskFsm( DefenderGroup ) - Fsm:EngageRoute( AttackerSet ) -- Engage on the TargetSetUnit - - self:SetDefenderTaskTarget( DefenderGroup, DetectedItem ) - - local DefenderGroupSize = DefenderGroup:GetSize() - DefendersMissing = DefendersMissing - DefenderGroupSize / SquadronOverhead - DefendersTotal = DefendersTotal + DefenderGroupSize / SquadronOverhead - end - - if DefendersMissing <= 0 then - break - end - end - - self:F( { DefenderCount = DefenderCount, DefendersMissing = DefendersMissing } ) - DefenderCount = DefendersMissing - - local ClosestDistance = 0 - local ClosestDefenderSquadronName = nil - - local BreakLoop = false - - while( DefenderCount > 0 and not BreakLoop ) do - - self:F( { DefenderSquadrons = self.DefenderSquadrons } ) - - for SquadronName, DefenderSquadron in pairs( self.DefenderSquadrons or {} ) do - - if DefenderSquadron[DefenseTaskType] then - - local AirbaseCoordinate = DefenderSquadron.Airbase:GetCoordinate() -- Core.Point#COORDINATE - local AttackerCoord = AttackerUnit:GetCoordinate() - local InterceptCoord = DetectedItem.InterceptCoord - self:F( { InterceptCoord = InterceptCoord } ) - if InterceptCoord then - local InterceptDistance = AirbaseCoordinate:Get2DDistance( InterceptCoord ) - local AirbaseDistance = AirbaseCoordinate:Get2DDistance( AttackerCoord ) - self:F( { InterceptDistance = InterceptDistance, AirbaseDistance = AirbaseDistance, InterceptCoord = InterceptCoord } ) - - if ClosestDistance == 0 or InterceptDistance < ClosestDistance then - - -- Only intercept if the distance to target is smaller or equal to the GciRadius limit. - if AirbaseDistance <= self.DefenseRadius then - - -- Check if there is a defense line... - local HasDefenseLine = self:HasDefenseLine( AirbaseCoordinate, DetectedItem ) - if HasDefenseLine == true then - ClosestDistance = InterceptDistance - ClosestDefenderSquadronName = SquadronName - end - end - end - end - end - end - - if ClosestDefenderSquadronName then - - local DefenderSquadron, Defense = self:CanDefend( ClosestDefenderSquadronName, DefenseTaskType ) - - if Defense then - - local DefenderOverhead = DefenderSquadron.Overhead or self.DefenderDefault.Overhead - local DefenderGrouping = DefenderSquadron.Grouping or self.DefenderDefault.Grouping - local DefendersNeeded = math.ceil( DefenderCount * DefenderOverhead ) - - self:F( { Overhead = DefenderOverhead, SquadronOverhead = DefenderSquadron.Overhead , DefaultOverhead = self.DefenderDefault.Overhead } ) - self:F( { Grouping = DefenderGrouping, SquadronGrouping = DefenderSquadron.Grouping, DefaultGrouping = self.DefenderDefault.Grouping } ) - self:F( { DefendersCount = DefenderCount, DefendersNeeded = DefendersNeeded } ) - - -- Validate that the maximum limit of Defenders has been reached. - -- If yes, then cancel the engaging of more defenders. - local DefendersLimit = DefenderSquadron.EngageLimit or self.DefenderDefault.EngageLimit - if DefendersLimit then - if DefendersTotal >= DefendersLimit then - DefendersNeeded = 0 - BreakLoop = true - else - -- If the total of amount of defenders + the defenders needed, is larger than the limit of defenders, - -- then the defenders needed is the difference between defenders total - defenders limit. - if DefendersTotal + DefendersNeeded > DefendersLimit then - DefendersNeeded = DefendersLimit - DefendersTotal - end - end - end - - -- DefenderSquadron.ResourceCount can have the value nil, which expresses unlimited resources. - -- DefendersNeeded cannot exceed DefenderSquadron.ResourceCount! - if DefenderSquadron.ResourceCount and DefendersNeeded > DefenderSquadron.ResourceCount then - DefendersNeeded = DefenderSquadron.ResourceCount - BreakLoop = true - end - - while ( DefendersNeeded > 0 ) do - self:ResourceQueue( false, DefenderSquadron, DefendersNeeded, Defense, DefenseTaskType, DetectedItem, ClosestDefenderSquadronName ) - DefendersNeeded = DefendersNeeded - DefenderGrouping - DefenderCount = DefenderCount - DefenderGrouping / DefenderOverhead - end -- while ( DefendersNeeded > 0 ) do - else - -- No more resources, try something else. - -- Subject for a later enhancement to try to depart from another squadron and disable this one. - BreakLoop = true - break - end - else - -- There isn't any closest airbase anymore, break the loop. - break - end - end -- if DefenderSquadron then - end -- if AttackerUnit - end - - - - --- Creates an SEAD task when the targets have radars. - -- @param #AI_AIR_DISPATCHER self - -- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem The detected item. - -- @return Core.Set#SET_UNIT The set of units of the targets to be engaged. - -- @return #nil If there are no targets to be set. - function AI_AIR_DISPATCHER:Evaluate_SEAD( DetectedItem ) - self:F( { DetectedItem.ItemID } ) - - local AttackerSet = DetectedItem.Set -- Core.Set#SET_UNIT - local AttackerCount = AttackerSet:HasSEAD() -- Is the AttackerSet a SEAD group, then the amount of radar emitters will be returned; that need to be attacked. - - if ( AttackerCount > 0 ) then - - -- First, count the active defenders, engaging the DetectedItem. - local DefendersTotal, DefendersEngaged, DefendersMissing = self:CountDefendersEngaged( DetectedItem, AttackerCount ) - - self:F( { AttackerCount = AttackerCount, DefendersTotal = DefendersTotal, DefendersEngaged = DefendersEngaged, DefendersMissing = DefendersMissing } ) - - local DefenderGroups = self:CountDefenders( DetectedItem, DefendersEngaged, "SEAD" ) - - - - if DetectedItem.IsDetected == true then - - return DefendersTotal, DefendersEngaged, DefendersMissing, DefenderGroups - end - end - - return nil, nil, nil - end - - - --- Creates an CAS task. - -- @param #AI_AIR_DISPATCHER self - -- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem The detected item. - -- @return Core.Set#SET_UNIT The set of units of the targets to be engaged. - -- @return #nil If there are no targets to be set. - function AI_AIR_DISPATCHER:Evaluate_CAS( DetectedItem ) - self:F( { DetectedItem.ItemID } ) - - local AttackerSet = DetectedItem.Set -- Core.Set#SET_UNIT - local AttackerCount = AttackerSet:Count() - local AttackerRadarCount = AttackerSet:HasSEAD() - local IsFriendliesNearBy = self.Detection:IsFriendliesNearBy( DetectedItem, Unit.Category.GROUND_UNIT ) - local IsCas = ( AttackerRadarCount == 0 ) and ( IsFriendliesNearBy == true ) -- Is the AttackerSet a CAS group? - - if IsCas == true then - - -- First, count the active defenders, engaging the DetectedItem. - local DefendersTotal, DefendersEngaged, DefendersMissing = self:CountDefendersEngaged( DetectedItem, AttackerCount ) - - self:F( { AttackerCount = AttackerCount, DefendersTotal = DefendersTotal, DefendersEngaged = DefendersEngaged, DefendersMissing = DefendersMissing } ) - - local DefenderGroups = self:CountDefenders( DetectedItem, DefendersEngaged, "CAS" ) - - if DetectedItem.IsDetected == true then - - return DefendersTotal, DefendersEngaged, DefendersMissing, DefenderGroups - end - end - - return nil, nil, nil - end - - - --- Evaluates an BAI task. - -- @param #AI_AIR_DISPATCHER self - -- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem The detected item. - -- @return Core.Set#SET_UNIT The set of units of the targets to be engaged. - -- @return #nil If there are no targets to be set. - function AI_AIR_DISPATCHER:Evaluate_BAI( DetectedItem ) - self:F( { DetectedItem.ItemID } ) - - local AttackerSet = DetectedItem.Set -- Core.Set#SET_UNIT - local AttackerCount = AttackerSet:Count() - local AttackerRadarCount = AttackerSet:HasSEAD() - local IsFriendliesNearBy = self.Detection:IsFriendliesNearBy( DetectedItem, Unit.Category.GROUND_UNIT ) - local IsBai = ( AttackerRadarCount == 0 ) and ( IsFriendliesNearBy == false ) -- Is the AttackerSet a BAI group? - - if IsBai == true then - - -- First, count the active defenders, engaging the DetectedItem. - local DefendersTotal, DefendersEngaged, DefendersMissing = self:CountDefendersEngaged( DetectedItem, AttackerCount ) - - self:F( { AttackerCount = AttackerCount, DefendersTotal = DefendersTotal, DefendersEngaged = DefendersEngaged, DefendersMissing = DefendersMissing } ) - - local DefenderGroups = self:CountDefenders( DetectedItem, DefendersEngaged, "BAI" ) - - if DetectedItem.IsDetected == true then - - return DefendersTotal, DefendersEngaged, DefendersMissing, DefenderGroups - end - end - - return nil, nil, nil - end - - - - - --- Assigns A2G AI Tasks in relation to the detected items. - -- @param #AI_AIR_DISPATCHER self - -- @param Functional.Detection#DETECTION_BASE Detection The detection created by the @{Functional.Detection#DETECTION_BASE} derived object. - -- @return #boolean Return true if you want the task assigning to continue... false will cancel the loop. - function AI_AIR_DISPATCHER:ProcessDetected( Detection ) - local AreaMsg = {} local TaskMsg = {} local ChangeMsg = {} local TaskReport = REPORT:New() - - for DefenderGroup, DefenderTask in pairs( self:GetDefenderTasks() ) do - local DefenderGroup = DefenderGroup -- Wrapper.Group#GROUP - local DefenderTaskFsm = self:GetDefenderTaskFsm( DefenderGroup ) - --if DefenderTaskFsm:Is( "LostControl" ) then - -- self:ClearDefenderTask( DefenderGroup ) - --end - if not DefenderGroup:IsAlive() then - self:F( { Defender = DefenderGroup:GetName(), DefenderState = DefenderTaskFsm:GetState() } ) - if not DefenderTaskFsm:Is( "Started" ) then - self:ClearDefenderTask( DefenderGroup ) - end - else - if DefenderTask.Target then - local AttackerItem = Detection:GetDetectedItemByIndex( DefenderTask.Target.Index ) - if not AttackerItem then - self:F( { "Removing obsolete Target:", DefenderTask.Target.Index } ) - self:ClearDefenderTaskTarget( DefenderGroup ) - else - if DefenderTask.Target.Set then - local TargetCount = DefenderTask.Target.Set:Count() - if TargetCount == 0 then - self:F( { "All Targets destroyed in Target, removing:", DefenderTask.Target.Index } ) - self:ClearDefenderTask( DefenderGroup ) - end - end - end - end - end - end + local DefenseTotal = 0 local Report = REPORT:New( "\nTactical Overview" ) local DefenderGroupCount = 0 - local DefendersTotal = 0 -- Now that all obsolete tasks are removed, loop through the detected targets. - for DetectedItemID, DetectedItem in pairs( Detection:GetDetectedItems() ) do + --for DetectedItemID, DetectedItem in pairs( Detection:GetDetectedItems() ) do + for DetectedItemID, DetectedItem in UTILS.spairs( Detection:GetDetectedItems(), function( t, a, b ) return self:Order(t[a]) < self:Order(t[b]) end ) do - local DetectedItem = DetectedItem -- Functional.Detection#DETECTION_BASE.DetectedItem - local DetectedSet = DetectedItem.Set -- Core.Set#SET_UNIT - local DetectedCount = DetectedSet:Count() - local DetectedZone = DetectedItem.Zone - - self:F( { "Target ID", DetectedItem.ItemID } ) - DetectedSet:Flush( self ) - - local DetectedID = DetectedItem.ID - local DetectionIndex = DetectedItem.Index - local DetectedItemChanged = DetectedItem.Changed - - local AttackCoordinate = self.Detection:GetDetectedItemCoordinate( DetectedItem ) - - -- Calculate if for this DetectedItem if a defense needs to be initiated. - -- This calculation is based on the distance between the defense point and the attackers, and the defensiveness parameter. - -- The attackers closest to the defense coordinates will be handled first, or course! - - local EngageCoordinate = nil - - for DefenseCoordinateName, DefenseCoordinate in pairs( self.DefenseCoordinates ) do - local DefenseCoordinate = DefenseCoordinate -- Core.Point#COORDINATE - - local EvaluateDistance = AttackCoordinate:Get2DDistance( DefenseCoordinate ) - - if EvaluateDistance <= self.DefenseRadius then - - local DistanceProbability = ( self.DefenseRadius / EvaluateDistance * self.DefenseReactivity ) - local DefenseProbability = math.random() - - self:F( { DistanceProbability = DistanceProbability, DefenseProbability = DefenseProbability } ) - - if DefenseProbability <= DistanceProbability / ( 300 / 30 ) then - EngageCoordinate = DefenseCoordinate - break - end - end - end - - if EngageCoordinate then - do - local DefendersTotal, DefendersEngaged, DefendersMissing, Friendlies = self:Evaluate_SEAD( DetectedItem ) -- Returns a SET_UNIT with the SEAD targets to be engaged... - if DefendersMissing and DefendersMissing > 0 then - self:F( { DefendersTotal = DefendersTotal, DefendersEngaged = DefendersEngaged, DefendersMissing = DefendersMissing } ) - self:Defend( DetectedItem, DefendersTotal, DefendersEngaged, DefendersMissing, Friendlies, "SEAD", EngageCoordinate ) - end - end + if not self.Detection:IsDetectedItemLocked( DetectedItem ) == true then + local DetectedItem = DetectedItem -- Functional.Detection#DETECTION_BASE.DetectedItem + local DetectedSet = DetectedItem.Set -- Core.Set#SET_UNIT + local DetectedCount = DetectedSet:Count() + local DetectedZone = DetectedItem.Zone - do - local DefendersTotal, DefendersEngaged, DefendersMissing, Friendlies = self:Evaluate_CAS( DetectedItem ) -- Returns a SET_UNIT with the CAS targets to be engaged... - if DefendersMissing and DefendersMissing > 0 then - self:F( { DefendersTotal = DefendersTotal, DefendersEngaged = DefendersEngaged, DefendersMissing = DefendersMissing } ) - self:Defend( DetectedItem, DefendersTotal, DefendersEngaged, DefendersMissing, Friendlies, "CAS", EngageCoordinate ) - end - end + self:F( { "Target ID", DetectedItem.ItemID } ) + + self:F( { DefenseLimit = self.DefenseLimit, DefenseTotal = DefenseTotal } ) + DetectedSet:Flush( self ) + + local DetectedID = DetectedItem.ID + local DetectionIndex = DetectedItem.Index + local DetectedItemChanged = DetectedItem.Changed - do - local DefendersTotal, DefendersEngaged, DefendersMissing, Friendlies = self:Evaluate_BAI( DetectedItem ) -- Returns a SET_UNIT with the CAS targets to be engaged... - if DefendersMissing and DefendersMissing > 0 then - self:F( { DefendersTotal = DefendersTotal, DefendersEngaged = DefendersEngaged, DefendersMissing = DefendersMissing } ) - self:Defend( DetectedItem, DefendersTotal, DefendersEngaged, DefendersMissing, Friendlies, "BAI", EngageCoordinate ) - end - end - end - --- do --- local DefendersMissing, Friendlies = self:Evaluate_CAS( DetectedItem ) --- if DefendersMissing and DefendersMissing > 0 then --- self:F( { DefendersMissing = DefendersMissing } ) --- self:CAS( DetectedItem, DefendersMissing, Friendlies ) --- end --- end - - if self.TacticalDisplay then -- Show tactical situation - local ThreatLevel = DetectedItem.Set:CalculateThreatLevelA2G() - Report:Add( string.format( " - %1s%s ( %4s ): ( #%d - %4s ) %s" , ( DetectedItem.IsDetected == true ) and "!" or " ", DetectedItem.ItemID, DetectedItem.Index, DetectedItem.Set:Count(), DetectedItem.Type or " --- ", string.rep( "■", ThreatLevel ) ) ) + local ThreatLevel = DetectedItem.Set:CalculateThreatLevelAIR() + Report:Add( string.format( " - %1s%s ( %04s ): ( #%02d - %-4s ) %s" , ( DetectedItem.IsDetected == true ) and "!" or " ", DetectedItem.ItemID, DetectedItem.Index, DetectedItem.Set:Count(), DetectedItem.Type or " --- ", string.rep( "■", ThreatLevel ) ) ) for Defender, DefenderTask in pairs( self:GetDefenderTasks() ) do local Defender = Defender -- Wrapper.Group#GROUP if DefenderTask.Target and DefenderTask.Target.Index == DetectedItem.Index then @@ -3142,52 +3107,50 @@ do -- AI_AIR_DISPATCHER end end - if self.TacticalDisplay then - Report:Add( "\n - No Targets:") - local TaskCount = 0 - for Defender, DefenderTask in pairs( self:GetDefenderTasks() ) do - TaskCount = TaskCount + 1 - local Defender = Defender -- Wrapper.Group#GROUP - if not DefenderTask.Target then - if Defender:IsAlive() then - local DefenderHasTask = Defender:HasTask() - local Fuel = Defender:GetFuelMin() * 100 - local Damage = Defender:GetLife() / Defender:GetLife0() * 100 - DefenderGroupCount = DefenderGroupCount + 1 - Report:Add( string.format( " - %s ( %s - %s ): ( #%d ) F: %3d, D:%3d - %s", - Defender:GetName(), - DefenderTask.Type, - DefenderTask.Fsm:GetState(), - Defender:GetSize(), - Fuel, - Damage, - Defender:HasTask() == true and "Executing" or "Idle" ) ) - end + Report:Add( "\n - No Targets:") + local TaskCount = 0 + for Defender, DefenderTask in pairs( self:GetDefenderTasks() ) do + TaskCount = TaskCount + 1 + local Defender = Defender -- Wrapper.Group#GROUP + if not DefenderTask.Target then + if Defender:IsAlive() then + local DefenderHasTask = Defender:HasTask() + local Fuel = Defender:GetFuelMin() * 100 + local Damage = Defender:GetLife() / Defender:GetLife0() * 100 + DefenderGroupCount = DefenderGroupCount + 1 + Report:Add( string.format( " - %s ( %s - %s ): ( #%d ) F: %3d, D:%3d - %s", + Defender:GetName(), + DefenderTask.Type, + DefenderTask.Fsm:GetState(), + Defender:GetSize(), + Fuel, + Damage, + Defender:HasTask() == true and "Executing" or "Idle" ) ) end end - Report:Add( string.format( "\n - %d Tasks - %d Defender Groups", TaskCount, DefenderGroupCount ) ) + end + Report:Add( string.format( "\n - %d Tasks - %d Defender Groups", TaskCount, DefenderGroupCount ) ) - Report:Add( string.format( "\n - %d Queued Aircraft Launches", #self.DefenseQueue ) ) - for DefenseQueueID, DefenseQueueItem in pairs( self.DefenseQueue ) do - local DefenseQueueItem = DefenseQueueItem -- #AI_AIR_DISPATCHER.DefenseQueueItem - Report:Add( string.format( " - %s - %s", DefenseQueueItem.SquadronName, DefenseQueueItem.DefenderSquadron.TakeoffTime, DefenseQueueItem.DefenderSquadron.TakeoffInterval) ) - - end + Report:Add( string.format( "\n - %d Queued Aircraft Launches", #self.DefenseQueue ) ) + for DefenseQueueID, DefenseQueueItem in pairs( self.DefenseQueue ) do + local DefenseQueueItem = DefenseQueueItem -- #AI_AIR_DISPATCHER.DefenseQueueItem + Report:Add( string.format( " - %s - %s", DefenseQueueItem.SquadronName, DefenseQueueItem.DefenderSquadron.TakeoffTime, DefenseQueueItem.DefenderSquadron.TakeoffInterval) ) - Report:Add( string.format( "\n - Squadron Resources: ", #self.DefenseQueue ) ) - for DefenderSquadronName, DefenderSquadron in pairs( self.DefenderSquadrons ) do - Report:Add( string.format( " - %s - %d", DefenderSquadronName, DefenderSquadron.ResourceCount and DefenderSquadron.ResourceCount or "n/a" ) ) - end - - self:F( Report:Text( "\n" ) ) - trigger.action.outText( Report:Text( "\n" ), 25 ) end - return true - end + Report:Add( string.format( "\n - Squadron Resources: ", #self.DefenseQueue ) ) + for DefenderSquadronName, DefenderSquadron in pairs( self.DefenderSquadrons ) do + Report:Add( string.format( " - %s - %d", DefenderSquadronName, DefenderSquadron.ResourceCount and DefenderSquadron.ResourceCount or "n/a" ) ) + end + self:F( Report:Text( "\n" ) ) + trigger.action.outText( Report:Text( "\n" ), 25 ) + + end + end + do --- Calculates which HUMAN friendlies are nearby the area. @@ -3203,7 +3166,7 @@ do local PlayersCount = 0 if PlayersNearBy then - local DetectedTreatLevel = DetectedSet:CalculateThreatLevelA2G() + local DetectedTreatLevel = DetectedSet:CalculateThreatLevelAIR() for PlayerUnitName, PlayerUnitData in pairs( PlayersNearBy ) do local PlayerUnit = PlayerUnitData -- Wrapper.Unit#UNIT local PlayerName = PlayerUnit:GetPlayerName() @@ -3249,7 +3212,7 @@ do local FriendliesCount = 0 if FriendlyUnitsNearBy then - local DetectedTreatLevel = DetectedSet:CalculateThreatLevelA2G() + local DetectedTreatLevel = DetectedSet:CalculateThreatLevelAIR() for FriendlyUnitName, FriendlyUnitData in pairs( FriendlyUnitsNearBy ) do local FriendlyUnit = FriendlyUnitData -- Wrapper.Unit#UNIT if FriendlyUnit:IsAirPlane() then @@ -3280,18 +3243,6 @@ do return FriendliesCount, FriendlyTypesReport end - --- Schedules a new Patrol for the given SquadronName. - -- @param #AI_AIR_DISPATCHER self - -- @param #string SquadronName The squadron name. - function AI_AIR_DISPATCHER:SchedulerPatrol( SquadronName ) - local PatrolTaskTypes = { "SEAD", "CAS", "BAI" } - local PatrolTaskType = PatrolTaskTypes[math.random(1,3)] - self:Patrol( SquadronName, PatrolTaskType ) - end - - - end - diff --git a/Moose Development/Moose/AI/AI_Air_Engage.lua b/Moose Development/Moose/AI/AI_Air_Engage.lua index a14d604aa..bbfd82497 100644 --- a/Moose Development/Moose/AI/AI_Air_Engage.lua +++ b/Moose Development/Moose/AI/AI_Air_Engage.lua @@ -364,7 +364,6 @@ end function AI_AIR_ENGAGE:onafterAbort( AIGroup, From, Event, To ) AIGroup:ClearTasks() self:Return() - self:__RTB( self.TaskDelay ) end @@ -500,7 +499,6 @@ function AI_AIR_ENGAGE:onafterEngageRoute( DefenderGroup, From, Event, To, Attac else self:I( DefenderGroupName .. ": No targets found -> Going RTB") self:Return() - self:__RTB( self.TaskDelay ) end end @@ -574,14 +572,14 @@ function AI_AIR_ENGAGE:onafterEngage( DefenderGroup, From, Event, To, AttackSetU if TargetDistance <= EngageDistance * 3 then - local AttackUnitTasks = self:CreateAttackUnitTasks( AttackSetUnit, DefenderGroup, EngageAltitude ) + local AttackUnitTasks = self:CreateAttackUnitTasks( AttackSetUnit, DefenderGroup, EngageAltitude ) -- Polymorphic if #AttackUnitTasks == 0 then - self:I( DefenderGroupName .. ": No targets found -> Going RTB") + self:I( DefenderGroupName .. ": No valid targets found -> Going RTB") self:Return() - self:__RTB( self.TaskDelay ) return else + self:I( DefenderGroupName .. ": Engaging targets " ) DefenderGroup:OptionROEOpenFire() DefenderGroup:OptionROTEvadeFire() DefenderGroup:OptionKeepWeaponsOnThreat() @@ -597,9 +595,8 @@ function AI_AIR_ENGAGE:onafterEngage( DefenderGroup, From, Event, To, AttackSetU end else - self:I( DefenderGroupName .. ": No targets found -> Going RTB") + self:I( DefenderGroupName .. ": No targets found -> returning.") self:Return() - self:__RTB( self.TaskDelay ) return end end diff --git a/Moose Development/Moose/AI/AI_Air_Squadron.lua b/Moose Development/Moose/AI/AI_Air_Squadron.lua new file mode 100644 index 000000000..d55e65ded --- /dev/null +++ b/Moose Development/Moose/AI/AI_Air_Squadron.lua @@ -0,0 +1,289 @@ +--- **AI** -- Models squadrons for airplanes and helicopters. +-- +-- This is a class used in the @{AI_Air_Dispatcher} and derived dispatcher classes. +-- +-- === +-- +-- ### Author: **FlightControl** +-- +-- === +-- +-- @module AI.AI_Air_Squadron +-- @image AI_Air_To_Air_Engage.JPG + + + +--- @type AI_AIR_SQUADRON +-- @extends Core.Base#BASE + + +--- Implements the core functions modeling squadrons for airplanes and helicopters. +-- +-- === +-- +-- @field #AI_AIR_SQUADRON +AI_AIR_SQUADRON = { + ClassName = "AI_AIR_SQUADRON", +} + + + +--- Creates a new AI_AIR_SQUADRON object +-- @param #AI_AIR_SQUADRON self +-- @return #AI_AIR_SQUADRON +function AI_AIR_SQUADRON:New( SquadronName, AirbaseName, TemplatePrefixes, ResourceCount ) + + self:I( { Air_Squadron = { SquadronName, AirbaseName, TemplatePrefixes, ResourceCount } } ) + + local AI_Air_Squadron = BASE:New() -- #AI_AIR_SQUADRON + + AI_Air_Squadron.Name = SquadronName + AI_Air_Squadron.Airbase = AIRBASE:FindByName( AirbaseName ) + AI_Air_Squadron.AirbaseName = AI_Air_Squadron.Airbase:GetName() + if not AI_Air_Squadron.Airbase then + error( "Cannot find airbase with name:" .. AirbaseName ) + end + + AI_Air_Squadron.Spawn = {} + if type( TemplatePrefixes ) == "string" then + local SpawnTemplate = TemplatePrefixes + self.DefenderSpawns[SpawnTemplate] = self.DefenderSpawns[SpawnTemplate] or SPAWN:New( SpawnTemplate ) -- :InitCleanUp( 180 ) + AI_Air_Squadron.Spawn[1] = self.DefenderSpawns[SpawnTemplate] + else + for TemplateID, SpawnTemplate in pairs( TemplatePrefixes ) do + self.DefenderSpawns[SpawnTemplate] = self.DefenderSpawns[SpawnTemplate] or SPAWN:New( SpawnTemplate ) -- :InitCleanUp( 180 ) + AI_Air_Squadron.Spawn[#AI_Air_Squadron.Spawn+1] = self.DefenderSpawns[SpawnTemplate] + end + end + AI_Air_Squadron.ResourceCount = ResourceCount + AI_Air_Squadron.TemplatePrefixes = TemplatePrefixes + AI_Air_Squadron.Captured = false -- Not captured. This flag will be set to true, when the airbase where the squadron is located, is captured. + + self:SetSquadronLanguage( SquadronName, "EN" ) -- Squadrons speak English by default. + + return AI_Air_Squadron +end + +--- Set the Name of the Squadron. +-- @param #AI_AIR_SQUADRON self +-- @param #string Name The Squadron Name. +-- @return #AI_AIR_SQUADRON The Squadron. +function AI_AIR_SQUADRON:SetName( Name ) + + self.Name = Name + + return self +end + +--- Get the Name of the Squadron. +-- @param #AI_AIR_SQUADRON self +-- @return #string The Squadron Name. +function AI_AIR_SQUADRON:GetName() + + return self.Name +end + +--- Set the ResourceCount of the Squadron. +-- @param #AI_AIR_SQUADRON self +-- @param #number ResourceCount The Squadron ResourceCount. +-- @return #AI_AIR_SQUADRON The Squadron. +function AI_AIR_SQUADRON:SetResourceCount( ResourceCount ) + + self.ResourceCount = ResourceCount + + return self +end + +--- Get the ResourceCount of the Squadron. +-- @param #AI_AIR_SQUADRON self +-- @return #number The Squadron ResourceCount. +function AI_AIR_SQUADRON:GetResourceCount() + + return self.ResourceCount +end + +--- Add Resources to the Squadron. +-- @param #AI_AIR_SQUADRON self +-- @param #number Resources The Resources to be added. +-- @return #AI_AIR_SQUADRON The Squadron. +function AI_AIR_SQUADRON:AddResources( Resources ) + + self.ResourceCount = self.ResourceCount + Resources + + return self +end + +--- Remove Resources to the Squadron. +-- @param #AI_AIR_SQUADRON self +-- @param #number Resources The Resources to be removed. +-- @return #AI_AIR_SQUADRON The Squadron. +function AI_AIR_SQUADRON:RemoveResources( Resources ) + + self.ResourceCount = self.ResourceCount - Resources + + return self +end + +--- Set the Overhead of the Squadron. +-- @param #AI_AIR_SQUADRON self +-- @param #number Overhead The Squadron Overhead. +-- @return #AI_AIR_SQUADRON The Squadron. +function AI_AIR_SQUADRON:SetOverhead( Overhead ) + + self.Overhead = Overhead + + return self +end + +--- Get the Overhead of the Squadron. +-- @param #AI_AIR_SQUADRON self +-- @return #number The Squadron Overhead. +function AI_AIR_SQUADRON:GetOverhead() + + return self.Overhead +end + +--- Set the Grouping of the Squadron. +-- @param #AI_AIR_SQUADRON self +-- @param #number Grouping The Squadron Grouping. +-- @return #AI_AIR_SQUADRON The Squadron. +function AI_AIR_SQUADRON:SetGrouping( Grouping ) + + self.Grouping = Grouping + + return self +end + +--- Get the Grouping of the Squadron. +-- @param #AI_AIR_SQUADRON self +-- @return #number The Squadron Grouping. +function AI_AIR_SQUADRON:GetGrouping() + + return self.Grouping +end + +--- Set the FuelThreshold of the Squadron. +-- @param #AI_AIR_SQUADRON self +-- @param #number FuelThreshold The Squadron FuelThreshold. +-- @return #AI_AIR_SQUADRON The Squadron. +function AI_AIR_SQUADRON:SetFuelThreshold( FuelThreshold ) + + self.FuelThreshold = FuelThreshold + + return self +end + +--- Get the FuelThreshold of the Squadron. +-- @param #AI_AIR_SQUADRON self +-- @return #number The Squadron FuelThreshold. +function AI_AIR_SQUADRON:GetFuelThreshold() + + return self.FuelThreshold +end + +--- Set the EngageProbability of the Squadron. +-- @param #AI_AIR_SQUADRON self +-- @param #number EngageProbability The Squadron EngageProbability. +-- @return #AI_AIR_SQUADRON The Squadron. +function AI_AIR_SQUADRON:SetEngageProbability( EngageProbability ) + + self.EngageProbability = EngageProbability + + return self +end + +--- Get the EngageProbability of the Squadron. +-- @param #AI_AIR_SQUADRON self +-- @return #number The Squadron EngageProbability. +function AI_AIR_SQUADRON:GetEngageProbability() + + return self.EngageProbability +end + +--- Set the Takeoff of the Squadron. +-- @param #AI_AIR_SQUADRON self +-- @param #number Takeoff The Squadron Takeoff. +-- @return #AI_AIR_SQUADRON The Squadron. +function AI_AIR_SQUADRON:SetTakeoff( Takeoff ) + + self.Takeoff = Takeoff + + return self +end + +--- Get the Takeoff of the Squadron. +-- @param #AI_AIR_SQUADRON self +-- @return #number The Squadron Takeoff. +function AI_AIR_SQUADRON:GetTakeoff() + + return self.Takeoff +end + +--- Set the Landing of the Squadron. +-- @param #AI_AIR_SQUADRON self +-- @param #number Landing The Squadron Landing. +-- @return #AI_AIR_SQUADRON The Squadron. +function AI_AIR_SQUADRON:SetLanding( Landing ) + + self.Landing = Landing + + return self +end + +--- Get the Landing of the Squadron. +-- @param #AI_AIR_SQUADRON self +-- @return #number The Squadron Landing. +function AI_AIR_SQUADRON:GetLanding() + + return self.Landing +end + +--- Set the TankerName of the Squadron. +-- @param #AI_AIR_SQUADRON self +-- @param #string TankerName The Squadron Tanker Name. +-- @return #AI_AIR_SQUADRON The Squadron. +function AI_AIR_SQUADRON:SetTankerName( TankerName ) + + self.TankerName = TankerName + + return self +end + +--- Get the Tanker Name of the Squadron. +-- @param #AI_AIR_SQUADRON self +-- @return #string The Squadron Tanker Name. +function AI_AIR_SQUADRON:GetTankerName() + + return self.TankerName +end + + +--- Set the Radio of the Squadron. +-- @param #AI_AIR_SQUADRON self +-- @param #number RadioFrequency The frequency of communication. +-- @param #number RadioModulation The modulation of communication. +-- @param #number RadioPower The power in Watts of communication. +-- @param #string Language The language of the radio speech. +-- @return #AI_AIR_SQUADRON The Squadron. +function AI_AIR_SQUADRON:SetRadio( RadioFrequency, RadioModulation, RadioPower, Language ) + + self.RadioFrequency = RadioFrequency + self.RadioModulation = RadioModulation or radio.modulation.AM + self.RadioPower = RadioPower or 100 + + if self.RadioSpeech then + self.RadioSpeech:Stop() + end + + self.RadioSpeech = nil + + self.RadioSpeech = RADIOSPEECH:New( RadioFrequency, RadioModulation ) + self.RadioSpeech.power = RadioPower + self.RadioSpeech:Start( 0.5 ) + + self.RadioSpeech:SetLanguage( Language ) + + return self +end + + diff --git a/Moose Development/Moose/Core/Scheduler.lua b/Moose Development/Moose/Core/Scheduler.lua index a0541c440..c5f796185 100644 --- a/Moose Development/Moose/Core/Scheduler.lua +++ b/Moose Development/Moose/Core/Scheduler.lua @@ -214,6 +214,7 @@ function SCHEDULER:New( SchedulerObject, SchedulerFunction, SchedulerArguments, local ScheduleID = nil self.MasterObject = SchedulerObject + self.ShowTrace = true if SchedulerFunction then ScheduleID = self:Schedule( SchedulerObject, SchedulerFunction, SchedulerArguments, Start, Repeat, RandomizeFactor, Stop, 4 )