diff --git a/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua b/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua index 557c9bc13..e0484ee64 100644 --- a/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua +++ b/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua @@ -4108,7 +4108,7 @@ do -- AI_A2G_DISPATCHER self:F( { "Target ID", DetectedItem.ItemID } ) - self:F( { DefenseLimit = self.DefenseLimitmit, DefenseTotal = DefenseTotal } ) + self:F( { DefenseLimit = self.DefenseLimit, DefenseTotal = DefenseTotal } ) DetectedSet:Flush( self ) local DetectedID = DetectedItem.ID @@ -4149,7 +4149,7 @@ do -- AI_A2G_DISPATCHER end end - if EngageCoordinate and DefenseTotal < self.DefenseLimit then + if EngageCoordinate and ( ( self.DefenseLimit and DefenseTotal < self.DefenseLimit ) or true ) then do local DefendersTotal, DefendersEngaged, DefendersMissing, Friendlies = self:Evaluate_SEAD( DetectedItem ) -- Returns a SET_UNIT with the SEAD targets to be engaged... if DefendersMissing > 0 then diff --git a/Moose Development/Moose/AI/AI_Escort.lua b/Moose Development/Moose/AI/AI_Escort.lua index 713129c0d..1e2807e5a 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,127 @@ 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 ) + + self:SetFlightModeFormation( EscortGroup ) + end + ) + + end --- Set a Detection method for the EscortUnit to be reported upon. @@ -276,7 +381,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 +396,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:MenusHelicopters( 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 +682,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 +797,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 +864,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 +887,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 +908,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 +940,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 +953,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 +998,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 +1005,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 +1061,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 +1068,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 +1163,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 +1282,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 +1301,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:SetFlightModeMission( EscortGroup ) local PointFrom = {} local GroupVec3 = EscortGroup:GetUnit(1):GetVec3() @@ -1131,7 +1344,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 +1364,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:SetFlightModeFormation( 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 +1442,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 +1466,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 +1488,7 @@ end function AI_ESCORT:_ReportNearbyTargetsNow( EscortGroup ) - local EscortUnit = self.EscortUnit + local EscortUnit = self.PlayerUnit self:_ReportTargetsScheduler( EscortGroup ) @@ -1283,21 +1497,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 +1536,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 +1562,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( "Resuming route.", MESSAGE.Type.Information, PlayerGroup ) + else + EscortGroup:MessageTypeToGroup( "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:SetFlightModeMission( 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 +1613,11 @@ function AI_ESCORT:_AttackTarget( EscortGroup, DetectedItem ) self:F( EscortGroup ) - local EscortUnit = self.EscortUnit - - self:ReleaseFormation( EscortGroup ) + self:SetFlightModeAttack( 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,46 @@ 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 +1864,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 = DetectedItemReportSummary:Text("\n") + local DetectedMenu = self.Detection:DetectedItemReportMenu( DetectedItem, EscortGroup, _DATABASE:GetPlayerSettings( self.PlayerUnit:GetPlayerName() ) ):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 +1906,17 @@ function AI_ESCORT:_ReportTargetsScheduler( EscortGroup ) end + EscortMenuAttackTargets:RemoveSubMenus( TimeUpdate, "Escort" ) + + 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 +1931,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 +1946,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 +1984,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..1b7a4288f --- /dev/null +++ b/Moose Development/Moose/AI/AI_Escort_Request.lua @@ -0,0 +1,292 @@ +--- **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 ) + + self:SetFlightModeFormation( EscortGroup ) + self:FormationTrail() + + 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..fea9c7c0e 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 = "M", + Formation = "F", + Attack = "A", + Reconnaissance = "R", +} + + + --- 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,15 @@ 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 } ) + + XStart = XStart or self.XStart + XSpace = XSpace or self.XSpace + YStart = YStart or self.YStart + YSpace = YSpace or self.YSpace + ZStart = ZStart or self.ZStart + ZSpace = ZSpace or self.ZSpace FollowGroupSet:Flush( self ) @@ -682,6 +724,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 +744,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 +763,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 +784,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 +803,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 +822,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 +842,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 +880,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 +940,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 +959,95 @@ function AI_FORMATION:SetFlightRandomization( FlightRandomization ) --R2.1 end ---- This releases the air unit in your flight from the formation flight. +--- Gets your escorts to flight mode. -- @param #AI_FORMATION self -- @param Wrapper.Group#GROUP FollowGroup FollowGroup. -- @return #AI_FORMATION -function AI_FORMATION:ReleaseFormation( FollowGroup ) +function AI_FORMATION:GetFlightMode( FollowGroup ) - FollowGroup.Following = false + if FollowGroup then + FollowGroup:SetState( FollowGroup, "PreviousMode", FollowGroup:GetState( FollowGroup, "Mode" ) ) + FollowGroup:SetState( FollowGroup, "Mode", self.__Enum.Mode.Mission ) + end + + + return FollowGroup:GetState( FollowGroup, "Mode" ) +end + + + +--- This sets your escorts to fly a mission. +-- @param #AI_FORMATION self +-- @param Wrapper.Group#GROUP FollowGroup FollowGroup. +-- @return #AI_FORMATION +function AI_FORMATION:SetFlightModeMission( FollowGroup ) + + 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:SetFlightModeAttack( 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:SetFlightModeFormation( 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 +1074,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,13 +1095,15 @@ 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 + if FollowGroup:GetState( FollowGroup, "Mode" ) == self.__Enum.Mode.Formation then + self:I({Mode=FollowGroup:GetState( FollowGroup, "Mode" )}) + FollowGroup:OptionROTEvadeFire() FollowGroup:OptionROEReturnFire() @@ -1055,8 +1164,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 +1201,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..ff6387c5a 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,21 @@ 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 then + if type(Callsign) ~= "number" 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 + end self:T3( { "Template:", SpawnTemplate } ) return SpawnTemplate diff --git a/Moose Development/Moose/Core/Spot.lua b/Moose Development/Moose/Core/Spot.lua index dbc3ea683..6395cd7b4 100644 --- a/Moose Development/Moose/Core/Spot.lua +++ b/Moose Development/Moose/Core/Spot.lua @@ -126,6 +126,39 @@ do -- @param Wrapper.Positionable#POSITIONABLE Target -- @param #number LaserCode Laser code. -- @param #number Duration Duration of lasing in seconds. + + self:AddTransition( "Off", "LaseOnCoordinate", "On" ) + + --- LaseOnCoordinate Handler OnBefore for SPOT. + -- @function [parent=#SPOT] OnBeforeLaseOnCoordinate + -- @param #SPOT self + -- @param #string From + -- @param #string Event + -- @param #string To + -- @return #boolean + + --- LaseOnCoordinate Handler OnAfter for SPOT. + -- @function [parent=#SPOT] OnAfterLaseOnCoordinate + -- @param #SPOT self + -- @param #string From + -- @param #string Event + -- @param #string To + + --- LaseOnCoordinate Trigger for SPOT. + -- @function [parent=#SPOT] LaseOnCoordinate + -- @param #SPOT self + -- @param Core.Point#COORDINATE Coordinate The coordinate to lase. + -- @param #number LaserCode Laser code. + -- @param #number Duration Duration of lasing in seconds. + + --- LaseOn Asynchronous Trigger for SPOT + -- @function [parent=#SPOT] __LaseOn + -- @param #SPOT self + -- @param #number Delay + -- @param Wrapper.Positionable#POSITIONABLE Target + -- @param #number LaserCode Laser code. + -- @param #number Duration Duration of lasing in seconds. + self:AddTransition( "On", "Lasing", "On" ) @@ -194,7 +227,8 @@ do return self end - --- @param #SPOT self + --- On after LaseOn event. Activates the laser spot. + -- @param #SPOT self -- @param From -- @param Event -- @param To @@ -226,6 +260,43 @@ do self:__Lasing( -1 ) end + + + --- On after LaseOnCoordinate event. Activates the laser spot. + -- @param #SPOT self + -- @param From + -- @param Event + -- @param To + -- @param Core.Point#COORDINATE Coordinate The coordinate at which the laser is pointing. + -- @param #number LaserCode Laser code. + -- @param #number Duration Duration of lasing in seconds. + function SPOT:onafterLaseOnCoordinate(From, Event, To, Coordinate, LaserCode, Duration) + self:F( { "LaseOnCoordinate", Coordinate, LaserCode, Duration } ) + + local function StopLase( self ) + self:LaseOff() + end + + self.Target = nil + self.TargetCoord=Coordinate + self.LaserCode = LaserCode + + self.Lasing = true + + local RecceDcsUnit = self.Recce:GetDCSObject() + + local aimat=Coordinate + aimat.y=aimat.y+1 + + self.SpotIR = Spot.createInfraRed( RecceDcsUnit, { x = 0, y = 2, z = 0 }, aimat:GetVec3() ) + self.SpotLaser = Spot.createLaser( RecceDcsUnit, { x = 0, y = 2, z = 0 }, aimat:GetVec3(), LaserCode ) + + if Duration then + self.ScheduleID = self.LaseScheduler:Schedule( self, StopLase, {self}, Duration ) + end + + self:__Lasing(-1) + end --- @param #SPOT self -- @param Core.Event#EVENTDATA EventData @@ -246,10 +317,20 @@ do -- @param To function SPOT:onafterLasing( From, Event, To ) - if self.Target:IsAlive() then + if self.Target and self.Target:IsAlive() then self.SpotIR:setPoint( self.Target:GetPointVec3():AddY(1):AddY(math.random(-100,100)/100):AddX(math.random(-100,100)/100):GetVec3() ) self.SpotLaser:setPoint( self.Target:GetPointVec3():AddY(1):GetVec3() ) self:__Lasing( -0.2 ) + elseif self.TargetCoord then + + local aimat=self.TargetCoord --Core.Point#COORDINATE + aimat.y=aimat.y+1+math.random(-100,100)/100 + aimat.x=aimat.x+math.random(-100,100)/100 + + self.SpotIR:setPoint(aimat:GetVec3()) + self.SpotLaser:setPoint(self.TargetCoord:GetVec3()) + + self:__Lasing( -0.2 ) else self:F( { "Target is not alive", self.Target:IsAlive() } ) end 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 7a946024f..5d9af594b 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' ) @@ -91,6 +92,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/Ops/RescueHelo.lua b/Moose Development/Moose/Ops/RescueHelo.lua index f2fa53b80..dff109c8f 100644 --- a/Moose Development/Moose/Ops/RescueHelo.lua +++ b/Moose Development/Moose/Ops/RescueHelo.lua @@ -235,7 +235,7 @@ RESCUEHELO.UID=0 --- Class version. -- @field #string version -RESCUEHELO.version="1.0.6" +RESCUEHELO.version="1.0.7" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -927,6 +927,9 @@ function RESCUEHELO:onafterStart(From, Event, To) -- Formation parameters. self.formation:FormationCenterWing(-self.offsetX, 50, math.abs(self.altitude), 50, self.offsetZ, 50) + -- Formation mode. + self.formation:SetFlightModeFormation(self.helo) + -- Start formation FSM. self.formation:__Start(delay) 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 diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index 02a4bda39..0e182e5d6 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -1,13 +1,13 @@ --- **Wrapper** -- CONTROLLABLE is an intermediate class wrapping Group and Unit classes "controllers". --- +-- -- === --- +-- -- ### Author: **FlightControl** --- --- ### Contributions: --- +-- +-- ### Contributions: +-- -- === --- +-- -- @module Wrapper.Controllable -- @image Wrapper_Controllable.JPG @@ -27,26 +27,26 @@ -- * Manage the "state" of the DCS Controllable. -- -- # 1) CONTROLLABLE constructor --- +-- -- The CONTROLLABLE class provides the following functions to construct a CONTROLLABLE instance: -- -- * @{#CONTROLLABLE.New}(): Create a CONTROLLABLE instance. -- -- # 2) CONTROLLABLE Task methods --- --- Several controllable task methods are available that help you to prepare tasks. +-- +-- Several controllable task methods are available that help you to prepare tasks. -- These methods return a string consisting of the task description, which can then be given to either a @{Wrapper.Controllable#CONTROLLABLE.PushTask} or @{Wrapper.Controllable#SetTask} method to assign the task to the CONTROLLABLE. --- Tasks are specific for the category of the CONTROLLABLE, more specific, for AIR, GROUND or AIR and GROUND. +-- Tasks are specific for the category of the CONTROLLABLE, more specific, for AIR, GROUND or AIR and GROUND. -- Each task description where applicable indicates for which controllable category the task is valid. -- There are 2 main subdivisions of tasks: Assigned tasks and EnRoute tasks. --- +-- -- ## 2.1) Task assignment --- +-- -- Assigned task methods make the controllable execute the task where the location of the (possible) targets of the task are known before being detected. -- This is different from the EnRoute tasks, where the targets of the task need to be detected before the task can be executed. --- +-- -- Find below a list of the **assigned task** methods: --- +-- -- * @{#CONTROLLABLE.TaskAttackGroup}: (AIR) Attack a Controllable. -- * @{#CONTROLLABLE.TaskAttackMapObject}: (AIR) Attacking the map object (building, structure, e.t.c). -- * @{#CONTROLLABLE.TaskAttackUnit}: (AIR) Attack the Unit. @@ -54,7 +54,7 @@ -- * @{#CONTROLLABLE.TaskBombingRunway}: (AIR) Delivering weapon on the runway. -- * @{#CONTROLLABLE.TaskEmbarking}: (AIR) Move the controllable to a Vec2 Point, wait for a defined duration and embark a controllable. -- * @{#CONTROLLABLE.TaskEmbarkToTransport}: (GROUND) Embark to a Transport landed at a location. --- * @{#CONTROLLABLE.TaskEscort}: (AIR) Escort another airborne controllable. +-- * @{#CONTROLLABLE.TaskEscort}: (AIR) Escort another airborne controllable. -- * @{#CONTROLLABLE.TaskFAC_AttackGroup}: (AIR + GROUND) The task makes the controllable/unit a FAC and orders the FAC to control the target (enemy ground controllable) destruction. -- * @{#CONTROLLABLE.TaskFireAtPoint}: (GROUND) Fire some or all ammunition at a VEC2 point. -- * @{#CONTROLLABLE.TaskFollow}: (AIR) Following another airborne controllable. @@ -72,9 +72,9 @@ -- * @{#CONTROLLABLE.TaskReturnToBase}: (AIR) Route the controllable to an airbase. -- -- ## 2.2) EnRoute assignment --- +-- -- EnRoute tasks require the targets of the task need to be detected by the controllable (using its sensors) before the task can be executed: --- +-- -- * @{#CONTROLLABLE.EnRouteTaskAWACS}: (AIR) Aircraft will act as an AWACS for friendly units (will provide them with information about contacts). No parameters. -- * @{#CONTROLLABLE.EnRouteTaskEngageControllable}: (AIR) Engaging a controllable. The task does not assign the target controllable to the unit/controllable to attack now; it just allows the unit/controllable to engage the target controllable as well as other assigned targets. -- * @{#CONTROLLABLE.EnRouteTaskEngageTargets}: (AIR) Engaging targets of defined types. @@ -83,96 +83,96 @@ -- * @{#CONTROLLABLE.EnRouteTaskFAC}: (AIR + GROUND) The task makes the controllable/unit a FAC and lets the FAC to choose a targets (enemy ground controllable) around as well as other assigned targets. -- * @{#CONTROLLABLE.EnRouteTaskFAC_EngageControllable}: (AIR + GROUND) The task makes the controllable/unit a FAC and lets the FAC to choose the target (enemy ground controllable) as well as other assigned targets. -- * @{#CONTROLLABLE.EnRouteTaskTanker}: (AIR) Aircraft will act as a tanker for friendly units. No parameters. --- +-- -- ## 2.3) Task preparation --- +-- -- There are certain task methods that allow to tailor the task behaviour: -- -- * @{#CONTROLLABLE.TaskWrappedAction}: Return a WrappedAction Task taking a Command. -- * @{#CONTROLLABLE.TaskCombo}: Return a Combo Task taking an array of Tasks. -- * @{#CONTROLLABLE.TaskCondition}: Return a condition section for a controlled task. -- * @{#CONTROLLABLE.TaskControlled}: Return a Controlled Task taking a Task and a TaskCondition. --- +-- -- ## 2.4) Call a function as a Task --- +-- -- A function can be called which is part of a Task. The method @{#CONTROLLABLE.TaskFunction}() prepares -- a Task that can call a GLOBAL function from within the Controller execution. -- This method can also be used to **embed a function call when a certain waypoint has been reached**. -- See below the **Tasks at Waypoints** section. --- +-- -- Demonstration Mission: [GRP-502 - Route at waypoint to random point](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/release-2-2-pre/GRP - Group Commands/GRP-502 - Route at waypoint to random point) --- +-- -- ## 2.5) Tasks at Waypoints --- +-- -- Special Task methods are available to set tasks at certain waypoints. -- The method @{#CONTROLLABLE.SetTaskWaypoint}() helps preparing a Route, embedding a Task at the Waypoint of the Route. --- +-- -- This creates a Task element, with an action to call a function as part of a Wrapped Task. --- +-- -- ## 2.6) Obtain the mission from controllable templates --- +-- -- Controllable templates contain complete mission descriptions. Sometimes you want to copy a complete mission from a controllable and assign it to another: --- +-- -- * @{#CONTROLLABLE.TaskMission}: (AIR + GROUND) Return a mission task from a mission template. -- -- # 3) Command methods --- +-- -- Controllable **command methods** prepare the execution of commands using the @{#CONTROLLABLE.SetCommand} method: --- +-- -- * @{#CONTROLLABLE.CommandDoScript}: Do Script command. -- * @{#CONTROLLABLE.CommandSwitchWayPoint}: Perform a switch waypoint command. --- +-- -- # 4) Routing of Controllables --- +-- -- Different routing methods exist to route GROUPs and UNITs to different locations: --- --- * @{#CONTROLLABLE.Route}(): Make the Controllable to follow a given route. +-- +-- * @{#CONTROLLABLE.Route}(): Make the Controllable to follow a given route. -- * @{#CONTROLLABLE.RouteGroundTo}(): Make the GROUND Controllable to drive towards a specific coordinate. --- * @{#CONTROLLABLE.RouteAirTo}(): Make the AIR Controllable to fly towards a specific coordinate. --- +-- * @{#CONTROLLABLE.RouteAirTo}(): Make the AIR Controllable to fly towards a specific coordinate. +-- -- # 5) Option methods --- +-- -- Controllable **Option methods** change the behaviour of the Controllable while being alive. --- +-- -- ## 5.1) Rule of Engagement: --- --- * @{#CONTROLLABLE.OptionROEWeaponFree} +-- +-- * @{#CONTROLLABLE.OptionROEWeaponFree} -- * @{#CONTROLLABLE.OptionROEOpenFire} -- * @{#CONTROLLABLE.OptionROEReturnFire} -- * @{#CONTROLLABLE.OptionROEEvadeFire} --- +-- -- To check whether an ROE option is valid for a specific controllable, use: --- --- * @{#CONTROLLABLE.OptionROEWeaponFreePossible} +-- +-- * @{#CONTROLLABLE.OptionROEWeaponFreePossible} -- * @{#CONTROLLABLE.OptionROEOpenFirePossible} -- * @{#CONTROLLABLE.OptionROEReturnFirePossible} -- * @{#CONTROLLABLE.OptionROEEvadeFirePossible} --- +-- -- ## 5.2) Reaction On Thread: --- +-- -- * @{#CONTROLLABLE.OptionROTNoReaction} -- * @{#CONTROLLABLE.OptionROTPassiveDefense} -- * @{#CONTROLLABLE.OptionROTEvadeFire} -- * @{#CONTROLLABLE.OptionROTVertical} --- +-- -- To test whether an ROT option is valid for a specific controllable, use: --- +-- -- * @{#CONTROLLABLE.OptionROTNoReactionPossible} -- * @{#CONTROLLABLE.OptionROTPassiveDefensePossible} -- * @{#CONTROLLABLE.OptionROTEvadeFirePossible} -- * @{#CONTROLLABLE.OptionROTVerticalPossible} --- +-- -- ## 5.3) Alarm state: --- +-- -- * @{#CONTROLLABLE.OptionAlarmStateAuto} -- * @{#CONTROLLABLE.OptionAlarmStateGreen} -- * @{#CONTROLLABLE.OptionAlarmStateRed} --- +-- -- ## 5.4) Jettison weapons: --- +-- -- * @{#CONTROLLABLE.OptionAllowJettisonWeaponsOnThreat} -- * @{#CONTROLLABLE.OptionKeepWeaponsOnThreat} --- +-- -- @field #CONTROLLABLE CONTROLLABLE = { ClassName = "CONTROLLABLE", @@ -188,7 +188,7 @@ function CONTROLLABLE:New( ControllableName ) local self = BASE:Inherit( self, POSITIONABLE:New( ControllableName ) ) -- #CONTROLLABLE --self:F( ControllableName ) self.ControllableName = ControllableName - + self.TaskScheduler = SCHEDULER:New( self ) return self end @@ -215,12 +215,12 @@ end --- Returns the health. Dead controllables have health <= 1.0. -- @param #CONTROLLABLE self -- @return #number The controllable health value (unit or group average). --- @return #nil The controllable is not existing or alive. +-- @return #nil The controllable is not existing or alive. function CONTROLLABLE:GetLife() self:F2( self.ControllableName ) local DCSControllable = self:GetDCSObject() - + if DCSControllable then local UnitLife = 0 local Units = self:GetUnits() @@ -237,19 +237,19 @@ function CONTROLLABLE:GetLife() end return UnitLife end - + return nil end --- Returns the initial health. -- @param #CONTROLLABLE self -- @return #number The controllable health value (unit or group average). --- @return #nil The controllable is not existing or alive. +-- @return #nil The controllable is not existing or alive. function CONTROLLABLE:GetLife0() self:F2( self.ControllableName ) local DCSControllable = self:GetDCSObject() - + if DCSControllable then local UnitLife = 0 local Units = self:GetUnits() @@ -266,14 +266,14 @@ function CONTROLLABLE:GetLife0() end return UnitLife end - + return nil end --- Returns relative minimum amount of fuel (from 0.0 to 1.0) a unit or group has in its internal tanks. -- This method returns nil to ensure polymorphic behaviour! This method needs to be overridden by GROUP or UNIT. -- @param #CONTROLLABLE self --- @return #nil The CONTROLLABLE is not existing or alive. +-- @return #nil The CONTROLLABLE is not existing or alive. function CONTROLLABLE:GetFuelMin() self:F( self.ControllableName ) @@ -283,7 +283,7 @@ end --- Returns relative average amount of fuel (from 0.0 to 1.0) a unit or group has in its internal tanks. -- This method returns nil to ensure polymorphic behaviour! This method needs to be overridden by GROUP or UNIT. -- @param #CONTROLLABLE self --- @return #nil The CONTROLLABLE is not existing or alive. +-- @return #nil The CONTROLLABLE is not existing or alive. function CONTROLLABLE:GetFuelAve() self:F( self.ControllableName ) @@ -293,7 +293,7 @@ end --- Returns relative amount of fuel (from 0.0 to 1.0) the unit has in its internal tanks. -- This method returns nil to ensure polymorphic behaviour! This method needs to be overridden by GROUP or UNIT. -- @param #CONTROLLABLE self --- @return #nil The CONTROLLABLE is not existing or alive. +-- @return #nil The CONTROLLABLE is not existing or alive. function CONTROLLABLE:GetFuel() self:F( self.ControllableName ) @@ -347,13 +347,13 @@ function CONTROLLABLE:PushTask( DCSTask, WaitTime ) local DCSControllable = self:GetDCSObject() if DCSControllable then - + local DCSControllableName = self:GetName() -- When a controllable SPAWNs, it takes about a second to get the controllable in the simulator. Setting tasks to unspawned controllables provides unexpected results. -- Therefore we schedule the functions to set the mission and options for the Controllable. - -- Controller:pushTask( DCSTask ) - + -- Controller:pushTask( DCSTask ) + local function PushTask( Controller, DCSTask ) if self and self:IsAlive() then local Controller = self:_GetController() @@ -388,7 +388,7 @@ function CONTROLLABLE:SetTask( DCSTask, WaitTime ) if DCSControllable then local DCSControllableName = self:GetName() - + self:T2( "Controllable Name = " .. DCSControllableName ) -- When a controllable SPAWNs, it takes about a second to get the controllable in the simulator. Setting tasks to unspawned controllables provides unexpected results. @@ -400,7 +400,7 @@ function CONTROLLABLE:SetTask( DCSTask, WaitTime ) local Controller = self:_GetController() --self:I( "Before SetTask" ) Controller:setTask( DCSTask ) - -- AI_FORMATION class (used by RESCUEHELO) calls SetTask twice per second! hence spamming the DCS log file ==> setting this to trace. + -- AI_FORMATION class (used by RESCUEHELO) calls SetTask twice per second! hence spamming the DCS log file ==> setting this to trace. self:T( { ControllableName = self:GetName(), DCSTask = DCSTask } ) else BASE:E( { DCSControllableName .. " is not alive anymore.", DCSTask = DCSTask } ) @@ -453,13 +453,13 @@ function CONTROLLABLE:TaskCondition( time, userFlag, userFlagValue, condition, d self:F2( { time, userFlag, userFlagValue, condition, duration, lastWayPoint } ) --[[ - StopCondition = { - time = Time, - userFlag = string, - userFlagValue = boolean, - condition = string, - duration = Time, - lastWaypoint = number, + StopCondition = { + time = Time, + userFlag = string, + userFlagValue = boolean, + condition = string, + duration = Time, + lastWaypoint = number, } --]] @@ -512,7 +512,7 @@ function CONTROLLABLE:TaskCombo( DCSTasks ) tasks = DCSTasks } } - + for TaskID, Task in ipairs( DCSTasks ) do self:T( Task ) end @@ -529,7 +529,7 @@ function CONTROLLABLE:TaskWrappedAction( DCSCommand, Index ) self:F2( { DCSCommand } ) local DCSTaskWrappedAction - + DCSTaskWrappedAction = { id = "WrappedAction", enabled = true, @@ -586,14 +586,14 @@ end -- @usage -- --- This test demonstrates the use(s) of the SwitchWayPoint method of the GROUP class. -- HeliGroup = GROUP:FindByName( "Helicopter" ) --- +-- -- --- Route the helicopter back to the FARP after 60 seconds. -- -- We use the SCHEDULER class to do this. -- SCHEDULER:New( nil, -- function( HeliGroup ) -- local CommandRTB = HeliGroup:CommandSwitchWayPoint( 2, 8 ) -- HeliGroup:SetCommand( CommandRTB ) --- end, { HeliGroup }, 90 +-- end, { HeliGroup }, 90 -- ) function CONTROLLABLE:CommandSwitchWayPoint( FromWayPoint, ToWayPoint ) self:F2( { FromWayPoint, ToWayPoint } ) @@ -614,11 +614,11 @@ end -- Use the result in the method @{#CONTROLLABLE.SetCommand}(). -- A value of true will make the ground group stop, a value of false will make it continue. -- Note that this can only work on GROUP level, although individual UNITs can be commanded, the whole GROUP will react. --- --- Example missions: --- +-- +-- Example missions: +-- -- * GRP-310 --- +-- -- @param #CONTROLLABLE self -- @param #boolean StopRoute true if the ground unit needs to stop, false if it needs to continue to move. -- @return DCS#Task @@ -643,7 +643,7 @@ end -- @return #CONTROLLABLE self function CONTROLLABLE:StartUncontrolled(delay) if delay and delay>0 then - SCHEDULER:New(nil, CONTROLLABLE.StartUncontrolled, {self}, delay) + SCHEDULER:New(nil, CONTROLLABLE.StartUncontrolled, {self}, delay) else self:SetCommand({id='Start', params={}}) end @@ -669,13 +669,13 @@ function CONTROLLABLE:CommandActivateBeacon(Type, System, Frequency, UnitID, Cha AA=AA or self:IsAir() UnitID=UnitID or self:GetID() - + -- Command local CommandActivateBeacon= { id = "ActivateBeacon", params = { ["type"] = Type, - ["system"] = System, + ["system"] = System, ["frequency"] = Frequency, ["unitId"] = UnitID, ["channel"] = Channel, @@ -685,13 +685,13 @@ function CONTROLLABLE:CommandActivateBeacon(Type, System, Frequency, UnitID, Cha ["bearing"] = Bearing, } } - + if Delay and Delay>0 then - SCHEDULER:New(nil, self.CommandActivateBeacon, {self, Type, System, Frequency, UnitID, Channel, ModeChannel, AA, Callsign, Bearing}, Delay) - else + SCHEDULER:New(nil, self.CommandActivateBeacon, {self, Type, System, Frequency, UnitID, Channel, ModeChannel, AA, Callsign, Bearing}, Delay) + else self:SetCommand(CommandActivateBeacon) end - + return self end @@ -711,14 +711,14 @@ function CONTROLLABLE:CommandActivateICLS(Channel, UnitID, Callsign, Delay) params= { ["type"] = BEACON.Type.ICLS, ["channel"] = Channel, - ["unitId"] = UnitID, + ["unitId"] = UnitID, ["callsign"] = Callsign, } } if Delay and Delay>0 then - SCHEDULER:New(nil, self.CommandActivateICLS, {self}, Delay) - else + SCHEDULER:New(nil, self.CommandActivateICLS, {self}, Delay) + else self:SetCommand(CommandActivateICLS) end @@ -732,13 +732,13 @@ end -- @return #CONTROLLABLE self function CONTROLLABLE:CommandDeactivateBeacon(Delay) self:F() - - -- Command to deactivate + + -- Command to deactivate local CommandDeactivateBeacon={id='DeactivateBeacon', params={}} if Delay and Delay>0 then - SCHEDULER:New(nil, self.CommandActivateBeacon, {self}, Delay) - else + SCHEDULER:New(nil, self.CommandActivateBeacon, {self}, Delay) + else self:SetCommand(CommandDeactivateBeacon) end @@ -751,13 +751,13 @@ end -- @return #CONTROLLABLE self function CONTROLLABLE:CommandDeactivateICLS(Delay) self:F() - - -- Command to deactivate + + -- Command to deactivate local CommandDeactivateICLS={id='DeactivateICLS', params={}} if Delay and Delay>0 then - SCHEDULER:New(nil, self.CommandDeactivateICLS, {self}, Delay) - else + SCHEDULER:New(nil, self.CommandDeactivateICLS, {self}, Delay) + else self:SetCommand(CommandDeactivateICLS) end @@ -772,13 +772,13 @@ end -- @return #CONTROLLABLE self function CONTROLLABLE:CommandSetCallsign(CallName, CallNumber, Delay) self:F() - - -- Command to set the callsign. + + -- Command to set the callsign. local CommandSetCallsign={id='SetCallsign', params={callname=CallName, callnumber=CallNumber or 1}} if Delay and Delay>0 then SCHEDULER:New(nil, self.CommandSetCallsign, {self, CallName, CallNumber}, Delay) - else + else self:SetCommand(CommandSetCallsign) end @@ -792,15 +792,15 @@ end -- @return #CONTROLLABLE self function CONTROLLABLE:CommandEPLRS(SwitchOnOff, Delay) self:F() - + if SwitchOnOff==nil then SwitchOnOff=true end - + -- ID local _id=self:GetID() - - -- Command to set the callsign. + + -- Command to set the callsign. local CommandEPLRS={id='EPLRS', params={value=SwitchOnOff, groupId=_id}} if Delay and Delay>0 then @@ -822,12 +822,12 @@ function CONTROLLABLE:TaskEPLRS(SwitchOnOff, idx) -- ID local _id=self:GetID() - - -- Command to set the callsign. + + -- Command to set the callsign. local CommandEPLRS={id='EPLRS', params={value=SwitchOnOff, groupId=_id}} return self:TaskWrappedAction(CommandEPLRS, idx or 1) - + end @@ -909,7 +909,7 @@ function CONTROLLABLE:TaskAttackUnit(AttackUnit, GroupAttack, WeaponExpend, Atta self:F2({self.ControllableName, AttackUnit, GroupAttack, WeaponExpend, AttackQty, Direction, Altitude, WeaponType}) local DCSTask - DCSTask = { + DCSTask = { id = 'AttackUnit', params = { unitId = AttackUnit:GetID(), @@ -926,12 +926,12 @@ function CONTROLLABLE:TaskAttackUnit(AttackUnit, GroupAttack, WeaponExpend, Atta } self:T3( DCSTask ) - + return DCSTask end ---- (AIR) Delivering weapon at the point on the ground. +--- (AIR) Delivering weapon at the point on the ground. -- @param #CONTROLLABLE self -- @param DCS#Vec2 Vec2 2D-coordinates of the point to deliver weapon at. -- @param #boolean GroupAttack (optional) If true, all units in the group will attack the Unit when found. @@ -944,34 +944,34 @@ end -- @return DCS#Task The DCS task structure. function CONTROLLABLE:TaskBombing( Vec2, GroupAttack, WeaponExpend, AttackQty, Direction, Altitude, WeaponType, Divebomb ) self:E( { self.ControllableName, Vec2, GroupAttack, WeaponExpend, AttackQty, Direction, Altitude, WeaponType, Divebomb } ) - + local _groupattack=false if GroupAttack then _groupattack=GroupAttack end - + local _direction=0 local _directionenabled=false if Direction then _direction=math.rad(Direction) _directionenabled=true end - + local _altitude=5000 local _altitudeenabled=false if Altitude then _altitude=Altitude _altitudeenabled=true end - + local _attacktype=nil if Divebomb then _attacktype="Dive" end - + local DCSTask - DCSTask = { + DCSTask = { id = 'Bombing', params = { x = Vec2.x, @@ -981,10 +981,10 @@ function CONTROLLABLE:TaskBombing( Vec2, GroupAttack, WeaponExpend, AttackQty, D attackQtyLimit = false, --AttackQty and true or false, attackQty = AttackQty or 1, directionEnabled = _directionenabled, - direction = _direction, + direction = _direction, altitudeEnabled = _altitudeenabled, altitude = _altitude, - weaponType = WeaponType, + weaponType = WeaponType, --attackType=_attacktype, }, } @@ -1007,19 +1007,19 @@ function CONTROLLABLE:TaskAttackMapObject( Vec2, GroupAttack, WeaponExpend, Atta self:F2( { self.ControllableName, Vec2, GroupAttack, WeaponExpend, AttackQty, Direction, Altitude, WeaponType } ) local DCSTask - DCSTask = { + DCSTask = { id = 'AttackMapObject', params = { point = Vec2, groupAttack = GroupAttack or false, expend = WeaponExpend or "Auto", attackQtyLimit = AttackQty and true or false, - attackQty = AttackQty, + attackQty = AttackQty, directionEnabled = Direction and true or false, - direction = Direction, + direction = Direction, altitudeEnabled = Altitude and true or false, altitude = Altitude or 30, - weaponType = WeaponType, + weaponType = WeaponType, }, }, @@ -1078,12 +1078,12 @@ end -- @param Core.Point#COORDINATE Coord Coordinate at which the CONTROLLABLE orbits. -- @param #number Altitude Altitude in meters of the orbit pattern. -- @param #number Speed Speed [m/s] flying the orbit pattern --- @param Core.Point#COORDINATE CoordRaceTrack (Optional) If this coordinate is specified, the CONTROLLABLE will fly a race-track pattern using this and the initial coordinate. +-- @param Core.Point#COORDINATE CoordRaceTrack (Optional) If this coordinate is specified, the CONTROLLABLE will fly a race-track pattern using this and the initial coordinate. -- @return #CONTROLLABLE self function CONTROLLABLE:TaskOrbit(Coord, Altitude, Speed, CoordRaceTrack) local Pattern=AI.Task.OrbitPattern.CIRCLE - + local P1=Coord:GetVec2() local P2=nil if CoordRaceTrack then @@ -1142,16 +1142,16 @@ end --- (AIR) Delivering weapon on the runway. See [hoggit](https://wiki.hoggitworld.com/view/DCS_task_bombingRunway) --- +-- -- Make sure the aircraft has the following role: --- +-- -- * CAS -- * Ground Attack -- * Runway Attack -- * Anti-Ship Strike -- * AFAC -- * Pinpoint Strike --- +-- -- @param #CONTROLLABLE self -- @param Wrapper.Airbase#AIRBASE Airbase Airbase to attack. -- @param #number WeaponType (optional) Bitmask of weapon types those allowed to use. See [DCS enum weapon flag](https://wiki.hoggitworld.com/view/DCS_enum_weapon_flag). Default 2147485694 = AnyBomb (GuidedBomb + AnyUnguidedBomb). @@ -1163,16 +1163,16 @@ end function CONTROLLABLE:TaskBombingRunway(Airbase, WeaponType, WeaponExpend, AttackQty, Direction, GroupAttack) self:F2( { self.ControllableName, Airbase, WeaponType, WeaponExpend, AttackQty, Direction, GroupAttack } ) --- BombingRunway = { --- id = 'BombingRunway', --- params = { +-- BombingRunway = { +-- id = 'BombingRunway', +-- params = { -- runwayId = AirdromeId, --- weaponType = number, +-- weaponType = number, -- expend = enum AI.Task.WeaponExpend, --- attackQty = number, --- direction = Azimuth, --- groupAttack = boolean, --- } +-- attackQty = number, +-- direction = Azimuth, +-- groupAttack = boolean, +-- } -- } -- Defaults. @@ -1184,11 +1184,11 @@ function CONTROLLABLE:TaskBombingRunway(Airbase, WeaponType, WeaponExpend, Attac DCSTask = { id = 'BombingRunway', params = { runwayId = Airbase:GetID(), - weaponType = WeaponType, + weaponType = WeaponType, expend = WeaponExpend, - attackQty = AttackQty, - direction = Direction, - groupAttack = GroupAttack, + attackQty = AttackQty, + direction = Direction, + groupAttack = GroupAttack, }, }, @@ -1203,9 +1203,9 @@ end function CONTROLLABLE:TaskRefueling() self:F2( { self.ControllableName } ) --- Refueling = { --- id = 'Refueling', --- params = {} +-- Refueling = { +-- id = 'Refueling', +-- params = {} -- } local DCSTask={id='Refueling', params={}} @@ -1231,22 +1231,22 @@ function CONTROLLABLE:TaskLandAtVec2( Point, Duration ) -- duration = Time -- } -- } - + local DCSTask if Duration and Duration > 0 then - DCSTask = { id = 'Land', - params = { - point = Point, - durationFlag = true, + DCSTask = { id = 'Land', + params = { + point = Point, + durationFlag = true, duration = Duration, - }, + }, } else - DCSTask = { id = 'Land', - params = { - point = Point, - durationFlag = false, - }, + DCSTask = { id = 'Land', + params = { + point = Point, + durationFlag = false, + }, } end @@ -1277,9 +1277,9 @@ end ---- (AIR) Following another airborne controllable. --- The unit / controllable will follow lead unit of another controllable, wingmens of both controllables will continue following their leaders. --- If another controllable is on land the unit / controllable will orbit around. +--- (AIR) Following another airborne controllable. +-- The unit / controllable will follow lead unit of another controllable, wingmens of both controllables will continue following their leaders. +-- If another controllable is on land the unit / controllable will orbit around. -- @param #CONTROLLABLE self -- @param Wrapper.Controllable#CONTROLLABLE FollowControllable The controllable to be followed. -- @param DCS#Vec3 Vec3 Position of the unit / lead unit of the controllable relative lead unit of another controllable in frame reference oriented by course of lead unit of another controllable. If another controllable is on land the unit / controllable will orbit around. @@ -1295,16 +1295,16 @@ function CONTROLLABLE:TaskFollow( FollowControllable, Vec3, LastWaypointIndex ) -- pos = Vec3, -- lastWptIndexFlag = boolean, -- lastWptIndex = number --- } +-- } -- } local LastWaypointIndexFlag = false if LastWaypointIndex then LastWaypointIndexFlag = true end - + local DCSTask - DCSTask = { + DCSTask = { id = 'Follow', params = { groupId = FollowControllable:GetID(), @@ -1319,15 +1319,15 @@ function CONTROLLABLE:TaskFollow( FollowControllable, Vec3, LastWaypointIndex ) end ---- (AIR) Escort another airborne controllable. --- The unit / controllable will follow lead unit of another controllable, wingmens of both controllables will continue following their leaders. +--- (AIR) Escort another airborne controllable. +-- The unit / controllable will follow lead unit of another controllable, wingmens of both controllables will continue following their leaders. -- The unit / controllable will also protect that controllable from threats of specified types. -- @param #CONTROLLABLE self -- @param Wrapper.Controllable#CONTROLLABLE FollowControllable The controllable to be escorted. -- @param DCS#Vec3 Vec3 Position of the unit / lead unit of the controllable relative lead unit of another controllable in frame reference oriented by course of lead unit of another controllable. If another controllable is on land the unit / controllable will orbit around. -- @param #number LastWaypointIndex Detach waypoint of another controllable. Once reached the unit / controllable Follow task is finished. --- @param #number EngagementDistance Maximal distance from escorted controllable to threat. If the threat is already engaged by escort escort will disengage if the distance becomes greater than 1.5 * engagementDistMax. --- @param DCS#AttributeNameArray TargetTypes Array of AttributeName that is contains threat categories allowed to engage. +-- @param #number EngagementDistance Maximal distance from escorted controllable to threat. If the threat is already engaged by escort escort will disengage if the distance becomes greater than 1.5 * engagementDistMax. +-- @param DCS#AttributeNameArray TargetTypes Array of AttributeName that is contains threat categories allowed to engage. -- @return DCS#Task The DCS task structure. function CONTROLLABLE:TaskEscort( FollowControllable, Vec3, LastWaypointIndex, EngagementDistance, TargetTypes ) self:F2( { self.ControllableName, FollowControllable, Vec3, LastWaypointIndex, EngagementDistance, TargetTypes } ) @@ -1341,16 +1341,16 @@ function CONTROLLABLE:TaskEscort( FollowControllable, Vec3, LastWaypointIndex, E -- lastWptIndex = number, -- engagementDistMax = Distance, -- targetTypes = array of AttributeName, --- } +-- } -- } local LastWaypointIndexFlag = false if LastWaypointIndex then LastWaypointIndexFlag = true end - + TargetTypes=TargetTypes or {} - + local DCSTask DCSTask = { id = 'Escort', params = { @@ -1375,7 +1375,7 @@ end -- @param DCS#Vec2 Vec2 The point to fire at. -- @param DCS#Distance Radius The radius of the zone to deploy the fire at. -- @param #number AmmoCount (optional) Quantity of ammunition to expand (omit to fire until ammunition is depleted). --- @param #number WeaponType (optional) Enum for weapon type ID. This value is only required if you want the group firing to use a specific weapon, for instance using the task on a ship to force it to fire guided missiles at targets within cannon range. See http://wiki.hoggit.us/view/DCS_enum_weapon_flag +-- @param #number WeaponType (optional) Enum for weapon type ID. This value is only required if you want the group firing to use a specific weapon, for instance using the task on a ship to force it to fire guided missiles at targets within cannon range. See http://wiki.hoggit.us/view/DCS_enum_weapon_flag -- @return DCS#Task The DCS task structure. function CONTROLLABLE:TaskFireAtPoint( Vec2, Radius, AmmoCount, WeaponType ) self:F2( { self.ControllableName, Vec2, Radius, AmmoCount, WeaponType } ) @@ -1386,7 +1386,7 @@ function CONTROLLABLE:TaskFireAtPoint( Vec2, Radius, AmmoCount, WeaponType ) -- point = Vec2, -- radius = Distance, -- expendQty = number, - -- expendQtyEnabled = boolean, + -- expendQtyEnabled = boolean, -- } -- } @@ -1394,21 +1394,18 @@ function CONTROLLABLE:TaskFireAtPoint( Vec2, Radius, AmmoCount, WeaponType ) DCSTask = { id = 'FireAtPoint', params = { - --point = Vec2, - x=Vec2.x, - y=Vec2.y, - zoneRadius=Radius, - expendQty=100, - expendQtyEnabled=false, - templateId="", + point = Vec2, + zoneRadius = Radius, + expendQty = 100, -- dummy value + expendQtyEnabled = false, } } - + if AmmoCount then DCSTask.params.expendQty = AmmoCount DCSTask.params.expendQtyEnabled = true end - + if WeaponType then DCSTask.params.weaponType=WeaponType end @@ -1423,10 +1420,10 @@ end function CONTROLLABLE:TaskHold() self:F2( { self.ControllableName } ) --- Hold = { --- id = 'Hold', --- params = { --- } +-- Hold = { +-- id = 'Hold', +-- params = { +-- } -- } local DCSTask @@ -1442,26 +1439,26 @@ end -- TASKS FOR AIRBORNE AND GROUND UNITS/CONTROLLABLES ---- (AIR + GROUND) The task makes the controllable/unit a FAC and orders the FAC to control the target (enemy ground controllable) destruction. +--- (AIR + GROUND) The task makes the controllable/unit a FAC and orders the FAC to control the target (enemy ground controllable) destruction. -- The killer is player-controlled allied CAS-aircraft that is in contact with the FAC. --- If the task is assigned to the controllable lead unit will be a FAC. +-- If the task is assigned to the controllable lead unit will be a FAC. -- @param #CONTROLLABLE self -- @param Wrapper.Controllable#CONTROLLABLE AttackGroup Target CONTROLLABLE. --- @param #number WeaponType Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. +-- @param #number WeaponType Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. -- @param DCS#AI.Task.Designation Designation (optional) Designation type. --- @param #boolean Datalink (optional) Allows to use datalink to send the target information to attack aircraft. Enabled by default. +-- @param #boolean Datalink (optional) Allows to use datalink to send the target information to attack aircraft. Enabled by default. -- @return DCS#Task The DCS task structure. function CONTROLLABLE:TaskFAC_AttackGroup( AttackGroup, WeaponType, Designation, Datalink ) self:F2( { self.ControllableName, AttackGroup, WeaponType, Designation, Datalink } ) --- FAC_AttackGroup = { --- id = 'FAC_AttackGroup', --- params = { +-- FAC_AttackGroup = { +-- id = 'FAC_AttackGroup', +-- params = { -- groupId = Group.ID, -- weaponType = number, -- designation = enum AI.Task.Designation, -- datalink = boolean --- } +-- } -- } local DCSTask @@ -1482,28 +1479,28 @@ end --- (AIR) Engaging targets of defined types. -- @param #CONTROLLABLE self --- @param DCS#Distance Distance Maximal distance from the target to a route leg. If the target is on a greater distance it will be ignored. --- @param DCS#AttributeNameArray TargetTypes Array of target categories allowed to engage. --- @param #number Priority All enroute tasks have the priority parameter. This is a number (less value - higher priority) that determines actions related to what task will be performed first. +-- @param DCS#Distance Distance Maximal distance from the target to a route leg. If the target is on a greater distance it will be ignored. +-- @param DCS#AttributeNameArray TargetTypes Array of target categories allowed to engage. +-- @param #number Priority All enroute tasks have the priority parameter. This is a number (less value - higher priority) that determines actions related to what task will be performed first. -- @return DCS#Task The DCS task structure. function CONTROLLABLE:EnRouteTaskEngageTargets( Distance, TargetTypes, Priority ) self:F2( { self.ControllableName, Distance, TargetTypes, Priority } ) --- EngageTargets ={ --- id = 'EngageTargets', --- params = { --- maxDist = Distance, --- targetTypes = array of AttributeName, --- priority = number --- } +-- EngageTargets ={ +-- id = 'EngageTargets', +-- params = { +-- maxDist = Distance, +-- targetTypes = array of AttributeName, +-- priority = number +-- } -- } local DCSTask DCSTask = { id = 'EngageTargets', params = { - maxDist = Distance, - targetTypes = TargetTypes, - priority = Priority + maxDist = Distance, + targetTypes = TargetTypes, + priority = Priority } } @@ -1515,31 +1512,31 @@ end --- (AIR) Engaging a targets of defined types at circle-shaped zone. -- @param #CONTROLLABLE self --- @param DCS#Vec2 Vec2 2D-coordinates of the zone. --- @param DCS#Distance Radius Radius of the zone. --- @param DCS#AttributeNameArray TargetTypes Array of target categories allowed to engage. --- @param #number Priority All en-route tasks have the priority parameter. This is a number (less value - higher priority) that determines actions related to what task will be performed first. +-- @param DCS#Vec2 Vec2 2D-coordinates of the zone. +-- @param DCS#Distance Radius Radius of the zone. +-- @param DCS#AttributeNameArray TargetTypes Array of target categories allowed to engage. +-- @param #number Priority All en-route tasks have the priority parameter. This is a number (less value - higher priority) that determines actions related to what task will be performed first. -- @return DCS#Task The DCS task structure. function CONTROLLABLE:EnRouteTaskEngageTargetsInZone( Vec2, Radius, TargetTypes, Priority ) self:F2( { self.ControllableName, Vec2, Radius, TargetTypes, Priority } ) --- EngageTargetsInZone = { --- id = 'EngageTargetsInZone', --- params = { --- point = Vec2, --- zoneRadius = Distance, --- targetTypes = array of AttributeName, --- priority = number +-- EngageTargetsInZone = { +-- id = 'EngageTargetsInZone', +-- params = { +-- point = Vec2, +-- zoneRadius = Distance, +-- targetTypes = array of AttributeName, +-- priority = number -- } -- } local DCSTask DCSTask = { id = 'EngageTargetsInZone', params = { - point = Vec2, - zoneRadius = Radius, - targetTypes = TargetTypes, - priority = Priority + point = Vec2, + zoneRadius = Radius, + targetTypes = TargetTypes, + priority = Priority } } @@ -1551,7 +1548,7 @@ end --- (AIR) Engaging a controllable. The task does not assign the target controllable to the unit/controllable to attack now; it just allows the unit/controllable to engage the target controllable as well as other assigned targets. -- @param #CONTROLLABLE self -- @param Wrapper.Controllable#CONTROLLABLE AttackGroup The Controllable to be attacked. --- @param #number Priority All en-route tasks have the priority parameter. This is a number (less value - higher priority) that determines actions related to what task will be performed first. +-- @param #number Priority All en-route tasks have the priority parameter. This is a number (less value - higher priority) that determines actions related to what task will be performed first. -- @param #number WeaponType (optional) Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. -- @param DCS#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. -- @param #number AttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. @@ -1612,7 +1609,7 @@ end --- (AIR) Search and attack the Unit. -- @param #CONTROLLABLE self -- @param Wrapper.Unit#UNIT EngageUnit The UNIT. --- @param #number Priority (optional) All en-route tasks have the priority parameter. This is a number (less value - higher priority) that determines actions related to what task will be performed first. +-- @param #number Priority (optional) All en-route tasks have the priority parameter. This is a number (less value - higher priority) that determines actions related to what task will be performed first. -- @param #boolean GroupAttack (optional) If true, all units in the group will attack the Unit when found. -- @param DCS#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. -- @param #number AttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. @@ -1668,10 +1665,10 @@ end function CONTROLLABLE:EnRouteTaskAWACS( ) self:F2( { self.ControllableName } ) --- AWACS = { --- id = 'AWACS', --- params = { --- } +-- AWACS = { +-- id = 'AWACS', +-- params = { +-- } -- } local DCSTask @@ -1691,10 +1688,10 @@ end function CONTROLLABLE:EnRouteTaskTanker( ) self:F2( { self.ControllableName } ) --- Tanker = { --- id = 'Tanker', --- params = { --- } +-- Tanker = { +-- id = 'Tanker', +-- params = { +-- } -- } local DCSTask @@ -1716,10 +1713,10 @@ end function CONTROLLABLE:EnRouteTaskEWR( ) self:F2( { self.ControllableName } ) --- EWR = { --- id = 'EWR', --- params = { --- } +-- EWR = { +-- id = 'EWR', +-- params = { +-- } -- } local DCSTask @@ -1733,30 +1730,30 @@ function CONTROLLABLE:EnRouteTaskEWR( ) end --- En-route tasks for airborne and ground units/controllables +-- En-route tasks for airborne and ground units/controllables ---- (AIR + GROUND) The task makes the controllable/unit a FAC and lets the FAC to choose the target (enemy ground controllable) as well as other assigned targets. +--- (AIR + GROUND) The task makes the controllable/unit a FAC and lets the FAC to choose the target (enemy ground controllable) as well as other assigned targets. -- The killer is player-controlled allied CAS-aircraft that is in contact with the FAC. --- If the task is assigned to the controllable lead unit will be a FAC. +-- If the task is assigned to the controllable lead unit will be a FAC. -- @param #CONTROLLABLE self -- @param Wrapper.Controllable#CONTROLLABLE AttackGroup Target CONTROLLABLE. --- @param #number Priority All en-route tasks have the priority parameter. This is a number (less value - higher priority) that determines actions related to what task will be performed first. --- @param #number WeaponType Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. +-- @param #number Priority All en-route tasks have the priority parameter. This is a number (less value - higher priority) that determines actions related to what task will be performed first. +-- @param #number WeaponType Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. -- @param DCS#AI.Task.Designation Designation (optional) Designation type. --- @param #boolean Datalink (optional) Allows to use datalink to send the target information to attack aircraft. Enabled by default. +-- @param #boolean Datalink (optional) Allows to use datalink to send the target information to attack aircraft. Enabled by default. -- @return DCS#Task The DCS task structure. function CONTROLLABLE:EnRouteTaskFAC_EngageGroup( AttackGroup, Priority, WeaponType, Designation, Datalink ) self:F2( { self.ControllableName, AttackGroup, WeaponType, Priority, Designation, Datalink } ) --- FAC_EngageControllable = { --- id = 'FAC_EngageControllable', --- params = { +-- FAC_EngageControllable = { +-- id = 'FAC_EngageControllable', +-- params = { -- groupId = Group.ID, -- weaponType = number, -- designation = enum AI.Task.Designation, -- datalink = boolean, -- priority = number, --- } +-- } -- } local DCSTask @@ -1775,22 +1772,22 @@ function CONTROLLABLE:EnRouteTaskFAC_EngageGroup( AttackGroup, Priority, WeaponT end ---- (AIR + GROUND) The task makes the controllable/unit a FAC and lets the FAC to choose a targets (enemy ground controllable) around as well as other assigned targets. +--- (AIR + GROUND) The task makes the controllable/unit a FAC and lets the FAC to choose a targets (enemy ground controllable) around as well as other assigned targets. -- The killer is player-controlled allied CAS-aircraft that is in contact with the FAC. --- If the task is assigned to the controllable lead unit will be a FAC. +-- If the task is assigned to the controllable lead unit will be a FAC. -- @param #CONTROLLABLE self -- @param DCS#Distance Radius The maximal distance from the FAC to a target. --- @param #number Priority All en-route tasks have the priority parameter. This is a number (less value - higher priority) that determines actions related to what task will be performed first. +-- @param #number Priority All en-route tasks have the priority parameter. This is a number (less value - higher priority) that determines actions related to what task will be performed first. -- @return DCS#Task The DCS task structure. function CONTROLLABLE:EnRouteTaskFAC( Radius, Priority ) self:F2( { self.ControllableName, Radius, Priority } ) --- FAC = { --- id = 'FAC', --- params = { +-- FAC = { +-- id = 'FAC', +-- params = { -- radius = Distance, -- priority = number --- } +-- } -- } local DCSTask @@ -1808,7 +1805,7 @@ end --[[ ---- Used in conjunction with the embarking task for a transport helicopter group. The Ground units will move to the specified location and wait to be picked up by a helicopter. +--- Used in conjunction with the embarking task for a transport helicopter group. The Ground units will move to the specified location and wait to be picked up by a helicopter. -- The helicopter will then fly them to their dropoff point defined by another task for the ground forces; DisembarkFromTransport task. -- The controllable has to be an infantry group! -- @param #CONTROLLABLE self @@ -1831,13 +1828,13 @@ function CONTROLLABLE:TaskEmbarkToTransport(Coordinate, Radius) return EmbarkToTransport end ---- Used in conjunction with the EmbarkToTransport task for a ground infantry group, the controlled helicopter flight will land at the specified coordinates, +--- Used in conjunction with the EmbarkToTransport task for a ground infantry group, the controlled helicopter flight will land at the specified coordinates, -- pick up boarding troops and transport them to that groups DisembarkFromTransport task. -- The CONTROLLABLE has to be a helicopter group! -- @param #CONTROLLABLE self -- @param Core.Set#SET_GROUP GroupSet Set of groups to be embarked by the controllable. -- @param Core.Point#COORDINATE Coordinate Coordinate of embarking. --- @param #number Duration Duration of embarking in seconds. +-- @param #number Duration Duration of embarking in seconds. -- @return #table Embarking task. function CONTROLLABLE:TaskEmbarking(GroupSet, Coordinate, Duration) @@ -1847,14 +1844,14 @@ function CONTROLLABLE:TaskEmbarking(GroupSet, Coordinate, Duration) local group=_group --Wrapper.Group#GROUP table.insert(gids, group:GetID()) end - + -- Group ID of controllable. local id=self:GetID() - + -- Distribution local distribution={} distribution[id]=gids - + local durationFlag=false if Duration then durationFlag=true @@ -1876,7 +1873,7 @@ function CONTROLLABLE:TaskEmbarking(GroupSet, Coordinate, Duration) y=Coordinate.z, } } - + self:E(DCStask) return DCStask @@ -1956,47 +1953,47 @@ end -- @param ... The variable arguments passed to the function when called! These arguments can be of any type! -- @return #CONTROLLABLE -- @usage --- --- local ZoneList = { --- ZONE:New( "ZONE1" ), --- ZONE:New( "ZONE2" ), --- ZONE:New( "ZONE3" ), --- ZONE:New( "ZONE4" ), --- ZONE:New( "ZONE5" ) +-- +-- local ZoneList = { +-- ZONE:New( "ZONE1" ), +-- ZONE:New( "ZONE2" ), +-- ZONE:New( "ZONE3" ), +-- ZONE:New( "ZONE4" ), +-- ZONE:New( "ZONE5" ) -- } --- +-- -- GroundGroup = GROUP:FindByName( "Vehicle" ) --- +-- -- --- @param Wrapper.Group#GROUP GroundGroup -- function RouteToZone( Vehicle, ZoneRoute ) --- +-- -- local Route = {} --- +-- -- Vehicle:E( { ZoneRoute = ZoneRoute } ) --- +-- -- Vehicle:MessageToAll( "Moving to zone " .. ZoneRoute:GetName(), 10 ) --- +-- -- -- Get the current coordinate of the Vehicle -- local FromCoord = Vehicle:GetCoordinate() --- +-- -- -- Select a random Zone and get the Coordinate of the new Zone. -- local RandomZone = ZoneList[ math.random( 1, #ZoneList ) ] -- Core.Zone#ZONE -- local ToCoord = RandomZone:GetCoordinate() --- +-- -- -- Create a "ground route point", which is a "point" structure that can be given as a parameter to a Task -- Route[#Route+1] = FromCoord:WaypointGround( 72 ) -- Route[#Route+1] = ToCoord:WaypointGround( 60, "Vee" ) --- +-- -- local TaskRouteToZone = Vehicle:TaskFunction( "RouteToZone", RandomZone ) --- +-- -- Vehicle:SetTaskWaypoint( Route[#Route], TaskRouteToZone ) -- Set for the given Route at Waypoint 2 the TaskRouteToZone. --- +-- -- Vehicle:Route( Route, math.random( 10, 20 ) ) -- Move after a random seconds to the Route. See the Route method for details. --- +-- -- end --- +-- -- RouteToZone( GroundGroup, ZoneList[1] ) --- +-- function CONTROLLABLE:TaskFunction( FunctionString, ... ) local DCSTask @@ -2045,31 +2042,31 @@ do -- Patrol methods -- @param #CONTROLLABLE self -- @return #CONTROLLABLE function CONTROLLABLE:PatrolRoute() - + local PatrolGroup = self -- Wrapper.Group#GROUP - + if not self:IsInstanceOf( "GROUP" ) then PatrolGroup = self:GetGroup() -- Wrapper.Group#GROUP end - + self:F( { PatrolGroup = PatrolGroup:GetName() } ) - + if PatrolGroup:IsGround() or PatrolGroup:IsShip() then - + local Waypoints = PatrolGroup:GetTemplateRoutePoints() - + -- Calculate the new Route. local FromCoord = PatrolGroup:GetCoordinate() local From = FromCoord:WaypointGround( 120 ) - + table.insert( Waypoints, 1, From ) local TaskRoute = PatrolGroup:TaskFunction( "CONTROLLABLE.PatrolRoute" ) - + self:F({Waypoints = Waypoints}) local Waypoint = Waypoints[#Waypoints] PatrolGroup:SetTaskWaypoint( Waypoint, TaskRoute ) -- Set for the given Route at Waypoint 2 the TaskRouteToZone. - + PatrolGroup:Route( Waypoints ) -- Move after a random seconds to the Route. See the Route method for details. end end @@ -2082,31 +2079,31 @@ do -- Patrol methods -- @param Core.Point#COORDINATE ToWaypoint The waypoint where the group should move to. -- @return #CONTROLLABLE function CONTROLLABLE:PatrolRouteRandom( Speed, Formation, ToWaypoint ) - + local PatrolGroup = self -- Wrapper.Group#GROUP - + if not self:IsInstanceOf( "GROUP" ) then PatrolGroup = self:GetGroup() -- Wrapper.Group#GROUP end self:F( { PatrolGroup = PatrolGroup:GetName() } ) - + if PatrolGroup:IsGround() or PatrolGroup:IsShip() then - + local Waypoints = PatrolGroup:GetTemplateRoutePoints() - + -- Calculate the new Route. local FromCoord = PatrolGroup:GetCoordinate() local FromWaypoint = 1 if ToWaypoint then FromWaypoint = ToWaypoint end - + -- Loop until a waypoint has been found that is not the same as the current waypoint. -- Otherwise the object zon't move or drive in circles and the algorithm would not do exactly -- what it is supposed to do, which is making groups drive around. local ToWaypoint - repeat + repeat -- Select a random waypoint and check if it is not the same waypoint as where the object is about. ToWaypoint = math.random( 1, #Waypoints ) until( ToWaypoint ~= FromWaypoint ) @@ -2118,12 +2115,12 @@ do -- Patrol methods local Route = {} Route[#Route+1] = FromCoord:WaypointGround( 0 ) Route[#Route+1] = ToCoord:WaypointGround( Speed, Formation ) - - + + local TaskRouteToZone = PatrolGroup:TaskFunction( "CONTROLLABLE.PatrolRouteRandom", Speed, Formation, ToWaypoint ) - + PatrolGroup:SetTaskWaypoint( Route[#Route], TaskRouteToZone ) -- Set for the given Route at Waypoint 2 the TaskRouteToZone. - + PatrolGroup:Route( Route, 1 ) -- Move after a random seconds to the Route. See the Route method for details. end end @@ -2136,41 +2133,41 @@ do -- Patrol methods -- @param #string Formation (Optional) Formation the group should use. -- @return #CONTROLLABLE function CONTROLLABLE:PatrolZones( ZoneList, Speed, Formation ) - + if not type( ZoneList ) == "table" then ZoneList = { ZoneList } end - + local PatrolGroup = self -- Wrapper.Group#GROUP - + if not self:IsInstanceOf( "GROUP" ) then PatrolGroup = self:GetGroup() -- Wrapper.Group#GROUP end self:F( { PatrolGroup = PatrolGroup:GetName() } ) - + if PatrolGroup:IsGround() or PatrolGroup:IsShip() then - + local Waypoints = PatrolGroup:GetTemplateRoutePoints() local Waypoint = Waypoints[math.random( 1, #Waypoints )] -- Select random waypoint. - + -- Calculate the new Route. local FromCoord = PatrolGroup:GetCoordinate() - + -- Select a random Zone and get the Coordinate of the new Zone. local RandomZone = ZoneList[ math.random( 1, #ZoneList ) ] -- Core.Zone#ZONE local ToCoord = RandomZone:GetRandomCoordinate( 10 ) - + -- Create a "ground route point", which is a "point" structure that can be given as a parameter to a Task local Route = {} Route[#Route+1] = FromCoord:WaypointGround( 20 ) Route[#Route+1] = ToCoord:WaypointGround( Speed, Formation ) - - + + local TaskRouteToZone = PatrolGroup:TaskFunction( "CONTROLLABLE.PatrolZones", ZoneList, Speed, Formation ) - + PatrolGroup:SetTaskWaypoint( Route[#Route], TaskRouteToZone ) -- Set for the given Route at Waypoint 2 the TaskRouteToZone. - + PatrolGroup:Route( Route, 1 ) -- Move after a random seconds to the Route. See the Route method for details. end end @@ -2201,9 +2198,9 @@ do -- Route methods -- @return #CONTROLLABLE self function CONTROLLABLE:RouteToVec2( Point, Speed ) self:F2( { Point, Speed } ) - + local ControllablePoint = self:GetUnit( 1 ):GetVec2() - + local PointFrom = {} PointFrom.x = ControllablePoint.x PointFrom.y = ControllablePoint.y @@ -2218,8 +2215,8 @@ do -- Route methods ["vangle"] = 0, ["steer"] = 2, } - - + + local PointTo = {} PointTo.x = Point.x PointTo.y = Point.y @@ -2234,17 +2231,17 @@ do -- Route methods ["vangle"] = 0, ["steer"] = 2, } - - + + local Points = { PointFrom, PointTo } - + self:T3( Points ) - + self:Route( Points ) - + return self end - + --- (AIR + GROUND) Make the Controllable move to a given point. -- @param #CONTROLLABLE self -- @param DCS#Vec3 Point The destination point in Vec3 format. @@ -2252,9 +2249,9 @@ do -- Route methods -- @return #CONTROLLABLE self function CONTROLLABLE:RouteToVec3( Point, Speed ) self:F2( { Point, Speed } ) - + local ControllableVec3 = self:GetUnit( 1 ):GetVec3() - + local PointFrom = {} PointFrom.x = ControllableVec3.x PointFrom.y = ControllableVec3.z @@ -2271,8 +2268,8 @@ do -- Route methods ["vangle"] = 0, ["steer"] = 2, } - - + + local PointTo = {} PointTo.x = Point.x PointTo.y = Point.z @@ -2289,19 +2286,19 @@ do -- Route methods ["vangle"] = 0, ["steer"] = 2, } - - + + local Points = { PointFrom, PointTo } - + self:T3( Points ) - + self:Route( Points ) - + return self end - - - + + + --- Make the controllable to follow a given route. -- @param #CONTROLLABLE self -- @param #table Route A table of Route Points. @@ -2309,17 +2306,17 @@ do -- Route methods -- @return #CONTROLLABLE The CONTROLLABLE. function CONTROLLABLE:Route( Route, DelaySeconds ) self:F2( Route ) - + local DCSControllable = self:GetDCSObject() if DCSControllable then local RouteTask = self:TaskRoute( Route ) -- Create a RouteTask, that will route the CONTROLLABLE to the Route. self:SetTask( RouteTask, DelaySeconds or 1 ) -- Execute the RouteTask after the specified seconds (default is 1). return self end - + return nil end - + --- Make the controllable to push follow a given route. -- @param #CONTROLLABLE self -- @param #table Route A table of Route Points. @@ -2327,40 +2324,40 @@ do -- Route methods -- @return #CONTROLLABLE The CONTROLLABLE. function CONTROLLABLE:RoutePush( Route, DelaySeconds ) self:F2( Route ) - + local DCSControllable = self:GetDCSObject() if DCSControllable then local RouteTask = self:TaskRoute( Route ) -- Create a RouteTask, that will route the CONTROLLABLE to the Route. self:PushTask( RouteTask, DelaySeconds or 1 ) -- Execute the RouteTask after the specified seconds (default is 1). return self end - + return nil end - - + + --- Stops the movement of the vehicle on the route. -- @param #CONTROLLABLE self -- @return #CONTROLLABLE function CONTROLLABLE:RouteStop() self:F(self:GetName() .. " RouteStop") - + local CommandStop = self:CommandStopRoute( true ) self:SetCommand( CommandStop ) - + end - + --- Resumes the movement of the vehicle on the route. -- @param #CONTROLLABLE self -- @return #CONTROLLABLE function CONTROLLABLE:RouteResume() self:F( self:GetName() .. " RouteResume") - + local CommandResume = self:CommandStopRoute( false ) self:SetCommand( CommandResume ) - + end - + --- Make the GROUND Controllable to drive towards a specific point. -- @param #CONTROLLABLE self -- @param Core.Point#COORDINATE ToCoordinate A Coordinate to drive to. @@ -2369,17 +2366,17 @@ do -- Route methods -- @param #number DelaySeconds Wait for the specified seconds before executing the Route. -- @return #CONTROLLABLE The CONTROLLABLE. function CONTROLLABLE:RouteGroundTo( ToCoordinate, Speed, Formation, DelaySeconds ) - + local FromCoordinate = self:GetCoordinate() - + local FromWP = FromCoordinate:WaypointGround() local ToWP = ToCoordinate:WaypointGround( Speed, Formation ) - + self:Route( { FromWP, ToWP }, DelaySeconds ) - + return self end - + --- Make the GROUND Controllable to drive towards a specific point using (mostly) roads. -- @param #CONTROLLABLE self -- @param Core.Point#COORDINATE ToCoordinate A Coordinate to drive to. @@ -2388,21 +2385,21 @@ do -- Route methods -- @param #string OffRoadFormation (Optional) The formation at initial and final waypoint. Default is "Off Road". -- @return #CONTROLLABLE The CONTROLLABLE. function CONTROLLABLE:RouteGroundOnRoad( ToCoordinate, Speed, DelaySeconds, OffRoadFormation ) - + -- Defaults. Speed=Speed or 20 DelaySeconds=DelaySeconds or 1 OffRoadFormation=OffRoadFormation or "Off Road" - + -- Get the route task. local route=self:TaskGroundOnRoad(ToCoordinate, Speed, OffRoadFormation) - + -- Route controllable to destination. self:Route( route, DelaySeconds ) - + return self end - + --- Make the TRAIN Controllable to drive towards a specific point using railroads. -- @param #CONTROLLABLE self -- @param Core.Point#COORDINATE ToCoordinate A Coordinate to drive to. @@ -2410,22 +2407,22 @@ do -- Route methods -- @param #number DelaySeconds (Optional) Wait for the specified seconds before executing the Route. Default is one second. -- @return #CONTROLLABLE The CONTROLLABLE. function CONTROLLABLE:RouteGroundOnRailRoads( ToCoordinate, Speed, DelaySeconds) - + -- Defaults. Speed=Speed or 20 DelaySeconds=DelaySeconds or 1 - + -- Get the route task. local route=self:TaskGroundOnRailRoads(ToCoordinate, Speed) - + -- Route controllable to destination. self:Route( route, DelaySeconds ) - - return self - end - - + return self + end + + + --- Make a task for a GROUND Controllable to drive towards a specific point using (mostly) roads. -- @param #CONTROLLABLE self -- @param Core.Point#COORDINATE ToCoordinate A Coordinate to drive to. @@ -2437,35 +2434,35 @@ do -- Route methods -- @return #boolean If true, path on road is possible. If false, task will route the group directly to its destination. function CONTROLLABLE:TaskGroundOnRoad( ToCoordinate, Speed, OffRoadFormation, Shortcut, FromCoordinate ) self:F2({ToCoordinate=ToCoordinate, Speed=Speed, OffRoadFormation=OffRoadFormation}) - + -- Defaults. Speed=Speed or 20 OffRoadFormation=OffRoadFormation or "Off Road" - + -- Initial (current) coordinate. FromCoordinate = FromCoordinate or self:GetCoordinate() - + -- Get path and path length on road including the end points (From and To). local PathOnRoad, LengthOnRoad, GotPath =FromCoordinate:GetPathOnRoad(ToCoordinate, true) - + -- Get the length only(!) on the road. local _,LengthRoad=FromCoordinate:GetPathOnRoad(ToCoordinate, false) - -- Off road part of the rout: Total=OffRoad+OnRoad. + -- Off road part of the rout: Total=OffRoad+OnRoad. local LengthOffRoad local LongRoad - + -- Calculate the direct distance between the initial and final points. local LengthDirect=FromCoordinate:Get2DDistance(ToCoordinate) - + if GotPath and LengthRoad then - + -- Off road part of the rout: Total=OffRoad+OnRoad. LengthOffRoad=LengthOnRoad-LengthRoad -- Length on road is 10 times longer than direct route or path on road is very short (<5% of total path). LongRoad=LengthOnRoad and ((LengthOnRoad > LengthDirect*10) or (LengthRoad/LengthOnRoad*100<5)) - + -- Debug info. self:T(string.format("Length on road = %.3f km", LengthOnRoad/1000)) self:T(string.format("Length directly = %.3f km", LengthDirect/1000)) @@ -2473,13 +2470,13 @@ do -- Route methods self:T(string.format("Length only road = %.3f km", LengthRoad/1000)) self:T(string.format("Length off road = %.3f km", LengthOffRoad/1000)) self:T(string.format("Percent on road = %.1f", LengthRoad/LengthOnRoad*100)) - + end - + -- Route, ground waypoints along road. local route={} local canroad=false - + -- Check if a valid path on road could be found. if GotPath and LengthRoad and LengthDirect > 2000 then -- if the length of the movement is less than 1 km, drive directly. -- Check whether the road is very long compared to direct path. @@ -2488,14 +2485,14 @@ do -- Route methods -- Road is long ==> we take the short cut. table.insert(route, FromCoordinate:WaypointGround(Speed, OffRoadFormation)) table.insert(route, ToCoordinate:WaypointGround(Speed, OffRoadFormation)) - + else -- Create waypoints. table.insert(route, FromCoordinate:WaypointGround(Speed, OffRoadFormation)) table.insert(route, PathOnRoad[2]:WaypointGround(Speed, "On Road")) table.insert(route, PathOnRoad[#PathOnRoad-1]:WaypointGround(Speed, "On Road")) - + -- Add the final coordinate because the final might not be on the road. local dist=ToCoordinate:Get2DDistance(PathOnRoad[#PathOnRoad-1]) if dist>10 then @@ -2503,16 +2500,16 @@ do -- Route methods table.insert(route, ToCoordinate:GetRandomCoordinateInRadius(10,5):WaypointGround(5, OffRoadFormation)) table.insert(route, ToCoordinate:GetRandomCoordinateInRadius(10,5):WaypointGround(5, OffRoadFormation)) end - + end - + canroad=true else - + -- No path on road could be found (can happen!) ==> Route group directly from A to B. table.insert(route, FromCoordinate:WaypointGround(Speed, OffRoadFormation)) table.insert(route, ToCoordinate:WaypointGround(Speed, OffRoadFormation)) - + end return route, canroad @@ -2525,31 +2522,31 @@ do -- Route methods -- @return Task function CONTROLLABLE:TaskGroundOnRailRoads(ToCoordinate, Speed) self:F2({ToCoordinate=ToCoordinate, Speed=Speed}) - + -- Defaults. Speed=Speed or 20 - + -- Current coordinate. local FromCoordinate = self:GetCoordinate() - + -- Get path and path length on railroad. local PathOnRail, LengthOnRail=FromCoordinate:GetPathOnRoad(ToCoordinate, false, true) - + -- Debug info. self:T(string.format("Length on railroad = %.3f km", LengthOnRail/1000)) - + -- Route, ground waypoints along road. local route={} - + -- Check if a valid path on railroad could be found. if PathOnRail then table.insert(route, PathOnRail[1]:WaypointGround(Speed, "On Railroad")) table.insert(route, PathOnRail[2]:WaypointGround(Speed, "On Railroad")) - + end - return route + return route end --- Make the AIR Controllable fly towards a specific point. @@ -2562,18 +2559,18 @@ do -- Route methods -- @param #number DelaySeconds Wait for the specified seconds before executing the Route. -- @return #CONTROLLABLE The CONTROLLABLE. function CONTROLLABLE:RouteAirTo( ToCoordinate, AltType, Type, Action, Speed, DelaySeconds ) - + local FromCoordinate = self:GetCoordinate() local FromWP = FromCoordinate:WaypointAir() - + local ToWP = ToCoordinate:WaypointAir( AltType, Type, Action, Speed ) - + self:Route( { FromWP, ToWP }, DelaySeconds ) - + return self end - - + + --- (AIR + GROUND) Route the controllable to a given zone. -- The controllable final destination point can be randomized. -- A speed can be given in km/h. @@ -2585,58 +2582,58 @@ do -- Route methods -- @param Base#FORMATION Formation The formation string. function CONTROLLABLE:TaskRouteToZone( Zone, Randomize, Speed, Formation ) self:F2( Zone ) - + local DCSControllable = self:GetDCSObject() - + if DCSControllable then - + local ControllablePoint = self:GetVec2() - + local PointFrom = {} PointFrom.x = ControllablePoint.x PointFrom.y = ControllablePoint.y PointFrom.type = "Turning Point" PointFrom.action = Formation or "Cone" PointFrom.speed = 20 / 3.6 - - + + local PointTo = {} local ZonePoint - + if Randomize then ZonePoint = Zone:GetRandomVec2() else ZonePoint = Zone:GetVec2() end - + PointTo.x = ZonePoint.x PointTo.y = ZonePoint.y PointTo.type = "Turning Point" - + if Formation then PointTo.action = Formation else PointTo.action = "Cone" end - + if Speed then PointTo.speed = Speed else PointTo.speed = 20 / 3.6 end - + local Points = { PointFrom, PointTo } - + self:T3( Points ) - + self:Route( Points ) - + return self end - + return nil end - + --- (GROUND) Route the controllable to a given Vec2. -- A speed can be given in km/h. -- A given formation can be given. @@ -2645,48 +2642,48 @@ do -- Route methods -- @param #number Speed The speed in m/s. Default is 5.555 m/s = 20 km/h. -- @param Base#FORMATION Formation The formation string. function CONTROLLABLE:TaskRouteToVec2( Vec2, Speed, Formation ) - + local DCSControllable = self:GetDCSObject() - + if DCSControllable then - + local ControllablePoint = self:GetVec2() - + local PointFrom = {} PointFrom.x = ControllablePoint.x PointFrom.y = ControllablePoint.y PointFrom.type = "Turning Point" PointFrom.action = Formation or "Cone" PointFrom.speed = 20 / 3.6 - - + + local PointTo = {} - + PointTo.x = Vec2.x PointTo.y = Vec2.y PointTo.type = "Turning Point" - + if Formation then PointTo.action = Formation else PointTo.action = "Cone" end - + if Speed then PointTo.speed = Speed else PointTo.speed = 20 / 3.6 end - + local Points = { PointFrom, PointTo } - + self:T3( Points ) - + self:Route( Points ) - + return self end - + return nil end @@ -2807,9 +2804,9 @@ function CONTROLLABLE:GetDetectedTargets( DetectVisual, DetectOptical, DetectRad local DetectionIRST = ( DetectIRST and DetectIRST == true ) and Controller.Detection.IRST or nil local DetectionRWR = ( DetectRWR and DetectRWR == true ) and Controller.Detection.RWR or nil local DetectionDLINK = ( DetectDLINK and DetectDLINK == true ) and Controller.Detection.DLINK or nil - + self:T2( { DetectionVisual, DetectionOptical, DetectionRadar, DetectionIRST, DetectionRWR, DetectionDLINK } ) - + return self:_GetController():getDetectedTargets( DetectionVisual, DetectionOptical, DetectionRadar, DetectionIRST, DetectionRWR, DetectionDLINK ) end @@ -2820,7 +2817,7 @@ function CONTROLLABLE:IsTargetDetected( DCSObject, DetectVisual, DetectOptical, self:F2( self.ControllableName ) local DCSControllable = self:GetDCSObject() - + if DCSControllable then local DetectionVisual = ( DetectVisual and DetectVisual == true ) and Controller.Detection.VISUAL or nil @@ -2834,7 +2831,7 @@ function CONTROLLABLE:IsTargetDetected( DCSObject, DetectVisual, DetectOptical, local TargetIsDetected, TargetIsVisible, TargetLastTime, TargetKnowType, TargetKnowDistance, TargetLastPos, TargetLastVelocity = Controller:isTargetDetected( DCSObject, DetectionVisual, DetectionOptical, DetectionRadar, DetectionIRST, DetectionRWR, DetectionDLINK ) - + return TargetIsDetected, TargetIsVisible, TargetLastTime, TargetKnowType, TargetKnowDistance, TargetLastPos, TargetLastVelocity end @@ -3174,8 +3171,9 @@ function CONTROLLABLE:OptionAlarmStateAuto() if self:IsGround() then Controller:setOption(AI.Option.Ground.id.ALARM_STATE, AI.Option.Ground.val.ALARM_STATE.AUTO) - elseif self:IsShip() then - Controller:setOption(AI.Option.Ground.id.ALARM_STATE, AI.Option.Ground.val.ALARM_STATE.AUTO) + elseif self:IsShip() then + --Controller:setOption(AI.Option.Naval.id.ALARM_STATE, AI.Option.Naval.val.ALARM_STATE.AUTO) + Controller:setOption(9, 0) end return self @@ -3198,7 +3196,8 @@ function CONTROLLABLE:OptionAlarmStateGreen() Controller:setOption( AI.Option.Ground.id.ALARM_STATE, AI.Option.Ground.val.ALARM_STATE.GREEN ) elseif self:IsShip() then -- AI.Option.Naval.id.ALARM_STATE does not seem to exist! - Controller:setOption( AI.Option.Ground.id.ALARM_STATE, AI.Option.Ground.val.ALARM_STATE.GREEN ) + --Controller:setOption( AI.Option.Naval.id.ALARM_STATE, AI.Option.Naval.val.ALARM_STATE.GREEN ) + Controller:setOption(9, 1) end return self @@ -3219,8 +3218,9 @@ function CONTROLLABLE:OptionAlarmStateRed() if self:IsGround() then Controller:setOption(AI.Option.Ground.id.ALARM_STATE, AI.Option.Ground.val.ALARM_STATE.RED) - elseif self:IsShip() then - Controller:setOption(AI.Option.Ground.id.ALARM_STATE, AI.Option.Ground.val.ALARM_STATE.RED) + elseif self:IsShip() then + --Controller:setOption(AI.Option.Naval.id.ALARM_STATE, AI.Option.Naval.val.ALARM_STATE.RED) + Controller:setOption(9, 2) end return self @@ -3409,4 +3409,3 @@ function CONTROLLABLE:IsAirPlane() return nil end - diff --git a/Moose Development/Moose/Wrapper/Positionable.lua b/Moose Development/Moose/Wrapper/Positionable.lua index 443192ed8..7f5bdd17f 100644 --- a/Moose Development/Moose/Wrapper/Positionable.lua +++ b/Moose Development/Moose/Wrapper/Positionable.lua @@ -1184,6 +1184,24 @@ function POSITIONABLE:LaseUnit( Target, LaserCode, Duration ) --R2.1 end +--- Start Lasing a COORDINATE. +-- @param #POSITIONABLE self +-- @param Core.Point#COORDIUNATE Coordinate The coordinate where the lase is pointing at. +-- @param #number LaserCode Laser code or random number in [1000, 9999]. +-- @param #number Duration Duration of lasing in seconds. +-- @return Core.Spot#SPOT +function POSITIONABLE:LaseCoordinate(Coordinate, LaserCode, Duration) + self:F2() + + LaserCode = LaserCode or math.random(1000, 9999) + + self.Spot = SPOT:New(self) -- Core.Spot#SPOT + self.Spot:LaseOnCoordinate(Coordinate, LaserCode, Duration) + self.LaserCode = LaserCode + + return self.Spot +end + --- Stop Lasing a POSITIONABLE -- @param #POSITIONABLE self -- @return #POSITIONABLE