diff --git a/Moose Development/Moose/AI/AI_A2A.lua b/Moose Development/Moose/AI/AI_A2A.lua index d26c97ae3..041e04556 100644 --- a/Moose Development/Moose/AI/AI_A2A.lua +++ b/Moose Development/Moose/AI/AI_A2A.lua @@ -12,6 +12,8 @@ -- -- @module AI_A2A +BASE:TraceClass("AI_A2A") + --- @type AI_A2A -- @extends Core.Fsm#FSM_CONTROLLABLE @@ -72,6 +74,32 @@ function AI_A2A:New( AIGroup ) self:ManageDamage( 1 ) self:SetStartState( "Stopped" ) + + self:AddTransition( "*", "Start", "Started" ) + + --- Start Handler OnBefore for AI_A2A + -- @function [parent=#AI_A2A] OnBeforeStart + -- @param #AI_A2A self + -- @param #string From + -- @param #string Event + -- @param #string To + -- @return #boolean + + --- Start Handler OnAfter for AI_A2A + -- @function [parent=#AI_A2A] OnAfterStart + -- @param #AI_A2A self + -- @param #string From + -- @param #string Event + -- @param #string To + + --- Start Trigger for AI_A2A + -- @function [parent=#AI_A2A] Start + -- @param #AI_A2A self + + --- Start Asynchronous Trigger for AI_A2A + -- @function [parent=#AI_A2A] __Start + -- @param #AI_A2A self + -- @param #number Delay self:AddTransition( "*", "Stop", "Stopped" ) @@ -287,7 +315,7 @@ end function AI_A2A:onafterStart( Controllable, From, Event, To ) self:F2() - self:__Status( 60 ) -- Check status status every 30 seconds. + self:__Status( 10 ) -- Check status status every 30 seconds. self:HandleEvent( EVENTS.PilotDead, self.OnPilotDead ) self:HandleEvent( EVENTS.Crash, self.OnCrash ) @@ -307,13 +335,14 @@ end --- @param #AI_A2A self function AI_A2A:onafterStatus() - self:F2() + self:F() if self.Controllable and self.Controllable:IsAlive() then local RTB = false local Fuel = self.Controllable:GetUnit(1):GetFuel() + self:F({Fuel=Fuel}) if Fuel < self.PatrolFuelTresholdPercentage then self:E( self.Controllable:GetName() .. " is out of fuel:" .. Fuel .. ", RTB!" ) local OldAIControllable = self.Controllable @@ -329,7 +358,9 @@ function AI_A2A:onafterStatus() -- TODO: Check GROUP damage function. local Damage = self.Controllable:GetLife() - if Damage <= self.PatrolDamageTreshold then + local InitialLife = self.Controllable:GetLife0() + self:F( { Damage = Damage, InitialLife = InitialLife, DamageTreshold = self.PatrolDamageTreshold } ) + if ( Damage / InitialLife ) < self.PatrolDamageTreshold then self:E( self.Controllable:GetName() .. " is damaged:" .. Damage .. ", RTB!" ) RTB = true end @@ -337,7 +368,7 @@ function AI_A2A:onafterStatus() if RTB == true then self:RTB() else - self:__Status( 60 ) -- Execute the Patrol event after 30 seconds. + self:__Status( 10 ) -- Execute the Patrol event after 30 seconds. end end end diff --git a/Moose Development/Moose/AI/AI_A2A_Cap.lua b/Moose Development/Moose/AI/AI_A2A_Cap.lua index f60dc492b..22ac96127 100644 --- a/Moose Development/Moose/AI/AI_A2A_Cap.lua +++ b/Moose Development/Moose/AI/AI_A2A_Cap.lua @@ -26,6 +26,7 @@ -- -- @module AI_A2A_Cap +BASE:TraceClass("AI_A2A_CAP") --- @type AI_A2A_CAP -- @extends AI.AI_A2A_Patrol#AI_A2A_PATROL @@ -117,8 +118,6 @@ AI_A2A_CAP = { ClassName = "AI_A2A_CAP", } - - --- Creates a new AI_A2A_CAP object -- @param #AI_A2A_CAP self -- @param Wrapper.Group#GROUP AIGroup @@ -343,11 +342,10 @@ end -- todo: need to fix this global function ---- @param Wrapper.Controllable#CONTROLLABLE AIControllable -function _NewEngageCapRoute( AIControllable ) +--- @param Wrapper.Group#GROUP AIGroup +function AI_A2A_CAP.AttackRoute( AIGroup ) - AIControllable:T( "NewEngageRoute" ) - local EngageZone = AIControllable:GetState( AIControllable, "EngageZone" ) -- AI.AI_Cap#AI_A2A_CAP + local EngageZone = AIGroup:GetState( AIGroup, "EngageZone" ) -- AI.AI_Cap#AI_A2A_CAP EngageZone:__Engage( 1 ) end @@ -384,85 +382,71 @@ function AI_A2A_CAP:onafterEngage( AIGroup, From, Event, To, AttackSetUnit ) self:F( { AIGroup, From, Event, To, AttackSetUnit} ) self.AttackSetUnit = AttackSetUnit or self.AttackSetUnit -- Core.Set#SET_UNIT + + local FirstAttackUnit = self.AttackSetUnit:GetFirst() + + if FirstAttackUnit then + + if AIGroup:IsAlive() then - if AIGroup:IsAlive() then + local EngageRoute = {} - local EngageRoute = {} - - --- Calculate the current route point. - local CurrentVec2 = self.Controllable:GetVec2() - - --TODO: Create GetAltitude function for GROUP, and delete GetUnit(1). - local CurrentAltitude = self.Controllable:GetUnit(1):GetAltitude() - local CurrentPointVec3 = POINT_VEC3:New( CurrentVec2.x, CurrentAltitude, CurrentVec2.y ) - local ToEngageZoneSpeed = self.PatrolMaxSpeed - local CurrentRoutePoint = CurrentPointVec3:RoutePointAir( + --- Calculate the target route point. + local CurrentCoord = AIGroup:GetCoordinate() + local ToTargetCoord = self.AttackSetUnit:GetFirst():GetCoordinate() + local ToTargetSpeed = math.random( self.MinSpeed, self.MaxSpeed ) + local ToInterceptAngle = CurrentCoord:GetAngleDegrees( CurrentCoord:GetDirectionVec3( ToTargetCoord ) ) + + --- Create a route point of type air. + local ToPatrolRoutePoint = CurrentCoord:Translate( 5000, ToInterceptAngle ):RoutePointAir( self.PatrolAltType, POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, - ToEngageZoneSpeed, + ToTargetSpeed, true ) - - EngageRoute[#EngageRoute+1] = CurrentRoutePoint - - - --- Find a random 2D point in PatrolZone. - local ToTargetVec2 = self.PatrolZone:GetRandomVec2() - self:T2( ToTargetVec2 ) - - --- Define Speed and Altitude. - local ToTargetAltitude = math.random( self.EngageFloorAltitude, self.EngageCeilingAltitude ) - local ToTargetSpeed = math.random( self.PatrolMinSpeed, self.PatrolMaxSpeed ) - self:T2( { self.PatrolMinSpeed, self.PatrolMaxSpeed, ToTargetSpeed } ) - - --- Obtain a 3D @{Point} from the 2D point + altitude. - local ToTargetPointVec3 = POINT_VEC3:New( ToTargetVec2.x, ToTargetAltitude, ToTargetVec2.y ) - - --- Create a route point of type air. - local ToPatrolRoutePoint = ToTargetPointVec3:RoutePointAir( - self.PatrolAltType, - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, - ToTargetSpeed, - true - ) - - EngageRoute[#EngageRoute+1] = ToPatrolRoutePoint - - AIGroup:OptionROEOpenFire() - AIGroup:OptionROTPassiveDefense() - - local AttackTasks = {} - - for AttackUnitID, AttackUnit in pairs( self.AttackSetUnit:GetSet() ) do - local AttackUnit = AttackUnit -- Wrapper.Unit#UNIT - self:T( { AttackUnit, AttackUnit:IsAlive(), AttackUnit:IsAir() } ) - if AttackUnit:IsAlive() and AttackUnit:IsAir() then - AttackTasks[#AttackTasks+1] = AIGroup:TaskAttackUnit( AttackUnit ) - end - end - - --- Now we're going to do something special, we're going to call a function from a waypoint action at the AIControllable... - self.Controllable:WayPointInitialize( EngageRoute ) - - - if #AttackTasks == 0 then - self:E("No targets found -> Going back to Patrolling") - self:__Abort( 1 ) - self:__Route( 1 ) - else - EngageRoute[1].task = AIGroup:TaskCombo( AttackTasks ) + + self:F( { Angle = ToInterceptAngle, ToTargetSpeed = ToTargetSpeed } ) + self:T2( { self.MinSpeed, self.MaxSpeed, ToTargetSpeed } ) - --- Do a trick, link the NewEngageRoute function of the object to the AIControllable in a temporary variable ... - self.Controllable:SetState( self.Controllable, "EngageZone", self ) + EngageRoute[#EngageRoute+1] = ToPatrolRoutePoint + + AIGroup:OptionROEOpenFire() + AIGroup:OptionROTPassiveDefense() - self.Controllable:WayPointFunction( #EngageRoute, 1, "_NewEngageCapRoute" ) + local AttackTasks = {} + + for AttackUnitID, AttackUnit in pairs( self.AttackSetUnit:GetSet() ) do + local AttackUnit = AttackUnit -- Wrapper.Unit#UNIT + self:T( { "Attacking Unit:", AttackUnit:GetName(), AttackUnit:IsAlive(), AttackUnit:IsAir() } ) + if AttackUnit:IsAlive() and AttackUnit:IsAir() then + AttackTasks[#AttackTasks+1] = AIGroup:TaskAttackUnit( AttackUnit ) + end + end + + --- Now we're going to do something special, we're going to call a function from a waypoint action at the AIControllable... + self.Controllable:WayPointInitialize( EngageRoute ) + + + if #AttackTasks == 0 then + self:E("No targets found -> Going back to Patrolling") + self:__Abort( 1 ) + self:__Route( 1 ) + else + AttackTasks[#AttackTasks+1] = AIGroup:TaskFunction( 1, #AttackTasks, "AI_A2A_CAP.AttackRoute" ) + EngageRoute[1].task = AIGroup:TaskCombo( AttackTasks ) + + --- Do a trick, link the NewEngageRoute function of the object to the AIControllable in a temporary variable ... + AIGroup:SetState( AIGroup, "EngageZone", self ) + end + + --- NOW ROUTE THE GROUP! + AIGroup:WayPointExecute( 1, 2 ) end - - --- NOW ROUTE THE GROUP! - self.Controllable:WayPointExecute( 1, 2 ) - + else + self:E("No targets found -> Going back to Patrolling") + self:__Abort( 1 ) + self:__Route( 1 ) end end diff --git a/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua b/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua index f5e422554..88f404a2c 100644 --- a/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua +++ b/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua @@ -56,11 +56,12 @@ do -- AI_A2A_DISPATCHER -- Inherits from DETECTION_MANAGER local self = BASE:Inherit( self, DETECTION_MANAGER:New( nil, Detection ) ) -- #AI_A2A_DISPATCHER - self.Detection = Detection + self.Detection = Detection -- Functional.Detection#DETECTION_AREAS -- This table models the DefenderSquadron templates. - self.DefenderSquadrons = self.DefenderSquadrons or {} -- The Defender Squadrons. - self.DefenderTasks = self.DefenderTasks or {} -- The Defenders Tasks. + self.DefenderSquadrons = {} -- The Defender Squadrons. + self.DefenderSpawns = {} + self.DefenderTasks = {} -- The Defenders Tasks. -- TODO: Check detection through radar. self.Detection:FilterCategories( Unit.Category.AIRPLANE, Unit.Category.HELICOPTER ) @@ -158,46 +159,36 @@ do -- AI_A2A_DISPATCHER -- @param #number Delay + -- Subscribe to the CRASH event so that when planes are shot + -- by a Unit from the dispatcher, they will be removed from the detection... + -- This will avoid the detection to still "know" the shot unit until the next detection. + -- Otherwise, a new intercept or engage may happen for an already shot plane! + + self:HandleEvent( EVENTS.Crash ) + self:HandleEvent( EVENTS.Dead ) self:__Start( 5 ) return self end - - - --- - -- @param #AI_A2A_DISPATCHER self - function AI_A2A_DISPATCHER:onafterCAP( From, Event, To, SquadronName ) - - self:F({SquadronName = SquadronName}) - self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {} - self.DefenderSquadrons[SquadronName].Cap = self.DefenderSquadrons[SquadronName].Cap or {} - - local DefenderSquadron = self.DefenderSquadrons[SquadronName] - local Cap = DefenderSquadron.Cap - - if Cap then - - if self:CanCAP( SquadronName ) then - local Spawn = DefenderSquadron.Spawn[ math.random( 1, #DefenderSquadron.Spawn ) ] - local AIGroup = Spawn:SpawnAtAirbase( DefenderSquadron.Airbase ) - self:F( { AIGroup = AIGroup:GetName() } ) - - if AIGroup then - - local Fsm = AI_A2A_CAP:New( AIGroup, Cap.Zone, Cap.FloorAltitude, Cap.CeilingAltitude, Cap.MinSpeed, Cap.MaxSpeed, Cap.AltType ) - Fsm:SetDispatcher( self ) - Fsm:__Patrol( 1 ) - - self:SetDefenderTask( AIGroup, "CAP", Fsm ) - end - end - else - error( "This squadron does not exist:" .. SquadronName ) - end - - end + --- @param #AI_A2A_DISPATCHER self + -- @param Core.Event#EVENTDATA EventData + function AI_A2A_DISPATCHER:OnEventCrash( EventData ) + self.Detection:ForgetDetectedUnit( EventData.IniUnitName ) + end + + + --- Calculates which AI friendlies are nearby the area + -- @param #AI_A2A_DISPATCHER self + -- @param DetectedItem + -- @return #number, Core.CommandCenter#REPORT + function AI_A2A_DISPATCHER:GetAIFriendliesNearBy( DetectedItem ) + + local FriendliesNearBy = self.Detection:GetFriendliesNearBy( DetectedItem ) + + return FriendliesNearBy + end --- -- @param #AI_A2A_DISPATCHER self @@ -226,6 +217,15 @@ do -- AI_A2A_DISPATCHER --- -- @param #AI_A2A_DISPATCHER self function AI_A2A_DISPATCHER:ClearDefenderTask( AIGroup ) + if AIGroup:IsAlive() then + local Target = self.DefenderTasks[AIGroup].Target + local Message = "Clearing (" .. self.DefenderTasks[AIGroup].Type .. ") " + Message = Message .. AIGroup:GetName() + if Target then + Message = Message .. ( Target and ( " from " .. Target.Index .. " [" .. Target.Set:Count() .. "]" ) ) or "" + end + self:F( { Target = Message } ) + end self.DefenderTasks[AIGroup] = nil return self end @@ -237,8 +237,10 @@ do -- AI_A2A_DISPATCHER self.DefenderTasks[AIGroup] = self.DefenderTasks[AIGroup] or {} self.DefenderTasks[AIGroup].Type = Type self.DefenderTasks[AIGroup].Fsm = Fsm - - self:SetDefenderTaskTarget( AIGroup, Target ) + + if Target then + self:SetDefenderTaskTarget( AIGroup, Target ) + end return self end @@ -247,9 +249,13 @@ do -- AI_A2A_DISPATCHER -- @param #AI_A2A_DISPATCHER self -- @param Wrapper.Group#GROUP AIGroup function AI_A2A_DISPATCHER:SetDefenderTaskTarget( AIGroup, Target ) - + + local Message = "(" .. self.DefenderTasks[AIGroup].Type .. ") " + Message = Message .. AIGroup:GetName() + Message = Message .. ( Target and ( " target " .. Target.Index .. " [" .. Target.Set:Count() .. "]" ) ) or "" + self:F( { Target = Message } ) if Target then - AIGroup:MessageToAll( AIGroup:GetName().. " target " .. Target.Index, 300 ) + AIGroup:MessageToAll( Message, 1200 ) self.DefenderTasks[AIGroup].Target = Target end return self @@ -257,83 +263,7 @@ do -- AI_A2A_DISPATCHER --- -- @param #AI_A2A_DISPATCHER self - function AI_A2A_DISPATCHER:onafterENGAGE( From, Event, To, Target, AIGroups ) - - self:F( { AIGroups = AIGroups } ) - - if AIGroups then - - for AIGroupID, AIGroup in pairs( AIGroups ) do - - local Fsm = self:GetDefenderTaskFsm( AIGroup ) - Fsm:__Engage( 1, Target.Set ) -- Engage on the TargetSetUnit - - self:SetDefenderTaskTarget( AIGroup, Target ) - end - end - end - - --- - -- @param #AI_A2A_DISPATCHER self - function AI_A2A_DISPATCHER:onafterINTERCEPT( From, Event, To, Target, DefendersMissing ) - - local ClosestDistance = 0 - local ClosestDefenderSquadronName = nil - - local AttackerCount = Target.Set:Count() - local DefendersCount = 0 - - while( DefendersCount < DefendersMissing ) do - - for SquadronName, DefenderSquadron in pairs( self.DefenderSquadrons or {} ) do - for InterceptID, Intercept in pairs( DefenderSquadron.Intercept or {} ) do - - local SpawnCoord = DefenderSquadron.Airbase:GetCoordinate() -- Core.Point#COORDINATE - local TargetCoord = Target.Set:GetFirst():GetCoordinate() - local Distance = SpawnCoord:Get2DDistance( TargetCoord ) - - if ClosestDistance == 0 or Distance < ClosestDistance then - ClosestDistance = Distance - ClosestDefenderSquadronName = SquadronName - end - end - end - - if ClosestDefenderSquadronName then - - local DefenderSquadron = self.DefenderSquadrons[ClosestDefenderSquadronName] - local Intercept = self.DefenderSquadrons[ClosestDefenderSquadronName].Intercept - - local Spawn = DefenderSquadron.Spawn[ math.random( 1, #DefenderSquadron.Spawn ) ] - local AIGroup = Spawn:SpawnAtAirbase( DefenderSquadron.Airbase ) - self:F( { AIGroup = AIGroup:GetName() } ) - - if AIGroup then - - DefendersCount = DefendersCount + AIGroup:GetSize() - - local Fsm = AI_A2A_INTERCEPT:New( AIGroup, Intercept.MinSpeed, Intercept.MaxSpeed ) - Fsm:SetDispatcher( self ) - Fsm:__Engage( 1, Target.Set ) -- Engage on the TargetSetUnit - - - self:SetDefenderTask( AIGroup, "INTERCEPT", Fsm, Target ) - - function Fsm:onafterRTB() - local Dispatcher = self:GetDispatcher() -- #AI_A2A_DISPATCHER - local AIGroup = self:GetControllable() - AIGroup:MessageToAll( AIGroup:GetName().. " cleared " .. Dispatcher.DefenderTasks[AIGroup].Target.Index, 300 ) - Dispatcher:ClearDefenderTask( AIGroup ) - end - - end - end - end - end - - - --- - -- @param #AI_A2A_DISPATCHER self + -- @return #AI_A2A_DISPATCHER function AI_A2A_DISPATCHER:SetSquadron( SquadronName, AirbaseName, SpawnTemplates, Resources ) self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {} @@ -348,18 +278,24 @@ do -- AI_A2A_DISPATCHER DefenderSquadron.Spawn = {} if type( SpawnTemplates ) == "string" then local SpawnTemplate = SpawnTemplates - DefenderSquadron.Spawn[1] = SPAWN:New( SpawnTemplate ) + self.DefenderSpawns[SpawnTemplate] = self.DefenderSpawns[SpawnTemplate] or SPAWN:New( SpawnTemplate ) + DefenderSquadron.Spawn[1] = self.DefenderSpawns[SpawnTemplate] else for TemplateID, SpawnTemplate in pairs( SpawnTemplates ) do - DefenderSquadron.Spawn[#DefenderSquadron.Spawn+1] = SPAWN:New( SpawnTemplate ) + self.DefenderSpawns[SpawnTemplate] = self.DefenderSpawns[SpawnTemplate] or SPAWN:New( SpawnTemplate ) + DefenderSquadron.Spawn[#DefenderSquadron.Spawn+1] = self.DefenderSpawns[SpawnTemplate] end end DefenderSquadron.Resources = Resources + + self:SetOverhead( SquadronName, 1 ) + return self end --- -- @param #AI_A2A_DISPATCHER self + -- @return #AI_A2A_DISPATCHER function AI_A2A_DISPATCHER:SetCAP( SquadronName, Zone, FloorAltitude, CeilingAltitude, MinSpeed, MaxSpeed, AltType ) self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {} @@ -388,6 +324,7 @@ do -- AI_A2A_DISPATCHER --- -- @param #AI_A2A_DISPATCHER self + -- @return #AI_A2A_DISPATCHER function AI_A2A_DISPATCHER:SetCAPInterval( SquadronName, CapLimit, LowInterval, HighInterval, Probability ) self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {} @@ -415,6 +352,7 @@ do -- AI_A2A_DISPATCHER --- -- @param #AI_A2A_DISPATCHER self + -- @return #AI_A2A_DISPATCHER function AI_A2A_DISPATCHER:GetCAPDelay( SquadronName ) self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {} @@ -442,9 +380,7 @@ do -- AI_A2A_DISPATCHER local Cap = DefenderSquadron.Cap if Cap then - self:E( { Cap = Cap } ) local CapCount = self:CountCapAirborne( SquadronName ) - self:F( { CapCount = CapCount, CapLimit = Cap.CapLimit } ) if CapCount < Cap.CapLimit then local Probability = math.random() if Probability <= Cap.Probability then @@ -459,6 +395,7 @@ do -- AI_A2A_DISPATCHER --- -- @param #AI_A2A_DISPATCHER self + -- @return #AI_A2A_DISPATCHER function AI_A2A_DISPATCHER:SetINTERCEPT( SquadronName, MinSpeed, MaxSpeed ) self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {} @@ -470,6 +407,44 @@ do -- AI_A2A_DISPATCHER Intercept.MaxSpeed = MaxSpeed end + --- + -- @param #AI_A2A_DISPATCHER self + -- @param #string SquadronName The name of the squadron. + -- @param #number Overhead The %-tage of Units that dispatching command will allocate to intercept in surplus of detected amount of units. + -- The default overhead is 1, so equal balance. The @{#AI_A2A_DISPATCHER.SetOverhead}() method can be used to tweak the defense strength, + -- taking into account the plane types of the squadron. For example, a MIG-31 with full long-distance A2A missiles payload, may still be less effective than a F-15C with short missiles... + -- So in this case, one may want to use the Overhead method to allocate more defending planes as the amount of detected attacking planes. + -- The overhead must be given as a decimal value with 1 as the neutral value, which means that Overhead values: + -- + -- * Higher than 1, will increase the defense unit amounts. + -- * Lower than 1, will decrease the defense unit amounts. + -- + -- The amount of defending units is calculated by multiplying the amount of detected attacking planes as part of the detected group + -- multiplied by the Overhead and rounded up to the smallest integer. + -- + -- The Overhead value set for a Squadron, can be programmatically adjusted (by using this SetOverhead method), to adjust the defense overhead during mission execution. + -- + -- See example below. + -- + -- @usage: + -- + -- local Dispatcher = AI_A2A_DISPATCHER:New( EWR ) + -- + -- -- An overhead of 1,5 with 1 planes detected, will allocate 2 planes ( 1 * 1,5 ) = 1,5 => rounded up gives 2. + -- -- An overhead of 1,5 with 2 planes detected, will allocate 3 planes ( 2 * 1,5 ) = 3 => rounded up gives 3. + -- -- An overhead of 1,5 with 3 planes detected, will allocate 5 planes ( 3 * 1,5 ) = 4,5 => rounded up gives 5 planes. + -- -- An overhead of 1,5 with 4 planes detected, will allocate 6 planes ( 4 * 1,5 ) = 6 => rounded up gives 6 planes. + -- + -- Dispatcher:SetOverhead( 1,5 ) + -- + -- + -- @return #AI_A2A_DISPATCHER + function AI_A2A_DISPATCHER:SetOverhead( SquadronName, Overhead ) + + self.Overhead = Overhead + + return self + end --- Creates an SWEEP task when there are targets for it. @@ -525,12 +500,12 @@ do -- AI_A2A_DISPATCHER -- First, count the active AIGroups Units, targetting the DetectedSet local AIUnitCount = 0 - for AIGroup, DefenderTask in pairs( self:GetDefenderTasks() ) do + local DefenderTasks = self:GetDefenderTasks() + for AIGroup, DefenderTask in pairs( DefenderTasks ) do local AIGroup = AIGroup -- Wrapper.Group#GROUP - if self:GetDefenderTaskTarget( AIGroup ) == Target then - if AIGroup:IsAlive() then - AIUnitCount = AIUnitCount + AIGroup:GetSize() - end + local DefenderTask = self:GetDefenderTaskTarget( AIGroup ) + if DefenderTask and DefenderTask.Index == Target.Index then + AIUnitCount = AIUnitCount + AIGroup:GetSize() end end @@ -541,7 +516,7 @@ do -- AI_A2A_DISPATCHER -- @param #AI_A2A_DISPATCHER self function AI_A2A_DISPATCHER:CountDefendersToBeEngaged( DetectedItem, DefenderCount ) - local ResultAIGroups = nil + local Friendlies = nil local DetectedSet = DetectedItem.Set local DetectedCount = DetectedSet:Count() @@ -551,23 +526,20 @@ do -- AI_A2A_DISPATCHER for AIName, AIFriendly in pairs( AIFriendlies or {} ) do -- We only allow to ENGAGE targets as long as the Units on both sides are balanced. if DetectedCount > DefenderCount then - local AIGroup = AIFriendly:GetGroup() -- Wrapper.Group#GROUP - self:F( { AIFriendly = AIGroup } ) - if AIGroup and AIGroup:IsAlive() then + local Friendly = AIFriendly:GetGroup() -- Wrapper.Group#GROUP + if Friendly and Friendly:IsAlive() then -- Ok, so we have a friendly near the potential target. -- Now we need to check if the AIGroup has a Task. - local DefenderTask = self:GetDefenderTask( AIGroup ) - self:F( {AIGroupTask = DefenderTask } ) + local DefenderTask = self:GetDefenderTask( Friendly ) if DefenderTask then -- The Task should be CAP or INTERCEPT - self:F( { Type = DefenderTask.Type } ) if DefenderTask.Type == "CAP" or DefenderTask.Type == "INTERCEPT" then -- If there is no target, then add the AIGroup to the ResultAIGroups for Engagement to the TargetSet - self:F( { Target = DefenderTask.Target } ) if DefenderTask.Target == nil then - ResultAIGroups = ResultAIGroups or {} - ResultAIGroups[AIGroup] = AIGroup - DefenderCount = DefenderCount + AIGroup:GetSize() + Friendlies = Friendlies or {} + Friendlies[Friendly] = Friendly + DefenderCount = DefenderCount + Friendly:GetSize() + self:F( { Friendly = Friendly:GetName() } ) end end end @@ -577,9 +549,134 @@ do -- AI_A2A_DISPATCHER end end - return ResultAIGroups + return Friendlies end + + + --- + -- @param #AI_A2A_DISPATCHER self + function AI_A2A_DISPATCHER:onafterCAP( From, Event, To, SquadronName ) + + self:F({SquadronName = SquadronName}) + self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {} + self.DefenderSquadrons[SquadronName].Cap = self.DefenderSquadrons[SquadronName].Cap or {} + + local DefenderSquadron = self.DefenderSquadrons[SquadronName] + local Cap = DefenderSquadron.Cap + + if Cap then + + if self:CanCAP( SquadronName ) then + local Spawn = DefenderSquadron.Spawn[ math.random( 1, #DefenderSquadron.Spawn ) ] + local AIGroup = Spawn:SpawnAtAirbase( DefenderSquadron.Airbase ) + self:F( { AIGroup = AIGroup:GetName() } ) + + if AIGroup then + + local Fsm = AI_A2A_CAP:New( AIGroup, Cap.Zone, Cap.FloorAltitude, Cap.CeilingAltitude, Cap.MinSpeed, Cap.MaxSpeed, Cap.AltType ) + Fsm:SetDispatcher( self ) + Fsm:Start() + Fsm:__Patrol( 1 ) + + self:SetDefenderTask( AIGroup, "CAP", Fsm ) + end + end + else + error( "This squadron does not exist:" .. SquadronName ) + end + + end + + + --- + -- @param #AI_A2A_DISPATCHER self + function AI_A2A_DISPATCHER:onafterENGAGE( From, Event, To, Target, AIGroups ) + + self:F( { AIGroups = AIGroups } ) + + if AIGroups then + + for AIGroupID, AIGroup in pairs( AIGroups ) do + + local Fsm = self:GetDefenderTaskFsm( AIGroup ) + Fsm:__Engage( 1, Target.Set ) -- Engage on the TargetSetUnit + + self:SetDefenderTaskTarget( AIGroup, Target ) + end + end + end + + --- + -- @param #AI_A2A_DISPATCHER self + function AI_A2A_DISPATCHER:onafterINTERCEPT( From, Event, To, Target, DefendersMissing, AIGroups ) + + local ClosestDistance = 0 + local ClosestDefenderSquadronName = nil + + local AttackerCount = Target.Set:Count() + local DefendersCount = 0 + + for AIGroupID, AIGroup in pairs( AIGroups or {} ) do + + local Fsm = self:GetDefenderTaskFsm( AIGroup ) + Fsm:__Engage( 1, Target.Set ) -- Engage on the TargetSetUnit + + self:SetDefenderTaskTarget( AIGroup, Target ) + + DefendersCount = DefendersCount + AIGroup:GetSize() + end + + while( DefendersCount < DefendersMissing ) do + + for SquadronName, DefenderSquadron in pairs( self.DefenderSquadrons or {} ) do + for InterceptID, Intercept in pairs( DefenderSquadron.Intercept or {} ) do + + local SpawnCoord = DefenderSquadron.Airbase:GetCoordinate() -- Core.Point#COORDINATE + local TargetCoord = Target.Set:GetFirst():GetCoordinate() + local Distance = SpawnCoord:Get2DDistance( TargetCoord ) + + if ClosestDistance == 0 or Distance < ClosestDistance then + ClosestDistance = Distance + ClosestDefenderSquadronName = SquadronName + end + end + end + + if ClosestDefenderSquadronName then + + local DefenderSquadron = self.DefenderSquadrons[ClosestDefenderSquadronName] + local Intercept = self.DefenderSquadrons[ClosestDefenderSquadronName].Intercept + + local Spawn = DefenderSquadron.Spawn[ math.random( 1, #DefenderSquadron.Spawn ) ] + local AIGroup = Spawn:SpawnAtAirbase( DefenderSquadron.Airbase ) + self:F( { AIGroup = AIGroup:GetName() } ) + + if AIGroup then + + DefendersCount = DefendersCount + AIGroup:GetSize() + + local Fsm = AI_A2A_INTERCEPT:New( AIGroup, Intercept.MinSpeed, Intercept.MaxSpeed ) + Fsm:SetDispatcher( self ) + Fsm:Start() + Fsm:__Engage( 1, Target.Set ) -- Engage on the TargetSetUnit + + + self:SetDefenderTask( AIGroup, "INTERCEPT", Fsm, Target ) + + function Fsm:onafterRTB() + self:F({"RTB"}) + local Dispatcher = self:GetDispatcher() -- #AI_A2A_DISPATCHER + local AIGroup = self:GetControllable() + Dispatcher:ClearDefenderTask( AIGroup ) + end + + end + end + end + end + + --- Creates an ENGAGE task when there are human friendlies airborne near the targets. -- @param #AI_A2A_DISPATCHER self @@ -619,63 +716,90 @@ do -- AI_A2A_DISPATCHER -- First, count the active AIGroups Units, targetting the DetectedSet local DefenderCount = self:CountDefendersEngaged( Target ) - local DefendersMissing = AttackerCount - DefenderCount + local DefendersMissing = math.ceil( ( AttackerCount - DefenderCount ) * self.Overhead ) + + local Friendlies = self:CountDefendersToBeEngaged( Target, DefenderCount ) if Target.IsDetected == true then - return DefendersMissing + return DefendersMissing, Friendlies end return nil, nil end - - - --- Calculates which friendlies are nearby the area + --- Assigns A2A AI Tasks in relation to the detected items. -- @param #AI_A2A_DISPATCHER self - -- @param DetectedItem - -- @return #number, Core.CommandCenter#REPORT - function AI_A2A_DISPATCHER:GetFriendliesNearBy( Target ) + -- @param Functional.Detection#DETECTION_BASE Detection The detection created by the @{Detection#DETECTION_BASE} derived object. + -- @return #boolean Return true if you want the task assigning to continue... false will cancel the loop. + function AI_A2A_DISPATCHER:ProcessDetected( Detection ) - local DetectedSet = Target.Set - local FriendlyUnitsNearBy = self.Detection:GetFriendliesNearBy( Target ) + local AreaMsg = {} + local TaskMsg = {} + local ChangeMsg = {} - local FriendlyTypes = {} - local FriendliesCount = 0 + local TaskReport = REPORT:New() - if FriendlyUnitsNearBy then - local DetectedTreatLevel = DetectedSet:CalculateThreatLevelA2G() - for FriendlyUnitName, FriendlyUnitData in pairs( FriendlyUnitsNearBy ) do - local FriendlyUnit = FriendlyUnitData -- Wrapper.Unit#UNIT - if FriendlyUnit:IsAirPlane() then - local FriendlyUnitThreatLevel = FriendlyUnit:GetThreatLevel() - FriendliesCount = FriendliesCount + 1 - local FriendlyType = FriendlyUnit:GetTypeName() - FriendlyTypes[FriendlyType] = FriendlyTypes[FriendlyType] and ( FriendlyTypes[FriendlyType] + 1 ) or 1 - if DetectedTreatLevel < FriendlyUnitThreatLevel + 2 then - end + + for AIGroup, DefenderTask in pairs( self:GetDefenderTasks() ) do + local AIGroup = AIGroup -- Wrapper.Group#GROUP + if not AIGroup:IsAlive() then + self:ClearDefenderTask( AIGroup ) + end + end + + -- Now that all obsolete tasks are removed, loop through the detected targets. + for DetectedItemID, DetectedItem in pairs( Detection:GetDetectedItems() ) do + + local DetectedItem = DetectedItem -- Functional.Detection#DETECTION_BASE.DetectedItem + local DetectedSet = DetectedItem.Set -- Core.Set#SET_UNIT + local DetectedCount = DetectedSet:Count() + local DetectedZone = DetectedItem.Zone + + self:F( { "Target ID", DetectedItem.ItemID } ) + DetectedSet:Flush() + + local DetectedID = DetectedItem.ID + local DetectionIndex = DetectedItem.Index + local DetectedItemChanged = DetectedItem.Changed + + do + local Friendlies = self:EvaluateENGAGE( DetectedItem ) -- Returns a SetUnit if there are targets to be INTERCEPTed... + if Friendlies then + self:F( { AIGroups = Friendlies } ) + self:ENGAGE( DetectedItem, Friendlies ) end end - - end - --self:E( { FriendliesCount = FriendliesCount } ) - - local FriendlyTypesReport = REPORT:New() - - if FriendliesCount > 0 then - for FriendlyType, FriendlyTypeCount in pairs( FriendlyTypes ) do - FriendlyTypesReport:Add( string.format("%d of %s", FriendlyTypeCount, FriendlyType ) ) + do + local DefendersMissing, Friendlies = self:EvaluateINTERCEPT( DetectedItem ) + if DefendersMissing then + self:F( { DefendersMissing = DefendersMissing } ) + self:INTERCEPT( DetectedItem, DefendersMissing, Friendlies ) + end end - else - FriendlyTypesReport:Add( "-" ) + end + + -- Show tactical situation + for Defender, DefenderTask in pairs( self:GetDefenderTasks() ) do + local Defender = Defender -- Wrapper.Group#GROUP + local Message = string.format( "%s, %s", Defender:GetName(), DefenderTask.Type ) + if DefenderTask.Target then + Message = Message .. " => " .. DefenderTask.Target.Index + end + self:F( { Tactical = Message } ) end - return FriendliesCount, FriendlyTypesReport + + return true end +end + +do + --- Calculates which HUMAN friendlies are nearby the area -- @param #AI_A2A_DISPATCHER self -- @param DetectedItem @@ -722,71 +846,50 @@ do -- AI_A2A_DISPATCHER return PlayersCount, PlayerTypesReport end - --- Calculates which AI friendlies are nearby the area + --- Calculates which friendlies are nearby the area -- @param #AI_A2A_DISPATCHER self -- @param DetectedItem -- @return #number, Core.CommandCenter#REPORT - function AI_A2A_DISPATCHER:GetAIFriendliesNearBy( DetectedItem ) + function AI_A2A_DISPATCHER:GetFriendliesNearBy( Target ) - local FriendliesNearBy = self.Detection:GetFriendliesNearBy( DetectedItem ) + local DetectedSet = Target.Set + local FriendlyUnitsNearBy = self.Detection:GetFriendliesNearBy( Target ) - return FriendliesNearBy - end + local FriendlyTypes = {} + local FriendliesCount = 0 - - --- Assigns A2A AI Tasks in relation to the detected items. - -- @param #AI_A2A_DISPATCHER self - -- @param Functional.Detection#DETECTION_BASE Detection The detection created by the @{Detection#DETECTION_BASE} derived object. - -- @return #boolean Return true if you want the task assigning to continue... false will cancel the loop. - function AI_A2A_DISPATCHER:ProcessDetected( Detection ) - - local AreaMsg = {} - local TaskMsg = {} - local ChangeMsg = {} - - local TaskReport = REPORT:New() - - - - -- Now that all obsolete tasks are removed, loop through the detected targets. - for DetectedItemID, DetectedItem in pairs( Detection:GetDetectedItems() ) do - - local DetectedItem = DetectedItem -- Functional.Detection#DETECTION_BASE.DetectedItem - local DetectedSet = DetectedItem.Set -- Core.Set#SET_UNIT - local DetectedCount = DetectedSet:Count() - local DetectedZone = DetectedItem.Zone - - self:F( { "Targets in DetectedItem", DetectedItem.ItemID, DetectedSet:Count() } ) - - for AIGroup, DefenderTask in pairs( self:GetDefenderTasks() ) do - local AIGroup = AIGroup -- Wrapper.Group#GROUP - if not AIGroup:IsAlive() then - self:ClearDefenderTask( AIGroup ) + if FriendlyUnitsNearBy then + local DetectedTreatLevel = DetectedSet:CalculateThreatLevelA2G() + for FriendlyUnitName, FriendlyUnitData in pairs( FriendlyUnitsNearBy ) do + local FriendlyUnit = FriendlyUnitData -- Wrapper.Unit#UNIT + if FriendlyUnit:IsAirPlane() then + local FriendlyUnitThreatLevel = FriendlyUnit:GetThreatLevel() + FriendliesCount = FriendliesCount + 1 + local FriendlyType = FriendlyUnit:GetTypeName() + FriendlyTypes[FriendlyType] = FriendlyTypes[FriendlyType] and ( FriendlyTypes[FriendlyType] + 1 ) or 1 + if DetectedTreatLevel < FriendlyUnitThreatLevel + 2 then + end end end - - local DetectedID = DetectedItem.ID - local A2A_Index = DetectedItem.Index - local DetectedItemChanged = DetectedItem.Changed - do - local AIGroups = self:EvaluateENGAGE( DetectedItem ) -- Returns a SetUnit if there are targets to be INTERCEPTed... - self:F( { AIGroups = AIGroups } ) - if AIGroups then - self:ENGAGE( DetectedItem, AIGroups ) - end - end + end - do - local DefendersMissing = self:EvaluateINTERCEPT( DetectedItem ) - self:F( { DefendersMissing = DefendersMissing } ) - if DefendersMissing then - self:INTERCEPT( DetectedItem, DefendersMissing ) - end + --self:E( { FriendliesCount = FriendliesCount } ) + + local FriendlyTypesReport = REPORT:New() + + if FriendliesCount > 0 then + for FriendlyType, FriendlyTypeCount in pairs( FriendlyTypes ) do + FriendlyTypesReport:Add( string.format("%d of %s", FriendlyTypeCount, FriendlyType ) ) end + else + FriendlyTypesReport:Add( "-" ) end - return true + + return FriendliesCount, FriendlyTypesReport end -end + + +end \ No newline at end of file diff --git a/Moose Development/Moose/AI/AI_A2A_Intercept.lua b/Moose Development/Moose/AI/AI_A2A_Intercept.lua index 9516110a9..b24f93e18 100644 --- a/Moose Development/Moose/AI/AI_A2A_Intercept.lua +++ b/Moose Development/Moose/AI/AI_A2A_Intercept.lua @@ -132,7 +132,7 @@ function AI_A2A_INTERCEPT:New( AIGroup, MinSpeed, MaxSpeed ) self.PatrolAltType = "RADIO" - self:AddTransition( { "Stopped", "Engaging" }, "Engage", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_A2A_INTERCEPT. + self:AddTransition( { "Started", "Engaging" }, "Engage", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_A2A_INTERCEPT. --- OnBefore Transition Handler for Event Engage. -- @function [parent=#AI_A2A_INTERCEPT] OnBeforeEngage @@ -314,7 +314,6 @@ function AI_A2A_INTERCEPT.InterceptRoute( AIControllable ) AIControllable:T( "NewEngageRoute" ) local EngageZone = AIControllable:GetState( AIControllable, "EngageZone" ) -- AI.AI_Cap#AI_A2A_INTERCEPT EngageZone:__Engage( 1 ) - AIControllable:MessageToAll( AIControllable:GetName() .. " Engaging", 15 ) end --- @param #AI_A2A_INTERCEPT self @@ -350,63 +349,71 @@ function AI_A2A_INTERCEPT:onafterEngage( AIGroup, From, Event, To, AttackSetUnit self:F( { AIGroup, From, Event, To, AttackSetUnit} ) self.AttackSetUnit = AttackSetUnit or self.AttackSetUnit -- Core.Set#SET_UNIT - - if AIGroup:IsAlive() then - - local EngageRoute = {} - - --- Calculate the current route point. - - local CurrentCoord = AIGroup:GetCoordinate() - local ToTargetCoord = self.AttackSetUnit:GetFirst():GetCoordinate() - local ToTargetSpeed = math.random( self.MinSpeed, self.MaxSpeed ) - local ToInterceptAngle = CurrentCoord:GetAngleDegrees( CurrentCoord:GetDirectionVec3( ToTargetCoord ) ) - - --- Create a route point of type air. - local ToPatrolRoutePoint = CurrentCoord:Translate( 5000, ToInterceptAngle ):RoutePointAir( - self.PatrolAltType, - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, - ToTargetSpeed, - true - ) - - self:F( { Angle = ToInterceptAngle, ToTargetSpeed = ToTargetSpeed } ) - self:T2( { self.MinSpeed, self.MaxSpeed, ToTargetSpeed } ) - - EngageRoute[#EngageRoute+1] = ToPatrolRoutePoint - - AIGroup:OptionROEOpenFire() - AIGroup:OptionROTPassiveDefense() - - local AttackTasks = {} - - for AttackUnitID, AttackUnit in pairs( self.AttackSetUnit:GetSet() ) do - local AttackUnit = AttackUnit -- Wrapper.Unit#UNIT - self:T( { "Intercepting Unit:", AttackUnit:GetName(), AttackUnit:IsAlive(), AttackUnit:IsAir() } ) - if AttackUnit:IsAlive() and AttackUnit:IsAir() then - AttackTasks[#AttackTasks+1] = AIGroup:TaskAttackUnit( AttackUnit ) - end - end - - --- Now we're going to do something special, we're going to call a function from a waypoint action at the AIControllable... - AIGroup:WayPointInitialize( EngageRoute ) - - - if #AttackTasks == 0 then - self:E("No targets found -> Going RTB") - self:__RTB( 1 ) - else - AttackTasks[#AttackTasks+1] = AIGroup:TaskFunction( 1, #AttackTasks, "AI_A2A_INTERCEPT.InterceptRoute" ) - EngageRoute[1].task = AIGroup:TaskCombo( AttackTasks ) - - --- Do a trick, link the NewEngageRoute function of the object to the AIControllable in a temporary variable ... - AIGroup:SetState( AIGroup, "EngageZone", self ) - end - - --- NOW ROUTE THE GROUP! - AIGroup:WayPointExecute( 1, 2 ) + local FirstAttackUnit = self.AttackSetUnit:GetFirst() + + if FirstAttackUnit then + + if AIGroup:IsAlive() then + + local EngageRoute = {} + + --- Calculate the target route point. + + local CurrentCoord = AIGroup:GetCoordinate() + local ToTargetCoord = self.AttackSetUnit:GetFirst():GetCoordinate() + local ToTargetSpeed = math.random( self.MinSpeed, self.MaxSpeed ) + local ToInterceptAngle = CurrentCoord:GetAngleDegrees( CurrentCoord:GetDirectionVec3( ToTargetCoord ) ) + + --- Create a route point of type air. + local ToPatrolRoutePoint = CurrentCoord:Translate( 5000, ToInterceptAngle ):RoutePointAir( + self.PatrolAltType, + POINT_VEC3.RoutePointType.TurningPoint, + POINT_VEC3.RoutePointAction.TurningPoint, + ToTargetSpeed, + true + ) + + self:F( { Angle = ToInterceptAngle, ToTargetSpeed = ToTargetSpeed } ) + self:T2( { self.MinSpeed, self.MaxSpeed, ToTargetSpeed } ) + + EngageRoute[#EngageRoute+1] = ToPatrolRoutePoint + + AIGroup:OptionROEOpenFire() + AIGroup:OptionROTPassiveDefense() + + local AttackTasks = {} + + for AttackUnitID, AttackUnit in pairs( self.AttackSetUnit:GetSet() ) do + local AttackUnit = AttackUnit -- Wrapper.Unit#UNIT + self:T( { "Intercepting Unit:", AttackUnit:GetName(), AttackUnit:IsAlive(), AttackUnit:IsAir() } ) + if AttackUnit:IsAlive() and AttackUnit:IsAir() then + AttackTasks[#AttackTasks+1] = AIGroup:TaskAttackUnit( AttackUnit ) + end + end + + --- Now we're going to do something special, we're going to call a function from a waypoint action at the AIControllable... + AIGroup:WayPointInitialize( EngageRoute ) + + + if #AttackTasks == 0 then + self:E("No targets found -> Going RTB") + self:__RTB( 1 ) + else + AttackTasks[#AttackTasks+1] = AIGroup:TaskFunction( 1, #AttackTasks, "AI_A2A_INTERCEPT.InterceptRoute" ) + EngageRoute[1].task = AIGroup:TaskCombo( AttackTasks ) + + --- Do a trick, link the NewEngageRoute function of the object to the AIControllable in a temporary variable ... + AIGroup:SetState( AIGroup, "EngageZone", self ) + end + + --- NOW ROUTE THE GROUP! + AIGroup:WayPointExecute( 1, 2 ) + + end + else + self:E("No targets found -> Going RTB") + self:__RTB( 1 ) end end diff --git a/Moose Development/Moose/AI/AI_A2A_Patrol.lua b/Moose Development/Moose/AI/AI_A2A_Patrol.lua index 33a63f3c8..c721e093d 100644 --- a/Moose Development/Moose/AI/AI_A2A_Patrol.lua +++ b/Moose Development/Moose/AI/AI_A2A_Patrol.lua @@ -179,7 +179,7 @@ function AI_A2A_PATROL:New( AIGroup, PatrolZone, PatrolFloorAltitude, PatrolCeil -- defafult PatrolAltType to "RADIO" if not specified self.PatrolAltType = PatrolAltType or "RADIO" - self:AddTransition( "Stopped", "Patrol", "Patrolling" ) + self:AddTransition( "Started", "Patrol", "Patrolling" ) --- OnBefore Transition Handler for Event Patrol. -- @function [parent=#AI_A2A_PATROL] OnBeforePatrol diff --git a/Moose Development/Moose/Core/Message.lua b/Moose Development/Moose/Core/Message.lua index 755bcd971..e632f38fc 100644 --- a/Moose Development/Moose/Core/Message.lua +++ b/Moose Development/Moose/Core/Message.lua @@ -87,7 +87,7 @@ function MESSAGE:New( MessageText, MessageDuration, MessageCategory ) self.MessageDuration = MessageDuration or 5 self.MessageTime = timer.getTime() - self.MessageText = MessageText + self.MessageText = MessageText:gsub("^\n","",1):gsub("\n$","",1) self.MessageSent = false self.MessageGroup = false diff --git a/Moose Development/Moose/Functional/Detection.lua b/Moose Development/Moose/Functional/Detection.lua index edab3062a..f009fcc07 100644 --- a/Moose Development/Moose/Functional/Detection.lua +++ b/Moose Development/Moose/Functional/Detection.lua @@ -611,7 +611,7 @@ do -- DETECTION_BASE if self.AcceptZones then for AcceptZoneID, AcceptZone in pairs( self.AcceptZones ) do local AcceptZone = AcceptZone -- Core.Zone#ZONE_BASE - if AcceptZone:IsPointVec2InZone( DetectedObjectVec2 ) == false then + if AcceptZone:IsVec2InZone( DetectedObjectVec2 ) == false then DetectionAccepted = false end end @@ -759,6 +759,25 @@ do -- DETECTION_BASE return self end + + --- Forget a Unit from a DetectionItem + -- @param #DETECTION_BASE self + -- @param #string UnitName The UnitName that needs to be forgotten from the DetectionItem Sets. + -- @return #DETECTION_BASE + function DETECTION_BASE:ForgetDetectedUnit( UnitName ) + self:F2() + + local DetectedItems = self:GetDetectedItems() + + for DetectedItemIndex, DetectedItem in pairs( DetectedItems ) do + local DetectedSet = self:GetDetectedSet( DetectedItemIndex ) + if DetectedSet then + DetectedSet:RemoveUnitsByName( UnitName ) + end + end + + return self + end --- Make a DetectionSet table. This function will be overridden in the derived clsses. -- @param #DETECTION_BASE self diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index 87d1948d7..9e475e047 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -228,6 +228,36 @@ function CONTROLLABLE:GetLife() return nil end +--- Returns the initial health. +-- @param #CONTROLLABLE self +-- @return #number The controllable health value (unit or group average). +-- @return #nil The controllable is not existing or alive. +function CONTROLLABLE:GetLife0() + self:F2( self.ControllableName ) + + local DCSControllable = self:GetDCSObject() + + if DCSControllable then + local UnitLife = 0 + local Units = self:GetUnits() + if #Units == 1 then + local Unit = Units[1] -- Wrapper.Unit#UNIT + UnitLife = Unit:GetLife0() + else + local UnitLifeTotal = 0 + for UnitID, Unit in pairs( Units ) do + local Unit = Unit -- Wrapper.Unit#UNIT + UnitLifeTotal = UnitLifeTotal + Unit:GetLife0() + end + UnitLife = UnitLifeTotal / #Units + end + return UnitLife + end + + return nil +end + + -- Tasks