Merge pull request #1244 from FlightControl-Master/FF/Develop

A2A Dispatcher and other fixes/improvements
This commit is contained in:
Frank 2019-12-17 00:16:04 +01:00 committed by GitHub
commit a193d342d7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 402 additions and 156 deletions

View File

@ -3045,8 +3045,7 @@ do -- AI_A2A_DISPATCHER
if DefenderTask.Type == "CAP" or DefenderTask.Type == "GCI" then
-- If there is no target, then add the AIGroup to the ResultAIGroups for Engagement to the AttackerSet
if DefenderTask.Target == nil then
if DefenderTask.Fsm:Is( "Returning" )
or DefenderTask.Fsm:Is( "Patrolling" ) then
if DefenderTask.Fsm:Is( "Returning" ) or DefenderTask.Fsm:Is( "Patrolling" ) then
Friendlies = Friendlies or {}
Friendlies[Friendly] = Friendly
DefenderCount = DefenderCount + Friendly:GetSize()
@ -3315,19 +3314,20 @@ do -- AI_A2A_DISPATCHER
-- @param Functional.Detection#DETECTION_BASE.DetectedItem AttackerDetection Detected item.
-- @param #table Defenders Defenders table.
function AI_A2A_DISPATCHER:onafterENGAGE( From, Event, To, AttackerDetection, Defenders )
self:F("ENGAGING "..tostring(AttackerDetection.Name))
self:F("ENGAGING Detection ID="..tostring(AttackerDetection.ID))
if Defenders then
for DefenderID, Defender in pairs( Defenders ) do
local Fsm = self:GetDefenderTaskFsm( Defender )
Fsm:EngageRoute( AttackerDetection.Set ) -- Engage on the TargetSetUnit
self:SetDefenderTaskTarget( Defender, AttackerDetection )
end
end
end
@ -3341,7 +3341,7 @@ do -- AI_A2A_DISPATCHER
-- @param #table DefenderFriendlies Friendly defenders.
function AI_A2A_DISPATCHER:onafterGCI( From, Event, To, AttackerDetection, DefendersMissing, DefenderFriendlies )
self:F("GCI "..tostring(AttackerDetection.Name))
self:F("GCI Detection ID="..tostring(AttackerDetection.ID))
self:F( { From, Event, To, AttackerDetection.Index, DefendersMissing, DefenderFriendlies } )
@ -3606,8 +3606,7 @@ do -- AI_A2A_DISPATCHER
-- 2. There is sufficient fuel
-- 3. There is sufficient ammo
-- 4. The plane is not damaged
if DefenderGroups and DetectedItem.IsDetected == true then
if DefenderGroups and DetectedItem.IsDetected == true then
return DefenderGroups
end
@ -3686,13 +3685,13 @@ do -- AI_A2A_DISPATCHER
local TaskReport = REPORT:New()
local Report = REPORT:New( "Tactical Overviews" )
local Report = REPORT:New( "Tactical Overview:" )
local DefenderGroupCount = 0
-- Now that all obsolete tasks are removed, loop through the detected targets.
--for DetectedItemID, DetectedItem in pairs( Detection:GetDetectedItems() ) do
for DetectedItemID, DetectedItem in UTILS.spairs( Detection:GetDetectedItems(), function( t, a, b ) return self:Order(t[a]) < self:Order(t[b]) end ) do
for DetectedItemID, DetectedItem in UTILS.spairs( Detection:GetDetectedItems(), function( t, a, b ) return self:Order(t[a]) < self:Order(t[b]) end ) do
local DetectedItem = DetectedItem -- Functional.Detection#DETECTION_BASE.DetectedItem
local DetectedSet = DetectedItem.Set -- Core.Set#SET_UNIT

View File

