diff --git a/Moose Development/Moose/AI/AI_A2A.lua b/Moose Development/Moose/AI/AI_A2A.lua index 2b3269b74..afd7cc70d 100644 --- a/Moose Development/Moose/AI/AI_A2A.lua +++ b/Moose Development/Moose/AI/AI_A2A.lua @@ -669,7 +669,7 @@ function AI_A2A:onafterRefuel( AIGroup, From, Event, To ) local ToRefuelSpeed = math.random( self.PatrolMinSpeed, self.PatrolMaxSpeed ) --- Create a route point of type air. - local ToRefuelRoutePoint = ToRefuelCoord:WaypointAir( + local ToRefuelRoutePoint = CurrentCoord:WaypointAir( self.PatrolAltType, POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, diff --git a/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua b/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua index c19b2fec7..eb7c8d004 100644 --- a/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua +++ b/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua @@ -3104,18 +3104,26 @@ do -- AI_A2A_DISPATCHER self:F({"CAP Birth", Defender:GetName()}) --self:GetParent(self).onafterBirth( self, Defender, From, Event, To ) + local DefenderName = Defender:GetCallsign() local Dispatcher = Fsm:GetDispatcher() -- #AI_A2A_DISPATCHER local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) if Squadron then + Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " airborne. Starting Patrol." ) Fsm:__Patrol( 2 ) -- Start Patrolling end end function Fsm:onafterRTB( Defender, From, Event, To ) + self:F({"CAP RTB", Defender:GetName()}) + self:GetParent(self).onafterRTB( self, Defender, From, Event, To ) + + local DefenderName = Defender:GetCallsign() local Dispatcher = self:GetDispatcher() -- #AI_A2A_DISPATCHER + local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) + Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " returning." ) Dispatcher:ClearDefenderTaskTarget( Defender ) end @@ -3291,11 +3299,13 @@ do -- AI_A2A_DISPATCHER self:F({"GCI Birth", Defender:GetName()}) --self:GetParent(self).onafterBirth( self, Defender, From, Event, To ) + local DefenderName = Defender:GetCallsign() local Dispatcher = Fsm:GetDispatcher() -- #AI_A2A_DISPATCHER local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) local DefenderTarget = Dispatcher:GetDefenderTaskTarget( Defender ) if DefenderTarget then + Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " airborne. Engaging target!" ) Fsm:__Engage( 2, DefenderTarget.Set ) -- Engage on the TargetSetUnit end end @@ -3304,7 +3314,10 @@ do -- AI_A2A_DISPATCHER self:F({"GCI RTB", Defender:GetName()}) self:GetParent(self).onafterRTB( self, Defender, From, Event, To ) + local DefenderName = Defender:GetCallsign() local Dispatcher = self:GetDispatcher() -- #AI_A2A_DISPATCHER + local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) + Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " returning." ) Dispatcher:ClearDefenderTaskTarget( Defender ) end @@ -3326,8 +3339,11 @@ do -- AI_A2A_DISPATCHER self:F({"GCI Home", Defender:GetName()}) self:GetParent(self).onafterHome( self, Defender, From, Event, To ) + local DefenderName = Defender:GetCallsign() local Dispatcher = self:GetDispatcher() -- #AI_A2A_DISPATCHER local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) + + Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " landing." ) if Action and Action == "Destroy" then Dispatcher:RemoveDefenderFromSquadron( Squadron, Defender ) @@ -3412,6 +3428,91 @@ do -- AI_A2A_DISPATCHER return nil, nil end + --- Shows the tactical display. + -- @param #AI_A2A_DISPATCHER self + function AI_A2A_DISPATCHER:ShowTacticalDisplay( Detection ) + + local AreaMsg = {} + local TaskMsg = {} + local ChangeMsg = {} + + local TaskReport = REPORT:New() + + local Report = REPORT:New( "Tactical Overviews" ) + + local DefenderGroupCount = 0 + + -- Now that all obsolete tasks are removed, loop through the detected targets. + for DetectedItemID, DetectedItem in pairs( Detection:GetDetectedItems() ) do + + local DetectedItem = DetectedItem -- Functional.Detection#DETECTION_BASE.DetectedItem + local DetectedSet = DetectedItem.Set -- Core.Set#SET_UNIT + local DetectedCount = DetectedSet:Count() + local DetectedZone = DetectedItem.Zone + + self:F( { "Target ID", DetectedItem.ItemID } ) + DetectedSet:Flush( self ) + + local DetectedID = DetectedItem.ID + local DetectionIndex = DetectedItem.Index + local DetectedItemChanged = DetectedItem.Changed + + -- Show tactical situation + Report:Add( string.format( "\n- Target %s (%s): (#%d) %s" , DetectedItem.ItemID, DetectedItem.Index, DetectedItem.Set:Count(), DetectedItem.Set:GetObjectNames() ) ) + 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 and Defender:IsAlive() then + DefenderGroupCount = DefenderGroupCount + 1 + local Fuel = Defender:GetFuelMin() * 100 + local Damage = Defender:GetLife() / Defender:GetLife0() * 100 + Report:Add( string.format( " - %s*%d/%d (%s - %s): (#%d) F: %3d, D:%3d - %s", + Defender:GetName(), + Defender:GetSize(), + Defender:GetInitialSize(), + DefenderTask.Type, + DefenderTask.Fsm:GetState(), + Defender:GetSize(), + Fuel, + Damage, + Defender:HasTask() == true and "Executing" or "Idle" ) ) + 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*%d/%d (%s - %s): (#%d) F: %3d, D:%3d - %s", + Defender:GetName(), + Defender:GetSize(), + Defender:GetInitialSize(), + 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 ) ) + + self:F( Report:Text( "\n" ) ) + trigger.action.outText( Report:Text( "\n" ), 25 ) + + return true + + end --- Assigns A2A AI Tasks in relation to the detected items. -- @param #AI_A2A_DISPATCHER self diff --git a/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua b/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua index 498c7a574..fa6177178 100644 --- a/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua +++ b/Moose Development/Moose/AI/AI_A2G_Dispatcher.lua @@ -3521,15 +3521,15 @@ do -- AI_A2G_DISPATCHER end end - function Fsm:onafterPatrolRoute( Defender, From, Event, To, AttackSetUnit ) + function Fsm:onafterPatrolRoute( Defender, From, Event, To ) self:F({"Defender PatrolRoute", Defender:GetName()}) - self:GetParent(self).onafterPatrolRoute( self, Defender, From, Event, To, AttackSetUnit ) + self:GetParent(self).onafterPatrolRoute( self, Defender, From, Event, To ) local DefenderName = Defender:GetCallsign() local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) if Squadron then - Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " returning." ) + Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " patrolling." ) end Dispatcher:ClearDefenderTaskTarget( Defender ) @@ -3627,7 +3627,7 @@ do -- AI_A2G_DISPATCHER self:F( { DefenderTarget = DefenderTarget } ) if DefenderTarget then - Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " airborne." ) + Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " airborne. Engaging!" ) Fsm:EngageRoute( DefenderTarget.Set ) -- Engage on the TargetSetUnit end end @@ -3644,7 +3644,7 @@ do -- AI_A2G_DISPATCHER local FirstUnit = AttackSetUnit:GetFirst() local Coordinate = FirstUnit:GetCoordinate() -- Core.Point#COORDINATE - Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " on route, bearing " .. Coordinate:ToString( Defender ) ) + Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " on route to ground target at " .. Coordinate:ToStringA2G( Defender ) ) end end @@ -3659,7 +3659,7 @@ do -- AI_A2G_DISPATCHER if FirstUnit then local Coordinate = FirstUnit:GetCoordinate() - Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " engaging target, bearing " .. Coordinate:ToString( Defender ) ) + Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " engaging ground target at " .. Coordinate:ToStringA2G( Defender ) ) end end @@ -3669,7 +3669,7 @@ do -- AI_A2G_DISPATCHER local DefenderName = Defender:GetCallsign() local Dispatcher = self:GetDispatcher() -- #AI_A2G_DISPATCHER local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) - Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " RTB." ) + Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " returning." ) self:GetParent(self).onafterRTB( self, Defender, From, Event, To ) @@ -3684,7 +3684,7 @@ do -- AI_A2G_DISPATCHER local DefenderName = Defender:GetCallsign() local Dispatcher = Fsm:GetDispatcher() -- #AI_A2G_DISPATCHER local Squadron = Dispatcher:GetSquadronFromDefender( Defender ) - Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " lost control." ) + --Dispatcher:MessageToPlayers( "Squadron " .. Squadron.Name .. ", " .. DefenderName .. " lost control." ) if Defender:IsAboveRunway() then Dispatcher:RemoveDefenderFromSquadron( Squadron, Defender ) diff --git a/Moose Development/Moose/AI/AI_Air.lua b/Moose Development/Moose/AI/AI_Air.lua index fe62ff71f..0bee1f1ab 100644 --- a/Moose Development/Moose/AI/AI_Air.lua +++ b/Moose Development/Moose/AI/AI_Air.lua @@ -704,7 +704,7 @@ function AI_AIR:onafterRefuel( AIGroup, From, Event, To ) ) --- Create a route point of type air. - local ToRefuelRoutePoint = ToRefuelCoord:WaypointAir( + local ToRefuelRoutePoint = FromRefuelCoord:WaypointAir( self.PatrolAltType, POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, @@ -715,7 +715,7 @@ function AI_AIR:onafterRefuel( AIGroup, From, Event, To ) self:F( { ToRefuelSpeed = ToRefuelSpeed } ) RefuelRoute[#RefuelRoute+1] = FromRefuelRoutePoint - RefuelRoute[#RefuelRoute+1] = ToRefuelRoutePoint + --RefuelRoute[#RefuelRoute+1] = ToRefuelRoutePoint AIGroup:OptionROEHoldFire() AIGroup:OptionROTEvadeFire() diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index a8bbc5a53..ec66435a8 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -52,9 +52,9 @@ -- -- If you have questions or suggestions, please visit the [MOOSE Discord](https://discord.gg/AeYAkHP) #ops-airboss channel. -- There you also find an example mission and the necessary voice over sound files. Check out the **pinned messages**. --- +-- -- ## Example Missions --- +-- -- Example missions can be found [here](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/OPS%20-%20Airboss). -- They contain the latest development Moose.lua file. -- @@ -2293,6 +2293,29 @@ function AIRBOSS:New(carriername, alias) -- @param #AIRBOSS.LSOgrade grade LSO grade. + --- Triggers the FSM event "LSOGrade". Called when the LSO grades a player + -- @function [parent=#AIRBOSS] LSOGrade + -- @param #AIRBOSS self + -- @param #AIRBOSS.PlayerData playerData Player Data. + -- @param #AIRBOSS.LSOgrade grade LSO grade. + + --- Triggers the FSM event "LSOGrade". Delayed called when the LSO grades a player. + -- @function [parent=#AIRBOSS] __LSOGrade + -- @param #AIRBOSS self + -- @param #number delay Delay in seconds. + -- @param #AIRBOSS.PlayerData playerData Player Data. + -- @param #AIRBOSS.LSOgrade grade LSO grade. + + --- On after "LSOGrade" user function. Called when the carrier passes a waypoint of its route. + -- @function [parent=#AIRBOSS] OnAfterLSOGrade + -- @param #AIRBOSS self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #AIRBOSS.PlayerData playerData Player Data. + -- @param #AIRBOSS.LSOgrade grade LSO grade. + + --- Triggers the FSM event "Stop" that stops the airboss. Event handlers are stopped. -- @function [parent=#AIRBOSS] Stop -- @param #AIRBOSS self @@ -3283,11 +3306,11 @@ function AIRBOSS:onafterStart(From, Event, To) -- Schedule radio queue checks. --self.RQLid=self.radiotimer:Schedule(nil, AIRBOSS._CheckRadioQueue, {self, self.RQLSO, "LSO"}, 1, 0.1) --self.RQMid=self.radiotimer:Schedule(nil, AIRBOSS._CheckRadioQueue, {self, self.RQMarshal, "MARSHAL"}, 1, 0.1) - + --self:I("FF: starting timer.scheduleFunction") --timer.scheduleFunction(AIRBOSS._CheckRadioQueueT, {airboss=self, radioqueue=self.RQLSO, name="LSO"}, timer.getTime()+1) --timer.scheduleFunction(AIRBOSS._CheckRadioQueueT, {airboss=self, radioqueue=self.RQMarshal, name="MARSHAL"}, timer.getTime()+1) - + -- Initial carrier position and orientation. self.Cposition=self:GetCoordinate() self.Corientation=self.carrier:GetOrientationX() @@ -3342,7 +3365,7 @@ function AIRBOSS:onafterStatus(From, Event, To) -- Update marshal and pattern queue every 30 seconds. if time-self.Tqueue>self.dTqueue then - + --collectgarbage() -- Get time. @@ -4067,6 +4090,7 @@ end -- @param #string To To state. function AIRBOSS:onafterStop(From, Event, To) self:I(self.lid..string.format("Stopping airboss script.")) + -- Unhandle events. self:UnHandleEvent(EVENTS.Birth) self:UnHandleEvent(EVENTS.Land) @@ -4076,10 +4100,8 @@ function AIRBOSS:onafterStop(From, Event, To) self:UnHandleEvent(EVENTS.Ejection) self:UnHandleEvent(EVENTS.PlayerLeaveUnit) self:UnHandleEvent(EVENTS.MissionEnd) - + self.CallScheduler:Clear() - - self=nil end ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -7846,14 +7868,14 @@ function AIRBOSS:_RemoveFlight(flight, completely) self:_RemoveFlightFromQueue(self.flights, flight) else - + -- Remove all grades until a final grade is reached. local grades=self.playerscores[flight.name] if grades and #grades>0 then while #grades>0 and grades[#grades].finalscore==nil do table.remove(grades, #grades) end - end + end -- Check if flight should be completely removed, e.g. after the player died or simply left the slot. if completely then @@ -12725,7 +12747,7 @@ end -- @param #AIRBOSS.PlayerData playerData Player data. function AIRBOSS:_Debrief(playerData) self:F(self.lid..string.format("Debriefing of player %s.", playerData.name)) - + -- Delete scheduler ID. playerData.debriefschedulerID=nil @@ -14383,7 +14405,7 @@ function AIRBOSS:_CheckRadioQueue(radioqueue, name) -- Check if queue is empty. if #radioqueue==0 then - + if name=="LSO" then self:T(self.lid..string.format("Stopping LSO radio queue.")) self.radiotimer:Stop(self.RQLid) @@ -14393,7 +14415,7 @@ function AIRBOSS:_CheckRadioQueue(radioqueue, name) self.radiotimer:Stop(self.RQMid) self.RQMid=nil end - + return end @@ -14484,7 +14506,7 @@ function AIRBOSS:_CheckRadioQueue(radioqueue, name) if _remove then table.remove(radioqueue, _remove) end - + return end @@ -14533,19 +14555,19 @@ function AIRBOSS:RadioTransmission(radio, call, loud, delay, interval, click, pi table.insert(self.RQLSO, transmission) caller="LSOCall" - + -- Schedule radio queue checks. if not self.RQLid then self:T(self.lid..string.format("Starting LSO radio queue.")) self.RQLid=self.radiotimer:Schedule(nil, AIRBOSS._CheckRadioQueue, {self, self.RQLSO, "LSO"}, 0.02, 0.05) end - + elseif radio.alias=="MARSHAL" then table.insert(self.RQMarshal, transmission) caller="MarshalCall" - + if not self.RQMid then self:T(self.lid..string.format("Starting Marhal radio queue.")) self.RQMid=self.radiotimer:Schedule(nil, AIRBOSS._CheckRadioQueue, {self, self.RQMarshal, "MARSHAL"}, 0.02, 0.05) @@ -15887,7 +15909,7 @@ function AIRBOSS:_ResetPlayerStatus(_unitName) -- Remove flight from queues. Collapse marshal stack if necessary. -- Section members are removed from the Spinning queue. If flight is member, he is removed from the section. self:_RemoveFlight(playerData) - + -- Stop pending debrief scheduler. if playerData.debriefschedulerID and self.Scheduler then self.Scheduler:Stop(playerData.debriefschedulerID) diff --git a/Moose Development/Moose/Tasking/DetectionManager.lua b/Moose Development/Moose/Tasking/DetectionManager.lua index b641ac1c3..9f827e3d4 100644 --- a/Moose Development/Moose/Tasking/DetectionManager.lua +++ b/Moose Development/Moose/Tasking/DetectionManager.lua @@ -221,6 +221,26 @@ do -- DETECTION MANAGER return self._ReportDisplayTime end + + + --- Set a command center to communicate actions to the players reporting to the command center. + -- @param #DETECTION_MANAGER self + -- @param Tasking.CommandCenter#COMMANDCENTER CommandCenter The command center. + -- @return #DETECTION_MANGER self + function DETECTION_MANAGER:SetTacticalMenu( DispatcherMainMenuText, DispatcherMenuText ) + + local DispatcherMainMenu = MENU_MISSION:New( DispatcherMainMenuText, nil ) + local DispatcherMenu = MENU_MISSION_COMMAND:New( DispatcherMenuText, DispatcherMainMenu, + function() + self:ShowTacticalDisplay( self.Detection ) + end + ) + + return self + end + + + --- Set a command center to communicate actions to the players reporting to the command center. -- @param #DETECTION_MANAGER self @@ -242,8 +262,11 @@ do -- DETECTION MANAGER self:F( { Message = Message } ) - if self.CC then - self.CC:MessageToCoalition( Message ) + if not self.PreviousMessage or self.PreviousMessage ~= Message then + self.PreviousMessage = Message + if self.CC then + self.CC:MessageToCoalition( Message ) + end end return self