From fd1eb5f601e432b0e6e2da21fce3afe20d9071be Mon Sep 17 00:00:00 2001 From: FlightControl Date: Fri, 16 Aug 2019 22:15:29 +0200 Subject: [PATCH 1/3] AI_A2A_DISPATCHER and AI_A2G_DISPATCHER ... Fixed in-air refuelling problem. Now refuelling is executed instateneously when patrolling in the air and the fuel treshold has been reached. --- Moose Development/Moose/AI/AI_A2A.lua | 2 +- Moose Development/Moose/AI/AI_Air.lua | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) 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_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() From 94476e418e8891e5dfba8858b771fcab5ad139da Mon Sep 17 00:00:00 2001 From: FlightControl Date: Sat, 17 Aug 2019 10:25:26 +0200 Subject: [PATCH 2/3] Messaging and Tactical Menu --- .../Moose/AI/AI_A2A_Dispatcher.lua | 101 ++++++++++++++++++ .../Moose/AI/AI_A2G_Dispatcher.lua | 16 +-- .../Moose/Tasking/DetectionManager.lua | 27 ++++- 3 files changed, 134 insertions(+), 10 deletions(-) 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/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 From ec85b42a388fef0cd8febae4d55fedced59b36d3 Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 18 Aug 2019 22:21:41 +0200 Subject: [PATCH 3/3] OPS AIRBOSS v1.0.8 - Fixed bug that status reset after wave off causes a unitcorn grade. - Optimized radio scheduler calls ==> leads to less mem consumtion according to collectgarbage("count"). RESCUEHELO v1.0.9 - Adjusted default follow time interval to 1.0 sec. - Added SetFollowTimeInterval() function. - Respawn after returned delayed by 5 sec. GROUP - Added nil check in RespawnAtCurrentAirbase() function. --- Moose Development/Moose/Ops/Airboss.lua | 167 ++++++++++++----- Moose Development/Moose/Ops/RescueHelo.lua | 25 ++- Moose Development/Moose/Wrapper/Group.lua | 199 +++++++++++---------- 3 files changed, 250 insertions(+), 141 deletions(-) diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index 960ea2510..e8a88ec21 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -48,11 +48,15 @@ -- -- Heatblur's mighty F-14B Tomcat has been added (March 13th 2019) as well. -- --- -- ## Discussion -- -- 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. -- -- ## IMPORTANT -- @@ -1671,6 +1675,7 @@ AIRBOSS.Difficulty={ -- @field #boolean showhints If true, show step hints. -- @field #table trapsheet Groove data table recorded every 0.5 seconds. -- @field #boolean trapon If true, save trap sheets. +-- @field #string debriefschedulerID Debrief scheduler ID. -- @extends #AIRBOSS.FlightGroup --- Main group level radio menu: F10 Other/Airboss. @@ -1683,7 +1688,7 @@ AIRBOSS.MenuF10Root=nil --- Airboss class version. -- @field #string version -AIRBOSS.version="1.0.7" +AIRBOSS.version="1.0.8" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -1944,7 +1949,7 @@ function AIRBOSS:New(carriername, alias) self.Debug=true BASE:TraceOnOff(true) BASE:TraceClass(self.ClassName) - BASE:TraceLevel(1) + BASE:TraceLevel(3) --self.dTstatus=0.1 end @@ -2265,6 +2270,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 @@ -2494,7 +2522,8 @@ end function AIRBOSS:CloseCurrentRecoveryWindow(delay) if delay and delay>0 then - SCHEDULER:New(nil, self.CloseCurrentRecoveryWindow, {self}, delay) + --SCHEDULER:New(nil, self.CloseCurrentRecoveryWindow, {self}, delay) + self:ScheduleOnce(delay, self.CloseCurrentRecoveryWindow, self) else if self:IsRecovering() and self.recoverywindow and self.recoverywindow.OPEN then self:RecoveryStop() @@ -2544,7 +2573,8 @@ function AIRBOSS:DeleteRecoveryWindow(window, delay) if delay and delay>0 then -- Delayed call. - SCHEDULER:New(nil, self.DeleteRecoveryWindow, {self, window}, delay) + --SCHEDULER:New(nil, self.DeleteRecoveryWindow, {self, window}, delay) + self:ScheduleOnce(delay, self.DeleteRecoveryWindow, self, window) else for i,_recovery in pairs(self.recoverytimes) do @@ -2964,7 +2994,8 @@ function AIRBOSS:SoundCheckLSO(delay) if delay and delay>0 then -- Delayed call. - SCHEDULER:New(nil, AIRBOSS.SoundCheckLSO, {self}, delay) + --SCHEDULER:New(nil, AIRBOSS.SoundCheckLSO, {self}, delay) + self:ScheduleOnce(delay, AIRBOSS.SoundCheckLSO, self) else @@ -2999,7 +3030,8 @@ function AIRBOSS:SoundCheckMarshal(delay) if delay and delay>0 then -- Delayed call. - SCHEDULER:New(nil, AIRBOSS.SoundCheckMarshal, {self}, delay) + --SCHEDULER:New(nil, AIRBOSS.SoundCheckMarshal, {self}, delay) + self:ScheduleOnce(delay, AIRBOSS.SoundCheckMarshal, self) else @@ -3249,9 +3281,13 @@ function AIRBOSS:onafterStart(From, Event, To) self:_ActivateBeacons() -- Schedule radio queue checks. - self.RQLid=self.radiotimer:Schedule(self, self._CheckRadioQueue, {self.RQLSO, "LSO"}, 1, 0.01) - self.RQMid=self.radiotimer:Schedule(self, self._CheckRadioQueue, {self.RQMarshal, "MARSHAL"}, 1, 0.01) - + --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() @@ -3687,7 +3723,7 @@ function AIRBOSS:_CheckRecoveryTimes() -- Closed. recovery.OPEN=false - + -- Window just closed. recovery.OVER=true @@ -4023,6 +4059,8 @@ end -- @param #string Event Event. -- @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) @@ -4032,6 +4070,8 @@ function AIRBOSS:onafterStop(From, Event, To) self:UnHandleEvent(EVENTS.Ejection) self:UnHandleEvent(EVENTS.PlayerLeaveUnit) self:UnHandleEvent(EVENTS.MissionEnd) + + self.CallScheduler:Clear() end ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -5848,7 +5888,8 @@ function AIRBOSS:_SetPlayerStep(playerData, step, delay) if delay and delay>0 then -- Delayed call. - SCHEDULER:New(nil, self._SetPlayerStep, {self, playerData, step}, delay) + --SCHEDULER:New(nil, self._SetPlayerStep, {self, playerData, step}, delay) + self:ScheduleOnce(delay, self._SetPlayerStep, self, playerData, step) else -- Check if player still exists after possible delay. @@ -7316,6 +7357,7 @@ function AIRBOSS:_InitPlayer(playerData, step) playerData.TIG0=nil playerData.wire=nil playerData.flag=-100 + playerData.debriefschedulerID=nil -- Set us up on final if group name contains "Groove". But only for the first pass. if playerData.group:GetName():match("Groove") and playerData.passes==0 then @@ -7796,6 +7838,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 -- Check if flight should be completely removed, e.g. after the player died or simply left the slot. if completely then @@ -7806,15 +7856,6 @@ function AIRBOSS:_RemoveFlight(flight, completely) -- Remove completely. self:_RemoveFlightFromQueue(self.flights, flight) - -- 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 - - -- Remove player from players table. local playerdata=self.players[flight.name] if playerdata then @@ -8005,7 +8046,8 @@ function AIRBOSS:_CheckPlayerStatus() elseif playerData.step==AIRBOSS.PatternStep.DEBRIEF then -- Debriefing in 5 seconds. - SCHEDULER:New(nil, self._Debrief, {self, playerData}, 5) + --SCHEDULER:New(nil, self._Debrief, {self, playerData}, 5) + playerData.debriefschedulerID=self:ScheduleOnce(5, self._Debrief, self, playerData) -- Undefined status. playerData.step=AIRBOSS.PatternStep.UNDEFINED @@ -8186,7 +8228,8 @@ function AIRBOSS:OnEventBirth(EventData) self:_AddF10Commands(_unitName) -- Delaying the new player for a second, because AI units of the flight would not be registered correctly. - SCHEDULER:New(nil, self._NewPlayer, {self, _unitName}, 1) + --SCHEDULER:New(nil, self._NewPlayer, {self, _unitName}, 1) + self:ScheduleOnce(1, self._NewPlayer, self, _unitName) end end @@ -8339,7 +8382,8 @@ function AIRBOSS:OnEventLand(EventData) self:_SetPlayerStep(playerData, AIRBOSS.PatternStep.UNDEFINED) -- Call trapped function in 1 second to make sure we did not bolter. - SCHEDULER:New(nil, self._Trapped, {self, playerData}, 1) + --SCHEDULER:New(nil, self._Trapped, {self, playerData}, 1) + self:ScheduleOnce(1, self._Trapped, self, playerData) end @@ -10233,7 +10277,8 @@ function AIRBOSS:_Trapped(playerData) end -- Call function again and check if converged or back in air. - SCHEDULER:New(nil, self._Trapped, {self, playerData}, 0.1) + --SCHEDULER:New(nil, self._Trapped, {self, playerData}, 0.1) + self:ScheduleOnce(0.1, self._Trapped, self, playerData) return end @@ -12672,6 +12717,9 @@ 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 -- Switch attitude monitor off if on. playerData.attitudemonitor=false @@ -12735,7 +12783,7 @@ function AIRBOSS:_Debrief(playerData) -- Add LSO grade to player grades table. table.insert(self.playerscores[playerData.name], mygrade) - + -- Trigger grading event. self:LSOGrade(playerData, mygrade) @@ -14299,6 +14347,14 @@ end -- RADIO MESSAGE Functions ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--- Function called by DCS timer. Unused. +-- @param #table param Parameters. +-- @param #number time Time. +function AIRBOSS._CheckRadioQueueT(param, time) + AIRBOSS._CheckRadioQueue(param.airboss, param.radioqueue, param.name) + return time+0.05 +end + --- Radio queue item. -- @type AIRBOSS.Radioitem -- @field #number Tplay Abs time when transmission should be played. @@ -14315,37 +14371,50 @@ end -- @param #string name Name of the queue. function AIRBOSS:_CheckRadioQueue(radioqueue, name) + --env.info(string.format("FF %s #radioqueue %d", name, #radioqueue)) + -- 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) + self.RQLid=nil + elseif name=="MARSHAL" then + self:T(self.lid..string.format("Stopping Marshal radio queue.")) + self.radiotimer:Stop(self.RQMid) + self.RQMid=nil + end + return end -- Get current abs time. - local time=timer.getAbsTime() + local _time=timer.getAbsTime() local playing=false local next=nil --#AIRBOSS.Radioitem - local remove=nil + local _remove=nil for i,_transmission in ipairs(radioqueue) do local transmission=_transmission --#AIRBOSS.Radioitem -- Check if transmission time has passed. - if time>=transmission.Tplay then + 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.call.duration then + if _time>=transmission.Tstarted+transmission.call.duration then -- Transmission over. transmission.isplaying=false - remove=i + _remove=i if transmission.radio.alias=="LSO" then - self.TQLSO=time + self.TQLSO=_time elseif transmission.radio.alias=="MARSHAL" then - self.TQMarshal=time + self.TQMarshal=_time end else -- still playing @@ -14375,7 +14444,7 @@ function AIRBOSS:_CheckRadioQueue(radioqueue, name) else - if time-Tlast>=transmission.interval then + if _time-Tlast>=transmission.interval then next=transmission else @@ -14400,14 +14469,15 @@ function AIRBOSS:_CheckRadioQueue(radioqueue, name) if next~=nil and not playing then self:Broadcast(next.radio, next.call, next.loud) next.isplaying=true - next.Tstarted=time + next.Tstarted=_time end -- Remove completed calls from queue. - if remove then - table.remove(radioqueue, remove) + if _remove then + table.remove(radioqueue, _remove) end - + + return end --- Add Radio transmission to radio queue. @@ -14455,12 +14525,23 @@ 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) + end end @@ -14743,7 +14824,8 @@ function AIRBOSS:MessageToPlayer(playerData, message, sender, receiver, duration if delay and delay>0 then -- Delayed call. - SCHEDULER:New(nil, self.MessageToPlayer, {self, playerData, message, sender, receiver, duration, clear}, delay) + --SCHEDULER:New(nil, self.MessageToPlayer, {self, playerData, message, sender, receiver, duration, clear}, delay) + self:ScheduleOnce(delay, self.MessageToPlayer, self, playerData, message, sender, receiver, duration, clear) else -- Wait until previous sound finished. @@ -15797,6 +15879,11 @@ 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) + end -- Initialize player data. self:_InitPlayer(playerData) diff --git a/Moose Development/Moose/Ops/RescueHelo.lua b/Moose Development/Moose/Ops/RescueHelo.lua index 350722c50..0a040f32e 100644 --- a/Moose Development/Moose/Ops/RescueHelo.lua +++ b/Moose Development/Moose/Ops/RescueHelo.lua @@ -57,6 +57,7 @@ -- @field #string alias Alias of the spawn group. -- @field #number uid Unique ID of this helo. -- @field #number modex Tail number of the helo. +-- @field #number dtFollow Follow time update interval in seconds. Default 1.0 sec. -- @extends Core.Fsm#FSM --- Rescue Helo @@ -227,6 +228,7 @@ RESCUEHELO = { alias = nil, uid = 0, modex = nil, + dtFollow = nil, } --- Unique ID (global). @@ -235,7 +237,7 @@ _RESCUEHELOID=0 --- Class version. -- @field #string version -RESCUEHELO.version="1.0.8" +RESCUEHELO.version="1.0.9" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -303,6 +305,7 @@ function RESCUEHELO:New(carrierunit, helogroupname) self:SetRescueZone() self:SetRescueHoverSpeed() self:SetRescueDuration() + self:SetFollowTimeInterval() self:SetRescueStopBoatOff() -- Some more. @@ -644,6 +647,15 @@ function RESCUEHELO:SetModex(modex) return self end +--- Set follow time update interval. +-- @param #RESCUEHELO self +-- @param #number dt Time interval in seconds. Default 1.0 sec. +-- @return #RESCUEHELO self +function RESCUEHELO:SetFollowTimeInterval(dt) + self.dtFollow=dt or 1.0 + return self +end + --- Use an uncontrolled aircraft already present in the mission rather than spawning a new helo as initial rescue helo. -- This can be useful when interfaced with, e.g., a warehouse. -- The group name is the one specified in the @{#RESCUEHELO.New} function. @@ -895,7 +907,7 @@ function RESCUEHELO:onafterStart(From, Event, To) -- Use an uncontrolled aircraft group. self.helo=GROUP:FindByName(self.helogroupname) - if self.helo:IsAlive() then + if self.helo and self.helo:IsAlive() then -- Start uncontrolled group. self.helo:StartUncontrolled() @@ -940,6 +952,9 @@ function RESCUEHELO:onafterStart(From, Event, To) -- Formation parameters. self.formation:FormationCenterWing(-self.offsetX, 50, math.abs(self.altitude), 50, self.offsetZ, 50) + -- Set follow time interval. + self.formation:SetFollowTimeInterval(self.dtFollow) + -- Formation mode. self.formation:SetFlightModeFormation(self.helo) @@ -1005,7 +1020,7 @@ function RESCUEHELO:onafterStatus(From, Event, To) else -- Send helo back to base. - self:RTB() + self:RTB(self.airbase) end @@ -1196,7 +1211,9 @@ function RESCUEHELO:onafterReturned(From, Event, To, airbase) self.helo:InitModex(self.modex) -- Respawn helo at current airbase. - self.helo:RespawnAtCurrentAirbase() + if self.helo and self.helo:IsAlive() then + self:ScheduleOnce(5, self.helo.RespawnAtCurrentAirbase, self.helo) + end -- Restart the formation. self:__Run(10) diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index a6f372e1a..8c855be45 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -1773,110 +1773,115 @@ end function GROUP:RespawnAtCurrentAirbase(SpawnTemplate, Takeoff, Uncontrolled) -- R2.4 self:F2( { SpawnTemplate, Takeoff, Uncontrolled} ) - -- Get closest airbase. Should be the one we are currently on. - local airbase=self:GetCoordinate():GetClosestAirbase() - - if airbase then - self:F2("Closest airbase = "..airbase:GetName()) - else - self:E("ERROR: could not find closest airbase!") - return nil - end - -- Takeoff type. Default hot. - Takeoff = Takeoff or SPAWN.Takeoff.Hot - - -- Coordinate of the airbase. - local AirbaseCoord=airbase:GetCoordinate() - - -- Spawn template. - SpawnTemplate = SpawnTemplate or self:GetTemplate() + if self and self:IsAlive() then - if SpawnTemplate then - - local SpawnPoint = SpawnTemplate.route.points[1] - - -- These are only for ships. - SpawnPoint.linkUnit = nil - SpawnPoint.helipadId = nil - SpawnPoint.airdromeId = nil - - -- Aibase id and category. - local AirbaseID = airbase:GetID() - local AirbaseCategory = airbase:GetDesc().category + -- Get closest airbase. Should be the one we are currently on. + local airbase=self:GetCoordinate():GetClosestAirbase() - if AirbaseCategory == Airbase.Category.SHIP or AirbaseCategory == Airbase.Category.HELIPAD then - SpawnPoint.linkUnit = AirbaseID - SpawnPoint.helipadId = AirbaseID - elseif AirbaseCategory == Airbase.Category.AIRDROME then - SpawnPoint.airdromeId = AirbaseID + if airbase then + self:F2("Closest airbase = "..airbase:GetName()) + else + self:E("ERROR: could not find closest airbase!") + return nil end - + -- Takeoff type. Default hot. + Takeoff = Takeoff or SPAWN.Takeoff.Hot - SpawnPoint.type = GROUPTEMPLATE.Takeoff[Takeoff][1] -- type - SpawnPoint.action = GROUPTEMPLATE.Takeoff[Takeoff][2] -- action + -- Coordinate of the airbase. + local AirbaseCoord=airbase:GetCoordinate() - -- Get the units of the group. - local units=self:GetUnits() - - local x - local y - for UnitID=1,#units do + -- Spawn template. + SpawnTemplate = SpawnTemplate or self:GetTemplate() + + if SpawnTemplate then + + local SpawnPoint = SpawnTemplate.route.points[1] + + -- These are only for ships. + SpawnPoint.linkUnit = nil + SpawnPoint.helipadId = nil + SpawnPoint.airdromeId = nil + + -- Aibase id and category. + local AirbaseID = airbase:GetID() + local AirbaseCategory = airbase:GetDesc().category + + if AirbaseCategory == Airbase.Category.SHIP or AirbaseCategory == Airbase.Category.HELIPAD then + SpawnPoint.linkUnit = AirbaseID + SpawnPoint.helipadId = AirbaseID + elseif AirbaseCategory == Airbase.Category.AIRDROME then + SpawnPoint.airdromeId = AirbaseID + end + + + SpawnPoint.type = GROUPTEMPLATE.Takeoff[Takeoff][1] -- type + SpawnPoint.action = GROUPTEMPLATE.Takeoff[Takeoff][2] -- action + + -- Get the units of the group. + local units=self:GetUnits() + + local x + local y + for UnitID=1,#units do + + local unit=units[UnitID] --Wrapper.Unit#UNIT + + -- Get closest parking spot of current unit. Note that we look for occupied spots since the unit is currently sitting on it! + local Parkingspot, TermialID, Distance=unit:GetCoordinate():GetClosestParkingSpot(airbase) - local unit=units[UnitID] --Wrapper.Unit#UNIT - - -- Get closest parking spot of current unit. Note that we look for occupied spots since the unit is currently sitting on it! - local Parkingspot, TermialID, Distance=unit:GetCoordinate():GetClosestParkingSpot(airbase) - - --Parkingspot:MarkToAll("parking spot") - self:T2(string.format("Closest parking spot distance = %s, terminal ID=%s", tostring(Distance), tostring(TermialID))) - - -- Get unit coordinates for respawning position. - local uc=unit:GetCoordinate() - --uc:MarkToAll(string.format("re-spawnplace %s terminal %d", unit:GetName(), TermialID)) - - SpawnTemplate.units[UnitID].x = uc.x --Parkingspot.x - SpawnTemplate.units[UnitID].y = uc.z --Parkingspot.z - SpawnTemplate.units[UnitID].alt = uc.y --Parkingspot.y - - SpawnTemplate.units[UnitID].parking = TermialID - SpawnTemplate.units[UnitID].parking_id = nil - - --SpawnTemplate.units[UnitID].unitId=nil - end - - --SpawnTemplate.groupId=nil - - SpawnPoint.x = SpawnTemplate.units[1].x --x --AirbaseCoord.x - SpawnPoint.y = SpawnTemplate.units[1].y --y --AirbaseCoord.z - SpawnPoint.alt = SpawnTemplate.units[1].alt --AirbaseCoord:GetLandHeight() - - SpawnTemplate.x = SpawnTemplate.units[1].x --x --AirbaseCoord.x - SpawnTemplate.y = SpawnTemplate.units[1].y --y --AirbaseCoord.z - - -- Set uncontrolled state. - SpawnTemplate.uncontrolled=Uncontrolled - - -- Set radio frequency and modulation. - if self.InitRespawnRadio then - SpawnTemplate.communication=self.InitRespawnRadio - end - if self.InitRespawnFreq then - SpawnTemplate.frequency=self.InitRespawnFreq - end - if self.InitRespawnModu then - SpawnTemplate.modulation=self.InitRespawnModu - end - - -- Destroy old group. - self:Destroy(false) - - -- Spawn new group. - _DATABASE:Spawn(SpawnTemplate) + --Parkingspot:MarkToAll("parking spot") + self:T2(string.format("Closest parking spot distance = %s, terminal ID=%s", tostring(Distance), tostring(TermialID))) - -- Reset events. - self:ResetEvents() - - return self + -- Get unit coordinates for respawning position. + local uc=unit:GetCoordinate() + --uc:MarkToAll(string.format("re-spawnplace %s terminal %d", unit:GetName(), TermialID)) + + SpawnTemplate.units[UnitID].x = uc.x --Parkingspot.x + SpawnTemplate.units[UnitID].y = uc.z --Parkingspot.z + SpawnTemplate.units[UnitID].alt = uc.y --Parkingspot.y + + SpawnTemplate.units[UnitID].parking = TermialID + SpawnTemplate.units[UnitID].parking_id = nil + + --SpawnTemplate.units[UnitID].unitId=nil + end + + --SpawnTemplate.groupId=nil + + SpawnPoint.x = SpawnTemplate.units[1].x --x --AirbaseCoord.x + SpawnPoint.y = SpawnTemplate.units[1].y --y --AirbaseCoord.z + SpawnPoint.alt = SpawnTemplate.units[1].alt --AirbaseCoord:GetLandHeight() + + SpawnTemplate.x = SpawnTemplate.units[1].x --x --AirbaseCoord.x + SpawnTemplate.y = SpawnTemplate.units[1].y --y --AirbaseCoord.z + + -- Set uncontrolled state. + SpawnTemplate.uncontrolled=Uncontrolled + + -- Set radio frequency and modulation. + if self.InitRespawnRadio then + SpawnTemplate.communication=self.InitRespawnRadio + end + if self.InitRespawnFreq then + SpawnTemplate.frequency=self.InitRespawnFreq + end + if self.InitRespawnModu then + SpawnTemplate.modulation=self.InitRespawnModu + end + + -- Destroy old group. + self:Destroy(false) + + -- Spawn new group. + _DATABASE:Spawn(SpawnTemplate) + + -- Reset events. + self:ResetEvents() + + return self + end + else + self:E("WARNING: GROUP is not alive!") end return nil