diff --git a/Moose Development/Moose/AI/AI_A2A.lua b/Moose Development/Moose/AI/AI_A2A.lua index afd7cc70d..f23fc006e 100644 --- a/Moose Development/Moose/AI/AI_A2A.lua +++ b/Moose Development/Moose/AI/AI_A2A.lua @@ -433,7 +433,7 @@ function AI_A2A:onafterStatus() self:F({DistanceFromHomeBase=DistanceFromHomeBase}) 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 ) RTB = false end @@ -453,10 +453,10 @@ function AI_A2A:onafterStatus() self:F({Fuel=Fuel, PatrolFuelThresholdPercentage=self.PatrolFuelThresholdPercentage}) if Fuel < self.PatrolFuelThresholdPercentage 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() 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 OrbitTask = OldAIControllable:TaskOrbitCircle( math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ), self.PatrolMinSpeed ) @@ -475,7 +475,7 @@ function AI_A2A:onafterStatus() local InitialLife = self.Controllable:GetLife0() self:F( { Damage = Damage, InitialLife = InitialLife, DamageThreshold = self.PatrolDamageThreshold } ) if ( Damage / InitialLife ) < self.PatrolDamageThreshold then - self:E( self.Controllable:GetName() .. " is damaged: " .. Damage .. " ... RTB!" ) + self:I( self.Controllable:GetName() .. " is damaged: " .. Damage .. " ... RTB!" ) self:Damaged() RTB = true self:SetStatusOff() @@ -493,7 +493,7 @@ function AI_A2A:onafterStatus() if Damage ~= InitialLife then self:Damaged() else - self:E( self.Controllable:GetName() .. " control lost! " ) + self:I( self.Controllable:GetName() .. " control lost! " ) self:LostControl() end else @@ -549,7 +549,7 @@ function AI_A2A:onafterRTB( AIGroup, From, Event, To ) if AIGroup and AIGroup:IsAlive() then - self:E( "Group " .. AIGroup:GetName() .. " ... RTB! ( " .. self:GetState() .. " )" ) + self:I( "Group " .. AIGroup:GetName() .. " ... RTB! ( " .. self:GetState() .. " )" ) self:ClearTargetDistance() AIGroup:ClearTasks() @@ -567,7 +567,7 @@ function AI_A2A:onafterRTB( AIGroup, From, Event, To ) local ToAirbaseCoord = CurrentCoord:Translate( 5000, ToAirbaseAngle ) if Distance < 5000 then - self:E( "RTB and near the airbase!" ) + self:I( "RTB and near the airbase!" ) self:Home() return end @@ -608,7 +608,7 @@ end function AI_A2A:onafterHome( 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 end @@ -622,7 +622,7 @@ end function AI_A2A:onafterHold( AIGroup, From, Event, To, HoldTime ) 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 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 ) 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 local Tanker = GROUP:FindByName( self.TankerName ) @@ -711,7 +711,7 @@ end function AI_A2A:OnCrash( EventData ) 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 self:__Crash( 1, EventData ) end diff --git a/Moose Development/Moose/AI/AI_A2A_Cap.lua b/Moose Development/Moose/AI/AI_A2A_Cap.lua index 49b87b3c0..ba41a3bab 100644 --- a/Moose Development/Moose/AI/AI_A2A_Cap.lua +++ b/Moose Development/Moose/AI/AI_A2A_Cap.lua @@ -278,6 +278,8 @@ function AI_A2A_CAP:New( AICap, PatrolZone, PatrolFloorAltitude, PatrolCeilingAl -- @param #AI_A2A_CAP self -- @param #number Delay The delay in seconds. + self:AddTransition( { "Patrolling", "Engaging" }, "Refuel", "Refuelling" ) + return self end diff --git a/Moose Development/Moose/AI/AI_A2A_Gci.lua b/Moose Development/Moose/AI/AI_A2A_Gci.lua index 53b7141ab..a6642a9ce 100644 --- a/Moose Development/Moose/AI/AI_A2A_Gci.lua +++ b/Moose Development/Moose/AI/AI_A2A_Gci.lua @@ -279,6 +279,8 @@ function AI_A2A_GCI:New( AIIntercept, EngageMinSpeed, EngageMaxSpeed ) -- @param #AI_A2A_GCI self -- @param #number Delay The delay in seconds. + self:AddTransition( { "Patrolling", "Engaging" }, "Refuel", "Refuelling" ) + return self end diff --git a/Moose Development/Moose/AI/AI_A2G_BAI.lua b/Moose Development/Moose/AI/AI_A2G_BAI.lua index d572347ba..ac8efbb4f 100644 --- a/Moose Development/Moose/AI/AI_A2G_BAI.lua +++ b/Moose Development/Moose/AI/AI_A2G_BAI.lua @@ -62,12 +62,14 @@ end -- @param #string To The To State string. 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 AttackCount = AttackSetUnit:Count() + self.AttackSetUnit = AttackSetUnit -- Kept in memory in case of resume from refuel in air! + if AttackCount > 0 then if DefenderGroup:IsAlive() then @@ -129,7 +131,7 @@ function AI_A2G_BAI:onafterEngage( DefenderGroup, From, Event, To, AttackSetUnit end if #AttackUnitTasks == 0 then - self:E( DefenderGroupName .. ": No targets found -> Going RTB") + self:I( DefenderGroupName .. ": No targets found -> Going RTB") self:Return() self:__RTB( self.TaskDelay ) else @@ -146,8 +148,19 @@ function AI_A2G_BAI:onafterEngage( DefenderGroup, From, Event, To, AttackSetUnit DefenderGroup:Route( EngageRoute, self.TaskDelay ) end else - self:E( DefenderGroupName .. ": No targets found -> Going RTB") + self:I( DefenderGroupName .. ": No targets found -> Going RTB") self:Return() self:__RTB( self.TaskDelay ) 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 \ No newline at end of file diff --git a/Moose Development/Moose/AI/AI_A2G_CAS.lua b/Moose Development/Moose/AI/AI_A2G_CAS.lua index c8773b3f5..8ed9ee29d 100644 --- a/Moose Development/Moose/AI/AI_A2G_CAS.lua +++ b/Moose Development/Moose/AI/AI_A2G_CAS.lua @@ -66,6 +66,8 @@ function AI_A2G_CAS:onafterEngage( DefenderGroup, From, Event, To, AttackSetUnit local DefenderGroupName = DefenderGroup:GetName() + self.AttackSetUnit = AttackSetUnit -- Kept in memory in case of resume from refuel in air! + local AttackCount = AttackSetUnit:Count() if AttackCount > 0 then @@ -124,7 +126,7 @@ function AI_A2G_CAS:onafterEngage( DefenderGroup, From, Event, To, AttackSetUnit if #AttackUnitTasks == 0 then - self:E( DefenderGroupName .. ": No targets found -> Going RTB") + self:I( DefenderGroupName .. ": No targets found -> Going RTB") self:Return() self:__RTB( self.TaskDelay ) else @@ -142,9 +144,19 @@ function AI_A2G_CAS:onafterEngage( DefenderGroup, From, Event, To, AttackSetUnit DefenderGroup:Route( EngageRoute, self.TaskDelay ) end else - self:E( DefenderGroupName .. ": No targets found -> Going RTB") + self:I( DefenderGroupName .. ": No targets found -> Going RTB") self:Return() self:__RTB( self.TaskDelay ) 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 \ No newline at end of file diff --git a/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua b/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua index fa6177178..0d74a86d2 100644 --- a/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua +++ b/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua @@ -1133,7 +1133,7 @@ do -- AI_A2G_DISPATCHER self.TakeoffScheduleID = self:ScheduleRepeat( 10, 10, 0, nil, self.ResourceTakeoff, self ) - self:__Start( 1 ) + self:__Start( 1 ) return self end @@ -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 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 ) - Fsm:SetHomeAirbase( DefenderSquadron.Airbase ) - Fsm:SetFuelThreshold( DefenderSquadron.FuelThreshold or self.DefenderDefault.FuelThreshold, 60 ) - Fsm:SetDamageThreshold( self.DefenderDefault.DamageThreshold ) - Fsm:SetDisengageRadius( self.DisengageRadius ) - Fsm:SetTanker( DefenderSquadron.TankerName or self.DefenderDefault.TankerName ) - Fsm:Start() + 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 ) + AI_A2G_Fsm:SetDispatcher( self ) + AI_A2G_Fsm:SetHomeAirbase( DefenderSquadron.Airbase ) + AI_A2G_Fsm:SetFuelThreshold( DefenderSquadron.FuelThreshold or self.DefenderDefault.FuelThreshold, 60 ) + AI_A2G_Fsm:SetDamageThreshold( self.DefenderDefault.DamageThreshold ) + AI_A2G_Fsm:SetDisengageRadius( self.DisengageRadius ) + AI_A2G_Fsm:SetTanker( DefenderSquadron.TankerName or self.DefenderDefault.TankerName ) + 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 ) - self:F({"Defender Birth", Defender:GetName()}) + function AI_A2G_Fsm:onafterTakeoff( DefenderGroup, From, Event, To ) + self:F({"Defender Takeoff", DefenderGroup:GetName()}) --self:GetParent(self).onafterBirth( self, Defender, From, Event, To ) - local DefenderName = Defender:GetCallsign() - local Dispatcher = Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) + local DefenderName = DefenderGroup:GetCallsign() + local Dispatcher = AI_A2G_Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER + local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) if Squadron then - Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " airborne." ) - Fsm:Patrol() -- Engage on the TargetSetUnit + Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " airborne.", "Wheels_up.wav", 3, "A2G/", DefenderGroup ) + AI_A2G_Fsm:Patrol() -- Engage on the TargetSetUnit end end - function Fsm:onafterPatrolRoute( Defender, From, Event, To ) - self:F({"Defender PatrolRoute", Defender:GetName()}) - self:GetParent(self).onafterPatrolRoute( self, Defender, From, Event, To ) + function AI_A2G_Fsm:onafterPatrolRoute( DefenderGroup, From, Event, To ) + self:F({"Defender PatrolRoute", DefenderGroup:GetName()}) + 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 Squadron = Dispatcher:GetSquadronFromDefender( Defender ) + local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) if Squadron then - Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " patrolling." ) + Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " patrolling.", "Patrolling.wav", 3, "A2G/", DefenderGroup ) end - Dispatcher:ClearDefenderTaskTarget( Defender ) + Dispatcher:ClearDefenderTaskTarget( DefenderGroup ) end - function Fsm:onafterRTB( Defender, From, Event, To ) - self:F({"Defender RTB", Defender:GetName()}) - self:GetParent(self).onafterRTB( self, Defender, From, Event, To ) + function AI_A2G_Fsm:onafterEngageRoute( DefenderGroup, From, Event, To, AttackSetUnit ) + self:F({"Engage Route", DefenderGroup:GetName()}) - local DefenderName = Defender:GetCallsign() - local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) - Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " returning." ) + 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 - Dispatcher:ClearDefenderTaskTarget( Defender ) + 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 Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) + Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " returning.", "Returning_to_base.wav", 3, "A2G/", DefenderGroup ) + + Dispatcher:ClearDefenderTaskTarget( DefenderGroup ) end --- @param #AI_A2G_DISPATCHER self - function Fsm:onafterLostControl( Defender, From, Event, To ) - self:F({"Defender LostControl", Defender:GetName()}) - self:GetParent(self).onafterHome( self, Defender, From, Event, To ) + function AI_A2G_Fsm:onafterLostControl( DefenderGroup, From, Event, To ) + self:F({"Defender LostControl", DefenderGroup:GetName()}) + self:GetParent(self).onafterHome( self, DefenderGroup, From, Event, To ) - local DefenderName = Defender:GetCallsign() - local Dispatcher = Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) + local DefenderName = DefenderGroup:GetCallsign() + local Dispatcher = AI_A2G_Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER + local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " lost control." ) - if Defender:IsAboveRunway() then - Dispatcher:RemoveDefenderFromSquadron( Squadron, Defender ) - Defender:Destroy() + if DefenderGroup:IsAboveRunway() then + Dispatcher:RemoveDefenderFromSquadron( Squadron, DefenderGroup ) + DefenderGroup:Destroy() end end --- @param #AI_A2G_DISPATCHER self - function Fsm:onafterHome( Defender, From, Event, To, Action ) - self:F({"Defender Home", Defender:GetName()}) - self:GetParent(self).onafterHome( self, Defender, From, Event, To ) + function AI_A2G_Fsm:onafterHome( DefenderGroup, From, Event, To, Action ) + self:F({"Defender Home", DefenderGroup:GetName()}) + 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 Squadron = Dispatcher:GetSquadronFromDefender( Defender ) - Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " landing." ) + local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) + Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " landing.", "Landing_at_base.wav", 3, "A2G/", DefenderGroup ) if Action and Action == "Destroy" then - Dispatcher:RemoveDefenderFromSquadron( Squadron, Defender ) - Defender:Destroy() + Dispatcher:RemoveDefenderFromSquadron( Squadron, DefenderGroup ) + DefenderGroup:Destroy() end if Dispatcher:GetSquadronLanding( Squadron.Name ) == AI_A2G_DISPATCHER.Landing.NearAirbase then - Dispatcher:RemoveDefenderFromSquadron( Squadron, Defender ) - Defender:Destroy() - Dispatcher:ResourcePark( Squadron, Defender ) + Dispatcher:RemoveDefenderFromSquadron( Squadron, DefenderGroup ) + DefenderGroup:Destroy() + Dispatcher:ResourcePark( Squadron, DefenderGroup ) 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 Fsm = AI_A2G[DefenseTaskType]:New( DefenderGroup, Defense.EngageMinSpeed, Defense.EngageMaxSpeed, Defense.EngageFloorAltitude, Defense.EngageCeilingAltitude ) -- AI.AI_A2G_ENGAGE - Fsm:SetDispatcher( self ) - Fsm:SetHomeAirbase( DefenderSquadron.Airbase ) - Fsm:SetFuelThreshold( DefenderSquadron.FuelThreshold or self.DefenderDefault.FuelThreshold, 60 ) - Fsm:SetDamageThreshold( self.DefenderDefault.DamageThreshold ) - Fsm:SetDisengageRadius( self.DisengageRadius ) - Fsm:Start() + local AI_A2G_Fsm = AI_A2G[DefenseTaskType]:New( DefenderGroup, Defense.EngageMinSpeed, Defense.EngageMaxSpeed, Defense.EngageFloorAltitude, Defense.EngageCeilingAltitude ) -- AI.AI_A2G_ENGAGE + AI_A2G_Fsm:SetDispatcher( self ) + AI_A2G_Fsm:SetHomeAirbase( DefenderSquadron.Airbase ) + AI_A2G_Fsm:SetFuelThreshold( DefenderSquadron.FuelThreshold or self.DefenderDefault.FuelThreshold, 60 ) + AI_A2G_Fsm:SetDamageThreshold( self.DefenderDefault.DamageThreshold ) + AI_A2G_Fsm:SetDisengageRadius( self.DisengageRadius ) + 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 ) - self:F({"Defender Birth", Defender:GetName()}) + function AI_A2G_Fsm:onafterTakeoff( DefenderGroup, From, Event, To ) + self:F({"Defender Birth", DefenderGroup:GetName()}) --self:GetParent(self).onafterBirth( self, Defender, From, Event, To ) - local DefenderName = Defender:GetCallsign() - local Dispatcher = Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) - local DefenderTarget = Dispatcher:GetDefenderTaskTarget( Defender ) + local DefenderName = DefenderGroup:GetCallsign() + local Dispatcher = AI_A2G_Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER + local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) + local DefenderTarget = Dispatcher:GetDefenderTaskTarget( DefenderGroup ) self:F( { DefenderTarget = DefenderTarget } ) if DefenderTarget then - Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " airborne. Engaging!" ) - Fsm:EngageRoute( DefenderTarget.Set ) -- Engage on the TargetSetUnit + Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " airborne. Engaging!", "Wheels_up.wav", 3, "A2G/", DefenderGroup ) + AI_A2G_Fsm:EngageRoute( DefenderTarget.Set ) -- Engage on the TargetSetUnit end end - function Fsm:onafterEngageRoute( Defender, From, Event, To, AttackSetUnit ) - self:F({"Engage Route", Defender:GetName()}) - self:GetParent(self).onafterEngageRoute( self, Defender, From, Event, To, AttackSetUnit ) + function AI_A2G_Fsm:onafterEngageRoute( DefenderGroup, From, Event, To, AttackSetUnit ) + self:F({"Engage Route", DefenderGroup:GetName()}) - local DefenderName = Defender:GetCallsign() - local Dispatcher = Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) + 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( 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 + self:GetParent(self).onafterEngageRoute( self, DefenderGroup, From, Event, To, AttackSetUnit ) end - function Fsm:OnAfterEngage( Defender, From, Event, To, AttackSetUnit ) - self:F({"Engage Route", Defender:GetName()}) + 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 = Defender:GetCallsign() - local Dispatcher = Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) + 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( Defender ) ) + Dispatcher:MessageToPlayers( Squadron, "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " engaging ground target at " .. Coordinate:ToStringA2G( DefenderGroup ), "Engaging_ground_target.wav", 3, "A2G/", DefenderGroup ) end end - function Fsm:onafterRTB( Defender, From, Event, To ) - self:F({"Defender RTB", Defender:GetName()}) + function AI_A2G_Fsm:onafterRTB( DefenderGroup, From, Event, To ) + self:F({"Defender RTB", DefenderGroup:GetName()}) - local DefenderName = Defender:GetCallsign() + local DefenderName = DefenderGroup:GetCallsign() local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) - Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " returning." ) + local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) + 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 --- @param #AI_A2G_DISPATCHER self - function Fsm:onafterLostControl( Defender, From, Event, To ) - self:F({"Defender LostControl", Defender:GetName()}) - self:GetParent(self).onafterHome( self, Defender, From, Event, To ) + function AI_A2G_Fsm:onafterLostControl( DefenderGroup, From, Event, To ) + self:F({"Defender LostControl", DefenderGroup:GetName()}) + self:GetParent(self).onafterHome( self, DefenderGroup, From, Event, To ) - local DefenderName = Defender:GetCallsign() - local Dispatcher = Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER - local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) + local DefenderName = DefenderGroup:GetCallsign() + local Dispatcher = AI_A2G_Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER + local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) --Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " lost control." ) - if Defender:IsAboveRunway() then - Dispatcher:RemoveDefenderFromSquadron( Squadron, Defender ) - Defender:Destroy() + if DefenderGroup:IsAboveRunway() then + Dispatcher:RemoveDefenderFromSquadron( Squadron, DefenderGroup ) + DefenderGroup:Destroy() end end --- @param #AI_A2G_DISPATCHER self - function Fsm:onafterHome( Defender, From, Event, To, Action ) - self:F({"Defender Home", Defender:GetName()}) - self:GetParent(self).onafterHome( self, Defender, From, Event, To ) + function AI_A2G_Fsm:onafterHome( DefenderGroup, From, Event, To, Action ) + self:F({"Defender Home", DefenderGroup:GetName()}) + 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 Squadron = Dispatcher:GetSquadronFromDefender( Defender ) - Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " landing." ) + local Squadron = Dispatcher:GetSquadronFromDefender( DefenderGroup ) + Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " landing.", "Landing_at_base.wav", 3, "A2G/", DefenderGroup ) if Action and Action == "Destroy" then - Dispatcher:RemoveDefenderFromSquadron( Squadron, Defender ) - Defender:Destroy() + Dispatcher:RemoveDefenderFromSquadron( Squadron, DefenderGroup ) + DefenderGroup:Destroy() end if Dispatcher:GetSquadronLanding( Squadron.Name ) == AI_A2G_DISPATCHER.Landing.NearAirbase then - Dispatcher:RemoveDefenderFromSquadron( Squadron, Defender ) - Defender:Destroy() - Dispatcher:ResourcePark( Squadron, Defender ) + Dispatcher:RemoveDefenderFromSquadron( Squadron, DefenderGroup ) + DefenderGroup:Destroy() + Dispatcher:ResourcePark( Squadron, DefenderGroup ) end end end @@ -4024,6 +4056,29 @@ do -- AI_A2G_DISPATCHER 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. -- @param #AI_A2G_DISPATCHER self function AI_A2G_DISPATCHER:Order( DetectedItem ) @@ -4043,6 +4098,107 @@ do -- AI_A2G_DISPATCHER return ShortestDistance 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. -- @param #AI_A2G_DISPATCHER self @@ -4102,7 +4258,7 @@ do -- AI_A2G_DISPATCHER -- Now that all obsolete tasks are removed, loop through the detected targets. --for DetectedItemID, DetectedItem in pairs( Detection:GetDetectedItems() ) do - for DetectedItemID, DetectedItem in UTILS.spairs( Detection:GetDetectedItems(), function( t, a, b ) return self:Order(t[a]) < self:Order(t[b]) end ) do + for 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 local DetectedItem = DetectedItem -- Functional.Detection#DETECTION_BASE.DetectedItem @@ -4124,44 +4280,43 @@ do -- AI_A2G_DISPATCHER -- Calculate if for this DetectedItem if a defense needs to be initiated. -- 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! - - local EngageCoordinate = nil - - for DefenseCoordinateName, DefenseCoordinate in pairs( self.DefenseCoordinates ) do - local DefenseCoordinate = DefenseCoordinate -- Core.Point#COORDINATE - - local EvaluateDistance = AttackCoordinate:Get2DDistance( DefenseCoordinate ) + + local EngageDefenses = nil - if EvaluateDistance <= self.DefenseRadius then - - local DistanceProbability = ( self.DefenseRadius / EvaluateDistance * self.DefenseReactivity ) + self:F( { DetectedDistance = DetectedDistance, DefenseRadius = self.DefenseRadius } ) + if DetectedDistance <= self.DefenseRadius then + + self:F( { DetectedApproach = self._DefenseApproach } ) + if self._DefenseApproach == AI_A2G_DISPATCHER.DefenseApproach.Distance then + EngageDefenses = true + self:F( { EngageDefenses = EngageDefenses } ) + end + + if self._DefenseApproach == AI_A2G_DISPATCHER.DefenseApproach.Random then + local DistanceProbability = ( self.DefenseRadius / DetectedDistance * self.DefenseReactivity ) local DefenseProbability = math.random() - + self:F( { DistanceProbability = DistanceProbability, DefenseProbability = DefenseProbability } ) - - if self._DefenseApproach == AI_A2G_DISPATCHER.DefenseApproach.Random then - - if DefenseProbability <= DistanceProbability / ( 300 / 30 ) then - EngageCoordinate = DefenseCoordinate - break - end - end - if self._DefenseApproach == AI_A2G_DISPATCHER.DefenseApproach.Distance then - EngageCoordinate = DefenseCoordinate - break + + if DefenseProbability <= DistanceProbability / ( 300 / 30 ) then + EngageDefenses = true end end + + end + self:F( { EngageDefenses = EngageDefenses, DefenseLimit = self.DefenseLimit, DefenseTotal = DefenseTotal } ) + -- 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 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 local DefendersTotal, DefendersEngaged, DefendersMissing, Friendlies = self:Evaluate_SEAD( DetectedItem ) -- Returns a SET_UNIT with the SEAD targets to be engaged... if DefendersMissing > 0 then 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 @@ -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... if DefendersMissing > 0 then 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 @@ -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... if DefendersMissing > 0 then 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 @@ -4191,7 +4346,7 @@ do -- AI_A2G_DISPATCHER for DefenseQueueID, DefenseQueueItem in pairs( self.DefenseQueue ) do 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 end end diff --git a/Moose Development/Moose/AI/AI_A2G_Engage.lua b/Moose Development/Moose/AI/AI_A2G_Engage.lua index 71a2eae6f..f74f82f80 100644 --- a/Moose Development/Moose/AI/AI_A2G_Engage.lua +++ b/Moose Development/Moose/AI/AI_A2G_Engage.lua @@ -304,6 +304,8 @@ function AI_A2G_ENGAGE:New( AIGroup, EngageMinSpeed, EngageMaxSpeed, EngageFloor -- @param #AI_A2G_ENGAGE self -- @param #number Delay The delay in seconds. + self:AddTransition( { "Patrolling", "Engaging" }, "Refuel", "Refuelling" ) + return self end @@ -389,17 +391,6 @@ function AI_A2G_ENGAGE:onafterAbort( AIGroup, From, Event, To ) 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 Wrapper.Group#GROUP AIGroup The Group Object managed by the FSM. -- @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() + self.AttackSetUnit = AttackSetUnit -- Kept in memory in case of resume from refuel in air! + local AttackCount = AttackSetUnit:Count() if AttackCount > 0 then @@ -513,8 +506,9 @@ function AI_A2G_ENGAGE:onafterEngageRoute( DefenderGroup, From, Event, To, Attac end else - self:E( DefenderGroupName .. ": No targets found -> Going RTB") + self:I( DefenderGroupName .. ": No targets found -> Going RTB") self:Return() self:__RTB( self.TaskDelay ) end -end \ No newline at end of file +end + diff --git a/Moose Development/Moose/AI/AI_A2G_Patrol.lua b/Moose Development/Moose/AI/AI_A2G_Patrol.lua index 86b5d49dc..68e1954ca 100644 --- a/Moose Development/Moose/AI/AI_A2G_Patrol.lua +++ b/Moose Development/Moose/AI/AI_A2G_Patrol.lua @@ -286,9 +286,18 @@ function AI_A2G_PATROL:onafterPatrolRoute( AIPatrol, From, Event, To ) self:SetTargetDistance( ToTargetCoord ) -- For RTB status check 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. - local ToPatrolRoutePoint = ToTargetCoord:WaypointAir( + local ToWP = ToTargetCoord:WaypointAir( self.PatrolAltType, POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, @@ -296,8 +305,7 @@ function AI_A2G_PATROL:onafterPatrolRoute( AIPatrol, From, Event, To ) true ) - PatrolRoute[#PatrolRoute+1] = ToPatrolRoutePoint - PatrolRoute[#PatrolRoute+1] = ToPatrolRoutePoint + PatrolRoute[#PatrolRoute+1] = ToWP local Tasks = {} 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() } ) if AIPatrol:IsAlive() then - Fsm:__Reset( self.TaskDelay ) - Fsm:__PatrolRoute( self.TaskDelay ) + Fsm:__Reset( Fsm.TaskDelay ) + Fsm:__PatrolRoute( Fsm.TaskDelay ) end end diff --git a/Moose Development/Moose/AI/AI_A2G_SEAD.lua b/Moose Development/Moose/AI/AI_A2G_SEAD.lua index ae2f2dad1..cd2883772 100644 --- a/Moose Development/Moose/AI/AI_A2G_SEAD.lua +++ b/Moose Development/Moose/AI/AI_A2G_SEAD.lua @@ -117,6 +117,8 @@ function AI_A2G_SEAD:onafterEngage( DefenderGroup, From, Event, To, AttackSetUni local DefenderGroupName = DefenderGroup:GetName() + self.AttackSetUnit = AttackSetUnit -- Kept in memory in case of resume from refuel in air! + local AttackCount = AttackSetUnit:Count() if AttackCount > 0 then @@ -181,7 +183,7 @@ function AI_A2G_SEAD:onafterEngage( DefenderGroup, From, Event, To, AttackSetUni end if #AttackUnitTasks == 0 then - self:E( DefenderGroupName .. ": No targets found -> Going RTB") + self:I( DefenderGroupName .. ": No targets found -> Going RTB") self:Return() self:__RTB( self.TaskDelay ) else @@ -200,9 +202,19 @@ function AI_A2G_SEAD:onafterEngage( DefenderGroup, From, Event, To, AttackSetUni DefenderGroup:Route( EngageRoute, self.TaskDelay ) end else - self:E( DefenderGroupName .. ": No targets found -> Going RTB") + self:I( DefenderGroupName .. ": No targets found -> Going RTB") self:Return() self:__RTB( self.TaskDelay ) 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 \ No newline at end of file diff --git a/Moose Development/Moose/AI/AI_Air.lua b/Moose Development/Moose/AI/AI_Air.lua index 0bee1f1ab..8c6dbb9e0 100644 --- a/Moose Development/Moose/AI/AI_Air.lua +++ b/Moose Development/Moose/AI/AI_Air.lua @@ -446,7 +446,7 @@ function AI_AIR:onafterStatus() local DistanceFromHomeBase = self.HomeAirbase:GetCoordinate():Get2DDistance( self.Controllable:GetCoordinate() ) 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 ) RTB = false end @@ -461,7 +461,7 @@ function AI_AIR:onafterStatus() -- 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() @@ -470,10 +470,10 @@ function AI_AIR:onafterStatus() if Fuel < self.FuelThresholdPercentage 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() 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 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. -- The damaged unit will RTB due to DCS logic, and the others will continue to engage. 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() RTB = true self:SetStatusOff() @@ -513,7 +513,7 @@ function AI_AIR:onafterStatus() if Damage ~= InitialLife then self:Damaged() else - self:E( self.Controllable:GetName() .. " control lost! " ) + self:I( self.Controllable:GetName() .. " control lost! " ) self:LostControl() end @@ -570,7 +570,7 @@ function AI_AIR:onafterRTB( AIGroup, From, Event, To ) if AIGroup and AIGroup:IsAlive() then - self:E( "Group " .. AIGroup:GetName() .. " ... RTB! ( " .. self:GetState() .. " )" ) + self:I( "Group " .. AIGroup:GetName() .. " ... RTB! ( " .. self:GetState() .. " )" ) self:ClearTargetDistance() --AIGroup:ClearTasks() @@ -588,7 +588,7 @@ function AI_AIR:onafterRTB( AIGroup, From, Event, To ) local ToAirbaseCoord = FromCoord:Translate( 5000, ToAirbaseAngle ) if Distance < 5000 then - self:E( "RTB and near the airbase!" ) + self:I( "RTB and near the airbase!" ) self:Home() return end @@ -634,7 +634,7 @@ end function AI_AIR:onafterHome( 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 end @@ -648,7 +648,7 @@ end function AI_AIR:onafterHold( AIGroup, From, Event, To, HoldTime ) 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 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 ) self:F( { AIGroup, From, Event, To } ) - self:E( "Group " .. self.Controllable:GetName() .. " ... Refuelling! ( " .. self:GetState() .. " )" ) if AIGroup and AIGroup:IsAlive() then local Tanker = GROUP:FindByName( self.TankerName ) + if Tanker:IsAlive() and Tanker:IsAirPlane() then + self:I( "Group " .. self.Controllable:GetName() .. " ... Refuelling! ( " .. self:GetState() .. "), at tanker " .. self.TankerName ) + local RefuelRoute = {} --- Calculate the target route point. diff --git a/Moose Development/Moose/AI/AI_Air_Dispatcher.lua b/Moose Development/Moose/AI/AI_Air_Dispatcher.lua index c802521dd..ac912f819 100644 --- a/Moose Development/Moose/AI/AI_Air_Dispatcher.lua +++ b/Moose Development/Moose/AI/AI_Air_Dispatcher.lua @@ -3289,6 +3289,9 @@ do self:Patrol( SquadronName, PatrolTaskType ) end + + + end diff --git a/Moose Development/Moose/AI/AI_Escort.lua b/Moose Development/Moose/AI/AI_Escort.lua index c7e9645f7..af3a4c304 100644 --- a/Moose Development/Moose/AI/AI_Escort.lua +++ b/Moose Development/Moose/AI/AI_Escort.lua @@ -545,7 +545,7 @@ function AI_ESCORT:SetFlightMenuFormation( Formation ) if MenuFormation then 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 MenuFlightFormationID = MENU_GROUP_COMMAND:New( self.PlayerGroup, Formation, FlightMenuFormation, function ( self, Formation, ... ) diff --git a/Moose Development/Moose/AI/AI_Escort_Dispatcher.lua b/Moose Development/Moose/AI/AI_Escort_Dispatcher.lua index 8615573fb..7b85afe07 100644 --- a/Moose Development/Moose/AI/AI_Escort_Dispatcher.lua +++ b/Moose Development/Moose/AI/AI_Escort_Dispatcher.lua @@ -3,6 +3,7 @@ -- ## Features: -- -- -- * Provides the facilities to trigger escorts when players join flight slots. +-- * -- -- === -- diff --git a/Moose Development/Moose/AI/AI_Escort_Dispatcher_Request.lua b/Moose Development/Moose/AI/AI_Escort_Dispatcher_Request.lua index 0bf3d1a6b..8735f7064 100644 --- a/Moose Development/Moose/AI/AI_Escort_Dispatcher_Request.lua +++ b/Moose Development/Moose/AI/AI_Escort_Dispatcher_Request.lua @@ -83,13 +83,6 @@ function AI_ESCORT_DISPATCHER_REQUEST:OnEventExit( EventData ) local PlayerGroup = EventData.IniGroup 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.AI_Escorts[PlayerGroupName] then self.AI_Escorts[PlayerGroupName]:Stop() @@ -107,11 +100,6 @@ function AI_ESCORT_DISPATCHER_REQUEST:OnEventBirth( EventData ) local PlayerGroup = EventData.IniGroup 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 not self.AI_Escorts[PlayerGroupName] then local LeaderUnit = PlayerUnit diff --git a/Moose Development/Moose/AI/AI_Patrol.lua b/Moose Development/Moose/AI/AI_Patrol.lua index c4c0330bf..6e2b6ff67 100644 --- a/Moose Development/Moose/AI/AI_Patrol.lua +++ b/Moose Development/Moose/AI/AI_Patrol.lua @@ -825,7 +825,7 @@ function AI_PATROL_ZONE:onafterStatus() local Fuel = self.Controllable:GetFuelMin() 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 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. local Damage = self.Controllable:GetLife() 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 end @@ -900,7 +900,6 @@ end function AI_PATROL_ZONE:OnCrash( EventData ) if self.Controllable:IsAlive() and EventData.IniDCSGroupName == self.Controllable:GetName() then - self:E( self.Controllable:GetUnits() ) if #self.Controllable:GetUnits() == 1 then self:__Crash( 1, EventData ) end diff --git a/Moose Development/Moose/Core/Base.lua b/Moose Development/Moose/Core/Base.lua index db2f5ddaf..c042904a1 100644 --- a/Moose Development/Moose/Core/Base.lua +++ b/Moose Development/Moose/Core/Base.lua @@ -805,15 +805,17 @@ do -- Scheduling end self.Scheduler.SchedulerObject = self.Scheduler + --self.MasterObject = self - local ScheduleID = _SCHEDULEDISPATCHER:AddSchedule( + local ScheduleID = self.Scheduler:Schedule( self, SchedulerFunction, { ... }, Start, Repeat, RandomizeFactor, - Stop + Stop, + 4 ) self._.Schedules[#self._.Schedules+1] = ScheduleID @@ -889,6 +891,26 @@ end -- Log a trace (only shown when trace is on) -- 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 -- 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. @@ -903,7 +925,7 @@ end -- -- Switch the tracing Off -- BASE:TraceOnOff( false ) function BASE:TraceOnOff( TraceOnOff ) - _TraceOnOff = TraceOnOff + _TraceOnOff = TraceOnOff or true end @@ -923,8 +945,8 @@ end -- @param #BASE self -- @param #number Level function BASE:TraceLevel( Level ) - _TraceLevel = Level - self:E( "Tracing level " .. Level ) + _TraceLevel = Level or 1 + self:I( "Tracing level " .. _TraceLevel ) end --- Trace all methods in MOOSE @@ -932,12 +954,12 @@ end -- @param #boolean TraceAll true = trace all methods in MOOSE. function BASE:TraceAll( TraceAll ) - _TraceAll = TraceAll + _TraceAll = TraceAll or true if _TraceAll then - self:E( "Tracing all methods in MOOSE " ) + self:I( "Tracing all methods in MOOSE " ) else - self:E( "Switched off tracing all methods in MOOSE" ) + self:I( "Switched off tracing all methods in MOOSE" ) end end @@ -947,7 +969,7 @@ end function BASE:TraceClass( Class ) _TraceClass[Class] = true _TraceClassMethod[Class] = {} - self:E( "Tracing class " .. Class ) + self:I( "Tracing class " .. Class ) end --- Set tracing for a specific method of class @@ -960,7 +982,7 @@ function BASE:TraceClassMethod( Class, Method ) _TraceClassMethod[Class].Method = {} end _TraceClassMethod[Class].Method[Method] = true - self:E( "Tracing method " .. Method .. " of class " .. Class ) + self:I( "Tracing method " .. Method .. " of class " .. Class ) end --- Trace a function call. This function is private. @@ -987,7 +1009,7 @@ function BASE:_F( Arguments, DebugInfoCurrentParam, DebugInfoFromParam ) if DebugInfoFrom then LineFrom = DebugInfoFrom.currentline 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 @@ -1062,7 +1084,7 @@ function BASE:_T( Arguments, DebugInfoCurrentParam, DebugInfoFromParam ) if DebugInfoFrom then LineFrom = DebugInfoFrom.currentline 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 @@ -1133,9 +1155,9 @@ function BASE:E( Arguments ) LineFrom = DebugInfoFrom.currentline 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 - 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 @@ -1161,9 +1183,9 @@ function BASE:I( Arguments ) LineFrom = DebugInfoFrom.currentline 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 - 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 diff --git a/Moose Development/Moose/Core/Database.lua b/Moose Development/Moose/Core/Database.lua index 2ecbdcf6e..6d2f404ef 100644 --- a/Moose Development/Moose/Core/Database.lua +++ b/Moose Development/Moose/Core/Database.lua @@ -1,9 +1,9 @@ ---- **Core** - Manages several databases containing templates, mission objects, and mission information. --- +--- **Core** - Manages several databases containing templates, mission objects, and mission information. +-- -- === --- +-- -- ## Features: --- +-- -- * During mission startup, scan the mission environment, and create / instantiate intelligently the different objects as defined within the mission. -- * Manage database of DCS Group templates (as modelled using the mission editor). -- - Group templates. @@ -20,14 +20,14 @@ -- * Manage database of hits to units and statics. -- * Manage database of destroys of units and statics. -- * Manage database of @{Core.Zone#ZONE_BASE} objects. --- +-- -- === --- +-- -- ### Author: **FlightControl** --- ### Contributions: --- +-- ### Contributions: +-- -- === --- +-- -- @module Core.Database -- @image Core_Database.JPG @@ -36,9 +36,9 @@ -- @extends Core.Base#BASE --- Contains collections of wrapper objects defined within MOOSE that reflect objects within the simulator. --- +-- -- Mission designers can use the DATABASE class to refer to: --- +-- -- * STATICS -- * UNITS -- * GROUPS @@ -47,12 +47,12 @@ -- * PLAYERSJOINED -- * PLAYERS -- * CARGOS --- +-- -- On top, for internal MOOSE administration purposes, the DATBASE administers the Unit and Group TEMPLATES as defined within the Mission Editor. --- +-- -- The singleton object **_DATABASE** is automatically created by MOOSE, that administers all objects within the mission. -- Moose refers to **_DATABASE** within the framework extensively, but you can also refer to the _DATABASE object within your missions if required. --- +-- -- @field #DATABASE DATABASE = { ClassName = "DATABASE", @@ -81,6 +81,7 @@ DATABASE = { HITS = {}, DESTROYS = {}, ZONES = {}, + ZONES_GOAL = {}, WAREHOUSES = {}, FLIGHTGROUPS = {}, FLIGHTCONTROLS = {}, @@ -115,7 +116,7 @@ function DATABASE:New() local self = BASE:Inherit( self, BASE:New() ) -- #DATABASE self:SetEventPriority( 1 ) - + self:HandleEvent( EVENTS.Birth, self._EventOnBirth ) self:HandleEvent( EVENTS.Dead, self._EventOnDeadOrCrash ) self:HandleEvent( EVENTS.Crash, self._EventOnDeadOrCrash ) @@ -125,11 +126,11 @@ function DATABASE:New() self:HandleEvent( EVENTS.DeleteCargo ) self:HandleEvent( EVENTS.NewZone ) self:HandleEvent( EVENTS.DeleteZone ) - + -- Follow alive players and clients --self:HandleEvent( EVENTS.PlayerEnterUnit, self._EventOnPlayerEnterUnit ) -- This is not working anymore!, handling this through the birth event. self:HandleEvent( EVENTS.PlayerLeaveUnit, self._EventOnPlayerLeaveUnit ) - + self:_RegisterTemplates() self:_RegisterGroupsAndUnits() self:_RegisterClients() @@ -138,16 +139,16 @@ function DATABASE:New() self:_RegisterAirbases() self.UNITS_Position = 0 - + --- @param #DATABASE self local function CheckPlayers( self ) - + local CoalitionsData = { AlivePlayersRed = coalition.getPlayers( coalition.side.RED ), AlivePlayersBlue = coalition.getPlayers( coalition.side.BLUE ), AlivePlayersNeutral = coalition.getPlayers( coalition.side.NEUTRAL )} for CoalitionId, CoalitionData in pairs( CoalitionsData ) do --self:E( { "CoalitionData:", CoalitionData } ) for UnitId, UnitData in pairs( CoalitionData ) do if UnitData and UnitData:isExist() then - + local UnitName = UnitData:getName() local PlayerName = UnitData:getPlayerName() local PlayerUnit = UNIT:Find( UnitData ) @@ -166,10 +167,10 @@ function DATABASE:New() end end end - + --self:E( "Scheduling" ) --PlayerCheckSchedule = SCHEDULER:New( nil, CheckPlayers, { self }, 1, 1 ) - + return self end @@ -189,12 +190,13 @@ end function DATABASE:AddUnit( DCSUnitName ) if not self.UNITS[DCSUnitName] then + self:I( { "Add UNIT:", DCSUnitName } ) local UnitRegister = UNIT:Register( DCSUnitName ) self.UNITS[DCSUnitName] = UNIT:Register( DCSUnitName ) - + table.insert( self.UNITS_Index, DCSUnitName ) end - + return self.UNITS[DCSUnitName] end @@ -203,7 +205,7 @@ end -- @param #DATABASE self function DATABASE:DeleteUnit( DCSUnitName ) - self.UNITS[DCSUnitName] = nil + self.UNITS[DCSUnitName] = nil end --- Adds a Static based on the Static Name in the DATABASE. @@ -214,7 +216,7 @@ function DATABASE:AddStatic( DCSStaticName ) self.STATICS[DCSStaticName] = STATIC:Register( DCSStaticName ) return self.STATICS[DCSStaticName] end - + return nil end @@ -223,7 +225,7 @@ end -- @param #DATABASE self function DATABASE:DeleteStatic( DCSStaticName ) - --self.STATICS[DCSStaticName] = nil + --self.STATICS[DCSStaticName] = nil end --- Finds a STATIC based on the StaticName. @@ -262,7 +264,7 @@ end -- @param #string AirbaseName The name of the airbase function DATABASE:DeleteAirbase( AirbaseName ) - self.AIRBASES[AirbaseName] = nil + self.AIRBASES[AirbaseName] = nil end --- Finds an AIRBASE based on the AirbaseName. @@ -283,29 +285,29 @@ do -- Zones -- @param #string ZoneName The name of the zone. -- @return Core.Zone#ZONE_BASE The found ZONE. function DATABASE:FindZone( ZoneName ) - + local ZoneFound = self.ZONES[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:AddZone( ZoneName, Zone ) - + if not self.ZONES[ZoneName] then self.ZONES[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:DeleteZone( ZoneName ) - - self.ZONES[ZoneName] = nil + + self.ZONES[ZoneName] = nil end @@ -322,56 +324,89 @@ do -- Zones self.ZONENAMES[ZoneName] = ZoneName self:AddZone( ZoneName, Zone ) end - + for ZoneGroupName, ZoneGroup in pairs( self.GROUPS ) do if ZoneGroupName:match("#ZONE_POLYGON") then local ZoneName1 = ZoneGroupName:match("(.*)#ZONE_POLYGON") local ZoneName2 = ZoneGroupName:match(".*#ZONE_POLYGON(.*)") local ZoneName = ZoneName1 .. ( ZoneName2 or "" ) - + self:I( { "Register ZONE_POLYGON:", Name = ZoneName } ) local Zone_Polygon = ZONE_POLYGON:New( ZoneName, ZoneGroup ) self.ZONENAMES[ZoneName] = ZoneName self:AddZone( ZoneName, Zone_Polygon ) end end - + end 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 --- Adds a Cargo based on the Cargo Name in the DATABASE. -- @param #DATABASE self -- @param #string CargoName The name of the airbase function DATABASE:AddCargo( Cargo ) - + if not self.CARGOS[Cargo.Name] then self.CARGOS[Cargo.Name] = Cargo end end - - + + --- Deletes a Cargo from the DATABASE based on the Cargo Name. -- @param #DATABASE self -- @param #string CargoName The name of the airbase function DATABASE:DeleteCargo( CargoName ) - - self.CARGOS[CargoName] = nil + + self.CARGOS[CargoName] = nil end - + --- Finds an CARGO based on the CargoName. -- @param #DATABASE self -- @param #string CargoName -- @return Wrapper.Cargo#CARGO The found CARGO. function DATABASE:FindCargo( CargoName ) - + local CargoFound = self.CARGOS[CargoName] return CargoFound end - + --- Checks if the Template name has a #CARGO tag. -- If yes, the group is a cargo. -- @param #DATABASE self @@ -380,10 +415,10 @@ do -- cargo function DATABASE:IsCargo( TemplateName ) TemplateName = env.getValueDictByKey( TemplateName ) - + local Cargo = TemplateName:match( "#(CARGO)" ) - return Cargo and Cargo == "CARGO" + return Cargo and Cargo == "CARGO" end --- Private method that registers new Static Templates within the DATABASE Object. @@ -392,7 +427,7 @@ do -- cargo function DATABASE:_RegisterCargos() local Groups = UTILS.DeepCopy( self.GROUPS ) -- This is a very important statement. CARGO_GROUP:New creates a new _DATABASE.GROUP entry, which will confuse the loop. I searched 4 hours on this to find the bug! - + for CargoGroupName, CargoGroup in pairs( Groups ) do self:I( { Cargo = CargoGroupName } ) if self:IsCargo( CargoGroupName ) then @@ -405,12 +440,12 @@ do -- cargo local Name = CargoParam and CargoParam:match( "N=([%a%d]+),?") or CargoName local LoadRadius = CargoParam and tonumber( CargoParam:match( "RR=([%a%d]+),?") ) local NearRadius = CargoParam and tonumber( CargoParam:match( "NR=([%a%d]+),?") ) - + self:I({"Register CargoGroup:",Type=Type,Name=Name,LoadRadius=LoadRadius,NearRadius=NearRadius}) CARGO_GROUP:New( CargoGroup, Type, Name, LoadRadius, NearRadius ) end end - + for CargoStaticName, CargoStatic in pairs( self.STATICS ) do if self:IsCargo( CargoStaticName ) then local CargoInfo = CargoStaticName:match("#CARGO(.*)") @@ -421,7 +456,7 @@ do -- cargo local Name = CargoParam and CargoParam:match( "N=([%a%d]+),?") or CargoName local LoadRadius = CargoParam and tonumber( CargoParam:match( "RR=([%a%d]+),?") ) local NearRadius = CargoParam and tonumber( CargoParam:match( "NR=([%a%d]+),?") ) - + if Category == "SLING" then self:I({"Register CargoSlingload:",Type=Type,Name=Name,LoadRadius=LoadRadius,NearRadius=NearRadius}) CARGO_SLINGLOAD:New( CargoStatic, Type, Name, LoadRadius, NearRadius ) @@ -433,7 +468,7 @@ do -- cargo end end end - + end end -- cargo @@ -479,9 +514,9 @@ function DATABASE:AddGroup( GroupName ) if not self.GROUPS[GroupName] then self:I( { "Add GROUP:", GroupName } ) self.GROUPS[GroupName] = GROUP:Register( GroupName ) - end - - return self.GROUPS[GroupName] + end + + return self.GROUPS[GroupName] end --- Adds a player based on the Player Name in the DATABASE. @@ -584,7 +619,7 @@ function DATABASE:Spawn( SpawnTemplate ) for UnitID, UnitData in pairs( SpawnTemplate.units ) do self:AddUnit( UnitData.name ) end - + return SpawnGroup end @@ -616,21 +651,21 @@ end function DATABASE:_RegisterGroupTemplate( GroupTemplate, CoalitionSide, CategoryID, CountryID, GroupName ) local GroupTemplateName = GroupName or env.getValueDictByKey( GroupTemplate.name ) - + if not self.Templates.Groups[GroupTemplateName] then self.Templates.Groups[GroupTemplateName] = {} self.Templates.Groups[GroupTemplateName].Status = nil end - + -- Delete the spans from the route, it is not needed and takes memory. - if GroupTemplate.route and GroupTemplate.route.spans then + if GroupTemplate.route and GroupTemplate.route.spans then GroupTemplate.route.spans = nil end - + GroupTemplate.CategoryID = CategoryID GroupTemplate.CoalitionID = CoalitionSide GroupTemplate.CountryID = CountryID - + self.Templates.Groups[GroupTemplateName].GroupName = GroupTemplateName self.Templates.Groups[GroupTemplateName].Template = GroupTemplate self.Templates.Groups[GroupTemplateName].groupId = GroupTemplate.groupId @@ -645,7 +680,7 @@ function DATABASE:_RegisterGroupTemplate( GroupTemplate, CoalitionSide, Category for unit_num, UnitTemplate in pairs( GroupTemplate.units ) do UnitTemplate.name = env.getValueDictByKey(UnitTemplate.name) - + self.Templates.Units[UnitTemplate.name] = {} self.Templates.Units[UnitTemplate.name].UnitName = UnitTemplate.name self.Templates.Units[UnitTemplate.name].Template = UnitTemplate @@ -663,8 +698,8 @@ function DATABASE:_RegisterGroupTemplate( GroupTemplate, CoalitionSide, Category self.Templates.ClientsByName[UnitTemplate.name].CountryID = CountryID self.Templates.ClientsByID[UnitTemplate.unitId] = UnitTemplate end - - UnitNames[#UnitNames+1] = self.Templates.Units[UnitTemplate.name].UnitName + + UnitNames[#UnitNames+1] = self.Templates.Units[UnitTemplate.name].UnitName end self:I( { Group = self.Templates.Groups[GroupTemplateName].GroupName, @@ -693,13 +728,13 @@ function DATABASE:_RegisterStaticTemplate( StaticTemplate, CoalitionID, Category local StaticTemplate = UTILS.DeepCopy( StaticTemplate ) local StaticTemplateName = env.getValueDictByKey(StaticTemplate.name) - + self.Templates.Statics[StaticTemplateName] = self.Templates.Statics[StaticTemplateName] or {} - + StaticTemplate.CategoryID = CategoryID StaticTemplate.CoalitionID = CoalitionID StaticTemplate.CountryID = CountryID - + self.Templates.Statics[StaticTemplateName].StaticName = StaticTemplateName self.Templates.Statics[StaticTemplateName].GroupTemplate = StaticTemplate self.Templates.Statics[StaticTemplateName].UnitTemplate = StaticTemplate.units[1] @@ -710,12 +745,12 @@ function DATABASE:_RegisterStaticTemplate( StaticTemplate, CoalitionID, Category self:I( { Static = self.Templates.Statics[StaticTemplateName].StaticName, Coalition = self.Templates.Statics[StaticTemplateName].CoalitionID, Category = self.Templates.Statics[StaticTemplateName].CategoryID, - Country = self.Templates.Statics[StaticTemplateName].CountryID + Country = self.Templates.Statics[StaticTemplateName].CountryID } ) - + self:AddStatic( StaticTemplateName ) - + end @@ -783,7 +818,7 @@ function DATABASE:_RegisterPlayers() end end end - + return self end @@ -799,12 +834,12 @@ function DATABASE:_RegisterGroupsAndUnits() if DCSGroup:isExist() then local DCSGroupName = DCSGroup:getName() - + self:I( { "Register Group:", DCSGroupName } ) self:AddGroup( DCSGroupName ) for DCSUnitId, DCSUnit in pairs( DCSGroup:getUnits() ) do - + local DCSUnitName = DCSUnit:getName() self:I( { "Register Unit:", DCSUnitName } ) self:AddUnit( DCSUnitName ) @@ -812,10 +847,10 @@ function DATABASE:_RegisterGroupsAndUnits() else self:E( { "Group does not exist: ", DCSGroup } ) end - + end end - + self:I("Groups:") for GroupName, Group in pairs( self.GROUPS ) do self:I( { "Group:", GroupName } ) @@ -833,7 +868,7 @@ function DATABASE:_RegisterClients() self:I( { "Register Client:", ClientName } ) self:AddClient( ClientName ) end - + return self end @@ -847,7 +882,7 @@ function DATABASE:_RegisterStatics() if DCSStatic:isExist() then local DCSStaticName = DCSStatic:getName() - + self:I( { "Register Static:", DCSStaticName } ) self:AddStatic( DCSStaticName ) else @@ -887,7 +922,7 @@ function DATABASE:_EventOnBirth( Event ) if Event.IniDCSUnit then if Event.IniObjectCategory == 3 then - self:AddStatic( Event.IniDCSUnitName ) + self:AddStatic( Event.IniDCSUnitName ) else if Event.IniObjectCategory == 1 then self:AddUnit( Event.IniDCSUnitName ) @@ -923,7 +958,7 @@ function DATABASE:_EventOnDeadOrCrash( Event ) if Event.IniObjectCategory == 3 then if self.STATICS[Event.IniDCSUnitName] then self:DeleteStatic( Event.IniDCSUnitName ) - end + end else if Event.IniObjectCategory == 1 then if self.UNITS[Event.IniDCSUnitName] then @@ -932,7 +967,7 @@ function DATABASE:_EventOnDeadOrCrash( Event ) end end end - + self:AccountDestroys( Event ) end @@ -986,7 +1021,7 @@ end -- @return #DATABASE self function DATABASE:ForEach( IteratorFunction, FinalizeFunction, arg, Set ) self:F2( arg ) - + local function CoRoutine() local Count = 0 for ObjectID, Object in pairs( Set ) do @@ -995,20 +1030,20 @@ function DATABASE:ForEach( IteratorFunction, FinalizeFunction, arg, Set ) Count = Count + 1 -- if Count % 100 == 0 then -- coroutine.yield( false ) --- end +-- end end return true end - + -- local co = coroutine.create( CoRoutine ) local co = CoRoutine - + local function Schedule() - + -- local status, res = coroutine.resume( co ) local status, res = co() self:T3( { status, res } ) - + if status == false then error( res ) end @@ -1021,8 +1056,9 @@ function DATABASE:ForEach( IteratorFunction, FinalizeFunction, arg, Set ) return false 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 end @@ -1033,7 +1069,7 @@ end -- @return #DATABASE self function DATABASE:ForEachStatic( IteratorFunction, FinalizeFunction, ... ) --R2.1 self:F2( arg ) - + self:ForEach( IteratorFunction, FinalizeFunction, arg, self.STATICS ) return self @@ -1046,7 +1082,7 @@ end -- @return #DATABASE self function DATABASE:ForEachUnit( IteratorFunction, FinalizeFunction, ... ) self:F2( arg ) - + self:ForEach( IteratorFunction, FinalizeFunction, arg, self.UNITS ) return self @@ -1059,7 +1095,7 @@ end -- @return #DATABASE self function DATABASE:ForEachGroup( IteratorFunction, FinalizeFunction, ... ) self:F2( arg ) - + self:ForEach( IteratorFunction, FinalizeFunction, arg, self.GROUPS ) return self @@ -1072,9 +1108,9 @@ end -- @return #DATABASE self function DATABASE:ForEachPlayer( IteratorFunction, FinalizeFunction, ... ) self:F2( arg ) - + self:ForEach( IteratorFunction, FinalizeFunction, arg, self.PLAYERS ) - + return self end @@ -1085,9 +1121,9 @@ end -- @return #DATABASE self function DATABASE:ForEachPlayerJoined( IteratorFunction, FinalizeFunction, ... ) self:F2( arg ) - + self:ForEach( IteratorFunction, FinalizeFunction, arg, self.PLAYERSJOINED ) - + return self end @@ -1097,9 +1133,9 @@ end -- @return #DATABASE self function DATABASE:ForEachPlayerUnit( IteratorFunction, FinalizeFunction, ... ) self:F2( arg ) - + self:ForEach( IteratorFunction, FinalizeFunction, arg, self.PLAYERUNITS ) - + return self end @@ -1110,7 +1146,7 @@ end -- @return #DATABASE self function DATABASE:ForEachClient( IteratorFunction, ... ) self:F2( arg ) - + self:ForEach( IteratorFunction, arg, self.CLIENTS ) return self @@ -1122,7 +1158,7 @@ end -- @return #DATABASE self function DATABASE:ForEachCargo( IteratorFunction, ... ) self:F2( arg ) - + self:ForEach( IteratorFunction, arg, self.CARGOS ) return self @@ -1244,10 +1280,10 @@ function DATABASE:_RegisterTemplates() if (CoalitionName == 'red' or CoalitionName == 'blue' or CoalitionName == 'neutrals') and type(coa_data) == 'table' then --self.Units[coa_name] = {} - + local CoalitionSide = coalition.side[string.upper(CoalitionName)] if CoalitionName=="red" then - CoalitionSide=coalition.side.NEUTRAL + CoalitionSide=coalition.side.RED elseif CoalitionName=="blue" then CoalitionSide=coalition.side.BLUE else @@ -1277,10 +1313,10 @@ function DATABASE:_RegisterTemplates() local CountryName = string.upper(cntry_data.name) local CountryID = cntry_data.id - + self.COUNTRY_ID[CountryName] = CountryID self.COUNTRY_NAME[CountryID] = CountryName - + --self.Units[coa_name][countryName] = {} --self.Units[coa_name][countryName]["countryId"] = cntry_data.id @@ -1299,18 +1335,18 @@ function DATABASE:_RegisterTemplates() for group_num, Template in pairs(obj_type_data.group) do if obj_type_name ~= "static" and Template and Template.units and type(Template.units) == 'table' then --making sure again- this is a valid group - self:_RegisterGroupTemplate( - Template, - CoalitionSide, - _DATABASECategory[string.lower(CategoryName)], - CountryID + self:_RegisterGroupTemplate( + Template, + CoalitionSide, + _DATABASECategory[string.lower(CategoryName)], + CountryID ) else - self:_RegisterStaticTemplate( - Template, - CoalitionSide, - _DATABASECategory[string.lower(CategoryName)], - CountryID + self:_RegisterStaticTemplate( + Template, + CoalitionSide, + _DATABASECategory[string.lower(CategoryName)], + CountryID ) end --if GroupTemplate and GroupTemplate.units then end --for group_num, GroupTemplate in pairs(obj_type_data.group) do @@ -1331,35 +1367,35 @@ end -- @param Core.Event#EVENTDATA Event function DATABASE:AccountHits( Event ) self:F( { Event } ) - + if Event.IniPlayerName ~= nil then -- It is a player that is hitting something self:T( "Hitting Something" ) - + -- What is he hitting? if Event.TgtCategory then - + -- A target got hit self.HITS[Event.TgtUnitName] = self.HITS[Event.TgtUnitName] or {} local Hit = self.HITS[Event.TgtUnitName] - + Hit.Players = Hit.Players or {} Hit.Players[Event.IniPlayerName] = true end end - + -- It is a weapon initiated by a player, that is hitting something -- This seems to occur only with scenery and static objects. - if Event.WeaponPlayerName ~= nil then + if Event.WeaponPlayerName ~= nil then self:T( "Hitting Scenery" ) - + -- What is he hitting? if Event.TgtCategory then - + if Event.WeaponCoalition then -- A coalition object was hit, probably a static. -- A target got hit self.HITS[Event.TgtUnitName] = self.HITS[Event.TgtUnitName] or {} local Hit = self.HITS[Event.TgtUnitName] - + Hit.Players = Hit.Players or {} Hit.Players[Event.WeaponPlayerName] = true else -- A scenery object was hit. @@ -1367,13 +1403,13 @@ end end end end - + --- Account the destroys. -- @param #DATABASE self -- @param Core.Event#EVENTDATA Event function DATABASE:AccountDestroys( Event ) self:F( { Event } ) - + local TargetUnit = nil local TargetGroup = nil local TargetUnitName = "" @@ -1385,26 +1421,26 @@ end local TargetUnitCoalition = nil local TargetUnitCategory = nil local TargetUnitType = nil - + if Event.IniDCSUnit then - + TargetUnit = Event.IniUnit TargetUnitName = Event.IniDCSUnitName TargetGroup = Event.IniDCSGroup TargetGroupName = Event.IniDCSGroupName TargetPlayerName = Event.IniPlayerName - + TargetCoalition = Event.IniCoalition --TargetCategory = TargetUnit:getCategory() --TargetCategory = TargetUnit:getDesc().category -- Workaround TargetCategory = Event.IniCategory TargetType = Event.IniTypeName - + TargetUnitType = TargetType - + self:T( { TargetUnitName, TargetGroupName, TargetPlayerName, TargetCoalition, TargetCategory, TargetType } ) end - + local Destroyed = false -- What is the player destroying? @@ -1413,8 +1449,3 @@ end self.DESTROYS[Event.IniUnitName] = true end end - - - - - diff --git a/Moose Development/Moose/Core/Event.lua b/Moose Development/Moose/Core/Event.lua index 0729dd498..4a6b96448 100644 --- a/Moose Development/Moose/Core/Event.lua +++ b/Moose Development/Moose/Core/Event.lua @@ -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_NEW_ZONE = world.event.S_EVENT_MAX + 1002 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. @@ -226,6 +228,8 @@ EVENTS = { DeleteCargo = world.event.S_EVENT_DELETE_CARGO, NewZone = world.event.S_EVENT_NEW_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, } @@ -462,6 +466,16 @@ local _EVENTMETA = { Event = "OnEventDeleteZone", 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] = { Order = -1, Event = "OnEventRemoveUnit", @@ -800,6 +814,38 @@ do -- Event Creation world.onEvent( Event ) 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. -- @param #EVENT self -- @param Wrapper.Unit#UNIT PlayerUnit. diff --git a/Moose Development/Moose/Core/Fsm.lua b/Moose Development/Moose/Core/Fsm.lua index ba8e3c4de..5149b4aa6 100644 --- a/Moose Development/Moose/Core/Fsm.lua +++ b/Moose Development/Moose/Core/Fsm.lua @@ -594,7 +594,17 @@ do -- FSM return errmsg end 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 local Result, Value = xpcall( function() return self[handler]( self, unpack( params ) ) end, ErrorHandler ) return Value @@ -718,13 +728,13 @@ do -- FSM if DelaySeconds < 0 then -- Only call the event ONCE! DelaySeconds = math.abs( DelaySeconds ) 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 else -- reschedule end 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 else error( "FSM: An asynchronous event trigger requires a DelaySeconds parameter!!! This can be positive or negative! Sorry, but will not process this." ) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index d9ef1b2cd..3cfe9d2f1 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -2075,7 +2075,7 @@ do -- COORDINATE -- @return #string The coordinate Text in the configured coordinate system. 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 diff --git a/Moose Development/Moose/Core/Radio.lua b/Moose Development/Moose/Core/Radio.lua index d82911f5e..575b45044 100644 --- a/Moose Development/Moose/Core/Radio.lua +++ b/Moose Development/Moose/Core/Radio.lua @@ -800,470 +800,4 @@ function BEACON:_TACANToFrequency(TACANChannel, TACANMode) return (A + TACANChannel - B) * 1000000 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 diff --git a/Moose Development/Moose/Core/RadioQueue.lua b/Moose Development/Moose/Core/RadioQueue.lua new file mode 100644 index 000000000..8ee0d7749 --- /dev/null +++ b/Moose Development/Moose/Core/RadioQueue.lua @@ -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 diff --git a/Moose Development/Moose/Core/ScheduleDispatcher.lua b/Moose Development/Moose/Core/ScheduleDispatcher.lua index 8173c85c8..b87b0685c 100644 --- a/Moose Development/Moose/Core/ScheduleDispatcher.lua +++ b/Moose Development/Moose/Core/ScheduleDispatcher.lua @@ -52,8 +52,8 @@ end -- Nothing of this code should be modified without testing it thoroughly. -- @param #SCHEDULEDISPATCHER self -- @param Core.Scheduler#SCHEDULER Scheduler -function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleArguments, Start, Repeat, Randomize, Stop ) - self:F2( { 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, TraceLevel } ) self.CallID = self.CallID + 1 local CallID = self.CallID .. "#" .. ( Scheduler.MasterObject and Scheduler.MasterObject.GetClassNameAndID and Scheduler.MasterObject:GetClassNameAndID() or "" ) or "" @@ -84,11 +84,52 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr self.Schedule[Scheduler][CallID].Repeat = Repeat or 0 self.Schedule[Scheduler][CallID].Randomize = Randomize or 0 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.Schedule[Scheduler][CallID].CallHandler = function( CallID ) - --self:E( CallID ) + self.Schedule[Scheduler][CallID].CallHandler = function( Params ) + + 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 ) env.info( "Error in timer function: " .. errmsg ) @@ -122,15 +163,19 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr local Stop = Schedule.Stop or 0 local ScheduleID = Schedule.ScheduleID + local Prefix = ( Repeat == 0 ) and "--->" or "+++>" + local Status, Result --self:E( { SchedulerObject = SchedulerObject } ) if SchedulerObject then local function Timer() + SchedulerObject:T( Prefix .. Name .. ":" .. Line .. " (" .. Source .. ")" ) return ScheduleFunction( SchedulerObject, unpack( ScheduleArguments ) ) end Status, Result = xpcall( Timer, ErrorHandler ) else local function Timer() + self:T( Prefix .. Name .. ":" .. Line .. " (" .. Source .. ")" ) return ScheduleFunction( unpack( ScheduleArguments ) ) end Status, Result = xpcall( Timer, ErrorHandler ) @@ -161,13 +206,13 @@ function SCHEDULEDISPATCHER:AddSchedule( Scheduler, ScheduleFunction, ScheduleAr self:Stop( Scheduler, CallID ) end else - self:E( "Scheduled obsolete call for CallID: " .. CallID ) + self:I( "<<<>" .. Name .. ":" .. Line .. " (" .. Source .. ")" ) end return nil end - self:Start( Scheduler, CallID ) + self:Start( Scheduler, CallID, Info ) return CallID end @@ -181,7 +226,7 @@ function SCHEDULEDISPATCHER:RemoveSchedule( Scheduler, CallID ) end end -function SCHEDULEDISPATCHER:Start( Scheduler, CallID ) +function SCHEDULEDISPATCHER:Start( Scheduler, CallID, Info ) self:F2( { Start = CallID, Scheduler = Scheduler } ) 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].ScheduleID = timer.scheduleFunction( Schedule[CallID].CallHandler, - CallID, + { CallID = CallID, Info = Info }, timer.getTime() + Schedule[CallID].Start ) end else for CallID, Schedule in pairs( self.Schedule[Scheduler] or {} ) do - self:Start( Scheduler, CallID ) -- Recursive + self:Start( Scheduler, CallID, Info ) -- Recursive end end end diff --git a/Moose Development/Moose/Core/Scheduler.lua b/Moose Development/Moose/Core/Scheduler.lua index 2b5e7e03b..df54ced90 100644 --- a/Moose Development/Moose/Core/Scheduler.lua +++ b/Moose Development/Moose/Core/Scheduler.lua @@ -216,7 +216,7 @@ function SCHEDULER:New( SchedulerObject, SchedulerFunction, SchedulerArguments, self.MasterObject = SchedulerObject 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 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 Stop Specifies the amount of seconds when the scheduler will be stopped. -- @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:T3( { SchedulerArguments } ) @@ -256,7 +256,9 @@ function SCHEDULER:Schedule( SchedulerObject, SchedulerFunction, SchedulerArgume Start, Repeat, RandomizeFactor, - Stop + Stop, + TraceLevel or 3, + Fsm ) self.Schedules[#self.Schedules+1] = ScheduleID diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index 8834dcb0b..149d22d23 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -384,7 +384,6 @@ do -- SET_BASE for ObjectName, Object in pairs( self.Database ) do if self:IsIncludeObject( Object ) then - self:E( { "Adding Object:", ObjectName } ) self:Add( ObjectName, Object ) end end @@ -5497,4 +5496,324 @@ do -- SET_ZONE return nil end -end \ No newline at end of file +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 diff --git a/Moose Development/Moose/Core/Settings.lua b/Moose Development/Moose/Core/Settings.lua index 0f371ab55..b28f4753d 100644 --- a/Moose Development/Moose/Core/Settings.lua +++ b/Moose Development/Moose/Core/Settings.lua @@ -1,9 +1,9 @@ --- **Core** - Manages various settings for running missions, consumed by moose classes and provides a menu system for players to tweak settings in running missions. -- -- === --- +-- -- ## Features: --- +-- -- * Provide a settings menu system to the players. -- * Provide a player settings menu and an overall mission settings menu. -- * Mission settings provide default settings, while player settings override mission settings. @@ -11,19 +11,19 @@ -- * Provide a menu to select between different coordinate formats for A2A coordinates. -- * Provide a menu to select between different message time duration options. -- * Provide a menu to select between different metric systems. --- +-- -- === --- +-- -- The documentation of the SETTINGS class can be found further in this document. --- +-- -- === --- +-- -- # **AUTHORS and CONTRIBUTIONS** --- --- ### Contributions: --- --- ### Authors: --- +-- +-- ### Contributions: +-- +-- ### Authors: +-- -- * **FlightControl**: Design & Programming -- -- @module Core.Settings @@ -34,168 +34,181 @@ -- @extends Core.Base#BASE --- Takes care of various settings that influence the behaviour of certain functionalities and classes within the MOOSE framework. --- +-- -- === --- +-- -- The SETTINGS class takes care of various settings that influence the behaviour of certain functionalities and classes within the MOOSE framework. -- SETTINGS can work on 2 levels: --- --- - **Default settings**: A running mission has **Default settings**. +-- +-- - **Default settings**: A running mission has **Default settings**. -- - **Player settings**: For each player its own **Player settings** can be defined, overriding the **Default settings**. --- +-- -- So, when there isn't any **Player setting** defined for a player for a specific setting, or, the player cannot be identified, the **Default setting** will be used instead. --- +-- -- # 1) \_SETTINGS object --- +-- -- MOOSE defines by default a singleton object called **\_SETTINGS**. Use this object to modify all the **Default settings** for a running mission. -- For each player, MOOSE will automatically allocate also a **player settings** object, and will expose a radio menu to allow the player to adapt the settings to his own preferences. --- +-- -- # 2) SETTINGS Menu --- +-- -- Settings can be adapted by the Players and by the Mission Administrator through **radio menus, which are automatically available in the mission**. -- These menus can be found **on level F10 under "Settings"**. There are two kinds of menus generated by the system. --- +-- -- ## 2.1) Default settings menu --- +-- -- A menu is created automatically per Command Center that allows to modify the **Default** settings. -- So, when joining a CC unit, a menu will be available that allows to change the settings parameters **FOR ALL THE PLAYERS**! -- Note that the **Default settings** will only be used when a player has not choosen its own settings. --- +-- -- ## 2.2) Player settings menu --- +-- -- A menu is created automatically per Player Slot (group) that allows to modify the **Player** settings. -- So, when joining a slot, a menu wil be available that allows to change the settings parameters **FOR THE PLAYER ONLY**! -- Note that when a player has not chosen a specific setting, the **Default settings** will be used. --- +-- -- ## 2.3) Show or Hide the Player Setting menus --- +-- -- Of course, it may be requried not to show any setting menus. In this case, a method is available on the **\_SETTINGS object**. -- Use @{#SETTINGS.SetPlayerMenuOff}() to hide the player menus, and use @{#SETTINGS.SetPlayerMenuOn}() show the player menus. -- Note that when this method is used, any player already in a slot will not have its menus visibility changed. --- The option will only have effect when a player enters a new slot or changes a slot. --- +-- The option will only have effect when a player enters a new slot or changes a slot. +-- -- Example: --- +-- -- _SETTINGS:SetPlayerMenuOff() -- will disable the player menus. -- _SETTINGS:SetPlayerMenuOn() -- will enable the player menus. -- -- But only when a player exits and reenters the slot these settings will have effect! --- --- +-- +-- -- # 3) Settings --- +-- -- There are different settings that are managed and applied within the MOOSE framework. -- See below a comprehensive description of each. --- +-- -- ## 3.1) **A2G coordinates** display formatting --- +-- -- ### 3.1.1) A2G coordinates setting **types** --- +-- -- Will customize which display format is used to indicate A2G coordinates in text as part of the Command Center communications. --- +-- -- - A2G BR: [Bearing Range](https://en.wikipedia.org/wiki/Bearing_(navigation)). -- - A2G MGRS: The [Military Grid Reference System](https://en.wikipedia.org/wiki/Military_Grid_Reference_System). The accuracy can also be adapted. -- - A2G LL DMS: Lattitude Longitude [Degrees Minutes Seconds](https://en.wikipedia.org/wiki/Geographic_coordinate_conversion). The accuracy can also be adapted. -- - A2G LL DDM: Lattitude Longitude [Decimal Degrees Minutes](https://en.wikipedia.org/wiki/Decimal_degrees). The accuracy can also be adapted. --- +-- -- ### 3.1.2) A2G coordinates setting **menu** --- +-- -- The settings can be changed by using the **Default settings menu** on the Command Center or the **Player settings menu** on the Player Slot. --- +-- -- ### 3.1.3) A2G coordinates setting **methods** --- +-- -- There are different methods that can be used to change the **System settings** using the \_SETTINGS object. --- +-- -- - @{#SETTINGS.SetA2G_BR}(): Enable the BR display formatting by default. -- - @{#SETTINGS.SetA2G_MGRS}(): Enable the MGRS display formatting by default. Use @{SETTINGS.SetMGRS_Accuracy}() to adapt the accuracy of the MGRS formatting. --- - @{#SETTINGS.SetA2G_LL_DMS}(): Enable the LL DMS display formatting by default. Use @{SETTINGS.SetLL_Accuracy}() to adapt the accuracy of the Seconds formatting. +-- - @{#SETTINGS.SetA2G_LL_DMS}(): Enable the LL DMS display formatting by default. Use @{SETTINGS.SetLL_Accuracy}() to adapt the accuracy of the Seconds formatting. -- - @{#SETTINGS.SetA2G_LL_DDM}(): Enable the LL DDM display formatting by default. Use @{SETTINGS.SetLL_Accuracy}() to adapt the accuracy of the Seconds formatting. --- +-- -- ### 3.1.4) A2G coordinates setting - additional notes --- --- One additional note on BR. In a situation when a BR coordinate should be given, +-- +-- One additional note on BR. In a situation when a BR coordinate should be given, -- but there isn't any player context (no player unit to reference from), the MGRS formatting will be applied! --- +-- -- ## 3.2) **A2A coordinates** formatting --- +-- -- ### 3.2.1) A2A coordinates setting **types** --- +-- -- Will customize which display format is used to indicate A2A coordinates in text as part of the Command Center communications. --- +-- -- - A2A BRAA: [Bearing Range Altitude Aspect](https://en.wikipedia.org/wiki/Bearing_(navigation)). -- - A2A MGRS: The [Military Grid Reference System](https://en.wikipedia.org/wiki/Military_Grid_Reference_System). The accuracy can also be adapted. -- - A2A LL DMS: Lattitude Longitude [Degrees Minutes Seconds](https://en.wikipedia.org/wiki/Geographic_coordinate_conversion). The accuracy can also be adapted. -- - A2A LL DDM: Lattitude Longitude [Decimal Degrees and Minutes](https://en.wikipedia.org/wiki/Decimal_degrees). The accuracy can also be adapted. -- - A2A BULLS: [Bullseye](http://falcon4.wikidot.com/concepts:bullseye). --- +-- -- ### 3.2.2) A2A coordinates setting **menu** --- +-- -- The settings can be changed by using the **Default settings menu** on the Command Center or the **Player settings menu** on the Player Slot. --- +-- -- ### 3.2.3) A2A coordinates setting **methods** --- +-- -- There are different methods that can be used to change the **System settings** using the \_SETTINGS object. --- +-- -- - @{#SETTINGS.SetA2A_BRAA}(): Enable the BR display formatting by default. -- - @{#SETTINGS.SetA2A_MGRS}(): Enable the MGRS display formatting by default. Use @{SETTINGS.SetMGRS_Accuracy}() to adapt the accuracy of the MGRS formatting. --- - @{#SETTINGS.SetA2A_LL_DMS}(): Enable the LL DMS display formatting by default. Use @{SETTINGS.SetLL_Accuracy}() to adapt the accuracy of the Seconds formatting. +-- - @{#SETTINGS.SetA2A_LL_DMS}(): Enable the LL DMS display formatting by default. Use @{SETTINGS.SetLL_Accuracy}() to adapt the accuracy of the Seconds formatting. -- - @{#SETTINGS.SetA2A_LL_DDM}(): Enable the LL DDM display formatting by default. Use @{SETTINGS.SetLL_Accuracy}() to adapt the accuracy of the Seconds formatting. -- - @{#SETTINGS.SetA2A_BULLS}(): Enable the BULLSeye display formatting by default. --- +-- -- ### 3.2.4) A2A coordinates settings - additional notes --- --- One additional note on BRAA. In a situation when a BRAA coordinate should be given, +-- +-- One additional note on BRAA. In a situation when a BRAA coordinate should be given, -- but there isn't any player context (no player unit to reference from), the MGRS formatting will be applied! --- +-- -- ## 3.3) **Measurements** formatting --- +-- -- ### 3.3.1) Measurements setting **types** --- +-- -- Will customize the measurements system being used as part as part of the Command Center communications. --- +-- -- - **Metrics** system: Applies the [Metrics system](https://en.wikipedia.org/wiki/Metric_system) ... --- - **Imperial** system: Applies the [Imperial system](https://en.wikipedia.org/wiki/Imperial_units) ... --- +-- - **Imperial** system: Applies the [Imperial system](https://en.wikipedia.org/wiki/Imperial_units) ... +-- -- ### 3.3.2) Measurements setting **menu** --- +-- -- The settings can be changed by using the **Default settings menu** on the Command Center or the **Player settings menu** on the Player Slot. --- +-- -- ### 3.3.3) Measurements setting **methods** --- +-- -- There are different methods that can be used to change the **Default settings** using the \_SETTINGS object. --- +-- -- - @{#SETTINGS.SetMetric}(): Enable the Metric system. -- - @{#SETTINGS.SetImperial}(): Enable the Imperial system. --- +-- -- ## 3.4) **Message** display times --- +-- -- ### 3.4.1) Message setting **types** --- +-- -- There are various **Message Types** that will influence the duration how long a message will appear as part of the Command Center communications. --- +-- -- - **Update** message: A short update message. -- - **Information** message: Provides new information **while** executing a mission. -- - **Briefing** message: Provides a complete briefing **before** executing a mission. -- - **Overview report**: Provides a short report overview, the summary of the report. -- - **Detailed report**: Provides a complete report. --- +-- -- ### 3.4.2) Message setting **menu** --- +-- -- The settings can be changed by using the **Default settings menu** on the Command Center or the **Player settings menu** on the Player Slot. --- +-- -- Each Message Type has specific timings that will be applied when the message is displayed. -- The Settings Menu will provide for each Message Type a selection of proposed durations from which can be choosen. -- So the player can choose its own amount of seconds how long a message should be displayed of a certain type. -- Note that **Update** messages can be chosen not to be displayed at all! --- +-- -- ### 3.4.3) Message setting **methods** --- +-- -- There are different methods that can be used to change the **System settings** using the \_SETTINGS object. --- --- - @{#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.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. +-- +-- ## 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 SETTINGS = { ClassName = "SETTINGS", @@ -204,6 +217,19 @@ SETTINGS = { 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 @@ -211,7 +237,7 @@ do -- SETTINGS --- SETTINGS constructor. -- @param #SETTINGS self -- @return #SETTINGS - function SETTINGS:Set( PlayerName ) + function SETTINGS:Set( PlayerName ) if PlayerName == nil then local self = BASE:Inherit( self, BASE:New() ) -- #SETTINGS @@ -225,6 +251,7 @@ do -- SETTINGS self:SetMessageTime( MESSAGE.Type.Information, 30 ) self:SetMessageTime( MESSAGE.Type.Overview, 60 ) self:SetMessageTime( MESSAGE.Type.Update, 15 ) + self:SetEraModern() self:SetMenutextShort(true) self:SetMenuStatic(true) return self @@ -245,20 +272,20 @@ do -- SETTINGS function SETTINGS:SetMenutextShort(onoff) self.MenuShort = onoff end - + --- Set menu to be static. -- @param #SETTINGS self -- @param #boolean onoff If *true* menu is static. If *false* menu will be updated after changes (default). function SETTINGS:SetMenuStatic(onoff) self.MenuStatic = onoff - end - + end + --- Sets the SETTINGS metric. -- @param #SETTINGS self function SETTINGS:SetMetric() self.Metric = true end - + --- Gets if the SETTINGS is metric. -- @param #SETTINGS self -- @return #boolean true if metric. @@ -271,7 +298,7 @@ do -- SETTINGS function SETTINGS:SetImperial() self.Metric = false end - + --- Gets if the SETTINGS is imperial. -- @param #SETTINGS self -- @return #boolean true if imperial. @@ -308,7 +335,7 @@ do -- SETTINGS function SETTINGS:GetMGRS_Accuracy() return self.MGRS_Accuracy or _SETTINGS:GetMGRS_Accuracy() end - + --- Sets the SETTINGS Message Display Timing of a MessageType -- @param #SETTINGS self -- @param Core.Message#MESSAGE MessageType The type of the message. @@ -317,8 +344,8 @@ do -- SETTINGS self.MessageTypeTimings = self.MessageTypeTimings or {} self.MessageTypeTimings[MessageType] = MessageTime end - - + + --- Gets the SETTINGS Message Display Timing of a MessageType -- @param #SETTINGS self -- @param Core.Message#MESSAGE MessageType The type of the message. @@ -460,9 +487,9 @@ do -- SETTINGS function SETTINGS:SetSystemMenu( MenuGroup, RootMenu ) local MenuText = "System Settings" - + local MenuTime = timer.getTime() - + local SettingsMenu = MENU_GROUP:New( MenuGroup, MenuText, RootMenu ):SetTime( MenuTime ) ------- @@ -472,9 +499,9 @@ do -- SETTINGS local text="A2G Coordinate System XXX" if self.MenuShort then text="A2G Coordinates" - end + end local A2GCoordinateMenu = MENU_GROUP:New( MenuGroup, text, SettingsMenu ):SetTime( MenuTime ) - + -- Set LL DMS if not self:IsA2G_LL_DMS() then local text="Lat/Lon Degree Min Sec (LL DMS)" @@ -483,16 +510,16 @@ do -- SETTINGS end MENU_GROUP_COMMAND:New( MenuGroup, text, A2GCoordinateMenu, self.A2GMenuSystem, self, MenuGroup, RootMenu, "LL DMS" ):SetTime( MenuTime ) end - + -- Set LL DDM if not self:IsA2G_LL_DDM() then local text="Lat/Lon Degree Dec Min (LL DDM)" if self.MenuShort then text="LL DDM" - end + end MENU_GROUP_COMMAND:New( MenuGroup, "Lat/Lon Degree Dec Min (LL DDM)", A2GCoordinateMenu, self.A2GMenuSystem, self, MenuGroup, RootMenu, "LL DDM" ):SetTime( MenuTime ) end - + -- Set LL DMS accuracy. if self:IsA2G_LL_DDM() then local text1="LL DDM Accuracy 1" @@ -500,30 +527,30 @@ do -- SETTINGS local text3="LL DDM Accuracy 3" if self.MenuShort then text1="LL DDM" - end + end MENU_GROUP_COMMAND:New( MenuGroup, "LL DDM Accuracy 1", A2GCoordinateMenu, self.MenuLL_DDM_Accuracy, self, MenuGroup, RootMenu, 1 ):SetTime( MenuTime ) MENU_GROUP_COMMAND:New( MenuGroup, "LL DDM Accuracy 2", A2GCoordinateMenu, self.MenuLL_DDM_Accuracy, self, MenuGroup, RootMenu, 2 ):SetTime( MenuTime ) MENU_GROUP_COMMAND:New( MenuGroup, "LL DDM Accuracy 3", A2GCoordinateMenu, self.MenuLL_DDM_Accuracy, self, MenuGroup, RootMenu, 3 ):SetTime( MenuTime ) end - + -- Set BR. if not self:IsA2G_BR() then local text="Bearing, Range (BR)" if self.MenuShort then text="BR" - end + end MENU_GROUP_COMMAND:New( MenuGroup, text , A2GCoordinateMenu, self.A2GMenuSystem, self, MenuGroup, RootMenu, "BR" ):SetTime( MenuTime ) end - + -- Set MGRS. if not self:IsA2G_MGRS() then local text="Military Grid (MGRS)" if self.MenuShort then text="MGRS" - end + end MENU_GROUP_COMMAND:New( MenuGroup, text, A2GCoordinateMenu, self.A2GMenuSystem, self, MenuGroup, RootMenu, "MGRS" ):SetTime( MenuTime ) end - + -- Set MGRS accuracy. if self:IsA2G_MGRS() then MENU_GROUP_COMMAND:New( MenuGroup, "MGRS Accuracy 1", A2GCoordinateMenu, self.MenuMGRS_Accuracy, self, MenuGroup, RootMenu, 1 ):SetTime( MenuTime ) @@ -532,22 +559,22 @@ do -- SETTINGS MENU_GROUP_COMMAND:New( MenuGroup, "MGRS Accuracy 4", A2GCoordinateMenu, self.MenuMGRS_Accuracy, self, MenuGroup, RootMenu, 4 ):SetTime( MenuTime ) MENU_GROUP_COMMAND:New( MenuGroup, "MGRS Accuracy 5", A2GCoordinateMenu, self.MenuMGRS_Accuracy, self, MenuGroup, RootMenu, 5 ):SetTime( MenuTime ) end - + ------- -- A2A Coordinate System ------- - + local text="A2A Coordinate System" if self.MenuShort then text="A2A Coordinates" - end + end local A2ACoordinateMenu = MENU_GROUP:New( MenuGroup, text, SettingsMenu ):SetTime( MenuTime ) if not self:IsA2A_LL_DMS() then local text="Lat/Lon Degree Min Sec (LL DMS)" if self.MenuShort then text="LL DMS" - end + end MENU_GROUP_COMMAND:New( MenuGroup, text, A2ACoordinateMenu, self.A2AMenuSystem, self, MenuGroup, RootMenu, "LL DMS" ):SetTime( MenuTime ) end @@ -555,7 +582,7 @@ do -- SETTINGS local text="Lat/Lon Degree Dec Min (LL DDM)" if self.MenuShort then text="LL DDM" - end + end MENU_GROUP_COMMAND:New( MenuGroup, text, A2ACoordinateMenu, self.A2AMenuSystem, self, MenuGroup, RootMenu, "LL DDM" ):SetTime( MenuTime ) end @@ -563,7 +590,7 @@ do -- SETTINGS MENU_GROUP_COMMAND:New( MenuGroup, "LL DDM Accuracy 1", A2ACoordinateMenu, self.MenuLL_DDM_Accuracy, self, MenuGroup, RootMenu, 1 ):SetTime( MenuTime ) MENU_GROUP_COMMAND:New( MenuGroup, "LL DDM Accuracy 2", A2ACoordinateMenu, self.MenuLL_DDM_Accuracy, self, MenuGroup, RootMenu, 2 ):SetTime( MenuTime ) MENU_GROUP_COMMAND:New( MenuGroup, "LL DDM Accuracy 3", A2ACoordinateMenu, self.MenuLL_DDM_Accuracy, self, MenuGroup, RootMenu, 3 ):SetTime( MenuTime ) - end + end if not self:IsA2A_BULLS() then local text="Bullseye (BULLS)" @@ -572,20 +599,20 @@ do -- SETTINGS end MENU_GROUP_COMMAND:New( MenuGroup, text, A2ACoordinateMenu, self.A2AMenuSystem, self, MenuGroup, RootMenu, "BULLS" ):SetTime( MenuTime ) end - + if not self:IsA2A_BRAA() then local text="Bearing Range Altitude Aspect (BRAA)" if self.MenuShort then text="BRAA" - end + end MENU_GROUP_COMMAND:New( MenuGroup, text, A2ACoordinateMenu, self.A2AMenuSystem, self, MenuGroup, RootMenu, "BRAA" ):SetTime( MenuTime ) end - + if not self:IsA2A_MGRS() then local text="Military Grid (MGRS)" if self.MenuShort then text="MGRS" - end + end MENU_GROUP_COMMAND:New( MenuGroup, text, A2ACoordinateMenu, self.A2AMenuSystem, self, MenuGroup, RootMenu, "MGRS" ):SetTime( MenuTime ) end @@ -596,33 +623,33 @@ do -- SETTINGS MENU_GROUP_COMMAND:New( MenuGroup, "MGRS Accuracy 4", A2ACoordinateMenu, self.MenuMGRS_Accuracy, self, MenuGroup, RootMenu, 4 ):SetTime( MenuTime ) MENU_GROUP_COMMAND:New( MenuGroup, "MGRS Accuracy 5", A2ACoordinateMenu, self.MenuMGRS_Accuracy, self, MenuGroup, RootMenu, 5 ):SetTime( MenuTime ) end - + local text="Measures and Weights System" if self.MenuShort then text="Unit System" end local MetricsMenu = MENU_GROUP:New( MenuGroup, text, SettingsMenu ):SetTime( MenuTime ) - + if self:IsMetric() then local text="Imperial (Miles,Feet)" if self.MenuShort then text="Imperial" - end + end MENU_GROUP_COMMAND:New( MenuGroup, text, MetricsMenu, self.MenuMWSystem, self, MenuGroup, RootMenu, false ):SetTime( MenuTime ) end - + if self:IsImperial() then local text="Metric (Kilometers,Meters)" if self.MenuShort then text="Metric" - end + end MENU_GROUP_COMMAND:New( MenuGroup, text, MetricsMenu, self.MenuMWSystem, self, MenuGroup, RootMenu, true ):SetTime( MenuTime ) - end + end local text="Messages and Reports" if self.MenuShort then text="Messages & Reports" - end + end local MessagesMenu = MENU_GROUP:New( MenuGroup, text, SettingsMenu ):SetTime( MenuTime ) local UpdateMessagesMenu = MENU_GROUP:New( MenuGroup, "Update Messages", MessagesMenu ):SetTime( MenuTime ) @@ -661,10 +688,10 @@ do -- SETTINGS MENU_GROUP_COMMAND:New( MenuGroup, "1 minute", DetailedReportsMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.DetailedReportsMenu, 60 ):SetTime( MenuTime ) MENU_GROUP_COMMAND:New( MenuGroup, "2 minutes", DetailedReportsMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.DetailedReportsMenu, 120 ):SetTime( MenuTime ) MENU_GROUP_COMMAND:New( MenuGroup, "3 minutes", DetailedReportsMenu, self.MenuMessageTimingsSystem, self, MenuGroup, RootMenu, MESSAGE.Type.DetailedReportsMenu, 180 ):SetTime( MenuTime ) - + SettingsMenu:Remove( MenuTime ) - + return self end @@ -697,24 +724,24 @@ do -- SETTINGS function SETTINGS:SetPlayerMenu( PlayerUnit ) if _SETTINGS.ShowPlayerMenu == true then - + local PlayerGroup = PlayerUnit:GetGroup() local PlayerName = PlayerUnit:GetPlayerName() local PlayerNames = PlayerGroup:GetPlayerNames() - + local PlayerMenu = MENU_GROUP:New( PlayerGroup, 'Settings "' .. PlayerName .. '"' ) - + self.PlayerMenu = PlayerMenu - + self:I(string.format("FF Setting menu for player %s", tostring(PlayerName))) - + local submenu = MENU_GROUP:New( PlayerGroup, "LL Accuracy", PlayerMenu ) MENU_GROUP_COMMAND:New( PlayerGroup, "0 Decimal Places", submenu, self.MenuGroupLL_DDM_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 0 ) MENU_GROUP_COMMAND:New( PlayerGroup, "1 Decimal Places", submenu, self.MenuGroupLL_DDM_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 1 ) MENU_GROUP_COMMAND:New( PlayerGroup, "2 Decimal Places", submenu, self.MenuGroupLL_DDM_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 2 ) MENU_GROUP_COMMAND:New( PlayerGroup, "3 Decimal Places", submenu, self.MenuGroupLL_DDM_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 3 ) - MENU_GROUP_COMMAND:New( PlayerGroup, "4 Decimal Places", submenu, self.MenuGroupLL_DDM_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 4 ) - + MENU_GROUP_COMMAND:New( PlayerGroup, "4 Decimal Places", submenu, self.MenuGroupLL_DDM_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 4 ) + local submenu = MENU_GROUP:New( PlayerGroup, "MGRS Accuracy", PlayerMenu ) MENU_GROUP_COMMAND:New( PlayerGroup, "Accuracy 0", submenu, self.MenuGroupMGRS_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 0 ) MENU_GROUP_COMMAND:New( PlayerGroup, "Accuracy 1", submenu, self.MenuGroupMGRS_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 1 ) @@ -722,7 +749,7 @@ do -- SETTINGS MENU_GROUP_COMMAND:New( PlayerGroup, "Accuracy 3", submenu, self.MenuGroupMGRS_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 3 ) MENU_GROUP_COMMAND:New( PlayerGroup, "Accuracy 4", submenu, self.MenuGroupMGRS_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 4 ) MENU_GROUP_COMMAND:New( PlayerGroup, "Accuracy 5", submenu, self.MenuGroupMGRS_AccuracySystem, self, PlayerUnit, PlayerGroup, PlayerName, 5 ) - + ------ -- A2G Coordinate System ------ @@ -730,118 +757,118 @@ do -- SETTINGS local text="A2G Coordinate System" if self.MenuShort then text="A2G Coordinates" - end + end local A2GCoordinateMenu = MENU_GROUP:New( PlayerGroup, text, PlayerMenu ) - + if not self:IsA2G_LL_DMS() or self.MenuStatic then local text="Lat/Lon Degree Min Sec (LL DMS)" if self.MenuShort then text="LL DMS" - end + end MENU_GROUP_COMMAND:New( PlayerGroup, text, A2GCoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "LL DMS" ) end - + if not self:IsA2G_LL_DDM() or self.MenuStatic then local text="Lat/Lon Degree Dec Min (LL DDM)" if self.MenuShort then text="LL DDM" - end + end MENU_GROUP_COMMAND:New( PlayerGroup, text, A2GCoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "LL DDM" ) end - + if not self:IsA2G_BR() or self.MenuStatic then local text="Bearing, Range (BR)" if self.MenuShort then text="BR" - end + end MENU_GROUP_COMMAND:New( PlayerGroup, text, A2GCoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "BR" ) end - + if not self:IsA2G_MGRS() or self.MenuStatic then local text="Military Grid (MGRS)" if self.MenuShort then text="MGRS" - end + end MENU_GROUP_COMMAND:New( PlayerGroup, text, A2GCoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "MGRS" ) - end - + end + ------ -- A2A Coordinates Menu ------ - + local text="A2A Coordinate System" if self.MenuShort then text="A2A Coordinates" - end + end local A2ACoordinateMenu = MENU_GROUP:New( PlayerGroup, text, PlayerMenu ) - - + + if not self:IsA2A_LL_DMS() or self.MenuStatic then local text="Lat/Lon Degree Min Sec (LL DMS)" if self.MenuShort then text="LL DMS" - end + end MENU_GROUP_COMMAND:New( PlayerGroup, text, A2ACoordinateMenu, self.MenuGroupA2GSystem, self, PlayerUnit, PlayerGroup, PlayerName, "LL DMS" ) end - + if not self:IsA2A_LL_DDM() or self.MenuStatic then local text="Lat/Lon Degree Dec Min (LL DDM)" if self.MenuShort then text="LL DDM" - end + end MENU_GROUP_COMMAND:New( PlayerGroup, text, A2ACoordinateMenu, self.MenuGroupA2ASystem, self, PlayerUnit, PlayerGroup, PlayerName, "LL DDM" ) end - + if not self:IsA2A_BULLS() or self.MenuStatic then local text="Bullseye (BULLS)" if self.MenuShort then text="BULLS" - end + end MENU_GROUP_COMMAND:New( PlayerGroup, text, A2ACoordinateMenu, self.MenuGroupA2ASystem, self, PlayerUnit, PlayerGroup, PlayerName, "BULLS" ) end - + if not self:IsA2A_BRAA() or self.MenuStatic then local text="Bearing Range Altitude Aspect (BRAA)" if self.MenuShort then text="BRAA" - end + end MENU_GROUP_COMMAND:New( PlayerGroup, text, A2ACoordinateMenu, self.MenuGroupA2ASystem, self, PlayerUnit, PlayerGroup, PlayerName, "BRAA" ) end - + if not self:IsA2A_MGRS() or self.MenuStatic then local text="Military Grid (MGRS)" if self.MenuShort then text="MGRS" - end + end MENU_GROUP_COMMAND:New( PlayerGroup, text, A2ACoordinateMenu, self.MenuGroupA2ASystem, self, PlayerUnit, PlayerGroup, PlayerName, "MGRS" ) end - + --- -- Unit system - --- - + --- + local text="Measures and Weights System" if self.MenuShort then text="Unit System" - end + end local MetricsMenu = MENU_GROUP:New( PlayerGroup, text, PlayerMenu ) - + if self:IsMetric() or self.MenuStatic then local text="Imperial (Miles,Feet)" if self.MenuShort then text="Imperial" - end + end MENU_GROUP_COMMAND:New( PlayerGroup, text, MetricsMenu, self.MenuGroupMWSystem, self, PlayerUnit, PlayerGroup, PlayerName, false ) end - + if self:IsImperial() or self.MenuStatic then local text="Metric (Kilometers,Meters)" if self.MenuShort then text="Metric" - end + end MENU_GROUP_COMMAND:New( PlayerGroup, text, MetricsMenu, self.MenuGroupMWSystem, self, PlayerUnit, PlayerGroup, PlayerName, true ) - end - + end + --- -- Messages and Reports --- @@ -849,9 +876,9 @@ do -- SETTINGS local text="Messages and Reports" if self.MenuShort then text="Messages & Reports" - end + end local MessagesMenu = MENU_GROUP:New( PlayerGroup, text, PlayerMenu ) - + local UpdateMessagesMenu = MENU_GROUP:New( PlayerGroup, "Update Messages", MessagesMenu ) MENU_GROUP_COMMAND:New( PlayerGroup, "Off", UpdateMessagesMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Update, 0 ) MENU_GROUP_COMMAND:New( PlayerGroup, "5 seconds", UpdateMessagesMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Update, 5 ) @@ -859,7 +886,7 @@ do -- SETTINGS MENU_GROUP_COMMAND:New( PlayerGroup, "15 seconds", UpdateMessagesMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Update, 15 ) MENU_GROUP_COMMAND:New( PlayerGroup, "30 seconds", UpdateMessagesMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Update, 30 ) MENU_GROUP_COMMAND:New( PlayerGroup, "1 minute", UpdateMessagesMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Update, 60 ) - + local InformationMessagesMenu = MENU_GROUP:New( PlayerGroup, "Information Messages", MessagesMenu ) MENU_GROUP_COMMAND:New( PlayerGroup, "5 seconds", InformationMessagesMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Information, 5 ) MENU_GROUP_COMMAND:New( PlayerGroup, "10 seconds", InformationMessagesMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Information, 10 ) @@ -867,30 +894,30 @@ do -- SETTINGS MENU_GROUP_COMMAND:New( PlayerGroup, "30 seconds", InformationMessagesMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Information, 30 ) MENU_GROUP_COMMAND:New( PlayerGroup, "1 minute", InformationMessagesMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Information, 60 ) MENU_GROUP_COMMAND:New( PlayerGroup, "2 minutes", InformationMessagesMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Information, 120 ) - + local BriefingReportsMenu = MENU_GROUP:New( PlayerGroup, "Briefing Reports", MessagesMenu ) MENU_GROUP_COMMAND:New( PlayerGroup, "15 seconds", BriefingReportsMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Briefing, 15 ) MENU_GROUP_COMMAND:New( PlayerGroup, "30 seconds", BriefingReportsMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Briefing, 30 ) MENU_GROUP_COMMAND:New( PlayerGroup, "1 minute", BriefingReportsMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Briefing, 60 ) MENU_GROUP_COMMAND:New( PlayerGroup, "2 minutes", BriefingReportsMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Briefing, 120 ) MENU_GROUP_COMMAND:New( PlayerGroup, "3 minutes", BriefingReportsMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Briefing, 180 ) - + local OverviewReportsMenu = MENU_GROUP:New( PlayerGroup, "Overview Reports", MessagesMenu ) MENU_GROUP_COMMAND:New( PlayerGroup, "15 seconds", OverviewReportsMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Overview, 15 ) MENU_GROUP_COMMAND:New( PlayerGroup, "30 seconds", OverviewReportsMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Overview, 30 ) MENU_GROUP_COMMAND:New( PlayerGroup, "1 minute", OverviewReportsMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Overview, 60 ) MENU_GROUP_COMMAND:New( PlayerGroup, "2 minutes", OverviewReportsMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Overview, 120 ) MENU_GROUP_COMMAND:New( PlayerGroup, "3 minutes", OverviewReportsMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.Overview, 180 ) - + local DetailedReportsMenu = MENU_GROUP:New( PlayerGroup, "Detailed Reports", MessagesMenu ) MENU_GROUP_COMMAND:New( PlayerGroup, "15 seconds", DetailedReportsMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.DetailedReportsMenu, 15 ) MENU_GROUP_COMMAND:New( PlayerGroup, "30 seconds", DetailedReportsMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.DetailedReportsMenu, 30 ) MENU_GROUP_COMMAND:New( PlayerGroup, "1 minute", DetailedReportsMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.DetailedReportsMenu, 60 ) MENU_GROUP_COMMAND:New( PlayerGroup, "2 minutes", DetailedReportsMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.DetailedReportsMenu, 120 ) MENU_GROUP_COMMAND:New( PlayerGroup, "3 minutes", DetailedReportsMenu, self.MenuGroupMessageTimingsSystem, self, PlayerUnit, PlayerGroup, PlayerName, MESSAGE.Type.DetailedReportsMenu, 180 ) - - end - + + end + return self end @@ -904,7 +931,7 @@ do -- SETTINGS self.PlayerMenu:Remove() self.PlayerMenu = nil end - + return self end @@ -961,7 +988,7 @@ do -- SETTINGS self:SetPlayerMenu(PlayerUnit) end end - + --- @param #SETTINGS self function SETTINGS:MenuGroupA2ASystem( PlayerUnit, PlayerGroup, PlayerName, A2ASystem ) self.A2ASystem = A2ASystem @@ -971,7 +998,7 @@ do -- SETTINGS self:SetPlayerMenu(PlayerUnit) end end - + --- @param #SETTINGS self function SETTINGS:MenuGroupLL_DDM_AccuracySystem( PlayerUnit, PlayerGroup, PlayerName, LL_Accuracy ) self.LL_Accuracy = LL_Accuracy @@ -981,7 +1008,7 @@ do -- SETTINGS self:SetPlayerMenu(PlayerUnit) end end - + --- @param #SETTINGS self function SETTINGS:MenuGroupMGRS_AccuracySystem( PlayerUnit, PlayerGroup, PlayerName, MGRS_Accuracy ) self.MGRS_Accuracy = MGRS_Accuracy @@ -1007,9 +1034,48 @@ do -- SETTINGS self:SetMessageTime( MessageType, MessageTime ) MESSAGE:New( string.format( "Settings: Default message time set for %s to %d.", MessageType, MessageTime ), 5 ):ToGroup( PlayerGroup ) 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 - - diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index 52c27fd05..9dedcb35a 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -120,7 +120,7 @@ ZONE_BASE = { -- @param #string ZoneName Name of the zone. -- @return #ZONE_BASE self function ZONE_BASE:New( ZoneName ) - local self = BASE:Inherit( self, BASE:New() ) + local self = BASE:Inherit( self, FSM:New() ) self:F( ZoneName ) self.ZoneName = ZoneName @@ -1393,7 +1393,7 @@ end function ZONE_POLYGON_BASE:Flush() self:F2() - self:E( { Polygon = self.ZoneName, Coordinates = self._.Polygon } ) + self:F( { Polygon = self.ZoneName, Coordinates = self._.Polygon } ) return self end diff --git a/Moose Development/Moose/Functional/Detection.lua b/Moose Development/Moose/Functional/Detection.lua index ac3f2923e..2c9c5d792 100644 --- a/Moose Development/Moose/Functional/Detection.lua +++ b/Moose Development/Moose/Functional/Detection.lua @@ -1682,9 +1682,7 @@ do -- DETECTION_BASE -- @return #DETECTION_BASE.DetectedItem function DETECTION_BASE:GetDetectedItemByIndex( Index ) - self:I( { DetectedItemsByIndex = self.DetectedItemsByIndex } ) - - self:I( { self.DetectedItemsByIndex } ) + self:F( { self.DetectedItemsByIndex } ) local DetectedItem = self.DetectedItemsByIndex[Index] if DetectedItem then diff --git a/Moose Development/Moose/Functional/ZoneCaptureCoalition.lua b/Moose Development/Moose/Functional/ZoneCaptureCoalition.lua index 32a860aa8..4ee91c93f 100644 --- a/Moose Development/Moose/Functional/ZoneCaptureCoalition.lua +++ b/Moose Development/Moose/Functional/ZoneCaptureCoalition.lua @@ -549,6 +549,9 @@ do -- ZONE_CAPTURE_COALITION -- If it is, then we must move the zone to attack state. 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 end @@ -566,7 +569,7 @@ do -- ZONE_CAPTURE_COALITION function ZONE_CAPTURE_COALITION:IsGuarded() - local IsGuarded = self.Zone:IsAllInZoneOfCoalition( self.Coalition ) + local IsGuarded = self:IsAllInZoneOfCoalition( self.Coalition ) self:F( { IsGuarded = IsGuarded } ) return IsGuarded end @@ -574,7 +577,7 @@ do -- ZONE_CAPTURE_COALITION function ZONE_CAPTURE_COALITION:IsEmpty() - local IsEmpty = self.Zone:IsNoneInZone() + local IsEmpty = self:IsNoneInZone() self:F( { IsEmpty = IsEmpty } ) return IsEmpty end @@ -582,7 +585,7 @@ do -- ZONE_CAPTURE_COALITION function ZONE_CAPTURE_COALITION:IsCaptured() - local IsCaptured = self.Zone:IsAllInZoneOfOtherCoalition( self.Coalition ) + local IsCaptured = self:IsAllInZoneOfOtherCoalition( self.Coalition ) self:F( { IsCaptured = IsCaptured } ) return IsCaptured end @@ -590,7 +593,7 @@ do -- ZONE_CAPTURE_COALITION function ZONE_CAPTURE_COALITION:IsAttacked() - local IsAttacked = self.Zone:IsSomeInZoneOfCoalition( self.Coalition ) + local IsAttacked = self:IsSomeInZoneOfCoalition( self.Coalition ) self:F( { IsAttacked = IsAttacked } ) return IsAttacked end @@ -601,7 +604,7 @@ do -- ZONE_CAPTURE_COALITION -- @param #ZONE_CAPTURE_COALITION self function ZONE_CAPTURE_COALITION:Mark() - local Coord = self.Zone:GetCoordinate() + local Coord = self:GetCoordinate() local ZoneName = self:GetZoneName() local State = self:GetState() @@ -640,7 +643,7 @@ do -- ZONE_CAPTURE_COALITION --self:GetParent( self ):onenterCaptured() - local NewCoalition = self.Zone:GetScannedCoalition() + local NewCoalition = self:GetScannedCoalition() self:F( { NewCoalition = NewCoalition } ) self:SetCoalition( NewCoalition ) @@ -673,14 +676,14 @@ do -- ZONE_CAPTURE_COALITION --BASE:GetParent( self ).onafterGuard( self ) 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 function ZONE_CAPTURE_COALITION:IsCaptured() - local IsCaptured = self.Zone:IsAllInZoneOfOtherCoalition( self.Coalition ) + local IsCaptured = self:IsAllInZoneOfOtherCoalition( self.Coalition ) self:F( { IsCaptured = IsCaptured } ) return IsCaptured end @@ -688,7 +691,7 @@ do -- ZONE_CAPTURE_COALITION function ZONE_CAPTURE_COALITION:IsAttacked() - local IsAttacked = self.Zone:IsSomeInZoneOfCoalition( self.Coalition ) + local IsAttacked = self:IsSomeInZoneOfCoalition( self.Coalition ) self:F( { IsAttacked = IsAttacked } ) return IsAttacked end @@ -744,13 +747,13 @@ do -- ZONE_CAPTURE_COALITION -- function ZONE_CAPTURE_COALITION:Start( StartInterval, RepeatInterval ) - StartInterval = StartInterval or 15 - RepeatInterval = RepeatInterval or 15 + self.StartInterval = StartInterval or 15 + self.RepeatInterval = RepeatInterval or 15 if self.ScheduleStatusZone then self:ScheduleStop( self.ScheduleStatusZone ) 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 @@ -803,7 +806,7 @@ do -- ZONE_CAPTURE_COALITION local UnitHit = EventData.TgtUnit if UnitHit then - if UnitHit:IsInZone( self.Zone ) then + if UnitHit:IsInZone( self ) then self:Attack() end end diff --git a/Moose Development/Moose/Functional/ZoneGoal.lua b/Moose Development/Moose/Functional/ZoneGoal.lua index 0caabc8a3..67f6a872b 100644 --- a/Moose Development/Moose/Functional/ZoneGoal.lua +++ b/Moose Development/Moose/Functional/ZoneGoal.lua @@ -44,14 +44,13 @@ do -- Zone --- ZONE_GOAL Constructor. -- @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 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.Zone = Zone -- Core.Zone#ZONE_BASE self.Goal = GOAL:New() self.SmokeTime = nil @@ -67,6 +66,7 @@ do -- Zone -- @param Wrapper.Unit#UNIT DestroyedUnit The destroyed unit. -- @param #string PlayerName The name of the player. + return self end @@ -74,7 +74,7 @@ do -- Zone -- @param #ZONE_GOAL self -- @return Core.Zone#ZONE_BASE function ZONE_GOAL:GetZone() - return self.Zone + return self end @@ -82,7 +82,7 @@ do -- Zone -- @param #ZONE_GOAL self -- @return #string function ZONE_GOAL:GetZoneName() - return self.Zone:GetName() + return self:GetName() end @@ -101,7 +101,7 @@ do -- Zone -- @param #ZONE_GOAL self -- @param #SMOKECOLOR.Color FlareColor function ZONE_GOAL:Flare( FlareColor ) - self.Zone:FlareZone( FlareColor, math.random( 1, 360 ) ) + self:FlareZone( FlareColor, math.random( 1, 360 ) ) end @@ -130,7 +130,7 @@ do -- Zone if self.SmokeTime == nil or self.SmokeTime + 300 <= CurrentTime then if self.SmokeColor then - self.Zone:GetCoordinate():Smoke( self.SmokeColor ) + self:GetCoordinate():Smoke( self.SmokeColor ) --self.SmokeColor = nil self.SmokeTime = CurrentTime end @@ -147,11 +147,9 @@ do -- Zone local Vec3 = EventData.IniDCSUnit:getPosition().p self:F( { Vec3 = Vec3 } ) - local ZoneGoal = self:GetZone() - self:F({ZoneGoal}) if EventData.IniDCSUnit then - if ZoneGoal:IsVec3InZone(Vec3) then + if self:IsVec3InZone(Vec3) then local PlayerHits = _DATABASE.HITS[EventData.IniUnitName] if PlayerHits then for PlayerName, PlayerHit in pairs( PlayerHits.Players or {} ) do diff --git a/Moose Development/Moose/Functional/ZoneGoalCoalition.lua b/Moose Development/Moose/Functional/ZoneGoalCoalition.lua index ba86ffe95..1de5218f3 100644 --- a/Moose Development/Moose/Functional/ZoneGoalCoalition.lua +++ b/Moose Development/Moose/Functional/ZoneGoalCoalition.lua @@ -104,7 +104,7 @@ do -- ZoneGoal local 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 diff --git a/Moose Development/Moose/Modules.lua b/Moose Development/Moose/Modules.lua index 2392b9be1..6b2fba0f4 100644 --- a/Moose Development/Moose/Modules.lua +++ b/Moose Development/Moose/Modules.lua @@ -20,6 +20,7 @@ __Moose.Include( 'Scripts/Moose/Core/Velocity.lua' ) __Moose.Include( 'Scripts/Moose/Core/Message.lua' ) __Moose.Include( 'Scripts/Moose/Core/Fsm.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/SpawnStatic.lua' ) __Moose.Include( 'Scripts/Moose/Core/Goal.lua' ) diff --git a/Moose Development/Moose/Tasking/CommandCenter.lua b/Moose Development/Moose/Tasking/CommandCenter.lua index dcef1bb72..0673facb1 100644 --- a/Moose Development/Moose/Tasking/CommandCenter.lua +++ b/Moose Development/Moose/Tasking/CommandCenter.lua @@ -180,7 +180,7 @@ COMMANDCENTER = { COMMANDCENTER.AutoAssignMethods = { ["Random"] = 1, ["Distance"] = 2, - ["Priority"] = 3 + ["Priority"] = 3, } --- The constructor takes an IDENTIFIABLE as the HQ command center. @@ -200,7 +200,8 @@ function COMMANDCENTER:New( CommandCenterPositionable, CommandCenterName ) self:SetAutoAssignTasks( false ) self:SetAutoAcceptTasks( true ) - self:SetAutoAssignMethod( COMMANDCENTER.AutoAssignMethods.Random ) + self:SetAutoAssignMethod( COMMANDCENTER.AutoAssignMethods.Distance ) + self:SetFlashStatus( false ) self:HandleEvent( EVENTS.Birth, --- @param #COMMANDCENTER self @@ -208,8 +209,8 @@ function COMMANDCENTER:New( CommandCenterPositionable, CommandCenterName ) function( self, EventData ) if EventData.IniObjectCategory == 1 then local EventGroup = GROUP:Find( EventData.IniDCSGroup ) - self:E( { CommandCenter = self:GetName(), EventGroup = EventGroup:GetName(), HasGroup = self:HasGroup( EventGroup ), EventData = EventData } ) - if EventGroup and self:HasGroup( EventGroup ) then + --self:E( { CommandCenter = self:GetName(), EventGroup = EventGroup:GetName(), HasGroup = self:HasGroup( EventGroup ), EventData = EventData } ) + if EventGroup and EventGroup:IsAlive() and self:HasGroup( EventGroup ) then local CommandCenterMenu = MENU_GROUP:New( EventGroup, self:GetText() ) local MenuReporting = MENU_GROUP:New( EventGroup, "Missions Reports", CommandCenterMenu ) 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 ) 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 diff --git a/Moose Development/Moose/Tasking/DetectionManager.lua b/Moose Development/Moose/Tasking/DetectionManager.lua index 9f827e3d4..8bec92065 100644 --- a/Moose Development/Moose/Tasking/DetectionManager.lua +++ b/Moose Development/Moose/Tasking/DetectionManager.lua @@ -254,11 +254,45 @@ do -- DETECTION MANAGER 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. -- @param #DETECTION_MANAGER self -- @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 - function DETECTION_MANAGER:MessageToPlayers( Message ) + function DETECTION_MANAGER:MessageToPlayers( Message, SoundFile, SoundDuration, SoundPath, DefenderGroup ) self:F( { Message = Message } ) @@ -269,6 +303,17 @@ do -- DETECTION MANAGER 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 end @@ -279,7 +324,6 @@ do -- DETECTION MANAGER -- @param Functional.Detection#DETECTION_BASE Detection -- @return #DETECTION_MANAGER self function DETECTION_MANAGER:ProcessDetected( Detection ) - self:E() end diff --git a/Moose Development/Moose/Tasking/Task.lua b/Moose Development/Moose/Tasking/Task.lua index 68c8a4803..7efa0962d 100644 --- a/Moose Development/Moose/Tasking/Task.lua +++ b/Moose Development/Moose/Tasking/Task.lua @@ -856,6 +856,8 @@ do -- Group Assignment CommandCenter:SetMenu() + self:MenuFlashTaskStatus( TaskGroup, self:GetMission():GetCommandCenter().FlashStatus ) + return self end @@ -1111,6 +1113,11 @@ function TASK:SetAssignedMenuForGroup( TaskGroup, MenuTime ) 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 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 @@ -1225,12 +1232,34 @@ end --- Report the task status. -- @param #TASK self +-- @param Wrapper.Group#GROUP TaskGroup function TASK:MenuTaskStatus( TaskGroup ) - local ReportText = self:ReportDetails( TaskGroup ) - - self:T( ReportText ) - self:GetMission():GetCommandCenter():MessageTypeToGroup( ReportText, TaskGroup, MESSAGE.Type.Detailed ) + if TaskGroup:IsAlive() then + + local ReportText = self:ReportDetails( TaskGroup ) + + 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 diff --git a/Moose Development/Moose/Tasking/TaskInfo.lua b/Moose Development/Moose/Tasking/TaskInfo.lua index 8f2d6231a..5ece5f005 100644 --- a/Moose Development/Moose/Tasking/TaskInfo.lua +++ b/Moose Development/Moose/Tasking/TaskInfo.lua @@ -52,16 +52,16 @@ end --- Add taskinfo. -- @param #TASKINFO self --- @param #string The info key. +-- @param #string Key The info key. -- @param Data The data of the info. -- @param #number Order The display order, which is a number from 0 to 100. -- @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. -- @return #TASKINFO self -function TASKINFO:AddInfo( Key, Data, Order, Detail, Keep ) - self.VolatileInfo:Add( Key, { Data = Data, Order = Order, Detail = Detail } ) +function TASKINFO:AddInfo( Key, Data, Order, Detail, Keep, ShowKey, Type ) + self.VolatileInfo:Add( Key, { Data = Data, Order = Order, Detail = Detail, ShowKey = ShowKey, Type = Type } ) 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 return self end @@ -124,8 +124,8 @@ end -- @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. -- @return #TASKINFO self -function TASKINFO:AddCoordinate( Coordinate, Order, Detail, Keep ) - self:AddInfo( "Coordinate", Coordinate, Order, Detail, Keep ) +function TASKINFO:AddCoordinate( Coordinate, Order, Detail, Keep, ShowKey, Name ) + self:AddInfo( Name or "Coordinate", Coordinate, Order, Detail, Keep, ShowKey, "Coordinate" ) return self end @@ -133,8 +133,8 @@ end --- Get the Coordinate. -- @param #TASKINFO self -- @return Core.Point#COORDINATE Coordinate -function TASKINFO:GetCoordinate() - return self:GetData( "Coordinate" ) +function TASKINFO:GetCoordinate( Name ) + return self:GetData( Name or "Coordinate" ) end @@ -308,10 +308,11 @@ function TASKINFO:Report( Report, Detail, ReportGroup, Task ) if Data.Detail:find( Detail ) then local Text = "" + local ShowKey = ( Data.ShowKey == nil or Data.ShowKey == true ) if Key == "TaskName" then Key = nil Text = Data.Data - elseif Key == "Coordinate" then + elseif Data.Type and Data.Type == "Coordinate" then local Coordinate = Data.Data -- Core.Point#COORDINATE Text = Coordinate:ToString( ReportGroup:GetUnit(1), nil, Task ) elseif Key == "Threat" then @@ -357,7 +358,7 @@ function TASKINFO:Report( Report, Detail, ReportGroup, Task ) end if Text ~= "" then - LineReport:Add( ( Key and ( Key .. ":" ) or "" ) .. Text ) + LineReport:Add( ( ( Key and ShowKey == true ) and ( Key .. ": " ) or "" ) .. Text ) end end diff --git a/Moose Development/Moose/Tasking/Task_Capture_Dispatcher.lua b/Moose Development/Moose/Tasking/Task_Capture_Dispatcher.lua index 74dff588e..7f1fa7d08 100644 --- a/Moose Development/Moose/Tasking/Task_Capture_Dispatcher.lua +++ b/Moose Development/Moose/Tasking/Task_Capture_Dispatcher.lua @@ -194,6 +194,45 @@ do -- TASK_CAPTURE_DISPATCHER 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. -- @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! Mission:AddTask( CaptureZone.Task ) 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() 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. end CaptureZone.Task:UpdateTaskInfo() + CaptureZone.Task.ZoneGoal.Attacked = true end 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. end CaptureZone.Task:UpdateTaskInfo() + CaptureZone.Task.ZoneGoal.Attacked = false end 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. end CaptureZone.Task:UpdateTaskInfo() + CaptureZone.Task.ZoneGoal.Attacked = false end 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. end CaptureZone.Task:UpdateTaskInfo() + CaptureZone.Task.ZoneGoal.Attacked = false end 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. end CaptureZone.Task:UpdateTaskInfo() + CaptureZone.Task.ZoneGoal.Attacked = false end -- 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. end CaptureZone.Task:UpdateTaskInfo() + CaptureZone.Task.ZoneGoal.Attacked = false end end diff --git a/Moose Development/Moose/Tasking/Task_Capture_Zone.lua b/Moose Development/Moose/Tasking/Task_Capture_Zone.lua index 9ab0f35c7..3ca128b98 100644 --- a/Moose Development/Moose/Tasking/Task_Capture_Zone.lua +++ b/Moose Development/Moose/Tasking/Task_Capture_Zone.lua @@ -228,11 +228,47 @@ do -- TASK_CAPTURE_ZONE local ZoneCoordinate = self.ZoneGoal:GetZone():GetCoordinate() self.TaskInfo:AddTaskName( 0, "MSOD", Persist ) self.TaskInfo:AddCoordinate( ZoneCoordinate, 1, "SOD", Persist ) - self.TaskInfo:AddText( "Zone Name", self.ZoneGoal:GetZoneName(), 10, "MOD", Persist ) - self.TaskInfo:AddText( "Zone Coalition", self.ZoneGoal:GetCoalitionName(), 11, "MOD", Persist ) - local SetUnit = self.ZoneGoal.Zone:GetScannedSetUnit() +-- self.TaskInfo:AddText( "Zone Name", self.ZoneGoal:GetZoneName(), 10, "MOD", Persist ) +-- self.TaskInfo:AddText( "Zone Coalition", self.ZoneGoal:GetCoalitionName(), 11, "MOD", Persist ) + local SetUnit = self.ZoneGoal:GetScannedSetUnit() local ThreatLevel, ThreatText = SetUnit:CalculateThreatLevelA2G() + local ThreatCount = SetUnit:Count() 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 @@ -274,7 +310,7 @@ do -- TASK_CAPTURE_ZONE -- @param #number AutoAssignMethod The method to be applied to the task. -- @param Tasking.CommandCenter#COMMANDCENTER CommandCenter The command center. -- @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 return math.random( 1, 9 ) diff --git a/Moose Development/Moose/Tasking/Task_Manager.lua b/Moose Development/Moose/Tasking/Task_Manager.lua index 8c2d76406..e4f4ee9a7 100644 --- a/Moose Development/Moose/Tasking/Task_Manager.lua +++ b/Moose Development/Moose/Tasking/Task_Manager.lua @@ -185,7 +185,6 @@ do -- TASK_MANAGER -- @param #TASK_MANAGER self -- @return #TASK_MANAGER self function TASK_MANAGER:ManageTasks() - self:E() end diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 064acb53b..8a6b461b3 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -521,6 +521,32 @@ function UTILS.spairs( t, order ) 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. function UTILS.rpairs( t ) -- collect the keys diff --git a/Moose Development/Moose/Wrapper/Client.lua b/Moose Development/Moose/Wrapper/Client.lua index ada653ba3..cef00651d 100644 --- a/Moose Development/Moose/Wrapper/Client.lua +++ b/Moose Development/Moose/Wrapper/Client.lua @@ -133,7 +133,8 @@ function CLIENT:FindByName( ClientName, ClientBriefing, Error ) end 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.ClientName = ClientName @@ -141,7 +142,7 @@ function CLIENT:Register( ClientName ) self.ClientAlive2 = false --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 ) return self diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index 762f7358f..5e6fd66a3 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -307,7 +307,6 @@ end -- @param #CONTROLLABLE self -- @return #CONTROLLABLE function CONTROLLABLE:ClearTasks() - self:E( "ClearTasks" ) local DCSControllable = self:GetDCSObject() @@ -444,7 +443,7 @@ end -- @param #CONTROLLABLE self -- @param DCS#Time time DCS mission time. -- @param #string userFlag Name of the user flag. --- @param #boolean userFlagValue User flag value *true* or *false*. Could also be numeric, i.e. either 0=*false* or 1=*true*. Other numeric values don't work! +-- @param #boolean userFlagValue User flag value *true* or *false*. Could also be numeric, i.e. either 0=*false* or 1=*true*. Other numeric values don't work! -- @param #string condition Lua string. -- @param DCS#Time duration Duration in seconds. -- @param #number lastWayPoint Last waypoint. @@ -944,7 +943,7 @@ end -- @param #boolean Divebomb (optional) Perform dive bombing. Default false. -- @return DCS#Task The DCS task structure. 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 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 end diff --git a/Moose Development/Moose/Wrapper/Unit.lua b/Moose Development/Moose/Wrapper/Unit.lua index a138bf0b0..b047e3258 100644 --- a/Moose Development/Moose/Wrapper/Unit.lua +++ b/Moose Development/Moose/Wrapper/Unit.lua @@ -696,7 +696,9 @@ end --- 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 1: Unit is infantry. @@ -709,13 +711,49 @@ end -- * Threat level 8: Unit is a Short 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. +-- +-- **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 function UNIT:GetThreatLevel() local ThreatLevel = 0 local ThreatText = "" - + local Descriptor = self:GetDesc() if Descriptor then