diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index deeb12002..b201ca95a 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -2,8 +2,9 @@ -- -- **Main Features:** -- --- * Monitor flight status of elements or entire group. +-- * Monitor flight status of elements and/or the entire group. -- * Monitor fuel and ammo status. +-- * Conveniently set radio freqencies, TACAN, ROE etc. -- * Sophisticated task queueing system. -- * Many additional events for each element and the whole group. -- @@ -98,15 +99,6 @@ -- -- ## Waypoint Tasks -- --- # Missions --- --- ## Anti-ship --- --- ## AWACS --- --- ## INTERCEPT --- --- -- # Examples -- -- Here are some examples to show how things are done. @@ -128,8 +120,6 @@ -- ## 8. Enroute Tasks -- -- --- --- -- @field #FLIGHTGROUP FLIGHTGROUP = { ClassName = "FLIGHTGROUP", @@ -2933,54 +2923,6 @@ function FLIGHTGROUP:IsLandingAirbase(wp) return nil end - ---- Check if task description is unique. --- @param #FLIGHTGROUP self --- @param #string description Task destription --- @return #boolean If true, no other task has the same description. -function FLIGHTGROUP:CheckTaskDescriptionUnique(description) - - -- Loop over tasks in queue - for _,_task in pairs(self.taskqueue) do - local task=_task --#FLIGHTGROUP.Task - if task.description==description then - return false - end - end - - return true -end - - ---- Get next waypoint of the flight group. --- @param #FLIGHTGROUP self --- @return Core.Point#COORDINATE Coordinate of the next waypoint. --- @return #number Number of waypoint. -function FLIGHTGROUP:GetNextWaypoint() - - -- Next waypoint. - local Nextwp=nil - if self.currentwp==#self.waypoints then - Nextwp=1 - else - Nextwp=self.currentwp+1 - end - - -- Next waypoint. - local nextwp=self.waypoints[Nextwp] --Core.Point#COORDINATE - - return nextwp,Nextwp -end - ---- Get next waypoint coordinates. --- @param #FLIGHTGROUP self --- @param #table wp Waypoint table. --- @return Core.Point#COORDINATE Coordinate of the next waypoint. -function FLIGHTGROUP:GetWaypointCoordinate(wp) - -- TODO: move this to COORDINATE class. - return COORDINATE:New(wp.x, wp.alt, wp.y) -end - --- Initialize Mission Editor waypoints. -- @param #FLIGHTGROUP self -- @param #table waypoints Table of waypoints. Default is from group template. @@ -3026,11 +2968,11 @@ end --- Add an AIR waypoint to the flight plan. -- @param #FLIGHTGROUP self -- @param Core.Point#COORDINATE coordinate The coordinate of the waypoint. Use COORDINATE:SetAltitude(altitude) to define the altitude. --- @param #number wpnumber Waypoint number. Default at the end. -- @param #number speed Speed in knots. Default 350 kts. +-- @param #number wpnumber Waypoint number. Default at the end. -- @param #boolean updateroute If true or nil, call UpdateRoute. If false, no call. -- @return #number Waypoint index. -function FLIGHTGROUP:AddWaypoint(coordinate, wpnumber, speed, updateroute) +function FLIGHTGROUP:AddWaypoint(coordinate, speed, wpnumber, updateroute) -- Waypoint number. Default is at the end. wpnumber=wpnumber or #self.waypoints+1 diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index 61bd2b40d..ae4b09694 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -2,10 +2,10 @@ -- -- **Main Features:** -- --- * Dynamically add and remove waypoints --- * Let the group steam into the wind --- * Command a full stop --- * Let a submarine dive and surface +-- * Dynamically add and remove waypoints. +-- * Let the group steam into the wind. +-- * Command a full stop. +-- * Let a submarine dive and surface. -- -- === -- @@ -18,6 +18,7 @@ -- @type NAVYGROUP -- @field #boolean turning If true, group is currently turning. -- @field #NAVYGROUP.IntoWind intowind Into wind info. +-- @field #table Qintowind Queue of "into wind" turns. -- @extends Ops.OpsGroup#OPSGROUP --- *Something must be left to chance; nothing is sure in a sea fight above all.* -- Horatio Nelson @@ -32,10 +33,12 @@ -- -- @field #NAVYGROUP NAVYGROUP = { - ClassName = "NAVYGROUP", - verbose = 2, - turning = false, - intowind = nil, + ClassName = "NAVYGROUP", + verbose = 2, + turning = false, + intowind = nil, + intowindcounter = 0, + Qintowind = {}, } --- Navy group element. @@ -49,6 +52,8 @@ NAVYGROUP = { -- @field #number Tstop Time to stop. -- @field #boolean Uturn U-turn. -- @field #number Speed Speed in knots. +-- @field #boolean Open Currently active. +-- @field #boolean Over This turn is over. --- NavyGroup version. @@ -90,6 +95,8 @@ function NAVYGROUP:New(GroupName) self:AddTransition("*", "Cruise", "Cruising") -- Hold position. self:AddTransition("*", "TurnIntoWind", "*") -- Command the group to turn into the wind. + self:AddTransition("*", "TurnIntoWindOver", "*") -- Turn into wind is over. + self:AddTransition("*", "TurningStarted", "*") -- Group started turning. self:AddTransition("*", "TurningStopped", "*") -- Group stopped turning. @@ -173,6 +180,97 @@ function NAVYGROUP:AddTaskAttackGroup(TargetGroup, WeaponExpend, WeaponType, Clo end +--- Add aircraft recovery time window and recovery case. +-- @param #NAVYGROUP self +-- @param #string starttime Start time, e.g. "8:00" for eight o'clock. Default now. +-- @param #string stoptime Stop time, e.g. "9:00" for nine o'clock. Default 90 minutes after start time. +-- @param #number speed Speed in knots during turn into wind leg. +-- @param #boolean uturn If true (or nil), carrier wil perform a U-turn and go back to where it came from before resuming its route to the next waypoint. If false, it will go directly to the next waypoint. +-- @return #NAVYGROUP.IntoWind Recovery window. +function NAVYGROUP:CreateTurnIntoWind(starttime, stoptime, speed, uturn) + + -- Absolute mission time in seconds. + local Tnow=timer.getAbsTime() + + if starttime and type(starttime)=="number" then + starttime=UTILS.SecondsToClock(Tnow+starttime) + end + + if stoptime and type(stoptime)=="number" then + stoptime=UTILS.SecondsToClock(Tnow+stoptime) + end + + -- Input or now. + starttime=starttime or UTILS.SecondsToClock(Tnow) + + -- Set start time. + local Tstart=UTILS.ClockToSeconds(starttime) + + -- Set stop time. + local Tstop=UTILS.ClockToSeconds(stoptime or Tstart+90*60) + + -- Consistancy check for timing. + if Tstart>Tstop then + self:E(string.format("ERROR:Into wind stop time %s lies before start time %s. Input rejected!", UTILS.SecondsToClock(Tstart), UTILS.SecondsToClock(Tstop))) + return self + end + if Tstop<=Tnow then + self:I(string.format("WARNING: Into wind stop time %s already over. Tnow=%s! Input rejected.", UTILS.SecondsToClock(Tstop), UTILS.SecondsToClock(Tnow))) + return self + end + + -- Increase counter. + self.intowindcounter=self.intowindcounter+1 + + -- Recovery window. + local recovery={} --#NAVYGROUP.IntoWind + recovery.Tstart=Tstart + recovery.Tstop=Tstop + recovery.Open=false + recovery.Over=false + recovery.Speed=speed or 20 + recovery.Uturn=uturn and uturn or false + recovery.Id=self.intowindcounter + + return recovery +end + +--- Add aircraft recovery time window and recovery case. +-- @param #NAVYGROUP self +-- @param #string starttime Start time, e.g. "8:00" for eight o'clock. Default now. +-- @param #string stoptime Stop time, e.g. "9:00" for nine o'clock. Default 90 minutes after start time. +-- @param #number speed Speed in knots during turn into wind leg. +-- @param #boolean uturn If true (or nil), carrier wil perform a U-turn and go back to where it came from before resuming its route to the next waypoint. If false, it will go directly to the next waypoint. +-- @return #NAVYGROUP.IntoWind Recovery window. +function NAVYGROUP:AddTurnIntoWind(starttime, stoptime, speed, uturn) + + local recovery=self:CreateTurnIntoWind(starttime, stoptime, speed, uturn) + + -- Add to table + table.insert(self.Qintowind, recovery) + + return recovery +end + +--- Check if the group is currently turning. +-- @param #NAVYGROUP self +-- @return #boolean If true, group is currently turning. +function NAVYGROUP:IsTurning() + return self.turning +end + +--- Check if the group is currently steaming into the wind. +-- @param #NAVYGROUP self +-- @return #boolean If true, group is currently steaming into the wind. +function NAVYGROUP:IsSteamingIntoWind() + if self.intowind then + return true + else + return false + end +end + + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Status ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -204,22 +302,15 @@ function NAVYGROUP:onafterStatus(From, Event, To) -- Check water is ahead. local collision=self:_CheckCollisionCoord(pos:Translate(self.collisiondist or 5000, hdg)) + + self:_CheckTurnsIntoWind() - local intowind=false if self.intowind then if timer.getAbsTime()>=self.intowind.Tstop then - if self.intowind.Uturn then - self:UpdateRoute(self.currentwp) - else - self:UpdateRoute() - end + self:TurnIntoWindOver() - self.intowind=nil - - else - intowind=true end end @@ -229,7 +320,8 @@ function NAVYGROUP:onafterStatus(From, Event, To) local nMissions=self:CountRemainingMissison() -- Info text. - local text=string.format("State %s: Speed=%.1f knots Heading=%03d intowind=%s turning=%s collision=%s Tasks=%d Missions=%d", fsmstate, speed, hdg, tostring(intowind), tostring(self.turning), tostring(collision), nTaskTot, nMissions) + local text=string.format("State %s: Speed=%.1f knots Heading=%03d intowind=%s turning=%s collision=%s Tasks=%d Missions=%d", + fsmstate, speed, hdg, tostring(self:IsSteamingIntoWind()), tostring(self:IsTurning()), tostring(collision), nTaskTot, nMissions) self:I(self.lid..text) @@ -349,7 +441,7 @@ end function NAVYGROUP:onafterUpdateRoute(From, Event, To, n, Speed, Depth) -- Update route from this waypoint number onwards. - n=n or self.currentwp+1 + n=n or self:GetWaypointIndexNext(true) -- Update waypoint tasks, i.e. inject WP tasks into waypoint table. self:_UpdateWaypointTasks() @@ -358,11 +450,15 @@ function NAVYGROUP:onafterUpdateRoute(From, Event, To, n, Speed, Depth) local waypoints={} -- Speed. - local speed=Speed and UTILS.KnotsToKmph(Speed) or self.speedCruise + --local speed=Speed and UTILS.KnotsToKmph(Speed) or self.speedCruise -- Depth for submarines. local depth=Depth or 0 + + -- Get current speed in km/h. + local speed=Speed and UTILS.KnotsToKmph(Speed) or self.group:GetVelocityKMH() + -- Current waypoint. local current=self:GetCoordinate():WaypointNaval(speed, depth) table.insert(waypoints, current) @@ -371,7 +467,27 @@ function NAVYGROUP:onafterUpdateRoute(From, Event, To, n, Speed, Depth) local wp=self.waypoints[i] -- Set speed. - wp.speed=UTILS.KmphToMps(speed) + if i==n then + + if Speed then + wp.speed=UTILS.KnotsToMps(Speed) + elseif self.speedCruise then + wp.speed=UTILS.KmphToMps(self.speedCruise) + else + -- Take default waypoint speed. + end + + else + + if self.speedCruise then + wp.speed=UTILS.KmphToMps(self.speedCruise) + else + -- Take default waypoint speed. + end + + end + + -- Set depth. wp.alt=-depth --Depth and -Depth or wp.alt -- Add waypoint. @@ -396,7 +512,7 @@ function NAVYGROUP:onafterUpdateRoute(From, Event, To, n, Speed, Depth) if #self.waypoints>1 then self:I(self.lid..string.format("Resuming route at first waypoint")) - self:UpdateRoute(1) + self:__UpdateRoute(-1, 1) end end @@ -408,38 +524,58 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. +-- @param #NAVYGROUP.IntoWind Into wind parameters. -- @param #number Duration Duration in seconds. -- @param #number Speed Speed in knots. -- @param #boolean Uturn Return to the place we came from. -function NAVYGROUP:onafterTurnIntoWind(From, Event, To, Duration, Speed, Uturn) +function NAVYGROUP:onafterTurnIntoWind(From, Event, To, IntoWind) - local headingTo=self:GetCoordinate():GetWind(50) + IntoWind.Heading=self:GetCoordinate():GetWind(50) + + IntoWind.Open=true - local intowind={} --#NAVYGROUP.IntoWind - intowind.Speed=Speed - intowind.Tstart=timer.getAbsTime() - intowind.Tstop=intowind.Tstart+Duration - intowind.Uturn=Uturn or false - intowind.Heading=headingTo - - self.intowind=intowind + self.intowind=IntoWind - self:I(self.lid..string.format("Steaming into wind: Heading=%03d Speed=%.1f knots, Tstart=%d Tstop=%d", intowind.Heading, intowind.Speed, intowind.Tstart, intowind.Tstop)) + self:I(self.lid..string.format("Steaming into wind: Heading=%03d Speed=%.1f knots, Tstart=%d Tstop=%d", IntoWind.Heading, IntoWind.Speed, IntoWind.Tstart, IntoWind.Tstop)) local distance=UTILS.NMToMeters(1000) local wp={} local coord=self:GetCoordinate() - local Coord=coord:Translate(distance, headingTo) + local Coord=coord:Translate(distance, IntoWind.Heading) - wp[1]=coord:WaypointNaval(Speed) - wp[2]=Coord:WaypointNaval(Speed) + wp[1]=coord:WaypointNaval(IntoWind.Speed) + wp[2]=Coord:WaypointNaval(IntoWind.Speed) self:Route(wp) end +--- On after "TurnIntoWindOver" event. +-- @param #NAVYGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #number Duration Duration in seconds. +-- @param #number Speed Speed in knots. +-- @param #boolean Uturn Return to the place we came from. +function NAVYGROUP:onafterTurnIntoWindOver(From, Event, To) + + self.intowind.Over=true + self.intowind.Open=false + + if self.intowind.Uturn then + self:UpdateRoute(self.currentwp, self.speedmax*0.7) + else + local wp=self:GetWaypointIndexNext(true)-- math.min(#self.waypoints, self.currentwp+1) + self:UpdateRoute() + end + + self.intowind=nil + +end + --- On after "FullStop" event. -- @param #NAVYGROUP self -- @param #string From From state. @@ -463,9 +599,10 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -function NAVYGROUP:onafterCruise(From, Event, To) +-- @param #number Speed Speed in knots. +function NAVYGROUP:onafterCruise(From, Event, To, Speed) - self:UpdateRoute() + self:UpdateRoute(nil, Speed) end @@ -541,11 +678,11 @@ end --- Add an a waypoint to the route. -- @param #NAVYGROUP self -- @param Core.Point#COORDINATE coordinate The coordinate of the waypoint. Use COORDINATE:SetAltitude(altitude) to define the altitude. +-- @param #number speed Speed in knots. Default is 70% of max speed. -- @param #number wpnumber Waypoint number. Default at the end. --- @param #number speed Speed in knots. Default 11 kts. -- @param #boolean updateroute If true or nil, call UpdateRoute. If false, no call. -- @return #number Waypoint index. -function NAVYGROUP:AddWaypoint(coordinate, wpnumber, speed, updateroute) +function NAVYGROUP:AddWaypoint(coordinate, speed, wpnumber, updateroute) -- Waypoint number. Default is at the end. wpnumber=wpnumber or #self.waypoints+1 @@ -555,7 +692,7 @@ function NAVYGROUP:AddWaypoint(coordinate, wpnumber, speed, updateroute) end -- Speed in knots. - speed=speed or 11 + speed=speed or self.speedmax*0.7 -- Speed at waypoint. local speedkmh=UTILS.KnotsToKmph(speed) @@ -635,7 +772,7 @@ function NAVYGROUP:_InitGroup() self.speedmax=self.group:GetSpeedMax() -- Cruise speed: 70% of max speed but within limit. - self.speedCruise=self.speedmax*0.7 + --self.speedCruise=self.speedmax*0.7 -- Group ammo. --self.ammo=self:GetAmmoTot() @@ -696,7 +833,7 @@ function NAVYGROUP:_InitGroup() local text=string.format("Initialized Navy Group %s:\n", self.groupname) text=text..string.format("AC type = %s\n", self.actype) text=text..string.format("Speed max = %.1f Knots\n", UTILS.KmphToKnots(self.speedmax)) - text=text..string.format("Speed cruise = %.1f Knots\n", UTILS.KmphToKnots(self.speedCruise)) + --text=text..string.format("Speed cruise = %.1f Knots\n", UTILS.KmphToKnots(self.speedCruise)) text=text..string.format("Elements = %d\n", #self.elements) text=text..string.format("Waypoints = %d\n", #self.waypoints) text=text..string.format("Radio = %.1f MHz %s %s\n", self.radioFreq, UTILS.GetModulationName(self.radioModu), tostring(self.radioOn)) @@ -839,8 +976,17 @@ function NAVYGROUP:_CheckGroupDone(delay) self:ScheduleOnce(delay, NAVYGROUP._CheckGroupDone, self) else - if not self.passedfinalwp then + if #self.waypoints>1 and self.passedfinalwp then + + local speed=self.speedCruise and UTILS.KmphToKnots(self.speedCruise) or UTILS.KmphToKnots(self.speedmax*0.7) + + -- Start route at first waypoint. + self:__UpdateRoute(-1, 1, speed) + + elseif not self.passedfinalwp then + self:UpdateRoute() + end end @@ -849,6 +995,67 @@ function NAVYGROUP:_CheckGroupDone(delay) end +--- Check queued turns into wind. +-- @param #NAVYGROUP self +function NAVYGROUP:_CheckTurnsIntoWind() + + -- Get current abs time. + local time=timer.getAbsTime() + local Cnow=UTILS.SecondsToClock(time) + + -- Debug output: + local text=string.format(self.lid.."Recovery time windows:") + + -- Handle case with no recoveries. + if #self.Qintowind==0 then + text=text.." none!" + end + + -- Sort windows wrt to start time. + table.sort(self.Qintowind, function(a, b) return a.Tstart=recovery.Tstart and time