From 4abdad5f35cc6683f06fc2a9ce24fae5158e41d3 Mon Sep 17 00:00:00 2001 From: FlightControl Date: Sat, 6 Apr 2019 13:04:28 +0200 Subject: [PATCH 1/7] First version --- Moose Development/Moose/AI/AI_Escort.lua | 1089 +++++++++++++++++++ Moose Development/Moose/AI/AI_Formation.lua | 235 ++-- Moose Development/Moose/Modules.lua | 1 + 3 files changed, 1225 insertions(+), 100 deletions(-) create mode 100644 Moose Development/Moose/AI/AI_Escort.lua diff --git a/Moose Development/Moose/AI/AI_Escort.lua b/Moose Development/Moose/AI/AI_Escort.lua new file mode 100644 index 000000000..0c4c6f8af --- /dev/null +++ b/Moose Development/Moose/AI/AI_Escort.lua @@ -0,0 +1,1089 @@ +--- **Functional** -- Taking the lead of AI escorting your flight or of other AI. +-- +-- === +-- +-- ## 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 whole 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. +-- +-- # RADIO MENUs that can be created: +-- +-- Find a summary below of the current available commands: +-- +-- ## Navigation ...: +-- +-- 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. +-- * **"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":** 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. +-- +-- ## 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 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 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. +-- +-- ## 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. +-- 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 +-- @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 + +--- AI_ESCORT class +-- +-- # AI_ESCORT construction methods. +-- +-- Create a new SPAWN 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. +-- +-- @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: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 +AI_ESCORT = { + ClassName = "AI_ESCORT", + EscortName = nil, -- The Escort Name + EscortUnit = nil, + EscortGroup = nil, + EscortMode = 1, + MODE = { + FOLLOW = 1, + MISSION = 2, + }, + Targets = {}, -- The identified targets + FollowScheduler = nil, + ReportTargets = true, + OptionROE = AI.Option.Air.val.ROE.OPEN_FIRE, + OptionReactionOnThreat = AI.Option.Air.val.REACTION_ON_THREAT.ALLOW_ABORT_MISSION, + SmokeDirectionVector = false, + TaskPoints = {} +} + +--- AI_ESCORT.Mode class +-- @type AI_ESCORT.MODE +-- @field #number FOLLOW +-- @field #number MISSION + +--- MENUPARAM type +-- @type MENUPARAM +-- @field #AI_ESCORT ParamSelf +-- @field #Distance ParamDistance +-- @field #function ParamFunction +-- @field #string ParamMessage + +--- AI_ESCORT class constructor for an AI group +-- @param #AI_ESCORT self +-- @param Wrapper.Client#CLIENT EscortUnit The client escorted by the EscortGroup. +-- @param Core.Set#SET_GROUP EscortGroupSet The set of group AI escorting the EscortUnit. +-- @param #string EscortName Name of the escort. +-- @param #string EscortBriefing A text showing the AI_ESCORT briefing to the player. Note that if no EscortBriefing is provided, the default briefing will be shown. +-- @return #AI_ESCORT self +-- @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: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." ) +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.EscortGroupSet = EscortGroupSet + self.EscortBriefing = EscortBriefing + + + + + +-- if not EscortBriefing then +-- EscortGroup:MessageToClient( EscortGroup:GetCategoryName() .. " '" .. EscortName .. "' (" .. EscortGroup:GetCallsign() .. ") reporting! " .. +-- "We're escorting your flight. " .. +-- "Use the Radio Menu and F10 and use the options under + " .. EscortName .. "\n", +-- 60, EscortUnit +-- ) +-- else +-- EscortGroup:MessageToClient( EscortGroup:GetCategoryName() .. " '" .. EscortName .. "' (" .. EscortGroup:GetCallsign() .. ") " .. EscortBriefing, +-- 60, EscortUnit +-- ) +-- end + + self.FollowDistance = 100 + self.CT1 = 0 + self.GT1 = 0 + + + 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 = {} + 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 + end + + EscortGroup.EscortMode = AI_ESCORT.MODE.FOLLOW + end + ) + + self.Detection = DETECTION_AREAS:New( EscortGroupSet, 5000 ) + + self.Detection:Start() + + return self +end + +function AI_ESCORT:onafterStart( EscortGroupSet ) + + self:E("Start") + + EscortGroupSet:ForEachGroup( + --- @param Core.Group#GROUP EscortGroup + function( EscortGroup ) + EscortGroup.EscortMenu = MENU_GROUP:New( self.EscortUnit:GetGroup(), EscortGroup:GetName() ) + EscortGroup:WayPointInitialize( 1 ) + + EscortGroup:OptionROTVertical() + EscortGroup:OptionROEOpenFire() + end + ) + +end + +--- Set a Detection method for the EscortUnit to be reported upon. +-- Detection methods are based on the derived classes from DETECTION_BASE. +-- @param #AI_ESCORT self +-- @param Function.Detection#DETECTION_BASE Detection +function AI_ESCORT:SetDetection( Detection ) + + self.Detection = Detection + self.EscortGroup.Detection = self.Detection + self.EscortUnit._EscortGroups[self.EscortGroup:GetName()].Detection = self.EscortGroup.Detection + + Detection:__Start( 1 ) + +end + +--- This function is for test, it will put on the frequency of the FollowScheduler a red smoke at the direction vector calculated for the escort to fly to. +-- This allows to visualize where the escort is flying to. +-- @param #AI_ESCORT self +-- @param #boolean SmokeDirection If true, then the direction vector will be smoked. +function AI_ESCORT:TestSmokeDirectionVector( SmokeDirection ) + self.SmokeDirectionVector = ( SmokeDirection == true ) and true or false +end + + +--- Defines the default menus +-- @param #AI_ESCORT self +-- @return #AI_ESCORT +function AI_ESCORT:Menus() + self:F() + +-- self:MenuScanForTargets( 100, 60 ) + + self:MenuHoldAtEscortPosition( 1000, 500 ) + self:MenuHoldAtLeaderPosition( 1000, 500 ) + + self:MenuFlare() + self:MenuSmoke() + + self:MenuReportTargets( 60 ) + self:MenuAssistedAttack() + self:MenuROE() + self:MenuEvasion() + +-- self:MenuResumeMission() + + + 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**. +-- @param #AI_ESCORT self +-- @param DCS#Distance Height Optional parameter that sets the height in meters to let the escort orbit at the current location. The default value is 30 meters. +-- @param DCS#Time Speed Optional parameter that lets the escort orbit with a specified speed. The default value is a speed that is average for the type of airplane or helicopter. +-- @param #string MenuTextFormat Optional parameter that shows the menu option text. The text string is formatted, and should contain two %d tokens in the string. The first for the Height, the second for the Time (if given). If no text is given, the default text will be displayed. +-- @return #AI_ESCORT +function AI_ESCORT:MenuHoldAtEscortPosition( Height, Speed, MenuTextFormat ) + self:F( { Height, Speed, MenuTextFormat } ) + + 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( "Hold at %d meter", Height ) + else + MenuText = string.format( "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 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 + ) + + 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 +-- @param DCS#Distance Height Optional parameter that sets the height in meters to let the escort orbit at the current location. The default value is 30 meters. +-- @param DCS#Time Speed Optional parameter that lets the escort orbit at the current position for a specified time. (not implemented yet). The default value is 0 seconds, meaning, that the escort will orbit forever until a sequent command is given. +-- @param #string MenuTextFormat Optional parameter that shows the menu option text. The text string is formatted, and should contain one or two %d tokens in the string. The first for the Height, the second for the Time (if given). If no text is given, the default text will be displayed. +-- @return #AI_ESCORT +function AI_ESCORT:MenuHoldAtLeaderPosition( Height, Speed, MenuTextFormat ) + self:F( { Height, Speed, MenuTextFormat } ) + + self.EscortGroupSet:ForEachGroupAlive( + --- @param Core.Group#GROUP EscortGroup + function( EscortGroup ) + if EscortGroup:IsAir() then + + if not self.EscortMenuHold then + self.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, + self.EscortMenuHold, + AI_ESCORT._HoldPosition, + self, + self.EscortUnit:GetGroup(), + EscortGroup, + Height, + Speed + ) + end + end + ) + + return self +end + +--- Defines a menu slot to let the escort scan for targets at a certain height for a certain time in seconds. +-- This menu will appear under **Scan targets**. +-- @param #AI_ESCORT self +-- @param DCS#Distance Height Optional parameter that sets the height in meters to let the escort orbit at the current location. The default value is 30 meters. +-- @param DCS#Time Seconds Optional parameter that lets the escort orbit at the current position for a specified time. (not implemented yet). The default value is 0 seconds, meaning, that the escort will orbit forever until a sequent command is given. +-- @param #string MenuTextFormat Optional parameter that shows the menu option text. The text string is formatted, and should contain one or two %d tokens in the string. The first for the Height, the second for the Time (if given). If no text is given, the default text will be displayed. +-- @return #AI_ESCORT +function AI_ESCORT:MenuScanForTargets( Height, Seconds, MenuTextFormat ) + self:F( { 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 ) + end + + if not Height then + Height = 100 + end + + if not Seconds then + Seconds = 30 + end + + local MenuText = "" + if not MenuTextFormat then + if Seconds == 0 then + MenuText = string.format( "At %d meter", Height ) + else + MenuText = string.format( "At %d meter for %d seconds", Height, Seconds ) + end + else + if Seconds == 0 then + MenuText = string.format( MenuTextFormat, Height ) + else + MenuText = string.format( MenuTextFormat, Height, Seconds ) + end + end + + if not self.EscortMenuScanForTargets then + self.EscortMenuScanForTargets = {} + end + + self.EscortMenuScanForTargets[#self.EscortMenuScanForTargets+1] = MENU_GROUP_COMMAND + :New( + self.EscortUnit:GetGroup(), + MenuText, + self.EscortMenuScan, + AI_ESCORT._ScanTargets, + self, + 30 + ) + 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**. +-- The flare will be fired from the first unit in the group. +-- @param #AI_ESCORT self +-- @param #string MenuTextFormat Optional parameter that shows the menu option text. If no text is given, the default text will be displayed. +-- @return #AI_ESCORT +function AI_ESCORT:MenuFlare( MenuTextFormat ) + self:F() + + 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 + ) + + 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. +-- The smoke will be fired from the first unit in the group. +-- @param #AI_ESCORT self +-- @param #string MenuTextFormat Optional parameter that shows the menu option text. If no text is given, the default text will be displayed. +-- @return #AI_ESCORT +function AI_ESCORT:MenuSmoke( MenuTextFormat ) + self:F() + + 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 + ) + + 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 ) + self:F( { Seconds } ) + + self.EscortGroupSet:ForEachGroupAlive( + --- @param Core.Group#GROUP EscortGroup + function( EscortGroup ) + if not EscortGroup.EscortMenuReportNearbyTargets then + EscortGroup.EscortMenuReportNearbyTargets = MENU_GROUP:New( self.EscortUnit:GetGroup(), "Report targets", EscortGroup.EscortMenu ) + end + + if not Seconds then + Seconds = 30 + 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 }, 1, Seconds ) + 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. +-- @param #AI_ESCORT self +-- @return #AI_ESCORT +function AI_ESCORT:MenuAssistedAttack() + self:F() + + -- 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", self.EscortMenu ) + + 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 ) + + 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 + ) + + 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 ) + 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 + ) + + return self +end + +--- Defines a menu to let the escort resume its mission from a waypoint on its route. +-- All rules of engagement will appear under the menu **Resume mission from**. +-- @param #AI_ESCORT self +-- @return #AI_ESCORT +function AI_ESCORT:MenuResumeMission() + self:F() + + if not self.EscortMenuResumeMission then + -- Mission Resume Menu Root + self.EscortMenuResumeMission = MENU_GROUP:New( self.EscortUnit:GetGroup(), "Resume mission from", self.EscortMenu ) + end + + return self +end + + +--- @param #AI_ESCORT self +-- @param Wrapper.Group#GROUP OrbitGroup +-- @param Wrapper.Group#GROUP EscortGroup +-- @param #number OrbitHeight +-- @param #number OrbitSeconds +function AI_ESCORT:_HoldPosition( OrbitGroup, EscortGroup, OrbitHeight, OrbitSeconds ) + + local EscortUnit = self.EscortUnit + + local OrbitUnit = OrbitGroup:GetUnit(1) -- Wrapper.Unit#UNIT + + self:ReleaseFormation( EscortGroup ) + + local PointFrom = {} + local GroupVec3 = EscortGroup:GetUnit(1):GetVec3() + PointFrom = {} + PointFrom.x = GroupVec3.x + PointFrom.y = GroupVec3.z + PointFrom.speed = 250 + PointFrom.type = AI.Task.WaypointType.TURNING_POINT + PointFrom.alt = GroupVec3.y + PointFrom.alt_type = AI.Task.AltitudeType.BARO + + local OrbitPoint = OrbitUnit:GetVec2() + local PointTo = {} + PointTo.x = OrbitPoint.x + PointTo.y = OrbitPoint.y + PointTo.speed = 250 + PointTo.type = AI.Task.WaypointType.TURNING_POINT + PointTo.alt = OrbitHeight + PointTo.alt_type = AI.Task.AltitudeType.BARO + PointTo.task = EscortGroup:TaskOrbitCircleAtVec2( OrbitPoint, OrbitHeight, 0 ) + + local Points = { PointFrom, PointTo } + + EscortGroup:OptionROEHoldFire() + EscortGroup:OptionROTPassiveDefense() + + EscortGroup:SetTask( EscortGroup:TaskRoute( Points ), 1 ) + EscortGroup:MessageTypeToGroup( "Orbiting at current location.", MESSAGE.Type.Information, EscortUnit:GetGroup() ) + +end + +--- @param #MENUPARAM MenuParam +function AI_ESCORT:_JoinUpAndFollow( Distance ) + + local EscortGroup = self.EscortGroup + local EscortUnit = self.EscortUnit + + self.Distance = Distance + + self:JoinUpAndFollow( EscortGroup, EscortUnit, self.Distance ) +end + + +--- @param #MENUPARAM MenuParam +function AI_ESCORT:_Flare( EscortGroup, Color, Message ) + + local EscortUnit = self.EscortUnit + + EscortGroup:GetUnit(1):Flare( Color ) + EscortGroup:MessageTypeToGroup( Message, MESSAGE.Type.Information, EscortUnit:GetGroup() ) +end + +--- @param #MENUPARAM MenuParam +function AI_ESCORT:_Smoke( EscortGroup, Color, Message ) + + local EscortUnit = self.EscortUnit + + EscortGroup:GetUnit(1):Smoke( Color ) + EscortGroup:MessageTypeToGroup( Message, MESSAGE.Type.Information, EscortUnit:GetGroup() ) +end + + +--- @param #MENUPARAM MenuParam +function AI_ESCORT:_ReportNearbyTargetsNow( EscortGroup ) + + local EscortUnit = self.EscortUnit + + self:_ReportTargetsScheduler( EscortGroup ) + +end + +function AI_ESCORT:_SwitchReportNearbyTargets( EscortGroup, ReportTargets ) + + local EscortUnit = self.EscortUnit + + self.ReportTargets = ReportTargets + + if self.ReportTargets then + if not EscortGroup.ReportTargetsScheduler then + EscortGroup.ReportTargetsScheduler:Schedule( self, self._ReportTargetsScheduler, {}, 1, 30 ) + end + else + routines.removeFunction( EscortGroup.ReportTargetsScheduler ) + EscortGroup.ReportTargetsScheduler = nil + end +end + +--- @param #MENUPARAM MenuParam +function AI_ESCORT:_ScanTargets( ScanDuration ) + + local EscortGroup = self.EscortGroup -- Wrapper.Group#GROUP + local EscortUnit = self.EscortUnit + + self.FollowScheduler:Stop( self.FollowSchedule ) + + if EscortGroup:IsHelicopter() then + EscortGroup:PushTask( + EscortGroup:TaskControlled( + EscortGroup:TaskOrbitCircle( 200, 20 ), + EscortGroup:TaskCondition( nil, nil, nil, nil, ScanDuration, nil ) + ), 1 ) + elseif EscortGroup:IsAirPlane() then + EscortGroup:PushTask( + EscortGroup:TaskControlled( + EscortGroup:TaskOrbitCircle( 1000, 500 ), + EscortGroup:TaskCondition( nil, nil, nil, nil, ScanDuration, nil ) + ), 1 ) + end + + EscortGroup:MessageToClient( "Scanning targets for " .. ScanDuration .. " seconds.", ScanDuration, EscortUnit ) + + if self.EscortMode == AI_ESCORT.MODE.FOLLOW then + self.FollowScheduler:Start( self.FollowSchedule ) + end + +end + +--- @param #AI_ESCORT self +-- @param Wrapper.Group#GROUP EscortGroup +function AI_ESCORT.___Resume( EscortGroup, self ) + + if EscortGroup.EscortMode == AI_ESCORT.MODE.FOLLOW then + self:JoinFormation( EscortGroup ) + end + +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 +function AI_ESCORT:_AttackTarget( EscortGroup, DetectedItem ) + + self:F( EscortGroup ) + + local EscortUnit = self.EscortUnit + + self:ReleaseFormation( EscortGroup ) + + if EscortGroup:IsAir() then + EscortGroup:OptionROEOpenFire() + EscortGroup:OptionROTPassiveDefense() + EscortGroup:SetState( EscortGroup, "Escort", self ) + + local DetectedSet = self.Detection:GetDetectedItemSet( DetectedItem ) + + local Tasks = {} + local AttackUnitTasks = {} + + DetectedSet:ForEachUnit( + --- @param Wrapper.Unit#UNIT DetectedUnit + function( DetectedUnit, Tasks ) + if DetectedUnit:IsAlive() then + AttackUnitTasks[#AttackUnitTasks+1] = EscortGroup:TaskAttackUnit( DetectedUnit ) + end + end, Tasks + ) + + Tasks[#Tasks+1] = EscortGroup:TaskCombo( AttackUnitTasks ) + Tasks[#Tasks+1] = EscortGroup:TaskFunction( "AI_ESCORT.___Resume", self, EscortGroup ) + + EscortGroup:SetTask( + EscortGroup:TaskCombo( + Tasks + ), 1 + ) + + else + + local DetectedSet = self.Detection:GetDetectedItemSet( DetectedItem ) + + local Tasks = {} + + DetectedSet:ForEachUnit( + --- @param Wrapper.Unit#UNIT DetectedUnit + function( DetectedUnit, Tasks ) + if DetectedUnit:IsAlive() then + Tasks[#Tasks+1] = EscortGroup:TaskFireAtPoint( DetectedUnit:GetVec2(), 50 ) + end + end, Tasks + ) + + EscortGroup:SetTask( + EscortGroup:TaskCombo( + Tasks + ), 1 + ) + + end + + EscortGroup:MessageTypeToGroup( "Engaging Designated Unit!", MESSAGE.Type.Information, EscortUnit ) + +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 +function AI_ESCORT:_AssistTarget( EscortGroup, DetectedItem ) + + local EscortUnit = self.EscortUnit + + local DetectedSet = self.Detection:GetDetectedItemSet( DetectedItem ) + + local Tasks = {} + + DetectedSet:ForEachUnit( + --- @param Wrapper.Unit#UNIT DetectedUnit + function( DetectedUnit, Tasks ) + if DetectedUnit:IsAlive() then + Tasks[#Tasks+1] = EscortGroup:TaskFireAtPoint( DetectedUnit:GetVec2(), 50 ) + end + end, Tasks + ) + + EscortGroup:SetTask( + EscortGroup:TaskCombo( + Tasks + ), 1 + ) + + + EscortGroup:MessageTypeToGroup( "Assisting with the destroying the enemy unit!", MESSAGE.Type.Information, EscortUnit:GetGroup() ) + +end + +--- @param #MENUPARAM MenuParam +function AI_ESCORT:_ROE( EscortGroup, EscortROEFunction, EscortROEMessage ) + + local EscortUnit = self.EscortUnit + + pcall( function() EscortROEFunction() end ) + EscortGroup:MessageTypeToGroup( EscortROEMessage, MESSAGE.Type.Information, EscortUnit:GetGroup() ) +end + +--- @param #MENUPARAM MenuParam +function AI_ESCORT:_ROT( EscortGroup, EscortROTFunction, EscortROTMessage ) + + local EscortUnit = self.EscortUnit + + pcall( function() EscortROTFunction() end ) + EscortGroup:MessageTypeToGroup( EscortROTMessage, MESSAGE.Type.Information, EscortUnit:GetGroup() ) +end + +--- @param #MENUPARAM MenuParam +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 ) +end + +--- Registers the waypoints +-- @param #AI_ESCORT self +-- @return #table +function AI_ESCORT:RegisterRoute() + self:F() + + local EscortGroup = self.EscortGroup -- Wrapper.Group#GROUP + + local TaskPoints = EscortGroup:GetTaskRoute() + + self:T( TaskPoints ) + + return TaskPoints +end + + + +--- Report Targets Scheduler. +-- @param #AI_ESCORT self +-- @param Wrapper.Group#GROUP EscortGroup +function AI_ESCORT:_ReportTargetsScheduler( EscortGroup ) + self:F( EscortGroup:GetName() ) + + if EscortGroup:IsAlive() and self.EscortUnit:IsAlive() then + + if true then + + local EscortGroupName = EscortGroup:GetName() + + EscortGroup.EscortMenuAttackNearbyTargets:RemoveSubMenus() + + if EscortGroup.EscortMenuTargetAssistance then + EscortGroup.EscortMenuTargetAssistance:RemoveSubMenus() + end + + local DetectedItems = self.Detection:GetDetectedItems() + self:F( DetectedItems ) + + local DetectedTargets = false + + local DetectedMsgs = {} + + local ClientEscortTargets = self.Detection + --local EscortUnit = EscortGroupData:GetUnit( 1 ) + + for DetectedItemIndex, DetectedItem in pairs( DetectedItems ) do + self:F( { DetectedItemIndex, DetectedItem } ) + + local DetectedItemReportSummary = self.Detection:DetectedItemReportSummary( DetectedItem, EscortGroup, _DATABASE:GetPlayerSettings( self.EscortUnit:GetPlayerName() ) ) + + if EscortGroup:IsAir() then + + local DetectedMsg = DetectedItemReportSummary:Text("\n") + DetectedMsgs[#DetectedMsgs+1] = DetectedMsg + + self:T( DetectedMsg ) + + MENU_GROUP_COMMAND:New( self.EscortUnit:GetGroup(), + DetectedMsg, + EscortGroup.EscortMenuAttackNearbyTargets, + AI_ESCORT._AttackTarget, + self, + EscortGroup, + DetectedItem + ) + else + if self.EscortMenuTargetAssistance then + + local DetectedMsg = DetectedItemReportSummary:Text("\n") + self:T( DetectedMsg ) + + local MenuTargetAssistance = MENU_GROUP:New( self.EscortUnit:GetGroup(), EscortGroupName, EscortGroup.EscortMenuTargetAssistance ) + MENU_GROUP_COMMAND:New( self.EscortUnit:GetGroup(), + DetectedMsg, + MenuTargetAssistance, + AI_ESCORT._AssistTarget, + self, + EscortGroup, + DetectedItem + ) + end + + end + DetectedTargets = true + end + self:F( DetectedMsgs ) + if DetectedTargets then + EscortGroup:MessageTypeToGroup( "Reporting detected targets:\n" .. table.concat( DetectedMsgs, "\n" ), MESSAGE.Type.Information, self.EscortUnit:GetGroup() ) + else + EscortGroup:MessageTypeToGroup( "No targets detected.", MESSAGE.Type.Information, self.EscortUnit:GetGroup() ) + end + + return true + else + end + end + + return false +end diff --git a/Moose Development/Moose/AI/AI_Formation.lua b/Moose Development/Moose/AI/AI_Formation.lua index c02096609..338d29738 100644 --- a/Moose Development/Moose/AI/AI_Formation.lua +++ b/Moose Development/Moose/AI/AI_Formation.lua @@ -136,6 +136,13 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin self.FollowUnit = FollowUnit -- Wrapper.Unit#UNIT self.FollowGroupSet = FollowGroupSet -- Core.Set#SET_GROUP + self.FollowGroupSet:ForEachGroup( + function( FollowGroup ) + self:E("Following") + FollowGroup.Following = true + end + ) + self:SetFlightRandomization( 2 ) self:SetStartState( "None" ) @@ -906,6 +913,31 @@ function AI_FORMATION:SetFlightRandomization( FlightRandomization ) --R2.1 end +--- This releases the air unit in your flight from the formation flight. +-- @param #AI_FORMATION self +-- @param Wrapper.Group#GROUP FollowGroup FollowGroup. +-- @return #AI_FORMATION +function AI_FORMATION:ReleaseFormation( FollowGroup ) + + FollowGroup.Following = false + + return self +end + + +--- This joins up the air unit in your formation flight. +-- @param #AI_FORMATION self +-- @param Wrapper.Group#GROUP FollowGroup FollowGroup. +-- @return #AI_FORMATION +function AI_FORMATION:JoinFormation( FollowGroup ) + + FollowGroup.Following = true + + return self +end + + + --- Stop function. Formation will not be updated any more. -- @param #AI_FORMATION self -- @param Core.Set#SET_GROUP FollowGroupSet The following set of groups. @@ -960,109 +992,112 @@ function AI_FORMATION:onenterFollowing( FollowGroupSet ) --R2.1 --- @param Wrapper.Group#GROUP FollowGroup -- @param Wrapper.Unit#UNIT ClientUnit function( FollowGroup, Formation, ClientUnit, CT1, CV1, CT2, CV2 ) + + if FollowGroup.Following == true then - FollowGroup:OptionROTEvadeFire() - FollowGroup:OptionROEReturnFire() - - local GroupUnit = FollowGroup:GetUnit( 1 ) - local FollowFormation = FollowGroup:GetState( self, "FormationVec3" ) - if FollowFormation then - local FollowDistance = FollowFormation.x - - local GT1 = GroupUnit:GetState( self, "GT1" ) - - if CT1 == nil or CT1 == 0 or GT1 == nil or GT1 == 0 then - GroupUnit:SetState( self, "GV1", GroupUnit:GetPointVec3() ) - GroupUnit:SetState( self, "GT1", timer.getTime() ) - else - local CD = ( ( CV2.x - CV1.x )^2 + ( CV2.y - CV1.y )^2 + ( CV2.z - CV1.z )^2 ) ^ 0.5 - local CT = CT2 - CT1 - - local CS = ( 3600 / CT ) * ( CD / 1000 ) / 3.6 - - local CDv = { x = CV2.x - CV1.x, y = CV2.y - CV1.y, z = CV2.z - CV1.z } - local Ca = math.atan2( CDv.x, CDv.z ) - + FollowGroup:OptionROTEvadeFire() + FollowGroup:OptionROEReturnFire() + + local GroupUnit = FollowGroup:GetUnit( 1 ) + local FollowFormation = FollowGroup:GetState( self, "FormationVec3" ) + if FollowFormation then + local FollowDistance = FollowFormation.x + local GT1 = GroupUnit:GetState( self, "GT1" ) - local GT2 = timer.getTime() - local GV1 = GroupUnit:GetState( self, "GV1" ) - local GV2 = GroupUnit:GetPointVec3() - GV2:AddX( math.random( -Formation.FlightRandomization / 2, Formation.FlightRandomization / 2 ) ) - GV2:AddY( math.random( -Formation.FlightRandomization / 2, Formation.FlightRandomization / 2 ) ) - GV2:AddZ( math.random( -Formation.FlightRandomization / 2, Formation.FlightRandomization / 2 ) ) - GroupUnit:SetState( self, "GT1", GT2 ) - GroupUnit:SetState( self, "GV1", GV2 ) - - - local GD = ( ( GV2.x - GV1.x )^2 + ( GV2.y - GV1.y )^2 + ( GV2.z - GV1.z )^2 ) ^ 0.5 - local GT = GT2 - GT1 - - - -- Calculate the distance - local GDv = { x = GV2.x - CV1.x, y = GV2.y - CV1.y, z = GV2.z - CV1.z } - local Alpha_T = math.atan2( GDv.x, GDv.z ) - math.atan2( CDv.x, CDv.z ) - local Alpha_R = ( Alpha_T < 0 ) and Alpha_T + 2 * math.pi or Alpha_T - local Position = math.cos( Alpha_R ) - local GD = ( ( GDv.x )^2 + ( GDv.z )^2 ) ^ 0.5 - local Distance = GD * Position + - CS * 0.5 - - -- Calculate the group direction vector - local GV = { x = GV2.x - CV2.x, y = GV2.y - CV2.y, z = GV2.z - CV2.z } - - -- Calculate GH2, GH2 with the same height as CV2. - local GH2 = { x = GV2.x, y = CV2.y + FollowFormation.y, z = GV2.z } - - -- Calculate the angle of GV to the orthonormal plane - local alpha = math.atan2( GV.x, GV.z ) - - local GVx = FollowFormation.z * math.cos( Ca ) + FollowFormation.x * math.sin( Ca ) - local GVz = FollowFormation.x * math.cos( Ca ) - FollowFormation.z * math.sin( Ca ) - - - -- 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 CVI = { x = CV2.x + CS * 10 * math.sin(Ca), - y = GH2.y - ( Distance + FollowFormation.x ) / 5, -- + FollowFormation.y, - z = CV2.z + CS * 10 * math.cos(Ca), - } - - -- Calculate the direction vector DV of the escort group. We use CVI as the base and CV2 as the direction. - local DV = { x = CV2.x - CVI.x, y = CV2.y - CVI.y, z = CV2.z - CVI.z } - - -- We now calculate the unary direction vector DVu, so that we can multiply DVu with the speed, which is expressed in meters / s. - -- We need to calculate this vector to predict the point the escort group needs to fly to according its speed. - -- The distance of the destination point should be far enough not to have the aircraft starting to swipe left to right... - local DVu = { x = DV.x / FollowDistance, y = DV.y, z = DV.z / FollowDistance } - - -- Now we can calculate the group destination vector GDV. - local GDV = { x = CVI.x, y = CVI.y, z = CVI.z } - - local ADDx = FollowFormation.x * math.cos(alpha) - FollowFormation.z * math.sin(alpha) - local ADDz = FollowFormation.z * math.cos(alpha) + FollowFormation.x * math.sin(alpha) - - local GDV_Formation = { - x = GDV.x - GVx, - y = GDV.y, - z = GDV.z - GVz - } - - if self.SmokeDirectionVector == true then - trigger.action.smoke( GDV, trigger.smokeColor.Green ) - trigger.action.smoke( GDV_Formation, trigger.smokeColor.White ) + + if CT1 == nil or CT1 == 0 or GT1 == nil or GT1 == 0 then + GroupUnit:SetState( self, "GV1", GroupUnit:GetPointVec3() ) + GroupUnit:SetState( self, "GT1", timer.getTime() ) + else + local CD = ( ( CV2.x - CV1.x )^2 + ( CV2.y - CV1.y )^2 + ( CV2.z - CV1.z )^2 ) ^ 0.5 + local CT = CT2 - CT1 + + local CS = ( 3600 / CT ) * ( CD / 1000 ) / 3.6 + + local CDv = { x = CV2.x - CV1.x, y = CV2.y - CV1.y, z = CV2.z - CV1.z } + local Ca = math.atan2( CDv.x, CDv.z ) + + local GT1 = GroupUnit:GetState( self, "GT1" ) + local GT2 = timer.getTime() + local GV1 = GroupUnit:GetState( self, "GV1" ) + local GV2 = GroupUnit:GetPointVec3() + GV2:AddX( math.random( -Formation.FlightRandomization / 2, Formation.FlightRandomization / 2 ) ) + GV2:AddY( math.random( -Formation.FlightRandomization / 2, Formation.FlightRandomization / 2 ) ) + GV2:AddZ( math.random( -Formation.FlightRandomization / 2, Formation.FlightRandomization / 2 ) ) + GroupUnit:SetState( self, "GT1", GT2 ) + GroupUnit:SetState( self, "GV1", GV2 ) + + + local GD = ( ( GV2.x - GV1.x )^2 + ( GV2.y - GV1.y )^2 + ( GV2.z - GV1.z )^2 ) ^ 0.5 + local GT = GT2 - GT1 + + + -- Calculate the distance + local GDv = { x = GV2.x - CV1.x, y = GV2.y - CV1.y, z = GV2.z - CV1.z } + local Alpha_T = math.atan2( GDv.x, GDv.z ) - math.atan2( CDv.x, CDv.z ) + local Alpha_R = ( Alpha_T < 0 ) and Alpha_T + 2 * math.pi or Alpha_T + local Position = math.cos( Alpha_R ) + local GD = ( ( GDv.x )^2 + ( GDv.z )^2 ) ^ 0.5 + local Distance = GD * Position + - CS * 0.5 + + -- Calculate the group direction vector + local GV = { x = GV2.x - CV2.x, y = GV2.y - CV2.y, z = GV2.z - CV2.z } + + -- Calculate GH2, GH2 with the same height as CV2. + local GH2 = { x = GV2.x, y = CV2.y + FollowFormation.y, z = GV2.z } + + -- Calculate the angle of GV to the orthonormal plane + local alpha = math.atan2( GV.x, GV.z ) + + local GVx = FollowFormation.z * math.cos( Ca ) + FollowFormation.x * math.sin( Ca ) + local GVz = FollowFormation.x * math.cos( Ca ) - FollowFormation.z * math.sin( Ca ) + + + -- 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 CVI = { x = CV2.x + CS * 10 * math.sin(Ca), + y = GH2.y - ( Distance + FollowFormation.x ) / 5, -- + FollowFormation.y, + z = CV2.z + CS * 10 * math.cos(Ca), + } + + -- Calculate the direction vector DV of the escort group. We use CVI as the base and CV2 as the direction. + local DV = { x = CV2.x - CVI.x, y = CV2.y - CVI.y, z = CV2.z - CVI.z } + + -- We now calculate the unary direction vector DVu, so that we can multiply DVu with the speed, which is expressed in meters / s. + -- We need to calculate this vector to predict the point the escort group needs to fly to according its speed. + -- The distance of the destination point should be far enough not to have the aircraft starting to swipe left to right... + local DVu = { x = DV.x / FollowDistance, y = DV.y, z = DV.z / FollowDistance } + + -- Now we can calculate the group destination vector GDV. + local GDV = { x = CVI.x, y = CVI.y, z = CVI.z } + + local ADDx = FollowFormation.x * math.cos(alpha) - FollowFormation.z * math.sin(alpha) + local ADDz = FollowFormation.z * math.cos(alpha) + FollowFormation.x * math.sin(alpha) + + local GDV_Formation = { + x = GDV.x - GVx, + y = GDV.y, + z = GDV.z - GVz + } + + if self.SmokeDirectionVector == true then + trigger.action.smoke( GDV, trigger.smokeColor.Green ) + trigger.action.smoke( GDV_Formation, trigger.smokeColor.White ) + end + + + + local Time = 60 + + local Speed = - ( Distance + FollowFormation.x ) / Time + local GS = Speed + CS + if Speed < 0 then + Speed = 0 + end + + -- 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) end - - - - local Time = 60 - - local Speed = - ( Distance + FollowFormation.x ) / Time - local GS = Speed + CS - if Speed < 0 then - Speed = 0 - end - - -- 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) end end end, diff --git a/Moose Development/Moose/Modules.lua b/Moose Development/Moose/Modules.lua index 8a86a02f9..68e0125d9 100644 --- a/Moose Development/Moose/Modules.lua +++ b/Moose Development/Moose/Modules.lua @@ -84,6 +84,7 @@ __Moose.Include( 'Scripts/Moose/AI/AI_Cap.lua' ) __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_Cargo.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_Cargo_APC.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_Cargo_Helicopter.lua' ) From 7a89960d212d0edb8504f7d4a4c3540454c11c5e Mon Sep 17 00:00:00 2001 From: FlightControl Date: Sat, 6 Apr 2019 16:33:22 +0200 Subject: [PATCH 2/7] Updates --- Moose Development/Moose/AI/AI_Escort.lua | 367 +++++++++++++++--- .../Moose/Functional/Detection.lua | 35 +- 2 files changed, 351 insertions(+), 51 deletions(-) diff --git a/Moose Development/Moose/AI/AI_Escort.lua b/Moose Development/Moose/AI/AI_Escort.lua index 0c4c6f8af..ec1576c4c 100644 --- a/Moose Development/Moose/AI/AI_Escort.lua +++ b/Moose Development/Moose/AI/AI_Escort.lua @@ -218,6 +218,8 @@ 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 ) @@ -324,6 +326,49 @@ end function AI_ESCORT:MenuHoldAtEscortPosition( Height, Speed, MenuTextFormat ) self:F( { Height, Speed, MenuTextFormat } ) + 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( "Hold at %d meter", Height ) + else + MenuText = string.format( "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.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 ) @@ -333,28 +378,6 @@ function AI_ESCORT:MenuHoldAtEscortPosition( Height, Speed, MenuTextFormat ) 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( "Hold at %d meter", Height ) - else - MenuText = string.format( "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 EscortGroup.EscortMenuHoldPosition then EscortGroup.EscortMenuHoldPosition = {} @@ -390,13 +413,56 @@ 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 + + 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.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 self.EscortMenuHold then - self.EscortMenuHold = MENU_GROUP:New( self.EscortUnit:GetGroup(), "Hold position", EscortGroup.EscortMenu ) + if not EscortGroup.EscortMenuHold then + EscortGroup.EscortMenuHold = MENU_GROUP:New( self.EscortUnit:GetGroup(), "Hold position", EscortGroup.EscortMenu ) end if not Height then @@ -430,7 +496,7 @@ function AI_ESCORT:MenuHoldAtLeaderPosition( Height, Speed, MenuTextFormat ) :New( self.EscortUnit:GetGroup(), MenuText, - self.EscortMenuHold, + EscortGroup.EscortMenuHold, AI_ESCORT._HoldPosition, self, self.EscortUnit:GetGroup(), @@ -512,6 +578,25 @@ 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" + else + 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 ) @@ -549,6 +634,26 @@ 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" + else + 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 ) @@ -588,27 +693,46 @@ end function AI_ESCORT:MenuReportTargets( 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 + + 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, {}, 1, Seconds ) + self.EscortGroupSet:ForEachGroupAlive( --- @param Core.Group#GROUP EscortGroup function( EscortGroup ) - if not EscortGroup.EscortMenuReportNearbyTargets then - EscortGroup.EscortMenuReportNearbyTargets = MENU_GROUP:New( self.EscortUnit:GetGroup(), "Report targets", EscortGroup.EscortMenu ) + 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 - - if not Seconds then - Seconds = 30 - 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 }, 1, Seconds ) end ) @@ -623,9 +747,16 @@ end function AI_ESCORT:MenuAssistedAttack() self:F() - -- 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", self.EscortMenu ) + self.EscortGroupSet:ForEachGroupAlive( + --- @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 ) + end + end + ) return self end @@ -756,6 +887,29 @@ function AI_ESCORT:_HoldPosition( OrbitGroup, EscortGroup, OrbitHeight, OrbitSec end + +--- @param #AI_ESCORT self +-- @param Wrapper.Group#GROUP OrbitGroup +-- @param #number OrbitHeight +-- @param #number OrbitSeconds +function AI_ESCORT:_FlightHoldPosition( OrbitGroup, OrbitHeight, OrbitSeconds ) + + local EscortUnit = self.EscortUnit + + self.EscortGroupSet:ForEachGroupAlive( + --- @param Core.Group#GROUP EscortGroup + function( EscortGroup, OrbitGroup ) + if EscortGroup:IsAir() then + if OrbitGroup == nil then + OrbitGroup = EscortGroup + end + self:_HoldPosition( OrbitGroup, EscortGroup, OrbitHeight, OrbitSeconds ) + end + end, OrbitGroup + ) + +end + --- @param #MENUPARAM MenuParam function AI_ESCORT:_JoinUpAndFollow( Distance ) @@ -768,7 +922,6 @@ function AI_ESCORT:_JoinUpAndFollow( Distance ) end ---- @param #MENUPARAM MenuParam function AI_ESCORT:_Flare( EscortGroup, Color, Message ) local EscortUnit = self.EscortUnit @@ -777,7 +930,22 @@ function AI_ESCORT:_Flare( EscortGroup, Color, Message ) EscortGroup:MessageTypeToGroup( Message, MESSAGE.Type.Information, EscortUnit:GetGroup() ) end ---- @param #MENUPARAM MenuParam + +function AI_ESCORT:_FlightFlare( Color, Message ) + + self.EscortGroupSet:ForEachGroupAlive( + --- @param Core.Group#GROUP EscortGroup + function( EscortGroup ) + if EscortGroup:IsAir() then + self:_Flare( EscortGroup, Color, Message ) + end + end + ) + +end + + + function AI_ESCORT:_Smoke( EscortGroup, Color, Message ) local EscortUnit = self.EscortUnit @@ -786,8 +954,20 @@ function AI_ESCORT:_Smoke( EscortGroup, Color, Message ) EscortGroup:MessageTypeToGroup( Message, MESSAGE.Type.Information, EscortUnit:GetGroup() ) end +function AI_ESCORT:_FlightSmoke( Color, Message ) + + self.EscortGroupSet:ForEachGroupAlive( + --- @param Core.Group#GROUP EscortGroup + function( EscortGroup ) + if EscortGroup:IsAir() then + self:_Smoke( EscortGroup, Color, Message ) + end + end + ) + +end + ---- @param #MENUPARAM MenuParam function AI_ESCORT:_ReportNearbyTargetsNow( EscortGroup ) local EscortUnit = self.EscortUnit @@ -796,6 +976,21 @@ function AI_ESCORT:_ReportNearbyTargetsNow( EscortGroup ) end + +function AI_ESCORT:_FlightReportNearbyTargetsNow() + + self.EscortGroupSet:ForEachGroupAlive( + --- @param Core.Group#GROUP EscortGroup + function( EscortGroup ) + if EscortGroup:IsAir() then + self:_ReportNearbyTargetsNow( EscortGroup ) + end + end + ) + +end + + function AI_ESCORT:_SwitchReportNearbyTargets( EscortGroup, ReportTargets ) local EscortUnit = self.EscortUnit @@ -812,6 +1007,20 @@ function AI_ESCORT:_SwitchReportNearbyTargets( EscortGroup, ReportTargets ) end end + +function AI_ESCORT:_FlightSwitchReportNearbyTargets( ReportTargets ) + + self.EscortGroupSet:ForEachGroupAlive( + --- @param Core.Group#GROUP EscortGroup + function( EscortGroup ) + if EscortGroup:IsAir() then + self:_SwitchReportNearbyTargets( EscortGroup, ReportTargets ) + end + end + ) + +end + --- @param #MENUPARAM MenuParam function AI_ESCORT:_ScanTargets( ScanDuration ) @@ -918,6 +1127,21 @@ function AI_ESCORT:_AttackTarget( EscortGroup, DetectedItem ) end + +function AI_ESCORT:_FlightAttackTarget( DetectedItem ) + + self.EscortGroupSet:ForEachGroupAlive( + --- @param Core.Group#GROUP EscortGroup + function( EscortGroup, DetectedItem ) + if EscortGroup:IsAir() then + self:_AttackTarget( EscortGroup, DetectedItem ) + end + end, DetectedItem + ) + +end + + --- --- @param #AI_ESCORT self -- @param Wrapper.Group#GROUP EscortGroup The escort group that will attack the detected item. @@ -1036,7 +1260,7 @@ function AI_ESCORT:_ReportTargetsScheduler( EscortGroup ) for DetectedItemIndex, DetectedItem in pairs( DetectedItems ) do self:F( { DetectedItemIndex, DetectedItem } ) - local DetectedItemReportSummary = self.Detection:DetectedItemReportSummary( DetectedItem, EscortGroup, _DATABASE:GetPlayerSettings( self.EscortUnit:GetPlayerName() ) ) + local DetectedItemReportSummary = self.Detection:DetectedItemReportMenu( DetectedItem, EscortGroup, _DATABASE:GetPlayerSettings( self.EscortUnit:GetPlayerName() ) ) if EscortGroup:IsAir() then @@ -1087,3 +1311,48 @@ function AI_ESCORT:_ReportTargetsScheduler( EscortGroup ) return false end + +--- Report Targets Scheduler. +-- @param #AI_ESCORT self +-- @param Wrapper.Group#GROUP EscortGroup +function AI_ESCORT:_FlightReportTargetsScheduler() + + self:F("FlightReportTargetScheduler") + + if self.EscortUnit:IsAlive() then + + self.FlightMenuAttackNearbyTargets:RemoveSubMenus() + + local DetectedItems = self.Detection:GetDetectedItems() + + local DetectedTargets = false + + local DetectedMsgs = {} + + local ClientEscortTargets = self.Detection + --local EscortUnit = EscortGroupData:GetUnit( 1 ) + + for DetectedItemIndex, DetectedItem in pairs( DetectedItems ) do + self:F( { DetectedItemIndex, DetectedItem } ) + + local DetectedItemReportSummary = self.Detection:DetectedItemReportMenu( DetectedItem, nil, _DATABASE:GetPlayerSettings( self.EscortUnit:GetPlayerName() ) ) + + local DetectedMsg = DetectedItemReportSummary:Text("\n") + DetectedMsgs[#DetectedMsgs+1] = DetectedMsg + + self:T( DetectedMsg ) + + MENU_GROUP_COMMAND:New( self.EscortUnit:GetGroup(), + DetectedMsg, + self.FlightMenuAttackNearbyTargets, + AI_ESCORT._FlightAttackTarget, + self, + DetectedItem + ) + end + + return true + end + + return false +end diff --git a/Moose Development/Moose/Functional/Detection.lua b/Moose Development/Moose/Functional/Detection.lua index c3d0c21a3..66c98c475 100644 --- a/Moose Development/Moose/Functional/Detection.lua +++ b/Moose Development/Moose/Functional/Detection.lua @@ -2478,6 +2478,37 @@ do -- DETECTION_AREAS end + --- Report summary of a detected item using a given numeric index. + -- @param #DETECTION_AREAS self + -- @param #DETECTION_BASE.DetectedItem DetectedItem The DetectedItem. + -- @param Wrapper.Group#GROUP AttackGroup The group to get the settings for. + -- @param Core.Settings#SETTINGS Settings (Optional) Message formatting settings to use. + -- @return Core.Report#REPORT The report of the detection items. + function DETECTION_AREAS:DetectedItemReportMenu( DetectedItem, AttackGroup, Settings ) + self:F( { DetectedItem = DetectedItem } ) + + local DetectedItemID = self:GetDetectedItemID( DetectedItem ) + + if DetectedItem then + local DetectedSet = self:GetDetectedItemSet( DetectedItem ) + local ReportSummaryItem + + local DetectedZone = self:GetDetectedItemZone( DetectedItem ) + local DetectedItemCoordinate = DetectedZone:GetCoordinate() + local DetectedItemCoordText = DetectedItemCoordinate:ToString( AttackGroup, Settings ) + + local ThreatLevelA2G = self:GetDetectedItemThreatLevel( DetectedItem ) + + local Report = REPORT:New() + Report:Add( string.format( "Threat: [%s%s]", string.rep( "■", ThreatLevelA2G ), string.rep( "□", 10-ThreatLevelA2G ) ) ) + Report:Add(DetectedItemID .. ", " .. DetectedItemCoordText) + + return Report + end + + return nil + end + --- Report summary of a detected item using a given numeric index. -- @param #DETECTION_AREAS self -- @param #DETECTION_BASE.DetectedItem DetectedItem The DetectedItem. @@ -2503,9 +2534,9 @@ do -- DETECTION_AREAS local Report = REPORT:New() Report:Add(DetectedItemID .. ", " .. DetectedItemCoordText) - Report:Add( string.format( "Threat: [%s]", string.rep( "■", ThreatLevelA2G ), string.rep( "□", 10-ThreatLevelA2G ) ) ) + Report:Add( string.format( "Threat: [%s%s]", string.rep( "■", ThreatLevelA2G ), string.rep( "□", 10-ThreatLevelA2G ) ) ) Report:Add( string.format("Type: %2d of %s", DetectedItemsCount, DetectedItemsTypes ) ) - Report:Add( string.format("Detected: %s", DetectedItem.IsDetected and "yes" or "no" ) ) + --Report:Add( string.format("Detected: %s", DetectedItem.IsDetected and "yes" or "no" ) ) return Report end From c2b46ceea11cafbdd89fc874dd8dcd7d2f2de16a Mon Sep 17 00:00:00 2001 From: Wingthor Date: Sun, 7 Apr 2019 02:04:08 +0200 Subject: [PATCH 3/7] Fixes GitHub issue 1140 SPAWN: GetLastAliveGroup() #1140 generating a nil value... "attempt to index field 'SpawnTemplatePrefixself' (a nil value)" --- Moose Development/Moose/Core/Spawn.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Core/Spawn.lua b/Moose Development/Moose/Core/Spawn.lua index 7b25c5367..6594633c9 100644 --- a/Moose Development/Moose/Core/Spawn.lua +++ b/Moose Development/Moose/Core/Spawn.lua @@ -2203,7 +2203,7 @@ end -- -- Do actions with the GroupPlane object. -- end function SPAWN:GetLastAliveGroup() - self:F( { self.SpawnTemplatePrefixself.SpawnAliasPrefix } ) + self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } ) self.SpawnIndex = self:_GetLastIndex() for SpawnIndex = self.SpawnIndex, 1, -1 do From f1d217b6d7a50ab96d0dc8ed08192bc10309ca5f Mon Sep 17 00:00:00 2001 From: FlightControl Date: Sun, 7 Apr 2019 08:31:22 +0200 Subject: [PATCH 4/7] wip --- Moose Development/Moose/AI/AI_Escort.lua | 209 +++++++++++++----- Moose Development/Moose/Core/Report.lua | 7 +- .../Moose/Functional/Detection.lua | 3 +- 3 files changed, 165 insertions(+), 54 deletions(-) diff --git a/Moose Development/Moose/AI/AI_Escort.lua b/Moose Development/Moose/AI/AI_Escort.lua index ec1576c4c..f13f626c0 100644 --- a/Moose Development/Moose/AI/AI_Escort.lua +++ b/Moose Development/Moose/AI/AI_Escort.lua @@ -160,6 +160,9 @@ AI_ESCORT = { TaskPoints = {} } +--- @field Functional.Detection#DETECTION_AREAS +AI_ESCORT.Detection = nil + --- AI_ESCORT.Mode class -- @type AI_ESCORT.MODE -- @field #number FOLLOW @@ -268,7 +271,7 @@ end --- Set a Detection method for the EscortUnit to be reported upon. -- Detection methods are based on the derived classes from DETECTION_BASE. -- @param #AI_ESCORT self --- @param Function.Detection#DETECTION_BASE Detection +-- @param Functional.Detection#DETECTION_AREAS Detection function AI_ESCORT:SetDetection( Detection ) self.Detection = Detection @@ -296,9 +299,11 @@ function AI_ESCORT:Menus() -- self:MenuScanForTargets( 100, 60 ) + self:MenuJoinUp() + self:MenuHoldAtEscortPosition( 1000, 500 ) self:MenuHoldAtLeaderPosition( 1000, 500 ) - + self:MenuFlare() self:MenuSmoke() @@ -314,6 +319,76 @@ function AI_ESCORT:Menus() end +--- Defines a menu slot to let the escort to join formation. +-- This menu will appear under **Navigation**. +-- @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 + + +--- Defines a menu slot to let the escort to join in a trail formation. +-- This menu will appear under **Navigation**. +-- @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. +-- @return #AI_ESCORT +function AI_ESCORT:MenuFormationTrail( XStart, XSpace, YStart ) + + if not self.FlightMenuReportNavigation then + self.FlightMenuReportNavigation = MENU_GROUP:New( self.EscortUnit:GetGroup(), "Navigation", self.FlightMenu ) + end + + if not self.FlightMenuJoinUp then + self.FlightMenuFormationTrail = MENU_GROUP_COMMAND:New( self.EscortUnit:GetGroup(), "Trail", self.FlightMenuReportNavigation, AI_ESCORT._FlightFormationTrail, self, XStart, XSpace, YStart ) + 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.EscortMenuFormationTrail then + EscortGroup.EscortMenuFormationTrail = MENU_GROUP_COMMAND:New( self.EscortUnit:GetGroup(), "Trail", EscortGroup.EscortMenuReportNavigation, ESCORT._EscortFormationTrail, self, EscortGroup, XStart, XSpace, YStart ) + end + + 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. @@ -711,7 +786,7 @@ function AI_ESCORT:MenuReportTargets( Seconds ) -- Attack Targets self.FlightMenuAttackNearbyTargets = MENU_GROUP:New( self.EscortUnit:GetGroup(), "Attack targets", self.FlightMenu ) - self.FlightReportTargetsScheduler = SCHEDULER:New( self, self._FlightReportTargetsScheduler, {}, 1, Seconds ) + self.FlightReportTargetsScheduler = SCHEDULER:New( self, self._FlightReportTargetsScheduler, {}, 5, Seconds ) self.EscortGroupSet:ForEachGroupAlive( --- @param Core.Group#GROUP EscortGroup @@ -910,15 +985,55 @@ function AI_ESCORT:_FlightHoldPosition( OrbitGroup, OrbitHeight, OrbitSeconds ) end ---- @param #MENUPARAM MenuParam -function AI_ESCORT:_JoinUpAndFollow( Distance ) - local EscortGroup = self.EscortGroup + +function AI_ESCORT:_JoinUp( EscortGroup ) + local EscortUnit = self.EscortUnit - self.Distance = Distance + self:JoinFormation( EscortGroup ) + EscortGroup.EscortMode = AI_ESCORT.MODE.FOLLOW +end + + +function AI_ESCORT:_FlightJoinUp( EscortGroup ) + + self.EscortGroupSet:ForEachGroupAlive( + --- @param Core.Group#GROUP EscortGroup + function( EscortGroup ) + if EscortGroup:IsAir() then + self:_JoinUp( EscortGroup ) + end + end + ) + +end + + +--- Lets the escort to join in a trail formation. +-- @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. +-- @return #AI_ESCORT +function AI_ESCORT:_EscortFormationTrail( EscortGroup, XStart, XSpace, YStart ) + + self:FormationTrail( XStart, XSpace, YStart ) + +end + + +function AI_ESCORT:_EscortFormationTrail( XStart, XSpace, YStart ) + + self.EscortGroupSet:ForEachGroupAlive( + --- @param Core.Group#GROUP EscortGroup + function( EscortGroup ) + if EscortGroup:IsAir() then + self:FormationTrail( EscortGroup, XStart, XSpace, YStart ) + end + end + ) - self:JoinUpAndFollow( EscortGroup, EscortUnit, self.Distance ) end @@ -1021,7 +1136,7 @@ function AI_ESCORT:_FlightSwitchReportNearbyTargets( ReportTargets ) end ---- @param #MENUPARAM MenuParam + function AI_ESCORT:_ScanTargets( ScanDuration ) local EscortGroup = self.EscortGroup -- Wrapper.Group#GROUP @@ -1055,12 +1170,16 @@ end -- @param Wrapper.Group#GROUP EscortGroup function AI_ESCORT.___Resume( EscortGroup, self ) + local PlayerGroup = self.EscortUnit:GetGroup() + if EscortGroup.EscortMode == AI_ESCORT.MODE.FOLLOW then self:JoinFormation( EscortGroup ) + EscortGroup:MessageTypeToClient( "Destroyed all targets. Rejoining.", MESSAGE.Type.Information, PlayerGroup ) end 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 @@ -1123,7 +1242,7 @@ function AI_ESCORT:_AttackTarget( EscortGroup, DetectedItem ) end - EscortGroup:MessageTypeToGroup( "Engaging Designated Unit!", MESSAGE.Type.Information, EscortUnit ) + EscortGroup:MessageTypeToGroup( "Engaging!", MESSAGE.Type.Information, EscortUnit ) end @@ -1170,11 +1289,11 @@ function AI_ESCORT:_AssistTarget( EscortGroup, DetectedItem ) ) - EscortGroup:MessageTypeToGroup( "Assisting with the destroying the enemy unit!", MESSAGE.Type.Information, EscortUnit:GetGroup() ) + EscortGroup:MessageTypeToGroup( "Assisting attack!", MESSAGE.Type.Information, EscortUnit:GetGroup() ) end ---- @param #MENUPARAM MenuParam + function AI_ESCORT:_ROE( EscortGroup, EscortROEFunction, EscortROEMessage ) local EscortUnit = self.EscortUnit @@ -1183,7 +1302,7 @@ function AI_ESCORT:_ROE( EscortGroup, EscortROEFunction, EscortROEMessage ) EscortGroup:MessageTypeToGroup( EscortROEMessage, MESSAGE.Type.Information, EscortUnit:GetGroup() ) end ---- @param #MENUPARAM MenuParam + function AI_ESCORT:_ROT( EscortGroup, EscortROTFunction, EscortROTMessage ) local EscortUnit = self.EscortUnit @@ -1192,7 +1311,7 @@ function AI_ESCORT:_ROT( EscortGroup, EscortROTFunction, EscortROTMessage ) EscortGroup:MessageTypeToGroup( EscortROTMessage, MESSAGE.Type.Information, EscortUnit:GetGroup() ) end ---- @param #MENUPARAM MenuParam + function AI_ESCORT:_ResumeMission( WayPoint ) local EscortGroup = self.EscortGroup @@ -1212,6 +1331,7 @@ function AI_ESCORT:_ResumeMission( WayPoint ) EscortGroup:MessageToClient( "Resuming mission from waypoint " .. WayPoint .. ".", 10, EscortUnit ) end + --- Registers the waypoints -- @param #AI_ESCORT self -- @return #table @@ -1248,12 +1368,7 @@ function AI_ESCORT:_ReportTargetsScheduler( EscortGroup ) end local DetectedItems = self.Detection:GetDetectedItems() - self:F( DetectedItems ) - local DetectedTargets = false - - local DetectedMsgs = {} - local ClientEscortTargets = self.Detection --local EscortUnit = EscortGroupData:GetUnit( 1 ) @@ -1262,15 +1377,11 @@ function AI_ESCORT:_ReportTargetsScheduler( EscortGroup ) local DetectedItemReportSummary = self.Detection:DetectedItemReportMenu( DetectedItem, EscortGroup, _DATABASE:GetPlayerSettings( self.EscortUnit:GetPlayerName() ) ) + local DetectedMenu = DetectedItemReportSummary:Text("\n") + if EscortGroup:IsAir() then - - local DetectedMsg = DetectedItemReportSummary:Text("\n") - DetectedMsgs[#DetectedMsgs+1] = DetectedMsg - - self:T( DetectedMsg ) - MENU_GROUP_COMMAND:New( self.EscortUnit:GetGroup(), - DetectedMsg, + DetectedMenu, EscortGroup.EscortMenuAttackNearbyTargets, AI_ESCORT._AttackTarget, self, @@ -1279,13 +1390,9 @@ function AI_ESCORT:_ReportTargetsScheduler( EscortGroup ) ) else if self.EscortMenuTargetAssistance then - - local DetectedMsg = DetectedItemReportSummary:Text("\n") - self:T( DetectedMsg ) - local MenuTargetAssistance = MENU_GROUP:New( self.EscortUnit:GetGroup(), EscortGroupName, EscortGroup.EscortMenuTargetAssistance ) MENU_GROUP_COMMAND:New( self.EscortUnit:GetGroup(), - DetectedMsg, + DetectedMenu, MenuTargetAssistance, AI_ESCORT._AssistTarget, self, @@ -1295,15 +1402,9 @@ function AI_ESCORT:_ReportTargetsScheduler( EscortGroup ) end end - DetectedTargets = true + end - self:F( DetectedMsgs ) - if DetectedTargets then - EscortGroup:MessageTypeToGroup( "Reporting detected targets:\n" .. table.concat( DetectedMsgs, "\n" ), MESSAGE.Type.Information, self.EscortUnit:GetGroup() ) - else - EscortGroup:MessageTypeToGroup( "No targets detected.", MESSAGE.Type.Information, self.EscortUnit:GetGroup() ) - end - + return true else end @@ -1312,14 +1413,20 @@ function AI_ESCORT:_ReportTargetsScheduler( EscortGroup ) return false end ---- Report Targets Scheduler. +--- Report Targets Scheduler for the flight. The report is generated from the perspective of the player plane, and is reported by the first plane in the formation set. -- @param #AI_ESCORT self -- @param Wrapper.Group#GROUP EscortGroup function AI_ESCORT:_FlightReportTargetsScheduler() self:F("FlightReportTargetScheduler") + + 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. - if self.EscortUnit:IsAlive() then + if self.EscortUnit:IsAlive() and EscortGroup:IsAlive() then + + local ClientGroup = self.EscortUnit:GetGroup() self.FlightMenuAttackNearbyTargets:RemoveSubMenus() @@ -1327,20 +1434,16 @@ function AI_ESCORT:_FlightReportTargetsScheduler() local DetectedTargets = false - local DetectedMsgs = {} - local ClientEscortTargets = self.Detection - --local EscortUnit = EscortGroupData:GetUnit( 1 ) for DetectedItemIndex, DetectedItem in pairs( DetectedItems ) do - self:F( { DetectedItemIndex, DetectedItem } ) - local DetectedItemReportSummary = self.Detection:DetectedItemReportMenu( DetectedItem, nil, _DATABASE:GetPlayerSettings( self.EscortUnit:GetPlayerName() ) ) + 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("\n") - DetectedMsgs[#DetectedMsgs+1] = DetectedMsg - - self:T( DetectedMsg ) + local DetectedMsg = DetectedItemReportSummary:Text(", ") + DetectedTargetsReport:AddIndent( DetectedMsg, "-" ) MENU_GROUP_COMMAND:New( self.EscortUnit:GetGroup(), DetectedMsg, @@ -1351,6 +1454,12 @@ function AI_ESCORT:_FlightReportTargetsScheduler() ) end + if DetectedTargets then + EscortGroup:MessageTypeToGroup( DetectedTargetsReport:Text( "\n" ), MESSAGE.Type.Information, self.EscortUnit:GetGroup() ) +-- else +-- EscortGroup:MessageTypeToGroup( "No targets detected.", MESSAGE.Type.Information, self.EscortUnit:GetGroup() ) + end + return true end diff --git a/Moose Development/Moose/Core/Report.lua b/Moose Development/Moose/Core/Report.lua index 35800c60e..bd860996b 100644 --- a/Moose Development/Moose/Core/Report.lua +++ b/Moose Development/Moose/Core/Report.lua @@ -70,11 +70,12 @@ function REPORT:Add( Text ) return self end ---- Add a new line to a REPORT. +--- Add a new line to a REPORT, but indented. A separator character can be specified to separate the reported lines visually. -- @param #REPORT self --- @param #string Text +-- @param #string Text The report text. +-- @param #string Separator (optional) The start of each report line can begin with an optional separator character. This can be a "-", or "#", or "*". You're free to choose what you find the best. -- @return #REPORT -function REPORT:AddIndent( Text, Separator ) --R2.1 +function REPORT:AddIndent( Text, Separator ) self.Report[#self.Report+1] = ( ( Separator and Separator .. string.rep( " ", self.Indent - 1 ) ) or string.rep(" ", self.Indent ) ) .. Text:gsub("\n","\n"..string.rep( " ", self.Indent ) ) return self end diff --git a/Moose Development/Moose/Functional/Detection.lua b/Moose Development/Moose/Functional/Detection.lua index 66c98c475..06ecfe87f 100644 --- a/Moose Development/Moose/Functional/Detection.lua +++ b/Moose Development/Moose/Functional/Detection.lua @@ -2500,8 +2500,9 @@ do -- DETECTION_AREAS local ThreatLevelA2G = self:GetDetectedItemThreatLevel( DetectedItem ) local Report = REPORT:New() + Report:Add( DetectedItemID ) Report:Add( string.format( "Threat: [%s%s]", string.rep( "■", ThreatLevelA2G ), string.rep( "□", 10-ThreatLevelA2G ) ) ) - Report:Add(DetectedItemID .. ", " .. DetectedItemCoordText) + Report:Add( DetectedItemCoordText ) return Report end From 0e14624d394dc9d6c13a37a2117644faa887ce68 Mon Sep 17 00:00:00 2001 From: FlightControl Date: Sun, 7 Apr 2019 11:07:20 +0200 Subject: [PATCH 5/7] wip --- Moose Development/Moose/AI/AI_Escort.lua | 74 +++++++++++++++++++++++- 1 file changed, 71 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/AI/AI_Escort.lua b/Moose Development/Moose/AI/AI_Escort.lua index f13f626c0..512ecbd2a 100644 --- a/Moose Development/Moose/AI/AI_Escort.lua +++ b/Moose Development/Moose/AI/AI_Escort.lua @@ -300,6 +300,8 @@ function AI_ESCORT:Menus() -- self:MenuScanForTargets( 100, 60 ) self:MenuJoinUp() + self:MenuFormationTrail( 0, 0, 50 ) + self:MenuFormationStack( 0, 0, 100, 150 ) self:MenuHoldAtEscortPosition( 1000, 500 ) self:MenuHoldAtLeaderPosition( 1000, 500 ) @@ -366,7 +368,7 @@ function AI_ESCORT:MenuFormationTrail( XStart, XSpace, YStart ) self.FlightMenuReportNavigation = MENU_GROUP:New( self.EscortUnit:GetGroup(), "Navigation", self.FlightMenu ) end - if not self.FlightMenuJoinUp then + if not self.FlightMenuFormationTrail then self.FlightMenuFormationTrail = MENU_GROUP_COMMAND:New( self.EscortUnit:GetGroup(), "Trail", self.FlightMenuReportNavigation, AI_ESCORT._FlightFormationTrail, self, XStart, XSpace, YStart ) end @@ -390,6 +392,45 @@ function AI_ESCORT:MenuFormationTrail( XStart, XSpace, YStart ) end +--- Defines a menu slot to let the escort to join in a stacked formation. +-- This menu will appear under **Navigation**. +-- @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. +-- @return #AI_ESCORT +function AI_ESCORT:MenuFormationStack( XStart, XSpace, YStart, YSpace ) + + if not self.FlightMenuReportNavigation then + self.FlightMenuReportNavigation = MENU_GROUP:New( self.EscortUnit:GetGroup(), "Navigation", self.FlightMenu ) + end + + if not self.FlightMenuFormationStack then + self.FlightMenuFormationStack = MENU_GROUP_COMMAND:New( self.EscortUnit:GetGroup(), "Stack", self.FlightMenuReportNavigation, AI_ESCORT._FlightFormationStack, self, XStart, XSpace, YStart, YSpace ) + 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.EscortMenuFormationStack then + EscortGroup.EscortMenuFormationStack = MENU_GROUP_COMMAND:New( self.EscortUnit:GetGroup(), "Stack", EscortGroup.EscortMenuReportNavigation, ESCORT._EscortFormationStack, self, EscortGroup, XStart, XSpace, YStart, YSpace ) + end + + 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**. @@ -1023,13 +1064,40 @@ function AI_ESCORT:_EscortFormationTrail( EscortGroup, XStart, XSpace, YStart ) end -function AI_ESCORT:_EscortFormationTrail( XStart, XSpace, YStart ) +function AI_ESCORT:_FlightFormationTrail( XStart, XSpace, YStart ) self.EscortGroupSet:ForEachGroupAlive( --- @param Core.Group#GROUP EscortGroup function( EscortGroup ) if EscortGroup:IsAir() then - self:FormationTrail( EscortGroup, XStart, XSpace, YStart ) + self:_EscortFormationTrail( EscortGroup, XStart, XSpace, YStart ) + end + end + ) + +end + +--- Lets the escort to join in a stacked formation. +-- @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 #number 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. +-- @return #AI_ESCORT +function AI_ESCORT:_EscortFormationStack( EscortGroup, XStart, XSpace, YStart, YSpace ) + + self:FormationTrail( XStart, XSpace, YStart, YSpace ) + +end + + +function AI_ESCORT:_FlightFormationStack( XStart, XSpace, YStart, YSpace ) + + self.EscortGroupSet:ForEachGroupAlive( + --- @param Core.Group#GROUP EscortGroup + function( EscortGroup ) + if EscortGroup:IsAir() then + self:_EscortFormationStack( EscortGroup, XStart, XSpace, YStart, YSpace ) end end ) From f9f4b3a9fdc890eea6042039f395afe6940f7a14 Mon Sep 17 00:00:00 2001 From: FlightControl Date: Mon, 8 Apr 2019 05:39:57 +0200 Subject: [PATCH 6/7] wip --- Moose Development/Moose/AI/AI_Escort.lua | 235 ++++++++++++++++++----- 1 file changed, 183 insertions(+), 52 deletions(-) diff --git a/Moose Development/Moose/AI/AI_Escort.lua b/Moose Development/Moose/AI/AI_Escort.lua index 512ecbd2a..9f4f813aa 100644 --- a/Moose Development/Moose/AI/AI_Escort.lua +++ b/Moose Development/Moose/AI/AI_Escort.lua @@ -321,8 +321,49 @@ function AI_ESCORT:Menus() end ---- Defines a menu slot to let the escort to join formation. --- This menu will appear under **Navigation**. + +function AI_ESCORT:MenuFormation( Formation, ... ) + + if not self.FlightMenuFormation then + self.FlightMenuFormation = MENU_GROUP:New( self.EscortUnit:GetGroup(), "Formation", self.FlightMenu ) + end + + 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 ) + if EscortGroup:IsAir() then + self:E({Formation=Formation}) + self["Formation"..Formation]( arg ) + end + end + ) + 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() @@ -356,7 +397,7 @@ end --- Defines a menu slot to let the escort to join in a trail formation. --- This menu will appear under **Navigation**. +-- This menu will appear under **Formation**. -- @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. @@ -364,36 +405,13 @@ end -- @return #AI_ESCORT function AI_ESCORT:MenuFormationTrail( XStart, XSpace, YStart ) - if not self.FlightMenuReportNavigation then - self.FlightMenuReportNavigation = MENU_GROUP:New( self.EscortUnit:GetGroup(), "Navigation", self.FlightMenu ) - end - - if not self.FlightMenuFormationTrail then - self.FlightMenuFormationTrail = MENU_GROUP_COMMAND:New( self.EscortUnit:GetGroup(), "Trail", self.FlightMenuReportNavigation, AI_ESCORT._FlightFormationTrail, self, XStart, XSpace, YStart ) - 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.EscortMenuFormationTrail then - EscortGroup.EscortMenuFormationTrail = MENU_GROUP_COMMAND:New( self.EscortUnit:GetGroup(), "Trail", EscortGroup.EscortMenuReportNavigation, ESCORT._EscortFormationTrail, self, EscortGroup, XStart, XSpace, YStart ) - end - - end - end - ) + self:MenuFormation( "Trail", XStart, XSpace, YStart ) return self end - --- Defines a menu slot to let the escort to join in a stacked formation. --- This menu will appear under **Navigation**. +-- This menu will appear under **Formation**. -- @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. @@ -402,34 +420,147 @@ end -- @return #AI_ESCORT function AI_ESCORT:MenuFormationStack( XStart, XSpace, YStart, YSpace ) - if not self.FlightMenuReportNavigation then - self.FlightMenuReportNavigation = MENU_GROUP:New( self.EscortUnit:GetGroup(), "Navigation", self.FlightMenu ) - end - - if not self.FlightMenuFormationStack then - self.FlightMenuFormationStack = MENU_GROUP_COMMAND:New( self.EscortUnit:GetGroup(), "Stack", self.FlightMenuReportNavigation, AI_ESCORT._FlightFormationStack, self, XStart, XSpace, YStart, YSpace ) - 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.EscortMenuFormationStack then - EscortGroup.EscortMenuFormationStack = MENU_GROUP_COMMAND:New( self.EscortUnit:GetGroup(), "Stack", EscortGroup.EscortMenuReportNavigation, ESCORT._EscortFormationStack, self, EscortGroup, XStart, XSpace, YStart, YSpace ) - end - - end - end - ) + self:MenuFormation( "Stack", XStart, XSpace, YStart, YSpace ) return self end +--- Defines a menu slot to let the escort to join in a leFt wing formation. +-- This menu will appear under **Formation**. +-- @param #AI_ESCORT self +-- @param #number XStart The start position on the X-axis in meters for the first group. +-- @param #nubmer YStart The start position on the Y-axis in meters for the first 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. +-- @return #AI_ESCORT +function AI_ESCORT:MenuFormationLeftWing( XStart, YStart, ZStart, ZSpace ) + + self:MenuFormation( "LeftWing", XStart, YStart, ZStart, ZSpace ) + + return self +end + + +--- Defines a menu slot to let the escort to join in a leFt wing formation. +-- This menu will appear under **Formation**. +-- @param #AI_ESCORT self +-- @param #number XStart The start position on the X-axis in meters for the first group. +-- @param #nubmer YStart The start position on the Y-axis in meters for the first 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. +-- @return #AI_ESCORT +function AI_ESCORT:MenuFormationLeftLine( XStart, YStart, ZStart, ZSpace ) + + self:MenuFormation( "LeftLine", XStart, YStart, ZStart, ZSpace ) + + return self +end + + +--- Defines a menu slot to let the escort to join in a right line formation. +-- This menu will appear under **Formation**. +-- @param #AI_ESCORT self +-- @param #number XStart The start position on the X-axis in meters for the first group. +-- @param #nubmer YStart The start position on the Y-axis in meters for the first 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. +-- @return #AI_ESCORT +function AI_ESCORT:MenuFormationRightLine( XStart, YStart, ZStart, ZSpace ) + + self:MenuFormation( "RightLine", XStart, YStart, ZStart, ZSpace ) + + return self +end + + +--- Defines a menu slot to let the escort to join in a left wing formation. +-- This menu will appear under **Formation**. +-- @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 #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_ESCORT +function AI_ESCORT:MenuFormationLeftWing( XStart, XSpace, YStart, ZStart, ZSpace ) + + self:MenuFormation( "LeftWing", XStart, XSpace, YStart, ZStart, ZSpace ) + + return self +end + + +--- Defines a menu slot to let the escort to join in a right wing formation. +-- This menu will appear under **Formation**. +-- @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 #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_ESCORT +function AI_ESCORT:MenuFormationRightWing( XStart, XSpace, YStart, ZStart, ZSpace ) + + self:MenuFormation( "RightWing", XStart, XSpace, YStart, ZStart, ZSpace ) + + return self +end + + +--- Defines a menu slot to let the escort to join in a center wing formation. +-- This menu will appear under **Formation**. +-- @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. +-- @return #AI_ESCORT +function AI_ESCORT:MenuFormationCenterWing( XStart, XSpace, YStart, YSpace, ZStart, ZSpace ) + + self:MenuFormation( "CenterWing", XStart, XSpace, YStart, YSpace, ZStart, ZSpace ) + + return self +end + + +--- Defines a menu slot to let the escort to join in a vic formation. +-- This menu will appear under **Formation**. +-- @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. +-- @return #AI_ESCORT +function AI_ESCORT:MenuFormationVic( XStart, XSpace, YStart, YSpace, ZStart, ZSpace ) + + self:MenuFormation( "Vic", XStart, XSpace, YStart, YSpace, ZStart, ZSpace ) + + return self +end + + +--- Defines a menu slot to let the escort to join in a box formation. +-- This menu will appear under **Formation**. +-- @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:MenuFormationBox( XStart, XSpace, YStart, YSpace, ZStart, ZSpace, ZLevels ) + + self:MenuFormation( "Box", XStart, XSpace, YStart, YSpace, ZStart, ZSpace, ZLevels ) + + 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. @@ -1086,7 +1217,7 @@ end -- @return #AI_ESCORT function AI_ESCORT:_EscortFormationStack( EscortGroup, XStart, XSpace, YStart, YSpace ) - self:FormationTrail( XStart, XSpace, YStart, YSpace ) + self:FormationStack( XStart, XSpace, YStart, YSpace ) end From ddfc22bb5007a59d865f8808bc242965814c958f Mon Sep 17 00:00:00 2001 From: FlightControl Date: Mon, 8 Apr 2019 06:41:22 +0200 Subject: [PATCH 7/7] wip --- Moose Development/Moose/AI/AI_Escort.lua | 32 ++++++++---------------- 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/Moose Development/Moose/AI/AI_Escort.lua b/Moose Development/Moose/AI/AI_Escort.lua index 9f4f813aa..713129c0d 100644 --- a/Moose Development/Moose/AI/AI_Escort.lua +++ b/Moose Development/Moose/AI/AI_Escort.lua @@ -300,8 +300,14 @@ function AI_ESCORT:Menus() -- self:MenuScanForTargets( 100, 60 ) self:MenuJoinUp() - self:MenuFormationTrail( 0, 0, 50 ) - self:MenuFormationStack( 0, 0, 100, 150 ) + 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:MenuHoldAtEscortPosition( 1000, 500 ) self:MenuHoldAtLeaderPosition( 1000, 500 ) @@ -333,12 +339,12 @@ function AI_ESCORT:MenuFormation( Formation, ... ) function ( self, Formation, ... ) self.EscortGroupSet:ForEachGroupAlive( --- @param Core.Group#GROUP EscortGroup - function( EscortGroup ) + function( EscortGroup, self, Formation, ... ) if EscortGroup:IsAir() then self:E({Formation=Formation}) - self["Formation"..Formation]( arg ) + self["Formation"..Formation]( self, ... ) end - end + end, self, Formation, ... ) end, self, Formation, ... ) @@ -426,22 +432,6 @@ function AI_ESCORT:MenuFormationStack( XStart, XSpace, YStart, YSpace ) end ---- Defines a menu slot to let the escort to join in a leFt wing formation. --- This menu will appear under **Formation**. --- @param #AI_ESCORT self --- @param #number XStart The start position on the X-axis in meters for the first group. --- @param #nubmer YStart The start position on the Y-axis in meters for the first 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. --- @return #AI_ESCORT -function AI_ESCORT:MenuFormationLeftWing( XStart, YStart, ZStart, ZSpace ) - - self:MenuFormation( "LeftWing", XStart, YStart, ZStart, ZSpace ) - - return self -end - - --- Defines a menu slot to let the escort to join in a leFt wing formation. -- This menu will appear under **Formation**. -- @param #AI_ESCORT self