Merge branch 'FF/Develop' into FF/Fox2

This commit is contained in:
Frank
2019-09-13 19:43:17 +02:00
43 changed files with 2059 additions and 1079 deletions
+11 -11
View File
@@ -433,7 +433,7 @@ function AI_A2A:onafterStatus()
self:F({DistanceFromHomeBase=DistanceFromHomeBase}) self:F({DistanceFromHomeBase=DistanceFromHomeBase})
if DistanceFromHomeBase > self.DisengageRadius then if DistanceFromHomeBase > self.DisengageRadius then
self:E( self.Controllable:GetName() .. " is too far from home base, RTB!" ) self:I( self.Controllable:GetName() .. " is too far from home base, RTB!" )
self:Hold( 300 ) self:Hold( 300 )
RTB = false RTB = false
end end
@@ -453,10 +453,10 @@ function AI_A2A:onafterStatus()
self:F({Fuel=Fuel, PatrolFuelThresholdPercentage=self.PatrolFuelThresholdPercentage}) self:F({Fuel=Fuel, PatrolFuelThresholdPercentage=self.PatrolFuelThresholdPercentage})
if Fuel < self.PatrolFuelThresholdPercentage then if Fuel < self.PatrolFuelThresholdPercentage then
if self.TankerName then if self.TankerName then
self:E( self.Controllable:GetName() .. " is out of fuel: " .. Fuel .. " ... Refuelling at Tanker!" ) self:I( self.Controllable:GetName() .. " is out of fuel: " .. Fuel .. " ... Refuelling at Tanker!" )
self:Refuel() self:Refuel()
else else
self:E( self.Controllable:GetName() .. " is out of fuel: " .. Fuel .. " ... RTB!" ) self:I( self.Controllable:GetName() .. " is out of fuel: " .. Fuel .. " ... RTB!" )
local OldAIControllable = self.Controllable local OldAIControllable = self.Controllable
local OrbitTask = OldAIControllable:TaskOrbitCircle( math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ), self.PatrolMinSpeed ) local OrbitTask = OldAIControllable:TaskOrbitCircle( math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ), self.PatrolMinSpeed )
@@ -475,7 +475,7 @@ function AI_A2A:onafterStatus()
local InitialLife = self.Controllable:GetLife0() local InitialLife = self.Controllable:GetLife0()
self:F( { Damage = Damage, InitialLife = InitialLife, DamageThreshold = self.PatrolDamageThreshold } ) self:F( { Damage = Damage, InitialLife = InitialLife, DamageThreshold = self.PatrolDamageThreshold } )
if ( Damage / InitialLife ) < self.PatrolDamageThreshold then if ( Damage / InitialLife ) < self.PatrolDamageThreshold then
self:E( self.Controllable:GetName() .. " is damaged: " .. Damage .. " ... RTB!" ) self:I( self.Controllable:GetName() .. " is damaged: " .. Damage .. " ... RTB!" )
self:Damaged() self:Damaged()
RTB = true RTB = true
self:SetStatusOff() self:SetStatusOff()
@@ -493,7 +493,7 @@ function AI_A2A:onafterStatus()
if Damage ~= InitialLife then if Damage ~= InitialLife then
self:Damaged() self:Damaged()
else else
self:E( self.Controllable:GetName() .. " control lost! " ) self:I( self.Controllable:GetName() .. " control lost! " )
self:LostControl() self:LostControl()
end end
else else
@@ -549,7 +549,7 @@ function AI_A2A:onafterRTB( AIGroup, From, Event, To )
if AIGroup and AIGroup:IsAlive() then if AIGroup and AIGroup:IsAlive() then
self:E( "Group " .. AIGroup:GetName() .. " ... RTB! ( " .. self:GetState() .. " )" ) self:I( "Group " .. AIGroup:GetName() .. " ... RTB! ( " .. self:GetState() .. " )" )
self:ClearTargetDistance() self:ClearTargetDistance()
AIGroup:ClearTasks() AIGroup:ClearTasks()
@@ -567,7 +567,7 @@ function AI_A2A:onafterRTB( AIGroup, From, Event, To )
local ToAirbaseCoord = CurrentCoord:Translate( 5000, ToAirbaseAngle ) local ToAirbaseCoord = CurrentCoord:Translate( 5000, ToAirbaseAngle )
if Distance < 5000 then if Distance < 5000 then
self:E( "RTB and near the airbase!" ) self:I( "RTB and near the airbase!" )
self:Home() self:Home()
return return
end end
@@ -608,7 +608,7 @@ end
function AI_A2A:onafterHome( AIGroup, From, Event, To ) function AI_A2A:onafterHome( AIGroup, From, Event, To )
self:F( { AIGroup, From, Event, To } ) self:F( { AIGroup, From, Event, To } )
self:E( "Group " .. self.Controllable:GetName() .. " ... Home! ( " .. self:GetState() .. " )" ) self:I( "Group " .. self.Controllable:GetName() .. " ... Home! ( " .. self:GetState() .. " )" )
if AIGroup and AIGroup:IsAlive() then if AIGroup and AIGroup:IsAlive() then
end end
@@ -622,7 +622,7 @@ end
function AI_A2A:onafterHold( AIGroup, From, Event, To, HoldTime ) function AI_A2A:onafterHold( AIGroup, From, Event, To, HoldTime )
self:F( { AIGroup, From, Event, To } ) self:F( { AIGroup, From, Event, To } )
self:E( "Group " .. self.Controllable:GetName() .. " ... Holding! ( " .. self:GetState() .. " )" ) self:I( "Group " .. self.Controllable:GetName() .. " ... Holding! ( " .. self:GetState() .. " )" )
if AIGroup and AIGroup:IsAlive() then if AIGroup and AIGroup:IsAlive() then
local OrbitTask = AIGroup:TaskOrbitCircle( math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ), self.PatrolMinSpeed ) local OrbitTask = AIGroup:TaskOrbitCircle( math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ), self.PatrolMinSpeed )
@@ -654,7 +654,7 @@ end
function AI_A2A:onafterRefuel( AIGroup, From, Event, To ) function AI_A2A:onafterRefuel( AIGroup, From, Event, To )
self:F( { AIGroup, From, Event, To } ) self:F( { AIGroup, From, Event, To } )
self:E( "Group " .. self.Controllable:GetName() .. " ... Refuelling! ( " .. self:GetState() .. " )" ) self:I( "Group " .. self.Controllable:GetName() .. " ... Refuelling! ( " .. self:GetState() .. " )" )
if AIGroup and AIGroup:IsAlive() then if AIGroup and AIGroup:IsAlive() then
local Tanker = GROUP:FindByName( self.TankerName ) local Tanker = GROUP:FindByName( self.TankerName )
@@ -711,7 +711,7 @@ end
function AI_A2A:OnCrash( EventData ) function AI_A2A:OnCrash( EventData )
if self.Controllable:IsAlive() and EventData.IniDCSGroupName == self.Controllable:GetName() then if self.Controllable:IsAlive() and EventData.IniDCSGroupName == self.Controllable:GetName() then
self:E( self.Controllable:GetUnits() ) self:I( self.Controllable:GetUnits() )
if #self.Controllable:GetUnits() == 1 then if #self.Controllable:GetUnits() == 1 then
self:__Crash( 1, EventData ) self:__Crash( 1, EventData )
end end
@@ -278,6 +278,8 @@ function AI_A2A_CAP:New( AICap, PatrolZone, PatrolFloorAltitude, PatrolCeilingAl
-- @param #AI_A2A_CAP self -- @param #AI_A2A_CAP self
-- @param #number Delay The delay in seconds. -- @param #number Delay The delay in seconds.
self:AddTransition( { "Patrolling", "Engaging" }, "Refuel", "Refuelling" )
return self return self
end end
@@ -279,6 +279,8 @@ function AI_A2A_GCI:New( AIIntercept, EngageMinSpeed, EngageMaxSpeed )
-- @param #AI_A2A_GCI self -- @param #AI_A2A_GCI self
-- @param #number Delay The delay in seconds. -- @param #number Delay The delay in seconds.
self:AddTransition( { "Patrolling", "Engaging" }, "Refuel", "Refuelling" )
return self return self
end end
+16 -3
View File
@@ -62,12 +62,14 @@ end
-- @param #string To The To State string. -- @param #string To The To State string.
function AI_A2G_BAI:onafterEngage( DefenderGroup, From, Event, To, AttackSetUnit ) function AI_A2G_BAI:onafterEngage( DefenderGroup, From, Event, To, AttackSetUnit )
self:F( { DefenderGroup, From, Event, To, AttackSetUnit} ) self:I( { DefenderGroup, From, Event, To, AttackSetUnit} )
local DefenderGroupName = DefenderGroup:GetName() local DefenderGroupName = DefenderGroup:GetName()
local AttackCount = AttackSetUnit:Count() local AttackCount = AttackSetUnit:Count()
self.AttackSetUnit = AttackSetUnit -- Kept in memory in case of resume from refuel in air!
if AttackCount > 0 then if AttackCount > 0 then
if DefenderGroup:IsAlive() then if DefenderGroup:IsAlive() then
@@ -129,7 +131,7 @@ function AI_A2G_BAI:onafterEngage( DefenderGroup, From, Event, To, AttackSetUnit
end end
if #AttackUnitTasks == 0 then if #AttackUnitTasks == 0 then
self:E( DefenderGroupName .. ": No targets found -> Going RTB") self:I( DefenderGroupName .. ": No targets found -> Going RTB")
self:Return() self:Return()
self:__RTB( self.TaskDelay ) self:__RTB( self.TaskDelay )
else else
@@ -146,8 +148,19 @@ function AI_A2G_BAI:onafterEngage( DefenderGroup, From, Event, To, AttackSetUnit
DefenderGroup:Route( EngageRoute, self.TaskDelay ) DefenderGroup:Route( EngageRoute, self.TaskDelay )
end end
else else
self:E( DefenderGroupName .. ": No targets found -> Going RTB") self:I( DefenderGroupName .. ": No targets found -> Going RTB")
self:Return() self:Return()
self:__RTB( self.TaskDelay ) self:__RTB( self.TaskDelay )
end end
end end
--- @param Wrapper.Group#GROUP AIEngage
function AI_A2G_BAI.Resume( AIEngage, Fsm )
AIEngage:F( { "AI_A2G_BAI.Resume:", AIEngage:GetName() } )
if AIEngage:IsAlive() then
Fsm:__Reset( Fsm.TaskDelay )
Fsm:__EngageRoute( Fsm.TaskDelay, Fsm.AttackSetUnit )
end
end
+14 -2
View File
@@ -66,6 +66,8 @@ function AI_A2G_CAS:onafterEngage( DefenderGroup, From, Event, To, AttackSetUnit
local DefenderGroupName = DefenderGroup:GetName() local DefenderGroupName = DefenderGroup:GetName()
self.AttackSetUnit = AttackSetUnit -- Kept in memory in case of resume from refuel in air!
local AttackCount = AttackSetUnit:Count() local AttackCount = AttackSetUnit:Count()
if AttackCount > 0 then if AttackCount > 0 then
@@ -124,7 +126,7 @@ function AI_A2G_CAS:onafterEngage( DefenderGroup, From, Event, To, AttackSetUnit
if #AttackUnitTasks == 0 then if #AttackUnitTasks == 0 then
self:E( DefenderGroupName .. ": No targets found -> Going RTB") self:I( DefenderGroupName .. ": No targets found -> Going RTB")
self:Return() self:Return()
self:__RTB( self.TaskDelay ) self:__RTB( self.TaskDelay )
else else
@@ -142,9 +144,19 @@ function AI_A2G_CAS:onafterEngage( DefenderGroup, From, Event, To, AttackSetUnit
DefenderGroup:Route( EngageRoute, self.TaskDelay ) DefenderGroup:Route( EngageRoute, self.TaskDelay )
end end
else else
self:E( DefenderGroupName .. ": No targets found -> Going RTB") self:I( DefenderGroupName .. ": No targets found -> Going RTB")
self:Return() self:Return()
self:__RTB( self.TaskDelay ) self:__RTB( self.TaskDelay )
end end
end end
--- @param Wrapper.Group#GROUP AIEngage
function AI_A2G_CAS.Resume( AIEngage, Fsm )
AIEngage:F( { "AI_A2G_CAS.Resume:", AIEngage:GetName() } )
if AIEngage:IsAlive() then
Fsm:__Reset( Fsm.TaskDelay )
Fsm:__EngageRoute( Fsm.TaskDelay, Fsm.AttackSetUnit )
end
end
+284 -129
View File
@@ -3496,91 +3496,123 @@ do -- AI_A2G_DISPATCHER
local AI_A2G_PATROL = { SEAD = AI_A2G_SEAD, BAI = AI_A2G_BAI, CAS = AI_A2G_CAS } local AI_A2G_PATROL = { SEAD = AI_A2G_SEAD, BAI = AI_A2G_BAI, CAS = AI_A2G_CAS }
local Fsm = AI_A2G_PATROL[DefenseTaskType]:New( DefenderGroup, Patrol.EngageMinSpeed, Patrol.EngageMaxSpeed, Patrol.EngageFloorAltitude, Patrol.EngageCeilingAltitude, Patrol.Zone, Patrol.PatrolFloorAltitude, Patrol.PatrolCeilingAltitude, Patrol.PatrolMinSpeed, Patrol.PatrolMaxSpeed, Patrol.AltType ) local AI_A2G_Fsm = AI_A2G_PATROL[DefenseTaskType]:New( DefenderGroup, Patrol.EngageMinSpeed, Patrol.EngageMaxSpeed, Patrol.EngageFloorAltitude, Patrol.EngageCeilingAltitude, Patrol.Zone, Patrol.PatrolFloorAltitude, Patrol.PatrolCeilingAltitude, Patrol.PatrolMinSpeed, Patrol.PatrolMaxSpeed, Patrol.AltType )
Fsm:SetDispatcher( self ) AI_A2G_Fsm:SetDispatcher( self )
Fsm:SetHomeAirbase( DefenderSquadron.Airbase ) AI_A2G_Fsm:SetHomeAirbase( DefenderSquadron.Airbase )
Fsm:SetFuelThreshold( DefenderSquadron.FuelThreshold or self.DefenderDefault.FuelThreshold, 60 ) AI_A2G_Fsm:SetFuelThreshold( DefenderSquadron.FuelThreshold or self.DefenderDefault.FuelThreshold, 60 )
Fsm:SetDamageThreshold( self.DefenderDefault.DamageThreshold ) AI_A2G_Fsm:SetDamageThreshold( self.DefenderDefault.DamageThreshold )
Fsm:SetDisengageRadius( self.DisengageRadius ) AI_A2G_Fsm:SetDisengageRadius( self.DisengageRadius )
Fsm:SetTanker( DefenderSquadron.TankerName or self.DefenderDefault.TankerName ) AI_A2G_Fsm:SetTanker( DefenderSquadron.TankerName or self.DefenderDefault.TankerName )
Fsm:Start() AI_A2G_Fsm:Start()
self:SetDefenderTask( SquadronName, DefenderGroup, DefenseTaskType, Fsm, nil, DefenderGrouping ) self:SetDefenderTask( SquadronName, DefenderGroup, DefenseTaskType, AI_A2G_Fsm, nil, DefenderGrouping )
function Fsm:onafterTakeoff( Defender, From, Event, To ) function AI_A2G_Fsm:onafterTakeoff( DefenderGroup, From, Event, To )
self:F({"Defender Birth", Defender:GetName()}) self:F({"Defender Takeoff", DefenderGroup:GetName()})
--self:GetParent(self).onafterBirth( self, Defender, From, Event, To ) --self:GetParent(self).onafterBirth( self, Defender, From, Event, To )
local DefenderName = Defender:GetCallsign() local DefenderName = DefenderGroup:GetCallsign()
local Dispatcher = Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER local Dispatcher = AI_A2G_Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER
local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup )
if Squadron then if Squadron then
Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " airborne." ) Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " airborne.", "Wheels_up.wav", 3, "A2G/", DefenderGroup )
Fsm:Patrol() -- Engage on the TargetSetUnit AI_A2G_Fsm:Patrol() -- Engage on the TargetSetUnit
end end
end end
function Fsm:onafterPatrolRoute( Defender, From, Event, To ) function AI_A2G_Fsm:onafterPatrolRoute( DefenderGroup, From, Event, To )
self:F({"Defender PatrolRoute", Defender:GetName()}) self:F({"Defender PatrolRoute", DefenderGroup:GetName()})
self:GetParent(self).onafterPatrolRoute( self, Defender, From, Event, To ) self:GetParent(self).onafterPatrolRoute( self, DefenderGroup, From, Event, To )
local DefenderName = Defender:GetCallsign() local DefenderName = DefenderGroup:GetCallsign()
local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER
local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup )
if Squadron then if Squadron then
Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " patrolling." ) Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " patrolling.", "Patrolling.wav", 3, "A2G/", DefenderGroup )
end end
Dispatcher:ClearDefenderTaskTarget( Defender ) Dispatcher:ClearDefenderTaskTarget( DefenderGroup )
end end
function Fsm:onafterRTB( Defender, From, Event, To ) function AI_A2G_Fsm:onafterEngageRoute( DefenderGroup, From, Event, To, AttackSetUnit )
self:F({"Defender RTB", Defender:GetName()}) self:F({"Engage Route", DefenderGroup:GetName()})
self:GetParent(self).onafterRTB( self, Defender, From, Event, To )
local DefenderName = Defender:GetCallsign() self:GetParent(self).onafterEngageRoute( self, DefenderGroup, From, Event, To, AttackSetUnit )
local DefenderName = DefenderGroup:GetCallsign()
local Dispatcher = AI_A2G_Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER
local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup )
if Squadron then
local FirstUnit = AttackSetUnit:GetFirst()
local Coordinate = FirstUnit:GetCoordinate() -- Core.Point#COORDINATE
Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " on route to ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), "Moving_on_to_ground_target.wav", 3, "A2G/", DefenderGroup )
end
end
function AI_A2G_Fsm:OnAfterEngage( DefenderGroup, From, Event, To, AttackSetUnit )
self:F({"Engage Route", DefenderGroup:GetName()})
--self:GetParent(self).onafterBirth( self, Defender, From, Event, To )
local DefenderName = DefenderGroup:GetCallsign()
local Dispatcher = AI_A2G_Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER
local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup )
local FirstUnit = AttackSetUnit:GetFirst()
if FirstUnit then
local Coordinate = FirstUnit:GetCoordinate()
Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " engaging ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), "Engaging_ground_target.wav", 3, "A2G/", DefenderGroup )
end
end
function AI_A2G_Fsm:onafterRTB( DefenderGroup, From, Event, To )
self:F({"Defender RTB", DefenderGroup:GetName()})
self:GetParent(self).onafterRTB( self, DefenderGroup, From, Event, To )
local DefenderName = DefenderGroup:GetCallsign()
local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER
local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup )
Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " returning." ) Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " returning.", "Returning_to_base.wav", 3, "A2G/", DefenderGroup )
Dispatcher:ClearDefenderTaskTarget( Defender ) Dispatcher:ClearDefenderTaskTarget( DefenderGroup )
end end
--- @param #AI_A2G_DISPATCHER self --- @param #AI_A2G_DISPATCHER self
function Fsm:onafterLostControl( Defender, From, Event, To ) function AI_A2G_Fsm:onafterLostControl( DefenderGroup, From, Event, To )
self:F({"Defender LostControl", Defender:GetName()}) self:F({"Defender LostControl", DefenderGroup:GetName()})
self:GetParent(self).onafterHome( self, Defender, From, Event, To ) self:GetParent(self).onafterHome( self, DefenderGroup, From, Event, To )
local DefenderName = Defender:GetCallsign() local DefenderName = DefenderGroup:GetCallsign()
local Dispatcher = Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER local Dispatcher = AI_A2G_Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER
local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup )
Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " lost control." ) Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " lost control." )
if Defender:IsAboveRunway() then if DefenderGroup:IsAboveRunway() then
Dispatcher:RemoveDefenderFromSquadron( Squadron, Defender ) Dispatcher:RemoveDefenderFromSquadron( Squadron, DefenderGroup )
Defender:Destroy() DefenderGroup:Destroy()
end end
end end
--- @param #AI_A2G_DISPATCHER self --- @param #AI_A2G_DISPATCHER self
function Fsm:onafterHome( Defender, From, Event, To, Action ) function AI_A2G_Fsm:onafterHome( DefenderGroup, From, Event, To, Action )
self:F({"Defender Home", Defender:GetName()}) self:F({"Defender Home", DefenderGroup:GetName()})
self:GetParent(self).onafterHome( self, Defender, From, Event, To ) self:GetParent(self).onafterHome( self, DefenderGroup, From, Event, To )
local DefenderName = Defender:GetCallsign() local DefenderName = DefenderGroup:GetCallsign()
local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER
local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup )
Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " landing." ) Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " landing.", "Landing_at_base.wav", 3, "A2G/", DefenderGroup )
if Action and Action == "Destroy" then if Action and Action == "Destroy" then
Dispatcher:RemoveDefenderFromSquadron( Squadron, Defender ) Dispatcher:RemoveDefenderFromSquadron( Squadron, DefenderGroup )
Defender:Destroy() DefenderGroup:Destroy()
end end
if Dispatcher:GetSquadronLanding( Squadron.Name ) == AI_A2G_DISPATCHER.Landing.NearAirbase then if Dispatcher:GetSquadronLanding( Squadron.Name ) == AI_A2G_DISPATCHER.Landing.NearAirbase then
Dispatcher:RemoveDefenderFromSquadron( Squadron, Defender ) Dispatcher:RemoveDefenderFromSquadron( Squadron, DefenderGroup )
Defender:Destroy() DefenderGroup:Destroy()
Dispatcher:ResourcePark( Squadron, Defender ) Dispatcher:ResourcePark( Squadron, DefenderGroup )
end end
end end
end end
@@ -3605,112 +3637,112 @@ do -- AI_A2G_DISPATCHER
local AI_A2G = { SEAD = AI_A2G_SEAD, BAI = AI_A2G_BAI, CAS = AI_A2G_CAS } local AI_A2G = { SEAD = AI_A2G_SEAD, BAI = AI_A2G_BAI, CAS = AI_A2G_CAS }
local Fsm = AI_A2G[DefenseTaskType]:New( DefenderGroup, Defense.EngageMinSpeed, Defense.EngageMaxSpeed, Defense.EngageFloorAltitude, Defense.EngageCeilingAltitude ) -- AI.AI_A2G_ENGAGE local AI_A2G_Fsm = AI_A2G[DefenseTaskType]:New( DefenderGroup, Defense.EngageMinSpeed, Defense.EngageMaxSpeed, Defense.EngageFloorAltitude, Defense.EngageCeilingAltitude ) -- AI.AI_A2G_ENGAGE
Fsm:SetDispatcher( self ) AI_A2G_Fsm:SetDispatcher( self )
Fsm:SetHomeAirbase( DefenderSquadron.Airbase ) AI_A2G_Fsm:SetHomeAirbase( DefenderSquadron.Airbase )
Fsm:SetFuelThreshold( DefenderSquadron.FuelThreshold or self.DefenderDefault.FuelThreshold, 60 ) AI_A2G_Fsm:SetFuelThreshold( DefenderSquadron.FuelThreshold or self.DefenderDefault.FuelThreshold, 60 )
Fsm:SetDamageThreshold( self.DefenderDefault.DamageThreshold ) AI_A2G_Fsm:SetDamageThreshold( self.DefenderDefault.DamageThreshold )
Fsm:SetDisengageRadius( self.DisengageRadius ) AI_A2G_Fsm:SetDisengageRadius( self.DisengageRadius )
Fsm:Start() AI_A2G_Fsm:Start()
self:SetDefenderTask( SquadronName, DefenderGroup, DefenseTaskType, Fsm, AttackerDetection, DefenderGrouping ) self:SetDefenderTask( SquadronName, DefenderGroup, DefenseTaskType, AI_A2G_Fsm, AttackerDetection, DefenderGrouping )
function Fsm:onafterTakeoff( Defender, From, Event, To ) function AI_A2G_Fsm:onafterTakeoff( DefenderGroup, From, Event, To )
self:F({"Defender Birth", Defender:GetName()}) self:F({"Defender Birth", DefenderGroup:GetName()})
--self:GetParent(self).onafterBirth( self, Defender, From, Event, To ) --self:GetParent(self).onafterBirth( self, Defender, From, Event, To )
local DefenderName = Defender:GetCallsign() local DefenderName = DefenderGroup:GetCallsign()
local Dispatcher = Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER local Dispatcher = AI_A2G_Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER
local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup )
local DefenderTarget = Dispatcher:GetDefenderTaskTarget( Defender ) local DefenderTarget = Dispatcher:GetDefenderTaskTarget( DefenderGroup )
self:F( { DefenderTarget = DefenderTarget } ) self:F( { DefenderTarget = DefenderTarget } )
if DefenderTarget then if DefenderTarget then
Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " airborne. Engaging!" ) Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " airborne. Engaging!", "Wheels_up.wav", 3, "A2G/", DefenderGroup )
Fsm:EngageRoute( DefenderTarget.Set ) -- Engage on the TargetSetUnit AI_A2G_Fsm:EngageRoute( DefenderTarget.Set ) -- Engage on the TargetSetUnit
end end
end end
function Fsm:onafterEngageRoute( Defender, From, Event, To, AttackSetUnit ) function AI_A2G_Fsm:onafterEngageRoute( DefenderGroup, From, Event, To, AttackSetUnit )
self:F({"Engage Route", Defender:GetName()}) self:F({"Engage Route", DefenderGroup:GetName()})
self:GetParent(self).onafterEngageRoute( self, Defender, From, Event, To, AttackSetUnit )
local DefenderName = Defender:GetCallsign() local DefenderName = DefenderGroup:GetCallsign()
local Dispatcher = Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER local Dispatcher = AI_A2G_Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER
local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup )
if Squadron then if Squadron then
local FirstUnit = AttackSetUnit:GetFirst() local FirstUnit = AttackSetUnit:GetFirst()
local Coordinate = FirstUnit:GetCoordinate() -- Core.Point#COORDINATE local Coordinate = FirstUnit:GetCoordinate() -- Core.Point#COORDINATE
Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " on route to ground target at " .. Coordinate:ToStringA2G( Defender ) ) Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " on route to ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), "Moving_on_to_ground_target.wav", 3, "A2G/", DefenderGroup )
end end
self:GetParent(self).onafterEngageRoute( self, DefenderGroup, From, Event, To, AttackSetUnit )
end end
function Fsm:OnAfterEngage( Defender, From, Event, To, AttackSetUnit ) function AI_A2G_Fsm:OnAfterEngage( DefenderGroup, From, Event, To, AttackSetUnit )
self:F({"Engage Route", Defender:GetName()}) self:F({"Engage Route", DefenderGroup:GetName()})
--self:GetParent(self).onafterBirth( self, Defender, From, Event, To ) --self:GetParent(self).onafterBirth( self, Defender, From, Event, To )
local DefenderName = Defender:GetCallsign() local DefenderName = DefenderGroup:GetCallsign()
local Dispatcher = Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER local Dispatcher = AI_A2G_Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER
local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup )
local FirstUnit = AttackSetUnit:GetFirst() local FirstUnit = AttackSetUnit:GetFirst()
if FirstUnit then if FirstUnit then
local Coordinate = FirstUnit:GetCoordinate() local Coordinate = FirstUnit:GetCoordinate()
Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " engaging ground target at " .. Coordinate:ToStringA2G( Defender ) ) Dispatcher:MessageToPlayers( Squadron, "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " engaging ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), "Engaging_ground_target.wav", 3, "A2G/", DefenderGroup )
end end
end end
function Fsm:onafterRTB( Defender, From, Event, To ) function AI_A2G_Fsm:onafterRTB( DefenderGroup, From, Event, To )
self:F({"Defender RTB", Defender:GetName()}) self:F({"Defender RTB", DefenderGroup:GetName()})
local DefenderName = Defender:GetCallsign() local DefenderName = DefenderGroup:GetCallsign()
local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER
local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup )
Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " returning." ) Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " returning.", "Returning_to_base.wav", 3, "A2G/", DefenderGroup )
self:GetParent(self).onafterRTB( self, Defender, From, Event, To ) self:GetParent(self).onafterRTB( self, DefenderGroup, From, Event, To )
Dispatcher:ClearDefenderTaskTarget( Defender ) Dispatcher:ClearDefenderTaskTarget( DefenderGroup )
end end
--- @param #AI_A2G_DISPATCHER self --- @param #AI_A2G_DISPATCHER self
function Fsm:onafterLostControl( Defender, From, Event, To ) function AI_A2G_Fsm:onafterLostControl( DefenderGroup, From, Event, To )
self:F({"Defender LostControl", Defender:GetName()}) self:F({"Defender LostControl", DefenderGroup:GetName()})
self:GetParent(self).onafterHome( self, Defender, From, Event, To ) self:GetParent(self).onafterHome( self, DefenderGroup, From, Event, To )
local DefenderName = Defender:GetCallsign() local DefenderName = DefenderGroup:GetCallsign()
local Dispatcher = Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER local Dispatcher = AI_A2G_Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER
local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup )
--Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " lost control." ) --Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " lost control." )
if Defender:IsAboveRunway() then if DefenderGroup:IsAboveRunway() then
Dispatcher:RemoveDefenderFromSquadron( Squadron, Defender ) Dispatcher:RemoveDefenderFromSquadron( Squadron, DefenderGroup )
Defender:Destroy() DefenderGroup:Destroy()
end end
end end
--- @param #AI_A2G_DISPATCHER self --- @param #AI_A2G_DISPATCHER self
function Fsm:onafterHome( Defender, From, Event, To, Action ) function AI_A2G_Fsm:onafterHome( DefenderGroup, From, Event, To, Action )
self:F({"Defender Home", Defender:GetName()}) self:F({"Defender Home", DefenderGroup:GetName()})
self:GetParent(self).onafterHome( self, Defender, From, Event, To ) self:GetParent(self).onafterHome( self, DefenderGroup, From, Event, To )
local DefenderName = Defender:GetCallsign() local DefenderName = DefenderGroup:GetCallsign()
local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER
local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup )
Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " landing." ) Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " landing.", "Landing_at_base.wav", 3, "A2G/", DefenderGroup )
if Action and Action == "Destroy" then if Action and Action == "Destroy" then
Dispatcher:RemoveDefenderFromSquadron( Squadron, Defender ) Dispatcher:RemoveDefenderFromSquadron( Squadron, DefenderGroup )
Defender:Destroy() DefenderGroup:Destroy()
end end
if Dispatcher:GetSquadronLanding( Squadron.Name ) == AI_A2G_DISPATCHER.Landing.NearAirbase then if Dispatcher:GetSquadronLanding( Squadron.Name ) == AI_A2G_DISPATCHER.Landing.NearAirbase then
Dispatcher:RemoveDefenderFromSquadron( Squadron, Defender ) Dispatcher:RemoveDefenderFromSquadron( Squadron, DefenderGroup )
Defender:Destroy() DefenderGroup:Destroy()
Dispatcher:ResourcePark( Squadron, Defender ) Dispatcher:ResourcePark( Squadron, DefenderGroup )
end end
end end
end end
@@ -4024,6 +4056,29 @@ do -- AI_A2G_DISPATCHER
end end
--- Determine the distance as the keys of reference of the detected items.
-- @param #AI_A2G_DISPATCHER self
function AI_A2G_DISPATCHER:Keys( DetectedItem )
self:F( { DetectedItem = DetectedItem } )
local AttackCoordinate = self.Detection:GetDetectedItemCoordinate( DetectedItem )
local ShortestDistance = 999999999
for DefenseCoordinateName, DefenseCoordinate in pairs( self.DefenseCoordinates ) do
local DefenseCoordinate = DefenseCoordinate -- Core.Point#COORDINATE
local EvaluateDistance = AttackCoordinate:Get2DDistance( DefenseCoordinate )
if EvaluateDistance <= ShortestDistance then
ShortestDistance = EvaluateDistance
end
end
return ShortestDistance
end
--- Assigns A2G AI Tasks in relation to the detected items. --- Assigns A2G AI Tasks in relation to the detected items.
-- @param #AI_A2G_DISPATCHER self -- @param #AI_A2G_DISPATCHER self
function AI_A2G_DISPATCHER:Order( DetectedItem ) function AI_A2G_DISPATCHER:Order( DetectedItem )
@@ -4044,6 +4099,107 @@ do -- AI_A2G_DISPATCHER
return ShortestDistance return ShortestDistance
end end
--- Shows the tactical display.
-- @param #AI_A2G_DISPATCHER self
function AI_A2G_DISPATCHER:ShowTacticalDisplay( Detection )
local AreaMsg = {}
local TaskMsg = {}
local ChangeMsg = {}
local TaskReport = REPORT:New()
local DefenseTotal = 0
local Report = REPORT:New( "\nTactical Overview" )
local DefenderGroupCount = 0
local DefendersTotal = 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
if not self.Detection:IsDetectedItemLocked( DetectedItem ) == true then
local DetectedItem = DetectedItem -- Functional.Detection#DETECTION_BASE.DetectedItem
local DetectedSet = DetectedItem.Set -- Core.Set#SET_UNIT
local DetectedCount = DetectedSet:Count()
local DetectedZone = DetectedItem.Zone
self:F( { "Target ID", DetectedItem.ItemID } )
self:F( { DefenseLimit = self.DefenseLimit, DefenseTotal = DefenseTotal } )
DetectedSet:Flush( self )
local DetectedID = DetectedItem.ID
local DetectionIndex = DetectedItem.Index
local DetectedItemChanged = DetectedItem.Changed
-- Show tactical situation
local ThreatLevel = DetectedItem.Set:CalculateThreatLevelA2G()
Report:Add( string.format( " - %1s%s ( %04s ): ( #%02d - %-4s ) %s" , ( DetectedItem.IsDetected == true ) and "!" or " ", DetectedItem.ItemID, DetectedItem.Index, DetectedItem.Set:Count(), DetectedItem.Type or " --- ", string.rep( "", ThreatLevel ) ) )
for Defender, DefenderTask in pairs( self:GetDefenderTasks() ) do
local Defender = Defender -- Wrapper.Group#GROUP
if DefenderTask.Target and DefenderTask.Target.Index == DetectedItem.Index then
if Defender:IsAlive() then
DefenderGroupCount = DefenderGroupCount + 1
local Fuel = Defender:GetFuelMin() * 100
local Damage = Defender:GetLife() / Defender:GetLife0() * 100
Report:Add( string.format( " - %s ( %s - %s ): ( #%d ) F: %3d, D:%3d - %s",
Defender:GetName(),
DefenderTask.Type,
DefenderTask.Fsm:GetState(),
Defender:GetSize(),
Fuel,
Damage,
Defender:HasTask() == true and "Executing" or "Idle" ) )
end
end
end
end
end
Report:Add( "\n - No Targets:")
local TaskCount = 0
for Defender, DefenderTask in pairs( self:GetDefenderTasks() ) do
TaskCount = TaskCount + 1
local Defender = Defender -- Wrapper.Group#GROUP
if not DefenderTask.Target then
if Defender:IsAlive() then
local DefenderHasTask = Defender:HasTask()
local Fuel = Defender:GetFuelMin() * 100
local Damage = Defender:GetLife() / Defender:GetLife0() * 100
DefenderGroupCount = DefenderGroupCount + 1
Report:Add( string.format( " - %s ( %s - %s ): ( #%d ) F: %3d, D:%3d - %s",
Defender:GetName(),
DefenderTask.Type,
DefenderTask.Fsm:GetState(),
Defender:GetSize(),
Fuel,
Damage,
Defender:HasTask() == true and "Executing" or "Idle" ) )
end
end
end
Report:Add( string.format( "\n - %d Tasks - %d Defender Groups", TaskCount, DefenderGroupCount ) )
Report:Add( string.format( "\n - %d Queued Aircraft Launches", #self.DefenseQueue ) )
for DefenseQueueID, DefenseQueueItem in pairs( self.DefenseQueue ) do
local DefenseQueueItem = DefenseQueueItem -- #AI_A2G_DISPATCHER.DefenseQueueItem
Report:Add( string.format( " - %s - %s", DefenseQueueItem.SquadronName, DefenseQueueItem.DefenderSquadron.TakeoffTime, DefenseQueueItem.DefenderSquadron.TakeoffInterval) )
end
Report:Add( string.format( "\n - Squadron Resources: ", #self.DefenseQueue ) )
for DefenderSquadronName, DefenderSquadron in pairs( self.DefenderSquadrons ) do
Report:Add( string.format( " - %s - %d", DefenderSquadronName, DefenderSquadron.ResourceCount and DefenderSquadron.ResourceCount or "n/a" ) )
end
self:F( Report:Text( "\n" ) )
trigger.action.outText( Report:Text( "\n" ), 25 )
end
--- Assigns A2G AI Tasks in relation to the detected items. --- Assigns A2G AI Tasks in relation to the detected items.
-- @param #AI_A2G_DISPATCHER self -- @param #AI_A2G_DISPATCHER self
-- @param Functional.Detection#DETECTION_BASE Detection The detection created by the @{Functional.Detection#DETECTION_BASE} derived object. -- @param Functional.Detection#DETECTION_BASE Detection The detection created by the @{Functional.Detection#DETECTION_BASE} derived object.
@@ -4102,7 +4258,7 @@ do -- AI_A2G_DISPATCHER
-- Now that all obsolete tasks are removed, loop through the detected targets. -- Now that all obsolete tasks are removed, loop through the detected targets.
--for DetectedItemID, DetectedItem in pairs( Detection:GetDetectedItems() ) do --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 DetectedDistance, DetectedItem in UTILS.kpairs( Detection:GetDetectedItems(), function( t ) return self:Keys( t ) end, function( t, a, b ) return self:Order(t[a]) < self:Order(t[b]) end ) do
if not self.Detection:IsDetectedItemLocked( DetectedItem ) == true then if not self.Detection:IsDetectedItemLocked( DetectedItem ) == true then
local DetectedItem = DetectedItem -- Functional.Detection#DETECTION_BASE.DetectedItem local DetectedItem = DetectedItem -- Functional.Detection#DETECTION_BASE.DetectedItem
@@ -4125,43 +4281,42 @@ do -- AI_A2G_DISPATCHER
-- This calculation is based on the distance between the defense point and the attackers, and the defensiveness parameter. -- This calculation is based on the distance between the defense point and the attackers, and the defensiveness parameter.
-- The attackers closest to the defense coordinates will be handled first, or course! -- The attackers closest to the defense coordinates will be handled first, or course!
local EngageCoordinate = nil local EngageDefenses = nil
for DefenseCoordinateName, DefenseCoordinate in pairs( self.DefenseCoordinates ) do self:F( { DetectedDistance = DetectedDistance, DefenseRadius = self.DefenseRadius } )
local DefenseCoordinate = DefenseCoordinate -- Core.Point#COORDINATE if DetectedDistance <= self.DefenseRadius then
local EvaluateDistance = AttackCoordinate:Get2DDistance( DefenseCoordinate ) self:F( { DetectedApproach = self._DefenseApproach } )
if self._DefenseApproach == AI_A2G_DISPATCHER.DefenseApproach.Distance then
EngageDefenses = true
self:F( { EngageDefenses = EngageDefenses } )
end
if EvaluateDistance <= self.DefenseRadius then if self._DefenseApproach == AI_A2G_DISPATCHER.DefenseApproach.Random then
local DistanceProbability = ( self.DefenseRadius / DetectedDistance * self.DefenseReactivity )
local DistanceProbability = ( self.DefenseRadius / EvaluateDistance * self.DefenseReactivity )
local DefenseProbability = math.random() local DefenseProbability = math.random()
self:F( { DistanceProbability = DistanceProbability, DefenseProbability = DefenseProbability } ) self:F( { DistanceProbability = DistanceProbability, DefenseProbability = DefenseProbability } )
if self._DefenseApproach == AI_A2G_DISPATCHER.DefenseApproach.Random then if DefenseProbability <= DistanceProbability / ( 300 / 30 ) then
EngageDefenses = true
if DefenseProbability <= DistanceProbability / ( 300 / 30 ) then
EngageCoordinate = DefenseCoordinate
break
end
end
if self._DefenseApproach == AI_A2G_DISPATCHER.DefenseApproach.Distance then
EngageCoordinate = DefenseCoordinate
break
end end
end end
end end
self:F( { EngageDefenses = EngageDefenses, DefenseLimit = self.DefenseLimit, DefenseTotal = DefenseTotal } )
-- There needs to be an EngageCoordinate. -- There needs to be an EngageCoordinate.
-- If self.DefenseLimit is set (thus limit the amount of defenses to one zone), then only start a new defense if the maximum has not been reached. -- If self.DefenseLimit is set (thus limit the amount of defenses to one zone), then only start a new defense if the maximum has not been reached.
-- If self.DefenseLimit has not been set, there is an unlimited amount of zones to be defended. -- If self.DefenseLimit has not been set, there is an unlimited amount of zones to be defended.
if ( EngageCoordinate and ( self.DefenseLimit and DefenseTotal < self.DefenseLimit ) or not self.DefenseLimit ) then if ( EngageDefenses and ( self.DefenseLimit and DefenseTotal < self.DefenseLimit ) or not self.DefenseLimit ) then
do do
local DefendersTotal, DefendersEngaged, DefendersMissing, Friendlies = self:Evaluate_SEAD( DetectedItem ) -- Returns a SET_UNIT with the SEAD targets to be engaged... local DefendersTotal, DefendersEngaged, DefendersMissing, Friendlies = self:Evaluate_SEAD( DetectedItem ) -- Returns a SET_UNIT with the SEAD targets to be engaged...
if DefendersMissing > 0 then if DefendersMissing > 0 then
self:F( { DefendersTotal = DefendersTotal, DefendersEngaged = DefendersEngaged, DefendersMissing = DefendersMissing } ) self:F( { DefendersTotal = DefendersTotal, DefendersEngaged = DefendersEngaged, DefendersMissing = DefendersMissing } )
self:Defend( DetectedItem, DefendersTotal, DefendersEngaged, DefendersMissing, Friendlies, "SEAD", EngageCoordinate ) self:Defend( DetectedItem, DefendersTotal, DefendersEngaged, DefendersMissing, Friendlies, "SEAD" )
end end
end end
@@ -4169,7 +4324,7 @@ do -- AI_A2G_DISPATCHER
local DefendersTotal, DefendersEngaged, DefendersMissing, Friendlies = self:Evaluate_CAS( DetectedItem ) -- Returns a SET_UNIT with the CAS targets to be engaged... local DefendersTotal, DefendersEngaged, DefendersMissing, Friendlies = self:Evaluate_CAS( DetectedItem ) -- Returns a SET_UNIT with the CAS targets to be engaged...
if DefendersMissing > 0 then if DefendersMissing > 0 then
self:F( { DefendersTotal = DefendersTotal, DefendersEngaged = DefendersEngaged, DefendersMissing = DefendersMissing } ) self:F( { DefendersTotal = DefendersTotal, DefendersEngaged = DefendersEngaged, DefendersMissing = DefendersMissing } )
self:Defend( DetectedItem, DefendersTotal, DefendersEngaged, DefendersMissing, Friendlies, "CAS", EngageCoordinate ) self:Defend( DetectedItem, DefendersTotal, DefendersEngaged, DefendersMissing, Friendlies, "CAS" )
end end
end end
@@ -4177,7 +4332,7 @@ do -- AI_A2G_DISPATCHER
local DefendersTotal, DefendersEngaged, DefendersMissing, Friendlies = self:Evaluate_BAI( DetectedItem ) -- Returns a SET_UNIT with the CAS targets to be engaged... local DefendersTotal, DefendersEngaged, DefendersMissing, Friendlies = self:Evaluate_BAI( DetectedItem ) -- Returns a SET_UNIT with the CAS targets to be engaged...
if DefendersMissing > 0 then if DefendersMissing > 0 then
self:F( { DefendersTotal = DefendersTotal, DefendersEngaged = DefendersEngaged, DefendersMissing = DefendersMissing } ) self:F( { DefendersTotal = DefendersTotal, DefendersEngaged = DefendersEngaged, DefendersMissing = DefendersMissing } )
self:Defend( DetectedItem, DefendersTotal, DefendersEngaged, DefendersMissing, Friendlies, "BAI", EngageCoordinate ) self:Defend( DetectedItem, DefendersTotal, DefendersEngaged, DefendersMissing, Friendlies, "BAI" )
end end
end end
end end
@@ -4191,7 +4346,7 @@ do -- AI_A2G_DISPATCHER
for DefenseQueueID, DefenseQueueItem in pairs( self.DefenseQueue ) do for DefenseQueueID, DefenseQueueItem in pairs( self.DefenseQueue ) do
local DefenseQueueItem = DefenseQueueItem -- #AI_A2G_DISPATCHER.DefenseQueueItem local DefenseQueueItem = DefenseQueueItem -- #AI_A2G_DISPATCHER.DefenseQueueItem
if DefenseQueueItem.AttackerDetection.Index == DetectedItem.Index then if DefenseQueueItem.AttackerDetection and DefenseQueueItem.AttackerDetection.Index and DefenseQueueItem.AttackerDetection.Index == DetectedItem.Index then
DefenseTotal = DefenseTotal + 1 DefenseTotal = DefenseTotal + 1
end end
end end
+6 -12
View File
@@ -304,6 +304,8 @@ function AI_A2G_ENGAGE:New( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloor
-- @param #AI_A2G_ENGAGE self -- @param #AI_A2G_ENGAGE self
-- @param #number Delay The delay in seconds. -- @param #number Delay The delay in seconds.
self:AddTransition( { "Patrolling", "Engaging" }, "Refuel", "Refuelling" )
return self return self
end end
@@ -389,17 +391,6 @@ function AI_A2G_ENGAGE:onafterAbort( AIGroup, From, Event, To )
end end
--- @param #AI_A2G_ENGAGE self
-- @param Wrapper.Group#GROUP DefenderGroup 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:onafterEngageRoute( DefenderGroup, From, Event, To, AttackSetUnit )
self:F( { DefenderGroup, From, Event, To, AttackSetUnit} )
end
--- @param #AI_A2G_ENGAGE self --- @param #AI_A2G_ENGAGE self
-- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM. -- @param Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM.
-- @param #string From The From State string. -- @param #string From The From State string.
@@ -446,6 +437,8 @@ function AI_A2G_ENGAGE:onafterEngageRoute( DefenderGroup, From, Event, To, Attac
local DefenderGroupName = DefenderGroup:GetName() local DefenderGroupName = DefenderGroup:GetName()
self.AttackSetUnit = AttackSetUnit -- Kept in memory in case of resume from refuel in air!
local AttackCount = AttackSetUnit:Count() local AttackCount = AttackSetUnit:Count()
if AttackCount > 0 then if AttackCount > 0 then
@@ -513,8 +506,9 @@ function AI_A2G_ENGAGE:onafterEngageRoute( DefenderGroup, From, Event, To, Attac
end end
else else
self:E( DefenderGroupName .. ": No targets found -> Going RTB") self:I( DefenderGroupName .. ": No targets found -> Going RTB")
self:Return() self:Return()
self:__RTB( self.TaskDelay ) self:__RTB( self.TaskDelay )
end end
end end
+13 -5
View File
@@ -287,8 +287,17 @@ function AI_A2G_PATROL:onafterPatrolRoute( AIPatrol, From, Event, To )
local ToTargetSpeed = math.random( self.PatrolMinSpeed, self.PatrolMaxSpeed ) local ToTargetSpeed = math.random( self.PatrolMinSpeed, self.PatrolMaxSpeed )
local FromWP = CurrentCoord:WaypointAir(
self.PatrolAltType or "RADIO",
POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint,
ToTargetSpeed,
true
)
PatrolRoute[#PatrolRoute+1] = FromWP
--- Create a route point of type air. --- Create a route point of type air.
local ToPatrolRoutePoint = ToTargetCoord:WaypointAir( local ToWP = ToTargetCoord:WaypointAir(
self.PatrolAltType, self.PatrolAltType,
POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointType.TurningPoint,
POINT_VEC3.RoutePointAction.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint,
@@ -296,8 +305,7 @@ function AI_A2G_PATROL:onafterPatrolRoute( AIPatrol, From, Event, To )
true true
) )
PatrolRoute[#PatrolRoute+1] = ToPatrolRoutePoint PatrolRoute[#PatrolRoute+1] = ToWP
PatrolRoute[#PatrolRoute+1] = ToPatrolRoutePoint
local Tasks = {} local Tasks = {}
Tasks[#Tasks+1] = AIPatrol:TaskFunction( "AI_A2G_PATROL.___PatrolRoute", self ) Tasks[#Tasks+1] = AIPatrol:TaskFunction( "AI_A2G_PATROL.___PatrolRoute", self )
@@ -316,8 +324,8 @@ function AI_A2G_PATROL.Resume( AIPatrol, Fsm )
AIPatrol:F( { "AI_A2G_PATROL.Resume:", AIPatrol:GetName() } ) AIPatrol:F( { "AI_A2G_PATROL.Resume:", AIPatrol:GetName() } )
if AIPatrol:IsAlive() then if AIPatrol:IsAlive() then
Fsm:__Reset( self.TaskDelay ) Fsm:__Reset( Fsm.TaskDelay )
Fsm:__PatrolRoute( self.TaskDelay ) Fsm:__PatrolRoute( Fsm.TaskDelay )
end end
end end
+14 -2
View File
@@ -117,6 +117,8 @@ function AI_A2G_SEAD:onafterEngage( DefenderGroup, From, Event, To, AttackSetUni
local DefenderGroupName = DefenderGroup:GetName() local DefenderGroupName = DefenderGroup:GetName()
self.AttackSetUnit = AttackSetUnit -- Kept in memory in case of resume from refuel in air!
local AttackCount = AttackSetUnit:Count() local AttackCount = AttackSetUnit:Count()
if AttackCount > 0 then if AttackCount > 0 then
@@ -181,7 +183,7 @@ function AI_A2G_SEAD:onafterEngage( DefenderGroup, From, Event, To, AttackSetUni
end end
if #AttackUnitTasks == 0 then if #AttackUnitTasks == 0 then
self:E( DefenderGroupName .. ": No targets found -> Going RTB") self:I( DefenderGroupName .. ": No targets found -> Going RTB")
self:Return() self:Return()
self:__RTB( self.TaskDelay ) self:__RTB( self.TaskDelay )
else else
@@ -200,9 +202,19 @@ function AI_A2G_SEAD:onafterEngage( DefenderGroup, From, Event, To, AttackSetUni
DefenderGroup:Route( EngageRoute, self.TaskDelay ) DefenderGroup:Route( EngageRoute, self.TaskDelay )
end end
else else
self:E( DefenderGroupName .. ": No targets found -> Going RTB") self:I( DefenderGroupName .. ": No targets found -> Going RTB")
self:Return() self:Return()
self:__RTB( self.TaskDelay ) self:__RTB( self.TaskDelay )
end end
end end
--- @param Wrapper.Group#GROUP AIEngage
function AI_A2G_SEAD.Resume( AIEngage, Fsm )
AIEngage:F( { "AI_A2G_SEAD.Resume:", AIEngage:GetName() } )
if AIEngage:IsAlive() then
Fsm:__Reset( Fsm.TaskDelay )
Fsm:__EngageRoute( Fsm.TaskDelay, Fsm.AttackSetUnit )
end
end
+13 -11
View File
@@ -446,7 +446,7 @@ function AI_AIR:onafterStatus()
local DistanceFromHomeBase = self.HomeAirbase:GetCoordinate():Get2DDistance( self.Controllable:GetCoordinate() ) local DistanceFromHomeBase = self.HomeAirbase:GetCoordinate():Get2DDistance( self.Controllable:GetCoordinate() )
if DistanceFromHomeBase > self.DisengageRadius then if DistanceFromHomeBase > self.DisengageRadius then
self:E( self.Controllable:GetName() .. " is too far from home base, RTB!" ) self:I( self.Controllable:GetName() .. " is too far from home base, RTB!" )
self:Hold( 300 ) self:Hold( 300 )
RTB = false RTB = false
end end
@@ -461,7 +461,7 @@ function AI_AIR:onafterStatus()
-- end -- end
if not self:Is( "Fuel" ) and not self:Is( "Home" ) then if not self:Is( "Fuel" ) and not self:Is( "Home" ) and not self:is( "Refuelling" )then
local Fuel = self.Controllable:GetFuelMin() local Fuel = self.Controllable:GetFuelMin()
@@ -470,10 +470,10 @@ function AI_AIR:onafterStatus()
if Fuel < self.FuelThresholdPercentage then if Fuel < self.FuelThresholdPercentage then
if self.TankerName then if self.TankerName then
self:E( self.Controllable:GetName() .. " is out of fuel: " .. Fuel .. " ... Refuelling at Tanker!" ) self:I( self.Controllable:GetName() .. " is out of fuel: " .. Fuel .. " ... Refuelling at Tanker!" )
self:Refuel() self:Refuel()
else else
self:E( self.Controllable:GetName() .. " is out of fuel: " .. Fuel .. " ... RTB!" ) self:I( self.Controllable:GetName() .. " is out of fuel: " .. Fuel .. " ... RTB!" )
local OldAIControllable = self.Controllable local OldAIControllable = self.Controllable
local OrbitTask = OldAIControllable:TaskOrbitCircle( math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ), self.PatrolMinSpeed ) local OrbitTask = OldAIControllable:TaskOrbitCircle( math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ), self.PatrolMinSpeed )
@@ -495,7 +495,7 @@ function AI_AIR:onafterStatus()
-- Note that a group can consist of more units, so if one unit is damaged of a group, the mission may continue. -- Note that a group can consist of more units, so if one unit is damaged of a group, the mission may continue.
-- The damaged unit will RTB due to DCS logic, and the others will continue to engage. -- The damaged unit will RTB due to DCS logic, and the others will continue to engage.
if ( Damage / InitialLife ) < self.PatrolDamageThreshold then if ( Damage / InitialLife ) < self.PatrolDamageThreshold then
self:E( self.Controllable:GetName() .. " is damaged: " .. Damage .. " ... RTB!" ) self:I( self.Controllable:GetName() .. " is damaged: " .. Damage .. " ... RTB!" )
self:Damaged() self:Damaged()
RTB = true RTB = true
self:SetStatusOff() self:SetStatusOff()
@@ -513,7 +513,7 @@ function AI_AIR:onafterStatus()
if Damage ~= InitialLife then if Damage ~= InitialLife then
self:Damaged() self:Damaged()
else else
self:E( self.Controllable:GetName() .. " control lost! " ) self:I( self.Controllable:GetName() .. " control lost! " )
self:LostControl() self:LostControl()
end end
@@ -570,7 +570,7 @@ function AI_AIR:onafterRTB( AIGroup, From, Event, To )
if AIGroup and AIGroup:IsAlive() then if AIGroup and AIGroup:IsAlive() then
self:E( "Group " .. AIGroup:GetName() .. " ... RTB! ( " .. self:GetState() .. " )" ) self:I( "Group " .. AIGroup:GetName() .. " ... RTB! ( " .. self:GetState() .. " )" )
self:ClearTargetDistance() self:ClearTargetDistance()
--AIGroup:ClearTasks() --AIGroup:ClearTasks()
@@ -588,7 +588,7 @@ function AI_AIR:onafterRTB( AIGroup, From, Event, To )
local ToAirbaseCoord = FromCoord:Translate( 5000, ToAirbaseAngle ) local ToAirbaseCoord = FromCoord:Translate( 5000, ToAirbaseAngle )
if Distance < 5000 then if Distance < 5000 then
self:E( "RTB and near the airbase!" ) self:I( "RTB and near the airbase!" )
self:Home() self:Home()
return return
end end
@@ -634,7 +634,7 @@ end
function AI_AIR:onafterHome( AIGroup, From, Event, To ) function AI_AIR:onafterHome( AIGroup, From, Event, To )
self:F( { AIGroup, From, Event, To } ) self:F( { AIGroup, From, Event, To } )
self:E( "Group " .. self.Controllable:GetName() .. " ... Home! ( " .. self:GetState() .. " )" ) self:I( "Group " .. self.Controllable:GetName() .. " ... Home! ( " .. self:GetState() .. " )" )
if AIGroup and AIGroup:IsAlive() then if AIGroup and AIGroup:IsAlive() then
end end
@@ -648,7 +648,7 @@ end
function AI_AIR:onafterHold( AIGroup, From, Event, To, HoldTime ) function AI_AIR:onafterHold( AIGroup, From, Event, To, HoldTime )
self:F( { AIGroup, From, Event, To } ) self:F( { AIGroup, From, Event, To } )
self:E( "Group " .. self.Controllable:GetName() .. " ... Holding! ( " .. self:GetState() .. " )" ) self:I( "Group " .. self.Controllable:GetName() .. " ... Holding! ( " .. self:GetState() .. " )" )
if AIGroup and AIGroup:IsAlive() then if AIGroup and AIGroup:IsAlive() then
local OrbitTask = AIGroup:TaskOrbitCircle( math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ), self.PatrolMinSpeed ) local OrbitTask = AIGroup:TaskOrbitCircle( math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ), self.PatrolMinSpeed )
@@ -680,12 +680,14 @@ end
function AI_AIR:onafterRefuel( AIGroup, From, Event, To ) function AI_AIR:onafterRefuel( AIGroup, From, Event, To )
self:F( { 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 if AIGroup and AIGroup:IsAlive() then
local Tanker = GROUP:FindByName( self.TankerName ) local Tanker = GROUP:FindByName( self.TankerName )
if Tanker:IsAlive() and Tanker:IsAirPlane() then if Tanker:IsAlive() and Tanker:IsAirPlane() then
self:I( "Group " .. self.Controllable:GetName() .. " ... Refuelling! ( " .. self:GetState() .. "), at tanker " .. self.TankerName )
local RefuelRoute = {} local RefuelRoute = {}
--- Calculate the target route point. --- Calculate the target route point.
@@ -3289,6 +3289,9 @@ do
self:Patrol( SquadronName, PatrolTaskType ) self:Patrol( SquadronName, PatrolTaskType )
end end
end end
+1 -1
View File
@@ -545,7 +545,7 @@ function AI_ESCORT:SetFlightMenuFormation( Formation )
if MenuFormation then if MenuFormation then
local Arguments = MenuFormation.Arguments local Arguments = MenuFormation.Arguments
self:I({Arguments=unpack(Arguments)}) --self:I({Arguments=unpack(Arguments)})
local FlightMenuFormation = MENU_GROUP:New( self.PlayerGroup, "Formation", self.MainMenu ) local FlightMenuFormation = MENU_GROUP:New( self.PlayerGroup, "Formation", self.MainMenu )
local MenuFlightFormationID = MENU_GROUP_COMMAND:New( self.PlayerGroup, Formation, FlightMenuFormation, local MenuFlightFormationID = MENU_GROUP_COMMAND:New( self.PlayerGroup, Formation, FlightMenuFormation,
function ( self, Formation, ... ) function ( self, Formation, ... )
@@ -3,6 +3,7 @@
-- ## Features: -- ## Features:
-- -- -- --
-- * Provides the facilities to trigger escorts when players join flight slots. -- * Provides the facilities to trigger escorts when players join flight slots.
-- *
-- --
-- === -- ===
-- --
@@ -83,13 +83,6 @@ function AI_ESCORT_DISPATCHER_REQUEST:OnEventExit( EventData )
local PlayerGroup = EventData.IniGroup local PlayerGroup = EventData.IniGroup
local PlayerUnit = EventData.IniUnit local PlayerUnit = EventData.IniUnit
self.CarrierSet:Flush(self)
self:I({EscortAirbase= self.EscortAirbase } )
self:I({PlayerGroupName = PlayerGroupName } )
self:I({PlayerGroup = PlayerGroup})
self:I({FirstGroup = self.CarrierSet:GetFirst()})
self:I({FindGroup = self.CarrierSet:FindGroup( PlayerGroupName )})
if self.CarrierSet:FindGroup( PlayerGroupName ) then if self.CarrierSet:FindGroup( PlayerGroupName ) then
if self.AI_Escorts[PlayerGroupName] then if self.AI_Escorts[PlayerGroupName] then
self.AI_Escorts[PlayerGroupName]:Stop() self.AI_Escorts[PlayerGroupName]:Stop()
@@ -107,11 +100,6 @@ function AI_ESCORT_DISPATCHER_REQUEST:OnEventBirth( EventData )
local PlayerGroup = EventData.IniGroup local PlayerGroup = EventData.IniGroup
local PlayerUnit = EventData.IniUnit local PlayerUnit = EventData.IniUnit
self:I({PlayerGroupName = PlayerGroupName } )
self:I({PlayerGroup = PlayerGroup})
self:I({FirstGroup = self.CarrierSet:GetFirst()})
self:I({FindGroup = self.CarrierSet:FindGroup( PlayerGroupName )})
if self.CarrierSet:FindGroup( PlayerGroupName ) then if self.CarrierSet:FindGroup( PlayerGroupName ) then
if not self.AI_Escorts[PlayerGroupName] then if not self.AI_Escorts[PlayerGroupName] then
local LeaderUnit = PlayerUnit local LeaderUnit = PlayerUnit
+2 -3
View File
@@ -825,7 +825,7 @@ function AI_PATROL_ZONE:onafterStatus()
local Fuel = self.Controllable:GetFuelMin() local Fuel = self.Controllable:GetFuelMin()
if Fuel < self.PatrolFuelThresholdPercentage then if Fuel < self.PatrolFuelThresholdPercentage then
self:E( self.Controllable:GetName() .. " is out of fuel:" .. Fuel .. ", RTB!" ) self:I( self.Controllable:GetName() .. " is out of fuel:" .. Fuel .. ", RTB!" )
local OldAIControllable = self.Controllable local OldAIControllable = self.Controllable
local OrbitTask = OldAIControllable:TaskOrbitCircle( math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ), self.PatrolMinSpeed ) local OrbitTask = OldAIControllable:TaskOrbitCircle( math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ), self.PatrolMinSpeed )
@@ -839,7 +839,7 @@ function AI_PATROL_ZONE:onafterStatus()
-- TODO: Check GROUP damage function. -- TODO: Check GROUP damage function.
local Damage = self.Controllable:GetLife() local Damage = self.Controllable:GetLife()
if Damage <= self.PatrolDamageThreshold then if Damage <= self.PatrolDamageThreshold then
self:E( self.Controllable:GetName() .. " is damaged:" .. Damage .. ", RTB!" ) self:I( self.Controllable:GetName() .. " is damaged:" .. Damage .. ", RTB!" )
RTB = true RTB = true
end end
@@ -900,7 +900,6 @@ end
function AI_PATROL_ZONE:OnCrash( EventData ) function AI_PATROL_ZONE:OnCrash( EventData )
if self.Controllable:IsAlive() and EventData.IniDCSGroupName == self.Controllable:GetName() then if self.Controllable:IsAlive() and EventData.IniDCSGroupName == self.Controllable:GetName() then
self:E( self.Controllable:GetUnits() )
if #self.Controllable:GetUnits() == 1 then if #self.Controllable:GetUnits() == 1 then
self:__Crash( 1, EventData ) self:__Crash( 1, EventData )
end end
+38 -16
View File
@@ -805,15 +805,17 @@ do -- Scheduling
end end
self.Scheduler.SchedulerObject = self.Scheduler self.Scheduler.SchedulerObject = self.Scheduler
--self.MasterObject = self
local ScheduleID = _SCHEDULEDISPATCHER:AddSchedule( local ScheduleID = self.Scheduler:Schedule(
self, self,
SchedulerFunction, SchedulerFunction,
{ ... }, { ... },
Start, Start,
Repeat, Repeat,
RandomizeFactor, RandomizeFactor,
Stop Stop,
4
) )
self._.Schedules[#self._.Schedules+1] = ScheduleID self._.Schedules[#self._.Schedules+1] = ScheduleID
@@ -889,6 +891,26 @@ end
-- Log a trace (only shown when trace is on) -- Log a trace (only shown when trace is on)
-- TODO: Make trace function using variable parameters. -- TODO: Make trace function using variable parameters.
--- Set trace on.
-- @param #BASE self
-- @usage
-- -- Switch the tracing On
-- BASE:TraceOn()
function BASE:TraceOn()
self:TraceOnOff( true )
end
--- Set trace off.
-- @param #BASE self
-- @usage
-- -- Switch the tracing Off
-- BASE:TraceOff()
function BASE:TraceOff()
self:TraceOnOff( false )
end
--- Set trace on or off --- Set trace on or off
-- Note that when trace is off, no BASE.Debug statement is performed, increasing performance! -- Note that when trace is off, no BASE.Debug statement is performed, increasing performance!
-- When Moose is loaded statically, (as one file), tracing is switched off by default. -- When Moose is loaded statically, (as one file), tracing is switched off by default.
@@ -903,7 +925,7 @@ end
-- -- Switch the tracing Off -- -- Switch the tracing Off
-- BASE:TraceOnOff( false ) -- BASE:TraceOnOff( false )
function BASE:TraceOnOff( TraceOnOff ) function BASE:TraceOnOff( TraceOnOff )
_TraceOnOff = TraceOnOff _TraceOnOff = TraceOnOff or true
end end
@@ -923,8 +945,8 @@ end
-- @param #BASE self -- @param #BASE self
-- @param #number Level -- @param #number Level
function BASE:TraceLevel( Level ) function BASE:TraceLevel( Level )
_TraceLevel = Level _TraceLevel = Level or 1
self:E( "Tracing level " .. Level ) self:I( "Tracing level " .. _TraceLevel )
end end
--- Trace all methods in MOOSE --- Trace all methods in MOOSE
@@ -932,12 +954,12 @@ end
-- @param #boolean TraceAll true = trace all methods in MOOSE. -- @param #boolean TraceAll true = trace all methods in MOOSE.
function BASE:TraceAll( TraceAll ) function BASE:TraceAll( TraceAll )
_TraceAll = TraceAll _TraceAll = TraceAll or true
if _TraceAll then if _TraceAll then
self:E( "Tracing all methods in MOOSE " ) self:I( "Tracing all methods in MOOSE " )
else else
self:E( "Switched off tracing all methods in MOOSE" ) self:I( "Switched off tracing all methods in MOOSE" )
end end
end end
@@ -947,7 +969,7 @@ end
function BASE:TraceClass( Class ) function BASE:TraceClass( Class )
_TraceClass[Class] = true _TraceClass[Class] = true
_TraceClassMethod[Class] = {} _TraceClassMethod[Class] = {}
self:E( "Tracing class " .. Class ) self:I( "Tracing class " .. Class )
end end
--- Set tracing for a specific method of class --- Set tracing for a specific method of class
@@ -960,7 +982,7 @@ function BASE:TraceClassMethod( Class, Method )
_TraceClassMethod[Class].Method = {} _TraceClassMethod[Class].Method = {}
end end
_TraceClassMethod[Class].Method[Method] = true _TraceClassMethod[Class].Method[Method] = true
self:E( "Tracing method " .. Method .. " of class " .. Class ) self:I( "Tracing method " .. Method .. " of class " .. Class )
end end
--- Trace a function call. This function is private. --- Trace a function call. This function is private.
@@ -987,7 +1009,7 @@ function BASE:_F( Arguments, DebugInfoCurrentParam, DebugInfoFromParam )
if DebugInfoFrom then if DebugInfoFrom then
LineFrom = DebugInfoFrom.currentline LineFrom = DebugInfoFrom.currentline
end end
env.info( string.format( "%6d(%6d)/%1s:%20s%05d.%s(%s)" , LineCurrent, LineFrom, "F", self.ClassName, self.ClassID, Function, routines.utils.oneLineSerialize( Arguments ) ) ) env.info( string.format( "%6d(%6d)/%1s:%30s%05d.%s(%s)" , LineCurrent, LineFrom, "F", self.ClassName, self.ClassID, Function, routines.utils.oneLineSerialize( Arguments ) ) )
end end
end end
end end
@@ -1062,7 +1084,7 @@ function BASE:_T( Arguments, DebugInfoCurrentParam, DebugInfoFromParam )
if DebugInfoFrom then if DebugInfoFrom then
LineFrom = DebugInfoFrom.currentline LineFrom = DebugInfoFrom.currentline
end end
env.info( string.format( "%6d(%6d)/%1s:%20s%05d.%s" , LineCurrent, LineFrom, "T", self.ClassName, self.ClassID, routines.utils.oneLineSerialize( Arguments ) ) ) env.info( string.format( "%6d(%6d)/%1s:%30s%05d.%s" , LineCurrent, LineFrom, "T", self.ClassName, self.ClassID, routines.utils.oneLineSerialize( Arguments ) ) )
end end
end end
end end
@@ -1133,9 +1155,9 @@ function BASE:E( Arguments )
LineFrom = DebugInfoFrom.currentline LineFrom = DebugInfoFrom.currentline
end end
env.info( string.format( "%6d(%6d)/%1s:%20s%05d.%s(%s)" , LineCurrent, LineFrom, "E", self.ClassName, self.ClassID, Function, routines.utils.oneLineSerialize( Arguments ) ) ) env.info( string.format( "%6d(%6d)/%1s:%30s%05d.%s(%s)" , LineCurrent, LineFrom, "E", self.ClassName, self.ClassID, Function, routines.utils.oneLineSerialize( Arguments ) ) )
else else
env.info( string.format( "%1s:%20s%05d(%s)" , "E", self.ClassName, self.ClassID, routines.utils.oneLineSerialize( Arguments ) ) ) env.info( string.format( "%1s:%30s%05d(%s)" , "E", self.ClassName, self.ClassID, routines.utils.oneLineSerialize( Arguments ) ) )
end end
end end
@@ -1161,9 +1183,9 @@ function BASE:I( Arguments )
LineFrom = DebugInfoFrom.currentline LineFrom = DebugInfoFrom.currentline
end end
env.info( string.format( "%6d(%6d)/%1s:%20s%05d.%s(%s)" , LineCurrent, LineFrom, "I", self.ClassName, self.ClassID, Function, routines.utils.oneLineSerialize( Arguments ) ) ) env.info( string.format( "%6d(%6d)/%1s:%30s%05d.%s(%s)" , LineCurrent, LineFrom, "I", self.ClassName, self.ClassID, Function, routines.utils.oneLineSerialize( Arguments ) ) )
else else
env.info( string.format( "%1s:%20s%05d(%s)" , "I", self.ClassName, self.ClassID, routines.utils.oneLineSerialize( Arguments ) ) ) env.info( string.format( "%1s:%30s%05d(%s)" , "I", self.ClassName, self.ClassID, routines.utils.oneLineSerialize( Arguments ) ) )
end end
end end
+38 -7
View File
@@ -81,6 +81,7 @@ DATABASE = {
HITS = {}, HITS = {},
DESTROYS = {}, DESTROYS = {},
ZONES = {}, ZONES = {},
ZONES_GOAL = {},
WAREHOUSES = {}, WAREHOUSES = {},
FLIGHTGROUPS = {}, FLIGHTGROUPS = {},
FLIGHTCONTROLS = {}, FLIGHTCONTROLS = {},
@@ -189,6 +190,7 @@ end
function DATABASE:AddUnit( DCSUnitName ) function DATABASE:AddUnit( DCSUnitName )
if not self.UNITS[DCSUnitName] then if not self.UNITS[DCSUnitName] then
self:I( { "Add UNIT:", DCSUnitName } )
local UnitRegister = UNIT:Register( DCSUnitName ) local UnitRegister = UNIT:Register( DCSUnitName )
self.UNITS[DCSUnitName] = UNIT:Register( DCSUnitName ) self.UNITS[DCSUnitName] = UNIT:Register( DCSUnitName )
@@ -341,6 +343,39 @@ do -- Zones
end -- zone end -- zone
do -- Zone_Goal
--- Finds a @{Zone} based on the zone name.
-- @param #DATABASE self
-- @param #string ZoneName The name of the zone.
-- @return Core.Zone#ZONE_BASE The found ZONE.
function DATABASE:FindZoneGoal( ZoneName )
local ZoneFound = self.ZONES_GOAL[ZoneName]
return ZoneFound
end
--- Adds a @{Zone} based on the zone name in the DATABASE.
-- @param #DATABASE self
-- @param #string ZoneName The name of the zone.
-- @param Core.Zone#ZONE_BASE Zone The zone.
function DATABASE:AddZoneGoal( ZoneName, Zone )
if not self.ZONES_GOAL[ZoneName] then
self.ZONES_GOAL[ZoneName] = Zone
end
end
--- Deletes a @{Zone} from the DATABASE based on the zone name.
-- @param #DATABASE self
-- @param #string ZoneName The name of the zone.
function DATABASE:DeleteZoneGoal( ZoneName )
self.ZONES_GOAL[ZoneName] = nil
end
end -- Zone_Goal
do -- cargo do -- cargo
--- Adds a Cargo based on the Cargo Name in the DATABASE. --- Adds a Cargo based on the Cargo Name in the DATABASE.
@@ -1021,7 +1056,8 @@ function DATABASE:ForEach( IteratorFunction, FinalizeFunction, arg, Set )
return false return false
end end
local Scheduler = SCHEDULER:New( self, Schedule, {}, 0.001, 0.001, 0 ) --local Scheduler = SCHEDULER:New( self, Schedule, {}, 0.001, 0.001, 0 )
Schedule()
return self return self
end end
@@ -1247,7 +1283,7 @@ function DATABASE:_RegisterTemplates()
local CoalitionSide = coalition.side[string.upper(CoalitionName)] local CoalitionSide = coalition.side[string.upper(CoalitionName)]
if CoalitionName=="red" then if CoalitionName=="red" then
CoalitionSide=coalition.side.NEUTRAL CoalitionSide=coalition.side.RED
elseif CoalitionName=="blue" then elseif CoalitionName=="blue" then
CoalitionSide=coalition.side.BLUE CoalitionSide=coalition.side.BLUE
else else
@@ -1413,8 +1449,3 @@ end
self.DESTROYS[Event.IniUnitName] = true self.DESTROYS[Event.IniUnitName] = true
end end
end end
+47 -1
View File
@@ -189,7 +189,9 @@ world.event.S_EVENT_NEW_CARGO = world.event.S_EVENT_MAX + 1000
world.event.S_EVENT_DELETE_CARGO = world.event.S_EVENT_MAX + 1001 world.event.S_EVENT_DELETE_CARGO = world.event.S_EVENT_MAX + 1001
world.event.S_EVENT_NEW_ZONE = world.event.S_EVENT_MAX + 1002 world.event.S_EVENT_NEW_ZONE = world.event.S_EVENT_MAX + 1002
world.event.S_EVENT_DELETE_ZONE = world.event.S_EVENT_MAX + 1003 world.event.S_EVENT_DELETE_ZONE = world.event.S_EVENT_MAX + 1003
world.event.S_EVENT_REMOVE_UNIT = world.event.S_EVENT_MAX + 1004 world.event.S_EVENT_NEW_ZONE_GOAL = world.event.S_EVENT_MAX + 1004
world.event.S_EVENT_DELETE_ZONE_GOAL = world.event.S_EVENT_MAX + 1005
world.event.S_EVENT_REMOVE_UNIT = world.event.S_EVENT_MAX + 1006
--- The different types of events supported by MOOSE. --- The different types of events supported by MOOSE.
@@ -226,6 +228,8 @@ EVENTS = {
DeleteCargo = world.event.S_EVENT_DELETE_CARGO, DeleteCargo = world.event.S_EVENT_DELETE_CARGO,
NewZone = world.event.S_EVENT_NEW_ZONE, NewZone = world.event.S_EVENT_NEW_ZONE,
DeleteZone = world.event.S_EVENT_DELETE_ZONE, DeleteZone = world.event.S_EVENT_DELETE_ZONE,
NewZoneGoal = world.event.S_EVENT_NEW_ZONE_GOAL,
DeleteZoneGoal = world.event.S_EVENT_DELETE_ZONE_GOAL,
RemoveUnit = world.event.S_EVENT_REMOVE_UNIT, RemoveUnit = world.event.S_EVENT_REMOVE_UNIT,
} }
@@ -462,6 +466,16 @@ local _EVENTMETA = {
Event = "OnEventDeleteZone", Event = "OnEventDeleteZone",
Text = "S_EVENT_DELETE_ZONE" Text = "S_EVENT_DELETE_ZONE"
}, },
[EVENTS.NewZoneGoal] = {
Order = 1,
Event = "OnEventNewZoneGoal",
Text = "S_EVENT_NEW_ZONE_GOAL"
},
[EVENTS.DeleteZoneGoal] = {
Order = 1,
Event = "OnEventDeleteZoneGoal",
Text = "S_EVENT_DELETE_ZONE_GOAL"
},
[EVENTS.RemoveUnit] = { [EVENTS.RemoveUnit] = {
Order = -1, Order = -1,
Event = "OnEventRemoveUnit", Event = "OnEventRemoveUnit",
@@ -800,6 +814,38 @@ do -- Event Creation
world.onEvent( Event ) world.onEvent( Event )
end end
--- Creation of a New ZoneGoal Event.
-- @param #EVENT self
-- @param Core.Functional#ZONE_GOAL ZoneGoal The ZoneGoal created.
function EVENT:CreateEventNewZoneGoal( ZoneGoal )
self:F( { ZoneGoal } )
local Event = {
id = EVENTS.NewZoneGoal,
time = timer.getTime(),
ZoneGoal = ZoneGoal,
}
world.onEvent( Event )
end
--- Creation of a ZoneGoal Deletion Event.
-- @param #EVENT self
-- @param Core.ZoneGoal#ZONE_GOAL ZoneGoal The ZoneGoal created.
function EVENT:CreateEventDeleteZoneGoal( ZoneGoal )
self:F( { ZoneGoal } )
local Event = {
id = EVENTS.DeleteZoneGoal,
time = timer.getTime(),
ZoneGoal = ZoneGoal,
}
world.onEvent( Event )
end
--- Creation of a S_EVENT_PLAYER_ENTER_UNIT Event. --- Creation of a S_EVENT_PLAYER_ENTER_UNIT Event.
-- @param #EVENT self -- @param #EVENT self
-- @param Wrapper.Unit#UNIT PlayerUnit. -- @param Wrapper.Unit#UNIT PlayerUnit.
+13 -3
View File
@@ -594,7 +594,17 @@ do -- FSM
return errmsg return errmsg
end end
if self[handler] then if self[handler] then
self:T( "*** FSM *** " .. step .. " *** " .. params[1] .. " --> " .. params[2] .. " --> " .. params[3] ) if step == "onafter" or step == "OnAfter" then
self:T( ":::>" .. step .. params[2] .. " : " .. params[1] .. " >> " .. params[2] .. ">" .. step .. params[2] .. "()" .. " >> " .. params[3] )
elseif step == "onbefore" or step == "OnBefore" then
self:T( ":::>" .. step .. params[2] .. " : " .. params[1] .. " >> " .. step .. params[2] .. "()" .. ">" .. params[2] .. " >> " .. params[3] )
elseif step == "onenter" or step == "OnEnter" then
self:T( ":::>" .. step .. params[3] .. " : " .. params[1] .. " >> " .. params[2] .. " >> " .. step .. params[3] .. "()" .. ">" .. params[3] )
elseif step == "onleave" or step == "OnLeave" then
self:T( ":::>" .. step .. params[1] .. " : " .. params[1] .. ">" .. step .. params[1] .. "()" .. " >> " .. params[2] .. " >> " .. params[3] )
else
self:T( ":::>" .. step .. " : " .. params[1] .. " >> " .. params[2] .. " >> " .. params[3] )
end
self._EventSchedules[EventName] = nil self._EventSchedules[EventName] = nil
local Result, Value = xpcall( function() return self[handler]( self, unpack( params ) ) end, ErrorHandler ) local Result, Value = xpcall( function() return self[handler]( self, unpack( params ) ) end, ErrorHandler )
return Value return Value
@@ -718,13 +728,13 @@ do -- FSM
if DelaySeconds < 0 then -- Only call the event ONCE! if DelaySeconds < 0 then -- Only call the event ONCE!
DelaySeconds = math.abs( DelaySeconds ) DelaySeconds = math.abs( DelaySeconds )
if not self._EventSchedules[EventName] then if not self._EventSchedules[EventName] then
CallID = self.CallScheduler:Schedule( self, self._handler, { EventName, ... }, DelaySeconds or 1 ) CallID = self.CallScheduler:Schedule( self, self._handler, { EventName, ... }, DelaySeconds or 1, nil, nil, nil, 4, true )
self._EventSchedules[EventName] = CallID self._EventSchedules[EventName] = CallID
else else
-- reschedule -- reschedule
end end
else else
CallID = self.CallScheduler:Schedule( self, self._handler, { EventName, ... }, DelaySeconds or 1 ) CallID = self.CallScheduler:Schedule( self, self._handler, { EventName, ... }, DelaySeconds or 1, nil, nil, nil, 4, true )
end end
else else
error( "FSM: An asynchronous event trigger requires a DelaySeconds parameter!!! This can be positive or negative! Sorry, but will not process this." ) error( "FSM: An asynchronous event trigger requires a DelaySeconds parameter!!! This can be positive or negative! Sorry, but will not process this." )
+1 -1
View File
@@ -2075,7 +2075,7 @@ do -- COORDINATE
-- @return #string The coordinate Text in the configured coordinate system. -- @return #string The coordinate Text in the configured coordinate system.
function COORDINATE:ToString( Controllable, Settings, Task ) function COORDINATE:ToString( Controllable, Settings, Task )
self:E( { Controllable = Controllable and Controllable:GetName() } ) -- self:E( { Controllable = Controllable and Controllable:GetName() } )
local Settings = Settings or ( Controllable and _DATABASE:GetPlayerSettings( Controllable:GetPlayerName() ) ) or _SETTINGS local Settings = Settings or ( Controllable and _DATABASE:GetPlayerSettings( Controllable:GetPlayerName() ) ) or _SETTINGS
-466
View File
@@ -800,470 +800,4 @@ function BEACON:_TACANToFrequency(TACANChannel, TACANMode)
return (A + TACANChannel - B) * 1000000 return (A + TACANChannel - B) * 1000000
end end
--- Manages radio transmissions.
--
-- @type RADIOQUEUE
-- @field #string ClassName Name of the class "RADIOQUEUE".
-- @field #string lid ID for dcs.log.
-- @field #number frequency The radio frequency in Hz.
-- @field #number modulation The radio modulation. Either radio.modulation.AM or radio.modulation.FM.
-- @field Core.Scheduler#SCHEDULER scheduler The scheduler.
-- @field #string RQid The radio queue scheduler ID.
-- @field #table queue The queue of transmissions.
-- @field #number Tlast Time (abs) when the last transmission finished.
-- @field Core.Point#COORDINATE sendercoord Coordinate from where transmissions are broadcasted.
-- @field #number sendername Name of the sending unit or static.
-- @field Wrapper.Positionable#POSITIONABLE positionable The positionable to broadcast the message.
-- @field #number power Power of radio station in Watts. Default 100 W.
-- @field #table numbers Table of number transmission parameters.
-- @extends Core.Base#BASE
RADIOQUEUE = {
ClassName = "RADIOQUEUE",
lid=nil,
frequency=nil,
modulation=nil,
scheduler=nil,
RQid=nil,
queue={},
Tlast=nil,
sendercoord=nil,
sendername=nil,
positionable=nil,
power=100,
numbers={},
}
--- Radio queue transmission data.
-- @type RADIOQUEUE.Transmission
-- @field #string filename Name of the file to be transmitted.
-- @field #string path Path in miz file where the file is located.
-- @field #number duration Duration in seconds.
-- @field #number Tstarted Mission time (abs) in seconds when the transmission started.
-- @field #boolean isplaying If true, transmission is currently playing.
-- @field #number Tplay Mission time (abs) in seconds when the transmission should be played.
--- Create a new RADIOQUEUE object for a given radio frequency/modulation.
-- @param #RADIOQUEUE self
-- @param #number frequency The radio frequency in MHz.
-- @param #number modulation (Optional) The radio modulation. Default radio.modulation.AM.
-- @return #RADIOQUEUE self The RADIOQUEUE object.
function RADIOQUEUE:New(frequency, modulation)
-- Inherit base
local self=BASE:Inherit(self, BASE:New()) -- Core.Radio#RADIOQUEUE
self.lid="RADIOQUEUE | "
if frequency==nil then
self:E(self.lid.."ERROR: No frequency specified as first parameter!")
return nil
end
-- Frequency in Hz.
self.frequency=frequency*1000000
-- Modulation.
self.modulation=modulation or radio.modulation.AM
-- Scheduler
self.scheduler=SCHEDULER:New()
return self
end
--- Start the radio queue.
-- @param #RADIOQUEUE self
-- @param #number delay (Optional) Delay in seconds, before the radio queue is started. Default 1 sec.
-- @param #number dt (Optional) Time step in seconds for checking the queue. Default 0.01 sec.
-- @return #RADIOQUEUE self The RADIOQUEUE object.
function RADIOQUEUE:Start(delay, dt)
delay=delay or 1
dt=dt or 0.01
self:I(self.lid..string.format("Starting RADIOQUEUE in %.1f seconds with interval dt=%.3f seconds.", delay, dt))
self.RQid=self.scheduler:Schedule(self, self._CheckRadioQueue, {}, delay, dt)
return self
end
--- Stop the radio queue. Stop scheduler and delete queue.
-- @param #RADIOQUEUE self
-- @return #RADIOQUEUE self The RADIOQUEUE object.
function RADIOQUEUE:Stop()
self:I(self.lid.."Stopping RADIOQUEUE.")
self.scheduler:Stop(self.RQid)
self.queue={}
return self
end
--- Set coordinate from where the transmission is broadcasted.
-- @param #RADIOQUEUE self
-- @param Core.Point#COORDINATE coordinate Coordinate of the sender.
-- @return #RADIOQUEUE self The RADIOQUEUE object.
function RADIOQUEUE:SetSenderCoordinate(coordinate)
self.sendercoord=coordinate
return self
end
--- Set name of unit or static from which transmissions are made.
-- @param #RADIOQUEUE self
-- @param #string name Name of the unit or static used for transmissions.
-- @return #RADIOQUEUE self The RADIOQUEUE object.
function RADIOQUEUE:SetSenderUnitName(name)
self.sendername=name
return self
end
--- Set parameters of a digit.
-- @param #RADIOQUEUE self
-- @param #number digit The digit 0-9.
-- @param #string filename The name of the sound file.
-- @param #number duration The duration of the sound file in seconds.
-- @param #string path The directory within the miz file where the sound is located. Default "l10n/DEFAULT/".
-- @return #RADIOQUEUE self The RADIOQUEUE object.
function RADIOQUEUE:SetDigit(digit, filename, duration, path)
local transmission={} --#RADIOQUEUE.Transmission
transmission.filename=filename
transmission.duration=duration
transmission.path=path or "l10n/DEFAULT/"
-- Convert digit to string in case it is given as a number.
if type(digit)=="number" then
digit=tostring(digit)
end
-- Set transmission.
self.numbers[digit]=transmission
return self
end
--- Add a transmission to the radio queue.
-- @param #RADIOQUEUE self
-- @param #RADIOQUEUE.Transmission transmission The transmission data table.
-- @return #RADIOQUEUE self The RADIOQUEUE object.
function RADIOQUEUE:AddTransmission(transmission)
self:F({transmission=transmission})
-- Init.
transmission.isplaying=false
transmission.Tstarted=nil
-- Add to queue.
table.insert(self.queue, transmission)
return self
end
--- Add a transmission to the radio queue.
-- @param #RADIOQUEUE self
-- @param #string filename Name of the sound file. Usually an ogg or wav file type.
-- @param #number duration Duration in seconds the file lasts.
-- @param #number path Directory path inside the miz file where the sound file is located. Default "l10n/DEFAULT/".
-- @param #number tstart Start time (abs) seconds. Default now.
-- @param #number interval Interval in seconds after the last transmission finished.
-- @return #RADIOQUEUE self The RADIOQUEUE object.
function RADIOQUEUE:NewTransmission(filename, duration, path, tstart, interval)
-- Sanity checks.
if not filename then
self:E(self.lid.."ERROR: No filename specified.")
return nil
end
if type(filename)~="string" then
self:E(self.lid.."ERROR: Filename specified is NOT a string.")
return nil
end
if not duration then
self:E(self.lid.."ERROR: No duration of transmission specified.")
return nil
end
if type(duration)~="number" then
self:E(self.lid.."ERROR: Duration specified is NOT a number.")
return nil
end
local transmission={} --#RADIOQUEUE.Transmission
transmission.filename=filename
transmission.duration=duration
transmission.path=path or "l10n/DEFAULT/"
transmission.Tplay=tstart or timer.getAbsTime()
-- Add transmission to queue.
self:AddTransmission(transmission)
return self
end
--- Convert a number (as string) into a radio transmission.
-- E.g. for board number or headings.
-- @param #RADIOQUEUE self
-- @param #string number Number string, e.g. "032" or "183".
-- @param #number delay Delay before transmission in seconds.
-- @param #number interval Interval between the next call.
-- @return #number Duration of the call in seconds.
function RADIOQUEUE:Number2Transmission(number, delay, interval)
--- Split string into characters.
local function _split(str)
local chars={}
for i=1,#str do
local c=str:sub(i,i)
table.insert(chars, c)
end
return chars
end
-- Split string into characters.
local numbers=_split(number)
local wait=0
for i=1,#numbers do
-- Current number
local n=numbers[i]
-- Radio call.
local transmission=UTILS.DeepCopy(self.numbers[n]) --#RADIOQUEUE.Transmission
transmission.Tplay=timer.getAbsTime()+(delay or 0)
if interval and i==1 then
transmission.interval=interval
end
self:AddTransmission(transmission)
-- Add up duration of the number.
wait=wait+transmission.duration
end
-- Return the total duration of the call.
return wait
end
--- Broadcast radio message.
-- @param #RADIOQUEUE self
-- @param #RADIOQUEUE.Transmission transmission The transmission.
function RADIOQUEUE:Broadcast(transmission)
-- Get unit sending the transmission.
local sender=self:_GetRadioSender()
-- Construct file name.
local filename=string.format("%s%s", transmission.path, transmission.filename)
-- Create subtitle for transmission.
--local subtitle=self:_RadioSubtitle(radio, call, loud)
if sender then
-- Broadcasting from aircraft. Only players tuned in to the right frequency will see the message.
self:T(self.lid..string.format("Broadcasting from aircraft %s", sender:GetName()))
-- Command to set the Frequency for the transmission.
local commandFrequency={
id="SetFrequency",
params={
frequency=self.frequency, -- Frequency in Hz.
modulation=self.modulation,
}}
-- Command to tranmit the call.
local commandTransmit={
id = "TransmitMessage",
params = {
file=filename,
duration=transmission.subduration or 5,
subtitle=transmission.subtitle or "",
loop=false,
}}
-- Set commend for frequency
sender:SetCommand(commandFrequency)
-- Set command for radio transmission.
sender:SetCommand(commandTransmit)
else
-- Broadcasting from carrier. No subtitle possible. Need to send messages to players.
self:T(self.lid..string.format("Broadcasting from carrier via trigger.action.radioTransmission()."))
-- Position from where to transmit.
local vec3=nil
-- Try to get positon from sender unit/static.
if self.sendername then
local coord=self:_GetRadioSenderCoord()
if coord then
vec3=coord:GetVec3()
end
end
-- Try to get fixed positon.
if self.sendercoord and not vec3 then
vec3=self.sendercoord:GetVec3()
end
-- Transmit via trigger.
if vec3 then
trigger.action.radioTransmission(filename, vec3, self.modulation, false, self.frequency, self.power)
end
end
end
--- Check radio queue for transmissions to be broadcasted.
-- @param #RADIOQUEUE self
function RADIOQUEUE:_CheckRadioQueue()
-- Check if queue is empty.
if #self.queue==0 then
return
end
-- Get current abs time.
local time=timer.getAbsTime()
local playing=false
local next=nil --#RADIOQUEUE.Transmission
local remove=nil
for i,_transmission in ipairs(self.queue) do
local transmission=_transmission --#RADIOQUEUE.Transmission
-- Check if transmission time has passed.
if time>=transmission.Tplay then
-- Check if transmission is currently playing.
if transmission.isplaying then
-- Check if transmission is finished.
if time>=transmission.Tstarted+transmission.duration then
-- Transmission over.
transmission.isplaying=false
-- Remove ith element in queue.
remove=i
-- Store time last transmission finished.
self.Tlast=time
else -- still playing
-- Transmission is still playing.
playing=true
end
else -- not playing yet
local Tlast=self.Tlast
if transmission.interval==nil then
-- Not playing ==> this will be next.
if next==nil then
next=transmission
end
else
if Tlast==nil or time-Tlast>=transmission.interval then
next=transmission
else
end
end
-- We got a transmission or one with an interval that is not due yet. No need for anything else.
if next or Tlast then
break
end
end
else
-- Transmission not due yet.
end
end
-- Found a new transmission.
if next~=nil and not playing then
self:Broadcast(next)
next.isplaying=true
next.Tstarted=time
end
-- Remove completed calls from queue.
if remove then
table.remove(self.queue, remove)
end
end
--- Get unit from which we want to transmit a radio message. This has to be an aircraft for subtitles to work.
-- @param #RADIOQUEUE self
-- @return Wrapper.Unit#UNIT Sending aircraft unit or nil if was not setup, is not an aircraft or is not alive.
function RADIOQUEUE:_GetRadioSender()
-- Check if we have a sending aircraft.
local sender=nil --Wrapper.Unit#UNIT
-- Try the general default.
if self.sendername then
-- First try to find a unit
sender=UNIT:FindByName(self.sendername)
-- Check that sender is alive and an aircraft.
if sender and sender:IsAlive() and sender:IsAir() then
return sender
end
end
return nil
end
--- Get unit from which we want to transmit a radio message. This has to be an aircraft for subtitles to work.
-- @param #RADIOQUEUE self
-- @return Core.Point#COORDINATE Coordinate of the sender unit.
function RADIOQUEUE:_GetRadioSenderCoord()
local vec3=nil
-- Try the general default.
if self.sendername then
-- First try to find a unit
local sender=UNIT:FindByName(self.sendername)
-- Check that sender is alive and an aircraft.
if sender and sender:IsAlive() then
return sender:GetCoodinate()
end
-- Now try a static.
local sender=STATIC:FindByName(self.sendername)
-- Check that sender is alive and an aircraft.
if sender then
return sender:GetCoodinate()
end
end
return nil
end
+483
View File
@@ -0,0 +1,483 @@
--- **Core** - Queues Radio Transmissions.
--
-- ===
--
-- ## Features:
--
-- * Managed Radio Transmissions.
--
-- ===
--
-- ### Authors: funkyfranky
--
-- @module Core.RadioQueue
-- @image Core_Radio.JPG
--- Manages radio transmissions.
--
-- @type RADIOQUEUE
-- @field #string ClassName Name of the class "RADIOQUEUE".
-- @field #string lid ID for dcs.log.
-- @field #number frequency The radio frequency in Hz.
-- @field #number modulation The radio modulation. Either radio.modulation.AM or radio.modulation.FM.
-- @field Core.Scheduler#SCHEDULER scheduler The scheduler.
-- @field #string RQid The radio queue scheduler ID.
-- @field #table queue The queue of transmissions.
-- @field #number Tlast Time (abs) when the last transmission finished.
-- @field Core.Point#COORDINATE sendercoord Coordinate from where transmissions are broadcasted.
-- @field #number sendername Name of the sending unit or static.
-- @field Wrapper.Positionable#POSITIONABLE positionable The positionable to broadcast the message.
-- @field #number power Power of radio station in Watts. Default 100 W.
-- @field #table numbers Table of number transmission parameters.
-- @extends Core.Base#BASE
RADIOQUEUE = {
ClassName = "RADIOQUEUE",
lid=nil,
frequency=nil,
modulation=nil,
scheduler=nil,
RQid=nil,
queue={},
Tlast=nil,
sendercoord=nil,
sendername=nil,
positionable=nil,
power=100,
numbers={},
}
--- Radio queue transmission data.
-- @type RADIOQUEUE.Transmission
-- @field #string filename Name of the file to be transmitted.
-- @field #string path Path in miz file where the file is located.
-- @field #number duration Duration in seconds.
-- @field #number Tstarted Mission time (abs) in seconds when the transmission started.
-- @field #boolean isplaying If true, transmission is currently playing.
-- @field #number Tplay Mission time (abs) in seconds when the transmission should be played.
--- Create a new RADIOQUEUE object for a given radio frequency/modulation.
-- @param #RADIOQUEUE self
-- @param #number frequency The radio frequency in MHz.
-- @param #number modulation (Optional) The radio modulation. Default radio.modulation.AM.
-- @return #RADIOQUEUE self The RADIOQUEUE object.
function RADIOQUEUE:New(frequency, modulation)
-- Inherit base
local self=BASE:Inherit(self, BASE:New()) -- #RADIOQUEUE
self.lid="RADIOQUEUE | "
if frequency==nil then
self:E(self.lid.."ERROR: No frequency specified as first parameter!")
return nil
end
-- Frequency in Hz.
self.frequency=frequency*1000000
-- Modulation.
self.modulation=modulation or radio.modulation.AM
-- Scheduler
self.scheduler=SCHEDULER:New()
return self
end
--- Start the radio queue.
-- @param #RADIOQUEUE self
-- @param #number delay (Optional) Delay in seconds, before the radio queue is started. Default 1 sec.
-- @param #number dt (Optional) Time step in seconds for checking the queue. Default 0.01 sec.
-- @return #RADIOQUEUE self The RADIOQUEUE object.
function RADIOQUEUE:Start(delay, dt)
delay=delay or 1
dt=dt or 0.01
self:I(self.lid..string.format("Starting RADIOQUEUE in %.1f seconds with interval dt=%.3f seconds.", delay, dt))
self.RQid=self.scheduler:Schedule(self, self._CheckRadioQueue, {}, delay, dt)
return self
end
--- Stop the radio queue. Stop scheduler and delete queue.
-- @param #RADIOQUEUE self
-- @return #RADIOQUEUE self The RADIOQUEUE object.
function RADIOQUEUE:Stop()
self:I(self.lid.."Stopping RADIOQUEUE.")
self.scheduler:Stop(self.RQid)
self.queue={}
return self
end
--- Set coordinate from where the transmission is broadcasted.
-- @param #RADIOQUEUE self
-- @param Core.Point#COORDINATE coordinate Coordinate of the sender.
-- @return #RADIOQUEUE self The RADIOQUEUE object.
function RADIOQUEUE:SetSenderCoordinate(coordinate)
self.sendercoord=coordinate
return self
end
--- Set name of unit or static from which transmissions are made.
-- @param #RADIOQUEUE self
-- @param #string name Name of the unit or static used for transmissions.
-- @return #RADIOQUEUE self The RADIOQUEUE object.
function RADIOQUEUE:SetSenderUnitName(name)
self.sendername=name
return self
end
--- Set parameters of a digit.
-- @param #RADIOQUEUE self
-- @param #number digit The digit 0-9.
-- @param #string filename The name of the sound file.
-- @param #number duration The duration of the sound file in seconds.
-- @param #string path The directory within the miz file where the sound is located. Default "l10n/DEFAULT/".
-- @return #RADIOQUEUE self The RADIOQUEUE object.
function RADIOQUEUE:SetDigit(digit, filename, duration, path)
local transmission={} --#RADIOQUEUE.Transmission
transmission.filename=filename
transmission.duration=duration
transmission.path=path or "l10n/DEFAULT/"
-- Convert digit to string in case it is given as a number.
if type(digit)=="number" then
digit=tostring(digit)
end
-- Set transmission.
self.numbers[digit]=transmission
return self
end
--- Add a transmission to the radio queue.
-- @param #RADIOQUEUE self
-- @param #RADIOQUEUE.Transmission transmission The transmission data table.
-- @return #RADIOQUEUE self The RADIOQUEUE object.
function RADIOQUEUE:AddTransmission(transmission)
self:F({transmission=transmission})
-- Init.
transmission.isplaying=false
transmission.Tstarted=nil
-- Add to queue.
table.insert(self.queue, transmission)
return self
end
--- Add a transmission to the radio queue.
-- @param #RADIOQUEUE self
-- @param #string filename Name of the sound file. Usually an ogg or wav file type.
-- @param #number duration Duration in seconds the file lasts.
-- @param #number path Directory path inside the miz file where the sound file is located. Default "l10n/DEFAULT/".
-- @param #number tstart Start time (abs) seconds. Default now.
-- @param #number interval Interval in seconds after the last transmission finished.
-- @return #RADIOQUEUE self The RADIOQUEUE object.
function RADIOQUEUE:NewTransmission(filename, duration, path, tstart, interval)
-- Sanity checks.
if not filename then
self:E(self.lid.."ERROR: No filename specified.")
return nil
end
if type(filename)~="string" then
self:E(self.lid.."ERROR: Filename specified is NOT a string.")
return nil
end
if not duration then
self:E(self.lid.."ERROR: No duration of transmission specified.")
return nil
end
if type(duration)~="number" then
self:E(self.lid.."ERROR: Duration specified is NOT a number.")
return nil
end
local transmission={} --#RADIOQUEUE.Transmission
transmission.filename=filename
transmission.duration=duration
transmission.path=path or "l10n/DEFAULT/"
transmission.Tplay=tstart or timer.getAbsTime()
-- Add transmission to queue.
self:AddTransmission(transmission)
return self
end
--- Convert a number (as string) into a radio transmission.
-- E.g. for board number or headings.
-- @param #RADIOQUEUE self
-- @param #string number Number string, e.g. "032" or "183".
-- @param #number delay Delay before transmission in seconds.
-- @param #number interval Interval between the next call.
-- @return #number Duration of the call in seconds.
function RADIOQUEUE:Number2Transmission(number, delay, interval)
--- Split string into characters.
local function _split(str)
local chars={}
for i=1,#str do
local c=str:sub(i,i)
table.insert(chars, c)
end
return chars
end
-- Split string into characters.
local numbers=_split(number)
local wait=0
for i=1,#numbers do
-- Current number
local n=numbers[i]
-- Radio call.
local transmission=UTILS.DeepCopy(self.numbers[n]) --#RADIOQUEUE.Transmission
transmission.Tplay=timer.getAbsTime()+(delay or 0)
if interval and i==1 then
transmission.interval=interval
end
self:AddTransmission(transmission)
-- Add up duration of the number.
wait=wait+transmission.duration
end
-- Return the total duration of the call.
return wait
end
--- Broadcast radio message.
-- @param #RADIOQUEUE self
-- @param #RADIOQUEUE.Transmission transmission The transmission.
function RADIOQUEUE:Broadcast(transmission)
-- Get unit sending the transmission.
local sender=self:_GetRadioSender()
-- Construct file name.
local filename=string.format("%s%s", transmission.path, transmission.filename)
-- Create subtitle for transmission.
--local subtitle=self:_RadioSubtitle(radio, call, loud)
if sender then
-- Broadcasting from aircraft. Only players tuned in to the right frequency will see the message.
self:T(self.lid..string.format("Broadcasting from aircraft %s", sender:GetName()))
-- Command to set the Frequency for the transmission.
local commandFrequency={
id="SetFrequency",
params={
frequency=self.frequency, -- Frequency in Hz.
modulation=self.modulation,
}}
-- Command to tranmit the call.
local commandTransmit={
id = "TransmitMessage",
params = {
file=filename,
duration=transmission.subduration or 5,
subtitle=transmission.subtitle or "",
loop=false,
}}
-- Set commend for frequency
sender:SetCommand(commandFrequency)
-- Set command for radio transmission.
sender:SetCommand(commandTransmit)
else
-- Broadcasting from carrier. No subtitle possible. Need to send messages to players.
self:T(self.lid..string.format("Broadcasting from carrier via trigger.action.radioTransmission()."))
-- Position from where to transmit.
local vec3=nil
-- Try to get positon from sender unit/static.
if self.sendername then
local coord=self:_GetRadioSenderCoord()
if coord then
vec3=coord:GetVec3()
end
end
-- Try to get fixed positon.
if self.sendercoord and not vec3 then
vec3=self.sendercoord:GetVec3()
end
-- Transmit via trigger.
if vec3 then
self:E("Sending")
self:E( { filename = filename, vec3 = vec3, modulation = self.modulation, frequency = self.frequency, power = self.power } )
trigger.action.radioTransmission(filename, vec3, self.modulation, false, self.frequency, self.power)
end
end
end
--- Check radio queue for transmissions to be broadcasted.
-- @param #RADIOQUEUE self
function RADIOQUEUE:_CheckRadioQueue()
-- Check if queue is empty.
if #self.queue==0 then
return
end
-- Get current abs time.
local time=timer.getAbsTime()
local playing=false
local next=nil --#RADIOQUEUE.Transmission
local remove=nil
for i,_transmission in ipairs(self.queue) do
local transmission=_transmission --#RADIOQUEUE.Transmission
-- Check if transmission time has passed.
if time>=transmission.Tplay then
-- Check if transmission is currently playing.
if transmission.isplaying then
-- Check if transmission is finished.
if time>=transmission.Tstarted+transmission.duration then
-- Transmission over.
transmission.isplaying=false
-- Remove ith element in queue.
remove=i
-- Store time last transmission finished.
self.Tlast=time
else -- still playing
-- Transmission is still playing.
playing=true
end
else -- not playing yet
local Tlast=self.Tlast
if transmission.interval==nil then
-- Not playing ==> this will be next.
if next==nil then
next=transmission
end
else
if Tlast==nil or time-Tlast>=transmission.interval then
next=transmission
else
end
end
-- We got a transmission or one with an interval that is not due yet. No need for anything else.
if next or Tlast then
break
end
end
else
-- Transmission not due yet.
end
end
-- Found a new transmission.
if next~=nil and not playing then
self:Broadcast(next)
next.isplaying=true
next.Tstarted=time
end
-- Remove completed calls from queue.
if remove then
table.remove(self.queue, remove)
end
end
--- Get unit from which we want to transmit a radio message. This has to be an aircraft for subtitles to work.
-- @param #RADIOQUEUE self
-- @return Wrapper.Unit#UNIT Sending aircraft unit or nil if was not setup, is not an aircraft or is not alive.
function RADIOQUEUE:_GetRadioSender()
-- Check if we have a sending aircraft.
local sender=nil --Wrapper.Unit#UNIT
-- Try the general default.
if self.sendername then
-- First try to find a unit
sender=UNIT:FindByName(self.sendername)
-- Check that sender is alive and an aircraft.
if sender and sender:IsAlive() and sender:IsAir() then
return sender
end
end
return nil
end
--- Get unit from which we want to transmit a radio message. This has to be an aircraft for subtitles to work.
-- @param #RADIOQUEUE self
-- @return Core.Point#COORDINATE Coordinate of the sender unit.
function RADIOQUEUE:_GetRadioSenderCoord()
local vec3=nil
-- Try the general default.
if self.sendername then
-- First try to find a unit
local sender=UNIT:FindByName(self.sendername)
-- Check that sender is alive and an aircraft.
if sender and sender:IsAlive() then
return sender:GetCoordinate()
end
-- Now try a static.
local sender=STATIC:FindByName(self.sendername)
-- Check that sender is alive and an aircraft.
if sender then
return sender:GetCoordinate()
end
end
return nil
end
@@ -52,8 +52,8 @@ end
-- Nothing of this code should be modified without testing it thoroughly. -- Nothing of this code should be modified without testing it thoroughly.
-- @param #SCHEDULEDISPATCHER self -- @param #SCHEDULEDISPATCHER self
-- @param Core.Scheduler#SCHEDULER Scheduler -- @param Core.Scheduler#SCHEDULER Scheduler
function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleArguments, Start, Repeat, Randomize, Stop ) function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleArguments, Start, Repeat, Randomize, Stop, TraceLevel, Fsm )
self:F2( { Scheduler, ScheduleFunction, ScheduleArguments, Start, Repeat, Randomize, Stop } ) self:F2( { Scheduler, ScheduleFunction, ScheduleArguments, Start, Repeat, Randomize, Stop, TraceLevel } )
self.CallID = self.CallID + 1 self.CallID = self.CallID + 1
local CallID = self.CallID .. "#" .. ( Scheduler.MasterObject and Scheduler.MasterObject.GetClassNameAndID and Scheduler.MasterObject:GetClassNameAndID() or "" ) or "" local CallID = self.CallID .. "#" .. ( Scheduler.MasterObject and Scheduler.MasterObject.GetClassNameAndID and Scheduler.MasterObject:GetClassNameAndID() or "" ) or ""
@@ -85,10 +85,51 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
self.Schedule[Scheduler][CallID].Randomize = Randomize or 0 self.Schedule[Scheduler][CallID].Randomize = Randomize or 0
self.Schedule[Scheduler][CallID].Stop = Stop self.Schedule[Scheduler][CallID].Stop = Stop
-- This section handles the tracing of the scheduled calls.
-- Because these calls will be executed with a delay, we inspect the place where these scheduled calls are initiated.
-- The Info structure contains the output of the debug.getinfo() calls, which inspects the call stack for the function name, line number and source name.
-- The call stack has many levels, and the correct semantical function call depends on where in the code AddSchedule was "used".
-- - Using SCHEDULER:New()
-- - Using Schedule:AddSchedule()
-- - Using Fsm:__Func()
-- - Using Class:ScheduleOnce()
-- - Using Class:ScheduleRepeat()
-- - ...
-- So for each of these scheduled call variations, AddSchedule is the workhorse which will schedule the call.
-- But the correct level with the correct semantical function location will differ depending on the above scheduled call invocation forms.
-- That's where the field TraceLevel contains optionally the level in the call stack where the call information is obtained.
-- The TraceLevel field indicates the correct level where the semantical scheduled call was invoked within the source, ensuring that function name, line number and source name are correct.
-- There is one quick ...
-- The FSM class models scheduled calls using the __Func syntax. However, these functions are "tailed".
-- There aren't defined anywhere within the source code, but rather implemented as triggers within the FSM logic,
-- and using the onbefore, onafter, onenter, onleave prefixes. (See the FSM for details).
-- Therefore, in the call stack, at the TraceLevel these functions are mentioned as "tail calls", and the Info.name field will be nil as a result.
-- To obtain the correct function name for FSM object calls, the function is mentioned in the call stack at a higher stack level.
-- So when function name stored in Info.name is nil, then I inspect the function name within the call stack one level higher.
-- So this little piece of code does its magic wonderfully, preformance overhead is neglectible, as scheduled calls don't happen that often.
local Info = {}
if debug then
TraceLevel = TraceLevel or 2
Info = debug.getinfo( TraceLevel, "nlS" )
local name_fsm = debug.getinfo( TraceLevel - 1, "n" ).name -- #string
if name_fsm then
Info.name = name_fsm
end
--env.info( debug.traceback() )
end
self:T3( self.Schedule[Scheduler][CallID] ) self:T3( self.Schedule[Scheduler][CallID] )
self.Schedule[Scheduler][CallID].CallHandler = function( CallID ) self.Schedule[Scheduler][CallID].CallHandler = function( Params )
--self:E( CallID )
local CallID = Params.CallID
local Info = Params.Info
local Source = Info.source or "?"
local Line = Info.currentline or "?"
local Name = Info.name or "?"
local ErrorHandler = function( errmsg ) local ErrorHandler = function( errmsg )
env.info( "Error in timer function: " .. errmsg ) env.info( "Error in timer function: " .. errmsg )
@@ -122,15 +163,19 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
local Stop = Schedule.Stop or 0 local Stop = Schedule.Stop or 0
local ScheduleID = Schedule.ScheduleID local ScheduleID = Schedule.ScheduleID
local Prefix = ( Repeat == 0 ) and "--->" or "+++>"
local Status, Result local Status, Result
--self:E( { SchedulerObject = SchedulerObject } ) --self:E( { SchedulerObject = SchedulerObject } )
if SchedulerObject then if SchedulerObject then
local function Timer() local function Timer()
SchedulerObject:T( Prefix .. Name .. ":" .. Line .. " (" .. Source .. ")" )
return ScheduleFunction( SchedulerObject, unpack( ScheduleArguments ) ) return ScheduleFunction( SchedulerObject, unpack( ScheduleArguments ) )
end end
Status, Result = xpcall( Timer, ErrorHandler ) Status, Result = xpcall( Timer, ErrorHandler )
else else
local function Timer() local function Timer()
self:T( Prefix .. Name .. ":" .. Line .. " (" .. Source .. ")" )
return ScheduleFunction( unpack( ScheduleArguments ) ) return ScheduleFunction( unpack( ScheduleArguments ) )
end end
Status, Result = xpcall( Timer, ErrorHandler ) Status, Result = xpcall( Timer, ErrorHandler )
@@ -161,13 +206,13 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr
self:Stop( Scheduler, CallID ) self:Stop( Scheduler, CallID )
end end
else else
self:E( "Scheduled obsolete call for CallID: " .. CallID ) self:I( "<<<>" .. Name .. ":" .. Line .. " (" .. Source .. ")" )
end end
return nil return nil
end end
self:Start( Scheduler, CallID ) self:Start( Scheduler, CallID, Info )
return CallID return CallID
end end
@@ -181,7 +226,7 @@ function SCHEDULEDISPATCHER:RemoveSchedule( Scheduler, CallID )
end end
end end
function SCHEDULEDISPATCHER:Start( Scheduler, CallID ) function SCHEDULEDISPATCHER:Start( Scheduler, CallID, Info )
self:F2( { Start = CallID, Scheduler = Scheduler } ) self:F2( { Start = CallID, Scheduler = Scheduler } )
if CallID then if CallID then
@@ -192,13 +237,13 @@ function SCHEDULEDISPATCHER:Start( Scheduler, CallID )
Schedule[CallID].StartTime = timer.getTime() -- Set the StartTime field to indicate when the scheduler started. Schedule[CallID].StartTime = timer.getTime() -- Set the StartTime field to indicate when the scheduler started.
Schedule[CallID].ScheduleID = timer.scheduleFunction( Schedule[CallID].ScheduleID = timer.scheduleFunction(
Schedule[CallID].CallHandler, Schedule[CallID].CallHandler,
CallID, { CallID = CallID, Info = Info },
timer.getTime() + Schedule[CallID].Start timer.getTime() + Schedule[CallID].Start
) )
end end
else else
for CallID, Schedule in pairs( self.Schedule[Scheduler] or {} ) do for CallID, Schedule in pairs( self.Schedule[Scheduler] or {} ) do
self:Start( Scheduler, CallID ) -- Recursive self:Start( Scheduler, CallID, Info ) -- Recursive
end end
end end
end end
+5 -3
View File
@@ -216,7 +216,7 @@ function SCHEDULER:New( SchedulerObject, SchedulerFunction, SchedulerArguments,
self.MasterObject = SchedulerObject self.MasterObject = SchedulerObject
if SchedulerFunction then if SchedulerFunction then
ScheduleID = self:Schedule( SchedulerObject, SchedulerFunction, SchedulerArguments, Start, Repeat, RandomizeFactor, Stop ) ScheduleID = self:Schedule( SchedulerObject, SchedulerFunction, SchedulerArguments, Start, Repeat, RandomizeFactor, Stop, 4 )
end end
return self, ScheduleID return self, ScheduleID
@@ -238,7 +238,7 @@ end
-- @param #number RandomizeFactor Specifies a randomization factor between 0 and 1 to randomize the Repeat. -- @param #number RandomizeFactor Specifies a randomization factor between 0 and 1 to randomize the Repeat.
-- @param #number Stop Specifies the amount of seconds when the scheduler will be stopped. -- @param #number Stop Specifies the amount of seconds when the scheduler will be stopped.
-- @return #number The ScheduleID of the planned schedule. -- @return #number The ScheduleID of the planned schedule.
function SCHEDULER:Schedule( SchedulerObject, SchedulerFunction, SchedulerArguments, Start, Repeat, RandomizeFactor, Stop ) function SCHEDULER:Schedule( SchedulerObject, SchedulerFunction, SchedulerArguments, Start, Repeat, RandomizeFactor, Stop, TraceLevel, Fsm )
self:F2( { Start, Repeat, RandomizeFactor, Stop } ) self:F2( { Start, Repeat, RandomizeFactor, Stop } )
self:T3( { SchedulerArguments } ) self:T3( { SchedulerArguments } )
@@ -256,7 +256,9 @@ function SCHEDULER:Schedule( SchedulerObject, SchedulerFunction, SchedulerArgume
Start, Start,
Repeat, Repeat,
RandomizeFactor, RandomizeFactor,
Stop Stop,
TraceLevel or 3,
Fsm
) )
self.Schedules[#self.Schedules+1] = ScheduleID self.Schedules[#self.Schedules+1] = ScheduleID
+320 -1
View File
@@ -384,7 +384,6 @@ do -- SET_BASE
for ObjectName, Object in pairs( self.Database ) do for ObjectName, Object in pairs( self.Database ) do
if self:IsIncludeObject( Object ) then if self:IsIncludeObject( Object ) then
self:E( { "Adding Object:", ObjectName } )
self:Add( ObjectName, Object ) self:Add( ObjectName, Object )
end end
end end
@@ -5498,3 +5497,323 @@ do -- SET_ZONE
end end
end end
do -- SET_ZONE_GOAL
--- @type SET_ZONE_GOAL
-- @extends Core.Set#SET_BASE
--- Mission designers can use the @{Core.Set#SET_ZONE_GOAL} class to build sets of zones of various types.
--
-- ## SET_ZONE_GOAL constructor
--
-- Create a new SET_ZONE_GOAL object with the @{#SET_ZONE_GOAL.New} method:
--
-- * @{#SET_ZONE_GOAL.New}: Creates a new SET_ZONE_GOAL object.
--
-- ## Add or Remove ZONEs from SET_ZONE_GOAL
--
-- ZONEs can be added and removed using the @{Core.Set#SET_ZONE_GOAL.AddZonesByName} and @{Core.Set#SET_ZONE_GOAL.RemoveZonesByName} respectively.
-- These methods take a single ZONE name or an array of ZONE names to be added or removed from SET_ZONE_GOAL.
--
-- ## SET_ZONE_GOAL filter criteria
--
-- You can set filter criteria to build the collection of zones in SET_ZONE_GOAL.
-- Filter criteria are defined by:
--
-- * @{#SET_ZONE_GOAL.FilterPrefixes}: Builds the SET_ZONE_GOAL with the zones having a certain text pattern of prefix.
--
-- Once the filter criteria have been set for the SET_ZONE_GOAL, you can start filtering using:
--
-- * @{#SET_ZONE_GOAL.FilterStart}: Starts the filtering of the zones within the SET_ZONE_GOAL.
--
-- ## SET_ZONE_GOAL iterators
--
-- Once the filters have been defined and the SET_ZONE_GOAL has been built, you can iterate the SET_ZONE_GOAL with the available iterator methods.
-- The iterator methods will walk the SET_ZONE_GOAL set, and call for each airbase within the set a function that you provide.
-- The following iterator methods are currently available within the SET_ZONE_GOAL:
--
-- * @{#SET_ZONE_GOAL.ForEachZone}: Calls a function for each zone it finds within the SET_ZONE_GOAL.
--
-- ===
-- @field #SET_ZONE_GOAL SET_ZONE_GOAL
SET_ZONE_GOAL = {
ClassName = "SET_ZONE_GOAL",
Zones = {},
Filter = {
Prefixes = nil,
},
FilterMeta = {
},
}
--- Creates a new SET_ZONE_GOAL object, building a set of zones.
-- @param #SET_ZONE_GOAL self
-- @return #SET_ZONE_GOAL self
-- @usage
-- -- Define a new SET_ZONE_GOAL Object. The DatabaseSet will contain a reference to all Zones.
-- DatabaseSet = SET_ZONE_GOAL:New()
function SET_ZONE_GOAL:New()
-- Inherits from BASE
local self = BASE:Inherit( self, SET_BASE:New( _DATABASE.ZONES_GOAL ) )
return self
end
--- Add ZONEs to SET_ZONE_GOAL.
-- @param Core.Set#SET_ZONE_GOAL self
-- @param Core.Zone#ZONE_BASE Zone A ZONE_BASE object.
-- @return self
function SET_ZONE_GOAL:AddZone( Zone )
self:Add( Zone:GetName(), Zone )
return self
end
--- Remove ZONEs from SET_ZONE_GOAL.
-- @param Core.Set#SET_ZONE_GOAL self
-- @param Core.Zone#ZONE_BASE RemoveZoneNames A single name or an array of ZONE_BASE names.
-- @return self
function SET_ZONE_GOAL:RemoveZonesByName( RemoveZoneNames )
local RemoveZoneNamesArray = ( type( RemoveZoneNames ) == "table" ) and RemoveZoneNames or { RemoveZoneNames }
for RemoveZoneID, RemoveZoneName in pairs( RemoveZoneNamesArray ) do
self:Remove( RemoveZoneName )
end
return self
end
--- Finds a Zone based on the Zone Name.
-- @param #SET_ZONE_GOAL self
-- @param #string ZoneName
-- @return Core.Zone#ZONE_BASE The found Zone.
function SET_ZONE_GOAL:FindZone( ZoneName )
local ZoneFound = self.Set[ZoneName]
return ZoneFound
end
--- Get a random zone from the set.
-- @param #SET_ZONE_GOAL self
-- @return Core.Zone#ZONE_BASE The random Zone.
-- @return #nil if no zone in the collection.
function SET_ZONE_GOAL:GetRandomZone()
if self:Count() ~= 0 then
local Index = self.Index
local ZoneFound = nil -- Core.Zone#ZONE_BASE
-- Loop until a zone has been found.
-- The :GetZoneMaybe() call will evaluate the probability for the zone to be selected.
-- If the zone is not selected, then nil is returned by :GetZoneMaybe() and the loop continues!
while not ZoneFound do
local ZoneRandom = math.random( 1, #Index )
ZoneFound = self.Set[Index[ZoneRandom]]:GetZoneMaybe()
end
return ZoneFound
end
return nil
end
--- Set a zone probability.
-- @param #SET_ZONE_GOAL self
-- @param #string ZoneName The name of the zone.
function SET_ZONE_GOAL:SetZoneProbability( ZoneName, ZoneProbability )
local Zone = self:FindZone( ZoneName )
Zone:SetZoneProbability( ZoneProbability )
end
--- Builds a set of zones of defined zone prefixes.
-- All the zones starting with the given prefixes will be included within the set.
-- @param #SET_ZONE_GOAL self
-- @param #string Prefixes The prefix of which the zone name starts with.
-- @return #SET_ZONE_GOAL self
function SET_ZONE_GOAL:FilterPrefixes( Prefixes )
if not self.Filter.Prefixes then
self.Filter.Prefixes = {}
end
if type( Prefixes ) ~= "table" then
Prefixes = { Prefixes }
end
for PrefixID, Prefix in pairs( Prefixes ) do
self.Filter.Prefixes[Prefix] = Prefix
end
return self
end
--- Starts the filtering.
-- @param #SET_ZONE_GOAL self
-- @return #SET_ZONE_GOAL self
function SET_ZONE_GOAL:FilterStart()
if _DATABASE then
-- We initialize the first set.
for ObjectName, Object in pairs( self.Database ) do
if self:IsIncludeObject( Object ) then
self:Add( ObjectName, Object )
else
self:RemoveZonesByName( ObjectName )
end
end
end
self:HandleEvent( EVENTS.NewZoneGoal )
self:HandleEvent( EVENTS.DeleteZoneGoal )
return self
end
--- Stops the filtering for the defined collection.
-- @param #SET_ZONE_GOAL self
-- @return #SET_ZONE_GOAL self
function SET_ZONE_GOAL:FilterStop()
self:UnHandleEvent( EVENTS.NewZoneGoal )
self:UnHandleEvent( EVENTS.DeleteZoneGoal )
return self
end
--- Handles the Database to check on an event (birth) that the Object was added in the Database.
-- This is required, because sometimes the _DATABASE birth event gets called later than the SET_BASE birth event!
-- @param #SET_ZONE_GOAL self
-- @param Core.Event#EVENTDATA Event
-- @return #string The name of the AIRBASE
-- @return #table The AIRBASE
function SET_ZONE_GOAL:AddInDatabase( Event )
self:F3( { Event } )
return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName]
end
--- Handles the Database to check on any event that Object exists in the Database.
-- This is required, because sometimes the _DATABASE event gets called later than the SET_BASE event or vise versa!
-- @param #SET_ZONE_GOAL self
-- @param Core.Event#EVENTDATA Event
-- @return #string The name of the AIRBASE
-- @return #table The AIRBASE
function SET_ZONE_GOAL:FindInDatabase( Event )
self:F3( { Event } )
return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName]
end
--- Iterate the SET_ZONE_GOAL and call an interator function for each ZONE, providing the ZONE and optional parameters.
-- @param #SET_ZONE_GOAL self
-- @param #function IteratorFunction The function that will be called when there is an alive ZONE in the SET_ZONE_GOAL. The function needs to accept a AIRBASE parameter.
-- @return #SET_ZONE_GOAL self
function SET_ZONE_GOAL:ForEachZone( IteratorFunction, ... )
self:F2( arg )
self:ForEach( IteratorFunction, arg, self:GetSet() )
return self
end
---
-- @param #SET_ZONE_GOAL self
-- @param Core.Zone#ZONE_BASE MZone
-- @return #SET_ZONE_GOAL self
function SET_ZONE_GOAL:IsIncludeObject( MZone )
self:F2( MZone )
local MZoneInclude = true
if MZone then
local MZoneName = MZone:GetName()
if self.Filter.Prefixes then
local MZonePrefix = false
for ZonePrefixId, ZonePrefix in pairs( self.Filter.Prefixes ) do
self:T3( { "Prefix:", string.find( MZoneName, ZonePrefix, 1 ), ZonePrefix } )
if string.find( MZoneName, ZonePrefix, 1 ) then
MZonePrefix = true
end
end
self:T( { "Evaluated Prefix", MZonePrefix } )
MZoneInclude = MZoneInclude and MZonePrefix
end
end
self:T2( MZoneInclude )
return MZoneInclude
end
--- Handles the OnEventNewZone event for the Set.
-- @param #SET_ZONE_GOAL self
-- @param Core.Event#EVENTDATA EventData
function SET_ZONE_GOAL:OnEventNewZoneGoal( EventData )
self:I( { "New Zone Capture Coalition", EventData } )
self:I( { "Zone Capture Coalition", EventData.ZoneGoal } )
if EventData.ZoneGoal then
if EventData.ZoneGoal and self:IsIncludeObject( EventData.ZoneGoal ) then
self:I( { "Adding Zone Capture Coalition", EventData.ZoneGoal.ZoneName, EventData.ZoneGoal } )
self:Add( EventData.ZoneGoal.ZoneName , EventData.ZoneGoal )
end
end
end
--- Handles the OnDead or OnCrash event for alive units set.
-- @param #SET_ZONE_GOAL self
-- @param Core.Event#EVENTDATA EventData
function SET_ZONE_GOAL:OnEventDeleteZoneGoal( EventData ) --R2.1
self:F3( { EventData } )
if EventData.ZoneGoal then
local Zone = _DATABASE:FindZone( EventData.ZoneGoal.ZoneName )
if Zone and Zone.ZoneName then
-- When cargo was deleted, it may probably be because of an S_EVENT_DEAD.
-- However, in the loading logic, an S_EVENT_DEAD is also generated after a Destroy() call.
-- And this is a problem because it will remove all entries from the SET_ZONE_GOALs.
-- To prevent this from happening, the Zone object has a flag NoDestroy.
-- When true, the SET_ZONE_GOAL won't Remove the Zone object from the set.
-- This flag is switched off after the event handlers have been called in the EVENT class.
self:F( { ZoneNoDestroy=Zone.NoDestroy } )
if Zone.NoDestroy then
else
self:Remove( Zone.ZoneName )
end
end
end
end
--- Validate if a coordinate is in one of the zones in the set.
-- Returns the ZONE object where the coordiante is located.
-- If zones overlap, the first zone that validates the test is returned.
-- @param #SET_ZONE_GOAL self
-- @param Core.Point#COORDINATE Coordinate The coordinate to be searched.
-- @return Core.Zone#ZONE_BASE The zone that validates the coordinate location.
-- @return #nil No zone has been found.
function SET_ZONE_GOAL:IsCoordinateInZone( Coordinate )
for _, Zone in pairs( self:GetSet() ) do
local Zone = Zone -- Core.Zone#ZONE_BASE
if Zone:IsCoordinateInZone( Coordinate ) then
return Zone
end
end
return nil
end
end
+68 -2
View File
@@ -194,6 +194,19 @@
-- - @{#SETTINGS.SetMessageTime}(): Define for a specific @{Message.MESSAGE.MessageType} the duration to be displayed in seconds. -- - @{#SETTINGS.SetMessageTime}(): Define for a specific @{Message.MESSAGE.MessageType} the duration to be displayed in seconds.
-- - @{#SETTINGS.GetMessageTime}(): Retrieves for a specific @{Message.MESSAGE.MessageType} the duration to be displayed in seconds. -- - @{#SETTINGS.GetMessageTime}(): Retrieves for a specific @{Message.MESSAGE.MessageType} the duration to be displayed in seconds.
-- --
-- ## 3.5) **Era** of the battle
--
-- The threat level metric is scaled according the era of the battle. A target that is AAA, will pose a much greather threat in WWII than on modern warfare.
-- Therefore, there are 4 era that are defined within the settings:
--
-- - **WWII** era: Use for warfare with equipment during the world war II time.
-- - **Korea** era: Use for warfare with equipment during the Korea war time.
-- - **Cold War** era: Use for warfare with equipment during the cold war time.
-- - **Modern** era: Use for warfare with modern equipment in the 2000s.
--
-- There are different API defined that you can use with the _SETTINGS object to configure your mission script to work in one of the 4 era:
-- @{#SETTINGS.SetEraWWII}(), @{#SETTINGS.SetEraKorea}(), @{#SETTINGS.SetEraCold}(), @{#SETTINGS.SetEraModern}()
--
-- === -- ===
-- --
-- @field #SETTINGS -- @field #SETTINGS
@@ -204,6 +217,19 @@ SETTINGS = {
MenuStatic = true, MenuStatic = true,
} }
SETTINGS.__Enum = {}
--- @type SETTINGS.__Enum.Era
-- @field #number WWII
-- @field #number Korea
-- @field #number Cold
-- @field #number Modern
SETTINGS.__Enum.Era = {
WWII = 1,
Korea = 2,
Cold = 3,
Modern = 4,
}
do -- SETTINGS do -- SETTINGS
@@ -225,6 +251,7 @@ do -- SETTINGS
self:SetMessageTime( MESSAGE.Type.Information, 30 ) self:SetMessageTime( MESSAGE.Type.Information, 30 )
self:SetMessageTime( MESSAGE.Type.Overview, 60 ) self:SetMessageTime( MESSAGE.Type.Overview, 60 )
self:SetMessageTime( MESSAGE.Type.Update, 15 ) self:SetMessageTime( MESSAGE.Type.Update, 15 )
self:SetEraModern()
self:SetMenutextShort(true) self:SetMenutextShort(true)
self:SetMenuStatic(true) self:SetMenuStatic(true)
return self return self
@@ -1010,6 +1037,45 @@ do -- SETTINGS
end end
--- Configures the era of the mission to be WWII.
-- @param #SETTINGS self
-- @return #SETTINGS self
function SETTINGS:SetEraWWII()
self.Era = SETTINGS.__Enum.Era.WWII
end
--- Configures the era of the mission to be Korea.
-- @param #SETTINGS self
-- @return #SETTINGS self
function SETTINGS:SetEraKorea()
self.Era = SETTINGS.__Enum.Era.Korea
end
--- Configures the era of the mission to be Cold war.
-- @param #SETTINGS self
-- @return #SETTINGS self
function SETTINGS:SetEraCold()
self.Era = SETTINGS.__Enum.Era.Cold
end
--- Configures the era of the mission to be Modern war.
-- @param #SETTINGS self
-- @return #SETTINGS self
function SETTINGS:SetEraModern()
self.Era = SETTINGS.__Enum.Era.Modern
end
end end
+2 -2
View File
@@ -120,7 +120,7 @@ ZONE_BASE = {
-- @param #string ZoneName Name of the zone. -- @param #string ZoneName Name of the zone.
-- @return #ZONE_BASE self -- @return #ZONE_BASE self
function ZONE_BASE:New( ZoneName ) function ZONE_BASE:New( ZoneName )
local self = BASE:Inherit( self, BASE:New() ) local self = BASE:Inherit( self, FSM:New() )
self:F( ZoneName ) self:F( ZoneName )
self.ZoneName = ZoneName self.ZoneName = ZoneName
@@ -1393,7 +1393,7 @@ end
function ZONE_POLYGON_BASE:Flush() function ZONE_POLYGON_BASE:Flush()
self:F2() self:F2()
self:E( { Polygon = self.ZoneName, Coordinates = self._.Polygon } ) self:F( { Polygon = self.ZoneName, Coordinates = self._.Polygon } )
return self return self
end end
@@ -1682,9 +1682,7 @@ do -- DETECTION_BASE
-- @return #DETECTION_BASE.DetectedItem -- @return #DETECTION_BASE.DetectedItem
function DETECTION_BASE:GetDetectedItemByIndex( Index ) function DETECTION_BASE:GetDetectedItemByIndex( Index )
self:I( { DetectedItemsByIndex = self.DetectedItemsByIndex } ) self:F( { self.DetectedItemsByIndex } )
self:I( { self.DetectedItemsByIndex } )
local DetectedItem = self.DetectedItemsByIndex[Index] local DetectedItem = self.DetectedItemsByIndex[Index]
if DetectedItem then if DetectedItem then
@@ -549,6 +549,9 @@ do -- ZONE_CAPTURE_COALITION
-- If it is, then we must move the zone to attack state. -- If it is, then we must move the zone to attack state.
self:HandleEvent( EVENTS.Hit, self.OnEventHit ) self:HandleEvent( EVENTS.Hit, self.OnEventHit )
-- ZoneGoal objects are added to the _DATABASE.ZONES_GOAL and SET_ZONE_GOAL sets.
_EVENTDISPATCHER:CreateEventNewZoneGoal( self )
return self return self
end end
@@ -566,7 +569,7 @@ do -- ZONE_CAPTURE_COALITION
function ZONE_CAPTURE_COALITION:IsGuarded() function ZONE_CAPTURE_COALITION:IsGuarded()
local IsGuarded = self.Zone:IsAllInZoneOfCoalition( self.Coalition ) local IsGuarded = self:IsAllInZoneOfCoalition( self.Coalition )
self:F( { IsGuarded = IsGuarded } ) self:F( { IsGuarded = IsGuarded } )
return IsGuarded return IsGuarded
end end
@@ -574,7 +577,7 @@ do -- ZONE_CAPTURE_COALITION
function ZONE_CAPTURE_COALITION:IsEmpty() function ZONE_CAPTURE_COALITION:IsEmpty()
local IsEmpty = self.Zone:IsNoneInZone() local IsEmpty = self:IsNoneInZone()
self:F( { IsEmpty = IsEmpty } ) self:F( { IsEmpty = IsEmpty } )
return IsEmpty return IsEmpty
end end
@@ -582,7 +585,7 @@ do -- ZONE_CAPTURE_COALITION
function ZONE_CAPTURE_COALITION:IsCaptured() function ZONE_CAPTURE_COALITION:IsCaptured()
local IsCaptured = self.Zone:IsAllInZoneOfOtherCoalition( self.Coalition ) local IsCaptured = self:IsAllInZoneOfOtherCoalition( self.Coalition )
self:F( { IsCaptured = IsCaptured } ) self:F( { IsCaptured = IsCaptured } )
return IsCaptured return IsCaptured
end end
@@ -590,7 +593,7 @@ do -- ZONE_CAPTURE_COALITION
function ZONE_CAPTURE_COALITION:IsAttacked() function ZONE_CAPTURE_COALITION:IsAttacked()
local IsAttacked = self.Zone:IsSomeInZoneOfCoalition( self.Coalition ) local IsAttacked = self:IsSomeInZoneOfCoalition( self.Coalition )
self:F( { IsAttacked = IsAttacked } ) self:F( { IsAttacked = IsAttacked } )
return IsAttacked return IsAttacked
end end
@@ -601,7 +604,7 @@ do -- ZONE_CAPTURE_COALITION
-- @param #ZONE_CAPTURE_COALITION self -- @param #ZONE_CAPTURE_COALITION self
function ZONE_CAPTURE_COALITION:Mark() function ZONE_CAPTURE_COALITION:Mark()
local Coord = self.Zone:GetCoordinate() local Coord = self:GetCoordinate()
local ZoneName = self:GetZoneName() local ZoneName = self:GetZoneName()
local State = self:GetState() local State = self:GetState()
@@ -640,7 +643,7 @@ do -- ZONE_CAPTURE_COALITION
--self:GetParent( self ):onenterCaptured() --self:GetParent( self ):onenterCaptured()
local NewCoalition = self.Zone:GetScannedCoalition() local NewCoalition = self:GetScannedCoalition()
self:F( { NewCoalition = NewCoalition } ) self:F( { NewCoalition = NewCoalition } )
self:SetCoalition( NewCoalition ) self:SetCoalition( NewCoalition )
@@ -673,14 +676,14 @@ do -- ZONE_CAPTURE_COALITION
--BASE:GetParent( self ).onafterGuard( self ) --BASE:GetParent( self ).onafterGuard( self )
if not self.SmokeScheduler then if not self.SmokeScheduler then
self.SmokeScheduler = self:ScheduleRepeat( 1, 1, 0.1, nil, self.StatusSmoke, self ) self.SmokeScheduler = self:ScheduleRepeat( self.StartInterval, self.RepeatInterval, 0.1, nil, self.StatusSmoke, self )
end end
end end
function ZONE_CAPTURE_COALITION:IsCaptured() function ZONE_CAPTURE_COALITION:IsCaptured()
local IsCaptured = self.Zone:IsAllInZoneOfOtherCoalition( self.Coalition ) local IsCaptured = self:IsAllInZoneOfOtherCoalition( self.Coalition )
self:F( { IsCaptured = IsCaptured } ) self:F( { IsCaptured = IsCaptured } )
return IsCaptured return IsCaptured
end end
@@ -688,7 +691,7 @@ do -- ZONE_CAPTURE_COALITION
function ZONE_CAPTURE_COALITION:IsAttacked() function ZONE_CAPTURE_COALITION:IsAttacked()
local IsAttacked = self.Zone:IsSomeInZoneOfCoalition( self.Coalition ) local IsAttacked = self:IsSomeInZoneOfCoalition( self.Coalition )
self:F( { IsAttacked = IsAttacked } ) self:F( { IsAttacked = IsAttacked } )
return IsAttacked return IsAttacked
end end
@@ -744,13 +747,13 @@ do -- ZONE_CAPTURE_COALITION
-- --
function ZONE_CAPTURE_COALITION:Start( StartInterval, RepeatInterval ) function ZONE_CAPTURE_COALITION:Start( StartInterval, RepeatInterval )
StartInterval = StartInterval or 15 self.StartInterval = StartInterval or 15
RepeatInterval = RepeatInterval or 15 self.RepeatInterval = RepeatInterval or 15
if self.ScheduleStatusZone then if self.ScheduleStatusZone then
self:ScheduleStop( self.ScheduleStatusZone ) self:ScheduleStop( self.ScheduleStatusZone )
end end
self.ScheduleStatusZone = self:ScheduleRepeat( StartInterval, RepeatInterval, 0.1, nil, self.StatusZone, self ) self.ScheduleStatusZone = self:ScheduleRepeat( self.StartInterval, self.RepeatInterval, 1.5, nil, self.StatusZone, self )
end end
@@ -803,7 +806,7 @@ do -- ZONE_CAPTURE_COALITION
local UnitHit = EventData.TgtUnit local UnitHit = EventData.TgtUnit
if UnitHit then if UnitHit then
if UnitHit:IsInZone( self.Zone ) then if UnitHit:IsInZone( self ) then
self:Attack() self:Attack()
end end
end end
@@ -44,14 +44,13 @@ do -- Zone
--- ZONE_GOAL Constructor. --- ZONE_GOAL Constructor.
-- @param #ZONE_GOAL self -- @param #ZONE_GOAL self
-- @param Core.Zone#ZONE_BASE Zone A @{Zone} object with the goal to be achieved. -- @param Core.Zone#ZONE_RADIUS Zone A @{Zone} object with the goal to be achieved.
-- @return #ZONE_GOAL -- @return #ZONE_GOAL
function ZONE_GOAL:New( Zone ) function ZONE_GOAL:New( Zone )
local self = BASE:Inherit( self, FSM:New() ) -- #ZONE_GOAL local self = BASE:Inherit( self, ZONE_RADIUS:New( Zone:GetName(), Zone:GetVec2(), Zone:GetRadius() ) ) -- #ZONE_GOAL
self:F( { Zone = Zone } ) self:F( { Zone = Zone } )
self.Zone = Zone -- Core.Zone#ZONE_BASE
self.Goal = GOAL:New() self.Goal = GOAL:New()
self.SmokeTime = nil self.SmokeTime = nil
@@ -67,6 +66,7 @@ do -- Zone
-- @param Wrapper.Unit#UNIT DestroyedUnit The destroyed unit. -- @param Wrapper.Unit#UNIT DestroyedUnit The destroyed unit.
-- @param #string PlayerName The name of the player. -- @param #string PlayerName The name of the player.
return self return self
end end
@@ -74,7 +74,7 @@ do -- Zone
-- @param #ZONE_GOAL self -- @param #ZONE_GOAL self
-- @return Core.Zone#ZONE_BASE -- @return Core.Zone#ZONE_BASE
function ZONE_GOAL:GetZone() function ZONE_GOAL:GetZone()
return self.Zone return self
end end
@@ -82,7 +82,7 @@ do -- Zone
-- @param #ZONE_GOAL self -- @param #ZONE_GOAL self
-- @return #string -- @return #string
function ZONE_GOAL:GetZoneName() function ZONE_GOAL:GetZoneName()
return self.Zone:GetName() return self:GetName()
end end
@@ -101,7 +101,7 @@ do -- Zone
-- @param #ZONE_GOAL self -- @param #ZONE_GOAL self
-- @param #SMOKECOLOR.Color FlareColor -- @param #SMOKECOLOR.Color FlareColor
function ZONE_GOAL:Flare( FlareColor ) function ZONE_GOAL:Flare( FlareColor )
self.Zone:FlareZone( FlareColor, math.random( 1, 360 ) ) self:FlareZone( FlareColor, math.random( 1, 360 ) )
end end
@@ -130,7 +130,7 @@ do -- Zone
if self.SmokeTime == nil or self.SmokeTime + 300 <= CurrentTime then if self.SmokeTime == nil or self.SmokeTime + 300 <= CurrentTime then
if self.SmokeColor then if self.SmokeColor then
self.Zone:GetCoordinate():Smoke( self.SmokeColor ) self:GetCoordinate():Smoke( self.SmokeColor )
--self.SmokeColor = nil --self.SmokeColor = nil
self.SmokeTime = CurrentTime self.SmokeTime = CurrentTime
end end
@@ -147,11 +147,9 @@ do -- Zone
local Vec3 = EventData.IniDCSUnit:getPosition().p local Vec3 = EventData.IniDCSUnit:getPosition().p
self:F( { Vec3 = Vec3 } ) self:F( { Vec3 = Vec3 } )
local ZoneGoal = self:GetZone()
self:F({ZoneGoal})
if EventData.IniDCSUnit then if EventData.IniDCSUnit then
if ZoneGoal:IsVec3InZone(Vec3) then if self:IsVec3InZone(Vec3) then
local PlayerHits = _DATABASE.HITS[EventData.IniUnitName] local PlayerHits = _DATABASE.HITS[EventData.IniUnitName]
if PlayerHits then if PlayerHits then
for PlayerName, PlayerHit in pairs( PlayerHits.Players or {} ) do for PlayerName, PlayerHit in pairs( PlayerHits.Players or {} ) do
@@ -104,7 +104,7 @@ do -- ZoneGoal
local State = self:GetState() local State = self:GetState()
self:F( { State = self:GetState() } ) self:F( { State = self:GetState() } )
self.Zone:Scan( { Object.Category.UNIT, Object.Category.STATIC } ) self:Scan( { Object.Category.UNIT, Object.Category.STATIC } )
end end
+1
View File
@@ -20,6 +20,7 @@ __Moose.Include( 'Scripts/Moose/Core/Velocity.lua' )
__Moose.Include( 'Scripts/Moose/Core/Message.lua' ) __Moose.Include( 'Scripts/Moose/Core/Message.lua' )
__Moose.Include( 'Scripts/Moose/Core/Fsm.lua' ) __Moose.Include( 'Scripts/Moose/Core/Fsm.lua' )
__Moose.Include( 'Scripts/Moose/Core/Radio.lua' ) __Moose.Include( 'Scripts/Moose/Core/Radio.lua' )
__Moose.Include( 'Scripts/Moose/Core/RadioQueue.lua' )
__Moose.Include( 'Scripts/Moose/Core/Spawn.lua' ) __Moose.Include( 'Scripts/Moose/Core/Spawn.lua' )
__Moose.Include( 'Scripts/Moose/Core/SpawnStatic.lua' ) __Moose.Include( 'Scripts/Moose/Core/SpawnStatic.lua' )
__Moose.Include( 'Scripts/Moose/Core/Goal.lua' ) __Moose.Include( 'Scripts/Moose/Core/Goal.lua' )
@@ -180,7 +180,7 @@ COMMANDCENTER = {
COMMANDCENTER.AutoAssignMethods = { COMMANDCENTER.AutoAssignMethods = {
["Random"] = 1, ["Random"] = 1,
["Distance"] = 2, ["Distance"] = 2,
["Priority"] = 3 ["Priority"] = 3,
} }
--- The constructor takes an IDENTIFIABLE as the HQ command center. --- The constructor takes an IDENTIFIABLE as the HQ command center.
@@ -200,7 +200,8 @@ function COMMANDCENTER:New( CommandCenterPositionable, CommandCenterName )
self:SetAutoAssignTasks( false ) self:SetAutoAssignTasks( false )
self:SetAutoAcceptTasks( true ) self:SetAutoAcceptTasks( true )
self:SetAutoAssignMethod( COMMANDCENTER.AutoAssignMethods.Random ) self:SetAutoAssignMethod( COMMANDCENTER.AutoAssignMethods.Distance )
self:SetFlashStatus( false )
self:HandleEvent( EVENTS.Birth, self:HandleEvent( EVENTS.Birth,
--- @param #COMMANDCENTER self --- @param #COMMANDCENTER self
@@ -208,8 +209,8 @@ function COMMANDCENTER:New( CommandCenterPositionable, CommandCenterName )
function( self, EventData ) function( self, EventData )
if EventData.IniObjectCategory == 1 then if EventData.IniObjectCategory == 1 then
local EventGroup = GROUP:Find( EventData.IniDCSGroup ) local EventGroup = GROUP:Find( EventData.IniDCSGroup )
self:E( { CommandCenter = self:GetName(), EventGroup = EventGroup:GetName(), HasGroup = self:HasGroup( EventGroup ), EventData = EventData } ) --self:E( { CommandCenter = self:GetName(), EventGroup = EventGroup:GetName(), HasGroup = self:HasGroup( EventGroup ), EventData = EventData } )
if EventGroup and self:HasGroup( EventGroup ) then if EventGroup and EventGroup:IsAlive() and self:HasGroup( EventGroup ) then
local CommandCenterMenu = MENU_GROUP:New( EventGroup, self:GetText() ) local CommandCenterMenu = MENU_GROUP:New( EventGroup, self:GetText() )
local MenuReporting = MENU_GROUP:New( EventGroup, "Missions Reports", CommandCenterMenu ) local MenuReporting = MENU_GROUP:New( EventGroup, "Missions Reports", CommandCenterMenu )
local MenuMissionsSummary = MENU_GROUP_COMMAND:New( EventGroup, "Missions Status Report", MenuReporting, self.ReportSummary, self, EventGroup ) local MenuMissionsSummary = MENU_GROUP_COMMAND:New( EventGroup, "Missions Status Report", MenuReporting, self.ReportSummary, self, EventGroup )
@@ -791,3 +792,12 @@ function COMMANDCENTER:ReportDetails( ReportGroup, Task )
self:MessageToGroup( Report:Text(), ReportGroup ) self:MessageToGroup( Report:Text(), ReportGroup )
end end
--- Let the command center flash a report of the status of the subscribed task to a group.
-- @param #COMMANDCENTER self
function COMMANDCENTER:SetFlashStatus( Flash )
self:F()
self.FlashStatus = Flash or true
end
@@ -254,11 +254,45 @@ do -- DETECTION MANAGER
end end
--- Get the command center to communicate actions to the players.
-- @param #DETECTION_MANAGER self
-- @return Tasking.CommandCenter#COMMANDCENTER The command center.
function DETECTION_MANAGER:GetCommandCenter()
return self.CC
end
--- Set the frequency of communication and the mode of communication for voice overs.
-- @param #DETECTION_MANAGER self
-- @param #number RadioFrequency The frequency of communication.
-- @param #number RadioModulation The modulation of communication.
-- @param #number RadioPower The power in Watts of communication.
function DETECTION_MANAGER:SetRadioFrequency( RadioFrequency, RadioModulation, RadioPower )
self.RadioFrequency = RadioFrequency
self.RadioModulation = RadioModulation or radio.modulation.AM
self.RadioPower = RadioPower or 100
if self.RadioQueue then
self.RadioQueue:Stop()
end
self.RadioQueue = nil
self.RadioQueue = RADIOQUEUE:New( self.RadioFrequency, self.RadioModulation )
self.RadioQueue.power = self.RadioPower
self.RadioQueue:Start( 0.5 )
end
--- Send an information message to the players reporting to the command center. --- Send an information message to the players reporting to the command center.
-- @param #DETECTION_MANAGER self -- @param #DETECTION_MANAGER self
-- @param #string Message The message to be sent. -- @param #string Message The message to be sent.
-- @param #string SoundFile The name of the sound file .wav or .ogg.
-- @param #number SoundDuration The duration of the sound.
-- @param #string SoundPath The path pointing to the folder in the mission file.
-- @param Wrapper.Group#GROUP DefenderGroup The defender group sending the message.
-- @return #DETECTION_MANGER self -- @return #DETECTION_MANGER self
function DETECTION_MANAGER:MessageToPlayers( Message ) function DETECTION_MANAGER:MessageToPlayers( Message, SoundFile, SoundDuration, SoundPath, DefenderGroup )
self:F( { Message = Message } ) self:F( { Message = Message } )
@@ -269,6 +303,17 @@ do -- DETECTION MANAGER
end end
end end
-- Here we handle the transmission of the voice over.
-- If for a certain reason the Defender does not exist, we use the coordinate of the airbase to send the message from.
if SoundFile then
local RadioQueue = self.RadioQueue -- Core.RadioQueue#RADIOQUEUE
local DefenderUnit = DefenderGroup:GetUnit(1)
if DefenderUnit and DefenderUnit:IsAlive() then
RadioQueue:SetSenderUnitName( DefenderUnit:GetName() )
end
RadioQueue:NewTransmission( SoundFile, SoundDuration, SoundPath )
end
return self return self
end end
@@ -279,7 +324,6 @@ do -- DETECTION MANAGER
-- @param Functional.Detection#DETECTION_BASE Detection -- @param Functional.Detection#DETECTION_BASE Detection
-- @return #DETECTION_MANAGER self -- @return #DETECTION_MANAGER self
function DETECTION_MANAGER:ProcessDetected( Detection ) function DETECTION_MANAGER:ProcessDetected( Detection )
self:E()
end end
+32 -3
View File
@@ -856,6 +856,8 @@ do -- Group Assignment
CommandCenter:SetMenu() CommandCenter:SetMenu()
self:MenuFlashTaskStatus( TaskGroup, self:GetMission():GetCommandCenter().FlashStatus )
return self return self
end end
@@ -1111,6 +1113,11 @@ function TASK:SetAssignedMenuForGroup( TaskGroup, MenuTime )
end end
local MarkMenu = MENU_GROUP_COMMAND:New( TaskGroup, string.format( "Mark Task Location on Map" ), TaskControl, self.MenuMarkToGroup, self, TaskGroup ):SetTime( MenuTime ):SetTag( "Tasking" ) local MarkMenu = MENU_GROUP_COMMAND:New( TaskGroup, string.format( "Mark Task Location on Map" ), TaskControl, self.MenuMarkToGroup, self, TaskGroup ):SetTime( MenuTime ):SetTag( "Tasking" )
local TaskTypeMenu = MENU_GROUP_COMMAND:New( TaskGroup, string.format( "Report Task Details" ), TaskControl, self.MenuTaskStatus, self, TaskGroup ):SetTime( MenuTime ):SetTag( "Tasking" ) local TaskTypeMenu = MENU_GROUP_COMMAND:New( TaskGroup, string.format( "Report Task Details" ), TaskControl, self.MenuTaskStatus, self, TaskGroup ):SetTime( MenuTime ):SetTag( "Tasking" )
if not self.FlashTaskStatus then
local TaskFlashStatusMenu = MENU_GROUP_COMMAND:New( TaskGroup, string.format( "Flash Task Details" ), TaskControl, self.MenuFlashTaskStatus, self, TaskGroup, true ):SetTime( MenuTime ):SetTag( "Tasking" )
else
local TaskFlashStatusMenu = MENU_GROUP_COMMAND:New( TaskGroup, string.format( "Stop Flash Task Details" ), TaskControl, self.MenuFlashTaskStatus, self, TaskGroup, nil ):SetTime( MenuTime ):SetTag( "Tasking" )
end
end end
end end
@@ -1225,12 +1232,34 @@ end
--- Report the task status. --- Report the task status.
-- @param #TASK self -- @param #TASK self
-- @param Wrapper.Group#GROUP TaskGroup
function TASK:MenuTaskStatus( TaskGroup ) function TASK:MenuTaskStatus( TaskGroup )
local ReportText = self:ReportDetails( TaskGroup ) if TaskGroup:IsAlive() then
self:T( ReportText ) local ReportText = self:ReportDetails( TaskGroup )
self:GetMission():GetCommandCenter():MessageTypeToGroup( ReportText, TaskGroup, MESSAGE.Type.Detailed )
self:T( ReportText )
self:GetMission():GetCommandCenter():MessageTypeToGroup( ReportText, TaskGroup, MESSAGE.Type.Detailed )
end
end
--- Report the task status.
-- @param #TASK self
function TASK:MenuFlashTaskStatus( TaskGroup, Flash )
self.FlashTaskStatus = Flash
if self.FlashTaskStatus then
self.FlashTaskScheduler, self.FlashTaskScheduleID = SCHEDULER:New( self, self.MenuTaskStatus, { TaskGroup }, 0, 60 )
else
if self.FlashTaskScheduler then
self.FlashTaskScheduler:Stop( self.FlashTaskScheduleID )
self.FlashTaskScheduler = nil
self.FlashTaskScheduleID = nil
end
end
end end
+11 -10
View File
@@ -52,16 +52,16 @@ end
--- Add taskinfo. --- Add taskinfo.
-- @param #TASKINFO self -- @param #TASKINFO self
-- @param #string The info key. -- @param #string Key The info key.
-- @param Data The data of the info. -- @param Data The data of the info.
-- @param #number Order The display order, which is a number from 0 to 100. -- @param #number Order The display order, which is a number from 0 to 100.
-- @param #TASKINFO.Detail Detail The detail Level. -- @param #TASKINFO.Detail Detail The detail Level.
-- @param #boolean Keep (optional) If true, this would indicate that the planned taskinfo would be persistent when the task is completed, so that the original planned task info is used at the completed reports. -- @param #boolean Keep (optional) If true, this would indicate that the planned taskinfo would be persistent when the task is completed, so that the original planned task info is used at the completed reports.
-- @return #TASKINFO self -- @return #TASKINFO self
function TASKINFO:AddInfo( Key, Data, Order, Detail, Keep ) function TASKINFO:AddInfo( Key, Data, Order, Detail, Keep, ShowKey, Type )
self.VolatileInfo:Add( Key, { Data = Data, Order = Order, Detail = Detail } ) self.VolatileInfo:Add( Key, { Data = Data, Order = Order, Detail = Detail, ShowKey = ShowKey, Type = Type } )
if Keep == true then if Keep == true then
self.PersistentInfo:Add( Key, { Data = Data, Order = Order, Detail = Detail } ) self.PersistentInfo:Add( Key, { Data = Data, Order = Order, Detail = Detail, ShowKey = ShowKey, Type = Type } )
end end
return self return self
end end
@@ -124,8 +124,8 @@ end
-- @param #TASKINFO.Detail Detail The detail Level. -- @param #TASKINFO.Detail Detail The detail Level.
-- @param #boolean Keep (optional) If true, this would indicate that the planned taskinfo would be persistent when the task is completed, so that the original planned task info is used at the completed reports. -- @param #boolean Keep (optional) If true, this would indicate that the planned taskinfo would be persistent when the task is completed, so that the original planned task info is used at the completed reports.
-- @return #TASKINFO self -- @return #TASKINFO self
function TASKINFO:AddCoordinate( Coordinate, Order, Detail, Keep ) function TASKINFO:AddCoordinate( Coordinate, Order, Detail, Keep, ShowKey, Name )
self:AddInfo( "Coordinate", Coordinate, Order, Detail, Keep ) self:AddInfo( Name or "Coordinate", Coordinate, Order, Detail, Keep, ShowKey, "Coordinate" )
return self return self
end end
@@ -133,8 +133,8 @@ end
--- Get the Coordinate. --- Get the Coordinate.
-- @param #TASKINFO self -- @param #TASKINFO self
-- @return Core.Point#COORDINATE Coordinate -- @return Core.Point#COORDINATE Coordinate
function TASKINFO:GetCoordinate() function TASKINFO:GetCoordinate( Name )
return self:GetData( "Coordinate" ) return self:GetData( Name or "Coordinate" )
end end
@@ -308,10 +308,11 @@ function TASKINFO:Report( Report, Detail, ReportGroup, Task )
if Data.Detail:find( Detail ) then if Data.Detail:find( Detail ) then
local Text = "" local Text = ""
local ShowKey = ( Data.ShowKey == nil or Data.ShowKey == true )
if Key == "TaskName" then if Key == "TaskName" then
Key = nil Key = nil
Text = Data.Data Text = Data.Data
elseif Key == "Coordinate" then elseif Data.Type and Data.Type == "Coordinate" then
local Coordinate = Data.Data -- Core.Point#COORDINATE local Coordinate = Data.Data -- Core.Point#COORDINATE
Text = Coordinate:ToString( ReportGroup:GetUnit(1), nil, Task ) Text = Coordinate:ToString( ReportGroup:GetUnit(1), nil, Task )
elseif Key == "Threat" then elseif Key == "Threat" then
@@ -357,7 +358,7 @@ function TASKINFO:Report( Report, Detail, ReportGroup, Task )
end end
if Text ~= "" then if Text ~= "" then
LineReport:Add( ( Key and ( Key .. ":" ) or "" ) .. Text ) LineReport:Add( ( ( Key and ShowKey == true ) and ( Key .. ": " ) or "" ) .. Text )
end end
end end
@@ -194,6 +194,45 @@ do -- TASK_CAPTURE_DISPATCHER
end end
--- Link a task capture dispatcher from the other coalition to understand its plan for defenses.
-- This is used for the tactical overview, so the players also know the zones attacked by the other coalition!
-- @param #TASK_CAPTURE_DISPATCHER self
-- @param #TASK_CAPTURE_DISPATCHER DefenseTaskCaptureDispatcher
function TASK_CAPTURE_DISPATCHER:SetDefenseTaskCaptureDispatcher( DefenseTaskCaptureDispatcher )
self.DefenseTaskCaptureDispatcher = DefenseTaskCaptureDispatcher
end
--- Get the linked task capture dispatcher from the other coalition to understand its plan for defenses.
-- This is used for the tactical overview, so the players also know the zones attacked by the other coalition!
-- @param #TASK_CAPTURE_DISPATCHER self
-- @return #TASK_CAPTURE_DISPATCHER
function TASK_CAPTURE_DISPATCHER:GetDefenseTaskCaptureDispatcher()
return self.DefenseTaskCaptureDispatcher
end
--- Link an AI A2G dispatcher from the other coalition to understand its plan for defenses.
-- This is used for the tactical overview, so the players also know the zones attacked by the other AI A2G dispatcher!
-- @param #TASK_CAPTURE_DISPATCHER self
-- @param AI.AI_A2G_Dispatcher#AI_A2G_DISPATCHER DefenseAIA2GDispatcher
function TASK_CAPTURE_DISPATCHER:SetDefenseAIA2GDispatcher( DefenseAIA2GDispatcher )
self.DefenseAIA2GDispatcher = DefenseAIA2GDispatcher
end
--- Get the linked AI A2G dispatcher from the other coalition to understand its plan for defenses.
-- This is used for the tactical overview, so the players also know the zones attacked by the AI A2G dispatcher!
-- @param #TASK_CAPTURE_DISPATCHER self
-- @return AI.AI_A2G_Dispatcher#AI_A2G_DISPATCHER
function TASK_CAPTURE_DISPATCHER:GetDefenseAIA2GDispatcher()
return self.DefenseAIA2GDispatcher
end
--- Add a capture zone task. --- Add a capture zone task.
-- @param #TASK_CAPTURE_DISPATCHER self -- @param #TASK_CAPTURE_DISPATCHER self
@@ -274,6 +313,9 @@ do -- TASK_CAPTURE_DISPATCHER
CaptureZone.Task.TaskPrefix = CaptureZone.TaskPrefix -- We keep the TaskPrefix for further reference! CaptureZone.Task.TaskPrefix = CaptureZone.TaskPrefix -- We keep the TaskPrefix for further reference!
Mission:AddTask( CaptureZone.Task ) Mission:AddTask( CaptureZone.Task )
TaskReport:Add( TaskName ) TaskReport:Add( TaskName )
-- Link the Task Dispatcher to the capture zone task, because it is used on the UpdateTaskInfo.
CaptureZone.Task:SetDispatcher( self )
CaptureZone.Task:UpdateTaskInfo() CaptureZone.Task:UpdateTaskInfo()
function CaptureZone.Task.OnEnterAssigned( Task, From, Event, To ) function CaptureZone.Task.OnEnterAssigned( Task, From, Event, To )
@@ -281,6 +323,7 @@ do -- TASK_CAPTURE_DISPATCHER
self.AI_A2G_Dispatcher:Unlock( Task.TaskZoneName ) -- This will unlock the zone to be defended by AI. self.AI_A2G_Dispatcher:Unlock( Task.TaskZoneName ) -- This will unlock the zone to be defended by AI.
end end
CaptureZone.Task:UpdateTaskInfo() CaptureZone.Task:UpdateTaskInfo()
CaptureZone.Task.ZoneGoal.Attacked = true
end end
function CaptureZone.Task.OnEnterSuccess( Task, From, Event, To ) function CaptureZone.Task.OnEnterSuccess( Task, From, Event, To )
@@ -289,6 +332,7 @@ do -- TASK_CAPTURE_DISPATCHER
self.AI_A2G_Dispatcher:Lock( Task.TaskZoneName ) -- This will lock the zone from being defended by AI. self.AI_A2G_Dispatcher:Lock( Task.TaskZoneName ) -- This will lock the zone from being defended by AI.
end end
CaptureZone.Task:UpdateTaskInfo() CaptureZone.Task:UpdateTaskInfo()
CaptureZone.Task.ZoneGoal.Attacked = false
end end
function CaptureZone.Task.OnEnterCancelled( Task, From, Event, To ) function CaptureZone.Task.OnEnterCancelled( Task, From, Event, To )
@@ -297,6 +341,7 @@ do -- TASK_CAPTURE_DISPATCHER
self.AI_A2G_Dispatcher:Lock( Task.TaskZoneName ) -- This will lock the zone from being defended by AI. self.AI_A2G_Dispatcher:Lock( Task.TaskZoneName ) -- This will lock the zone from being defended by AI.
end end
CaptureZone.Task:UpdateTaskInfo() CaptureZone.Task:UpdateTaskInfo()
CaptureZone.Task.ZoneGoal.Attacked = false
end end
function CaptureZone.Task.OnEnterFailed( Task, From, Event, To ) function CaptureZone.Task.OnEnterFailed( Task, From, Event, To )
@@ -305,6 +350,7 @@ do -- TASK_CAPTURE_DISPATCHER
self.AI_A2G_Dispatcher:Lock( Task.TaskZoneName ) -- This will lock the zone from being defended by AI. self.AI_A2G_Dispatcher:Lock( Task.TaskZoneName ) -- This will lock the zone from being defended by AI.
end end
CaptureZone.Task:UpdateTaskInfo() CaptureZone.Task:UpdateTaskInfo()
CaptureZone.Task.ZoneGoal.Attacked = false
end end
function CaptureZone.Task.OnEnterAborted( Task, From, Event, To ) function CaptureZone.Task.OnEnterAborted( Task, From, Event, To )
@@ -313,6 +359,7 @@ do -- TASK_CAPTURE_DISPATCHER
self.AI_A2G_Dispatcher:Lock( Task.TaskZoneName ) -- This will lock the zone from being defended by AI. self.AI_A2G_Dispatcher:Lock( Task.TaskZoneName ) -- This will lock the zone from being defended by AI.
end end
CaptureZone.Task:UpdateTaskInfo() CaptureZone.Task:UpdateTaskInfo()
CaptureZone.Task.ZoneGoal.Attacked = false
end end
-- Now broadcast the onafterCargoPickedUp event to the Task Cargo Dispatcher. -- Now broadcast the onafterCargoPickedUp event to the Task Cargo Dispatcher.
@@ -322,6 +369,7 @@ do -- TASK_CAPTURE_DISPATCHER
self.AI_A2G_Dispatcher:Lock( Task.TaskZoneName ) -- This will lock the zone from being defended by AI. self.AI_A2G_Dispatcher:Lock( Task.TaskZoneName ) -- This will lock the zone from being defended by AI.
end end
CaptureZone.Task:UpdateTaskInfo() CaptureZone.Task:UpdateTaskInfo()
CaptureZone.Task.ZoneGoal.Attacked = false
end end
end end
@@ -228,11 +228,47 @@ do -- TASK_CAPTURE_ZONE
local ZoneCoordinate = self.ZoneGoal:GetZone():GetCoordinate() local ZoneCoordinate = self.ZoneGoal:GetZone():GetCoordinate()
self.TaskInfo:AddTaskName( 0, "MSOD", Persist ) self.TaskInfo:AddTaskName( 0, "MSOD", Persist )
self.TaskInfo:AddCoordinate( ZoneCoordinate, 1, "SOD", Persist ) self.TaskInfo:AddCoordinate( ZoneCoordinate, 1, "SOD", Persist )
self.TaskInfo:AddText( "Zone Name", self.ZoneGoal:GetZoneName(), 10, "MOD", Persist ) -- self.TaskInfo:AddText( "Zone Name", self.ZoneGoal:GetZoneName(), 10, "MOD", Persist )
self.TaskInfo:AddText( "Zone Coalition", self.ZoneGoal:GetCoalitionName(), 11, "MOD", Persist ) -- self.TaskInfo:AddText( "Zone Coalition", self.ZoneGoal:GetCoalitionName(), 11, "MOD", Persist )
local SetUnit = self.ZoneGoal.Zone:GetScannedSetUnit() local SetUnit = self.ZoneGoal:GetScannedSetUnit()
local ThreatLevel, ThreatText = SetUnit:CalculateThreatLevelA2G() local ThreatLevel, ThreatText = SetUnit:CalculateThreatLevelA2G()
local ThreatCount = SetUnit:Count()
self.TaskInfo:AddThreat( ThreatText, ThreatLevel, 20, "MOD", Persist ) self.TaskInfo:AddThreat( ThreatText, ThreatLevel, 20, "MOD", Persist )
self.TaskInfo:AddInfo( "Remaining Units", ThreatCount, 21, "MOD", Persist, true)
if self.Dispatcher then
local DefenseTaskCaptureDispatcher = self.Dispatcher:GetDefenseTaskCaptureDispatcher() -- Tasking.Task_Capture_Dispatcher#TASK_CAPTURE_DISPATCHER
if DefenseTaskCaptureDispatcher then
-- Loop through all zones of the player Defenses, and check which zone has an assigned task!
-- The Zones collection contains a Task. This Task is checked if it is assigned.
-- If Assigned, then this task will be the task that is the closest to the defense zone.
for TaskName, CaptureZone in pairs( DefenseTaskCaptureDispatcher.Zones or {} ) do
local Task = CaptureZone.Task -- Tasking.Task_Capture_Zone#TASK_CAPTURE_ZONE
if Task and Task:IsStateAssigned() then -- We also check assigned.
-- Now we register the defense player zone information to the task report.
self.TaskInfo:AddInfo( "Defense Player Zone", Task.ZoneGoal:GetName(), 30, "MOD", Persist )
self.TaskInfo:AddCoordinate( Task.ZoneGoal:GetZone():GetCoordinate(), 31, "MOD", Persist, false, "Defense Player Coordinate" )
end
end
end
local DefenseAIA2GDispatcher = self.Dispatcher:GetDefenseAIA2GDispatcher() -- AI.AI_A2G_Dispatcher#AI_A2G_DISPATCHER
if DefenseAIA2GDispatcher then
-- Loop through all the tasks of the AI Defenses, and check which zone is involved in the defenses and is active!
for Defender, Task in pairs( DefenseAIA2GDispatcher:GetDefenderTasks() or {} ) do
local DetectedItem = DefenseAIA2GDispatcher:GetDefenderTaskTarget( Defender )
if DetectedItem then
local DetectedZone = DefenseAIA2GDispatcher.Detection:GetDetectedItemZone( DetectedItem )
if DetectedZone then
self.TaskInfo:AddInfo( "Defense AI Zone", DetectedZone:GetName(), 40, "MOD", Persist )
self.TaskInfo:AddCoordinate( DetectedZone:GetCoordinate(), 41, "MOD", Persist, false, "Defense AI Coordinate" )
end
end
end
end
end
end end
@@ -274,7 +310,7 @@ do -- TASK_CAPTURE_ZONE
-- @param #number AutoAssignMethod The method to be applied to the task. -- @param #number AutoAssignMethod The method to be applied to the task.
-- @param Tasking.CommandCenter#COMMANDCENTER CommandCenter The command center. -- @param Tasking.CommandCenter#COMMANDCENTER CommandCenter The command center.
-- @param Wrapper.Group#GROUP TaskGroup The player group. -- @param Wrapper.Group#GROUP TaskGroup The player group.
function TASK_CAPTURE_ZONE:GetAutoAssignPriority( AutoAssignMethod, CommandCenter, TaskGroup ) function TASK_CAPTURE_ZONE:GetAutoAssignPriority( AutoAssignMethod, CommandCenter, TaskGroup, AutoAssignReference )
if AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Random then if AutoAssignMethod == COMMANDCENTER.AutoAssignMethods.Random then
return math.random( 1, 9 ) return math.random( 1, 9 )
@@ -185,7 +185,6 @@ do -- TASK_MANAGER
-- @param #TASK_MANAGER self -- @param #TASK_MANAGER self
-- @return #TASK_MANAGER self -- @return #TASK_MANAGER self
function TASK_MANAGER:ManageTasks() function TASK_MANAGER:ManageTasks()
self:E()
end end
@@ -521,6 +521,32 @@ function UTILS.spairs( t, order )
end end
end end
-- Here is a customized version of pairs, which I called kpairs because it iterates over the table in a sorted order, based on a function that will determine the keys as reference first.
function UTILS.kpairs( t, getkey, order )
-- collect the keys
local keys = {}
local keyso = {}
for k, o in pairs(t) do keys[#keys+1] = k keyso[#keyso+1] = getkey( o ) end
-- if order function given, sort by it by passing the table and keys a, b,
-- otherwise just sort the keys
if order then
table.sort(keys, function(a,b) return order(t, a, b) end)
else
table.sort(keys)
end
-- return the iterator function
local i = 0
return function()
i = i + 1
if keys[i] then
return keyso[i], t[keys[i]]
end
end
end
-- Here is a customized version of pairs, which I called rpairs because it iterates over the table in a random order. -- Here is a customized version of pairs, which I called rpairs because it iterates over the table in a random order.
function UTILS.rpairs( t ) function UTILS.rpairs( t )
-- collect the keys -- collect the keys
+3 -2
View File
@@ -133,7 +133,8 @@ function CLIENT:FindByName( ClientName, ClientBriefing, Error )
end end
function CLIENT:Register( ClientName ) function CLIENT:Register( ClientName )
local self = BASE:Inherit( self, UNIT:Register( ClientName ) )
local self = BASE:Inherit( self, UNIT:Register( ClientName ) ) -- #CLIENT
self:F( ClientName ) self:F( ClientName )
self.ClientName = ClientName self.ClientName = ClientName
@@ -141,7 +142,7 @@ function CLIENT:Register( ClientName )
self.ClientAlive2 = false self.ClientAlive2 = false
--self.AliveCheckScheduler = routines.scheduleFunction( self._AliveCheckScheduler, { self }, timer.getTime() + 1, 5 ) --self.AliveCheckScheduler = routines.scheduleFunction( self._AliveCheckScheduler, { self }, timer.getTime() + 1, 5 )
self.AliveCheckScheduler = SCHEDULER:New( self, self._AliveCheckScheduler, { "Client Alive " .. ClientName }, 1, 5 ) self.AliveCheckScheduler = SCHEDULER:New( self, self._AliveCheckScheduler, { "Client Alive " .. ClientName }, 1, 5, 0.5 )
self:F( self ) self:F( self )
return self return self
@@ -307,7 +307,6 @@ end
-- @param #CONTROLLABLE self -- @param #CONTROLLABLE self
-- @return #CONTROLLABLE -- @return #CONTROLLABLE
function CONTROLLABLE:ClearTasks() function CONTROLLABLE:ClearTasks()
self:E( "ClearTasks" )
local DCSControllable = self:GetDCSObject() local DCSControllable = self:GetDCSObject()
@@ -944,7 +943,7 @@ end
-- @param #boolean Divebomb (optional) Perform dive bombing. Default false. -- @param #boolean Divebomb (optional) Perform dive bombing. Default false.
-- @return DCS#Task The DCS task structure. -- @return DCS#Task The DCS task structure.
function CONTROLLABLE:TaskBombing( Vec2, GroupAttack, WeaponExpend, AttackQty, Direction, Altitude, WeaponType, Divebomb ) function CONTROLLABLE:TaskBombing( Vec2, GroupAttack, WeaponExpend, AttackQty, Direction, Altitude, WeaponType, Divebomb )
self:E( { self.ControllableName, Vec2, GroupAttack, WeaponExpend, AttackQty, Direction, Altitude, WeaponType, Divebomb } ) self:F( { self.ControllableName, Vec2, GroupAttack, WeaponExpend, AttackQty, Direction, Altitude, WeaponType, Divebomb } )
local _groupattack=false local _groupattack=false
if GroupAttack then if GroupAttack then
@@ -990,7 +989,7 @@ function CONTROLLABLE:TaskBombing( Vec2, GroupAttack, WeaponExpend, AttackQty, D
}, },
} }
self:E( { TaskBombing=DCSTask } ) self:F( { TaskBombing=DCSTask } )
return DCSTask return DCSTask
end end
+39 -1
View File
@@ -696,7 +696,9 @@ end
--- Returns the Unit's A2G threat level on a scale from 1 to 10 ... --- Returns the Unit's A2G threat level on a scale from 1 to 10 ...
-- The following threat levels are foreseen: -- Depending on the era and the type of unit, the following threat levels are foreseen:
--
-- **Modern**:
-- --
-- * Threat level 0: Unit is unarmed. -- * Threat level 0: Unit is unarmed.
-- * Threat level 1: Unit is infantry. -- * Threat level 1: Unit is infantry.
@@ -709,6 +711,42 @@ end
-- * Threat level 8: Unit is a Short Range SAM, radar guided. -- * Threat level 8: Unit is a Short Range SAM, radar guided.
-- * Threat level 9: Unit is a Medium Range SAM, radar guided. -- * Threat level 9: Unit is a Medium Range SAM, radar guided.
-- * Threat level 10: Unit is a Long Range SAM, radar guided. -- * Threat level 10: Unit is a Long Range SAM, radar guided.
--
-- **Cold**:
--
-- * Threat level 0: Unit is unarmed.
-- * Threat level 1: Unit is infantry.
-- * Threat level 2: Unit is an infantry vehicle.
-- * Threat level 3: Unit is ground artillery.
-- * Threat level 4: Unit is a tank.
-- * Threat level 5: Unit is a modern tank or ifv with ATGM.
-- * Threat level 6: Unit is a AAA.
-- * Threat level 7: Unit is a SAM or manpad, IR guided.
-- * Threat level 8: Unit is a Short Range SAM, radar guided.
-- * Threat level 10: Unit is a Medium Range SAM, radar guided.
--
-- **Korea**:
--
-- * Threat level 0: Unit is unarmed.
-- * Threat level 1: Unit is infantry.
-- * Threat level 2: Unit is an infantry vehicle.
-- * Threat level 3: Unit is ground artillery.
-- * Threat level 5: Unit is a tank.
-- * Threat level 6: Unit is a AAA.
-- * Threat level 7: Unit is a SAM or manpad, IR guided.
-- * Threat level 10: Unit is a Short Range SAM, radar guided.
--
-- **WWII**:
--
-- * Threat level 0: Unit is unarmed.
-- * Threat level 1: Unit is infantry.
-- * Threat level 2: Unit is an infantry vehicle.
-- * Threat level 3: Unit is ground artillery.
-- * Threat level 5: Unit is a tank.
-- * Threat level 7: Unit is FLAK.
-- * Threat level 10: Unit is AAA.
--
--
-- @param #UNIT self -- @param #UNIT self
function UNIT:GetThreatLevel() function UNIT:GetThreatLevel()