Manual diff merge from FF/Development

Hopefully cleans up the mess now.
This commit is contained in:
Frank 2018-12-09 19:11:46 +01:00
parent 8d222fd0c3
commit afb0a8af33
15 changed files with 7307 additions and 1234 deletions

View File

@ -438,13 +438,14 @@ function AI_A2A:onafterStatus()
RTB = false RTB = false
end end
end end
if self:Is( "Fuel" ) or self:Is( "Damaged" ) or self:Is( "LostControl" ) then -- I think this code is not requirement anymore after release 2.5.
if DistanceFromHomeBase < 5000 then -- if self:Is( "Fuel" ) or self:Is( "Damaged" ) or self:Is( "LostControl" ) then
self:E( self.Controllable:GetName() .. " is too far from home base, RTB!" ) -- if DistanceFromHomeBase < 5000 then
self:Home( "Destroy" ) -- self:E( self.Controllable:GetName() .. " is near the home base, RTB!" )
end -- self:Home( "Destroy" )
end -- end
-- end
if not self:Is( "Fuel" ) and not self:Is( "Home" ) then if not self:Is( "Fuel" ) and not self:Is( "Home" ) then
@ -481,9 +482,12 @@ function AI_A2A:onafterStatus()
end end
-- Check if planes went RTB and are out of control. -- Check if planes went RTB and are out of control.
-- We only check if planes are out of control, when they are in duty.
if self.Controllable:HasTask() == false then if self.Controllable:HasTask() == false then
if not self:Is( "Started" ) and if not self:Is( "Started" ) and
not self:Is( "Stopped" ) and not self:Is( "Stopped" ) and
not self:Is( "Fuel" ) and
not self:Is( "Damaged" ) and
not self:Is( "Home" ) then not self:Is( "Home" ) then
if self.IdleCount >= 2 then if self.IdleCount >= 2 then
if Damage ~= InitialLife then if Damage ~= InitialLife then
@ -503,8 +507,11 @@ function AI_A2A:onafterStatus()
if RTB == true then if RTB == true then
self:__RTB( 0.5 ) self:__RTB( 0.5 )
end end
if not self:Is("Home") then
self:__Status( 10 )
end
self:__Status( 10 )
end end
end end

View File

