diff --git a/Moose Development/Moose/AI/AI_Escort.lua b/Moose Development/Moose/AI/AI_Escort.lua index 713129c0d..6a817ffbf 100644 --- a/Moose Development/Moose/AI/AI_Escort.lua +++ b/Moose Development/Moose/AI/AI_Escort.lua @@ -25,11 +25,49 @@ -- -- Allows you to interact with escorting AI on your flight and take the lead. -- --- Each escorting group can be commanded with a whole set of radio commands (radio menu in your flight, and then F10). +-- Each escorting group can be commanded with a complete set of radio commands (radio menu in your flight, and then F10). -- --- The radio commands will vary according the category of the group. The richest set of commands are with Helicopters and AirPlanes. +-- The radio commands will vary according the category of the group. The richest set of commands are with helicopters and airPlanes. -- Ships and Ground troops will have a more limited set, but they can provide support through the bombing of targets designated by the other escorts. --- +-- +-- Escorts detect targets using a built-in detection mechanism. The detected targets are reported at a specified time interval. +-- Once targets are reported, each escort has these targets as menu options to command the attack of these targets. +-- Targets are by default grouped per area of 5000 meters, but the kind of detection and the grouping range can be altered. +-- +-- Different formations can be selected in the Flight menu: Trail, Stack, Left Line, Right Line, Left Wing, Right Wing, Central Wing and Boxed formations are available. +-- The Flight menu also allows for a mass attack, where all of the escorts are commanded to attack a target. +-- +-- Escorts can emit flares to reports their location. They can be commanded to hold at a location, which can be their current or the leader location. +-- In this way, you can spread out the escorts over the battle field before a coordinated attack. +-- +-- But basically, the escort class provides 4 modes of operation, and depending on the mode, you are either leading the flight, or following the flight. +-- +-- ## Leading the flight +-- +-- When leading the flight, you are expected to guide the escorts towards the target areas, +-- and carefully coordinate the attack based on the threat levels reported, and the available weapons +-- carried by the escorts. Ground ships or ground troops can execute A-assisted attacks, when they have long-range ground precision weapons for attack. +-- +-- ## Following the flight +-- +-- Escorts can be commanded to execute a specific mission path. In this mode, the escorts are in the lead. +-- You as a player, are following the escorts, and are commanding them to progress the mission while +-- ensuring that the escorts survive. You are joining the escorts in the battlefield. They will detect and report targets +-- and you will ensure that the attacks are well coordinated, assigning the correct escort type for the detected target +-- type. Once the attack is finished, the escort will resume the mission it was assigned. +-- In other words, you can use the escorts for reconnaissance, and for guiding the attack. +-- Imagine you as a mi-8 pilot, assigned to pickup cargo. Two ka-50s are guiding the way, and you are +-- following. You are in control. The ka-50s detect targets, report them, and you command how the attack +-- will commence and from where. You can control where the escorts are holding position and which targets +-- are attacked first. You are in control how the ka-50s will follow their mission path. +-- +-- Escorts can act as part of a AI A2G dispatcher offensive. In this way, You was a player are in control. +-- The mission is defined by the A2G dispatcher, and you are responsible to join the flight and ensure that the +-- attack is well coordinated. +-- +-- It is with great proud that I present you this class, and I hope you will enjoy the functionality and the dynamism +-- it brings in your DCS world simulations. +-- -- # RADIO MENUs that can be created: -- -- Find a summary below of the current available commands: @@ -38,7 +76,7 @@ -- -- Escort group navigation functions: -- --- * **"Join-Up and Follow at x meters":** The escort group fill follow you at about x meters, and they will follow you. +-- * **"Join-Up":** The escort group fill follow you in the assigned formation. -- * **"Flare":** Provides menu commands to let the escort group shoot a flare in the air in a color. -- * **"Smoke":** Provides menu commands to let the escort group smoke the air in a color. Note that smoking is only available for ground and naval troops. -- @@ -46,32 +84,33 @@ -- -- Escort group navigation functions: -- --- * **"At current location":** Stops the escort group and they will hover 30 meters above the ground at the position they stopped. --- * **"At client location":** Stops the escort group and they will hover 30 meters above the ground at the position they stopped. +-- * **"At current location":** The escort group will hover above the ground at the position they were. The altitude can be specified as a parameter. +-- * **"At my location":** The escort group will hover or orbit at the position where you are. The escort will fly to your location and hold position. The altitude can be specified as a parameter. -- -- ## Report targets ...: -- --- Report targets will make the escort group to report any target that it identifies within a 8km range. Any detected target can be attacked using the 4. Attack nearby targets function. (see below). +-- Report targets will make the escort group to report any target that it identifies within detection range. Any detected target can be attacked using the "Attack Targets" menu function. (see below). -- -- * **"Report now":** Will report the current detected targets. --- * **"Report targets on":** Will make the escort group to report detected targets and will fill the "Attack nearby targets" menu list. +-- * **"Report targets on":** Will make the escorts to report the detected targets and will fill the "Attack Targets" menu list. -- * **"Report targets off":** Will stop detecting targets. -- --- ## Scan targets ...: --- --- Menu items to pop-up the escort group for target scanning. After scanning, the escort group will resume with the mission or defined task. --- --- * **"Scan targets 30 seconds":** Scan 30 seconds for targets. --- * **"Scan targets 60 seconds":** Scan 60 seconds for targets. --- -- ## Attack targets ...: -- -- This menu item will list all detected targets within a 15km range. Depending on the level of detection (known/unknown) and visuality, the targets type will also be listed. +-- This menu will be available in Flight menu or in each Escort menu. +-- +-- ## Scan targets ...: +-- +-- Menu items to pop-up the escort group for target scanning. After scanning, the escort group will resume with the mission or rejoin formation. +-- +-- * **"Scan targets 30 seconds":** Scan 30 seconds for targets. +-- * **"Scan targets 60 seconds":** Scan 60 seconds for targets. -- -- ## Request assistance from ...: -- --- This menu item will list all detected targets within a 15km range, as with the menu item **Attack Targets**. --- This menu item allows to request attack support from other escorts supporting the current client group. +-- This menu item will list all detected targets within a 15km range, similar as with the menu item **Attack Targets**. +-- This menu item allows to request attack support from other ground based escorts supporting the current escort. -- eg. the function allows a player to request support from the Ship escort to attack a target identified by the Plane escort with its Tomahawk missiles. -- eg. the function allows a player to request support from other Planes escorting to bomb the unit with illumination missiles or bombs, so that the main plane escort can attack the area. -- @@ -111,22 +150,16 @@ --- @type AI_ESCORT -- @extends AI.AI_Formation#AI_FORMATION --- @field Wrapper.Client#CLIENT EscortUnit --- @field Wrapper.Group#GROUP EscortGroup --- @field #string EscortName --- @field #AI_ESCORT.MODE EscortMode The mode the escort is in. --- @field Core.Scheduler#SCHEDULER FollowScheduler The instance of the SCHEDULER class. --- @field #number FollowDistance The current follow distance. --- @field #boolean ReportTargets If true, nearby targets are reported. --- @Field DCS#AI.Option.Air.val.ROE OptionROE Which ROE is set to the EscortGroup. --- @field DCS#AI.Option.Air.val.REACTION_ON_THREAT OptionReactionOnThreat Which REACTION_ON_THREAT is set to the EscortGroup. --- @field FunctionalMENU_GROUPDETECTION_BASE Detection + + +-- TODO: Add the menus when the class Start method is activated. +-- TODO: Remove the menus when the class Stop method is called. --- AI_ESCORT class -- -- # AI_ESCORT construction methods. -- --- Create a new SPAWN object with the @{#AI_ESCORT.New} method: +-- Create a new AI_ESCORT object with the @{#AI_ESCORT.New} method: -- -- * @{#AI_ESCORT.New}: Creates a new AI_ESCORT object from a @{Wrapper.Group#GROUP} for a @{Wrapper.Client#CLIENT}, with an optional briefing text. -- @@ -147,10 +180,6 @@ AI_ESCORT = { EscortUnit = nil, EscortGroup = nil, EscortMode = 1, - MODE = { - FOLLOW = 1, - MISSION = 2, - }, Targets = {}, -- The identified targets FollowScheduler = nil, ReportTargets = true, @@ -163,11 +192,6 @@ AI_ESCORT = { --- @field Functional.Detection#DETECTION_AREAS AI_ESCORT.Detection = nil ---- AI_ESCORT.Mode class --- @type AI_ESCORT.MODE --- @field #number FOLLOW --- @field #number MISSION - --- MENUPARAM type -- @type MENUPARAM -- @field #AI_ESCORT ParamSelf @@ -196,13 +220,18 @@ function AI_ESCORT:New( EscortUnit, EscortGroupSet, EscortName, EscortBriefing ) local self = BASE:Inherit( self, AI_FORMATION:New( EscortUnit, EscortGroupSet, EscortName, EscortBriefing ) ) -- #AI_ESCORT self:F( { EscortUnit, EscortGroupSet } ) - self.EscortUnit = self.FollowUnit -- Wrapper.Unit#UNIT + self.PlayerUnit = self.FollowUnit -- Wrapper.Unit#UNIT + self.PlayerGroup = self.FollowUnit:GetGroup() -- Wrapper.Group#GROUP + + self.EscortName = EscortName + self.EscortGroupSet = EscortGroupSet + + self.EscortGroupSet:SetSomeIteratorLimit( 8 ) + self.EscortBriefing = EscortBriefing - - - + self.Menu = {} -- if not EscortBriefing then -- EscortGroup:MessageToClient( EscortGroup:GetCategoryName() .. " '" .. EscortName .. "' (" .. EscortGroup:GetCallsign() .. ") reporting! " .. @@ -221,51 +250,125 @@ function AI_ESCORT:New( EscortUnit, EscortGroupSet, EscortName, EscortBriefing ) self.GT1 = 0 - self.FlightMenu = MENU_GROUP:New( self.EscortUnit:GetGroup(), "Flight" ) - EscortGroupSet:ForEachGroup( --- @param Core.Group#GROUP EscortGroup function( EscortGroup ) - EscortGroup.EscortMenu = MENU_GROUP:New( self.EscortUnit:GetGroup(), EscortGroup:GetName() ) - -- Set EscortGroup known at EscortUnit. - if not self.EscortUnit._EscortGroups then - self.EscortUnit._EscortGroups = {} + if not self.PlayerUnit._EscortGroups then + self.PlayerUnit._EscortGroups = {} end - if not self.EscortUnit._EscortGroups[EscortGroup:GetName()] then - self.EscortUnit._EscortGroups[EscortGroup:GetName()] = {} - self.EscortUnit._EscortGroups[EscortGroup:GetName()].EscortGroup = EscortGroup - self.EscortUnit._EscortGroups[EscortGroup:GetName()].EscortName = self.EscortName - self.EscortUnit._EscortGroups[EscortGroup:GetName()].Detection = self.Detection + if not self.PlayerUnit._EscortGroups[EscortGroup:GetName()] then + self.PlayerUnit._EscortGroups[EscortGroup:GetName()] = {} + self.PlayerUnit._EscortGroups[EscortGroup:GetName()].EscortGroup = EscortGroup + self.PlayerUnit._EscortGroups[EscortGroup:GetName()].EscortName = self.EscortName + self.PlayerUnit._EscortGroups[EscortGroup:GetName()].Detection = self.Detection end - - EscortGroup.EscortMode = AI_ESCORT.MODE.FOLLOW end ) - - self.Detection = DETECTION_AREAS:New( EscortGroupSet, 5000 ) - - self.Detection:Start() - + return self end + +function AI_ESCORT:_InitFlightMenus() + + self:SetFlightMenuJoinUp() + self:SetFlightMenuFormation( "Trail" ) + self:SetFlightMenuFormation( "Stack" ) + self:SetFlightMenuFormation( "LeftLine" ) + self:SetFlightMenuFormation( "RightLine" ) + self:SetFlightMenuFormation( "LeftWing" ) + self:SetFlightMenuFormation( "RightWing" ) + self:SetFlightMenuFormation( "Vic" ) + self:SetFlightMenuFormation( "Box" ) + + self:SetFlightMenuHoldAtEscortPosition() + self:SetFlightMenuHoldAtLeaderPosition() + + self:SetFlightMenuFlare() + self:SetFlightMenuSmoke() + + self:SetFlightMenuROE() + self:SetFlightMenuROT() + + self:SetFlightMenuTargets() + +end + +function AI_ESCORT:_InitEscortMenus( EscortGroup ) + + EscortGroup.EscortMenu = MENU_GROUP:New( self.PlayerGroup, EscortGroup:GetCallsign(), self.MainMenu ) + + self:SetEscortMenuJoinUp( EscortGroup ) + self:SetEscortMenuResumeMission( EscortGroup ) + + self:SetEscortMenuHoldAtEscortPosition( EscortGroup ) + self:SetEscortMenuHoldAtLeaderPosition( EscortGroup ) + + self:SetEscortMenuFlare( EscortGroup ) + self:SetEscortMenuSmoke( EscortGroup ) + + self:SetEscortMenuROE( EscortGroup ) + self:SetEscortMenuROT( EscortGroup ) + + self:SetEscortMenuTargets( EscortGroup ) + +end + +function AI_ESCORT:_InitEscortRoute( EscortGroup ) + + EscortGroup.MissionRoute = EscortGroup:GetTaskRoute() + +end + + +--- @param #AI_ESCORT self +-- @param Core.Set#SET_GROUP EscortGroupSet function AI_ESCORT:onafterStart( EscortGroupSet ) - self:E("Start") + self:F() EscortGroupSet:ForEachGroup( --- @param Core.Group#GROUP EscortGroup function( EscortGroup ) - EscortGroup.EscortMenu = MENU_GROUP:New( self.EscortUnit:GetGroup(), EscortGroup:GetName() ) - EscortGroup:WayPointInitialize( 1 ) + EscortGroup:WayPointInitialize() EscortGroup:OptionROTVertical() EscortGroup:OptionROEOpenFire() end ) + + -- TODO:Revise this... + local LeaderEscort = EscortGroupSet:GetFirst() -- Wrapper.Group#GROUP + if LeaderEscort then + local Report = REPORT:New( "Escort reporting:" ) + Report:Add( "Joining Up " .. EscortGroupSet:GetUnitTypeNames():Text( ", " ) .. " from " .. LeaderEscort:GetCoordinate():ToString( self.PlayerUnit ) ) + LeaderEscort:MessageTypeToGroup( Report:Text(), MESSAGE.Type.Information, self.PlayerUnit ) + end + + self.Detection = DETECTION_AREAS:New( EscortGroupSet, 5000 ) + + self.Detection:__Start( 30 ) + self:HandleEvent( EVENTS.Dead, OnEventDeadOrCrash ) + self:HandleEvent( EVENTS.Crash, OnEventDeadOrCrash ) + + self.MainMenu = MENU_GROUP:New( self.PlayerGroup, self.EscortName ) + self.FlightMenu = MENU_GROUP:New( self.PlayerGroup, "Flight", self.MainMenu ) + + self:_InitFlightMenus() + + self.EscortGroupSet:ForSomeGroupAlive( + --- @param Core.Group#GROUP EscortGroup + function( EscortGroup ) + + self:_InitEscortMenus( EscortGroup ) + self:_InitEscortRoute( EscortGroup ) + + end + ) + end --- Set a Detection method for the EscortUnit to be reported upon. @@ -276,7 +379,7 @@ function AI_ESCORT:SetDetection( Detection ) self.Detection = Detection self.EscortGroup.Detection = self.Detection - self.EscortUnit._EscortGroups[self.EscortGroup:GetName()].Detection = self.EscortGroup.Detection + self.PlayerUnit._EscortGroups[self.EscortGroup:GetName()].Detection = self.EscortGroup.Detection Detection:__Start( 1 ) @@ -291,114 +394,139 @@ function AI_ESCORT:TestSmokeDirectionVector( SmokeDirection ) end ---- Defines the default menus +--- Defines the default menus for helicopters. -- @param #AI_ESCORT self +-- @param #number XStart The start position on the X-axis in meters for the first group. +-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. +-- @param #nubmer YStart The start position on the Y-axis in meters for the first group. +-- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. +-- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. +-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. +-- @param #number ZLevels The amount of levels on the Z-axis. -- @return #AI_ESCORT -function AI_ESCORT:Menus() +function AI_ESCORT:MenusHelicopter( XStart, XSpace, YStart, YSpace, ZStart, ZSpace, ZLevels ) self:F() -- self:MenuScanForTargets( 100, 60 ) - self:MenuJoinUp() - self:MenuFormationTrail( 0, 50, 100 ) - self:MenuFormationStack( 0, 0, 100, 100 ) - self:MenuFormationLeftLine( 0, 0, 100, 100 ) - self:MenuFormationRightLine( 0, 0, 100, 100 ) - self:MenuFormationLeftWing( 0, 50, 0, 100, 100 ) - self:MenuFormationRightWing( 0, 50, 0, 100, 100 ) - self:MenuFormationCenterWing( 50, 50, 0, 50, 100, 100 ) - self:MenuFormationBox( 50, 100, 0, 50, 50, 100, 10 ) + self.XStart = XStart or 50 + self.XSpace = XSpace or 50 + self.YStart = YStart or 50 + self.YSpace = YSpace or 50 + self.ZStart = ZStart or 50 + self.ZSpace = ZSpace or 50 + self.ZLevels = ZLevels or 10 + self:MenuJoinUp() + self:MenuFormationTrail(self.XStart,self.XSpace,self.YStart) + self:MenuFormationStack(self.XStart,self.XSpace,self.YStart,self.YSpace) + self:MenuFormationLeftLine(self.XStart,self.YStart,self.ZStart,self.ZSpace) + self:MenuFormationRightLine(self.XStart,self.YStart,self.ZStart,self.ZSpace) + self:MenuFormationLeftWing(self.XStart,self.XSpace,self.YStart,self.ZStart,self.ZSpace) + self:MenuFormationRightWing(self.XStart,self.XSpace,self.YStart,self.ZStart,self.ZSpace) + self:MenuFormationVic(self.XStart,self.XSpace,self.YStart,self.YSpace,self.ZStart,self.ZSpace) + self:MenuFormationBox(self.XStart,self.XSpace,self.YStart,self.YSpace,self.ZStart,self.ZSpace,self.ZLevels) + + self:MenuHoldAtEscortPosition( 30 ) + self:MenuHoldAtEscortPosition( 100 ) + self:MenuHoldAtEscortPosition( 500 ) + self:MenuHoldAtLeaderPosition( 30, 500 ) + + self:MenuFlare() + self:MenuSmoke() + + self:MenuTargets( 60 ) + self:MenuAssistedAttack() + self:MenuROE() + self:MenuROT() + + return self +end + + +--- Defines the default menus for airplanes. +-- @param #AI_ESCORT self +-- @param #number XStart The start position on the X-axis in meters for the first group. +-- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. +-- @param #nubmer YStart The start position on the Y-axis in meters for the first group. +-- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. +-- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. +-- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. +-- @param #number ZLevels The amount of levels on the Z-axis. +-- @return #AI_ESCORT +function AI_ESCORT:MenusAirplanes( XStart, XSpace, YStart, YSpace, ZStart, ZSpace, ZLevels ) + self:F() + +-- self:MenuScanForTargets( 100, 60 ) + + self.XStart = XStart or 50 + self.XSpace = XSpace or 50 + self.YStart = YStart or 50 + self.YSpace = YSpace or 50 + self.ZStart = ZStart or 50 + self.ZSpace = ZSpace or 50 + self.ZLevels = ZLevels or 10 + + self:MenuJoinUp() + self:MenuFormationTrail(self.XStart,self.XSpace,self.YStart) + self:MenuFormationStack(self.XStart,self.XSpace,self.YStart,self.YSpace) + self:MenuFormationLeftLine(self.XStart,self.YStart,self.ZStart,self.ZSpace) + self:MenuFormationRightLine(self.XStart,self.YStart,self.ZStart,self.ZSpace) + self:MenuFormationLeftWing(self.XStart,self.XSpace,self.YStart,self.ZStart,self.ZSpace) + self:MenuFormationRightWing(self.XStart,self.XSpace,self.YStart,self.ZStart,self.ZSpace) + self:MenuFormationVic(self.XStart,self.XSpace,self.YStart,self.YSpace,self.ZStart,self.ZSpace) + self:MenuFormationBox(self.XStart,self.XSpace,self.YStart,self.YSpace,self.ZStart,self.ZSpace,self.ZLevels) + self:MenuHoldAtEscortPosition( 1000, 500 ) self:MenuHoldAtLeaderPosition( 1000, 500 ) self:MenuFlare() self:MenuSmoke() - self:MenuReportTargets( 60 ) + self:MenuTargets( 60 ) self:MenuAssistedAttack() self:MenuROE() - self:MenuEvasion() - --- self:MenuResumeMission() - + self:MenuROT() return self end +function AI_ESCORT:SetFlightMenuFormation( Formation ) + + local FormationID = "Formation" .. Formation + + local MenuFormation = self.Menu[FormationID] + + if MenuFormation then + local Arguments = MenuFormation.Arguments + self:I({Arguments=unpack(Arguments)}) + local FlightMenuFormation = MENU_GROUP:New( self.PlayerGroup, "Formation", self.MainMenu ) + local MenuFlightFormationID = MENU_GROUP_COMMAND:New( self.PlayerGroup, Formation, FlightMenuFormation, + function ( self, Formation, ... ) + self.EscortGroupSet:ForSomeGroupAlive( + --- @param Core.Group#GROUP EscortGroup + function( EscortGroup, self, Formation, Arguments ) + if EscortGroup:IsAir() then + self:E({FormationID=FormationID}) + self[FormationID]( self, unpack(Arguments) ) + end + end, self, Formation, Arguments + ) + end, self, Formation, Arguments + ) + end + + return self +end + function AI_ESCORT:MenuFormation( Formation, ... ) - if not self.FlightMenuFormation then - self.FlightMenuFormation = MENU_GROUP:New( self.EscortUnit:GetGroup(), "Formation", self.FlightMenu ) - end + local FormationID = "Formation"..Formation + self.Menu[FormationID] = self.Menu[FormationID] or {} + self.Menu[FormationID].Arguments = arg - if not self["FlightMenuFormation"..Formation] then - self["FlightMenuFormation"..Formation] = MENU_GROUP_COMMAND:New( self.EscortUnit:GetGroup(), Formation, self.FlightMenuFormation, - function ( self, Formation, ... ) - self.EscortGroupSet:ForEachGroupAlive( - --- @param Core.Group#GROUP EscortGroup - function( EscortGroup, self, Formation, ... ) - if EscortGroup:IsAir() then - self:E({Formation=Formation}) - self["Formation"..Formation]( self, ... ) - end - end, self, Formation, ... - ) - end, self, Formation, ... - ) - end - --- self.EscortGroupSet:ForEachGroupAlive( --- --- @param Core.Group#GROUP EscortGroup --- function( EscortGroup ) --- if EscortGroup:IsAir() then --- if not EscortGroup.EscortMenuReportNavigation then --- EscortGroup.EscortMenuReportNavigation = MENU_GROUP:New( self.EscortUnit:GetGroup(), "Navigation", EscortGroup.EscortMenu ) --- end --- --- if not EscortGroup["EscortMenuFormation"..Formation] then --- EscortGroup["EscortMenuFormation"..Formation] = MENU_GROUP_COMMAND:New( self.EscortUnit:GetGroup(), Formation, EscortGroup.EscortMenuReportNavigation, AI_ESCORT["_EscortFormation"..Formation], self, EscortGroup, ... ) --- end --- end --- end --- ) - -end - - ---- Defines --- Defines a menu slot to let the escort to join formation. --- This menu will appear under **Formation**. --- @param #AI_ESCORT self --- @return #AI_ESCORT -function AI_ESCORT:MenuJoinUp() - - if not self.FlightMenuReportNavigation then - self.FlightMenuReportNavigation = MENU_GROUP:New( self.EscortUnit:GetGroup(), "Navigation", self.FlightMenu ) - end - - if not self.FlightMenuJoinUp then - self.FlightMenuJoinUp = MENU_GROUP_COMMAND:New( self.EscortUnit:GetGroup(), "Join Up", self.FlightMenuReportNavigation, AI_ESCORT._FlightJoinUp, self ) - end - - self.EscortGroupSet:ForEachGroupAlive( - --- @param Core.Group#GROUP EscortGroup - function( EscortGroup ) - if EscortGroup:IsAir() then - if not EscortGroup.EscortMenuReportNavigation then - EscortGroup.EscortMenuReportNavigation = MENU_GROUP:New( self.EscortUnit:GetGroup(), "Navigation", EscortGroup.EscortMenu ) - end - - if not EscortGroup.EscortMenuJoinUpAndFollow then - EscortGroup.EscortMenuJoinUpAndFollow = MENU_GROUP_COMMAND:New( self.EscortUnit:GetGroup(), "Join-Up", EscortGroup.EscortMenuReportNavigation, ESCORT._JoinUp, self, EscortGroup ) - end - - end - end - ) - - return self end @@ -552,6 +680,87 @@ function AI_ESCORT:MenuFormationBox( XStart, XSpace, YStart, YSpace, ZStart, ZSp return self end +function AI_ESCORT:SetFlightMenuJoinUp() + + if self.Menu.JoinUp == true then + local FlightMenuReportNavigation = MENU_GROUP:New( self.PlayerGroup, "Navigation", self.FlightMenu ) + local FlightMenuJoinUp = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Join Up", FlightMenuReportNavigation, AI_ESCORT._FlightJoinUp, self ) + end + +end + + +--- Sets a menu slot to join formation for an escort. +-- @param #AI_ESCORT self +-- @return #AI_ESCORT +function AI_ESCORT:SetEscortMenuJoinUp( EscortGroup ) + + if self.Menu.JoinUp == true then + if EscortGroup:IsAir() then + local EscortGroupName = EscortGroup:GetName() + local EscortMenuReportNavigation = MENU_GROUP:New( self.PlayerGroup, "Navigation", EscortGroup.EscortMenu ) + local EscortMenuJoinUp = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Join Up", EscortMenuReportNavigation, AI_ESCORT._JoinUp, self, EscortGroup ) + end + end +end + + +--- Defines --- Defines a menu slot to let the escort to join formation. +-- @param #AI_ESCORT self +-- @return #AI_ESCORT +function AI_ESCORT:MenuJoinUp() + + self.Menu.JoinUp = true + + return self +end + + +function AI_ESCORT:SetFlightMenuHoldAtEscortPosition() + + for _, MenuHoldAtEscortPosition in pairs( self.Menu.HoldAtEscortPosition ) do + local FlightMenuReportNavigation = MENU_GROUP:New( self.PlayerGroup, "Navigation", self.FlightMenu ) + + local FlightMenuHoldPosition = MENU_GROUP_COMMAND + :New( + self.PlayerGroup, + MenuHoldAtEscortPosition.MenuText, + FlightMenuReportNavigation, + AI_ESCORT._FlightHoldPosition, + self, + nil, + MenuHoldAtEscortPosition.Height, + MenuHoldAtEscortPosition.Speed + ) + + end + return self +end + +function AI_ESCORT:SetEscortMenuHoldAtEscortPosition( EscortGroup ) + + for _, HoldAtEscortPosition in pairs( self.Menu.HoldAtEscortPosition ) do + if EscortGroup:IsAir() then + local EscortGroupName = EscortGroup:GetName() + local EscortMenuReportNavigation = MENU_GROUP:New( self.PlayerGroup, "Navigation", EscortGroup.EscortMenu ) + local EscortMenuHoldPosition = MENU_GROUP_COMMAND + :New( + self.PlayerGroup, + HoldAtEscortPosition.MenuText, + EscortMenuReportNavigation, + AI_ESCORT._HoldPosition, + self, + EscortGroup, + EscortGroup, + HoldAtEscortPosition.Height, + HoldAtEscortPosition.Speed + ) + end + end + + return self +end + --- Defines a menu slot to let the escort hold at their current position and stay low with a specified height during a specified time in seconds. -- This menu will appear under **Hold position**. @@ -586,60 +795,63 @@ function AI_ESCORT:MenuHoldAtEscortPosition( Height, Speed, MenuTextFormat ) end end - if not self.FlightMenuHold then - self.FlightMenuHold = MENU_GROUP:New( self.EscortUnit:GetGroup(), "Hold position", self.FlightMenu ) - end - - if not self.FlightMenuHoldPosition then - self.FlightMenuHoldPosition = {} - end - - self.FlightMenuHoldPosition[#self.FlightMenuHoldPosition+1] = MENU_GROUP_COMMAND - :New( - self.EscortUnit:GetGroup(), - MenuText, - self.FlightMenuHold, - AI_ESCORT._FlightHoldPosition, - self, - nil, - Height, - Speed - ) - - self.EscortGroupSet:ForEachGroupAlive( - --- @param Core.Group#GROUP EscortGroup - function( EscortGroup ) - if EscortGroup:IsAir() then - - if not EscortGroup.EscortMenuHold then - EscortGroup.EscortMenuHold = MENU_GROUP:New( self.EscortUnit:GetGroup(), "Hold position", EscortGroup.EscortMenu ) - end - - - if not EscortGroup.EscortMenuHoldPosition then - EscortGroup.EscortMenuHoldPosition = {} - end - - EscortGroup.EscortMenuHoldPosition[#EscortGroup.EscortMenuHoldPosition+1] = MENU_GROUP_COMMAND - :New( - self.EscortUnit:GetGroup(), - MenuText, - EscortGroup.EscortMenuHold, - AI_ESCORT._HoldPosition, - self, - EscortGroup, - EscortGroup, - Height, - Speed - ) - end - end - ) + self.Menu.HoldAtEscortPosition = self.Menu.HoldAtEscortPosition or {} + self.Menu.HoldAtEscortPosition[#self.Menu.HoldAtEscortPosition+1] = {} + self.Menu.HoldAtEscortPosition[#self.Menu.HoldAtEscortPosition].Height = Height + self.Menu.HoldAtEscortPosition[#self.Menu.HoldAtEscortPosition].Speed = Speed + self.Menu.HoldAtEscortPosition[#self.Menu.HoldAtEscortPosition].MenuText = MenuText return self end +function AI_ESCORT:SetFlightMenuHoldAtLeaderPosition() + + for _, MenuHoldAtLeaderPosition in pairs( self.Menu.HoldAtLeaderPosition ) do + local FlightMenuReportNavigation = MENU_GROUP:New( self.PlayerGroup, "Navigation", self.FlightMenu ) + + local FlightMenuHoldAtLeaderPosition = MENU_GROUP_COMMAND + :New( + self.PlayerGroup, + MenuHoldAtLeaderPosition.MenuText, + FlightMenuReportNavigation, + AI_ESCORT._FlightHoldPosition, + self, + self.PlayerGroup, + MenuHoldAtLeaderPosition.Height, + MenuHoldAtLeaderPosition.Speed + ) + end + + return self +end + +function AI_ESCORT:SetEscortMenuHoldAtLeaderPosition( EscortGroup ) + + for _, HoldAtLeaderPosition in pairs( self.Menu.HoldAtLeaderPosition ) do + if EscortGroup:IsAir() then + + local EscortGroupName = EscortGroup:GetName() + local EscortMenuReportNavigation = MENU_GROUP:New( self.PlayerGroup, "Navigation", EscortGroup.EscortMenu ) + + local EscortMenuHoldAtLeaderPosition = MENU_GROUP_COMMAND + :New( + self.PlayerGroup, + HoldAtLeaderPosition.MenuText, + EscortMenuReportNavigation, + AI_ESCORT._HoldPosition, + self, + self.PlayerGroup, + EscortGroup, + HoldAtLeaderPosition.Height, + HoldAtLeaderPosition.Speed + ) + end + end + + return self +end + --- Defines a menu slot to let the escort hold at the client position and stay low with a specified height during a specified time in seconds. -- This menu will appear under **Navigation**. -- @param #AI_ESCORT self @@ -650,10 +862,6 @@ end function AI_ESCORT:MenuHoldAtLeaderPosition( Height, Speed, MenuTextFormat ) self:F( { Height, Speed, MenuTextFormat } ) - if not self.FlightMenuHold then - self.FlightMenuHold = MENU_GROUP:New( self.EscortUnit:GetGroup(), "Hold position", self.FlightMenu ) - end - if not Height then Height = 30 end @@ -677,73 +885,11 @@ function AI_ESCORT:MenuHoldAtLeaderPosition( Height, Speed, MenuTextFormat ) end end - if not self.FlightMenuHoldAtLeaderPosition then - self.FlightMenuHoldAtLeaderPosition = {} - end - - self.FlightMenuHoldAtLeaderPosition[#self.FlightMenuHoldAtLeaderPosition+1] = MENU_GROUP_COMMAND - :New( - self.EscortUnit:GetGroup(), - MenuText, - self.FlightMenuHold, - AI_ESCORT._FlightHoldPosition, - self, - self.EscortUnit:GetGroup(), - Height, - Speed - ) - - self.EscortGroupSet:ForEachGroupAlive( - --- @param Core.Group#GROUP EscortGroup - function( EscortGroup ) - if EscortGroup:IsAir() then - - if not EscortGroup.EscortMenuHold then - EscortGroup.EscortMenuHold = MENU_GROUP:New( self.EscortUnit:GetGroup(), "Hold position", EscortGroup.EscortMenu ) - end - - if not Height then - Height = 30 - end - - if not Speed then - Speed = 0 - end - - local MenuText = "" - if not MenuTextFormat then - if Speed == 0 then - MenuText = string.format( "Rejoin and hold at %d meter", Height ) - else - MenuText = string.format( "Rejoin and hold at %d meter at %d", Height, Speed ) - end - else - if Speed == 0 then - MenuText = string.format( MenuTextFormat, Height ) - else - MenuText = string.format( MenuTextFormat, Height, Speed ) - end - end - - if not self.EscortMenuHoldAtLeaderPosition then - self.EscortMenuHoldAtLeaderPosition = {} - end - - self.EscortMenuHoldAtLeaderPosition[#self.EscortMenuHoldAtLeaderPosition+1] = MENU_GROUP_COMMAND - :New( - self.EscortUnit:GetGroup(), - MenuText, - EscortGroup.EscortMenuHold, - AI_ESCORT._HoldPosition, - self, - self.EscortUnit:GetGroup(), - EscortGroup, - Height, - Speed - ) - end - end - ) + self.Menu.HoldAtLeaderPosition = self.Menu.HoldAtLeaderPosition or {} + self.Menu.HoldAtLeaderPosition[#self.Menu.HoldAtLeaderPosition+1] = {} + self.Menu.HoldAtLeaderPosition[#self.Menu.HoldAtLeaderPosition].Height = Height + self.Menu.HoldAtLeaderPosition[#self.Menu.HoldAtLeaderPosition].Speed = Speed + self.Menu.HoldAtLeaderPosition[#self.Menu.HoldAtLeaderPosition].MenuText = MenuText return self end @@ -760,7 +906,7 @@ function AI_ESCORT:MenuScanForTargets( Height, Seconds, MenuTextFormat ) if self.EscortGroup:IsAir() then if not self.EscortMenuScan then - self.EscortMenuScan = MENU_GROUP:New( self.EscortUnit:GetGroup(), "Scan for targets", self.EscortMenu ) + self.EscortMenuScan = MENU_GROUP:New( self.PlayerGroup, "Scan for targets", self.EscortMenu ) end if not Height then @@ -792,7 +938,7 @@ function AI_ESCORT:MenuScanForTargets( Height, Seconds, MenuTextFormat ) self.EscortMenuScanForTargets[#self.EscortMenuScanForTargets+1] = MENU_GROUP_COMMAND :New( - self.EscortUnit:GetGroup(), + self.PlayerGroup, MenuText, self.EscortMenuScan, AI_ESCORT._ScanTargets, @@ -805,6 +951,41 @@ function AI_ESCORT:MenuScanForTargets( Height, Seconds, MenuTextFormat ) end +function AI_ESCORT:SetFlightMenuFlare() + + for _, MenuFlare in pairs( self.Menu.Flare) do + local FlightMenuReportNavigation = MENU_GROUP:New( self.PlayerGroup, "Navigation", self.FlightMenu ) + local FlightMenuFlare = MENU_GROUP:New( self.PlayerGroup, MenuFlare.MenuText, FlightMenuReportNavigation ) + + local FlightMenuFlareGreenFlight = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Release green flare", FlightMenuFlare, AI_ESCORT._FlightFlare, self, FLARECOLOR.Green, "Released a green flare!" ) + local FlightMenuFlareRedFlight = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Release red flare", FlightMenuFlare, AI_ESCORT._FlightFlare, self, FLARECOLOR.Red, "Released a red flare!" ) + local FlightMenuFlareWhiteFlight = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Release white flare", FlightMenuFlare, AI_ESCORT._FlightFlare, self, FLARECOLOR.White, "Released a white flare!" ) + local FlightMenuFlareYellowFlight = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Release yellow flare", FlightMenuFlare, AI_ESCORT._FlightFlare, self, FLARECOLOR.Yellow, "Released a yellow flare!" ) + end + + return self +end + +function AI_ESCORT:SetEscortMenuFlare( EscortGroup ) + + for _, MenuFlare in pairs( self.Menu.Flare) do + if EscortGroup:IsAir() then + + local EscortGroupName = EscortGroup:GetName() + local EscortMenuReportNavigation = MENU_GROUP:New( self.PlayerGroup, "Navigation", EscortGroup.EscortMenu ) + local EscortMenuFlare = MENU_GROUP:New( self.PlayerGroup, MenuFlare.MenuText, EscortMenuReportNavigation ) + + local EscortMenuFlareGreen = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Release green flare", EscortMenuFlare, AI_ESCORT._Flare, self, EscortGroup, FLARECOLOR.Green, "Released a green flare!" ) + local EscortMenuFlareRed = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Release red flare", EscortMenuFlare, AI_ESCORT._Flare, self, EscortGroup, FLARECOLOR.Red, "Released a red flare!" ) + local EscortMenuFlareWhite = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Release white flare", EscortMenuFlare, AI_ESCORT._Flare, self, EscortGroup, FLARECOLOR.White, "Released a white flare!" ) + local EscortMenuFlareYellow = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Release yellow flare", EscortMenuFlare, AI_ESCORT._Flare, self, EscortGroup, FLARECOLOR.Yellow, "Released a yellow flare!" ) + end + end + + return self +end + + --- Defines a menu slot to let the escort disperse a flare in a certain color. -- This menu will appear under **Navigation**. @@ -815,10 +996,6 @@ end function AI_ESCORT:MenuFlare( MenuTextFormat ) self:F() - if not self.FlightMenuReportNavigation then - self.FlightMenuReportNavigation = MENU_GROUP:New( self.EscortUnit:GetGroup(), "Navigation", self.FlightMenu ) - end - local MenuText = "" if not MenuTextFormat then MenuText = "Flare" @@ -826,41 +1003,52 @@ function AI_ESCORT:MenuFlare( MenuTextFormat ) MenuText = MenuTextFormat end - if not self.FlightMenuFlare then - self.FlightMenuFlare = MENU_GROUP:New( self.EscortUnit:GetGroup(), MenuText, self.FlightMenuReportNavigation ) - self.FlightMenuFlareGreen = MENU_GROUP_COMMAND:New( self.EscortUnit:GetGroup(), "Release green flare", self.FlightMenuFlare, AI_ESCORT._FlightFlare, self, FLARECOLOR.Green, "Released a green flare!" ) - self.FlightMenuFlareRed = MENU_GROUP_COMMAND:New( self.EscortUnit:GetGroup(), "Release red flare", self.FlightMenuFlare, AI_ESCORT._FlightFlare, self, FLARECOLOR.Red, "Released a red flare!" ) - self.FlightMenuFlareWhite = MENU_GROUP_COMMAND:New( self.EscortUnit:GetGroup(), "Release white flare", self.FlightMenuFlare, AI_ESCORT._FlightFlare, self, FLARECOLOR.White, "Released a white flare!" ) - self.FlightMenuFlareYellow = MENU_GROUP_COMMAND:New( self.EscortUnit:GetGroup(), "Release yellow flare", self.FlightMenuFlare, AI_ESCORT._FlightFlare, self, FLARECOLOR.Yellow, "Released a yellow flare!" ) - end - - self.EscortGroupSet:ForEachGroupAlive( - --- @param Core.Group#GROUP EscortGroup - function( EscortGroup ) - if not EscortGroup.EscortMenuReportNavigation then - EscortGroup.EscortMenuReportNavigation = MENU_GROUP:New( self.EscortUnit:GetGroup(), "Navigation", EscortGroup.EscortMenu ) - end - - local MenuText = "" - if not MenuTextFormat then - MenuText = "Flare" - else - MenuText = MenuTextFormat - end - - if not EscortGroup.EscortMenuFlare then - EscortGroup.EscortMenuFlare = MENU_GROUP:New( self.EscortUnit:GetGroup(), MenuText, EscortGroup.EscortMenuReportNavigation ) - EscortGroup.EscortMenuFlareGreen = MENU_GROUP_COMMAND:New( self.EscortUnit:GetGroup(), "Release green flare", EscortGroup.EscortMenuFlare, AI_ESCORT._Flare, self, EscortGroup, FLARECOLOR.Green, "Released a green flare!" ) - EscortGroup.EscortMenuFlareRed = MENU_GROUP_COMMAND:New( self.EscortUnit:GetGroup(), "Release red flare", EscortGroup.EscortMenuFlare, AI_ESCORT._Flare, self, EscortGroup, FLARECOLOR.Red, "Released a red flare!" ) - EscortGroup.EscortMenuFlareWhite = MENU_GROUP_COMMAND:New( self.EscortUnit:GetGroup(), "Release white flare", EscortGroup.EscortMenuFlare, AI_ESCORT._Flare, self, EscortGroup, FLARECOLOR.White, "Released a white flare!" ) - EscortGroup.EscortMenuFlareYellow = MENU_GROUP_COMMAND:New( self.EscortUnit:GetGroup(), "Release yellow flare", EscortGroup.EscortMenuFlare, AI_ESCORT._Flare, self, EscortGroup, FLARECOLOR.Yellow, "Released a yellow flare!" ) - end - end - ) + self.Menu.Flare = self.Menu.Flare or {} + self.Menu.Flare[#self.Menu.Flare+1] = {} + self.Menu.Flare[#self.Menu.Flare].MenuText = MenuText return self end + +function AI_ESCORT:SetFlightMenuSmoke() + + for _, MenuSmoke in pairs( self.Menu.Smoke) do + local FlightMenuReportNavigation = MENU_GROUP:New( self.PlayerGroup, "Navigation", self.FlightMenu ) + local FlightMenuSmoke = MENU_GROUP:New( self.PlayerGroup, MenuSmoke.MenuText, FlightMenuReportNavigation ) + + local FlightMenuSmokeGreenFlight = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Release green smoke", FlightMenuSmoke, AI_ESCORT._FlightSmoke, self, SMOKECOLOR.Green, "Releasing green smoke!" ) + local FlightMenuSmokeRedFlight = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Release red smoke", FlightMenuSmoke, AI_ESCORT._FlightSmoke, self, SMOKECOLOR.Red, "Releasing red smoke!" ) + local FlightMenuSmokeWhiteFlight = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Release white smoke", FlightMenuSmoke, AI_ESCORT._FlightSmoke, self, SMOKECOLOR.White, "Releasing white smoke!" ) + local FlightMenuSmokeOrangeFlight = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Release orange smoke", FlightMenuSmoke, AI_ESCORT._FlightSmoke, self, SMOKECOLOR.Orange, "Releasing orange smoke!" ) + local FlightMenuSmokeBlueFlight = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Release blue smoke", FlightMenuSmoke, AI_ESCORT._FlightSmoke, self, SMOKECOLOR.Blue, "Releasing blue smoke!" ) + end + + return self +end + + +function AI_ESCORT:SetEscortMenuSmoke( EscortGroup ) + + for _, MenuSmoke in pairs( self.Menu.Smoke) do + if EscortGroup:IsAir() then + + local EscortGroupName = EscortGroup:GetName() + local EscortMenuReportNavigation = MENU_GROUP:New( self.PlayerGroup, "Navigation", EscortGroup.EscortMenu ) + local EscortMenuSmoke = MENU_GROUP:New( self.PlayerGroup, MenuSmoke.MenuText, EscortMenuReportNavigation ) + + local EscortMenuSmokeGreen = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Release green smoke", EscortMenuSmoke, AI_ESCORT._Smoke, self, EscortGroup, SMOKECOLOR.Green, "Releasing green smoke!" ) + local EscortMenuSmokeRed = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Release red smoke", EscortMenuSmoke, AI_ESCORT._Smoke, self, EscortGroup, SMOKECOLOR.Red, "Releasing red smoke!" ) + local EscortMenuSmokeWhite = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Release white smoke", EscortMenuSmoke, AI_ESCORT._Smoke, self, EscortGroup, SMOKECOLOR.White, "Releasing white smoke!" ) + local EscortMenuSmokeOrange = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Release orange smoke", EscortMenuSmoke, AI_ESCORT._Smoke, self, EscortGroup, SMOKECOLOR.Orange, "Releasing orange smoke!" ) + local EscortMenuSmokeBlue = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Release blue smoke", EscortMenuSmoke, AI_ESCORT._Smoke, self, EscortGroup, SMOKECOLOR.Blue, "Releasing blue smoke!" ) + end + end + + return self +end + + --- Defines a menu slot to let the escort disperse a smoke in a certain color. -- This menu will appear under **Navigation**. -- Note that smoke menu options will only be displayed for ships and ground units. Not for air units. @@ -871,10 +1059,6 @@ end function AI_ESCORT:MenuSmoke( MenuTextFormat ) self:F() - if not self.FlightMenuReportNavigation then - self.FlightMenuReportNavigation = MENU_GROUP:New( self.EscortUnit:GetGroup(), "Navigation", self.FlightMenu ) - end - local MenuText = "" if not MenuTextFormat then MenuText = "Smoke" @@ -882,115 +1066,94 @@ function AI_ESCORT:MenuSmoke( MenuTextFormat ) MenuText = MenuTextFormat end - if not self.FlightMenuSmoke then - self.FlightMenuSmoke = MENU_GROUP:New( self.EscortUnit:GetGroup(), "Smoke", self.FlightMenuReportNavigation ) - self.FlightMenuSmokeGreen = MENU_GROUP_COMMAND:New( self.EscortUnit:GetGroup(), "Release green smoke", self.FlightMenuSmoke, AI_ESCORT._FlightSmoke, self, SMOKECOLOR.Green, "Releasing green smoke!" ) - self.FlightMenuSmokeRed = MENU_GROUP_COMMAND:New( self.EscortUnit:GetGroup(), "Release red smoke", self.FlightMenuSmoke, AI_ESCORT._FlightSmoke, self, SMOKECOLOR.Red, "Releasing red smoke!" ) - self.FlightMenuSmokeWhite = MENU_GROUP_COMMAND:New( self.EscortUnit:GetGroup(), "Release white smoke", self.FlightMenuSmoke, AI_ESCORT._FlightSmoke, self, SMOKECOLOR.White, "Releasing white smoke!" ) - self.FlightMenuSmokeOrange = MENU_GROUP_COMMAND:New( self.EscortUnit:GetGroup(), "Release orange smoke", self.FlightMenuSmoke, AI_ESCORT._FlightSmoke, self, SMOKECOLOR.Orange, "Releasing orange smoke!" ) - self.FlightMenuSmokeBlue = MENU_GROUP_COMMAND:New( self.EscortUnit:GetGroup(), "Release blue smoke", self.FlightMenuSmoke, AI_ESCORT._FlightSmoke, self, SMOKECOLOR.Blue, "Releasing blue smoke!" ) - end - - self.EscortGroupSet:ForEachGroupAlive( - --- @param Core.Group#GROUP EscortGroup - function( EscortGroup ) - if not EscortGroup:IsAir() then - if not EscortGroup.EscortMenuReportNavigation then - EscortGroup.EscortMenuReportNavigation = MENU_GROUP:New( self.EscortUnit:GetGroup(), "Navigation", EscortGroup.EscortMenu ) - end - - local MenuText = "" - if not MenuTextFormat then - MenuText = "Smoke" - else - MenuText = MenuTextFormat - end - - if not EscortGroup.EscortMenuSmoke then - EscortGroup.EscortMenuSmoke = MENU_GROUP:New( self.EscortUnit:GetGroup(), "Smoke", EscortGroup.EscortMenuReportNavigation ) - EscortGroup.EscortMenuSmokeGreen = MENU_GROUP_COMMAND:New( self.EscortUnit:GetGroup(), "Release green smoke", EscortGroup.EscortMenuSmoke, AI_ESCORT._Smoke, self, EscortGroup, SMOKECOLOR.Green, "Releasing green smoke!" ) - EscortGroup.EscortMenuSmokeRed = MENU_GROUP_COMMAND:New( self.EscortUnit:GetGroup(), "Release red smoke", EscortGroup.EscortMenuSmoke, AI_ESCORT._Smoke, self, EscortGroup, SMOKECOLOR.Red, "Releasing red smoke!" ) - EscortGroup.EscortMenuSmokeWhite = MENU_GROUP_COMMAND:New( self.EscortUnit:GetGroup(), "Release white smoke", EscortGroup.EscortMenuSmoke, AI_ESCORT._Smoke, self, EscortGroup, SMOKECOLOR.White, "Releasing white smoke!" ) - EscortGroup.EscortMenuSmokeOrange = MENU_GROUP_COMMAND:New( self.EscortUnit:GetGroup(), "Release orange smoke", EscortGroup.EscortMenuSmoke, AI_ESCORT._Smoke, self, EscortGroup, SMOKECOLOR.Orange, "Releasing orange smoke!" ) - EscortGroup.EscortMenuSmokeBlue = MENU_GROUP_COMMAND:New( self.EscortUnit:GetGroup(), "Release blue smoke", EscortGroup.EscortMenuSmoke, AI_ESCORT._Smoke, self, EscortGroup, SMOKECOLOR.Blue, "Releasing blue smoke!" ) - end - end - end - ) + self.Menu.Smoke = self.Menu.Smoke or {} + self.Menu.Smoke[#self.Menu.Smoke+1] = {} + self.Menu.Smoke[#self.Menu.Smoke].MenuText = MenuText return self end + +function AI_ESCORT:SetFlightMenuTargets() + + for _, MenuTargets in pairs( self.Menu.Targets) do + local FlightMenuReportTargets = MENU_GROUP:New( self.PlayerGroup, "Report targets", self.FlightMenu ) + + -- Report Targets + local FlightMenuReportTargetsNow = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Report targets now!", FlightMenuReportTargets, AI_ESCORT._FlightReportNearbyTargetsNow, self ) + local FlightMenuReportTargetsOn = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Report targets on", FlightMenuReportTargets, AI_ESCORT._FlightSwitchReportNearbyTargets, self, true ) + local FlightMenuReportTargetsOff = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Report targets off", FlightMenuReportTargets, AI_ESCORT._FlightSwitchReportNearbyTargets, self, false ) + + -- Attack Targets + local FlightMenuAttackNearbyTargets = MENU_GROUP:New( self.PlayerGroup, "Attack targets", self.FlightMenu ) + + MenuTargets.FlightReportTargetsScheduler = SCHEDULER:New( self, self._FlightReportTargetsScheduler, {}, MenuTargets.Interval, MenuTargets.Interval ) + end + + return self +end + + +function AI_ESCORT:SetEscortMenuTargets( EscortGroup ) + + for _, MenuTargets in pairs( self.Menu.Targets) do + if EscortGroup:IsAir() then + local EscortGroupName = EscortGroup:GetName() + --local EscortMenuReportTargets = MENU_GROUP:New( self.PlayerGroup, "Report targets", EscortGroup.EscortMenu ) + + -- Report Targets + EscortGroup.EscortMenuReportNearbyTargetsNow = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Report targets", EscortGroup.EscortMenu, AI_ESCORT._ReportNearbyTargetsNow, self, EscortGroup, true ) + --EscortGroup.EscortMenuReportNearbyTargetsOn = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Report targets on", EscortGroup.EscortMenuReportNearbyTargets, AI_ESCORT._SwitchReportNearbyTargets, self, EscortGroup, true ) + --EscortGroup.EscortMenuReportNearbyTargetsOff = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Report targets off", EscortGroup.EscortMenuReportNearbyTargets, AI_ESCORT._SwitchReportNearbyTargets, self, EscortGroup, false ) + + -- Attack Targets + --local EscortMenuAttackTargets = MENU_GROUP:New( self.PlayerGroup, "Attack targets", EscortGroup.EscortMenu ) + + EscortGroup.ReportTargetsScheduler = SCHEDULER:New( self, self._ReportTargetsScheduler, { EscortGroup }, 1, MenuTargets.Interval ) + EscortGroup.ResumeScheduler = SCHEDULER:New( self, self._ResumeScheduler, { EscortGroup }, 1, 60 ) + end + end + + return self +end + + + --- Defines a menu slot to let the escort report their current detected targets with a specified time interval in seconds. -- This menu will appear under **Report targets**. -- Note that if a report targets menu is not specified, no targets will be detected by the escort, and the attack and assisted attack menus will not be displayed. -- @param #AI_ESCORT self -- @param DCS#Time Seconds Optional parameter that lets the escort report their current detected targets after specified time interval in seconds. The default time is 30 seconds. -- @return #AI_ESCORT -function AI_ESCORT:MenuReportTargets( Seconds ) +function AI_ESCORT:MenuTargets( Seconds ) self:F( { Seconds } ) - if not self.FlightMenuReportNearbyTargets then - self.FlightMenuReportNearbyTargets = MENU_GROUP:New( self.EscortUnit:GetGroup(), "Report targets", self.FlightMenu ) - end - if not Seconds then Seconds = 30 end + + self.Menu.Targets = self.Menu.Targets or {} + self.Menu.Targets[#self.Menu.Targets+1] = {} + self.Menu.Targets[#self.Menu.Targets].Interval = Seconds - local timer = 1 - - -- Report Targets - self.FlightMenuReportNearbyTargetsNow = MENU_GROUP_COMMAND:New( self.EscortUnit:GetGroup(), "Report targets now!", self.FlightMenuReportNearbyTargets, AI_ESCORT._FlightReportNearbyTargetsNow, self ) - self.FlightMenuReportNearbyTargetsOn = MENU_GROUP_COMMAND:New( self.EscortUnit:GetGroup(), "Report targets on", self.FlightMenuReportNearbyTargets, AI_ESCORT._FlightSwitchReportNearbyTargets, self, true ) - self.FlightMenuReportNearbyTargetsOff = MENU_GROUP_COMMAND:New( self.EscortUnit:GetGroup(), "Report targets off", self.FlightMenuReportNearbyTargets, AI_ESCORT._FlightSwitchReportNearbyTargets, self, false ) - - -- Attack Targets - self.FlightMenuAttackNearbyTargets = MENU_GROUP:New( self.EscortUnit:GetGroup(), "Attack targets", self.FlightMenu ) - - self.FlightReportTargetsScheduler = SCHEDULER:New( self, self._FlightReportTargetsScheduler, {}, 5, Seconds ) - - self.EscortGroupSet:ForEachGroupAlive( - --- @param Core.Group#GROUP EscortGroup - function( EscortGroup ) - if EscortGroup:IsAir() then - if not EscortGroup.EscortMenuReportNearbyTargets then - EscortGroup.EscortMenuReportNearbyTargets = MENU_GROUP:New( self.EscortUnit:GetGroup(), "Report targets", EscortGroup.EscortMenu ) - end - - -- Report Targets - EscortGroup.EscortMenuReportNearbyTargetsNow = MENU_GROUP_COMMAND:New( self.EscortUnit:GetGroup(), "Report targets now!", EscortGroup.EscortMenuReportNearbyTargets, AI_ESCORT._ReportNearbyTargetsNow, self, EscortGroup ) - EscortGroup.EscortMenuReportNearbyTargetsOn = MENU_GROUP_COMMAND:New( self.EscortUnit:GetGroup(), "Report targets on", EscortGroup.EscortMenuReportNearbyTargets, AI_ESCORT._SwitchReportNearbyTargets, self, EscortGroup, true ) - EscortGroup.EscortMenuReportNearbyTargetsOff = MENU_GROUP_COMMAND:New( self.EscortUnit:GetGroup(), "Report targets off", EscortGroup.EscortMenuReportNearbyTargets, AI_ESCORT._SwitchReportNearbyTargets, self, EscortGroup, false ) - - -- Attack Targets - EscortGroup.EscortMenuAttackNearbyTargets = MENU_GROUP:New( self.EscortUnit:GetGroup(), "Attack targets", EscortGroup.EscortMenu ) - - - EscortGroup.ReportTargetsScheduler = SCHEDULER:New( self, self._ReportTargetsScheduler, { EscortGroup }, timer, Seconds ) - timer=timer+1 - end - end - ) - return self end --- Defines a menu slot to let the escort attack its detected targets using assisted attack from another escort joined also with the client. -- This menu will appear under **Request assistance from**. --- Note that this method needs to be preceded with the method MenuReportTargets. +-- Note that this method needs to be preceded with the method MenuTargets. -- @param #AI_ESCORT self -- @return #AI_ESCORT function AI_ESCORT:MenuAssistedAttack() self:F() - self.EscortGroupSet:ForEachGroupAlive( + self.EscortGroupSet:ForSomeGroupAlive( --- @param Core.Group#GROUP EscortGroup function( EscortGroup ) if not EscortGroup:IsAir() then -- Request assistance from other escorts. -- This is very useful to let f.e. an escorting ship attack a target detected by an escorting plane... - self.EscortMenuTargetAssistance = MENU_GROUP:New( self.EscortUnit:GetGroup(), "Request assistance from", EscortGroup.EscortMenu ) + self.EscortMenuTargetAssistance = MENU_GROUP:New( self.PlayerGroup, "Request assistance from", EscortGroup.EscortMenu ) end end ) @@ -998,69 +1161,117 @@ function AI_ESCORT:MenuAssistedAttack() return self end +function AI_ESCORT:SetFlightMenuROE() + + for _, MenuROE in pairs( self.Menu.ROE) do + local FlightMenuROE = MENU_GROUP:New( self.PlayerGroup, "Rule Of Engagement", self.FlightMenu ) + + local FlightMenuROEHoldFire = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Hold fire", FlightMenuROE, AI_ESCORT._FlightROEHoldFire, self, "Holding weapons!" ) + local FlightMenuROEReturnFire = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Return fire", FlightMenuROE, AI_ESCORT._FlightROEReturnFire, self, "Returning fire!" ) + local FlightMenuROEOpenFire = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Open Fire", FlightMenuROE, AI_ESCORT._FlightROEOpenFire, self, "Open fire at designated targets!" ) + local FlightMenuROEWeaponFree = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Engage all targets", FlightMenuROE, AI_ESCORT._FlightROEWeaponFree, self, "Engaging all targets!" ) + end + + return self +end + + +function AI_ESCORT:SetEscortMenuROE( EscortGroup ) + + for _, MenuROE in pairs( self.Menu.ROE) do + if EscortGroup:IsAir() then + + local EscortGroupName = EscortGroup:GetName() + local EscortMenuROE = MENU_GROUP:New( self.PlayerGroup, "Rule Of Engagement", EscortGroup.EscortMenu ) + + if EscortGroup:OptionROEHoldFirePossible() then + local EscortMenuROEHoldFire = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Hold fire", EscortMenuROE, AI_ESCORT._ROE, self, EscortGroup, EscortGroup.OptionROEHoldFire, "Holding weapons!" ) + end + if EscortGroup:OptionROEReturnFirePossible() then + local EscortMenuROEReturnFire = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Return fire", EscortMenuROE, AI_ESCORT._ROE, self, EscortGroup, EscortGroup.OptionROEReturnFire, "Returning fire!" ) + end + if EscortGroup:OptionROEOpenFirePossible() then + EscortGroup.EscortMenuROEOpenFire = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Open Fire", EscortMenuROE, AI_ESCORT._ROE, self, EscortGroup, EscortGroup.OptionROEOpenFire, "Opening fire on designated targets!!" ) + end + if EscortGroup:OptionROEWeaponFreePossible() then + EscortGroup.EscortMenuROEWeaponFree = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Engage all targets", EscortMenuROE, AI_ESCORT._ROE, self, EscortGroup, EscortGroup.OptionROEWeaponFree, "Opening fire on targets of opportunity!" ) + end + end + end + + return self +end + + --- Defines a menu to let the escort set its rules of engagement. -- All rules of engagement will appear under the menu **ROE**. -- @param #AI_ESCORT self -- @return #AI_ESCORT -function AI_ESCORT:MenuROE( MenuTextFormat ) - self:F( MenuTextFormat ) +function AI_ESCORT:MenuROE() + self:F() - self.EscortGroupSet:ForEachGroupAlive( - --- @param Core.Group#GROUP EscortGroup - function( EscortGroup ) - if not EscortGroup.EscortMenuROE then - -- Rules of Engagement - EscortGroup.EscortMenuROE = MENU_GROUP:New( self.EscortUnit:GetGroup(), "ROE", EscortGroup.EscortMenu ) - if EscortGroup:OptionROEHoldFirePossible() then - EscortGroup.EscortMenuROEHoldFire = MENU_GROUP_COMMAND:New( self.EscortUnit:GetGroup(), "Hold Fire", EscortGroup.EscortMenuROE, AI_ESCORT._ROE, self, EscortGroup, EscortGroup:OptionROEHoldFire(), "Holding weapons!" ) - end - if EscortGroup:OptionROEReturnFirePossible() then - EscortGroup.EscortMenuROEReturnFire = MENU_GROUP_COMMAND:New( self.EscortUnit:GetGroup(), "Return Fire", EscortGroup.EscortMenuROE, AI_ESCORT._ROE, self, EscortGroup, EscortGroup:OptionROEReturnFire(), "Returning fire!" ) - end - if EscortGroup:OptionROEOpenFirePossible() then - EscortGroup.EscortMenuROEOpenFire = MENU_GROUP_COMMAND:New( self.EscortUnit:GetGroup(), "Open Fire", EscortGroup.EscortMenuROE, AI_ESCORT._ROE, self, EscortGroup, EscortGroup:OptionROEOpenFire(), "Opening fire on designated targets!!" ) - end - if EscortGroup:OptionROEWeaponFreePossible() then - EscortGroup.EscortMenuROEWeaponFree = MENU_GROUP_COMMAND:New( self.EscortUnit:GetGroup(), "Weapon Free", EscortGroup.EscortMenuROE, AI_ESCORT._ROE, self, EscortGroup, EscortGroup:OptionROEWeaponFree(), "Opening fire on targets of opportunity!" ) - end - end - end - ) + self.Menu.ROE = self.Menu.ROE or {} + self.Menu.ROE[#self.Menu.ROE+1] = {} return self end +function AI_ESCORT:SetFlightMenuROT() + + for _, MenuROT in pairs( self.Menu.ROT) do + local FlightMenuROT = MENU_GROUP:New( self.PlayerGroup, "Reaction On Threat", self.FlightMenu ) + + local FlightMenuROTNoReaction = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Fight until death", FlightMenuROT, AI_ESCORT._FlightROTNoReaction, self, "Fighting until death!" ) + local FlightMenuROTPassiveDefense = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Use flares, chaff and jammers", FlightMenuROT, AI_ESCORT._FlightROTPassiveDefense, self, "Defending using jammers, chaff and flares!" ) + local FlightMenuROTEvadeFire = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Open fire", FlightMenuROT, AI_ESCORT._FlightROTEvadeFire, self, "Evading on enemy fire!" ) + local FlightMenuROTVertical = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Avoid radar and evade fire", FlightMenuROT, AI_ESCORT._FlightROTVertical, self, "Evading on enemy fire with vertical manoeuvres!" ) + end + + return self +end + + +function AI_ESCORT:SetEscortMenuROT( EscortGroup ) + + for _, MenuROT in pairs( self.Menu.ROT) do + if EscortGroup:IsAir() then + + local EscortGroupName = EscortGroup:GetName() + local EscortMenuROT = MENU_GROUP:New( self.PlayerGroup, "Reaction On Threat", EscortGroup.EscortMenu ) + + if not EscortGroup.EscortMenuEvasion then + -- Reaction to Threats + if EscortGroup:OptionROTNoReactionPossible() then + local EscortMenuEvasionNoReaction = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Fight until death", EscortMenuROT, AI_ESCORT._ROT, self, EscortGroup, EscortGroup.OptionROTNoReaction, "Fighting until death!" ) + end + if EscortGroup:OptionROTPassiveDefensePossible() then + local EscortMenuEvasionPassiveDefense = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Use flares, chaff and jammers", EscortMenuROT, AI_ESCORT._ROT, self, EscortGroup, EscortGroup.OptionROTPassiveDefense, "Defending using jammers, chaff and flares!" ) + end + if EscortGroup:OptionROTEvadeFirePossible() then + local EscortMenuEvasionEvadeFire = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Open fire", EscortMenuROT, AI_ESCORT._ROT, self, EscortGroup, EscortGroup.OptionROTEvadeFire, "Evading on enemy fire!" ) + end + if EscortGroup:OptionROTVerticalPossible() then + local EscortMenuOptionEvasionVertical = MENU_GROUP_COMMAND:New( self.PlayerGroup, "Avoid radar and evade fire", EscortMenuROT, AI_ESCORT._ROT, self, EscortGroup, EscortGroup.OptionROTVertical, "Evading on enemy fire with vertical manoeuvres!" ) + end + end + end + end + + return self +end + + + --- Defines a menu to let the escort set its evasion when under threat. -- All rules of engagement will appear under the menu **Evasion**. -- @param #AI_ESCORT self -- @return #AI_ESCORT -function AI_ESCORT:MenuEvasion( MenuTextFormat ) +function AI_ESCORT:MenuROT( MenuTextFormat ) self:F( MenuTextFormat ) - self.EscortGroupSet:ForEachGroupAlive( - --- @param Core.Group#GROUP EscortGroup - function( EscortGroup ) - if EscortGroup:IsAir() then - if not EscortGroup.EscortMenuEvasion then - -- Reaction to Threats - EscortGroup.EscortMenuEvasion = MENU_GROUP:New( self.EscortUnit:GetGroup(), "Evasion", EscortGroup.EscortMenu ) - if EscortGroup:OptionROTNoReactionPossible() then - EscortGroup.EscortMenuEvasionNoReaction = MENU_GROUP_COMMAND:New( self.EscortUnit:GetGroup(), "Fight until death", EscortGroup.EscortMenuEvasion, AI_ESCORT._ROT, self, EscortGroup, EscortGroup:OptionROTNoReaction(), "Fighting until death!" ) - end - if EscortGroup:OptionROTPassiveDefensePossible() then - EscortGroup.EscortMenuEvasionPassiveDefense = MENU_GROUP_COMMAND:New( self.EscortUnit:GetGroup(), "Use flares, chaff and jammers", EscortGroup.EscortMenuEvasion, AI_ESCORT._ROT, self, EscortGroup, EscortGroup:OptionROTPassiveDefense(), "Defending using jammers, chaff and flares!" ) - end - if EscortGroup:OptionROTEvadeFirePossible() then - EscortGroup.EscortMenuEvasionEvadeFire = MENU_GROUP_COMMAND:New( self.EscortUnit:GetGroup(), "Evade enemy fire", EscortGroup.EscortMenuEvasion, AI_ESCORT._ROT, self, EscortGroup, EscortGroup:OptionROTEvadeFire(), "Evading on enemy fire!" ) - end - if EscortGroup:OptionROTVerticalPossible() then - EscortGroup.EscortMenuOptionEvasionVertical = MENU_GROUP_COMMAND:New( self.EscortUnit:GetGroup(), "Go below radar and evade fire", EscortGroup.EscortMenuEvasion, AI_ESCORT._ROT, self, EscortGroup, EscortGroup:OptionROTVertical(), "Evading on enemy fire with vertical manoeuvres!" ) - end - end - end - end - ) + self.Menu.ROT = self.Menu.ROT or {} + self.Menu.ROT[#self.Menu.ROT+1] = {} return self end @@ -1069,12 +1280,12 @@ end -- All rules of engagement will appear under the menu **Resume mission from**. -- @param #AI_ESCORT self -- @return #AI_ESCORT -function AI_ESCORT:MenuResumeMission() +function AI_ESCORT:SetEscortMenuResumeMission( EscortGroup ) self:F() - if not self.EscortMenuResumeMission then - -- Mission Resume Menu Root - self.EscortMenuResumeMission = MENU_GROUP:New( self.EscortUnit:GetGroup(), "Resume mission from", self.EscortMenu ) + if EscortGroup:IsAir() then + local EscortGroupName = EscortGroup:GetName() + EscortGroup.EscortMenuResumeMission = MENU_GROUP:New( self.PlayerGroup, "Resume from", EscortGroup.EscortMenu ) end return self @@ -1088,11 +1299,11 @@ end -- @param #number OrbitSeconds function AI_ESCORT:_HoldPosition( OrbitGroup, EscortGroup, OrbitHeight, OrbitSeconds ) - local EscortUnit = self.EscortUnit + local EscortUnit = self.PlayerUnit local OrbitUnit = OrbitGroup:GetUnit(1) -- Wrapper.Unit#UNIT - self:ReleaseFormation( EscortGroup ) + self:ModeMission( EscortGroup ) local PointFrom = {} local GroupVec3 = EscortGroup:GetUnit(1):GetVec3() @@ -1131,7 +1342,7 @@ end -- @param #number OrbitSeconds function AI_ESCORT:_FlightHoldPosition( OrbitGroup, OrbitHeight, OrbitSeconds ) - local EscortUnit = self.EscortUnit + local EscortUnit = self.PlayerUnit self.EscortGroupSet:ForEachGroupAlive( --- @param Core.Group#GROUP EscortGroup @@ -1151,14 +1362,15 @@ end function AI_ESCORT:_JoinUp( EscortGroup ) - local EscortUnit = self.EscortUnit + local EscortUnit = self.PlayerUnit - self:JoinFormation( EscortGroup ) - EscortGroup.EscortMode = AI_ESCORT.MODE.FOLLOW + self:ModeFormation( EscortGroup ) + + EscortGroup:MessageTypeToGroup( "Joining up!", MESSAGE.Type.Information, EscortUnit:GetGroup() ) end -function AI_ESCORT:_FlightJoinUp( EscortGroup ) +function AI_ESCORT:_FlightJoinUp() self.EscortGroupSet:ForEachGroupAlive( --- @param Core.Group#GROUP EscortGroup @@ -1228,7 +1440,7 @@ end function AI_ESCORT:_Flare( EscortGroup, Color, Message ) - local EscortUnit = self.EscortUnit + local EscortUnit = self.PlayerUnit EscortGroup:GetUnit(1):Flare( Color ) EscortGroup:MessageTypeToGroup( Message, MESSAGE.Type.Information, EscortUnit:GetGroup() ) @@ -1252,7 +1464,7 @@ end function AI_ESCORT:_Smoke( EscortGroup, Color, Message ) - local EscortUnit = self.EscortUnit + local EscortUnit = self.PlayerUnit EscortGroup:GetUnit(1):Smoke( Color ) EscortGroup:MessageTypeToGroup( Message, MESSAGE.Type.Information, EscortUnit:GetGroup() ) @@ -1274,7 +1486,7 @@ end function AI_ESCORT:_ReportNearbyTargetsNow( EscortGroup ) - local EscortUnit = self.EscortUnit + local EscortUnit = self.PlayerUnit self:_ReportTargetsScheduler( EscortGroup ) @@ -1283,21 +1495,14 @@ end function AI_ESCORT:_FlightReportNearbyTargetsNow() - self.EscortGroupSet:ForEachGroupAlive( - --- @param Core.Group#GROUP EscortGroup - function( EscortGroup ) - if EscortGroup:IsAir() then - self:_ReportNearbyTargetsNow( EscortGroup ) - end - end - ) + self:_FlightReportTargetsScheduler() end function AI_ESCORT:_SwitchReportNearbyTargets( EscortGroup, ReportTargets ) - local EscortUnit = self.EscortUnit + local EscortUnit = self.PlayerUnit self.ReportTargets = ReportTargets @@ -1329,7 +1534,7 @@ end function AI_ESCORT:_ScanTargets( ScanDuration ) local EscortGroup = self.EscortGroup -- Wrapper.Group#GROUP - local EscortUnit = self.EscortUnit + local EscortUnit = self.PlayerUnit self.FollowScheduler:Stop( self.FollowSchedule ) @@ -1355,20 +1560,50 @@ function AI_ESCORT:_ScanTargets( ScanDuration ) end ---- @param #AI_ESCORT self --- @param Wrapper.Group#GROUP EscortGroup +--- @param Wrapper.Group#GROUP EscortGroup +-- @param #AI_ESCORT self function AI_ESCORT.___Resume( EscortGroup, self ) - local PlayerGroup = self.EscortUnit:GetGroup() + self:F( { self=self } ) + + local PlayerGroup = self.PlayerGroup - if EscortGroup.EscortMode == AI_ESCORT.MODE.FOLLOW then - self:JoinFormation( EscortGroup ) - EscortGroup:MessageTypeToClient( "Destroyed all targets. Rejoining.", MESSAGE.Type.Information, PlayerGroup ) + EscortGroup:OptionROEHoldFire() + EscortGroup:OptionROTVertical() + + EscortGroup:SetState( EscortGroup, "Mode", EscortGroup:GetState( EscortGroup, "PreviousMode" ) ) + + if EscortGroup:GetState( EscortGroup, "Mode" ) == self.__Enum.Mode.Mission then + EscortGroup:MessageTypeToGroup( "Destroyed all targets. Resuming route.", MESSAGE.Type.Information, PlayerGroup ) + else + EscortGroup:MessageTypeToGroup( "Destroyed all targets. Rejoining formation.", MESSAGE.Type.Information, PlayerGroup ) end end +--- @param #AI_ESCORT self +-- @param Wrapper.Group#GROUP EscortGroup +-- @param #number WayPoint +function AI_ESCORT:_ResumeMission( EscortGroup, WayPoint ) + + --self.FollowScheduler:Stop( self.FollowSchedule ) + + self:ModeMission( EscortGroup ) + + local WayPoints = EscortGroup.MissionRoute + self:T( WayPoint, WayPoints ) + + for WayPointIgnore = 1, WayPoint do + table.remove( WayPoints, 1 ) + end + + EscortGroup:SetTask( EscortGroup:TaskRoute( WayPoints ), 1 ) + + EscortGroup:MessageTypeToGroup( "Resuming mission from waypoint ", MESSAGE.Type.Information, self.PlayerGroup ) +end + + --- @param #AI_ESCORT self -- @param Wrapper.Group#GROUP EscortGroup The escort group that will attack the detected item. -- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem @@ -1376,13 +1611,13 @@ function AI_ESCORT:_AttackTarget( EscortGroup, DetectedItem ) self:F( EscortGroup ) - local EscortUnit = self.EscortUnit + local EscortUnit = self.PlayerUnit - self:ReleaseFormation( EscortGroup ) + self:ModeAttack( EscortGroup ) if EscortGroup:IsAir() then EscortGroup:OptionROEOpenFire() - EscortGroup:OptionROTPassiveDefense() + EscortGroup:OptionROTVertical() EscortGroup:SetState( EscortGroup, "Escort", self ) local DetectedSet = self.Detection:GetDetectedItemSet( DetectedItem ) @@ -1400,9 +1635,9 @@ function AI_ESCORT:_AttackTarget( EscortGroup, DetectedItem ) ) Tasks[#Tasks+1] = EscortGroup:TaskCombo( AttackUnitTasks ) - Tasks[#Tasks+1] = EscortGroup:TaskFunction( "AI_ESCORT.___Resume", self, EscortGroup ) + Tasks[#Tasks+1] = EscortGroup:TaskFunction( "AI_ESCORT.___Resume", self ) - EscortGroup:SetTask( + EscortGroup:PushTask( EscortGroup:TaskCombo( Tasks ), 1 @@ -1423,7 +1658,7 @@ function AI_ESCORT:_AttackTarget( EscortGroup, DetectedItem ) end, Tasks ) - EscortGroup:SetTask( + EscortGroup:PushTask( EscortGroup:TaskCombo( Tasks ), 1 @@ -1431,8 +1666,7 @@ function AI_ESCORT:_AttackTarget( EscortGroup, DetectedItem ) end - EscortGroup:MessageTypeToGroup( "Engaging!", MESSAGE.Type.Information, EscortUnit ) - + EscortGroup:MessageTypeToGroup( "Engaging Target!", MESSAGE.Type.Information, self.PlayerGroup ) end @@ -1456,7 +1690,7 @@ end -- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem function AI_ESCORT:_AssistTarget( EscortGroup, DetectedItem ) - local EscortUnit = self.EscortUnit + local EscortUnit = self.PlayerUnit local DetectedSet = self.Detection:GetDetectedItemSet( DetectedItem ) @@ -1482,44 +1716,90 @@ function AI_ESCORT:_AssistTarget( EscortGroup, DetectedItem ) end - function AI_ESCORT:_ROE( EscortGroup, EscortROEFunction, EscortROEMessage ) + pcall( function() EscortROEFunction( EscortGroup ) end ) + EscortGroup:MessageTypeToGroup( EscortROEMessage, MESSAGE.Type.Information, self.PlayerGroup ) +end - local EscortUnit = self.EscortUnit - pcall( function() EscortROEFunction() end ) - EscortGroup:MessageTypeToGroup( EscortROEMessage, MESSAGE.Type.Information, EscortUnit:GetGroup() ) +function AI_ESCORT:_FlightROEHoldFire( EscortROEMessage ) + self.EscortGroupSet:ForEachGroupAlive( + --- @param Wrapper.Group#GROUP EscortGroup + function( EscortGroup ) + self:_ROE( EscortGroup, EscortGroup.OptionROEHoldFire, EscortROEMessage ) + end + ) +end + +function AI_ESCORT:_FlightROEOpenFire( EscortROEMessage ) + self.EscortGroupSet:ForEachGroupAlive( + --- @param Wrapper.Group#GROUP EscortGroup + function( EscortGroup ) + self:_ROE( EscortGroup, EscortGroup.OptionROEOpenFire, EscortROEMessage ) + end + ) +end + +function AI_ESCORT:_FlightROEReturnFire( EscortROEMessage ) + self.EscortGroupSet:ForEachGroupAlive( + --- @param Wrapper.Group#GROUP EscortGroup + function( EscortGroup ) + self:_ROE( EscortGroup, EscortGroup.OptionROEReturnFire, EscortROEMessage ) + end + ) +end + +function AI_ESCORT:_FlightROEWeaponFree( EscortROEMessage ) + self.EscortGroupSet:ForEachGroupAlive( + --- @param Wrapper.Group#GROUP EscortGroup + function( EscortGroup ) + self:_ROE( EscortGroup, EscortGroup.OptionROEWeaponFree, EscortROEMessage ) + end + ) end function AI_ESCORT:_ROT( EscortGroup, EscortROTFunction, EscortROTMessage ) - - local EscortUnit = self.EscortUnit - - pcall( function() EscortROTFunction() end ) - EscortGroup:MessageTypeToGroup( EscortROTMessage, MESSAGE.Type.Information, EscortUnit:GetGroup() ) + pcall( function() EscortROTFunction( EscortGroup ) end ) + EscortGroup:MessageTypeToGroup( EscortROTMessage, MESSAGE.Type.Information, self.PlayerGroup ) end -function AI_ESCORT:_ResumeMission( WayPoint ) - - local EscortGroup = self.EscortGroup - local EscortUnit = self.EscortUnit - - self.FollowScheduler:Stop( self.FollowSchedule ) - - local WayPoints = EscortGroup:GetTaskRoute() - self:T( WayPoint, WayPoints ) - - for WayPointIgnore = 1, WayPoint do - table.remove( WayPoints, 1 ) - end - - SCHEDULER:New( EscortGroup, EscortGroup.SetTask, { EscortGroup:TaskRoute( WayPoints ) }, 1 ) - - EscortGroup:MessageToClient( "Resuming mission from waypoint " .. WayPoint .. ".", 10, EscortUnit ) +function AI_ESCORT:_FlightROTNoReaction( EscortROTMessage ) + self.EscortGroupSet:ForEachGroupAlive( + --- @param Wrapper.Group#GROUP EscortGroup + function( EscortGroup ) + self:_ROT( EscortGroup, EscortGroup.OptionROTNoReaction, EscortROTMessage ) + end + ) end +function AI_ESCORT:_FlightROTPassiveDefense( EscortROTMessage ) + self.EscortGroupSet:ForEachGroupAlive( + --- @param Wrapper.Group#GROUP EscortGroup + function( EscortGroup ) + self:_ROT( EscortGroup, EscortGroup.OptionROTPassiveDefense, EscortROTMessage ) + end + ) +end + +function AI_ESCORT:_FlightROTEvadeFire( EscortROTMessage ) + self.EscortGroupSet:ForEachGroupAlive( + --- @param Wrapper.Group#GROUP EscortGroup + function( EscortGroup ) + self:_ROT( EscortGroup, EscortGroup.OptionROTEvadeFire, EscortROTMessage ) + end + ) +end + +function AI_ESCORT:_FlightROTVertical( EscortROTMessage ) + self.EscortGroupSet:ForEachGroupAlive( + --- @param Wrapper.Group#GROUP EscortGroup + function( EscortGroup ) + self:_ROT( EscortGroup, EscortGroup.OptionROTVertical, EscortROTMessage ) + end + ) +end --- Registers the waypoints -- @param #AI_ESCORT self @@ -1536,21 +1816,47 @@ function AI_ESCORT:RegisterRoute() return TaskPoints end +--- Resume Scheduler. +-- @param #AI_ESCORT self +-- @param Wrapper.Group#GROUP EscortGroup +function AI_ESCORT:_ResumeScheduler( EscortGroup ) + self:F( EscortGroup:GetName() ) + + if EscortGroup:IsAlive() and self.PlayerUnit:IsAlive() then + + + local EscortGroupName = EscortGroup:GetCallsign() + + if EscortGroup.EscortMenuResumeMission then + EscortGroup.EscortMenuResumeMission:RemoveSubMenus() + + local TaskPoints = EscortGroup.MissionRoute + + for WayPointID, WayPoint in pairs( TaskPoints ) do + local EscortVec3 = EscortGroup:GetVec3() + local Distance = ( ( WayPoint.x - EscortVec3.x )^2 + + ( WayPoint.y - EscortVec3.z )^2 + ) ^ 0.5 / 1000 + MENU_GROUP_COMMAND:New( self.PlayerGroup, "Waypoint " .. WayPointID .. " at " .. string.format( "%.2f", Distance ).. "km", EscortGroup.EscortMenuResumeMission, AI_ESCORT._ResumeMission, self, EscortGroup, WayPointID ) + end + end + end +end --- Report Targets Scheduler. -- @param #AI_ESCORT self -- @param Wrapper.Group#GROUP EscortGroup -function AI_ESCORT:_ReportTargetsScheduler( EscortGroup ) +function AI_ESCORT:_ReportTargetsScheduler( EscortGroup, Report ) self:F( EscortGroup:GetName() ) - if EscortGroup:IsAlive() and self.EscortUnit:IsAlive() then + if EscortGroup:IsAlive() and self.PlayerUnit:IsAlive() then - if true then - local EscortGroupName = EscortGroup:GetName() + local EscortGroupName = EscortGroup:GetCallsign() + + local DetectedTargetsReport = REPORT:New( "Reporting targets:\n" ) -- A new report to display the detected targets as a message to the player. - EscortGroup.EscortMenuAttackNearbyTargets:RemoveSubMenus() if EscortGroup.EscortMenuTargetAssistance then EscortGroup.EscortMenuTargetAssistance:RemoveSubMenus() @@ -1559,28 +1865,35 @@ function AI_ESCORT:_ReportTargetsScheduler( EscortGroup ) local DetectedItems = self.Detection:GetDetectedItems() local ClientEscortTargets = self.Detection - --local EscortUnit = EscortGroupData:GetUnit( 1 ) + local TimeUpdate = timer.getTime() + + local EscortMenuAttackTargets = MENU_GROUP:New( self.PlayerGroup, "Attack targets", EscortGroup.EscortMenu ) + + local DetectedTargets = false for DetectedItemIndex, DetectedItem in pairs( DetectedItems ) do - self:F( { DetectedItemIndex, DetectedItem } ) + DetectedTargets = true - local DetectedItemReportSummary = self.Detection:DetectedItemReportMenu( DetectedItem, EscortGroup, _DATABASE:GetPlayerSettings( self.EscortUnit:GetPlayerName() ) ) + local DetectedMenu = self.Detection:DetectedItemReportMenu( DetectedItem, EscortGroup, _DATABASE:GetPlayerSettings( self.PlayerUnit:GetPlayerName() ) ):Text("\n") - local DetectedMenu = DetectedItemReportSummary:Text("\n") + local DetectedItemReportSummary = self.Detection:DetectedItemReportSummary( DetectedItem, EscortGroup, _DATABASE:GetPlayerSettings( self.PlayerUnit:GetPlayerName() ) ) + local ReportSummary = DetectedItemReportSummary:Text(", ") + DetectedTargetsReport:AddIndent( ReportSummary, "-" ) if EscortGroup:IsAir() then - MENU_GROUP_COMMAND:New( self.EscortUnit:GetGroup(), + + MENU_GROUP_COMMAND:New( self.PlayerGroup, DetectedMenu, - EscortGroup.EscortMenuAttackNearbyTargets, + EscortMenuAttackTargets, AI_ESCORT._AttackTarget, self, EscortGroup, DetectedItem - ) + ):SetTag( "Escort" ):SetTime( TimeUpdate ) else if self.EscortMenuTargetAssistance then - local MenuTargetAssistance = MENU_GROUP:New( self.EscortUnit:GetGroup(), EscortGroupName, EscortGroup.EscortMenuTargetAssistance ) - MENU_GROUP_COMMAND:New( self.EscortUnit:GetGroup(), + local MenuTargetAssistance = MENU_GROUP:New( self.PlayerGroup, EscortGroupName, EscortGroup.EscortMenuTargetAssistance ) + MENU_GROUP_COMMAND:New( self.PlayerGroup, DetectedMenu, MenuTargetAssistance, AI_ESCORT._AssistTarget, @@ -1594,9 +1907,17 @@ function AI_ESCORT:_ReportTargetsScheduler( EscortGroup ) end + EscortMenuAttackTargets:RemoveSubMenus( TimeUpdate, "Esort" ) + + if Report then + if DetectedTargets then + EscortGroup:MessageTypeToGroup( DetectedTargetsReport:Text( "\n" ), MESSAGE.Type.Information, self.PlayerGroup ) + else + EscortGroup:MessageTypeToGroup( "No targets detected.", MESSAGE.Type.Information, self.PlayerGroup ) + end + end + return true - else - end end return false @@ -1611,13 +1932,14 @@ function AI_ESCORT:_FlightReportTargetsScheduler() local EscortGroup = self.EscortGroupSet:GetFirst() -- Wrapper.Group#GROUP - local DetectedTargetsReport = REPORT:New( "Reporting detected targets:\n" ) -- A new report to display the detected targets as a message to the player. + local DetectedTargetsReport = REPORT:New( "Reporting your targets:\n" ) -- A new report to display the detected targets as a message to the player. - if self.EscortUnit:IsAlive() and EscortGroup:IsAlive() then + if EscortGroup and ( self.PlayerUnit:IsAlive() and EscortGroup:IsAlive() ) then - local ClientGroup = self.EscortUnit:GetGroup() + local ClientGroup = self.PlayerGroup + + local TimeUpdate = timer.getTime() - self.FlightMenuAttackNearbyTargets:RemoveSubMenus() local DetectedItems = self.Detection:GetDetectedItems() @@ -1625,28 +1947,37 @@ function AI_ESCORT:_FlightReportTargetsScheduler() local ClientEscortTargets = self.Detection + local FlightMenuAttackTargets = MENU_GROUP:New( self.PlayerGroup, "Attack targets", self.FlightMenu ) + for DetectedItemIndex, DetectedItem in pairs( DetectedItems ) do + + self:F("FlightReportTargetScheduler Targets") + DetectedTargets = true -- There are detected targets, when the content of the for loop is executed. We use it to display a message. - local DetectedItemReportSummary = self.Detection:DetectedItemReportMenu( DetectedItem, ClientGroup, _DATABASE:GetPlayerSettings( self.EscortUnit:GetPlayerName() ) ) - - local DetectedMsg = DetectedItemReportSummary:Text(", ") - DetectedTargetsReport:AddIndent( DetectedMsg, "-" ) - - MENU_GROUP_COMMAND:New( self.EscortUnit:GetGroup(), - DetectedMsg, - self.FlightMenuAttackNearbyTargets, + local DetectedItemReportMenu = self.Detection:DetectedItemReportMenu( DetectedItem, ClientGroup, _DATABASE:GetPlayerSettings( self.PlayerUnit:GetPlayerName() ) ) + local ReportMenuText = DetectedItemReportMenu:Text(", ") + + MENU_GROUP_COMMAND:New( self.PlayerGroup, + ReportMenuText, + FlightMenuAttackTargets, AI_ESCORT._FlightAttackTarget, self, DetectedItem - ) + ):SetTag( "Flight" ):SetTime( TimeUpdate ) + + local DetectedItemReportSummary = self.Detection:DetectedItemReportSummary( DetectedItem, ClientGroup, _DATABASE:GetPlayerSettings( self.PlayerUnit:GetPlayerName() ) ) + local ReportSummary = DetectedItemReportSummary:Text(", ") + DetectedTargetsReport:AddIndent( ReportSummary, "-" ) end + FlightMenuAttackTargets:RemoveSubMenus( TimeUpdate, "Flight" ) + if DetectedTargets then - EscortGroup:MessageTypeToGroup( DetectedTargetsReport:Text( "\n" ), MESSAGE.Type.Information, self.EscortUnit:GetGroup() ) + EscortGroup:MessageTypeToGroup( DetectedTargetsReport:Text( "\n" ), MESSAGE.Type.Information, self.PlayerGroup ) -- else --- EscortGroup:MessageTypeToGroup( "No targets detected.", MESSAGE.Type.Information, self.EscortUnit:GetGroup() ) +-- EscortGroup:MessageTypeToGroup( "No targets detected.", MESSAGE.Type.Information, self.PlayerGroup ) end return true @@ -1654,3 +1985,5 @@ function AI_ESCORT:_FlightReportTargetsScheduler() return false end + + diff --git a/Moose Development/Moose/AI/AI_Escort_Request.lua b/Moose Development/Moose/AI/AI_Escort_Request.lua new file mode 100644 index 000000000..7a2fecb55 --- /dev/null +++ b/Moose Development/Moose/AI/AI_Escort_Request.lua @@ -0,0 +1,293 @@ +--- **Functional** -- Taking the lead of AI escorting your flight or of other AI, upon request using the menu. +-- +-- === +-- +-- ## Features: +-- +-- * Escort navigation commands. +-- * Escort hold at position commands. +-- * Escorts reporting detected targets. +-- * Escorts scanning targets in advance. +-- * Escorts attacking specific targets. +-- * Request assistance from other groups for attack. +-- * Manage rule of engagement of escorts. +-- * Manage the allowed evasion techniques of escorts. +-- * Make escort to execute a defined mission or path. +-- * Escort tactical situation reporting. +-- +-- === +-- +-- ## Missions: +-- +-- [ESC - Escorting](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/ESC%20-%20Escorting) +-- +-- === +-- +-- Allows you to interact with escorting AI on your flight and take the lead. +-- +-- Each escorting group can be commanded with a complete set of radio commands (radio menu in your flight, and then F10). +-- +-- The radio commands will vary according the category of the group. The richest set of commands are with helicopters and airPlanes. +-- Ships and Ground troops will have a more limited set, but they can provide support through the bombing of targets designated by the other escorts. +-- +-- Escorts detect targets using a built-in detection mechanism. The detected targets are reported at a specified time interval. +-- Once targets are reported, each escort has these targets as menu options to command the attack of these targets. +-- Targets are by default grouped per area of 5000 meters, but the kind of detection and the grouping range can be altered. +-- +-- Different formations can be selected in the Flight menu: Trail, Stack, Left Line, Right Line, Left Wing, Right Wing, Central Wing and Boxed formations are available. +-- The Flight menu also allows for a mass attack, where all of the escorts are commanded to attack a target. +-- +-- Escorts can emit flares to reports their location. They can be commanded to hold at a location, which can be their current or the leader location. +-- In this way, you can spread out the escorts over the battle field before a coordinated attack. +-- +-- But basically, the escort class provides 4 modes of operation, and depending on the mode, you are either leading the flight, or following the flight. +-- +-- ## Leading the flight +-- +-- When leading the flight, you are expected to guide the escorts towards the target areas, +-- and carefully coordinate the attack based on the threat levels reported, and the available weapons +-- carried by the escorts. Ground ships or ground troops can execute A-assisted attacks, when they have long-range ground precision weapons for attack. +-- +-- ## Following the flight +-- +-- Escorts can be commanded to execute a specific mission path. In this mode, the escorts are in the lead. +-- You as a player, are following the escorts, and are commanding them to progress the mission while +-- ensuring that the escorts survive. You are joining the escorts in the battlefield. They will detect and report targets +-- and you will ensure that the attacks are well coordinated, assigning the correct escort type for the detected target +-- type. Once the attack is finished, the escort will resume the mission it was assigned. +-- In other words, you can use the escorts for reconnaissance, and for guiding the attack. +-- Imagine you as a mi-8 pilot, assigned to pickup cargo. Two ka-50s are guiding the way, and you are +-- following. You are in control. The ka-50s detect targets, report them, and you command how the attack +-- will commence and from where. You can control where the escorts are holding position and which targets +-- are attacked first. You are in control how the ka-50s will follow their mission path. +-- +-- Escorts can act as part of a AI A2G dispatcher offensive. In this way, You was a player are in control. +-- The mission is defined by the A2G dispatcher, and you are responsible to join the flight and ensure that the +-- attack is well coordinated. +-- +-- It is with great proud that I present you this class, and I hope you will enjoy the functionality and the dynamism +-- it brings in your DCS world simulations. +-- +-- # RADIO MENUs that can be created: +-- +-- Find a summary below of the current available commands: +-- +-- ## Navigation ...: +-- +-- Escort group navigation functions: +-- +-- * **"Join-Up":** The escort group fill follow you in the assigned formation. +-- * **"Flare":** Provides menu commands to let the escort group shoot a flare in the air in a color. +-- * **"Smoke":** Provides menu commands to let the escort group smoke the air in a color. Note that smoking is only available for ground and naval troops. +-- +-- ## Hold position ...: +-- +-- Escort group navigation functions: +-- +-- * **"At current location":** The escort group will hover above the ground at the position they were. The altitude can be specified as a parameter. +-- * **"At my location":** The escort group will hover or orbit at the position where you are. The escort will fly to your location and hold position. The altitude can be specified as a parameter. +-- +-- ## Report targets ...: +-- +-- Report targets will make the escort group to report any target that it identifies within detection range. Any detected target can be attacked using the "Attack Targets" menu function. (see below). +-- +-- * **"Report now":** Will report the current detected targets. +-- * **"Report targets on":** Will make the escorts to report the detected targets and will fill the "Attack Targets" menu list. +-- * **"Report targets off":** Will stop detecting targets. +-- +-- ## Attack targets ...: +-- +-- This menu item will list all detected targets within a 15km range. Depending on the level of detection (known/unknown) and visuality, the targets type will also be listed. +-- This menu will be available in Flight menu or in each Escort menu. +-- +-- ## Scan targets ...: +-- +-- Menu items to pop-up the escort group for target scanning. After scanning, the escort group will resume with the mission or rejoin formation. +-- +-- * **"Scan targets 30 seconds":** Scan 30 seconds for targets. +-- * **"Scan targets 60 seconds":** Scan 60 seconds for targets. +-- +-- ## Request assistance from ...: +-- +-- This menu item will list all detected targets within a 15km range, similar as with the menu item **Attack Targets**. +-- This menu item allows to request attack support from other ground based escorts supporting the current escort. +-- eg. the function allows a player to request support from the Ship escort to attack a target identified by the Plane escort with its Tomahawk missiles. +-- eg. the function allows a player to request support from other Planes escorting to bomb the unit with illumination missiles or bombs, so that the main plane escort can attack the area. +-- +-- ## ROE ...: +-- +-- Sets the Rules of Engagement (ROE) of the escort group when in flight. +-- +-- * **"Hold Fire":** The escort group will hold fire. +-- * **"Return Fire":** The escort group will return fire. +-- * **"Open Fire":** The escort group will open fire on designated targets. +-- * **"Weapon Free":** The escort group will engage with any target. +-- +-- ## Evasion ...: +-- +-- Will define the evasion techniques that the escort group will perform during flight or combat. +-- +-- * **"Fight until death":** The escort group will have no reaction to threats. +-- * **"Use flares, chaff and jammers":** The escort group will use passive defense using flares and jammers. No evasive manoeuvres are executed. +-- * **"Evade enemy fire":** The rescort group will evade enemy fire before firing. +-- * **"Go below radar and evade fire":** The escort group will perform evasive vertical manoeuvres. +-- +-- ## Resume Mission ...: +-- +-- Escort groups can have their own mission. This menu item will allow the escort group to resume their Mission from a given waypoint. +-- Note that this is really fantastic, as you now have the dynamic of taking control of the escort groups, and allowing them to resume their path or mission. +-- +-- === +-- +-- ### Authors: **FlightControl** +-- +-- === +-- +-- @module AI.AI_Escort +-- @image Escorting.JPG + + + +--- @type AI_ESCORT_REQUEST +-- @extends AI.AI_Escort#AI_ESCORT + +--- AI_ESCORT_REQUEST class +-- +-- # AI_ESCORT_REQUEST construction methods. +-- +-- Create a new AI_ESCORT_REQUEST object with the @{#AI_ESCORT_REQUEST.New} method: +-- +-- * @{#AI_ESCORT_REQUEST.New}: Creates a new AI_ESCORT_REQUEST object from a @{Wrapper.Group#GROUP} for a @{Wrapper.Client#CLIENT}, with an optional briefing text. +-- +-- @usage +-- -- Declare a new EscortPlanes object as follows: +-- +-- -- First find the GROUP object and the CLIENT object. +-- local EscortUnit = CLIENT:FindByName( "Unit Name" ) -- The Unit Name is the name of the unit flagged with the skill Client in the mission editor. +-- local EscortGroup = GROUP:FindByName( "Group Name" ) -- The Group Name is the name of the group that will escort the Escort Client. +-- +-- -- Now use these 2 objects to construct the new EscortPlanes object. +-- EscortPlanes = AI_ESCORT_REQUEST:New( EscortUnit, EscortGroup, "Desert", "Welcome to the mission. You are escorted by a plane with code name 'Desert', which can be instructed through the F10 radio menu." ) +-- +-- @field #AI_ESCORT_REQUEST +AI_ESCORT_REQUEST = { + ClassName = "AI_ESCORT_REQUEST", +} + +--- AI_ESCORT_REQUEST.Mode class +-- @type AI_ESCORT_REQUEST.MODE +-- @field #number FOLLOW +-- @field #number MISSION + +--- MENUPARAM type +-- @type MENUPARAM +-- @field #AI_ESCORT_REQUEST ParamSelf +-- @field #Distance ParamDistance +-- @field #function ParamFunction +-- @field #string ParamMessage + +--- AI_ESCORT_REQUEST class constructor for an AI group +-- @param #AI_ESCORT_REQUEST self +-- @param Wrapper.Client#CLIENT EscortUnit The client escorted by the EscortGroup. +-- @param Core.Spawn#SPAWN EscortSpawn The spawn object of AI, escorting the EscortUnit. +-- @param Wrapper.Airbase#AIRBASE EscortAirbase The airbase where escorts will be spawned once requested. +-- @param #string EscortName Name of the escort. +-- @param #string EscortBriefing A text showing the AI_ESCORT_REQUEST briefing to the player. Note that if no EscortBriefing is provided, the default briefing will be shown. +-- @return #AI_ESCORT_REQUEST +-- @usage +-- EscortSpawn = SPAWN:NewWithAlias( "Red A2G Escort Template", "Red A2G Escort AI" ):InitLimit( 10, 10 ) +-- EscortSpawn:ParkAtAirbase( AIRBASE:FindByName( AIRBASE.Caucasus.Sochi_Adler ), AIRBASE.TerminalType.OpenBig ) +-- +-- local EscortUnit = UNIT:FindByName( "Red A2G Pilot" ) +-- +-- Escort = AI_ESCORT_REQUEST:New( EscortUnit, EscortSpawn, AIRBASE:FindByName(AIRBASE.Caucasus.Sochi_Adler), "A2G", "Briefing" ) +-- Escort:FormationTrail( 50, 100, 100 ) +-- Escort:Menus() +-- Escort:__Start( 5 ) +function AI_ESCORT_REQUEST:New( EscortUnit, EscortSpawn, EscortAirbase, EscortName, EscortBriefing ) + + self.EscortGroupSet = SET_GROUP:New() + self.EscortSpawn = EscortSpawn + self.EscortAirbase = EscortAirbase + + local self = BASE:Inherit( self, AI_ESCORT:New( EscortUnit, self.EscortGroupSet, EscortName, EscortBriefing ) ) -- #AI_ESCORT_REQUEST + + self.LeaderGroup = self.PlayerUnit:GetGroup() + + self.Detection = DETECTION_AREAS:New( self.EscortGroupSet, 5000 ) + self.Detection:__Start( 30 ) + + self.SpawnMode = self.__Enum.Mode.Mission + + return self +end + +--- @param #AI_ESCORT_REQUEST self +function AI_ESCORT_REQUEST:SpawnEscort() + + local EscortGroup = self.EscortSpawn:SpawnAtAirbase( self.EscortAirbase, SPAWN.Takeoff.Hot ) + + EscortGroup:OptionROTVertical() + EscortGroup:OptionROEHoldFire() + + self:ScheduleOnce( 0.1, + function( EscortGroup ) + + self.EscortGroupSet:AddGroup( EscortGroup ) + + local LeaderEscort = self.EscortGroupSet:GetFirst() -- Wrapper.Group#GROUP + local Report = REPORT:New() + Report:Add( "Joining Up " .. self.EscortGroupSet:GetUnitTypeNames():Text( ", " ) .. " from " .. LeaderEscort:GetCoordinate():ToString( self.EscortUnit ) ) + LeaderEscort:MessageTypeToGroup( Report:Text(), MESSAGE.Type.Information, self.PlayerUnit ) + + if self.SpawnMode == self.__Enum.Mode.Formation then + self:ModeFormation( EscortGroup ) + end + + self:_InitFlightMenus() + self:_InitEscortMenus( EscortGroup ) + self:_InitEscortRoute( EscortGroup ) + + --- @param #AI_ESCORT self + -- @param Core.Event#EVENTDATA EventData + function EscortGroup:OnEventDeadOrCrash( EventData ) + self:F( { "EventDead", EventData } ) + self.EscortMenu:Remove() + end + + EscortGroup:HandleEvent( EVENTS.Dead, EscortGroup.OnEventDeadOrCrash ) + EscortGroup:HandleEvent( EVENTS.Crash, EscortGroup.OnEventDeadOrCrash ) + + end, EscortGroup + ) + +end + +--- @param #AI_ESCORT_REQUEST self +-- @param Core.Set#SET_GROUP EscortGroupSet +function AI_ESCORT_REQUEST:onafterStart( EscortGroupSet ) + + self:F() + + if not self.MenuRequestEscort then + self.MenuRequestEscort = MENU_GROUP_COMMAND:New( self.LeaderGroup, "Request new escort ", self.MainMenu, + function() + self:SpawnEscort() + end + ) + end + + self:GetParent( self ).onafterStart( self, EscortGroupSet ) + + self:HandleEvent( EVENTS.Dead, self.OnEventDeadOrCrash ) + self:HandleEvent( EVENTS.Crash, self.OnEventDeadOrCrash ) + +end + +--- Set the spawn mode to be mission execution. +-- @param #AI_ESCORT_REQUEST self +function AI_ESCORT_REQUEST:SetEscortSpawnMission() + + self.SpawnMode = self.__Enum.Mode.Mission + +end diff --git a/Moose Development/Moose/AI/AI_Formation.lua b/Moose Development/Moose/AI/AI_Formation.lua index 338d29738..cf1971f8f 100644 --- a/Moose Development/Moose/AI/AI_Formation.lua +++ b/Moose Development/Moose/AI/AI_Formation.lua @@ -110,10 +110,45 @@ AI_FORMATION = { dtFollow = 0.5, } ---- AI_FORMATION.Mode class --- @type AI_FORMATION.MODE --- @field #number FOLLOW --- @field #number MISSION +AI_FORMATION.__Enum = {} + +--- @type AI_FORMATION.__Enum.Formation +-- @field #number None +-- @field #number Line +-- @field #number Trail +-- @field #number Stack +-- @field #number LeftLine +-- @field #number RightLine +-- @field #number LeftWing +-- @field #number RightWing +-- @field #number Vic +-- @field #number Box +AI_FORMATION.__Enum.Formation = { + None = 0, + Mission = 1, + Line = 2, + Trail = 3, + Stack = 4, + LeftLine = 5, + RightLine = 6, + LeftWing = 7, + RightWing = 8, + Vic = 9, + Box = 10, +} + +--- @type AI_FORMATION.__Enum.Mode +-- @field #number Mission +-- @field #number Formation +AI_FORMATION.__Enum.Mode = { + Mission = 0, + Formation = 1, + Attack = 2, + Reconnaissance = 3, +} + + + --- MENUPARAM type -- @type MENUPARAM @@ -139,7 +174,7 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin self.FollowGroupSet:ForEachGroup( function( FollowGroup ) self:E("Following") - FollowGroup.Following = true + FollowGroup:SetState( self, "Mode", self.__Enum.Mode.Formation ) end ) @@ -663,8 +698,8 @@ end -- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. -- @return #AI_FORMATION -function AI_FORMATION:onafterFormationLine( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, YSpace, ZStart, ZSpace ) --R2.1 - self:F( { FollowGroupSet, From , Event ,To, XStart, XSpace, YStart, YSpace, ZStart, ZSpace } ) +function AI_FORMATION:onafterFormationLine( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, YSpace, ZStart, ZSpace, Formation ) --R2.1 + self:F( { FollowGroupSet, From , Event ,To, XStart, XSpace, YStart, YSpace, ZStart, ZSpace, Formation } ) FollowGroupSet:Flush( self ) @@ -682,6 +717,8 @@ function AI_FORMATION:onafterFormationLine( FollowGroupSet, From , Event , To, X local Vec3 = PointVec3:GetVec3() FollowGroup:SetState( self, "FormationVec3", Vec3 ) i = i + 1 + + FollowGroup:SetState( FollowGroup, "Formation", Formation ) end return self @@ -700,7 +737,7 @@ end -- @return #AI_FORMATION function AI_FORMATION:onafterFormationTrail( FollowGroupSet, From , Event , To, XStart, XSpace, YStart ) --R2.1 - self:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,0,0,0) + self:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,0,0,0, self.__Enum.Formation.Trail ) return self end @@ -719,7 +756,7 @@ end -- @return #AI_FORMATION function AI_FORMATION:onafterFormationStack( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, YSpace ) --R2.1 - self:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,YSpace,0,0) + self:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,YSpace,0,0, self.__Enum.Formation.Stack ) return self end @@ -740,7 +777,7 @@ end -- @return #AI_FORMATION function AI_FORMATION:onafterFormationLeftLine( FollowGroupSet, From , Event , To, XStart, YStart, ZStart, ZSpace ) --R2.1 - self:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,0,YStart,0,ZStart,ZSpace) + self:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,0,YStart,0,-ZStart,-ZSpace, self.__Enum.Formation.LeftLine ) return self end @@ -759,7 +796,7 @@ end -- @return #AI_FORMATION function AI_FORMATION:onafterFormationRightLine( FollowGroupSet, From , Event , To, XStart, YStart, ZStart, ZSpace ) --R2.1 - self:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,0,YStart,0,-ZStart,-ZSpace) + self:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,0,YStart,0,ZStart,ZSpace,self.__Enum.Formation.RightLine) return self end @@ -778,7 +815,7 @@ end -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. function AI_FORMATION:onafterFormationLeftWing( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, ZStart, ZSpace ) --R2.1 - self:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,0,ZStart,ZSpace) + self:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,0,-ZStart,-ZSpace,self.__Enum.Formation.LeftWing) return self end @@ -798,7 +835,7 @@ end -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. function AI_FORMATION:onafterFormationRightWing( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, ZStart, ZSpace ) --R2.1 - self:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,0,-ZStart,-ZSpace) + self:onafterFormationLine(FollowGroupSet,From,Event,To,XStart,XSpace,YStart,0,ZStart,ZSpace,self.__Enum.Formation.RightWing) return self end @@ -836,6 +873,7 @@ function AI_FORMATION:onafterFormationCenterWing( FollowGroupSet, From , Event , local Vec3 = PointVec3:GetVec3() FollowGroup:SetState( self, "FormationVec3", Vec3 ) i = i + 1 + FollowGroup:SetState( FollowGroup, "Formation", self.__Enum.Formation.Vic ) end return self @@ -895,6 +933,7 @@ function AI_FORMATION:onafterFormationBox( FollowGroupSet, From , Event , To, XS local Vec3 = PointVec3:GetVec3() FollowGroup:SetState( self, "FormationVec3", Vec3 ) i = i + 1 + FollowGroup:SetState( FollowGroup, "Formation", self.__Enum.Formation.Box ) end return self @@ -913,30 +952,79 @@ function AI_FORMATION:SetFlightRandomization( FlightRandomization ) --R2.1 end ---- This releases the air unit in your flight from the formation flight. + +--- This sets your escorts to fly a mission. -- @param #AI_FORMATION self -- @param Wrapper.Group#GROUP FollowGroup FollowGroup. -- @return #AI_FORMATION -function AI_FORMATION:ReleaseFormation( FollowGroup ) +function AI_FORMATION:ModeMission( FollowGroup ) - FollowGroup.Following = false + if FollowGroup then + FollowGroup:SetState( FollowGroup, "PreviousMode", FollowGroup:GetState( FollowGroup, "Mode" ) ) + FollowGroup:SetState( FollowGroup, "Mode", self.__Enum.Mode.Mission ) + else + self.EscortGroupSet:ForSomeGroupAlive( + --- @param Core.Group#GROUP EscortGroup + function( FollowGroup ) + FollowGroup:SetState( FollowGroup, "PreviousMode", FollowGroup:GetState( FollowGroup, "Mode" ) ) + FollowGroup:SetState( FollowGroup, "Mode", self.__Enum.Mode.Mission ) + end + ) + end + return self end ---- This joins up the air unit in your formation flight. +--- This sets your escorts to execute an attack. -- @param #AI_FORMATION self -- @param Wrapper.Group#GROUP FollowGroup FollowGroup. -- @return #AI_FORMATION -function AI_FORMATION:JoinFormation( FollowGroup ) +function AI_FORMATION:ModeAttack( FollowGroup ) - FollowGroup.Following = true + if FollowGroup then + FollowGroup:SetState( FollowGroup, "PreviousMode", FollowGroup:GetState( FollowGroup, "Mode" ) ) + FollowGroup:SetState( FollowGroup, "Mode", self.__Enum.Mode.Attack ) + else + self.EscortGroupSet:ForSomeGroupAlive( + --- @param Core.Group#GROUP EscortGroup + function( FollowGroup ) + FollowGroup:SetState( FollowGroup, "PreviousMode", FollowGroup:GetState( FollowGroup, "Mode" ) ) + FollowGroup:SetState( FollowGroup, "Mode", self.__Enum.Mode.Attack ) + end + ) + end + return self end +--- This sets your escorts to fly in a formation. +-- @param #AI_FORMATION self +-- @param Wrapper.Group#GROUP FollowGroup FollowGroup. +-- @return #AI_FORMATION +function AI_FORMATION:ModeFormation( FollowGroup ) + + if FollowGroup then + FollowGroup:SetState( FollowGroup, "PreviousMode", FollowGroup:GetState( FollowGroup, "Mode" ) ) + FollowGroup:SetState( FollowGroup, "Mode", self.__Enum.Mode.Formation ) + else + self.EscortGroupSet:ForSomeGroupAlive( + --- @param Core.Group#GROUP EscortGroup + function( FollowGroup ) + FollowGroup:SetState( FollowGroup, "PreviousMode", FollowGroup:GetState( FollowGroup, "Mode" ) ) + FollowGroup:SetState( FollowGroup, "Mode", self.__Enum.Mode.Formation ) + end + ) + end + + return self +end + + + --- Stop function. Formation will not be updated any more. -- @param #AI_FORMATION self @@ -963,15 +1051,11 @@ end --- @param #AI_FORMATION self function AI_FORMATION:onenterFollowing( FollowGroupSet ) --R2.1 - self:F( ) - self:T( { self.FollowUnit.UnitName, self.FollowUnit:IsAlive() } ) if self.FollowUnit:IsAlive() then local ClientUnit = self.FollowUnit - self:T( {ClientUnit.UnitName } ) - local CT1, CT2, CV1, CV2 CT1 = ClientUnit:GetState( self, "CT1" ) @@ -988,12 +1072,14 @@ function AI_FORMATION:onenterFollowing( FollowGroupSet ) --R2.1 ClientUnit:SetState( self, "CV1", CV2 ) end - FollowGroupSet:ForEachGroup( + FollowGroupSet:ForEachGroupAlive( --- @param Wrapper.Group#GROUP FollowGroup -- @param Wrapper.Unit#UNIT ClientUnit function( FollowGroup, Formation, ClientUnit, CT1, CV1, CT2, CV2 ) - if FollowGroup.Following == true then + self:I({Mode=FollowGroup:GetState( FollowGroup, "Mode" )}) + + if FollowGroup:GetState( FollowGroup, "Mode" ) == self.__Enum.Mode.Formation then FollowGroup:OptionROTEvadeFire() FollowGroup:OptionROEReturnFire() @@ -1055,8 +1141,13 @@ function AI_FORMATION:onenterFollowing( FollowGroupSet ) --R2.1 -- Now we calculate the intersecting vector between the circle around CV2 with radius FollowDistance and GH2. -- From the GeoGebra model: CVI = (x(CV2) + FollowDistance cos(alpha), y(GH2) + FollowDistance sin(alpha), z(CV2)) + local Inclination = ( Distance + FollowFormation.x ) / 10 + if Inclination < -30 then + Inclination = - 30 + end local CVI = { x = CV2.x + CS * 10 * math.sin(Ca), - y = GH2.y - ( Distance + FollowFormation.x ) / 5, -- + FollowFormation.y, + y = GH2.y + Inclination, -- + FollowFormation.y, + y = GH2.y, z = CV2.z + CS * 10 * math.cos(Ca), } @@ -1087,13 +1178,22 @@ function AI_FORMATION:onenterFollowing( FollowGroupSet ) --R2.1 - local Time = 60 + local Time = 120 local Speed = - ( Distance + FollowFormation.x ) / Time - local GS = Speed + CS - if Speed < 0 then - Speed = 0 + + if Distance > -10000 then + Speed = - ( Distance + FollowFormation.x ) / 60 end + + if Distance > -2500 then + Speed = - ( Distance + FollowFormation.x ) / 20 + end + + local GS = Speed + CS + + self:F( { Distance = Distance, Speed = Speed, CS = CS, GS = GS } ) + -- Now route the escort to the desired point with the desired speed. FollowGroup:RouteToVec3( GDV_Formation, GS ) -- DCS models speed in Mps (Miles per second) diff --git a/Moose Development/Moose/Core/Database.lua b/Moose Development/Moose/Core/Database.lua index 0e87d5f7c..6b281d920 100644 --- a/Moose Development/Moose/Core/Database.lua +++ b/Moose Development/Moose/Core/Database.lua @@ -879,7 +879,7 @@ end -- @param #DATABASE self -- @param Core.Event#EVENTDATA Event function DATABASE:_EventOnBirth( Event ) - self:F2( { Event } ) + self:F( { Event } ) if Event.IniDCSUnit then if Event.IniObjectCategory == 3 then @@ -896,6 +896,7 @@ function DATABASE:_EventOnBirth( Event ) local PlayerName = Event.IniUnit:GetPlayerName() if PlayerName then self:I( { "Player Joined:", PlayerName } ) + self:AddClient( Event.IniDCSUnitName ) if not self.PLAYERS[PlayerName] then self:AddPlayer( Event.IniUnitName, PlayerName ) end diff --git a/Moose Development/Moose/Core/Event.lua b/Moose Development/Moose/Core/Event.lua index 3c00758eb..83c344e7a 100644 --- a/Moose Development/Moose/Core/Event.lua +++ b/Moose Development/Moose/Core/Event.lua @@ -990,7 +990,7 @@ function EVENT:onEvent( Event ) local PriorityEnd = PriorityOrder == -1 and 1 or 5 if Event.IniObjectCategory ~= Object.Category.STATIC then - self:T( { EventMeta.Text, Event, Event.IniDCSUnitName, Event.TgtDCSUnitName, PriorityOrder } ) + self:F( { EventMeta.Text, Event, Event.IniDCSUnitName, Event.TgtDCSUnitName, PriorityOrder } ) end for EventPriority = PriorityBegin, PriorityEnd, PriorityOrder do diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index 314b6460c..a2e7fe2bc 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -2059,7 +2059,7 @@ do -- COORDINATE -- * Uses default settings in COORDINATE. -- * Can be overridden if for a GROUP containing x clients, a menu was selected to override the default. -- @param #COORDINATE self - -- @param Wrapper.Controllable#CONTROLLABLE Controllable + -- @param Wrapper.Controllable#CONTROLLABLE Controllable The controllable to retrieve the settings from, otherwise the default settings will be chosen. -- @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. -- @param Tasking.Task#TASK Task The task for which coordinates need to be calculated. -- @return #string The coordinate Text in the configured coordinate system. diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index 4399c92f7..f0e24adf5 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -125,7 +125,7 @@ do -- SET_BASE self.Index = {} self.CallScheduler = SCHEDULER:New( self ) - + self:SetEventPriority( 2 ) return self @@ -341,6 +341,25 @@ do -- SET_BASE return self end + --- Define the SET iterator **"limit"**. + -- @param #SET_BASE self + -- @param #number Limit Defines how many objects are evaluated of the set as part of the Some iterators. The default is 1. + -- @return #SET_BASE self + function SET_BASE:SetSomeIteratorLimit( Limit ) + + self.SomeIteratorLimit = Limit or 1 + + return self + end + + --- Get the SET iterator **"limit"**. + -- @param #SET_BASE self + -- @return #number Defines how many objects are evaluated of the set as part of the Some iterators. + function SET_BASE:GetSomeIteratorLimit() + + return self.SomeIteratorLimit or self:Count() + end + --- Filters for the defined collection. -- @param #SET_BASE self @@ -590,6 +609,66 @@ do -- SET_BASE return self end + + --- Iterate the SET_BASE and derived classes and call an iterator function for the given SET_BASE, providing the Object for each element within the set and optional parameters. + -- @param #SET_BASE self + -- @param #function IteratorFunction The function that will be called. + -- @return #SET_BASE self + function SET_BASE:ForSome( IteratorFunction, arg, Set, Function, FunctionArguments ) + self:F3( arg ) + + Set = Set or self:GetSet() + arg = arg or {} + + local Limit = self:GetSomeIteratorLimit() + + local function CoRoutine() + local Count = 0 + for ObjectID, ObjectData in pairs( Set ) do + local Object = ObjectData + self:T3( Object ) + if Function then + if Function( unpack( FunctionArguments ), Object ) == true then + IteratorFunction( Object, unpack( arg ) ) + end + else + IteratorFunction( Object, unpack( arg ) ) + end + Count = Count + 1 + if Count >= Limit then + break + end + -- if Count % self.YieldInterval == 0 then + -- coroutine.yield( false ) + -- end + end + return true + end + + -- local co = coroutine.create( CoRoutine ) + local co = CoRoutine + + local function Schedule() + + -- local status, res = coroutine.resume( co ) + local status, res = co() + self:T3( { status, res } ) + + if status == false then + error( res ) + end + if res == false then + return true -- resume next time the loop + end + + return false + end + + --self.CallScheduler:Schedule( self, Schedule, {}, self.TimeInterval, self.TimeInterval, 0 ) + Schedule() + + return self + end ----- Iterate the SET_BASE and call an interator function for each **alive** unit, providing the Unit and optional parameters. @@ -845,7 +924,40 @@ do -- SET_GROUP return AliveSet.Set or {} end + + --- Returns a report of of unit types. + -- @param #SET_GROUP self + -- @return Core.Report#REPORT A report of the unit types found. The key is the UnitTypeName and the value is the amount of unit types found. + function SET_GROUP:GetUnitTypeNames() + self:F2() + local MT = {} -- Message Text + local UnitTypes = {} + + local ReportUnitTypes = REPORT:New() + + for GroupID, GroupData in pairs( self:GetSet() ) do + local Units = GroupData:GetUnits() + for UnitID, UnitData in pairs( Units ) do + if UnitData:IsAlive() then + local UnitType = UnitData:GetTypeName() + + if not UnitTypes[UnitType] then + UnitTypes[UnitType] = 1 + else + UnitTypes[UnitType] = UnitTypes[UnitType] + 1 + end + end + end + end + + for UnitTypeID, UnitType in pairs( UnitTypes ) do + ReportUnitTypes:Add( UnitType .. " of " .. UnitTypeID ) + end + + return ReportUnitTypes + end + --- Add a GROUP to SET_GROUP. -- Note that for each unit in the group that is set, a default cargo bay limit is initialized. -- @param Core.Set#SET_GROUP self @@ -1144,7 +1256,7 @@ do -- SET_GROUP --- Iterate the SET_GROUP and call an iterator function for each GROUP object, providing the GROUP and optional parameters. -- @param #SET_GROUP self - -- @param #function IteratorFunction The function that will be called when there is an alive GROUP in the SET_GROUP. The function needs to accept a GROUP parameter. + -- @param #function IteratorFunction The function that will be called for all GROUP in the SET_GROUP. The function needs to accept a GROUP parameter. -- @return #SET_GROUP self function SET_GROUP:ForEachGroup( IteratorFunction, ... ) self:F2( arg ) @@ -1154,6 +1266,18 @@ do -- SET_GROUP return self end + --- Iterate the SET_GROUP and call an iterator function for some GROUP objects, providing the GROUP and optional parameters. + -- @param #SET_GROUP self + -- @param #function IteratorFunction The function that will be called for some GROUP in the SET_GROUP. The function needs to accept a GROUP parameter. + -- @return #SET_GROUP self + function SET_GROUP:ForSomeGroup( IteratorFunction, ... ) + self:F2( arg ) + + self:ForSome( IteratorFunction, arg, self:GetSet() ) + + return self + end + --- Iterate the SET_GROUP and call an iterator function for each **alive** GROUP object, providing the GROUP and optional parameters. -- @param #SET_GROUP self -- @param #function IteratorFunction The function that will be called when there is an alive GROUP in the SET_GROUP. The function needs to accept a GROUP parameter. @@ -1166,6 +1290,18 @@ do -- SET_GROUP return self end + --- Iterate the SET_GROUP and call an iterator function for some **alive** GROUP objects, providing the GROUP and optional parameters. + -- @param #SET_GROUP self + -- @param #function IteratorFunction The function that will be called when there is an alive GROUP in the SET_GROUP. The function needs to accept a GROUP parameter. + -- @return #SET_GROUP self + function SET_GROUP:ForSomeGroupAlive( IteratorFunction, ... ) + self:F2( arg ) + + self:ForSome( IteratorFunction, arg, self:GetAliveSet() ) + + return self + end + --- Iterate the SET_GROUP and call an iterator function for each **alive** GROUP presence completely in a @{Zone}, providing the GROUP and optional parameters to the called function. -- @param #SET_GROUP self -- @param Core.Zone#ZONE ZoneObject The Zone to be tested for. diff --git a/Moose Development/Moose/Core/Spawn.lua b/Moose Development/Moose/Core/Spawn.lua index 206f1e4cb..ce03cd14e 100644 --- a/Moose Development/Moose/Core/Spawn.lua +++ b/Moose Development/Moose/Core/Spawn.lua @@ -1156,12 +1156,24 @@ function SPAWN:ReSpawn( SpawnIndex ) return SpawnGroup end + +--- Set the spawn index to a specified index number. +-- This method can be used to "reset" the spawn counter to a specific index number. +-- This will actually enable a respawn of groups from the specific index. +-- @param #SPAWN self +-- @param #string SpawnIndex The index of the group from where the spawning will start again. The default value would be 0, which means a complete reset of the spawnindex. +-- @return #SPAWN self +function SPAWN:SetSpawnIndex( SpawnIndex ) + self.SpawnIndex = SpawnIndex or 0 +end + + --- Will spawn a group with a specified index number. -- Uses @{DATABASE} global object defined in MOOSE. -- @param #SPAWN self -- @param #string SpawnIndex The index of the group to be spawned. -- @return Wrapper.Group#GROUP The group that was spawned. You can use this group for further actions. -function SPAWN:SpawnWithIndex( SpawnIndex ) +function SPAWN:SpawnWithIndex( SpawnIndex, NoBirth ) self:F2( { SpawnTemplatePrefix = self.SpawnTemplatePrefix, SpawnIndex = SpawnIndex, AliveUnits = self.AliveUnits, SpawnMaxGroups = self.SpawnMaxGroups } ) if self:_GetSpawnIndex( SpawnIndex ) then @@ -1263,14 +1275,16 @@ function SPAWN:SpawnWithIndex( SpawnIndex ) SpawnTemplate.CoalitionID = self.SpawnInitCoalition or SpawnTemplate.CoalitionID - if SpawnTemplate.CategoryID == Group.Category.HELICOPTER or SpawnTemplate.CategoryID == Group.Category.AIRPLANE then - if SpawnTemplate.route.points[1].type == "TakeOffParking" then - SpawnTemplate.uncontrolled = self.SpawnUnControlled - end - end +-- if SpawnTemplate.CategoryID == Group.Category.HELICOPTER or SpawnTemplate.CategoryID == Group.Category.AIRPLANE then +-- if SpawnTemplate.route.points[1].type == "TakeOffParking" then +-- SpawnTemplate.uncontrolled = self.SpawnUnControlled +-- end +-- end end - self:HandleEvent( EVENTS.Birth, self._OnBirth ) + if not NoBirth then + self:HandleEvent( EVENTS.Birth, self._OnBirth ) + end self:HandleEvent( EVENTS.Dead, self._OnDeadOrCrash ) self:HandleEvent( EVENTS.Crash, self._OnDeadOrCrash ) self:HandleEvent( EVENTS.RemoveUnit, self._OnDeadOrCrash ) @@ -1459,12 +1473,23 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT EmergencyAirSpawn=true end + self:F( { SpawnIndex = self.SpawnIndex } ) + if self:_GetSpawnIndex( self.SpawnIndex + 1 ) then -- Get group template. local SpawnTemplate = self.SpawnGroups[self.SpawnIndex].SpawnTemplate + self:F( { SpawnTemplate = SpawnTemplate } ) + if SpawnTemplate then + + -- Check if the aircraft with the specified SpawnIndex is already spawned. + -- If yes, ensure that the aircraft is spawned at the same aircraft spot. + + local GroupAlive = self:GetGroupFromIndex( self.SpawnIndex ) + + self:F( { GroupAlive = GroupAlive } ) -- Debug output self:T( { "Current point of ", self.SpawnTemplatePrefix, SpawnAirbase } ) @@ -1544,7 +1569,8 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT local spots -- Spawn happens on ground, i.e. at an airbase, a FARP or a ship. - if spawnonground then + if spawnonground and not SpawnTemplate.parked then + -- Number of free parking spots. local nfree=0 @@ -1714,9 +1740,347 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT end + if not SpawnTemplate.parked then + -- Translate the position of the Group Template to the Vec3. + + SpawnTemplate.parked = true + + for UnitID = 1, nunits do + self:T2('Before Translation SpawnTemplate.units['..UnitID..'].x = '..SpawnTemplate.units[UnitID].x..', SpawnTemplate.units['..UnitID..'].y = '..SpawnTemplate.units[UnitID].y) + + -- Template of the current unit. + local UnitTemplate = SpawnTemplate.units[UnitID] + + -- Tranlate position and preserve the relative position/formation of all aircraft. + local SX = UnitTemplate.x + local SY = UnitTemplate.y + local BX = SpawnTemplate.route.points[1].x + local BY = SpawnTemplate.route.points[1].y + local TX = PointVec3.x + (SX-BX) + local TY = PointVec3.z + (SY-BY) + + if spawnonground then + + -- Ships and FARPS seem to have a build in queue. + if spawnonship or spawnonfarp or spawnonrunway then + + self:T(string.format("Group %s spawning at farp, ship or runway %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName())) + + -- Spawn on ship. We take only the position of the ship. + SpawnTemplate.units[UnitID].x = PointVec3.x --TX + SpawnTemplate.units[UnitID].y = PointVec3.z --TY + SpawnTemplate.units[UnitID].alt = PointVec3.y + + else + + self:T(string.format("Group %s spawning at airbase %s on parking spot id %d", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), parkingindex[UnitID])) + + -- Get coordinates of parking spot. + SpawnTemplate.units[UnitID].x = parkingspots[UnitID].x + SpawnTemplate.units[UnitID].y = parkingspots[UnitID].z + SpawnTemplate.units[UnitID].alt = parkingspots[UnitID].y + + --parkingspots[UnitID]:MarkToAll(string.format("Group %s spawning at airbase %s on parking spot id %d", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), parkingindex[UnitID])) + end + + else + + self:T(string.format("Group %s spawning in air at %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName())) + + -- Spawn in air as requested initially. Original template orientation is perserved, altitude is already correctly set. + SpawnTemplate.units[UnitID].x = TX + SpawnTemplate.units[UnitID].y = TY + SpawnTemplate.units[UnitID].alt = PointVec3.y + + end + + -- Parking spot id. + UnitTemplate.parking = nil + UnitTemplate.parking_id = nil + if parkingindex[UnitID] then + UnitTemplate.parking = parkingindex[UnitID] + end + + -- Debug output. + self:T2(string.format("Group %s unit number %d: Parking = %s",self.SpawnTemplatePrefix, UnitID, tostring(UnitTemplate.parking))) + self:T2(string.format("Group %s unit number %d: Parking ID = %s",self.SpawnTemplatePrefix, UnitID, tostring(UnitTemplate.parking_id))) + self:T2('After Translation SpawnTemplate.units['..UnitID..'].x = '..SpawnTemplate.units[UnitID].x..', SpawnTemplate.units['..UnitID..'].y = '..SpawnTemplate.units[UnitID].y) + end + end + + -- Set gereral spawnpoint position. + SpawnPoint.x = PointVec3.x + SpawnPoint.y = PointVec3.z + SpawnPoint.alt = PointVec3.y + + SpawnTemplate.x = PointVec3.x + SpawnTemplate.y = PointVec3.z + + SpawnTemplate.uncontrolled = nil + + -- Spawn group. + local GroupSpawned = self:SpawnWithIndex( self.SpawnIndex ) + + -- When spawned in the air, we need to generate a Takeoff Event. + if Takeoff == GROUP.Takeoff.Air then + for UnitID, UnitSpawned in pairs( GroupSpawned:GetUnits() ) do + SCHEDULER:New( nil, BASE.CreateEventTakeoff, { GroupSpawned, timer.getTime(), UnitSpawned:GetDCSObject() } , 5 ) + end + end + + -- Check if we accidentally spawned on the runway. Needs to be schedules, because group is not immidiately alive. + if Takeoff~=SPAWN.Takeoff.Runway and Takeoff~=SPAWN.Takeoff.Air and spawnonairport then + SCHEDULER:New(nil, AIRBASE.CheckOnRunWay, {SpawnAirbase, GroupSpawned, 75, true} , 1.0) + end + + return GroupSpawned + end + end + + return nil +end + + +--- Will park a group at an @{Wrapper.Airbase}. +-- +-- @param #SPAWN self +-- @param Wrapper.Airbase#AIRBASE SpawnAirbase The @{Wrapper.Airbase} where to spawn the group. +-- @param Wrapper.Airbase#AIRBASE.TerminalType TerminalType (optional) The terminal type the aircraft should be spawned at. See @{Wrapper.Airbase#AIRBASE.TerminalType}. +-- @param #table Parkingdata (optional) Table holding the coordinates and terminal ids for all units of the group. Spawning will be forced to happen at exactily these spots! +-- @return #nil Nothing is returned! +function SPAWN:ParkAircraft( SpawnAirbase, TerminalType, Parkingdata, SpawnIndex ) + + self:F( { SpawnIndex = SpawnIndex, SpawnMaxGroups = self.SpawnMaxGroups } ) + + -- Get position of airbase. + local PointVec3 = SpawnAirbase:GetCoordinate() + self:T2(PointVec3) + + -- Set take off type. Default is hot. + local Takeoff = SPAWN.Takeoff.Cold + + -- Get group template. + local SpawnTemplate = self.SpawnGroups[SpawnIndex].SpawnTemplate + + if SpawnTemplate then + + -- Check if the aircraft with the specified SpawnIndex is already spawned. + -- If yes, ensure that the aircraft is spawned at the same aircraft spot. + + local GroupAlive = self:GetGroupFromIndex( SpawnIndex ) + + -- Debug output + self:T( { "Current point of ", self.SpawnTemplatePrefix, SpawnAirbase } ) + + -- Template group, unit and its attributes. + local TemplateGroup = GROUP:FindByName(self.SpawnTemplatePrefix) + local TemplateUnit=TemplateGroup:GetUnit(1) + local ishelo=TemplateUnit:HasAttribute("Helicopters") + local isbomber=TemplateUnit:HasAttribute("Bombers") + local istransport=TemplateUnit:HasAttribute("Transports") + local isfighter=TemplateUnit:HasAttribute("Battleplanes") + + -- Number of units in the group. With grouping this can actually differ from the template group size! + local nunits=#SpawnTemplate.units + + -- First waypoint of the group. + local SpawnPoint = SpawnTemplate.route.points[1] + + -- These are only for ships and FARPS. + SpawnPoint.linkUnit = nil + SpawnPoint.helipadId = nil + SpawnPoint.airdromeId = nil + + -- Get airbase ID and category. + local AirbaseID = SpawnAirbase:GetID() + local AirbaseCategory = SpawnAirbase:GetDesc().category + self:F( { AirbaseCategory = AirbaseCategory } ) + + -- Set airdromeId. + if AirbaseCategory == Airbase.Category.SHIP then + SpawnPoint.linkUnit = AirbaseID + SpawnPoint.helipadId = AirbaseID + elseif AirbaseCategory == Airbase.Category.HELIPAD then + SpawnPoint.linkUnit = AirbaseID + SpawnPoint.helipadId = AirbaseID + elseif AirbaseCategory == Airbase.Category.AIRDROME then + SpawnPoint.airdromeId = AirbaseID + end + + -- Set waypoint type/action. + SpawnPoint.alt = 0 + SpawnPoint.type = GROUPTEMPLATE.Takeoff[Takeoff][1] -- type + SpawnPoint.action = GROUPTEMPLATE.Takeoff[Takeoff][2] -- action + + -- Check if we spawn on ground. + local spawnonground=not (Takeoff==SPAWN.Takeoff.Air) + self:T({spawnonground=spawnonground, TOtype=Takeoff, TOair=Takeoff==SPAWN.Takeoff.Air}) + + -- Check where we actually spawn if we spawn on ground. + local spawnonship=false + local spawnonfarp=false + local spawnonrunway=false + local spawnonairport=false + if spawnonground then + if AirbaseCategory == Airbase.Category.SHIP then + spawnonship=true + elseif AirbaseCategory == Airbase.Category.HELIPAD then + spawnonfarp=true + elseif AirbaseCategory == Airbase.Category.AIRDROME then + spawnonairport=true + end + spawnonrunway=Takeoff==SPAWN.Takeoff.Runway + end + + -- Array with parking spots coordinates. + local parkingspots={} + local parkingindex={} + local spots + + -- Spawn happens on ground, i.e. at an airbase, a FARP or a ship. + if spawnonground and not SpawnTemplate.parked then + + + -- Number of free parking spots. + local nfree=0 + + -- Set terminal type. + local termtype=TerminalType + + -- Scan options. Might make that input somehow. + local scanradius=50 + local scanunits=true + local scanstatics=true + local scanscenery=false + local verysafe=false + + -- Number of free parking spots at the airbase. + if spawnonship or spawnonfarp or spawnonrunway then + -- These places work procedural and have some kind of build in queue ==> Less effort. + self:T(string.format("Group %s is spawned on farp/ship/runway %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName())) + nfree=SpawnAirbase:GetFreeParkingSpotsNumber(termtype, true) + spots=SpawnAirbase:GetFreeParkingSpotsTable(termtype, true) + elseif Parkingdata~=nil then + -- Parking data explicitly set by user as input parameter. + nfree=#Parkingdata + spots=Parkingdata + else + if ishelo then + if termtype==nil then + -- Helo is spawned. Try exclusive helo spots first. + self:T(string.format("Helo group %s is at %s using terminal type %d.", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), AIRBASE.TerminalType.HelicopterOnly)) + spots=SpawnAirbase:FindFreeParkingSpotForAircraft(TemplateGroup, AIRBASE.TerminalType.HelicopterOnly, scanradius, scanunits, scanstatics, scanscenery, verysafe, nunits) + nfree=#spots + if nfree=1 then + + -- All units get the same spot. DCS takes care of the rest. + for i=1,nunits do + table.insert(parkingspots, spots[1].Coordinate) + table.insert(parkingindex, spots[1].TerminalID) + end + -- This is actually used... + PointVec3=spots[1].Coordinate + + else + -- If there is absolutely no spot ==> air start! + _notenough=true + end + + elseif spawnonairport then + + if nfree>=nunits then + + for i=1,nunits do + table.insert(parkingspots, spots[i].Coordinate) + table.insert(parkingindex, spots[i].TerminalID) + end + + else + -- Not enough spots for the whole group ==> air start! + _notenough=true + end + end + + -- Not enough spots ==> Prepare airstart. + if _notenough then + + if not self.SpawnUnControlled then + else + self:E(string.format("WARNING: Group %s has no parking spots at %s ==> No emergency air start or uncontrolled spawning ==> No spawn!", self.SpawnTemplatePrefix, SpawnAirbase:GetName())) + return nil + end + end + + else + + end + + if not SpawnTemplate.parked then -- Translate the position of the Group Template to the Vec3. + + SpawnTemplate.parked = true + for UnitID = 1, nunits do - self:T2('Before Translation SpawnTemplate.units['..UnitID..'].x = '..SpawnTemplate.units[UnitID].x..', SpawnTemplate.units['..UnitID..'].y = '..SpawnTemplate.units[UnitID].y) + self:F('Before Translation SpawnTemplate.units['..UnitID..'].x = '..SpawnTemplate.units[UnitID].x..', SpawnTemplate.units['..UnitID..'].y = '..SpawnTemplate.units[UnitID].y) -- Template of the current unit. local UnitTemplate = SpawnTemplate.units[UnitID] @@ -1776,33 +2140,85 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT self:T2(string.format("Group %s unit number %d: Parking ID = %s",self.SpawnTemplatePrefix, UnitID, tostring(UnitTemplate.parking_id))) self:T2('After Translation SpawnTemplate.units['..UnitID..'].x = '..SpawnTemplate.units[UnitID].x..', SpawnTemplate.units['..UnitID..'].y = '..SpawnTemplate.units[UnitID].y) end - - -- Set gereral spawnpoint position. - SpawnPoint.x = PointVec3.x - SpawnPoint.y = PointVec3.z - SpawnPoint.alt = PointVec3.y - - SpawnTemplate.x = PointVec3.x - SpawnTemplate.y = PointVec3.z - - -- Spawn group. - local GroupSpawned = self:SpawnWithIndex( self.SpawnIndex ) - - -- When spawned in the air, we need to generate a Takeoff Event. - if Takeoff == GROUP.Takeoff.Air then - for UnitID, UnitSpawned in pairs( GroupSpawned:GetUnits() ) do - SCHEDULER:New( nil, BASE.CreateEventTakeoff, { GroupSpawned, timer.getTime(), UnitSpawned:GetDCSObject() } , 5 ) - end - end - - -- Check if we accidentally spawned on the runway. Needs to be schedules, because group is not immidiately alive. - if Takeoff~=SPAWN.Takeoff.Runway and Takeoff~=SPAWN.Takeoff.Air and spawnonairport then - SCHEDULER:New(nil, AIRBASE.CheckOnRunWay, {SpawnAirbase, GroupSpawned, 75, true} , 1.0) - end - - return GroupSpawned end + + -- Set gereral spawnpoint position. + SpawnPoint.x = PointVec3.x + SpawnPoint.y = PointVec3.z + SpawnPoint.alt = PointVec3.y + + SpawnTemplate.x = PointVec3.x + SpawnTemplate.y = PointVec3.z + + SpawnTemplate.uncontrolled = true + + -- Spawn group. + local GroupSpawned = self:SpawnWithIndex( SpawnIndex, true ) + + -- When spawned in the air, we need to generate a Takeoff Event. + if Takeoff == GROUP.Takeoff.Air then + for UnitID, UnitSpawned in pairs( GroupSpawned:GetUnits() ) do + SCHEDULER:New( nil, BASE.CreateEventTakeoff, { GroupSpawned, timer.getTime(), UnitSpawned:GetDCSObject() } , 5 ) + end + end + + -- Check if we accidentally spawned on the runway. Needs to be schedules, because group is not immidiately alive. + if Takeoff~=SPAWN.Takeoff.Runway and Takeoff~=SPAWN.Takeoff.Air and spawnonairport then + SCHEDULER:New(nil, AIRBASE.CheckOnRunWay, {SpawnAirbase, GroupSpawned, 75, true} , 1.0) + end + end + +end + +--- Will park a group at an @{Wrapper.Airbase}. +-- This method is mostly advisable to be used if you want to simulate parking units at an airbase and be visible. +-- Note that each point in the route assigned to the spawning group is reset to the point of the spawn. +-- +-- All groups that are in the spawn collection and that are alive, and not in the air, are parked. +-- +-- The @{Wrapper.Airbase#AIRBASE} object must refer to a valid airbase known in the sim. +-- You can use the following enumerations to search for the pre-defined airbases on the current known maps of DCS: +-- +-- * @{Wrapper.Airbase#AIRBASE.Caucasus}: The airbases on the Caucasus map. +-- * @{Wrapper.Airbase#AIRBASE.Nevada}: The airbases on the Nevada (NTTR) map. +-- * @{Wrapper.Airbase#AIRBASE.Normandy}: The airbases on the Normandy map. +-- +-- Use the method @{Wrapper.Airbase#AIRBASE.FindByName}() to retrieve the airbase object. +-- The known AIRBASE objects are automatically imported at mission start by MOOSE. +-- Therefore, there isn't any New() constructor defined for AIRBASE objects. +-- +-- Ships and Farps are added within the mission, and are therefore not known. +-- For these AIRBASE objects, there isn't an @{Wrapper.Airbase#AIRBASE} enumeration defined. +-- You need to provide the **exact name** of the airbase as the parameter to the @{Wrapper.Airbase#AIRBASE.FindByName}() method! +-- +-- @param #SPAWN self +-- @param Wrapper.Airbase#AIRBASE SpawnAirbase The @{Wrapper.Airbase} where to spawn the group. +-- @param Wrapper.Airbase#AIRBASE.TerminalType TerminalType (optional) The terminal type the aircraft should be spawned at. See @{Wrapper.Airbase#AIRBASE.TerminalType}. +-- @param #table Parkingdata (optional) Table holding the coordinates and terminal ids for all units of the group. Spawning will be forced to happen at exactily these spots! +-- @return #nil Nothing is returned! +-- @usage +-- Spawn_Plane = SPAWN:New( "Plane" ) +-- Spawn_Plane:ParkAtAirbase( AIRBASE:FindByName( AIRBASE.Caucasus.Krymsk ) ) +-- +-- Spawn_Heli = SPAWN:New( "Heli") +-- +-- Spawn_Heli:ParkAtAirbase( AIRBASE:FindByName( "FARP Cold" ) ) +-- +-- Spawn_Heli:ParkAtAirbase( AIRBASE:FindByName( "Carrier" ) ) +-- +-- Spawn_Plane:ParkAtAirbase( AIRBASE:FindByName( AIRBASE.Caucasus.Krymsk ), AIRBASE.TerminalType.OpenBig ) +-- +function SPAWN:ParkAtAirbase( SpawnAirbase, TerminalType, Parkingdata ) -- R2.2, R2.4, R2.5 + self:F( { self.SpawnTemplatePrefix, SpawnAirbase, TerminalType } ) + + self:ParkAircraft( SpawnAirbase, TerminalType, Parkingdata, 1 ) + + for SpawnIndex = 2, self.SpawnMaxGroups do + self:ScheduleOnce( SpawnIndex * 0.1, SPAWN.ParkAircraft, self, SpawnAirbase, TerminalType, Parkingdata, SpawnIndex ) + end + + self:SetSpawnIndex() return nil end @@ -2094,7 +2510,7 @@ end function SPAWN:InitUnControlled( UnControlled ) self:F2( { self.SpawnTemplatePrefix, UnControlled } ) - self.SpawnUnControlled = UnControlled or true + self.SpawnUnControlled = ( UnControlled == true ) and true or nil for SpawnGroupID = 1, self.SpawnMaxGroups do self.SpawnGroups[SpawnGroupID].UnControlled = self.SpawnUnControlled @@ -2428,6 +2844,19 @@ function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex ) --R2.2 SpawnTemplate.units[UnitID].unitId = nil end end + + -- Callsign + for UnitID = 1, #SpawnTemplate.units do + local Callsign = SpawnTemplate.units[UnitID].callsign + if Callsign[1] ~= nil then -- blue callsign + Callsign[2] = ( ( SpawnIndex - 1 ) % 10 ) + 1 + local CallsignName = SpawnTemplate.units[UnitID].callsign["name"] -- #string + local CallsignLen = CallsignName:len() + SpawnTemplate.units[UnitID].callsign["name"] = CallsignName:sub(1,CallsignLen) .. SpawnTemplate.units[UnitID].callsign[2] .. SpawnTemplate.units[UnitID].callsign[3] + else + SpawnTemplate.units[UnitID].callsign = Callsign + SpawnIndex + end + end self:T3( { "Template:", SpawnTemplate } ) return SpawnTemplate diff --git a/Moose Development/Moose/Functional/Detection.lua b/Moose Development/Moose/Functional/Detection.lua index 06ecfe87f..133706fb9 100644 --- a/Moose Development/Moose/Functional/Detection.lua +++ b/Moose Development/Moose/Functional/Detection.lua @@ -305,7 +305,7 @@ do -- DETECTION_BASE --- DETECTION constructor. -- @param #DETECTION_BASE self - -- @param Core.Set#SET_BASE DetectionSet The @{Set} that is used to detect the units. + -- @param Core.Set#SET_GROUP DetectionSet The @{Set} of @{Group}s that is used to detect the units. -- @return #DETECTION_BASE self function DETECTION_BASE:New( DetectionSet ) @@ -542,12 +542,16 @@ do -- DETECTION_BASE end self.DetectionCount = self.DetectionSet:Count() - for DetectionID, DetectionData in pairs( self.DetectionSet:GetSet() ) do - --self:F( { DetectionGroupData } ) - self:F( { DetectionGroup = DetectionData:GetName() } ) - self:__Detection( DetectDelay, DetectionData, DetectionTimeStamp ) -- Process each detection asynchronously. - DetectDelay = DetectDelay + 1 - end + + self.DetectionSet:ForEachGroupAlive( + function( DetectionGroup ) + self:__Detection( DetectDelay, DetectionGroup, DetectionTimeStamp ) -- Process each detection asynchronously. + DetectDelay = DetectDelay + 1 + end + ) + + self:__Detect( -self.RefreshTimeInterval ) + end --- @param #DETECTION_BASE self @@ -802,10 +806,9 @@ do -- DETECTION_BASE self:__DetectedItem( 0.1, DetectedItem ) end end - - self:__Detect( self.RefreshTimeInterval ) end + end @@ -2502,7 +2505,6 @@ do -- DETECTION_AREAS local Report = REPORT:New() Report:Add( DetectedItemID ) Report:Add( string.format( "Threat: [%s%s]", string.rep( "■", ThreatLevelA2G ), string.rep( "□", 10-ThreatLevelA2G ) ) ) - Report:Add( DetectedItemCoordText ) return Report end diff --git a/Moose Development/Moose/Modules.lua b/Moose Development/Moose/Modules.lua index ad7b3bfe3..2c485f2a2 100644 --- a/Moose Development/Moose/Modules.lua +++ b/Moose Development/Moose/Modules.lua @@ -1,3 +1,4 @@ +__Moose.Include( 'Scripts/Moose/Utilities/Enums.lua' ) __Moose.Include( 'Scripts/Moose/Utilities/Routines.lua' ) __Moose.Include( 'Scripts/Moose/Utilities/Utils.lua' ) @@ -86,6 +87,7 @@ __Moose.Include( 'Scripts/Moose/AI/AI_Cas.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_Bai.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_Formation.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_Escort.lua' ) +__Moose.Include( 'Scripts/Moose/AI/AI_Escort_Request.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_Cargo.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_Cargo_APC.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_Cargo_Helicopter.lua' ) diff --git a/Moose Development/Moose/Utilities/Enums.lua b/Moose Development/Moose/Utilities/Enums.lua new file mode 100644 index 000000000..a5bd06237 --- /dev/null +++ b/Moose Development/Moose/Utilities/Enums.lua @@ -0,0 +1,15 @@ +ENUMS = {} + +ENUMS.ROE = { + HoldFire = 1, + ReturnFire = 2, + OpenFire = 3, + WeaponFree = 4 + } + +ENUMS.ROT = { + NoReaction = 1, + PassiveDefense = 2, + EvadeFire = 3, + Vertical = 4 +} \ No newline at end of file diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index cca67c3f0..ab38769f4 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -553,12 +553,15 @@ function AIRBASE:GetParkingSpotsTable(termtype) local spots={} for _,_spot in pairs(parkingdata) do if AIRBASE._CheckTerminalType(_spot.Term_Type, termtype) then + self:I({_spot=_spot}) local _free=_isfree(_spot) local _coord=COORDINATE:NewFromVec3(_spot.vTerminalPos) table.insert(spots, {Coordinate=_coord, TerminalID=_spot.Term_Index, TerminalType=_spot.Term_Type, TOAC=_spot.TO_AC, Free=_free, TerminalID0=_spot.Term_Index_0, DistToRwy=_spot.fDistToRW}) end end + self:I({ spots = spots } ) + return spots end @@ -708,6 +711,8 @@ function AIRBASE:FindFreeParkingSpotForAircraft(group, terminaltype, scanradius, local _spot=parkingspot.Coordinate -- Core.Point#COORDINATE local _termid=parkingspot.TerminalID + self:I({_termid=_termid}) + if AIRBASE._CheckTerminalType(parkingspot.TerminalType, terminaltype) then -- Very safe uses the DCS getParking() info to check if a spot is free. Unfortunately, the function returns free=false until the aircraft has actually taken-off. @@ -787,7 +792,7 @@ function AIRBASE:FindFreeParkingSpotForAircraft(group, terminaltype, scanradius, --_spot:MarkToAll(string.format("Parking spot %d free=%s", parkingspot.TerminalID, tostring(not occupied))) if occupied then - self:T(string.format("%s: Parking spot id %d occupied.", airport, _termid)) + self:I(string.format("%s: Parking spot id %d occupied.", airport, _termid)) else self:I(string.format("%s: Parking spot id %d free.", airport, _termid)) if nvalid<_nspots then