@ -334,9 +334,8 @@ end
-- @param #string Event The Event string.
-- @param #string To The To State string.
function AI_AIR_ENGAGE:onafterEngage( AIGroup, From, Event, To )
-- TODO: This function is overwritten below!
self:HandleEvent( EVENTS.Dead )
end
-- todo: need to fix this global function
@ -349,10 +348,10 @@ end
-- @param #string Event The Event string.
-- @param #string To The To State string.
function AI_AIR_ENGAGE:onbeforeEngage( AIGroup, From, Event, To )
if self.Accomplished == true then
return false
end
end
return true
end
--- onafter event handler for Abort event.
@ -405,14 +404,10 @@ end
--- @param Wrapper.Group#GROUP AIControllable
function AI_AIR_ENGAGE.___EngageRoute( AIGroup, Fsm, AttackSetUnit )
Fsm:I( { "AI_AIR_ENGAGE.___EngageRoute:", AIGroup:GetName() } )
Fsm:I(string.format("AI_AIR_ENGAGE.___EngageRoute: %s", tostring(AIGroup:GetName())))
if AIGroup:IsAlive() then
Fsm:__EngageRoute( Fsm.TaskDelay, AttackSetUnit )
--local Task = AIGroup:TaskOrbitCircle( 4000, 400 )
--AIGroup:SetTask( Task )
if AIGroup and AIGroup:IsAlive() then
Fsm:__EngageRoute( Fsm.TaskDelay or 0.1, AttackSetUnit )
end
end
@ -423,7 +418,6 @@ end
-- @param #string Event The Event string.
-- @param #string To The To State string.
function AI_AIR_ENGAGE:onafterEngageRoute( DefenderGroup, From, Event, To, AttackSetUnit )
self:I( { DefenderGroup, From, Event, To, AttackSetUnit } )
local DefenderGroupName = DefenderGroup:GetName()
@ -450,39 +444,33 @@ function AI_AIR_ENGAGE:onafterEngageRoute( DefenderGroup, From, Event, To, Attac
TargetCoord:SetY( EngageAltitude ) -- Ground targets don't have an altitude.
local TargetDistance = DefenderCoord:Get2DDistance( TargetCoord )
local EngageDistance = ( DefenderGroup:IsHelicopter() and 5000 ) or ( DefenderGroup:IsAirPlane() and 10000 )
local EngageDistance = ( DefenderGroup:IsHelicopter() and 5000 ) or ( DefenderGroup:IsAirPlane() and 10000 )
if TargetDistance <= EngageDistance * 3 then
-- TODO: A factor of * 3 is way too close. This causes the AI not to engange until merged sometimes!
if TargetDistance <= EngageDistance * 9 then
self:I(string.format("AI_AIR_ENGAGE onafterEngageRoute ==> __Engage - target distance = %.1f km", TargetDistance/1000))
self:__Engage( 0.1, AttackSetUnit )
else
self:I(string.format("FF AI_AIR_ENGAGE onafterEngageRoute ==> Routing - target distance = %.1f km", TargetDistance/1000))
local EngageRoute = {}
local AttackTasks = {}
--- Calculate the target route point.
local FromWP = DefenderCoord:WaypointAir(
self.PatrolAltType or "RADIO",
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
EngageSpeed,
true
)
local FromWP = DefenderCoord:WaypointAir(self.PatrolAltType or "RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, EngageSpeed, true)
EngageRoute[#EngageRoute+1] = FromWP
self:SetTargetDistance( TargetCoord ) -- For RTB status check
local FromEngageAngle = DefenderCoord:GetAngleDegrees( DefenderCoord:GetDirectionVec3( TargetCoord ) )
local ToWP = DefenderCoord:Translate( EngageDistance, FromEngageAngle, true ):WaypointAir(
self.PatrolAltType or "RADIO",
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
EngageSpeed,
true
)
local ToCoord=DefenderCoord:Translate( EngageDistance, FromEngageAngle, true )
local ToWP = ToCoord:WaypointAir(self.PatrolAltType or "RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, EngageSpeed, true)
EngageRoute[#EngageRoute+1] = ToWP
@ -492,11 +480,12 @@ function AI_AIR_ENGAGE:onafterEngageRoute( DefenderGroup, From, Event, To, Attac
DefenderGroup:OptionROEReturnFire()
DefenderGroup:OptionROTEvadeFire()
DefenderGroup:Route( EngageRoute, self.TaskDelay )
DefenderGroup:Route( EngageRoute, self.TaskDelay or 0.1 )
end
end
else
-- TODO: This will make an A2A Dispatcher CAP flight to return rather than going back to patrolling!
self:I( DefenderGroupName .. ": No targets found -> Going RTB")
self:Return()
end
@ -506,10 +495,11 @@ end
--- @param Wrapper.Group#GROUP AIControllable
function AI_AIR_ENGAGE.___Engage( AIGroup, Fsm, AttackSetUnit )
Fsm:I( { "AI_AIR_ENGAGE.___Engage:", AIGroup:GetName() } )
Fsm:I(string.format("AI_AIR_ENGAGE.___Engage: %s", tostring(AIGroup:GetName())))
if AIGroup:IsAlive() then
Fsm:__Engage( Fsm.TaskDelay, AttackSetUnit )
if AIGroup and AIGroup:IsAlive() then
local delay=Fsm.TaskDelay or 0.1
Fsm:__Engage(delay, AttackSetUnit)
end
end
@ -520,7 +510,6 @@ end
-- @param #string Event The Event string.
-- @param #string To The To State string.
function AI_AIR_ENGAGE:onafterEngage( DefenderGroup, From, Event, To, AttackSetUnit )
self:F( { DefenderGroup, From, Event, To, AttackSetUnit} )
local DefenderGroupName = DefenderGroup:GetName()
@ -532,7 +521,7 @@ function AI_AIR_ENGAGE:onafterEngage( DefenderGroup, From, Event, To, AttackSetU
if AttackCount > 0 then
if DefenderGroup:IsAlive() then
if DefenderGroup and DefenderGroup:IsAlive() then
local EngageAltitude = math.random( self.EngageFloorAltitude or 500, self.EngageCeilingAltitude or 1000 )
local EngageSpeed = math.random( self.EngageMinSpeed, self.EngageMaxSpeed )
@ -544,33 +533,25 @@ function AI_AIR_ENGAGE:onafterEngage( DefenderGroup, From, Event, To, AttackSetU
TargetCoord:SetY( EngageAltitude ) -- Ground targets don't have an altitude.
local TargetDistance = DefenderCoord:Get2DDistance( TargetCoord )
local EngageDistance = ( DefenderGroup:IsHelicopter() and 5000 ) or ( DefenderGroup:IsAirPlane() and 10000 )
local EngageRoute = {}
local AttackTasks = {}
local FromWP = DefenderCoord:WaypointAir(
self.EngageAltType or "RADIO",
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
EngageSpeed,
true
)
local FromWP = DefenderCoord:WaypointAir(self.EngageAltType or "RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, EngageSpeed, true)
EngageRoute[#EngageRoute+1] = FromWP
self:SetTargetDistance( TargetCoord ) -- For RTB status check
local FromEngageAngle = DefenderCoord:GetAngleDegrees( DefenderCoord:GetDirectionVec3( TargetCoord ) )
local ToWP = DefenderCoord:Translate( EngageDistance, FromEngageAngle, true ):WaypointAir(
self.EngageAltType or "RADIO",
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
EngageSpeed,
true
)
local ToCoord=DefenderCoord:Translate( EngageDistance, FromEngageAngle, true )
local ToWP = ToCoord:WaypointAir(self.EngageAltType or "RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, EngageSpeed, true)
EngageRoute[#EngageRoute+1] = ToWP
if TargetDistance <= EngageDistance * 3 then
-- TODO: A factor of * 3 this way too low. This causes the AI NOT to engage until very close or even merged sometimes. Some A2A missiles have a much longer range! Needs more frequent updates of the task!
if TargetDistance <= EngageDistance * 9 then
local AttackUnitTasks = self:CreateAttackUnitTasks( AttackSetUnit, DefenderGroup, EngageAltitude ) -- Polymorphic
@ -579,7 +560,8 @@ function AI_AIR_ENGAGE:onafterEngage( DefenderGroup, From, Event, To, AttackSetU
self:Return()
return
else
self:I( DefenderGroupName .. ": Engaging targets " )
local text=string.format("%s: Engaging targets at distance %.2f NM", DefenderGroupName, UTILS.MetersToNM(TargetDistance))
self:I(text)
DefenderGroup:OptionROEOpenFire()
DefenderGroup:OptionROTEvadeFire()
DefenderGroup:OptionKeepWeaponsOnThreat()
@ -591,10 +573,11 @@ function AI_AIR_ENGAGE:onafterEngage( DefenderGroup, From, Event, To, AttackSetU
AttackTasks[#AttackTasks+1] = DefenderGroup:TaskFunction( "AI_AIR_ENGAGE.___Engage", self, AttackSetUnit )
EngageRoute[#EngageRoute].task = DefenderGroup:TaskCombo( AttackTasks )
DefenderGroup:Route( EngageRoute, self.TaskDelay )
DefenderGroup:Route( EngageRoute, self.TaskDelay or 0.1 )
end
else
-- TODO: This will make an A2A Dispatcher CAP flight to return rather than going back to patrolling!
self:I( DefenderGroupName .. ": No targets found -> returning.")
self:Return()
return
@ -605,9 +588,9 @@ end
function AI_AIR_ENGAGE.Resume( AIEngage, Fsm )
AIEngage:F( { "Resume:", AIEngage:GetName() } )
if AIEngage:IsAlive() then
Fsm:__Reset( Fsm.TaskDelay )
Fsm:__EngageRoute( Fsm.TaskDelay, Fsm.AttackSetUnit )
if AIEngage and AIEngage:IsAlive() then
Fsm:__Reset( Fsm.TaskDelay or 0.1 )
Fsm:__EngageRoute( Fsm.TaskDelay or 0.2, Fsm.AttackSetUnit )
end
end

View File

@ -421,7 +421,7 @@ end
-- @param #string To The To State string.
function AI_CAP_ZONE:onafterEngage( Controllable, From, Event, To )
if Controllable:IsAlive() then
if Controllable and Controllable:IsAlive() then
local EngageRoute = {}

View File

@ -198,7 +198,7 @@ FOX.MenuF10Root=nil
--- FOX class version.
-- @field #string version
FOX.version="0.6.0"
FOX.version="0.6.1"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- ToDo list
@ -243,6 +243,7 @@ function FOX:New()
self:AddTransition("*", "MissileDestroyed", "*") -- Missile was destroyed before impact.
self:AddTransition("*", "EnterSafeZone", "*") -- Player enters a safe zone.
self:AddTransition("*", "ExitSafeZone", "*") -- Player exists a safe zone.
self:AddTransition("Running", "Stop", "Stopped") -- Stop FOX script.
------------------------
--- Pseudo Functions ---

View File

@ -5,6 +5,10 @@
-- ## Features:
--
-- * Hold fire of attacked units when being fired upon.
-- * Retreat to a user defined zone.
-- * Fall back on hits.
-- * Take cover on hits.
-- * Gaussian distribution of suppression time.
--
-- ===
--
@ -44,6 +48,7 @@
-- @type SUPPRESSION
-- @field #string ClassName Name of the class.
-- @field #boolean Debug Write Debug messages to DCS log file and send Debug messages to all players.
-- @field #string lid String for DCS log file.
-- @field #boolean flare Flare units when they get hit or die.
-- @field #boolean smoke Smoke places to which the group retreats, falls back or hides.
-- @field #list DCSdesc Table containing all DCS descriptors of the group.
@ -222,41 +227,42 @@
--
-- @field #SUPPRESSION
SUPPRESSION={
ClassName = "SUPPRESSION",
Debug = false,
flare = false,
smoke = false,
DCSdesc = nil,
Type = nil,
IsInfantry=nil,
SpeedMax = nil,
Tsuppress_ave = 15,
Tsuppress_min = 5,
Tsuppress_max = 25,
TsuppressOver = nil,
IniGroupStrength = nil,
Nhit = 0,
Formation = "Off road",
Speed = 4,
MenuON = false,
FallbackON = false,
FallbackWait = 60,
FallbackDist = 100,
FallbackHeading = nil,
TakecoverON = false,
TakecoverWait = 120,
TakecoverRange = 300,
hideout = nil,
PminFlee = 10,
PmaxFlee = 90,
RetreatZone = nil,
RetreatDamage = nil,
RetreatWait = 7200,
ClassName = "SUPPRESSION",
Debug = false,
lid = nil,
flare = false,
smoke = false,
DCSdesc = nil,
Type = nil,
IsInfantry = nil,
SpeedMax = nil,
Tsuppress_ave = 15,
Tsuppress_min = 5,
Tsuppress_max = 25,
TsuppressOver = nil,
IniGroupStrength = nil,
Nhit = 0,
Formation = "Off road",
Speed = 4,
MenuON = false,
FallbackON = false,
FallbackWait = 60,
FallbackDist = 100,
FallbackHeading = nil,
TakecoverON = false,
TakecoverWait = 120,
TakecoverRange = 300,
hideout = nil,
PminFlee = 10,
PmaxFlee = 90,
RetreatZone = nil,
RetreatDamage = nil,
RetreatWait = 7200,
CurrentAlarmState = "unknown",
CurrentROE = "unknown",
CurrentROE = "unknown",
DefaultAlarmState = "Auto",
DefaultROE = "Weapon Free",
eventmoose = true,
DefaultROE = "Weapon Free",
eventmoose = true,
}
--- Enumerator of possible rules of engagement.
@ -279,13 +285,9 @@ SUPPRESSION.AlarmState={
-- @field #string MenuF10
SUPPRESSION.MenuF10=nil
--- Some ID to identify who we are in output of the DCS.log file.
-- @field #string id
SUPPRESSION.id="SUPPRESSION | "
--- PSEUDOATC version.
-- @field #number version
SUPPRESSION.version="0.9.0"
SUPPRESSION.version="0.9.1"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@ -302,22 +304,22 @@ SUPPRESSION.version="0.9.0"
-- @return #SUPPRESSION SUPPRESSION object.
-- @return nil If group does not exist or is not a ground group.
function SUPPRESSION:New(group)
BASE:F2(group)
-- Inherits from FSM_CONTROLLABLE
local self=BASE:Inherit(self, FSM_CONTROLLABLE:New()) -- #SUPPRESSION
-- Check that group is present.
if group then
self:T(SUPPRESSION.id..string.format("SUPPRESSION version %s. Activating suppressive fire for group %s", SUPPRESSION.version, group:GetName()))
self.lid=string.format("SUPPRESSION %s | ", tostring(group:GetName()))
self:T(self.lid..string.format("SUPPRESSION version %s. Activating suppressive fire for group %s", SUPPRESSION.version, group:GetName()))
else
self:E(SUPPRESSION.id.."Suppressive fire: Requested group does not exist! (Has to be a MOOSE group.)")
self:E(self.lid.."SUPPRESSION | Requested group does not exist! (Has to be a MOOSE group.)")
return nil
end
-- Check that we actually have a GROUND group.
if group:IsGround()==false then
self:E(SUPPRESSION.id..string.format("SUPPRESSION fire group %s has to be a GROUND group!", group:GetName()))
self:E(self.lid..string.format("SUPPRESSION fire group %s has to be a GROUND group!", group:GetName()))
return nil
end
@ -350,6 +352,7 @@ function SUPPRESSION:New(group)
-- Transitions
self:AddTransition("*", "Start", "CombatReady")
self:AddTransition("*", "Status", "*")
self:AddTransition("CombatReady", "Hit", "Suppressed")
self:AddTransition("Suppressed", "Hit", "Suppressed")
self:AddTransition("Suppressed", "Recovered", "CombatReady")
@ -364,6 +367,38 @@ function SUPPRESSION:New(group)
self:AddTransition("TakingCover", "Hit", "TakingCover")
self:AddTransition("FallingBack", "Hit", "FallingBack")
--- Trigger "Status" event.
-- @function [parent=#SUPPRESSION] Status
-- @param #SUPPRESSION self
--- Trigger "Status" event after a delay.
-- @function [parent=#SUPPRESSION] __Status
-- @param #SUPPRESSION self
-- @param #number Delay Delay in seconds.
--- User function for OnAfter "Status" event.
-- @function [parent=#SUPPRESSION] OnAfterStatus
-- @param #SUPPRESSION self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group.
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
--- Trigger "Hit" event.
-- @function [parent=#SUPPRESSION] Hit
-- @param #SUPPRESSION self
-- @param Wrapper.Unit#UNIT Unit Unit that was hit.
-- @param Wrapper.Unit#UNIT AttackUnit Unit that attacked.
--- Trigger "Hit" event after a delay.
-- @function [parent=#SUPPRESSION] __Hit
-- @param #SUPPRESSION self
-- @param #number Delay Delay in seconds.
-- @param Wrapper.Unit#UNIT Unit Unit that was hit.
-- @param Wrapper.Unit#UNIT AttackUnit Unit that attacked.
--- User function for OnBefore "Hit" event.
-- @function [parent=#SUPPRESSION] OnBeforeHit
-- @param #SUPPRESSION self
@ -375,7 +410,7 @@ function SUPPRESSION:New(group)
-- @param Wrapper.Unit#UNIT AttackUnit Unit that attacked.
-- @return #boolean
--- User function for OnAfer "Hit" event.
--- User function for OnAfter "Hit" event.
-- @function [parent=#SUPPRESSION] OnAfterHit
-- @param #SUPPRESSION self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group.
@ -384,7 +419,16 @@ function SUPPRESSION:New(group)
-- @param #string To To state.
-- @param Wrapper.Unit#UNIT Unit Unit that was hit.
-- @param Wrapper.Unit#UNIT AttackUnit Unit that attacked.
--- Trigger "Recovered" event.
-- @function [parent=#SUPPRESSION] Recovered
-- @param #SUPPRESSION self
--- Trigger "Recovered" event after a delay.
-- @function [parent=#SUPPRESSION] Recovered
-- @param #number Delay Delay in seconds.
-- @param #SUPPRESSION self
--- User function for OnBefore "Recovered" event.
-- @function [parent=#SUPPRESSION] OnBeforeRecovered
@ -404,6 +448,17 @@ function SUPPRESSION:New(group)
-- @param #string To To state.
--- Trigger "TakeCover" event.
-- @function [parent=#SUPPRESSION] TakeCover
-- @param #SUPPRESSION self
-- @param Core.Point#COORDINATE Hideout Place where the group will hide.
--- Trigger "TakeCover" event after a delay.
-- @function [parent=#SUPPRESSION] __TakeCover
-- @param #SUPPRESSION self
-- @param #number Delay Delay in seconds.
-- @param Core.Point#COORDINATE Hideout Place where the group will hide.
--- User function for OnBefore "TakeCover" event.
-- @function [parent=#SUPPRESSION] OnBeforeTakeCover
-- @param #SUPPRESSION self
@ -424,6 +479,17 @@ function SUPPRESSION:New(group)
-- @param Core.Point#COORDINATE Hideout Place where the group will hide.
--- Trigger "FallBack" event.
-- @function [parent=#SUPPRESSION] FallBack
-- @param #SUPPRESSION self
-- @param Wrapper.Unit#UNIT AttackUnit Attacking unit. We will move away from this.
--- Trigger "FallBack" event after a delay.
-- @function [parent=#SUPPRESSION] __FallBack
-- @param #SUPPRESSION self
-- @param #number Delay Delay in seconds.
-- @param Wrapper.Unit#UNIT AttackUnit Attacking unit. We will move away from this.
--- User function for OnBefore "FallBack" event.
-- @function [parent=#SUPPRESSION] OnBeforeFallBack
-- @param #SUPPRESSION self
@ -444,6 +510,15 @@ function SUPPRESSION:New(group)
-- @param Wrapper.Unit#UNIT AttackUnit Attacking unit. We will move away from this.
--- Trigger "Retreat" event.
-- @function [parent=#SUPPRESSION] Retreat
-- @param #SUPPRESSION self
--- Trigger "Retreat" event after a delay.
-- @function [parent=#SUPPRESSION] __Retreat
-- @param #SUPPRESSION self
-- @param #number Delay Delay in seconds.
--- User function for OnBefore "Retreat" event.
-- @function [parent=#SUPPRESSION] OnBeforeRetreat
-- @param #SUPPRESSION self
@ -462,6 +537,15 @@ function SUPPRESSION:New(group)
-- @param #string To To state.
--- Trigger "Retreated" event.
-- @function [parent=#SUPPRESSION] Retreated
-- @param #SUPPRESSION self
--- Trigger "Retreated" event after a delay.
-- @function [parent=#SUPPRESSION] __Retreated
-- @param #SUPPRESSION self
-- @param #number Delay Delay in seconds.
--- User function for OnBefore "Retreated" event.
-- @function [parent=#SUPPRESSION] OnBeforeRetreated
-- @param #SUPPRESSION self
@ -480,6 +564,15 @@ function SUPPRESSION:New(group)
-- @param #string To To state.
--- Trigger "FightBack" event.
-- @function [parent=#SUPPRESSION] FightBack
-- @param #SUPPRESSION self
--- Trigger "FightBack" event after a delay.
-- @function [parent=#SUPPRESSION] __FightBack
-- @param #SUPPRESSION self
-- @param #number Delay Delay in seconds.
--- User function for OnBefore "FlightBack" event.
-- @function [parent=#SUPPRESSION] OnBeforeFightBack
-- @param #SUPPRESSION self
@ -498,6 +591,23 @@ function SUPPRESSION:New(group)
-- @param #string To To state.
--- Trigger "Dead" event.
-- @function [parent=#SUPPRESSION] Dead
-- @param #SUPPRESSION self
--- Trigger "Dead" event after a delay.
-- @function [parent=#SUPPRESSION] __Dead
-- @param #SUPPRESSION self
-- @param #number Delay Delay in seconds.
--- User function for OnAfter "Dead" event.
-- @function [parent=#SUPPRESSION] OnAfterDead
-- @param #SUPPRESSION self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group.
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
return self
end
@ -524,9 +634,9 @@ function SUPPRESSION:SetSuppressionTime(Tave, Tmin, Tmax)
self.Tsuppress_ave=math.max(self.Tsuppress_min)
self.Tsuppress_ave=math.min(self.Tsuppress_max)
self:T(SUPPRESSION.id..string.format("Set ave suppression time to %d seconds.", self.Tsuppress_ave))
self:T(SUPPRESSION.id..string.format("Set min suppression time to %d seconds.", self.Tsuppress_min))
self:T(SUPPRESSION.id..string.format("Set max suppression time to %d seconds.", self.Tsuppress_max))
self:T(self.lid..string.format("Set ave suppression time to %d seconds.", self.Tsuppress_ave))
self:T(self.lid..string.format("Set min suppression time to %d seconds.", self.Tsuppress_min))
self:T(self.lid..string.format("Set max suppression time to %d seconds.", self.Tsuppress_max))
end
--- Set the zone to which a group retreats after being damaged too much.
@ -756,13 +866,14 @@ end
--- Status of group. Current ROE, alarm state, life.
-- @param #SUPPRESSION self
-- @param #boolean message Send message to all players.
function SUPPRESSION:Status(message)
function SUPPRESSION:StatusReport(message)
local name=self.Controllable:GetName()
local nunits=#self.Controllable:GetUnits()
local roe=self.CurrentROE
local state=self.CurrentAlarmState
local life_min, life_max, life_ave, life_ave0, groupstrength=self:_GetLife()
local at=self.Controllable:GetAmmunition()
local text=string.format("Status of group %s\n", name)
text=text..string.format("Number of units: %d of %d\n", nunits, self.IniGroupStrength)
@ -774,10 +885,11 @@ function SUPPRESSION:Status(message)
text=text..string.format("Life max: %3.0f\n", life_max)
text=text..string.format("Life ave: %3.0f\n", life_ave)
text=text..string.format("Life ave0: %3.0f\n", life_ave0)
text=text..string.format("Ammo tot: %d\n", at)
text=text..string.format("Group strength: %3.0f", groupstrength)
MESSAGE:New(text, 10):ToAllIf(message or self.Debug)
self:T(SUPPRESSION.id.."\n"..text)
self:I(self.lid.."\n"..text)
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@ -794,6 +906,7 @@ function SUPPRESSION:onafterStart(Controllable, From, Event, To)
self:_EventFromTo("onafterStart", Event, From, To)
local text=string.format("Started SUPPRESSION for group %s.", Controllable:GetName())
self:I(self.lid..text)
MESSAGE:New(text, 10):ToAllIf(self.Debug)
local rzone="not defined"
@ -854,7 +967,7 @@ function SUPPRESSION:onafterStart(Controllable, From, Event, To)
text=text..string.format("Speed max = %5.1f km/h\n", self.SpeedMax)
text=text..string.format("Formation = %s\n", self.Formation)
text=text..string.format("******************************************************\n")
self:T(SUPPRESSION.id..text)
self:T(self.lid..text)
-- Add event handler.
if self.eventmoose then
@ -863,9 +976,37 @@ function SUPPRESSION:onafterStart(Controllable, From, Event, To)
else
world.addEventHandler(self)
end
self:__Status(-1)
end
--- After "Status" event.
-- @param #SUPPRESSION self
-- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group.
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
function SUPPRESSION:onafterStatus(Controllable, From, Event, To)
--local text=string.format("State=%s, ROE %d, Life=%.1f", Controllable:GetName())
--MESSAGE:New(text, 10):ToAllIf(self.Debug)
local group=self.Controllable --Wrapper.Group#GROUP
local n=group:GetAmmunition()
self:StatusReport(false)
-- Retreat if completely out of ammo and retreat zone defined.
if n==0 and self.RetreatZone then
self:Retreat()
end
self:__Status(-30)
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Before "Hit" event. (Of course, this is not really before the group got hit.)
@ -881,7 +1022,7 @@ function SUPPRESSION:onbeforeHit(Controllable, From, Event, To, Unit, AttackUnit
self:_EventFromTo("onbeforeHit", Event, From, To)
--local Tnow=timer.getTime()
--env.info(SUPPRESSION.id..string.format("Last hit = %s %s", tostring(self.LastHit), tostring(Tnow)))
--env.info(self.lid..string.format("Last hit = %s %s", tostring(self.LastHit), tostring(Tnow)))
return true
end
@ -925,7 +1066,7 @@ function SUPPRESSION:onafterHit(Controllable, From, Event, To, Unit, AttackUnit)
text=string.format("\nGroup %s: Life min=%5.1f, max=%5.1f, ave=%5.1f, ave0=%5.1f group=%5.1f\n", Controllable:GetName(), life_min, life_max, life_ave, life_ave0, groupstrength)
text=string.format("Group %s: Damage = %8.4f (%8.4f retreat threshold).\n", Controllable:GetName(), Damage, self.RetreatDamage)
text=string.format("Group %s: P_Flee = %5.1f %5.1f=P_rand (P_Flee > Prand ==> Flee)\n", Controllable:GetName(), Pflee, P)
self:T(SUPPRESSION.id..text)
self:T(self.lid..text)
-- Group is obviously destroyed.
if Damage >= 99.9 then
@ -957,11 +1098,6 @@ function SUPPRESSION:onafterHit(Controllable, From, Event, To, Unit, AttackUnit)
end
end
-- Give info on current status.
if self.Debug then
self:Status()
end
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@ -980,7 +1116,7 @@ function SUPPRESSION:onbeforeRecovered(Controllable, From, Event, To)
local Tnow=timer.getTime()
-- Debug info
self:T(SUPPRESSION.id..string.format("onbeforeRecovered: Time now: %d - Time over: %d", Tnow, self.TsuppressionOver))
self:T(self.lid..string.format("onbeforeRecovered: Time now: %d - Time over: %d", Tnow, self.TsuppressionOver))
-- Recovery is only possible if enough time since the last hit has passed.
if Tnow >= self.TsuppressionOver then
@ -1005,7 +1141,7 @@ function SUPPRESSION:onafterRecovered(Controllable, From, Event, To)
-- Debug message.
local text=string.format("Group %s has recovered!", Controllable:GetName())
MESSAGE:New(text, 10):ToAllIf(self.Debug)
self:T(SUPPRESSION.id..text)
self:T(self.lid..text)
-- Set ROE back to default.
self:_SetROE()
@ -1066,7 +1202,7 @@ function SUPPRESSION:onafterFallBack(Controllable, From, Event, To, AttackUnit)
self:_EventFromTo("onafterFallback", Event, From, To)
-- Debug info
self:T(SUPPRESSION.id..string.format("Group %s is falling back after %d hits.", Controllable:GetName(), self.Nhit))
self:T(self.lid..string.format("Group %s is falling back after %d hits.", Controllable:GetName(), self.Nhit))
-- Coordinate of the attacker and attacked unit.
local ACoord=AttackUnit:GetCoordinate()
@ -1174,8 +1310,8 @@ function SUPPRESSION:onbeforeRetreat(Controllable, From, Event, To)
self:_EventFromTo("onbeforeRetreat", Event, From, To)
if From=="Retreating" then
local text=string.format("Group %s is already retreating.")
self:T2(SUPPRESSION.id..text)
local text=string.format("Group %s is already retreating.", tostring(Controllable:GetName()))
self:T2(self.lid..text)
return false
else
return true
@ -1195,7 +1331,7 @@ function SUPPRESSION:onafterRetreat(Controllable, From, Event, To)
-- Route the group to a zone.
local text=string.format("Group %s is retreating! Alarm state green.", Controllable:GetName())
MESSAGE:New(text, 10):ToAllIf(self.Debug)
self:T(SUPPRESSION.id..text)
self:T(self.lid..text)
-- Get a random point in the retreat zone.
local ZoneCoord=self.RetreatZone:GetRandomCoordinate() -- Core.Point#COORDINATE
@ -1272,11 +1408,11 @@ function SUPPRESSION:onafterDead(Controllable, From, Event, To)
local text=string.format("Group %s: One of our units just died! %d units left.", self.Controllable:GetName(), nunits)
MESSAGE:New(text, 10):ToAllIf(self.Debug)
self:T(SUPPRESSION.id..text)
self:T(self.lid..text)
-- Go to stop state.
if nunits==0 then
self:T(SUPPRESSION.id..string.format("Stopping SUPPRESSION for group %s.", Controllable:GetName()))
self:T(self.lid..string.format("Stopping SUPPRESSION for group %s.", Controllable:GetName()))
self:Stop()
if self.mooseevents then
self:UnHandleEvent(EVENTS.Dead)
@ -1348,7 +1484,7 @@ function SUPPRESSION:_OnEventHit(EventData)
-- Check that correct group was hit.
if GroupNameTgt == GroupNameSelf then
self:T(SUPPRESSION.id..string.format("Hit event at t = %5.1f", timer.getTime()))
self:T(self.lid..string.format("Hit event at t = %5.1f", timer.getTime()))
-- Flare unit that was hit.
if self.flare or self.Debug then
@ -1359,11 +1495,11 @@ function SUPPRESSION:_OnEventHit(EventData)
self.Nhit=self.Nhit+1
-- Info on hit times.
self:T(SUPPRESSION.id..string.format("Group %s has just been hit %d times.", self.Controllable:GetName(), self.Nhit))
self:T(self.lid..string.format("Group %s has just been hit %d times.", self.Controllable:GetName(), self.Nhit))
--self:Status()
local life=tgt:getLife()/(tgt:getLife0()+1)*100
self:T2(SUPPRESSION.id..string.format("Target unit life = %5.1f", life))
self:T2(self.lid..string.format("Target unit life = %5.1f", life))
-- FSM Hit event.
self:__Hit(3, TgtUnit, IniUnit)
@ -1380,35 +1516,35 @@ function SUPPRESSION:_OnEventDead(EventData)
local GroupNameIni=EventData.IniGroupName
-- Check for correct group.
if GroupNameIni== GroupNameSelf then
if GroupNameIni==GroupNameSelf then
-- Dead Unit.
local IniUnit=EventData.IniUnit --Wrapper.Unit#UNIT
local IniUnitName=EventData.IniUnitName
if EventData.IniUnit then
self:T2(SUPPRESSION.id..string.format("Group %s: Dead MOOSE unit DOES exist! Unit name %s.", GroupNameIni, IniUnitName))
self:T2(self.lid..string.format("Group %s: Dead MOOSE unit DOES exist! Unit name %s.", GroupNameIni, IniUnitName))
else
self:T2(SUPPRESSION.id..string.format("Group %s: Dead MOOSE unit DOES NOT not exist! Unit name %s.", GroupNameIni, IniUnitName))
self:T2(self.lid..string.format("Group %s: Dead MOOSE unit DOES NOT not exist! Unit name %s.", GroupNameIni, IniUnitName))
end
if EventData.IniDCSUnit then
self:T2(SUPPRESSION.id..string.format("Group %s: Dead DCS unit DOES exist! Unit name %s.", GroupNameIni, IniUnitName))
self:T2(self.lid..string.format("Group %s: Dead DCS unit DOES exist! Unit name %s.", GroupNameIni, IniUnitName))
else
self:T2(SUPPRESSION.id..string.format("Group %s: Dead DCS unit DOES NOT exist! Unit name %s.", GroupNameIni, IniUnitName))
self:T2(self.lid..string.format("Group %s: Dead DCS unit DOES NOT exist! Unit name %s.", GroupNameIni, IniUnitName))
end
-- Flare unit that died.
if IniUnit and (self.flare or self.Debug) then
IniUnit:FlareWhite()
self:T(SUPPRESSION.id..string.format("Flare Dead MOOSE unit."))
self:T(self.lid..string.format("Flare Dead MOOSE unit."))
end
-- Flare unit that died.
if EventData.IniDCSUnit and (self.flare or self.Debug) then
local p=EventData.IniDCSUnit:getPosition().p
trigger.action.signalFlare(p, trigger.flareColor.Yellow , 0)
self:T(SUPPRESSION.id..string.format("Flare Dead DCS unit."))
self:T(self.lid..string.format("Flare Dead DCS unit."))
end
-- Get status.
@ -1462,7 +1598,7 @@ function SUPPRESSION:_Suppress()
-- Debug message.
local text=string.format("Group %s is suppressed for %d seconds. Suppression ends at %d:%02d.", Controllable:GetName(), Tsuppress, self.TsuppressionOver/60, self.TsuppressionOver%60)
MESSAGE:New(text, 10):ToAllIf(self.Debug)
self:T(SUPPRESSION.id..text)
self:T(self.lid..text)
end
@ -1520,7 +1656,7 @@ function SUPPRESSION:_Run(fin, speed, formation, wait)
local MarkerID=ini:MarkToAll(string.format("Waypoing %d of group %s (initial)", #wp, self.Controllable:GetName()))
end
self:T2(SUPPRESSION.id..string.format("Number of waypoints %d", nx))
self:T2(self.lid..string.format("Number of waypoints %d", nx))
for i=1,nx-2 do
local x=dx*i
@ -1529,13 +1665,13 @@ function SUPPRESSION:_Run(fin, speed, formation, wait)
wp[#wp+1]=coord:WaypointGround(speed, formation)
tasks[#tasks+1]=group:TaskFunction("SUPPRESSION._Passing_Waypoint", self, #wp, false)
self:T2(SUPPRESSION.id..string.format("%d x = %4.1f", i, x))
self:T2(self.lid..string.format("%d x = %4.1f", i, x))
if self.Debug then
local MarkerID=coord:MarkToAll(string.format("Waypoing %d of group %s", #wp, self.Controllable:GetName()))
end
end
self:T2(SUPPRESSION.id..string.format("Total distance: %4.1f", dist))
self:T2(self.lid..string.format("Total distance: %4.1f", dist))
-- Final waypoint.
wp[#wp+1]=fin:WaypointGround(speed, formation)
@ -1584,7 +1720,7 @@ function SUPPRESSION._Passing_Waypoint(group, Fsm, i, final)
local text=string.format("Group %s passing waypoint %d (final=%s)", group:GetName(), i, tostring(final))
MESSAGE:New(text,10):ToAllIf(Fsm.Debug)
if Fsm.Debug then
env.info(SUPPRESSION.id..text)
env.info(self.lid..text)
end
if final then
@ -1629,7 +1765,7 @@ function SUPPRESSION:_SearchHideout()
-- Place markers on every possible scenery object.
local MarkerID=SceneryObject:GetCoordinate():MarkToAll(string.format("%s scenery object %s", self.Controllable:GetName(),SceneryObject:GetTypeName()))
local text=string.format("%s scenery: %s, Coord %s", self.Controllable:GetName(), SceneryObject:GetTypeName(), SceneryObject:GetCoordinate():ToStringLLDMS())
self:T2(SUPPRESSION.id..text)
self:T2(self.lid..text)
end
-- Add to table.
@ -1642,7 +1778,7 @@ function SUPPRESSION:_SearchHideout()
if #hideouts>0 then
-- Debug info.
self:T(SUPPRESSION.id.."Number of hideouts "..#hideouts)
self:T(self.lid.."Number of hideouts "..#hideouts)
-- Sort results table wrt number of hits.
local _sort = function(a,b) return a.distance < b.distance end
@ -1655,7 +1791,7 @@ function SUPPRESSION:_SearchHideout()
Hideout=hideouts[1].object:GetCoordinate()
else
self:E(SUPPRESSION.id.."No hideouts found!")
self:E(self.lid.."No hideouts found!")
end
return Hideout
@ -1685,7 +1821,7 @@ function SUPPRESSION:_GetLife()
local groupstrength=#units/self.IniGroupStrength*100
self.T2(SUPPRESSION.id..string.format("Group %s _GetLife nunits = %d", self.Controllable:GetName(), #units))
self.T2(self.lid..string.format("Group %s _GetLife nunits = %d", self.Controllable:GetName(), #units))
for _,unit in pairs(units) do
@ -1702,7 +1838,7 @@ function SUPPRESSION:_GetLife()
life_ave=life_ave+life
if self.Debug then
local text=string.format("n=%02d: Life = %3.1f, Life0 = %3.1f, min=%3.1f, max=%3.1f, ave=%3.1f, group=%3.1f", n, unit:GetLife(), unit:GetLife0(), life_min, life_max, life_ave/n,groupstrength)
self:T2(SUPPRESSION.id..text)
self:T2(self.lid..text)
end
end
@ -1795,13 +1931,13 @@ function SUPPRESSION:_SetROE(roe)
elseif roe==SUPPRESSION.ROE.Return then
group:OptionROEReturnFire()
else
self:E(SUPPRESSION.id.."Unknown ROE requested: "..tostring(roe))
self:E(self.lid.."Unknown ROE requested: "..tostring(roe))
group:OptionROEOpenFire()
self.CurrentROE=SUPPRESSION.ROE.Free
end
local text=string.format("Group %s now has ROE %s.", self.Controllable:GetName(), self.CurrentROE)
self:T(SUPPRESSION.id..text)
self:T(self.lid..text)
end
--- Sets the alarm state of the group and updates the current alarm state variable.
@ -1824,13 +1960,13 @@ function SUPPRESSION:_SetAlarmState(state)
elseif state==SUPPRESSION.AlarmState.Red then
group:OptionAlarmStateRed()
else
self:E(SUPPRESSION.id.."Unknown alarm state requested: "..tostring(state))
self:E(self.lid.."Unknown alarm state requested: "..tostring(state))
group:OptionAlarmStateAuto()
self.CurrentAlarmState=SUPPRESSION.AlarmState.Auto
end
local text=string.format("Group %s now has Alarm State %s.", self.Controllable:GetName(), self.CurrentAlarmState)
self:T(SUPPRESSION.id..text)
self:T(self.lid..text)
end
--- Print event-from-to string to DCS log file.
@ -1841,7 +1977,7 @@ end
-- @param #string To To state.
function SUPPRESSION:_EventFromTo(BA, Event, From, To)
local text=string.format("\n%s: %s EVENT %s: %s --> %s", BA, self.Controllable:GetName(), Event, From, To)
self:T2(SUPPRESSION.id..text)
self:T2(self.lid..text)
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

View File

@ -1046,6 +1046,45 @@ function GROUP:GetFuel()
end
--- Get the number of shells, rockets, bombs and missiles the whole group currently has.
-- @param #GROUP self
-- @return #number Total amount of ammo the group has left. This is the sum of shells, rockets, bombs and missiles of all units.
-- @return #number Number of shells left.
-- @return #number Number of rockets left.
-- @return #number Number of bombs left.
-- @return #number Number of missiles left.
function GROUP:GetAmmunition()
self:F( self.ControllableName )
local DCSControllable = self:GetDCSObject()
local Ntot=0
local Nshells=0
local Nrockets=0
local Nmissiles=0
if DCSControllable then
-- Loop over units.
for UnitID, UnitData in pairs( self:GetUnits() ) do
local Unit = UnitData -- Wrapper.Unit#UNIT
-- Get ammo of the unit
local ntot, nshells, nrockets, nmissiles = Unit:GetAmmunition()
Ntot=Ntot+ntot
Nshells=Nshells+nshells
Nrockets=Nrockets+nrockets
Nmissiles=Nmissiles+nmissiles
end
end
return Ntot, Nshells, Nrockets, Nmissiles
end
do -- Is Zone methods
--- Returns true if all units of the group are within a @{Zone}.

View File

@ -493,6 +493,94 @@ function UNIT:GetAmmo()
return nil
end
--- Get the number of ammunition and in particular the number of shells, rockets, bombs and missiles a unit currently has.
-- @param #UNIT self
-- @return #number Total amount of ammo the unit has left. This is the sum of shells, rockets, bombs and missiles.
-- @return #number Number of shells left.
-- @return #number Number of rockets left.
-- @return #number Number of bombs left.
-- @return #number Number of missiles left.
function UNIT:GetAmmunition()
-- Init counter.
local nammo=0
local nshells=0
local nrockets=0
local nmissiles=0
local nbombs=0
local unit=self
-- Get ammo table.
local ammotable=unit:GetAmmo()
if ammotable then
local weapons=#ammotable
-- Loop over all weapons.
for w=1,weapons do
-- Number of current weapon.
local Nammo=ammotable[w]["count"]
-- Type name of current weapon.
local Tammo=ammotable[w]["desc"]["typeName"]
local _weaponString = UTILS.Split(Tammo,"%.")
local _weaponName = _weaponString[#_weaponString]
-- Get the weapon category: shell=0, missile=1, rocket=2, bomb=3
local Category=ammotable[w].desc.category
-- Get missile category: Weapon.MissileCategory AAM=1, SAM=2, BM=3, ANTI_SHIP=4, CRUISE=5, OTHER=6
local MissileCategory=nil
if Category==Weapon.Category.MISSILE then
MissileCategory=ammotable[w].desc.missileCategory
end
-- We are specifically looking for shells or rockets here.
if Category==Weapon.Category.SHELL then
-- Add up all shells.
nshells=nshells+Nammo
elseif Category==Weapon.Category.ROCKET then
-- Add up all rockets.
nrockets=nrockets+Nammo
elseif Category==Weapon.Category.BOMB then
-- Add up all rockets.
nbombs=nbombs+Nammo
elseif Category==Weapon.Category.MISSILE then
-- Add up all cruise missiles (category 5)
if MissileCategory==Weapon.MissileCategory.AAM then
nmissiles=nmissiles+Nammo
elseif MissileCategory==Weapon.MissileCategory.ANTI_SHIP then
nmissiles=nmissiles+Nammo
elseif MissileCategory==Weapon.MissileCategory.BM then
nmissiles=nmissiles+Nammo
elseif MissileCategory==Weapon.MissileCategory.OTHER then
nmissiles=nmissiles+Nammo
end
end
end
end
-- Total amount of ammunition.
nammo=nshells+nrockets+nmissiles+nbombs
return nammo, nshells, nrockets, nbombs, nmissiles
end
--- Returns the unit sensors.
-- @param #UNIT self
-- @return DCS#Unit.Sensors