@ -274,7 +274,7 @@ do -- AI_A2A_DISPATCHER
-- A2ADispatcher_Red = AI_A2A_DISPATCHER:New( EWR_Red ) -- A2ADispatcher_Red = AI_A2A_DISPATCHER:New( EWR_Red )
-- A2ADispatcher_Blue = AI_A2A_DISPATCHER:New( EWR_Blue ) -- A2ADispatcher_Blue = AI_A2A_DISPATCHER:New( EWR_Blue )
-- --
-- ### 2. Define the detected **target grouping radius**: -- ### 1.2. Define the detected **target grouping radius**:
-- --
-- The target grouping radius is a property of the Detection object, that was passed to the AI\_A2A\_DISPATCHER object, but can be changed. -- The target grouping radius is a property of the Detection object, that was passed to the AI\_A2A\_DISPATCHER object, but can be changed.
-- The grouping radius should not be too small, but also depends on the types of planes and the era of the simulation. -- The grouping radius should not be too small, but also depends on the types of planes and the era of the simulation.
@ -1013,12 +1013,48 @@ do -- AI_A2A_DISPATCHER
self:SetTacticalDisplay( false ) self:SetTacticalDisplay( false )
self.DefenderCAPIndex = 0
self:__Start( 5 ) self:__Start( 5 )
return self return self
end end
--- @param #AI_A2A_DISPATCHER self
function AI_A2A_DISPATCHER:onafterStart( From, Event, To )
self:GetParent( self, AI_A2A_DISPATCHER ).onafterStart( self, From, Event, To )
-- Spawn the resources.
for SquadronName, DefenderSquadron in pairs( self.DefenderSquadrons ) do
DefenderSquadron.Resource = {}
if DefenderSquadron.ResourceCount then
for Resource = 1, DefenderSquadron.ResourceCount do
self:ParkDefender( DefenderSquadron )
end
end
end
end
--- @param #AI_A2A_DISPATCHER self
function AI_A2A_DISPATCHER:ParkDefender( DefenderSquadron )
local TemplateID = math.random( 1, #DefenderSquadron.Spawn )
local Spawn = DefenderSquadron.Spawn[ TemplateID ] -- Core.Spawn#SPAWN
Spawn:InitGrouping( 1 )
local SpawnGroup
if self:IsSquadronVisible( DefenderSquadron.Name ) then
SpawnGroup = Spawn:SpawnAtAirbase( DefenderSquadron.Airbase, SPAWN.Takeoff.Cold )
local GroupName = SpawnGroup:GetName()
DefenderSquadron.Resources = DefenderSquadron.Resources or {}
DefenderSquadron.Resources[TemplateID] = DefenderSquadron.Resources[TemplateID] or {}
DefenderSquadron.Resources[TemplateID][GroupName] = {}
DefenderSquadron.Resources[TemplateID][GroupName] = SpawnGroup
end
end
--- @param #AI_A2A_DISPATCHER self --- @param #AI_A2A_DISPATCHER self
-- @param Core.Event#EVENTDATA EventData -- @param Core.Event#EVENTDATA EventData
function AI_A2A_DISPATCHER:OnEventBaseCaptured( EventData ) function AI_A2A_DISPATCHER:OnEventBaseCaptured( EventData )
@ -1030,7 +1066,7 @@ do -- AI_A2A_DISPATCHER
-- Now search for all squadrons located at the airbase, and sanatize them. -- Now search for all squadrons located at the airbase, and sanatize them.
for SquadronName, Squadron in pairs( self.DefenderSquadrons ) do for SquadronName, Squadron in pairs( self.DefenderSquadrons ) do
if Squadron.AirbaseName == AirbaseName then if Squadron.AirbaseName == AirbaseName then
Squadron.Resources = -999 -- The base has been captured, and the resources are eliminated. No more spawning. Squadron.ResourceCount = -999 -- The base has been captured, and the resources are eliminated. No more spawning.
Squadron.Captured = true Squadron.Captured = true
self:I( "Squadron " .. SquadronName .. " captured." ) self:I( "Squadron " .. SquadronName .. " captured." )
end end
@ -1059,6 +1095,7 @@ do -- AI_A2A_DISPATCHER
self:RemoveDefenderFromSquadron( Squadron, Defender ) self:RemoveDefenderFromSquadron( Squadron, Defender )
end end
DefenderUnit:Destroy() DefenderUnit:Destroy()
self:ParkDefender( Squadron, Defender )
return return
end end
if DefenderUnit:GetLife() ~= DefenderUnit:GetLife0() then if DefenderUnit:GetLife() ~= DefenderUnit:GetLife0() then
@ -1085,6 +1122,7 @@ do -- AI_A2A_DISPATCHER
self:RemoveDefenderFromSquadron( Squadron, Defender ) self:RemoveDefenderFromSquadron( Squadron, Defender )
end end
DefenderUnit:Destroy() DefenderUnit:Destroy()
self:ParkDefender( Squadron, Defender )
end end
end end
end end
@ -1474,7 +1512,7 @@ do -- AI_A2A_DISPATCHER
-- Just remember that your template (groups late activated) need to start with the prefix you have specified in your code. -- Just remember that your template (groups late activated) need to start with the prefix you have specified in your code.
-- If you have only one prefix name for a squadron, you don't need to use the `{ }`, otherwise you need to use the brackets. -- If you have only one prefix name for a squadron, you don't need to use the `{ }`, otherwise you need to use the brackets.
-- --
-- @param #number Resources (optional) A number that specifies how many resources are in stock of the squadron. If not specified, the squadron will have infinite resources available. -- @param #number ResourceCount (optional) A number that specifies how many resources are in stock of the squadron. If not specified, the squadron will have infinite resources available.
-- --
-- @usage -- @usage
-- -- Now Setup the A2A dispatcher, and initialize it using the Detection object. -- -- Now Setup the A2A dispatcher, and initialize it using the Detection object.
@ -1497,13 +1535,13 @@ do -- AI_A2A_DISPATCHER
-- --
-- @usage -- @usage
-- -- This is an example like the previous, but now with infinite resources. -- -- This is an example like the previous, but now with infinite resources.
-- -- The Resources parameter is not given in the SetSquadron method. -- -- The ResourceCount parameter is not given in the SetSquadron method.
-- A2ADispatcher:SetSquadron( "104th", "Batumi", "Mig-29" ) -- A2ADispatcher:SetSquadron( "104th", "Batumi", "Mig-29" )
-- A2ADispatcher:SetSquadron( "23th", "Batumi", "Su-27" ) -- A2ADispatcher:SetSquadron( "23th", "Batumi", "Su-27" )
-- --
-- --
-- @return #AI_A2A_DISPATCHER -- @return #AI_A2A_DISPATCHER
function AI_A2A_DISPATCHER:SetSquadron( SquadronName, AirbaseName, TemplatePrefixes, Resources ) function AI_A2A_DISPATCHER:SetSquadron( SquadronName, AirbaseName, TemplatePrefixes, ResourceCount )
self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {} self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {}
@ -1528,11 +1566,11 @@ do -- AI_A2A_DISPATCHER
DefenderSquadron.Spawn[#DefenderSquadron.Spawn+1] = self.DefenderSpawns[SpawnTemplate] DefenderSquadron.Spawn[#DefenderSquadron.Spawn+1] = self.DefenderSpawns[SpawnTemplate]
end end
end end
DefenderSquadron.Resources = Resources DefenderSquadron.ResourceCount = ResourceCount
DefenderSquadron.TemplatePrefixes = TemplatePrefixes DefenderSquadron.TemplatePrefixes = TemplatePrefixes
DefenderSquadron.Captured = false -- Not captured. This flag will be set to true, when the airbase where the squadron is located, is captured. DefenderSquadron.Captured = false -- Not captured. This flag will be set to true, when the airbase where the squadron is located, is captured.
self:F( { Squadron = {SquadronName, AirbaseName, TemplatePrefixes, Resources } } ) self:F( { Squadron = {SquadronName, AirbaseName, TemplatePrefixes, ResourceCount } } )
return self return self
end end
@ -1551,6 +1589,54 @@ do -- AI_A2A_DISPATCHER
end end
--- Set the Squadron visible before startup of the dispatcher.
-- All planes will be spawned as uncontrolled on the parking spot.
-- They will lock the parking spot.
-- @param #AI_A2A_DISPATCHER self
-- @param #string SquadronName The squadron name.
-- @return #AI_A2A_DISPATCHER
-- @usage
--
-- -- Set the Squadron visible before startup of dispatcher.
-- A2ADispatcher:SetSquadronVisible( "Mineralnye" )
--
function AI_A2A_DISPATCHER:SetSquadronVisible( SquadronName )
self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {}
local DefenderSquadron = self:GetSquadron( SquadronName )
DefenderSquadron.Uncontrolled = true
for SpawnTemplate, DefenderSpawn in pairs( self.DefenderSpawns ) do
DefenderSpawn:InitUnControlled()
end
end
--- Check if the Squadron is visible before startup of the dispatcher.
-- @param #AI_A2A_DISPATCHER self
-- @param #string SquadronName The squadron name.
-- @return #bool true if visible.
-- @usage
--
-- -- Set the Squadron visible before startup of dispatcher.
-- local IsVisible = A2ADispatcher:IsSquadronVisible( "Mineralnye" )
--
function AI_A2A_DISPATCHER:IsSquadronVisible( SquadronName )
self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {}
local DefenderSquadron = self:GetSquadron( SquadronName )
if DefenderSquadron then
return DefenderSquadron.Uncontrolled == true
end
return nil
end
--- Set a CAP for a Squadron. --- Set a CAP for a Squadron.
-- @param #AI_A2A_DISPATCHER self -- @param #AI_A2A_DISPATCHER self
-- @param #string SquadronName The squadron name. -- @param #string SquadronName The squadron name.
@ -1699,7 +1785,7 @@ do -- AI_A2A_DISPATCHER
if DefenderSquadron.Captured == false then -- We can only spawn new CAP if the base has not been captured. if DefenderSquadron.Captured == false then -- We can only spawn new CAP if the base has not been captured.
if ( not DefenderSquadron.Resources ) or ( DefenderSquadron.Resources and DefenderSquadron.Resources > 0 ) then -- And, if there are sufficient resources. if ( not DefenderSquadron.ResourceCount ) or ( DefenderSquadron.ResourceCount and DefenderSquadron.ResourceCount > 0 ) then -- And, if there are sufficient resources.
local Cap = DefenderSquadron.Cap local Cap = DefenderSquadron.Cap
if Cap then if Cap then
@ -1732,7 +1818,7 @@ do -- AI_A2A_DISPATCHER
if DefenderSquadron.Captured == false then -- We can only spawn new CAP if the base has not been captured. if DefenderSquadron.Captured == false then -- We can only spawn new CAP if the base has not been captured.
if ( not DefenderSquadron.Resources ) or ( DefenderSquadron.Resources and DefenderSquadron.Resources > 0 ) then -- And, if there are sufficient resources. if ( not DefenderSquadron.ResourceCount ) or ( DefenderSquadron.ResourceCount and DefenderSquadron.ResourceCount > 0 ) then -- And, if there are sufficient resources.
local Gci = DefenderSquadron.Gci local Gci = DefenderSquadron.Gci
if Gci then if Gci then
return DefenderSquadron return DefenderSquadron
@ -2490,21 +2576,21 @@ do -- AI_A2A_DISPATCHER
self.Defenders = self.Defenders or {} self.Defenders = self.Defenders or {}
local DefenderName = Defender:GetName() local DefenderName = Defender:GetName()
self.Defenders[ DefenderName ] = Squadron self.Defenders[ DefenderName ] = Squadron
if Squadron.Resources then if Squadron.ResourceCount then
Squadron.Resources = Squadron.Resources - Size Squadron.ResourceCount = Squadron.ResourceCount - Size
end end
self:F( { DefenderName = DefenderName, SquadronResources = Squadron.Resources } ) self:F( { DefenderName = DefenderName, SquadronResourceCount = Squadron.ResourceCount } )
end end
--- @param #AI_A2A_DISPATCHER self --- @param #AI_A2A_DISPATCHER self
function AI_A2A_DISPATCHER:RemoveDefenderFromSquadron( Squadron, Defender ) function AI_A2A_DISPATCHER:RemoveDefenderFromSquadron( Squadron, Defender )
self.Defenders = self.Defenders or {} self.Defenders = self.Defenders or {}
local DefenderName = Defender:GetName() local DefenderName = Defender:GetName()
if Squadron.Resources then if Squadron.ResourceCount then
Squadron.Resources = Squadron.Resources + Defender:GetSize() Squadron.ResourceCount = Squadron.ResourceCount + Defender:GetSize()
end end
self.Defenders[ DefenderName ] = nil self.Defenders[ DefenderName ] = nil
self:F( { DefenderName = DefenderName, SquadronResources = Squadron.Resources } ) self:F( { DefenderName = DefenderName, SquadronResourceCount = Squadron.ResourceCount } )
end end
function AI_A2A_DISPATCHER:GetSquadronFromDefender( Defender ) function AI_A2A_DISPATCHER:GetSquadronFromDefender( Defender )
@ -2646,7 +2732,80 @@ do -- AI_A2A_DISPATCHER
return Friendlies return Friendlies
end end
---
-- @param #AI_A2A_DISPATCHER self
function AI_A2A_DISPATCHER:ResourceActivate( DefenderSquadron, DefendersNeeded )
local SquadronName = DefenderSquadron.Name
DefendersNeeded = DefendersNeeded or 4
local DefenderGrouping = DefenderSquadron.Grouping or self.DefenderDefault.Grouping
DefenderGrouping = ( DefenderGrouping < DefendersNeeded ) and DefenderGrouping or DefendersNeeded
if self:IsSquadronVisible( SquadronName ) then
-- Here we CAP the new planes.
-- The Resources table is filled in advance.
local TemplateID = math.random( 1, #DefenderSquadron.Spawn ) -- Choose the template.
-- We determine the grouping based on the parameters set.
self:F( { DefenderGrouping = DefenderGrouping } )
-- New we will form the group to spawn in.
-- We search for the first free resource matching the template.
local DefenderUnitIndex = 1
local DefenderCAPTemplate = nil
local DefenderName = nil
for GroupName, DefenderGroup in pairs( DefenderSquadron.Resources[TemplateID] or {} ) do
self:F( { GroupName = GroupName } )
local DefenderTemplate = _DATABASE:GetGroupTemplate( GroupName )
if DefenderUnitIndex == 1 then
DefenderCAPTemplate = UTILS.DeepCopy( DefenderTemplate )
self.DefenderCAPIndex = self.DefenderCAPIndex + 1
DefenderCAPTemplate.name = SquadronName .. "#" .. self.DefenderCAPIndex .. "#" .. GroupName
DefenderName = DefenderCAPTemplate.name
else
-- Add the unit in the template to the DefenderCAPTemplate.
local DefenderUnitTemplate = DefenderTemplate.units[1]
DefenderCAPTemplate.units[DefenderUnitIndex] = DefenderUnitTemplate
end
DefenderUnitIndex = DefenderUnitIndex + 1
DefenderSquadron.Resources[TemplateID][GroupName] = nil
if DefenderUnitIndex > DefenderGrouping then
break
end
end
if DefenderCAPTemplate then
local TakeoffMethod = self:GetSquadronTakeoff( SquadronName )
local SpawnGroup = GROUP:Register( DefenderName )
DefenderCAPTemplate.lateActivation = nil
DefenderCAPTemplate.uncontrolled = nil
local Takeoff = self:GetSquadronTakeoff( SquadronName )
DefenderCAPTemplate.route.points[1].type = GROUPTEMPLATE.Takeoff[Takeoff][1] -- type
DefenderCAPTemplate.route.points[1].action = GROUPTEMPLATE.Takeoff[Takeoff][2] -- action
local Defender = _DATABASE:Spawn( DefenderCAPTemplate )
self:AddDefenderToSquadron( DefenderSquadron, Defender, DefenderGrouping )
return Defender, DefenderGrouping
end
else
local Spawn = DefenderSquadron.Spawn[ math.random( 1, #DefenderSquadron.Spawn ) ] -- Core.Spawn#SPAWN
if DefenderGrouping then
Spawn:InitGrouping( DefenderGrouping )
else
Spawn:InitGrouping()
end
local TakeoffMethod = self:GetSquadronTakeoff( SquadronName )
local Defender = Spawn:SpawnAtAirbase( DefenderSquadron.Airbase, TakeoffMethod, DefenderSquadron.TakeoffAltitude or self.DefenderDefault.TakeoffAltitude ) -- Wrapper.Group#GROUP
self:AddDefenderToSquadron( DefenderSquadron, Defender, DefenderGrouping )
return Defender, DefenderGrouping
end
return nil, nil
end
--- ---
-- @param #AI_A2A_DISPATCHER self -- @param #AI_A2A_DISPATCHER self
@ -2663,15 +2822,9 @@ do -- AI_A2A_DISPATCHER
local Cap = DefenderSquadron.Cap local Cap = DefenderSquadron.Cap
if Cap then if Cap then
local Spawn = DefenderSquadron.Spawn[ math.random( 1, #DefenderSquadron.Spawn ) ] -- Core.Spawn#SPAWN
local DefenderGrouping = DefenderSquadron.Grouping or self.DefenderDefault.Grouping
Spawn:InitGrouping( DefenderGrouping )
local TakeoffMethod = self:GetSquadronTakeoff( SquadronName ) local DefenderCAP, DefenderGrouping = self:ResourceActivate( DefenderSquadron )
local DefenderCAP = Spawn:SpawnAtAirbase( DefenderSquadron.Airbase, TakeoffMethod, DefenderSquadron.TakeoffAltitude or self.DefenderDefault.TakeoffAltitude )
self:AddDefenderToSquadron( DefenderSquadron, DefenderCAP, DefenderGrouping )
if DefenderCAP then if DefenderCAP then
local Fsm = AI_A2A_CAP:New( DefenderCAP, Cap.Zone, Cap.FloorAltitude, Cap.CeilingAltitude, Cap.PatrolMinSpeed, Cap.PatrolMaxSpeed, Cap.EngageMinSpeed, Cap.EngageMaxSpeed, Cap.AltType ) local Fsm = AI_A2A_CAP:New( DefenderCAP, Cap.Zone, Cap.FloorAltitude, Cap.CeilingAltitude, Cap.PatrolMinSpeed, Cap.PatrolMaxSpeed, Cap.EngageMinSpeed, Cap.EngageMaxSpeed, Cap.AltType )
@ -2686,7 +2839,7 @@ do -- AI_A2A_DISPATCHER
self:SetDefenderTask( SquadronName, DefenderCAP, "CAP", Fsm ) self:SetDefenderTask( SquadronName, DefenderCAP, "CAP", Fsm )
function Fsm:onafterTakeoff( Defender, From, Event, To ) function Fsm:onafterTakeoff( Defender, From, Event, To )
self:F({"GCI Birth", Defender:GetName()}) self:F({"CAP Birth", Defender:GetName()})
--self:GetParent(self).onafterBirth( self, Defender, From, Event, To ) --self:GetParent(self).onafterBirth( self, Defender, From, Event, To )
local Dispatcher = Fsm:GetDispatcher() -- #AI_A2A_DISPATCHER local Dispatcher = Fsm:GetDispatcher() -- #AI_A2A_DISPATCHER
@ -2720,9 +2873,9 @@ do -- AI_A2A_DISPATCHER
if Dispatcher:GetSquadronLanding( Squadron.Name ) == AI_A2A_DISPATCHER.Landing.NearAirbase then if Dispatcher:GetSquadronLanding( Squadron.Name ) == AI_A2A_DISPATCHER.Landing.NearAirbase then
Dispatcher:RemoveDefenderFromSquadron( Squadron, Defender ) Dispatcher:RemoveDefenderFromSquadron( Squadron, Defender )
Defender:Destroy() Defender:Destroy()
self:ParkDefender( Squadron, Defender )
end end
end end
end end
end end
end end
@ -2828,31 +2981,19 @@ do -- AI_A2A_DISPATCHER
self:F( { Grouping = DefenderGrouping, SquadronGrouping = DefenderSquadron.Grouping, DefaultGrouping = self.DefenderDefault.Grouping } ) self:F( { Grouping = DefenderGrouping, SquadronGrouping = DefenderSquadron.Grouping, DefaultGrouping = self.DefenderDefault.Grouping } )
self:F( { DefendersCount = DefenderCount, DefendersNeeded = DefendersNeeded } ) self:F( { DefendersCount = DefenderCount, DefendersNeeded = DefendersNeeded } )
-- DefenderSquadron.Resources can have the value nil, which expresses unlimited resources. -- DefenderSquadron.ResourceCount can have the value nil, which expresses unlimited resources.
-- DefendersNeeded cannot exceed DefenderSquadron.Resources! -- DefendersNeeded cannot exceed DefenderSquadron.ResourceCount!
if DefenderSquadron.Resources and DefendersNeeded > DefenderSquadron.Resources then if DefenderSquadron.ResourceCount and DefendersNeeded > DefenderSquadron.ResourceCount then
DefendersNeeded = DefenderSquadron.Resources DefendersNeeded = DefenderSquadron.ResourceCount
BreakLoop = true BreakLoop = true
end end
while ( DefendersNeeded > 0 ) do while ( DefendersNeeded > 0 ) do
local Spawn = DefenderSquadron.Spawn[ math.random( 1, #DefenderSquadron.Spawn ) ] -- Core.Spawn#SPAWN local DefenderGCI, DefenderGrouping = self:ResourceActivate( DefenderSquadron, DefendersNeeded )
local DefenderGrouping = ( DefenderGrouping < DefendersNeeded ) and DefenderGrouping or DefendersNeeded
if DefenderGrouping then
Spawn:InitGrouping( DefenderGrouping )
else
Spawn:InitGrouping()
end
local TakeoffMethod = self:GetSquadronTakeoff( ClosestDefenderSquadronName )
local DefenderGCI = Spawn:SpawnAtAirbase( DefenderSquadron.Airbase, TakeoffMethod, DefenderSquadron.TakeoffAltitude or self.DefenderDefault.TakeoffAltitude ) -- Wrapper.Group#GROUP
self:F( { GCIDefender = DefenderGCI:GetName() } )
DefendersNeeded = DefendersNeeded - DefenderGrouping DefendersNeeded = DefendersNeeded - DefenderGrouping
self:AddDefenderToSquadron( DefenderSquadron, DefenderGCI, DefenderGrouping )
if DefenderGCI then if DefenderGCI then
DefenderCount = DefenderCount - DefenderGrouping / DefenderOverhead DefenderCount = DefenderCount - DefenderGrouping / DefenderOverhead
@ -2919,6 +3060,7 @@ do -- AI_A2A_DISPATCHER
if Dispatcher:GetSquadronLanding( Squadron.Name ) == AI_A2A_DISPATCHER.Landing.NearAirbase then if Dispatcher:GetSquadronLanding( Squadron.Name ) == AI_A2A_DISPATCHER.Landing.NearAirbase then
Dispatcher:RemoveDefenderFromSquadron( Squadron, Defender ) Dispatcher:RemoveDefenderFromSquadron( Squadron, Defender )
Defender:Destroy() Defender:Destroy()
self:ParkDefender( Squadron, Defender )
end end
end end
end -- if DefenderGCI then end -- if DefenderGCI then
@ -3500,7 +3642,7 @@ do
-- For airplanes, 6000 (6km) is recommended, and is also the default value of this parameter. -- For airplanes, 6000 (6km) is recommended, and is also the default value of this parameter.
-- @param #number EngageRadius The radius in meters wherein detected airplanes will be engaged by airborne defenders without a task. -- @param #number EngageRadius The radius in meters wherein detected airplanes will be engaged by airborne defenders without a task.
-- @param #number GciRadius The radius in meters wherein detected airplanes will GCI. -- @param #number GciRadius The radius in meters wherein detected airplanes will GCI.
-- @param #number Resources The amount of resources that will be allocated to each squadron. -- @param #number ResourceCount The amount of resources that will be allocated to each squadron.
-- @return #AI_A2A_GCICAP -- @return #AI_A2A_GCICAP
-- @usage -- @usage
-- --
@ -3575,7 +3717,7 @@ do
-- --
-- A2ADispatcher = AI_A2A_GCICAP:New( { "DF CCCP" }, { "SQ CCCP" }, nil, nil, nil, nil, nil, 30 ) -- A2ADispatcher = AI_A2A_GCICAP:New( { "DF CCCP" }, { "SQ CCCP" }, nil, nil, nil, nil, nil, 30 )
-- --
function AI_A2A_GCICAP:New( EWRPrefixes, TemplatePrefixes, CapPrefixes, CapLimit, GroupingRadius, EngageRadius, GciRadius, Resources ) function AI_A2A_GCICAP:New( EWRPrefixes, TemplatePrefixes, CapPrefixes, CapLimit, GroupingRadius, EngageRadius, GciRadius, ResourceCount )
local EWRSetGroup = SET_GROUP:New() local EWRSetGroup = SET_GROUP:New()
EWRSetGroup:FilterPrefixes( EWRPrefixes ) EWRSetGroup:FilterPrefixes( EWRPrefixes )
@ -3629,7 +3771,7 @@ do
end end
end end
if Templates then if Templates then
self:SetSquadron( AirbaseName, AirbaseName, Templates, Resources ) self:SetSquadron( AirbaseName, AirbaseName, Templates, ResourceCount )
end end
end end
@ -3706,7 +3848,7 @@ do
-- For airplanes, 6000 (6km) is recommended, and is also the default value of this parameter. -- For airplanes, 6000 (6km) is recommended, and is also the default value of this parameter.
-- @param #number EngageRadius The radius in meters wherein detected airplanes will be engaged by airborne defenders without a task. -- @param #number EngageRadius The radius in meters wherein detected airplanes will be engaged by airborne defenders without a task.
-- @param #number GciRadius The radius in meters wherein detected airplanes will GCI. -- @param #number GciRadius The radius in meters wherein detected airplanes will GCI.
-- @param #number Resources The amount of resources that will be allocated to each squadron. -- @param #number ResourceCount The amount of resources that will be allocated to each squadron.
-- @return #AI_A2A_GCICAP -- @return #AI_A2A_GCICAP
-- @usage -- @usage
-- --
@ -3790,9 +3932,9 @@ do
-- --
-- A2ADispatcher = AI_A2A_GCICAP:NewWithBorder( { "DF CCCP" }, { "SQ CCCP" }, "Border", nil, nil, nil, nil, nil, 30 ) -- A2ADispatcher = AI_A2A_GCICAP:NewWithBorder( { "DF CCCP" }, { "SQ CCCP" }, "Border", nil, nil, nil, nil, nil, 30 )
-- --
function AI_A2A_GCICAP:NewWithBorder( EWRPrefixes, TemplatePrefixes, BorderPrefix, CapPrefixes, CapLimit, GroupingRadius, EngageRadius, GciRadius, Resources ) function AI_A2A_GCICAP:NewWithBorder( EWRPrefixes, TemplatePrefixes, BorderPrefix, CapPrefixes, CapLimit, GroupingRadius, EngageRadius, GciRadius, ResourceCount )
local self = AI_A2A_GCICAP:New( EWRPrefixes, TemplatePrefixes, CapPrefixes, CapLimit, GroupingRadius, EngageRadius, GciRadius, Resources ) local self = AI_A2A_GCICAP:New( EWRPrefixes, TemplatePrefixes, CapPrefixes, CapLimit, GroupingRadius, EngageRadius, GciRadius, ResourceCount )
if BorderPrefix then if BorderPrefix then
self:SetBorderZone( ZONE_POLYGON:New( BorderPrefix, GROUP:FindByName( BorderPrefix ) ) ) self:SetBorderZone( ZONE_POLYGON:New( BorderPrefix, GROUP:FindByName( BorderPrefix ) ) )

View File

@ -0,0 +1,69 @@
--- **AI** -- Models the process of air to ground operations for airplanes and helicopters.
--
-- ===
--
-- ### Author: **FlightControl**
--
-- ===
--
-- @module AI.AI_A2G
-- @image AI_Air_To_Ground_Dispatching.JPG
--- @type AI_A2G
-- @extends AI.AI_Air#AI_AIR
--- The AI_A2G class implements the core functions to operate an AI @{Wrapper.Group} A2G tasking.
--
--
-- # 1) AI_A2G constructor
--
-- * @{#AI_A2G.New}(): Creates a new AI_A2G object.
--
-- # 2) AI_A2G is a Finite State Machine.
--
-- This section must be read as follows. Each of the rows indicate a state transition, triggered through an event, and with an ending state of the event was executed.
-- The first column is the **From** state, the second column the **Event**, and the third column the **To** state.
--
-- So, each of the rows have the following structure.
--
-- * **From** => **Event** => **To**
--
-- Important to know is that an event can only be executed if the **current state** is the **From** state.
-- This, when an **Event** that is being triggered has a **From** state that is equal to the **Current** state of the state machine, the event will be executed,
-- and the resulting state will be the **To** state.
--
-- These are the different possible state transitions of this state machine implementation:
--
-- * Idle => Start => Monitoring
--
-- ## 2.1) AI_A2G States.
--
-- * **Idle**: The process is idle.
--
-- ## 2.2) AI_A2G Events.
--
-- * **Start**: Start the transport process.
-- * **Stop**: Stop the transport process.
-- * **Monitor**: Monitor and take action.
--
-- @field #AI_A2G
AI_A2G = {
ClassName = "AI_A2G",
}
--- Creates a new AI_A2G process.
-- @param #AI_A2G self
-- @param Wrapper.Group#GROUP AIGroup The group object to receive the A2G Process.
-- @return #AI_A2G
function AI_A2G:New( AIGroup )
-- Inherits from BASE
local self = BASE:Inherit( self, AI_AIR:New( AIGroup ) ) -- #AI_A2G
self:SetFuelThreshold( .2, 60 )
self:SetDamageThreshold( 0.4 )
self:SetDisengageRadius( 70000 )
return self
end

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,440 @@
--- **AI** -- Models the process of air to ground engagement for airplanes and helicopters.
--
-- This is a class used in the @{AI_A2G_Dispatcher}.
--
-- ===
--
-- ### Author: **FlightControl**
--
-- ===
--
-- @module AI.AI_A2G_Engage
-- @image AI_Air_To_Ground_Engage.JPG
--- @type AI_A2G_ENGAGE
-- @extends AI.AI_A2A#AI_A2A
--- Implements the core functions to intercept intruders. Use the Engage trigger to intercept intruders.
--
-- ![Process](..\Presentations\AI_GCI\Dia3.JPG)
--
-- The AI_A2G_ENGAGE is assigned a @{Wrapper.Group} and this must be done before the AI_A2G_ENGAGE process can be started using the **Start** event.
--
-- ![Process](..\Presentations\AI_GCI\Dia4.JPG)
--
-- The AI will fly towards the random 3D point within the patrol zone, using a random speed within the given altitude and speed limits.
-- Upon arrival at the 3D point, a new random 3D point will be selected within the patrol zone using the given limits.
--
-- ![Process](..\Presentations\AI_GCI\Dia5.JPG)
--
-- This cycle will continue.
--
-- ![Process](..\Presentations\AI_GCI\Dia6.JPG)
--
-- During the patrol, the AI will detect enemy targets, which are reported through the **Detected** event.
--
-- ![Process](..\Presentations\AI_GCI\Dia9.JPG)
--
-- When enemies are detected, the AI will automatically engage the enemy.
--
-- ![Process](..\Presentations\AI_GCI\Dia10.JPG)
--
-- Until a fuel or damage treshold has been reached by the AI, or when the AI is commanded to RTB.
-- When the fuel treshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
--
-- ![Process](..\Presentations\AI_GCI\Dia13.JPG)
--
-- ## 1. AI_A2G_ENGAGE constructor
--
-- * @{#AI_A2G_ENGAGE.New}(): Creates a new AI_A2G_ENGAGE object.
--
-- ## 3. Set the Range of Engagement
--
-- ![Range](..\Presentations\AI_GCI\Dia11.JPG)
--
-- An optional range can be set in meters,
-- that will define when the AI will engage with the detected airborne enemy targets.
-- The range can be beyond or smaller than the range of the Patrol Zone.
-- The range is applied at the position of the AI.
-- Use the method @{AI.AI_GCI#AI_A2G_ENGAGE.SetEngageRange}() to define that range.
--
-- ## 4. Set the Zone of Engagement
--
-- ![Zone](..\Presentations\AI_GCI\Dia12.JPG)
--
-- An optional @{Zone} can be set,
-- that will define when the AI will engage with the detected airborne enemy targets.
-- Use the method @{AI.AI_Cap#AI_A2G_ENGAGE.SetEngageZone}() to define that Zone.
--
-- ===
--
-- @field #AI_A2G_ENGAGE
AI_A2G_ENGAGE = {
ClassName = "AI_A2G_ENGAGE",
}
--- Creates a new AI_A2G_ENGAGE object
-- @param #AI_A2G_ENGAGE self
-- @param Wrapper.Group#GROUP AIGroup
-- @return #AI_A2G_ENGAGE
function AI_A2G_ENGAGE:New( AIGroup, EngageMinSpeed, EngageMaxSpeed )
-- Inherits from BASE
local self = BASE:Inherit( self, AI_A2G:New( AIGroup ) ) -- #AI_A2G_ENGAGE
self.Accomplished = false
self.Engaging = false
self.EngageMinSpeed = EngageMinSpeed
self.EngageMaxSpeed = EngageMaxSpeed
self.PatrolMinSpeed = EngageMinSpeed
self.PatrolMaxSpeed = EngageMaxSpeed
self.PatrolAltType = "RADIO"
self:AddTransition( { "Started", "Engaging", "Returning", "Airborne" }, "Engage", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_A2G_ENGAGE.
--- OnBefore Transition Handler for Event Engage.
-- @function [parent=#AI_A2G_ENGAGE] OnBeforeEngage
-- @param #AI_A2G_ENGAGE self
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnAfter Transition Handler for Event Engage.
-- @function [parent=#AI_A2G_ENGAGE] OnAfterEngage
-- @param #AI_A2G_ENGAGE self
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
--- Synchronous Event Trigger for Event Engage.
-- @function [parent=#AI_A2G_ENGAGE] Engage
-- @param #AI_A2G_ENGAGE self
--- Asynchronous Event Trigger for Event Engage.
-- @function [parent=#AI_A2G_ENGAGE] __Engage
-- @param #AI_A2G_ENGAGE self
-- @param #number Delay The delay in seconds.
--- OnLeave Transition Handler for State Engaging.
-- @function [parent=#AI_A2G_ENGAGE] OnLeaveEngaging
-- @param #AI_A2G_ENGAGE self
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnEnter Transition Handler for State Engaging.
-- @function [parent=#AI_A2G_ENGAGE] OnEnterEngaging
-- @param #AI_A2G_ENGAGE self
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
self:AddTransition( "Engaging", "Fired", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_A2G_ENGAGE.
--- OnBefore Transition Handler for Event Fired.
-- @function [parent=#AI_A2G_ENGAGE] OnBeforeFired
-- @param #AI_A2G_ENGAGE self
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnAfter Transition Handler for Event Fired.
-- @function [parent=#AI_A2G_ENGAGE] OnAfterFired
-- @param #AI_A2G_ENGAGE self
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
--- Synchronous Event Trigger for Event Fired.
-- @function [parent=#AI_A2G_ENGAGE] Fired
-- @param #AI_A2G_ENGAGE self
--- Asynchronous Event Trigger for Event Fired.
-- @function [parent=#AI_A2G_ENGAGE] __Fired
-- @param #AI_A2G_ENGAGE self
-- @param #number Delay The delay in seconds.
self:AddTransition( "*", "Destroy", "*" ) -- FSM_CONTROLLABLE Transition for type #AI_A2G_ENGAGE.
--- OnBefore Transition Handler for Event Destroy.
-- @function [parent=#AI_A2G_ENGAGE] OnBeforeDestroy
-- @param #AI_A2G_ENGAGE self
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnAfter Transition Handler for Event Destroy.
-- @function [parent=#AI_A2G_ENGAGE] OnAfterDestroy
-- @param #AI_A2G_ENGAGE self
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
--- Synchronous Event Trigger for Event Destroy.
-- @function [parent=#AI_A2G_ENGAGE] Destroy
-- @param #AI_A2G_ENGAGE self
--- Asynchronous Event Trigger for Event Destroy.
-- @function [parent=#AI_A2G_ENGAGE] __Destroy
-- @param #AI_A2G_ENGAGE self
-- @param #number Delay The delay in seconds.
self:AddTransition( "Engaging", "Abort", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_A2G_ENGAGE.
--- OnBefore Transition Handler for Event Abort.
-- @function [parent=#AI_A2G_ENGAGE] OnBeforeAbort
-- @param #AI_A2G_ENGAGE self
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnAfter Transition Handler for Event Abort.
-- @function [parent=#AI_A2G_ENGAGE] OnAfterAbort
-- @param #AI_A2G_ENGAGE self
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
--- Synchronous Event Trigger for Event Abort.
-- @function [parent=#AI_A2G_ENGAGE] Abort
-- @param #AI_A2G_ENGAGE self
--- Asynchronous Event Trigger for Event Abort.
-- @function [parent=#AI_A2G_ENGAGE] __Abort
-- @param #AI_A2G_ENGAGE self
-- @param #number Delay The delay in seconds.
self:AddTransition( "Engaging", "Accomplish", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_A2G_ENGAGE.
--- OnBefore Transition Handler for Event Accomplish.
-- @function [parent=#AI_A2G_ENGAGE] OnBeforeAccomplish
-- @param #AI_A2G_ENGAGE self
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnAfter Transition Handler for Event Accomplish.
-- @function [parent=#AI_A2G_ENGAGE] OnAfterAccomplish
-- @param #AI_A2G_ENGAGE self
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
--- Synchronous Event Trigger for Event Accomplish.
-- @function [parent=#AI_A2G_ENGAGE] Accomplish
-- @param #AI_A2G_ENGAGE self
--- Asynchronous Event Trigger for Event Accomplish.
-- @function [parent=#AI_A2G_ENGAGE] __Accomplish
-- @param #AI_A2G_ENGAGE self
-- @param #number Delay The delay in seconds.
return self
end
--- onafter event handler for Start event.
-- @param #AI_A2G_ENGAGE self
-- @param Wrapper.Group#GROUP AIGroup The AI group managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
function AI_A2G_ENGAGE:onafterStart( AIGroup, From, Event, To )
self:GetParent( self ).onafterStart( self, AIGroup, From, Event, To )
AIGroup:HandleEvent( EVENTS.Takeoff, nil, self )
end
--- onafter event handler for Engage event.
-- @param #AI_A2G_ENGAGE self
-- @param Wrapper.Group#GROUP AIGroup The AI Group managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
function AI_A2G_ENGAGE:onafterEngage( AIGroup, From, Event, To )
self:HandleEvent( EVENTS.Dead )
end
-- todo: need to fix this global function
--- @param Wrapper.Group#GROUP AIControllable
function AI_A2G_ENGAGE.EngageRoute( AIGroup, Fsm )
AIGroup:F( { "AI_A2G_ENGAGE.EngageRoute:", AIGroup:GetName() } )
if AIGroup:IsAlive() then
Fsm:__Engage( 0.5 )
--local Task = AIGroup:TaskOrbitCircle( 4000, 400 )
--AIGroup:SetTask( Task )
end
end
--- onbefore event handler for Engage event.
-- @param #AI_A2G_ENGAGE self
-- @param Wrapper.Group#GROUP AIGroup The group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
function AI_A2G_ENGAGE:onbeforeEngage( AIGroup, From, Event, To )
if self.Accomplished == true then
return false
end
end
--- onafter event handler for Abort event.
-- @param #AI_A2G_ENGAGE self
-- @param Wrapper.Group#GROUP AIGroup The AI Group managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
function AI_A2G_ENGAGE:onafterAbort( AIGroup, From, Event, To )
AIGroup:ClearTasks()
self:Return()
self:__RTB( 0.5 )
end
--- @param #AI_A2G_ENGAGE self
-- @param Wrapper.Group#GROUP AIGroup The GroupGroup managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
function AI_A2G_ENGAGE: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 and FirstAttackUnit:IsAlive() then
if AIGroup:IsAlive() then
local EngageRoute = {}
local CurrentCoord = AIGroup:GetCoordinate()
--- Calculate the target route point.
local CurrentCoord = AIGroup:GetCoordinate()
local ToTargetCoord = self.AttackSetUnit:GetFirst():GetCoordinate()
self:SetTargetDistance( ToTargetCoord ) -- For RTB status check
local ToTargetSpeed = math.random( self.EngageMinSpeed, self.EngageMaxSpeed )
local ToEngageAngle = CurrentCoord:GetAngleDegrees( CurrentCoord:GetDirectionVec3( ToTargetCoord ) )
--- Create a route point of type air.
local ToPatrolRoutePoint = CurrentCoord:Translate( 15000, ToEngageAngle ):WaypointAir(
self.PatrolAltType,
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
ToTargetSpeed,
true
)
self:F( { Angle = ToEngageAngle, ToTargetSpeed = ToTargetSpeed } )
self:F( { self.EngageMinSpeed, self.EngageMaxSpeed, ToTargetSpeed } )
EngageRoute[#EngageRoute+1] = ToPatrolRoutePoint
EngageRoute[#EngageRoute+1] = ToPatrolRoutePoint
local AttackTasks = {}
for AttackUnitID, AttackUnit in pairs( self.AttackSetUnit:GetSet() ) do
local AttackUnit = AttackUnit -- Wrapper.Unit#UNIT
if AttackUnit:IsAlive() and AttackUnit:IsGround() then
self:T( { "Eliminating Unit:", AttackUnit:GetName(), AttackUnit:IsAlive(), AttackUnit:IsGround() } )
AttackTasks[#AttackTasks+1] = AIGroup:TaskAttackUnit( AttackUnit )
end
end
if #AttackTasks == 0 then
self:E("No targets found -> Going RTB")
self:Return()
self:__RTB( 0.5 )
else
AIGroup:OptionROEOpenFire()
AIGroup:OptionROTEvadeFire()
AttackTasks[#AttackTasks+1] = AIGroup:TaskFunction( "AI_A2G_ENGAGE.EngageRoute", self )
EngageRoute[#EngageRoute].task = AIGroup:TaskCombo( AttackTasks )
end
AIGroup:Route( EngageRoute, 0.5 )
end
else
self:E("No targets found -> Going RTB")
self:Return()
self:__RTB( 0.5 )
end
end
--- @param #AI_A2G_ENGAGE self
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
function AI_A2G_ENGAGE:onafterAccomplish( AIGroup, From, Event, To )
self.Accomplished = true
self:SetDetectionOff()
end
--- @param #AI_A2G_ENGAGE self
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @param Core.Event#EVENTDATA EventData
function AI_A2G_ENGAGE:onafterDestroy( AIGroup, From, Event, To, EventData )
if EventData.IniUnit then
self.AttackUnits[EventData.IniUnit] = nil
end
end
--- @param #AI_A2G_ENGAGE self
-- @param Core.Event#EVENTDATA EventData
function AI_A2G_ENGAGE:OnEventDead( EventData )
self:F( { "EventDead", EventData } )
if EventData.IniDCSUnit then
if self.AttackUnits and self.AttackUnits[EventData.IniUnit] then
self:__Destroy( 1, EventData )
end
end
end

View File

@ -0,0 +1,488 @@
--- **AI** -- Models the process of A2G patrolling and engaging ground targets for airplanes and helicopters.
--
-- ===
--
-- ### Author: **FlightControl**
--
-- ===
--
-- @module AI.AI_A2G_Patrol
-- @image AI_Air_To_Ground_Patrol.JPG
--- @type AI_A2G_PATROL
-- @extends AI.AI_A2A_Patrol#AI_A2A_PATROL
--- The AI_A2G_PATROL class implements the core functions to patrol a @{Zone} by an AI @{Wrapper.Group} or @{Wrapper.Group}
-- and automatically engage any airborne enemies that are within a certain range or within a certain zone.
--
-- ![Process](..\Presentations\AI_CAP\Dia3.JPG)
--
-- The AI_A2G_PATROL is assigned a @{Wrapper.Group} and this must be done before the AI_A2G_PATROL process can be started using the **Start** event.
--
-- ![Process](..\Presentations\AI_CAP\Dia4.JPG)
--
-- The AI will fly towards the random 3D point within the patrol zone, using a random speed within the given altitude and speed limits.
-- Upon arrival at the 3D point, a new random 3D point will be selected within the patrol zone using the given limits.
--
-- ![Process](..\Presentations\AI_CAP\Dia5.JPG)
--
-- This cycle will continue.
--
-- ![Process](..\Presentations\AI_CAP\Dia6.JPG)
--
-- During the patrol, the AI will detect enemy targets, which are reported through the **Detected** event.
--
-- ![Process](..\Presentations\AI_CAP\Dia9.JPG)
--
-- When enemies are detected, the AI will automatically engage the enemy.
--
-- ![Process](..\Presentations\AI_CAP\Dia10.JPG)
--
-- Until a fuel or damage treshold has been reached by the AI, or when the AI is commanded to RTB.
-- When the fuel treshold has been reached, the airplane will fly towards the nearest friendly airbase and will land.
--
-- ![Process](..\Presentations\AI_CAP\Dia13.JPG)
--
-- ## 1. AI_A2G_PATROL constructor
--
-- * @{#AI_A2G_PATROL.New}(): Creates a new AI_A2G_PATROL object.
--
-- ## 2. AI_A2G_PATROL is a FSM
--
-- ![Process](..\Presentations\AI_CAP\Dia2.JPG)
--
-- ### 2.1 AI_A2G_PATROL States
--
-- * **None** ( Group ): The process is not started yet.
-- * **Patrolling** ( Group ): The AI is patrolling the Patrol Zone.
-- * **Engaging** ( Group ): The AI is engaging the bogeys.
-- * **Returning** ( Group ): The AI is returning to Base..
--
-- ### 2.2 AI_A2G_PATROL Events
--
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Start}**: Start the process.
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Route}**: Route the AI to a new random 3D point within the Patrol Zone.
-- * **@{#AI_A2G_PATROL.Engage}**: Let the AI engage the bogeys.
-- * **@{#AI_A2G_PATROL.Abort}**: Aborts the engagement and return patrolling in the patrol zone.
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.RTB}**: Route the AI to the home base.
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detect}**: The AI is detecting targets.
-- * **@{AI.AI_Patrol#AI_PATROL_ZONE.Detected}**: The AI has detected new targets.
-- * **@{#AI_A2G_PATROL.Destroy}**: The AI has destroyed a bogey @{Wrapper.Unit}.
-- * **@{#AI_A2G_PATROL.Destroyed}**: The AI has destroyed all bogeys @{Wrapper.Unit}s assigned in the CAS task.
-- * **Status** ( Group ): The AI is checking status (fuel and damage). When the tresholds have been reached, the AI will RTB.
--
-- ## 3. Set the Range of Engagement
--
-- ![Range](..\Presentations\AI_CAP\Dia11.JPG)
--
-- An optional range can be set in meters,
-- that will define when the AI will engage with the detected airborne enemy targets.
-- The range can be beyond or smaller than the range of the Patrol Zone.
-- The range is applied at the position of the AI.
-- Use the method @{AI.AI_CAP#AI_A2G_PATROL.SetEngageRange}() to define that range.
--
-- ## 4. Set the Zone of Engagement
--
-- ![Zone](..\Presentations\AI_CAP\Dia12.JPG)
--
-- An optional @{Zone} can be set,
-- that will define when the AI will engage with the detected airborne enemy targets.
-- Use the method @{AI.AI_Cap#AI_A2G_PATROL.SetEngageZone}() to define that Zone.
--
-- ===
--
-- @field #AI_A2G_PATROL
AI_A2G_PATROL = {
ClassName = "AI_A2G_PATROL",
}
--- Creates a new AI_A2G_PATROL object
-- @param #AI_A2G_PATROL self
-- @param Wrapper.Group#GROUP AIPatrol
-- @param Core.Zone#ZONE_BASE PatrolZone The @{Zone} where the patrol needs to be executed.
-- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
-- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
-- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Group} in km/h.
-- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h.
-- @param DCS#Speed EngageMinSpeed The minimum speed of the @{Wrapper.Group} in km/h when engaging a target.
-- @param DCS#Speed EngageMaxSpeed The maximum speed of the @{Wrapper.Group} in km/h when engaging a target.
-- @param DCS#AltitudeType PatrolAltType The altitude type ("RADIO"=="AGL", "BARO"=="ASL"). Defaults to RADIO
-- @return #AI_A2G_PATROL
function AI_A2G_PATROL:New( AIPatrol, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, EngageMinSpeed, EngageMaxSpeed, PatrolAltType )
-- Inherits from BASE
local self = BASE:Inherit( self, AI_A2A_PATROL:New( AIPatrol, PatrolZone, PatrolFloorAltitude, PatrolCeilingAltitude, PatrolMinSpeed, PatrolMaxSpeed, PatrolAltType ) ) -- #AI_A2G_PATROL
self.Accomplished = false
self.Engaging = false
self.EngageMinSpeed = EngageMinSpeed
self.EngageMaxSpeed = EngageMaxSpeed
self:AddTransition( { "Patrolling", "Engaging", "Returning", "Airborne" }, "Engage", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_A2G_PATROL.
--- OnBefore Transition Handler for Event Engage.
-- @function [parent=#AI_A2G_PATROL] OnBeforeEngage
-- @param #AI_A2G_PATROL self
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnAfter Transition Handler for Event Engage.
-- @function [parent=#AI_A2G_PATROL] OnAfterEngage
-- @param #AI_A2G_PATROL self
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
--- Synchronous Event Trigger for Event Engage.
-- @function [parent=#AI_A2G_PATROL] Engage
-- @param #AI_A2G_PATROL self
--- Asynchronous Event Trigger for Event Engage.
-- @function [parent=#AI_A2G_PATROL] __Engage
-- @param #AI_A2G_PATROL self
-- @param #number Delay The delay in seconds.
--- OnLeave Transition Handler for State Engaging.
-- @function [parent=#AI_A2G_PATROL] OnLeaveEngaging
-- @param #AI_A2G_PATROL self
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnEnter Transition Handler for State Engaging.
-- @function [parent=#AI_A2G_PATROL] OnEnterEngaging
-- @param #AI_A2G_PATROL self
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
self:AddTransition( "Engaging", "Fired", "Engaging" ) -- FSM_CONTROLLABLE Transition for type #AI_A2G_PATROL.
--- OnBefore Transition Handler for Event Fired.
-- @function [parent=#AI_A2G_PATROL] OnBeforeFired
-- @param #AI_A2G_PATROL self
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnAfter Transition Handler for Event Fired.
-- @function [parent=#AI_A2G_PATROL] OnAfterFired
-- @param #AI_A2G_PATROL self
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
--- Synchronous Event Trigger for Event Fired.
-- @function [parent=#AI_A2G_PATROL] Fired
-- @param #AI_A2G_PATROL self
--- Asynchronous Event Trigger for Event Fired.
-- @function [parent=#AI_A2G_PATROL] __Fired
-- @param #AI_A2G_PATROL self
-- @param #number Delay The delay in seconds.
self:AddTransition( "*", "Destroy", "*" ) -- FSM_CONTROLLABLE Transition for type #AI_A2G_PATROL.
--- OnBefore Transition Handler for Event Destroy.
-- @function [parent=#AI_A2G_PATROL] OnBeforeDestroy
-- @param #AI_A2G_PATROL self
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnAfter Transition Handler for Event Destroy.
-- @function [parent=#AI_A2G_PATROL] OnAfterDestroy
-- @param #AI_A2G_PATROL self
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
--- Synchronous Event Trigger for Event Destroy.
-- @function [parent=#AI_A2G_PATROL] Destroy
-- @param #AI_A2G_PATROL self
--- Asynchronous Event Trigger for Event Destroy.
-- @function [parent=#AI_A2G_PATROL] __Destroy
-- @param #AI_A2G_PATROL self
-- @param #number Delay The delay in seconds.
self:AddTransition( "Engaging", "Abort", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_A2G_PATROL.
--- OnBefore Transition Handler for Event Abort.
-- @function [parent=#AI_A2G_PATROL] OnBeforeAbort
-- @param #AI_A2G_PATROL self
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnAfter Transition Handler for Event Abort.
-- @function [parent=#AI_A2G_PATROL] OnAfterAbort
-- @param #AI_A2G_PATROL self
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
--- Synchronous Event Trigger for Event Abort.
-- @function [parent=#AI_A2G_PATROL] Abort
-- @param #AI_A2G_PATROL self
--- Asynchronous Event Trigger for Event Abort.
-- @function [parent=#AI_A2G_PATROL] __Abort
-- @param #AI_A2G_PATROL self
-- @param #number Delay The delay in seconds.
self:AddTransition( "Engaging", "Accomplish", "Patrolling" ) -- FSM_CONTROLLABLE Transition for type #AI_A2G_PATROL.
--- OnBefore Transition Handler for Event Accomplish.
-- @function [parent=#AI_A2G_PATROL] OnBeforeAccomplish
-- @param #AI_A2G_PATROL self
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnAfter Transition Handler for Event Accomplish.
-- @function [parent=#AI_A2G_PATROL] OnAfterAccomplish
-- @param #AI_A2G_PATROL self
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
--- Synchronous Event Trigger for Event Accomplish.
-- @function [parent=#AI_A2G_PATROL] Accomplish
-- @param #AI_A2G_PATROL self
--- Asynchronous Event Trigger for Event Accomplish.
-- @function [parent=#AI_A2G_PATROL] __Accomplish
-- @param #AI_A2G_PATROL self
-- @param #number Delay The delay in seconds.
return self
end
--- onafter State Transition for Event Patrol.
-- @param #AI_A2G_PATROL self
-- @param Wrapper.Group#GROUP AIPatrol The AI Group managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
function AI_A2G_PATROL:onafterStart( AIPatrol, From, Event, To )
self:GetParent( self ).onafterStart( self, AIPatrol, From, Event, To )
AIPatrol:HandleEvent( EVENTS.Takeoff, nil, self )
end
--- Set the Engage Zone which defines where the AI will engage bogies.
-- @param #AI_A2G_PATROL self
-- @param Core.Zone#ZONE EngageZone The zone where the AI is performing CAP.
-- @return #AI_A2G_PATROL self
function AI_A2G_PATROL:SetEngageZone( EngageZone )
self:F2()
if EngageZone then
self.EngageZone = EngageZone
else
self.EngageZone = nil
end
end
--- Set the Engage Range when the AI will engage with airborne enemies.
-- @param #AI_A2G_PATROL self
-- @param #number EngageRange The Engage Range.
-- @return #AI_A2G_PATROL self
function AI_A2G_PATROL:SetEngageRange( EngageRange )
self:F2()
if EngageRange then
self.EngageRange = EngageRange
else
self.EngageRange = nil
end
end
--- onafter State Transition for Event Patrol.
-- @param #AI_A2G_PATROL self
-- @param Wrapper.Group#GROUP AIPatrol The AI Group managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
function AI_A2G_PATROL:onafterPatrol( AIPatrol, From, Event, To )
-- Call the parent Start event handler
self:GetParent(self).onafterPatrol( self, AIPatrol, From, Event, To )
self:HandleEvent( EVENTS.Dead )
end
-- todo: need to fix this global function
--- @param Wrapper.Group#GROUP AIPatrol
function AI_A2G_PATROL.AttackRoute( AIPatrol, Fsm )
AIPatrol:F( { "AI_A2G_PATROL.AttackRoute:", AIPatrol:GetName() } )
if AIPatrol:IsAlive() then
Fsm:__Engage( 0.5 )
end
end
--- @param #AI_A2G_PATROL self
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
function AI_A2G_PATROL:onbeforeEngage( AIPatrol, From, Event, To )
if self.Accomplished == true then
return false
end
end
--- @param #AI_A2G_PATROL self
-- @param Wrapper.Group#GROUP AIPatrol The AI Group managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
function AI_A2G_PATROL:onafterAbort( AIPatrol, From, Event, To )
AIPatrol:ClearTasks()
self:__Route( 0.5 )
end
--- @param #AI_A2G_PATROL self
-- @param Wrapper.Group#GROUP AIPatrol The AIPatrol Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
function AI_A2G_PATROL:onafterEngage( AIPatrol, From, Event, To, AttackSetUnit )
self:F( { AIPatrol, From, Event, To, AttackSetUnit} )
self.AttackSetUnit = AttackSetUnit or self.AttackSetUnit -- Core.Set#SET_UNIT
local FirstAttackUnit = self.AttackSetUnit:GetFirst() -- Wrapper.Unit#UNIT
if FirstAttackUnit and FirstAttackUnit:IsAlive() then -- If there is no attacker anymore, stop the engagement.
if AIPatrol:IsAlive() then
local EngageRoute = {}
--- Calculate the target route point.
local CurrentCoord = AIPatrol:GetCoordinate()
local ToTargetCoord = self.AttackSetUnit:GetFirst():GetCoordinate()
local ToTargetSpeed = math.random( self.EngageMinSpeed, self.EngageMaxSpeed )
local ToInterceptAngle = CurrentCoord:GetAngleDegrees( CurrentCoord:GetDirectionVec3( ToTargetCoord ) )
--- Create a route point of type air.
local ToPatrolRoutePoint = CurrentCoord:Translate( 5000, ToInterceptAngle ):WaypointAir(
self.PatrolAltType,
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
ToTargetSpeed,
true
)
self:F( { Angle = ToInterceptAngle, ToTargetSpeed = ToTargetSpeed } )
self:T2( { self.MinSpeed, self.MaxSpeed, ToTargetSpeed } )
EngageRoute[#EngageRoute+1] = ToPatrolRoutePoint
EngageRoute[#EngageRoute+1] = ToPatrolRoutePoint
local AttackTasks = {}
for AttackUnitID, AttackUnit in pairs( self.AttackSetUnit:GetSet() ) do
local AttackUnit = AttackUnit -- Wrapper.Unit#UNIT
self:T( { "Attacking Unit:", AttackUnit:GetName(), AttackUnit:IsAlive(), AttackUnit:IsAir() } )
if AttackUnit:IsAlive() and AttackUnit:IsGround() then
AttackTasks[#AttackTasks+1] = AIPatrol:TaskAttackUnit( AttackUnit )
end
end
if #AttackTasks == 0 then
self:E("No targets found -> Going back to Patrolling")
self:__Abort( 0.5 )
else
AIPatrol:OptionROEOpenFire()
AIPatrol:OptionROTEvadeFire()
AttackTasks[#AttackTasks+1] = AIPatrol:TaskFunction( "AI_A2G_PATROL.AttackRoute", self )
EngageRoute[#EngageRoute].task = AIPatrol:TaskCombo( AttackTasks )
end
AIPatrol:Route( EngageRoute, 0.5 )
end
else
self:E("No targets found -> Going back to Patrolling")
self:__Abort( 0.5 )
end
end
--- @param #AI_A2G_PATROL self
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
function AI_A2G_PATROL:onafterAccomplish( AIPatrol, From, Event, To )
self.Accomplished = true
self:SetDetectionOff()
end
--- @param #AI_A2G_PATROL self
-- @param Wrapper.Group#GROUP AIPatrol The Group Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @param Core.Event#EVENTDATA EventData
function AI_A2G_PATROL:onafterDestroy( AIPatrol, From, Event, To, EventData )
if EventData.IniUnit then
self.AttackUnits[EventData.IniUnit] = nil
end
end
--- @param #AI_A2G_PATROL self
-- @param Core.Event#EVENTDATA EventData
function AI_A2G_PATROL:OnEventDead( EventData )
self:F( { "EventDead", EventData } )
if EventData.IniDCSUnit then
if self.AttackUnits and self.AttackUnits[EventData.IniUnit] then
self:__Destroy( 1, EventData )
end
end
end
--- @param Wrapper.Group#GROUP AIPatrol
function AI_A2G_PATROL.Resume( AIPatrol, Fsm )
AIPatrol:I( { "AI_A2G_PATROL.Resume:", AIPatrol:GetName() } )
if AIPatrol:IsAlive() then
Fsm:__Reset( 1 )
Fsm:__Route( 5 )
end
end

View File

@ -0,0 +1,732 @@
--- **AI** -- Models the process of AI air operations.
--
-- ===
--
-- ### Author: **FlightControl**
--
-- ===
--
-- @module AI.AI_Air
-- @image AI_Air_Operations.JPG
--- @type AI_AIR
-- @extends Core.Fsm#FSM_CONTROLLABLE
--- The AI_AIR class implements the core functions to operate an AI @{Wrapper.Group}.
--
--
-- # 1) AI_AIR constructor
--
-- * @{#AI_AIR.New}(): Creates a new AI_AIR object.
--
-- # 2) AI_AIR is a Finite State Machine.
--
-- This section must be read as follows. Each of the rows indicate a state transition, triggered through an event, and with an ending state of the event was executed.
-- The first column is the **From** state, the second column the **Event**, and the third column the **To** state.
--
-- So, each of the rows have the following structure.
--
-- * **From** => **Event** => **To**
--
-- Important to know is that an event can only be executed if the **current state** is the **From** state.
-- This, when an **Event** that is being triggered has a **From** state that is equal to the **Current** state of the state machine, the event will be executed,
-- and the resulting state will be the **To** state.
--
-- These are the different possible state transitions of this state machine implementation:
--
-- * Idle => Start => Monitoring
--
-- ## 2.1) AI_AIR States.
--
-- * **Idle**: The process is idle.
--
-- ## 2.2) AI_AIR Events.
--
-- * **Start**: Start the transport process.
-- * **Stop**: Stop the transport process.
-- * **Monitor**: Monitor and take action.
--
-- @field #AI_AIR
AI_AIR = {
ClassName = "AI_AIR",
}
--- Creates a new AI_AIR process.
-- @param #AI_AIR self
-- @param Wrapper.Group#GROUP AIGroup The group object to receive the A2G Process.
-- @return #AI_AIR
function AI_AIR:New( AIGroup )
-- Inherits from BASE
local self = BASE:Inherit( self, FSM_CONTROLLABLE:New() ) -- #AI_AIR
self:SetControllable( AIGroup )
self:SetStartState( "Stopped" )
self:AddTransition( "*", "Start", "Started" )
--- Start Handler OnBefore for AI_AIR
-- @function [parent=#AI_AIR] OnBeforeStart
-- @param #AI_AIR self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #boolean
--- Start Handler OnAfter for AI_AIR
-- @function [parent=#AI_AIR] OnAfterStart
-- @param #AI_AIR self
-- @param #string From
-- @param #string Event
-- @param #string To
--- Start Trigger for AI_AIR
-- @function [parent=#AI_AIR] Start
-- @param #AI_AIR self
--- Start Asynchronous Trigger for AI_AIR
-- @function [parent=#AI_AIR] __Start
-- @param #AI_AIR self
-- @param #number Delay
self:AddTransition( "*", "Stop", "Stopped" )
--- OnLeave Transition Handler for State Stopped.
-- @function [parent=#AI_AIR] OnLeaveStopped
-- @param #AI_AIR self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnEnter Transition Handler for State Stopped.
-- @function [parent=#AI_AIR] OnEnterStopped
-- @param #AI_AIR self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
--- OnBefore Transition Handler for Event Stop.
-- @function [parent=#AI_AIR] OnBeforeStop
-- @param #AI_AIR self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnAfter Transition Handler for Event Stop.
-- @function [parent=#AI_AIR] OnAfterStop
-- @param #AI_AIR self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
--- Synchronous Event Trigger for Event Stop.
-- @function [parent=#AI_AIR] Stop
-- @param #AI_AIR self
--- Asynchronous Event Trigger for Event Stop.
-- @function [parent=#AI_AIR] __Stop
-- @param #AI_AIR self
-- @param #number Delay The delay in seconds.
self:AddTransition( "*", "Status", "*" ) -- FSM_CONTROLLABLE Transition for type #AI_AIR.
--- OnBefore Transition Handler for Event Status.
-- @function [parent=#AI_AIR] OnBeforeStatus
-- @param #AI_AIR self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnAfter Transition Handler for Event Status.
-- @function [parent=#AI_AIR] OnAfterStatus
-- @param #AI_AIR self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
--- Synchronous Event Trigger for Event Status.
-- @function [parent=#AI_AIR] Status
-- @param #AI_AIR self
--- Asynchronous Event Trigger for Event Status.
-- @function [parent=#AI_AIR] __Status
-- @param #AI_AIR self
-- @param #number Delay The delay in seconds.
self:AddTransition( "*", "RTB", "*" ) -- FSM_CONTROLLABLE Transition for type #AI_AIR.
--- OnBefore Transition Handler for Event RTB.
-- @function [parent=#AI_AIR] OnBeforeRTB
-- @param #AI_AIR self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnAfter Transition Handler for Event RTB.
-- @function [parent=#AI_AIR] OnAfterRTB
-- @param #AI_AIR self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
--- Synchronous Event Trigger for Event RTB.
-- @function [parent=#AI_AIR] RTB
-- @param #AI_AIR self
--- Asynchronous Event Trigger for Event RTB.
-- @function [parent=#AI_AIR] __RTB
-- @param #AI_AIR self
-- @param #number Delay The delay in seconds.
--- OnLeave Transition Handler for State Returning.
-- @function [parent=#AI_AIR] OnLeaveReturning
-- @param #AI_AIR self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
-- @return #boolean Return false to cancel Transition.
--- OnEnter Transition Handler for State Returning.
-- @function [parent=#AI_AIR] OnEnterReturning
-- @param #AI_AIR self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
self:AddTransition( "Patrolling", "Refuel", "Refuelling" )
--- Refuel Handler OnBefore for AI_AIR
-- @function [parent=#AI_AIR] OnBeforeRefuel
-- @param #AI_AIR self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From
-- @param #string Event
-- @param #string To
-- @return #boolean
--- Refuel Handler OnAfter for AI_AIR
-- @function [parent=#AI_AIR] OnAfterRefuel
-- @param #AI_AIR self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From
-- @param #string Event
-- @param #string To
--- Refuel Trigger for AI_AIR
-- @function [parent=#AI_AIR] Refuel
-- @param #AI_AIR self
--- Refuel Asynchronous Trigger for AI_AIR
-- @function [parent=#AI_AIR] __Refuel
-- @param #AI_AIR self
-- @param #number Delay
self:AddTransition( "*", "Takeoff", "Airborne" )
self:AddTransition( "*", "Return", "Returning" )
self:AddTransition( "*", "Hold", "Holding" )
self:AddTransition( "*", "Home", "Home" )
self:AddTransition( "*", "LostControl", "LostControl" )
self:AddTransition( "*", "Fuel", "Fuel" )
self:AddTransition( "*", "Damaged", "Damaged" )
self:AddTransition( "*", "Eject", "*" )
self:AddTransition( "*", "Crash", "Crashed" )
self:AddTransition( "*", "PilotDead", "*" )
self.IdleCount = 0
return self
end
--- @param Wrapper.Group#GROUP self
-- @param Core.Event#EVENTDATA EventData
function GROUP:OnEventTakeoff( EventData, Fsm )
Fsm:Takeoff()
self:UnHandleEvent( EVENTS.Takeoff )
end
function AI_AIR:SetDispatcher( Dispatcher )
self.Dispatcher = Dispatcher
end
function AI_AIR:GetDispatcher()
return self.Dispatcher
end
function AI_AIR:SetTargetDistance( Coordinate )
local CurrentCoord = self.Controllable:GetCoordinate()
self.TargetDistance = CurrentCoord:Get2DDistance( Coordinate )
self.ClosestTargetDistance = ( not self.ClosestTargetDistance or self.ClosestTargetDistance > self.TargetDistance ) and self.TargetDistance or self.ClosestTargetDistance
end
function AI_AIR:ClearTargetDistance()
self.TargetDistance = nil
self.ClosestTargetDistance = nil
end
--- Sets (modifies) the minimum and maximum speed of the patrol.
-- @param #AI_AIR self
-- @param DCS#Speed PatrolMinSpeed The minimum speed of the @{Wrapper.Controllable} in km/h.
-- @param DCS#Speed PatrolMaxSpeed The maximum speed of the @{Wrapper.Controllable} in km/h.
-- @return #AI_AIR self
function AI_AIR:SetSpeed( PatrolMinSpeed, PatrolMaxSpeed )
self:F2( { PatrolMinSpeed, PatrolMaxSpeed } )
self.PatrolMinSpeed = PatrolMinSpeed
self.PatrolMaxSpeed = PatrolMaxSpeed
end
--- Sets the floor and ceiling altitude of the patrol.
-- @param #AI_AIR self
-- @param DCS#Altitude PatrolFloorAltitude The lowest altitude in meters where to execute the patrol.
-- @param DCS#Altitude PatrolCeilingAltitude The highest altitude in meters where to execute the patrol.
-- @return #AI_AIR self
function AI_AIR:SetAltitude( PatrolFloorAltitude, PatrolCeilingAltitude )
self:F2( { PatrolFloorAltitude, PatrolCeilingAltitude } )
self.PatrolFloorAltitude = PatrolFloorAltitude
self.PatrolCeilingAltitude = PatrolCeilingAltitude
end
--- Sets the home airbase.
-- @param #AI_AIR self
-- @param Wrapper.Airbase#AIRBASE HomeAirbase
-- @return #AI_AIR self
function AI_AIR:SetHomeAirbase( HomeAirbase )
self:F2( { HomeAirbase } )
self.HomeAirbase = HomeAirbase
end
--- Sets to refuel at the given tanker.
-- @param #AI_AIR self
-- @param Wrapper.Group#GROUP TankerName The group name of the tanker as defined within the Mission Editor or spawned.
-- @return #AI_AIR self
function AI_AIR:SetTanker( TankerName )
self:F2( { TankerName } )
self.TankerName = TankerName
end
--- Sets the disengage range, that when engaging a target beyond the specified range, the engagement will be cancelled and the plane will RTB.
-- @param #AI_AIR self
-- @param #number DisengageRadius The disengage range.
-- @return #AI_AIR self
function AI_AIR:SetDisengageRadius( DisengageRadius )
self:F2( { DisengageRadius } )
self.DisengageRadius = DisengageRadius
end
--- Set the status checking off.
-- @param #AI_AIR self
-- @return #AI_AIR self
function AI_AIR:SetStatusOff()
self:F2()
self.CheckStatus = false
end
--- When the AI is out of fuel, it is required that a new AI is started, before the old AI can return to the home base.
-- Therefore, with a parameter and a calculation of the distance to the home base, the fuel treshold is calculated.
-- When the fuel treshold is reached, the AI will continue for a given time its patrol task in orbit, while a new AIControllable is targetted to the AI_AIR.
-- Once the time is finished, the old AI will return to the base.
-- @param #AI_AIR self
-- @param #number FuelThresholdPercentage The treshold in percentage (between 0 and 1) when the AIControllable is considered to get out of fuel.
-- @param #number OutOfFuelOrbitTime The amount of seconds the out of fuel AIControllable will orbit before returning to the base.
-- @return #AI_AIR self
function AI_AIR:SetFuelThreshold( FuelThresholdPercentage, OutOfFuelOrbitTime )
self.FuelThresholdPercentage = FuelThresholdPercentage
self.OutOfFuelOrbitTime = OutOfFuelOrbitTime
self.Controllable:OptionRTBBingoFuel( false )
return self
end
--- When the AI is damaged beyond a certain treshold, it is required that the AI returns to the home base.
-- However, damage cannot be foreseen early on.
-- Therefore, when the damage treshold is reached,
-- the AI will return immediately to the home base (RTB).
-- Note that for groups, the average damage of the complete group will be calculated.
-- So, in a group of 4 airplanes, 2 lost and 2 with damage 0.2, the damage treshold will be 0.25.
-- @param #AI_AIR self
-- @param #number PatrolDamageThreshold The treshold in percentage (between 0 and 1) when the AI is considered to be damaged.
-- @return #AI_AIR self
function AI_AIR:SetDamageThreshold( PatrolDamageThreshold )
self.PatrolManageDamage = true
self.PatrolDamageThreshold = PatrolDamageThreshold
return self
end
--- Defines a new patrol route using the @{Process_PatrolZone} parameters and settings.
-- @param #AI_AIR self
-- @return #AI_AIR self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable The Controllable Object managed by the FSM.
-- @param #string From The From State string.
-- @param #string Event The Event string.
-- @param #string To The To State string.
function AI_AIR:onafterStart( Controllable, From, Event, To )
self:__Status( 10 ) -- Check status status every 30 seconds.
self:HandleEvent( EVENTS.PilotDead, self.OnPilotDead )
self:HandleEvent( EVENTS.Crash, self.OnCrash )
self:HandleEvent( EVENTS.Ejection, self.OnEjection )
Controllable:OptionROEHoldFire()
Controllable:OptionROTVertical()
end
--- @param #AI_AIR self
function AI_AIR:onbeforeStatus()
return self.CheckStatus
end
--- @param #AI_AIR self
function AI_AIR:onafterStatus()
if self.Controllable and self.Controllable:IsAlive() then
local RTB = false
local DistanceFromHomeBase = self.HomeAirbase:GetCoordinate():Get2DDistance( self.Controllable:GetCoordinate() )
if not self:Is( "Holding" ) and not self:Is( "Returning" ) then
local DistanceFromHomeBase = self.HomeAirbase:GetCoordinate():Get2DDistance( self.Controllable:GetCoordinate() )
self:F({DistanceFromHomeBase=DistanceFromHomeBase})
if DistanceFromHomeBase > self.DisengageRadius then
self:E( self.Controllable:GetName() .. " is too far from home base, RTB!" )
self:Hold( 300 )
RTB = false
end
end
-- I think this code is not requirement anymore after release 2.5.
-- if self:Is( "Fuel" ) or self:Is( "Damaged" ) or self:Is( "LostControl" ) then
-- if DistanceFromHomeBase < 5000 then
-- self:E( self.Controllable:GetName() .. " is near the home base, RTB!" )
-- self:Home( "Destroy" )
-- end
-- end
if not self:Is( "Fuel" ) and not self:Is( "Home" ) then
local Fuel = self.Controllable:GetFuelMin()
self:F({Fuel=Fuel, FuelThresholdPercentage=self.FuelThresholdPercentage})
if Fuel < self.FuelThresholdPercentage then
if self.TankerName then
self:E( self.Controllable:GetName() .. " is out of fuel: " .. Fuel .. " ... Refuelling at Tanker!" )
self:Refuel()
else
self:E( self.Controllable:GetName() .. " is out of fuel: " .. Fuel .. " ... RTB!" )
local OldAIControllable = self.Controllable
local OrbitTask = OldAIControllable:TaskOrbitCircle( math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ), self.PatrolMinSpeed )
local TimedOrbitTask = OldAIControllable:TaskControlled( OrbitTask, OldAIControllable:TaskCondition(nil,nil,nil,nil,self.OutOfFuelOrbitTime,nil ) )
OldAIControllable:SetTask( TimedOrbitTask, 10 )
self:Fuel()
RTB = true
end
else
end
end
-- TODO: Check GROUP damage function.
local Damage = self.Controllable:GetLife()
local InitialLife = self.Controllable:GetLife0()
self:F( { Damage = Damage, InitialLife = InitialLife, DamageThreshold = self.PatrolDamageThreshold } )
if ( Damage / InitialLife ) < self.PatrolDamageThreshold then
self:E( self.Controllable:GetName() .. " is damaged: " .. Damage .. " ... RTB!" )
self:Damaged()
RTB = true
self:SetStatusOff()
end
-- Check if planes went RTB and are out of control.
-- We only check if planes are out of control, when they are in duty.
if self.Controllable:HasTask() == false then
if not self:Is( "Started" ) and
not self:Is( "Stopped" ) and
not self:Is( "Fuel" ) and
not self:Is( "Damaged" ) and
not self:Is( "Home" ) then
if self.IdleCount >= 2 then
if Damage ~= InitialLife then
self:Damaged()
else
self:E( self.Controllable:GetName() .. " control lost! " )
self:LostControl()
end
else
self.IdleCount = self.IdleCount + 1
end
end
else
self.IdleCount = 0
end
if RTB == true then
self:__RTB( 0.5 )
end
if not self:Is("Home") then
self:__Status( 10 )
end
end
end
--- @param Wrapper.Group#GROUP AIGroup
function AI_AIR.RTBRoute( AIGroup, Fsm )
AIGroup:F( { "AI_AIR.RTBRoute:", AIGroup:GetName() } )
if AIGroup:IsAlive() then
Fsm:__RTB( 0.5 )
end
end
--- @param Wrapper.Group#GROUP AIGroup
function AI_AIR.RTBHold( AIGroup, Fsm )
AIGroup:F( { "AI_AIR.RTBHold:", AIGroup:GetName() } )
if AIGroup:IsAlive() then
Fsm:__RTB( 0.5 )
Fsm:Return()
local Task = AIGroup:TaskOrbitCircle( 4000, 400 )
AIGroup:SetTask( Task )
end
end
--- @param #AI_AIR self
-- @param Wrapper.Group#GROUP AIGroup
function AI_AIR:onafterRTB( AIGroup, From, Event, To )
self:F( { AIGroup, From, Event, To } )
if AIGroup and AIGroup:IsAlive() then
self:E( "Group " .. AIGroup:GetName() .. " ... RTB! ( " .. self:GetState() .. " )" )
self:ClearTargetDistance()
AIGroup:ClearTasks()
local EngageRoute = {}
--- Calculate the target route point.
local CurrentCoord = AIGroup:GetCoordinate()
local ToTargetCoord = self.HomeAirbase:GetCoordinate()
local ToTargetSpeed = math.random( self.PatrolMinSpeed, self.PatrolMaxSpeed )
local ToAirbaseAngle = CurrentCoord:GetAngleDegrees( CurrentCoord:GetDirectionVec3( ToTargetCoord ) )
local Distance = CurrentCoord:Get2DDistance( ToTargetCoord )
local ToAirbaseCoord = CurrentCoord:Translate( 5000, ToAirbaseAngle )
if Distance < 5000 then
self:E( "RTB and near the airbase!" )
self:Home()
return
end
--- Create a route point of type air.
local ToRTBRoutePoint = ToAirbaseCoord:WaypointAir(
self.PatrolAltType,
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
ToTargetSpeed,
true
)
self:F( { Angle = ToAirbaseAngle, ToTargetSpeed = ToTargetSpeed } )
self:T2( { self.MinSpeed, self.MaxSpeed, ToTargetSpeed } )
EngageRoute[#EngageRoute+1] = ToRTBRoutePoint
EngageRoute[#EngageRoute+1] = ToRTBRoutePoint
AIGroup:OptionROEHoldFire()
AIGroup:OptionROTEvadeFire()
--- Now we're going to do something special, we're going to call a function from a waypoint action at the AIControllable...
AIGroup:WayPointInitialize( EngageRoute )
local Tasks = {}
Tasks[#Tasks+1] = AIGroup:TaskFunction( "AI_AIR.RTBRoute", self )
EngageRoute[#EngageRoute].task = AIGroup:TaskCombo( Tasks )
--- NOW ROUTE THE GROUP!
AIGroup:Route( EngageRoute, 0.5 )
end
end
--- @param #AI_AIR self
-- @param Wrapper.Group#GROUP AIGroup
function AI_AIR:onafterHome( AIGroup, From, Event, To )
self:F( { AIGroup, From, Event, To } )
self:E( "Group " .. self.Controllable:GetName() .. " ... Home! ( " .. self:GetState() .. " )" )
if AIGroup and AIGroup:IsAlive() then
end
end
--- @param #AI_AIR self
-- @param Wrapper.Group#GROUP AIGroup
function AI_AIR:onafterHold( AIGroup, From, Event, To, HoldTime )
self:F( { AIGroup, From, Event, To } )
self:E( "Group " .. self.Controllable:GetName() .. " ... Holding! ( " .. self:GetState() .. " )" )
if AIGroup and AIGroup:IsAlive() then
local OrbitTask = AIGroup:TaskOrbitCircle( math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ), self.PatrolMinSpeed )
local TimedOrbitTask = AIGroup:TaskControlled( OrbitTask, AIGroup:TaskCondition( nil, nil, nil, nil, HoldTime , nil ) )
local RTBTask = AIGroup:TaskFunction( "AI_AIR.RTBHold", self )
local OrbitHoldTask = AIGroup:TaskOrbitCircle( 4000, self.PatrolMinSpeed )
--AIGroup:SetState( AIGroup, "AI_AIR", self )
AIGroup:SetTask( AIGroup:TaskCombo( { TimedOrbitTask, RTBTask, OrbitHoldTask } ), 1 )
end
end
--- @param Wrapper.Group#GROUP AIGroup
function AI_AIR.Resume( AIGroup, Fsm )
AIGroup:I( { "AI_AIR.Resume:", AIGroup:GetName() } )
if AIGroup:IsAlive() then
Fsm:__RTB( 0.5 )
end
end
--- @param #AI_AIR self
-- @param Wrapper.Group#GROUP AIGroup
function AI_AIR:onafterRefuel( AIGroup, From, Event, To )
self:F( { AIGroup, From, Event, To } )
self:E( "Group " .. self.Controllable:GetName() .. " ... Refuelling! ( " .. self:GetState() .. " )" )
if AIGroup and AIGroup:IsAlive() then
local Tanker = GROUP:FindByName( self.TankerName )
if Tanker:IsAlive() and Tanker:IsAirPlane() then
local RefuelRoute = {}
--- Calculate the target route point.
local CurrentCoord = AIGroup:GetCoordinate()
local ToRefuelCoord = Tanker:GetCoordinate()
local ToRefuelSpeed = math.random( self.PatrolMinSpeed, self.PatrolMaxSpeed )
--- Create a route point of type air.
local ToRefuelRoutePoint = ToRefuelCoord:WaypointAir(
self.PatrolAltType,
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
ToRefuelSpeed,
true
)
self:F( { ToRefuelSpeed = ToRefuelSpeed } )
RefuelRoute[#RefuelRoute+1] = ToRefuelRoutePoint
RefuelRoute[#RefuelRoute+1] = ToRefuelRoutePoint
AIGroup:OptionROEHoldFire()
AIGroup:OptionROTEvadeFire()
local Tasks = {}
Tasks[#Tasks+1] = AIGroup:TaskRefueling()
Tasks[#Tasks+1] = AIGroup:TaskFunction( self:GetClassName() .. ".Resume", self )
RefuelRoute[#RefuelRoute].task = AIGroup:TaskCombo( Tasks )
AIGroup:Route( RefuelRoute, 0.5 )
else
self:RTB()
end
end
end
--- @param #AI_AIR self
function AI_AIR:onafterDead()
self:SetStatusOff()
end
--- @param #AI_AIR self
-- @param Core.Event#EVENTDATA EventData
function AI_AIR:OnCrash( EventData )
if self.Controllable:IsAlive() and EventData.IniDCSGroupName == self.Controllable:GetName() then
self:E( self.Controllable:GetUnits() )
if #self.Controllable:GetUnits() == 1 then
self:__Crash( 1, EventData )
end
end
end
--- @param #AI_AIR self
-- @param Core.Event#EVENTDATA EventData
function AI_AIR:OnEjection( EventData )
if self.Controllable:IsAlive() and EventData.IniDCSGroupName == self.Controllable:GetName() then
self:__Eject( 1, EventData )
end
end
--- @param #AI_AIR self
-- @param Core.Event#EVENTDATA EventData
function AI_AIR:OnPilotDead( EventData )
if self.Controllable:IsAlive() and EventData.IniDCSGroupName == self.Controllable:GetName() then
self:__PilotDead( 1, EventData )
end
end

File diff suppressed because it is too large Load Diff

View File

@ -1702,7 +1702,7 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT
-- When spawned in the air, we need to generate a Takeoff Event. -- When spawned in the air, we need to generate a Takeoff Event.
if Takeoff == GROUP.Takeoff.Air then if Takeoff == GROUP.Takeoff.Air then
for UnitID, UnitSpawned in pairs( GroupSpawned:GetUnits() ) do for UnitID, UnitSpawned in pairs( GroupSpawned:GetUnits() ) do
SCHEDULER:New( nil, BASE.CreateEventTakeoff, { GroupSpawned, timer.getTime(), UnitSpawned:GetDCSObject() } , 1 ) SCHEDULER:New( nil, BASE.CreateEventTakeoff, { GroupSpawned, timer.getTime(), UnitSpawned:GetDCSObject() } , 5 )
end end
end end
@ -2005,10 +2005,10 @@ end
function SPAWN:InitUnControlled( UnControlled ) function SPAWN:InitUnControlled( UnControlled )
self:F2( { self.SpawnTemplatePrefix, UnControlled } ) self:F2( { self.SpawnTemplatePrefix, UnControlled } )
self.SpawnUnControlled = UnControlled self.SpawnUnControlled = UnControlled or true
for SpawnGroupID = 1, self.SpawnMaxGroups do for SpawnGroupID = 1, self.SpawnMaxGroups do
self.SpawnGroups[SpawnGroupID].UnControlled = UnControlled self.SpawnGroups[SpawnGroupID].UnControlled = self.SpawnUnControlled
end end
return self return self

View File

@ -618,6 +618,9 @@ function ZONE_RADIUS:GetVec3( Height )
end end
--- Scan the zone for the presence of units of the given ObjectCategories. --- Scan the zone for the presence of units of the given ObjectCategories.
-- Note that after a zone has been scanned, the zone can be evaluated by: -- Note that after a zone has been scanned, the zone can be evaluated by:
-- --
@ -629,11 +632,11 @@ end
-- @{#ZONE_RADIUS. -- @{#ZONE_RADIUS.
-- @param #ZONE_RADIUS self -- @param #ZONE_RADIUS self
-- @param ObjectCategories -- @param ObjectCategories
-- @param Coalition -- @param UnitCategories
-- @usage -- @usage
-- self.Zone:Scan() -- self.Zone:Scan()
-- local IsAttacked = self.Zone:IsSomeInZoneOfCoalition( self.Coalition ) -- local IsAttacked = self.Zone:IsSomeInZoneOfCoalition( self.Coalition )
function ZONE_RADIUS:Scan( ObjectCategories ) function ZONE_RADIUS:Scan( ObjectCategories, UnitCategories )
self.ScanData = {} self.ScanData = {}
self.ScanData.Coalitions = {} self.ScanData.Coalitions = {}
@ -660,9 +663,24 @@ function ZONE_RADIUS:Scan( ObjectCategories )
if ( ObjectCategory == Object.Category.UNIT and ZoneObject:isExist() and ZoneObject:isActive() ) or if ( ObjectCategory == Object.Category.UNIT and ZoneObject:isExist() and ZoneObject:isActive() ) or
(ObjectCategory == Object.Category.STATIC and ZoneObject:isExist()) then (ObjectCategory == Object.Category.STATIC and ZoneObject:isExist()) then
local CoalitionDCSUnit = ZoneObject:getCoalition() local CoalitionDCSUnit = ZoneObject:getCoalition()
self.ScanData.Coalitions[CoalitionDCSUnit] = true local Include = false
self.ScanData.Units[ZoneObject] = ZoneObject if not UnitCategories then
self:F2( { Name = ZoneObject:getName(), Coalition = CoalitionDCSUnit } ) Include = true
else
local CategoryDCSUnit = ZoneObject:getDesc().category
for UnitCategoryID, UnitCategory in pairs( UnitCategories ) do
if UnitCategory == CategoryDCSUnit then
Include = true
break
end
end
end
if Include then
local CoalitionDCSUnit = ZoneObject:getCoalition()
self.ScanData.Coalitions[CoalitionDCSUnit] = true
self.ScanData.Units[ZoneObject] = ZoneObject
self:F2( { Name = ZoneObject:getName(), Coalition = CoalitionDCSUnit } )
end
end end
if ObjectCategory == Object.Category.SCENERY then if ObjectCategory == Object.Category.SCENERY then
local SceneryType = ZoneObject:getTypeName() local SceneryType = ZoneObject:getTypeName()

View File

@ -286,9 +286,9 @@ do -- DESIGNATE
-- * The status report can be automatically flashed by selecting "Status" -> "Flash Status On". -- * The status report can be automatically flashed by selecting "Status" -> "Flash Status On".
-- * The automatic flashing of the status report can be deactivated by selecting "Status" -> "Flash Status Off". -- * The automatic flashing of the status report can be deactivated by selecting "Status" -> "Flash Status Off".
-- * The flashing of the status menu is disabled by default. -- * The flashing of the status menu is disabled by default.
-- * The method @{#DESIGNATE.FlashStatusMenu}() can be used to enable or disable to flashing of the status menu. -- * The method @{#DESIGNATE.SetFlashStatusMenu}() can be used to enable or disable to flashing of the status menu.
-- --
-- Designate:FlashStatusMenu( true ) -- Designate:SetFlashStatusMenu( true )
-- --
-- The example will activate the flashing of the status menu for this Designate object. -- The example will activate the flashing of the status menu for this Designate object.
-- --
@ -474,7 +474,7 @@ do -- DESIGNATE
self.Designating = {} self.Designating = {}
self:SetDesignateName() self:SetDesignateName()
self.LaseDuration = 60 self:SetLaseDuration() -- Default is 120 seconds.
self:SetFlashStatusMenu( false ) self:SetFlashStatusMenu( false )
self:SetFlashDetectionMessages( true ) self:SetFlashDetectionMessages( true )
@ -677,6 +677,14 @@ do -- DESIGNATE
return self return self
end end
--- Set the lase duration for designations.
-- @param #DESIGNATE self
-- @param #number LaseDuration The time in seconds a lase will continue to hold on target. The default is 120 seconds.
-- @return #DESIGNATE
function DESIGNATE:SetLaseDuration( LaseDuration )
self.LaseDuration = LaseDuration or 120
return self
end
--- Generate an array of possible laser codes. --- Generate an array of possible laser codes.
-- Each new lase will select a code from this table. -- Each new lase will select a code from this table.
@ -1000,9 +1008,9 @@ do -- DESIGNATE
if string.find( Designating, "L", 1, true ) == nil then if string.find( Designating, "L", 1, true ) == nil then
MENU_GROUP_COMMAND_DELAYED:New( AttackGroup, "Search other target", DetectedMenu, self.MenuForget, self, DesignateIndex ):SetTime( MenuTime ):SetTag( self.DesignateName ) MENU_GROUP_COMMAND_DELAYED:New( AttackGroup, "Search other target", DetectedMenu, self.MenuForget, self, DesignateIndex ):SetTime( MenuTime ):SetTag( self.DesignateName )
for LaserCode, MenuText in pairs( self.MenuLaserCodes ) do for LaserCode, MenuText in pairs( self.MenuLaserCodes ) do
MENU_GROUP_COMMAND_DELAYED:New( AttackGroup, string.format( MenuText, LaserCode ), DetectedMenu, self.MenuLaseCode, self, DesignateIndex, 60, LaserCode ):SetTime( MenuTime ):SetTag( self.DesignateName ) MENU_GROUP_COMMAND_DELAYED:New( AttackGroup, string.format( MenuText, LaserCode ), DetectedMenu, self.MenuLaseCode, self, DesignateIndex, self.LaseDuration, LaserCode ):SetTime( MenuTime ):SetTag( self.DesignateName )
end end
MENU_GROUP_COMMAND_DELAYED:New( AttackGroup, "Lase with random laser code(s)", DetectedMenu, self.MenuLaseOn, self, DesignateIndex, 60 ):SetTime( MenuTime ):SetTag( self.DesignateName ) MENU_GROUP_COMMAND_DELAYED:New( AttackGroup, "Lase with random laser code(s)", DetectedMenu, self.MenuLaseOn, self, DesignateIndex, self.LaseDuration ):SetTime( MenuTime ):SetTag( self.DesignateName )
else else
MENU_GROUP_COMMAND_DELAYED:New( AttackGroup, "Stop lasing", DetectedMenu, self.MenuLaseOff, self, DesignateIndex ):SetTime( MenuTime ):SetTag( self.DesignateName ) MENU_GROUP_COMMAND_DELAYED:New( AttackGroup, "Stop lasing", DetectedMenu, self.MenuLaseOff, self, DesignateIndex ):SetTime( MenuTime ):SetTag( self.DesignateName )
end end
@ -1160,10 +1168,10 @@ do -- DESIGNATE
if string.find( self.Designating[Index], "L", 1, true ) == nil then if string.find( self.Designating[Index], "L", 1, true ) == nil then
self.Designating[Index] = self.Designating[Index] .. "L" self.Designating[Index] = self.Designating[Index] .. "L"
self.LaseStart = timer.getTime()
self.LaseDuration = Duration
self:Lasing( Index, Duration, LaserCode )
end end
self.LaseStart = timer.getTime()
self.LaseDuration = Duration
self:Lasing( Index, Duration, LaserCode )
end end
@ -1322,7 +1330,7 @@ do -- DESIGNATE
local MarkedLaserCodesText = ReportLaserCodes:Text(', ') local MarkedLaserCodesText = ReportLaserCodes:Text(', ')
self.CC:GetPositionable():MessageToSetGroup( "Marking " .. MarkingCount .. " x " .. MarkedTypesText .. ", code " .. MarkedLaserCodesText .. ".", 5, self.AttackSet, self.DesignateName ) self.CC:GetPositionable():MessageToSetGroup( "Marking " .. MarkingCount .. " x " .. MarkedTypesText .. ", code " .. MarkedLaserCodesText .. ".", 5, self.AttackSet, self.DesignateName )
self:__Lasing( -30, Index, Duration, LaserCodeRequested ) self:__Lasing( -self.LaseDuration, Index, Duration, LaserCodeRequested )
self:SetDesignateMenu() self:SetDesignateMenu()

View File

@ -69,7 +69,6 @@
-- @field #boolean autosave Automatically save assets to file when mission ends. -- @field #boolean autosave Automatically save assets to file when mission ends.
-- @field #string autosavepath Path where the asset file is saved on auto save. -- @field #string autosavepath Path where the asset file is saved on auto save.
-- @field #string autosavefilename File name of the auto asset save file. Default is auto generated from warehouse id and name. -- @field #string autosavefilename File name of the auto asset save file. Default is auto generated from warehouse id and name.
-- @field #boolean safeparking If true, parking spots for aircraft are considered as occupied if e.g. a client aircraft is parked there. Default false.
-- @extends Core.Fsm#FSM -- @extends Core.Fsm#FSM
--- Have your assets at the right place at the right time - or not! --- Have your assets at the right place at the right time - or not!
@ -625,8 +624,7 @@
-- The @{#WAREHOUSE.OnAfterAttacked} function can be used by the mission designer to react to the enemy attack. For example by deploying some or all ground troops -- The @{#WAREHOUSE.OnAfterAttacked} function can be used by the mission designer to react to the enemy attack. For example by deploying some or all ground troops
-- currently in stock to defend the warehouse. Note that the warehouse also has a self defence option which can be enabled by the @{#WAREHOUSE.SetAutoDefenceOn}() -- currently in stock to defend the warehouse. Note that the warehouse also has a self defence option which can be enabled by the @{#WAREHOUSE.SetAutoDefenceOn}()
-- function. In this case, the warehouse will automatically spawn all ground troops. If the spawn zone is further away from the warehouse zone, all mobile troops -- function. In this case, the warehouse will automatically spawn all ground troops. If the spawn zone is further away from the warehouse zone, all mobile troops
-- are routed to the warehouse zone. The self request which is triggered on an automatic defence has the assignment "AutoDefence". So you can use this to -- are routed to the warehouse zone.
-- give orders to the groups that were spawned using the @{#WAREHOUSE.OnAfterSelfRequest} function.
-- --
-- If only ground troops of the enemy coalition are present in the warehouse zone, the warehouse and all its assets falls into the hands of the enemy. -- If only ground troops of the enemy coalition are present in the warehouse zone, the warehouse and all its assets falls into the hands of the enemy.
-- In this case the event **Captured** is triggered which can be captured by the @{#WAREHOUSE.OnAfterCaptured} function. -- In this case the event **Captured** is triggered which can be captured by the @{#WAREHOUSE.OnAfterCaptured} function.
@ -1557,7 +1555,6 @@ WAREHOUSE = {
autosave = false, autosave = false,
autosavepath = nil, autosavepath = nil,
autosavefile = nil, autosavefile = nil,
saveparking = false,
} }
--- Item of the warehouse stock table. --- Item of the warehouse stock table.
@ -1719,19 +1716,17 @@ WAREHOUSE.Quantity = {
--- Warehouse database. Note that this is a global array to have easier exchange between warehouses. --- Warehouse database. Note that this is a global array to have easier exchange between warehouses.
-- @type WAREHOUSE.db -- @type WAREHOUSE.db
-- @field #number AssetID Unique ID of each asset. This is a running number, which is increased each time a new asset is added. -- @field #number AssetID Unique ID of each asset. This is a running number, which is increased each time a new asset is added.
-- @field #table Assets Table holding registered assets, which are of type @{Functional.Warehouse#WAREHOUSE.Assetitem}.# -- @field #table Assets Table holding registered assets, which are of type @{Functional.Warehouse#WAREHOUSE.Assetitem}.
-- @field #number WarehouseID Unique ID of the warehouse. Running number.
-- @field #table Warehouses Table holding all defined @{#WAREHOUSE} objects by their unique ids. -- @field #table Warehouses Table holding all defined @{#WAREHOUSE} objects by their unique ids.
WAREHOUSE.db = { WAREHOUSE.db = {
AssetID = 0, AssetID = 0,
Assets = {}, Assets = {},
WarehouseID = 0, Warehouses = {}
Warehouses = {}
} }
--- Warehouse class version. --- Warehouse class version.
-- @field #string version -- @field #string version
WAREHOUSE.version="0.6.6" WAREHOUSE.version="0.6.4"
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: Warehouse todo list. -- TODO: Warehouse todo list.
@ -1740,12 +1735,12 @@ WAREHOUSE.version="0.6.6"
-- TODO: Add check if assets "on the move" are stationary. Can happen if ground units get stuck in buildings. If stationary auto complete transport by adding assets to request warehouse? Time? -- TODO: Add check if assets "on the move" are stationary. Can happen if ground units get stuck in buildings. If stationary auto complete transport by adding assets to request warehouse? Time?
-- TODO: Optimize findpathonroad. Do it only once (first time) and safe paths between warehouses similar to off-road paths. -- TODO: Optimize findpathonroad. Do it only once (first time) and safe paths between warehouses similar to off-road paths.
-- TODO: Spawn assets only virtually, i.e. remove requested assets from stock but do NOT spawn them ==> Interface to A2A dispatcher! Maybe do a negative sign on asset number? -- TODO: Spawn assets only virtually, i.e. remove requested assets from stock but do NOT spawn them ==> Interface to A2A dispatcher! Maybe do a negative sign on asset number?
-- TODO: Test capturing a neutral warehouse.
-- TODO: Make more examples: ARTY, CAP, ... -- TODO: Make more examples: ARTY, CAP, ...
-- TODO: Check also general requests like all ground. Is this a problem for self propelled if immobile units are among the assets? Check if transport. -- TODO: Check also general requests like all ground. Is this a problem for self propelled if immobile units are among the assets? Check if transport.
-- TODO: Handle the case when units of a group die during the transfer. -- TODO: Handle the case when units of a group die during the transfer.
-- TODO: Added habours as interface for transport to from warehouses? Could make a rudimentary shipping dispatcher. -- TODO: Added habours as interface for transport to from warehouses? Could make a rudimentary shipping dispatcher.
-- DONE: Test capturing a neutral warehouse. -- TODO: Add save/load capability of warehouse <==> percistance after mission restart. Difficult in lua!
-- DONE: Add save/load capability of warehouse <==> percistance after mission restart. Difficult in lua!
-- DONE: Get cargo bay and weight from CARGO_GROUP and GROUP. No necessary any more! -- DONE: Get cargo bay and weight from CARGO_GROUP and GROUP. No necessary any more!
-- DONE: Add possibility to set weight and cargo bay manually in AddAsset function as optional parameters. -- DONE: Add possibility to set weight and cargo bay manually in AddAsset function as optional parameters.
-- DONE: Check overlapping aircraft sometimes. -- DONE: Check overlapping aircraft sometimes.
@ -1792,7 +1787,7 @@ WAREHOUSE.version="0.6.6"
--- The WAREHOUSE constructor. Creates a new WAREHOUSE object from a static object. Parameters like the coalition and country are taken from the static object structure. --- The WAREHOUSE constructor. Creates a new WAREHOUSE object from a static object. Parameters like the coalition and country are taken from the static object structure.
-- @param #WAREHOUSE self -- @param #WAREHOUSE self
-- @param Wrapper.Static#STATIC warehouse The physical structure representing the warehouse. -- @param Wrapper.Static#STATIC warehouse The physical structure of the warehouse.
-- @param #string alias (Optional) Alias of the warehouse, i.e. the name it will be called when sending messages etc. Default is the name of the static -- @param #string alias (Optional) Alias of the warehouse, i.e. the name it will be called when sending messages etc. Default is the name of the static
-- @return #WAREHOUSE self -- @return #WAREHOUSE self
function WAREHOUSE:New(warehouse, alias) function WAREHOUSE:New(warehouse, alias)
@ -1800,11 +1795,7 @@ function WAREHOUSE:New(warehouse, alias)
-- Check if just a string was given and convert to static. -- Check if just a string was given and convert to static.
if type(warehouse)=="string" then if type(warehouse)=="string" then
warehouse=UNIT:FindByName(warehouse) warehouse=STATIC:FindByName(warehouse, true)
if warehouse==nil then
env.info(string.format("No warehouse unit with name %s found trying static.", warehouse))
warehouse=STATIC:FindByName(warehouse, true)
end
end end
-- Nil check. -- Nil check.
@ -1827,13 +1818,7 @@ function WAREHOUSE:New(warehouse, alias)
-- Set some variables. -- Set some variables.
self.warehouse=warehouse self.warehouse=warehouse
self.uid=tonumber(warehouse:GetID())
-- Increase global warehouse counter.
WAREHOUSE.db.WarehouseID=WAREHOUSE.db.WarehouseID+1
-- Set unique ID for this warehouse.
self.uid=WAREHOUSE.db.WarehouseID
--self.uid=tonumber(warehouse:GetID())
-- Closest of the same coalition but within a certain range. -- Closest of the same coalition but within a certain range.
local _airbase=self:GetCoordinate():GetClosestAirbase(nil, self:GetCoalition()) local _airbase=self:GetCoordinate():GetClosestAirbase(nil, self:GetCoalition())
@ -1877,7 +1862,7 @@ function WAREHOUSE:New(warehouse, alias)
self:AddTransition("*", "Stop", "Stopped") -- Stop the warehouse. self:AddTransition("*", "Stop", "Stopped") -- Stop the warehouse.
self:AddTransition("Stopped", "Restart", "Running") -- Restart the warehouse when it was stopped before. self:AddTransition("Stopped", "Restart", "Running") -- Restart the warehouse when it was stopped before.
self:AddTransition("Loaded", "Restart", "Running") -- Restart the warehouse when assets were loaded from file before. self:AddTransition("Loaded", "Restart", "Running") -- Restart the warehouse when assets were loaded from file before.
self:AddTransition("*", "Save", "*") -- Save the warehouse state to disk. self:AddTransition("*", "Save", "*") -- TODO Save the warehouse state to disk.
self:AddTransition("*", "Attacked", "Attacked") -- Warehouse is under attack by enemy coalition. self:AddTransition("*", "Attacked", "Attacked") -- Warehouse is under attack by enemy coalition.
self:AddTransition("Attacked", "Defeated", "Running") -- Attack by other coalition was defeated! self:AddTransition("Attacked", "Defeated", "Running") -- Attack by other coalition was defeated!
self:AddTransition("*", "ChangeCountry", "*") -- Change country (and coalition) of the warehouse. Warehouse is respawned! self:AddTransition("*", "ChangeCountry", "*") -- Change country (and coalition) of the warehouse. Warehouse is respawned!
@ -2378,24 +2363,6 @@ function WAREHOUSE:SetReportOff()
return self return self
end end
--- Enable safe parking option, i.e. parking spots at an airbase will be considered as occupied when a client aircraft is parked there (even if the client slot is not taken by a player yet).
-- Note that also incoming aircraft can reserve/occupie parking spaces.
-- @param #WAREHOUSE self
-- @return #WAREHOUSE self
function WAREHOUSE:SetSafeParkingOn()
self.safeparking=true
return self
end
--- Disable safe parking option. Note that is the default setting.
-- @param #WAREHOUSE self
-- @return #WAREHOUSE self
function WAREHOUSE:SetSafeParkingOff()
self.safeparking=false
return self
end
--- Set interval of status updates. Note that normally only one request can be processed per time interval. --- Set interval of status updates. Note that normally only one request can be processed per time interval.
-- @param #WAREHOUSE self -- @param #WAREHOUSE self
-- @param #number timeinterval Time interval in seconds. -- @param #number timeinterval Time interval in seconds.
@ -3563,13 +3530,13 @@ function WAREHOUSE:onafterAddAsset(From, Event, To, group, ngroups, forceattribu
else else
self:T(warehouse.wid..string.format("WARNING: Group %s is neither cargo nor transport!", group:GetName())) self:T(warehouse.wid..string.format("WARNING: Group %s is neither cargo nor transport!", group:GetName()))
end end
-- If no assignment was given we take the assignment of the request if there is any.
if assignment==nil and request.assignment~=nil then
assignment=request.assignment
end
end end
-- If no assignment was given we take the assignment of the request if there is any.
if assignment==nil and request.assignment~=nil then
assignment=request.assignment
end
end end
-- Get the asset from the global DB. -- Get the asset from the global DB.
@ -3621,7 +3588,6 @@ function WAREHOUSE:onafterAddAsset(From, Event, To, group, ngroups, forceattribu
else else
self:E(self.wid.."ERROR: Unknown group added as asset!") self:E(self.wid.."ERROR: Unknown group added as asset!")
self:E({unknowngroup=group})
end end
-- Update status. -- Update status.
@ -4654,7 +4620,7 @@ function WAREHOUSE:onafterAttacked(From, Event, To, Coalition, Country)
text=text..string.format("Deploying all %d ground assets.", nground) text=text..string.format("Deploying all %d ground assets.", nground)
-- Add self request. -- Add self request.
self:AddRequest(self, WAREHOUSE.Descriptor.CATEGORY, Group.Category.GROUND, WAREHOUSE.Quantity.ALL, nil, nil , 0, "AutoDefence") self:AddRequest(self, WAREHOUSE.Descriptor.CATEGORY, Group.Category.GROUND, WAREHOUSE.Quantity.ALL, nil, nil , 0)
else else
text=text..string.format("No ground assets currently available.") text=text..string.format("No ground assets currently available.")
end end
@ -6330,26 +6296,25 @@ function WAREHOUSE:_CheckRequestValid(request)
-- TODO: maybe only check if spots > 0 for the necessary terminal type? At least for FARPS. -- TODO: maybe only check if spots > 0 for the necessary terminal type? At least for FARPS.
-- Get necessary terminal type. -- Get necessary terminal type.
local termtype_dep=self:_GetTerminal(asset.attribute, self:GetAirbaseCategory()) local termtype=self:_GetTerminal(asset.attribute)
local termtype_des=self:_GetTerminal(asset.attribute, request.warehouse:GetAirbaseCategory())
-- Get number of parking spots. -- Get number of parking spots.
local np_departure=self.airbase:GetParkingSpotsNumber(termtype_dep) local np_departure=self.airbase:GetParkingSpotsNumber(termtype)
local np_destination=request.airbase:GetParkingSpotsNumber(termtype_des) local np_destination=request.airbase:GetParkingSpotsNumber(termtype)
-- Debug info. -- Debug info.
self:T(string.format("Asset attribute = %s, DEPARTURE: terminal type = %d, spots = %d, DESTINATION: terminal type = %d, spots = %d", asset.attribute, termtype_dep, np_departure, termtype_des, np_destination)) self:T(string.format("Asset attribute = %s, terminal type = %d, spots at departure = %d, destination = %d", asset.attribute, termtype, np_departure, np_destination))
-- Not enough parking at sending warehouse. -- Not enough parking at sending warehouse.
--if (np_departure < request.nasset) and not (self.category==Airbase.Category.SHIP or self.category==Airbase.Category.HELIPAD) then --if (np_departure < request.nasset) and not (self.category==Airbase.Category.SHIP or self.category==Airbase.Category.HELIPAD) then
if np_departure < nasset then if np_departure < nasset then
self:E(string.format("ERROR: Incorrect request. Not enough parking spots of terminal type %d at warehouse. Available spots %d < %d necessary.", termtype_dep, np_departure, nasset)) self:E(string.format("ERROR: Incorrect request. Not enough parking spots of terminal type %d at warehouse. Available spots %d < %d necessary.", termtype, np_departure, nasset))
valid=false valid=false
end end
-- No parking at requesting warehouse. -- No parking at requesting warehouse.
if np_destination == 0 then if np_destination == 0 then
self:E(string.format("ERROR: Incorrect request. No parking spots of terminal type %d at requesting warehouse. Available spots = %d!", termtype_des, np_destination)) self:E(string.format("ERROR: Incorrect request. No parking spots of terminal type %d at requesting warehouse. Available spots = %d!", termtype, np_destination))
valid=false valid=false
end end
@ -6487,7 +6452,7 @@ function WAREHOUSE:_CheckRequestValid(request)
self:T(text) self:T(text)
-- Get necessary terminal type for helos or transport aircraft. -- Get necessary terminal type for helos or transport aircraft.
local termtype=self:_GetTerminal(request.transporttype, self:GetAirbaseCategory()) local termtype=self:_GetTerminal(request.transporttype)
-- Get number of parking spots. -- Get number of parking spots.
local np_departure=self.airbase:GetParkingSpotsNumber(termtype) local np_departure=self.airbase:GetParkingSpotsNumber(termtype)
@ -6506,7 +6471,6 @@ function WAREHOUSE:_CheckRequestValid(request)
if request.transporttype==WAREHOUSE.TransportType.AIRPLANE then if request.transporttype==WAREHOUSE.TransportType.AIRPLANE then
-- Total number of parking spots for transport planes at destination. -- Total number of parking spots for transport planes at destination.
termtype=self:_GetTerminal(request.transporttype, request.warehouse:GetAirbaseCategory())
local np_destination=request.airbase:GetParkingSpotsNumber(termtype) local np_destination=request.airbase:GetParkingSpotsNumber(termtype)
-- Debug info. -- Debug info.
@ -6948,13 +6912,13 @@ end
--- Get the proper terminal type based on generalized attribute of the group. --- Get the proper terminal type based on generalized attribute of the group.
--@param #WAREHOUSE self --@param #WAREHOUSE self
--@param #WAREHOUSE.Attribute _attribute Generlized attibute of unit. --@param #WAREHOUSE.Attribute _attribute Generlized attibute of unit.
--@param #number _category Airbase category.
--@return Wrapper.Airbase#AIRBASE.TerminalType Terminal type for this group. --@return Wrapper.Airbase#AIRBASE.TerminalType Terminal type for this group.
function WAREHOUSE:_GetTerminal(_attribute, _category) function WAREHOUSE:_GetTerminal(_attribute)
-- Default terminal is "large". -- Default terminal is "large".
local _terminal=AIRBASE.TerminalType.OpenBig local _terminal=AIRBASE.TerminalType.OpenBig
if _attribute==WAREHOUSE.Attribute.AIR_FIGHTER then if _attribute==WAREHOUSE.Attribute.AIR_FIGHTER then
-- Fighter ==> small. -- Fighter ==> small.
_terminal=AIRBASE.TerminalType.FighterAircraft _terminal=AIRBASE.TerminalType.FighterAircraft
@ -6964,15 +6928,6 @@ function WAREHOUSE:_GetTerminal(_attribute, _category)
elseif _attribute==WAREHOUSE.Attribute.AIR_TRANSPORTHELO or _attribute==WAREHOUSE.Attribute.AIR_ATTACKHELO then elseif _attribute==WAREHOUSE.Attribute.AIR_TRANSPORTHELO or _attribute==WAREHOUSE.Attribute.AIR_ATTACKHELO then
-- Helicopter. -- Helicopter.
_terminal=AIRBASE.TerminalType.HelicopterUsable _terminal=AIRBASE.TerminalType.HelicopterUsable
else
--_terminal=AIRBASE.TerminalType.OpenMedOrBig
end
-- For ships, we allow medium spots for all fixed wing aircraft. There are smaller tankers and AWACS aircraft that can use a carrier.
if _category==Airbase.Category.SHIP then
if not (_attribute==WAREHOUSE.Attribute.AIR_TRANSPORTHELO or _attribute==WAREHOUSE.Attribute.AIR_ATTACKHELO) then
_terminal=AIRBASE.TerminalType.OpenMedOrBig
end
end end
return _terminal return _terminal
@ -7047,6 +7002,20 @@ function WAREHOUSE:_FindParkingForAssets(airbase, assets)
table.insert(obstacles,{coord=_coord, size=_size, name=_name, type="scenery"}) table.insert(obstacles,{coord=_coord, size=_size, name=_name, type="scenery"})
end end
--[[
-- TODO Clients? Unoccupied client aircraft are also important! Are they already included in scanned units maybe?
local clients=_DATABASE.CLIENTS
for _,_client in pairs(clients) do
local client=_client --Wrapper.Client#CLIENT
env.info(string.format("FF Client name %s", client:GetName()))
local unit=UNIT:FindByName(client:GetName())
--local unit=client:GetClientGroupUnit()
local _coord=unit:GetCoordinate()
local _name=unit:GetName()
local _size=self:_GetObjectSize(client:GetClientGroupDCSUnit())
table.insert(obstacles,{coord=_coord, size=_size, name=_name, type="client"})
end
]]
end end
-- Parking data for all assets. -- Parking data for all assets.
@ -7057,7 +7026,7 @@ function WAREHOUSE:_FindParkingForAssets(airbase, assets)
local _asset=asset --#WAREHOUSE.Assetitem local _asset=asset --#WAREHOUSE.Assetitem
-- Get terminal type of this asset -- Get terminal type of this asset
local terminaltype=self:_GetTerminal(asset.attribute, self:GetAirbaseCategory()) local terminaltype=self:_GetTerminal(asset.attribute)
-- Asset specific parking. -- Asset specific parking.
parking[_asset.uid]={} parking[_asset.uid]={}
@ -7079,17 +7048,10 @@ function WAREHOUSE:_FindParkingForAssets(airbase, assets)
local _toac=parkingspot.TOAC local _toac=parkingspot.TOAC
--env.info(string.format("FF asset=%s (id=%d): needs terminal type=%d, id=%d, #obstacles=%d", _asset.templatename, _asset.uid, terminaltype, _termid, #obstacles)) --env.info(string.format("FF asset=%s (id=%d): needs terminal type=%d, id=%d, #obstacles=%d", _asset.templatename, _asset.uid, terminaltype, _termid, #obstacles))
local free=true
local problem=nil
-- Safe parking using TO_AC from DCS result.
if self.safeparking and _toac then
free=false
self:T("Parking spot %d is occupied by other aircraft taking off or landing.", _termid)
end
-- Loop over all obstacles. -- Loop over all obstacles.
local free=true
local problem=nil
for _,obstacle in pairs(obstacles) do for _,obstacle in pairs(obstacles) do
-- Check if aircraft overlaps with any obstacle. -- Check if aircraft overlaps with any obstacle.

View File

@ -545,6 +545,10 @@ do -- ZONE_CAPTURE_COALITION
-- @param #ZONE_CAPTURE_COALITION self -- @param #ZONE_CAPTURE_COALITION self
-- @param #number Delay -- @param #number Delay
-- We check if a unit within the zone is hit.
-- If it is, then we must move the zone to attack state.
self:HandleEvent( EVENTS.Hit, self.OnEventHit )
return self return self
end end
@ -789,5 +793,20 @@ do -- ZONE_CAPTURE_COALITION
end end
end end
--- @param #ZONE_CAPTURE_COALITION self
-- @param Core.Event#EVENTDATA EventData The event data.
function ZONE_CAPTURE_COALITION:OnEventHit( EventData )
local UnitHit = EventData.TgtUnit
if UnitHit then
if UnitHit:IsInZone( self.Zone ) then
self:Attack()
end
end
end
end end

View File

@ -213,3 +213,36 @@ function STATIC:ReSpawnAt( Coordinate, Heading )
SpawnStatic:ReSpawnAt( Coordinate, Heading ) SpawnStatic:ReSpawnAt( Coordinate, Heading )
end end
--- Returns true if the unit is within a @{Zone}.
-- @param #STATIC self
-- @param Core.Zone#ZONE_BASE Zone The zone to test.
-- @return #boolean Returns true if the unit is within the @{Core.Zone#ZONE_BASE}
function STATIC:IsInZone( Zone )
self:F2( { self.StaticName, Zone } )
if self:IsAlive() then
local IsInZone = Zone:IsVec3InZone( self:GetVec3() )
return IsInZone
end
return false
end
--- Returns true if the unit is not within a @{Zone}.
-- @param #STATIC self
-- @param Core.Zone#ZONE_BASE Zone The zone to test.
-- @return #boolean Returns true if the unit is not within the @{Core.Zone#ZONE_BASE}
function STATIC:IsNotInZone( Zone )
self:F2( { self.StaticName, Zone } )
if self:IsAlive() then
local IsInZone = not Zone:IsVec3InZone( self:GetVec3() )
self:T( { IsInZone } )
return IsInZone
else
return false
end
end

View File

@ -59,12 +59,21 @@ Functional/Suppression.lua
Functional/PseudoATC.lua Functional/PseudoATC.lua
Functional/Warehouse.lua Functional/Warehouse.lua
Ops/Airboss.lua
Ops/RecoveryTanker.lua
Ops/RescueHelo.lua
AI/AI_Balancer.lua AI/AI_Balancer.lua
AI/AI_Air.lua
AI/AI_A2A.lua AI/AI_A2A.lua
AI/AI_A2A_Patrol.lua AI/AI_A2A_Patrol.lua
AI/AI_A2A_Cap.lua AI/AI_A2A_Cap.lua
AI/AI_A2A_Gci.lua AI/AI_A2A_Gci.lua
AI/AI_A2A_Dispatcher.lua AI/AI_A2A_Dispatcher.lua
AI/AI_A2G.lua
AI/AI_A2G_Engage.lua
AI/AI_A2G_Patrol.lua
AI/AI_A2G_Dispatcher.lua
AI/AI_Patrol.lua AI/AI_Patrol.lua
AI/AI_Cap.lua AI/AI_Cap.lua
AI/AI_Cas.lua AI/AI_Cas.lua