diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 34898751d..708d74281 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -59,6 +59,7 @@ -- @field #string livery Livery. -- @field Core.Set#SET_ZONE checkzones Set of zones. -- @field #table inzones Table of zones in which the group is currently in. +-- @field #boolean groupinitialized If true, group parameters were initialized. -- @extends Core.Fsm#FSM --- *To invent an airplane is nothing. To build one is something. To fly is everything.* -- Otto Lilienthal @@ -178,6 +179,7 @@ FLIGHTGROUP = { livery = nil, checkzones = nil, inzones = {}, + groupinitialized = nil, } @@ -867,20 +869,64 @@ function FLIGHTGROUP:IsDead() return self:Is("Dead") end ---- Check if flight low on fuel. +--- Check if flight is low on fuel. -- @param #FLIGHTGROUP self -- @return #boolean If true, flight is low on fuel. function FLIGHTGROUP:IsFuelLow() return self.fuellow end ---- Check if flight critical on fuel +--- Check if flight is critical on fuel. -- @param #FLIGHTGROUP self -- @return #boolean If true, flight is critical on fuel. function FLIGHTGROUP:IsFuelCritical() return self.fuelcritical end +--- Check if flight is alive. +-- @param #FLIGHTGROUP self +-- @return #boolean *true* if group is exists and is activated, *false* if group is exist but is NOT activated. *nil* otherwise, e.g. the GROUP object is *nil* or the group is not spawned yet. +function FLIGHTGROUP:IsAlive() + + if self.group then + return self.group:IsAlive() + end + + return nil +end + +--- Activate a *late activated* group. +-- @param #FLIGHTGROUP self +-- @param #number delay (Optional) Delay in seconds before the group is activated. Default is immediately. +-- @return #FLIGHTGROUP self +function FLIGHTGROUP:Activate(delay) + + if self:IsAlive()==false then + if delay then + self:ScheduleOnce(delay, FLIGHTGROUP.Activate, self) + else + self.group:Activate() + end + end + + return self +end + +--- Start an *uncontrolled* group. +-- @param #FLIGHTGROUP self +-- @param #number delay (Optional) Delay in seconds before the group is started. Default is immediately. +-- @return #FLIGHTGROUP self +function FLIGHTGROUP:StartUncontrolled(delay) + + if self:IsAlive() then + self.group:StartUncontrolled(delay) + else + self:E(self.lid.."ERROR: Could not start uncontrolled group as it is NOT alive (yet)!") + end + + return self +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Start & Status ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -903,32 +949,34 @@ function FLIGHTGROUP:onafterStart(From, Event, To) if group then -- Set group object. - self.group=group + self.group=group + + -- Get units of group. + local units=group:GetUnits() + + -- Add elemets. + for _,unit in pairs(units) do + local element=self:AddElementByName(unit:GetName()) + end + + if not self.groupinitialized then + self:_InitGroup() + end if group:IsAlive() then - - -- Get units of group. - local units=group:GetUnits() - + -- Debug info. - self:I(self.lid..string.format("FF Found alive group %s at start with %d units", group:GetName(), #units)) - - -- Init waypoints. - if not self.waypoints then - self:InitWaypoints() - end - - -- Add elemets. - for _,unit in pairs(units) do - local element=self:AddElementByName(unit:GetName()) - end - + self:I(self.lid..string.format("Found EXISTING and ALIVE group %s at start with %d/%d units/elements", group:GetName(), #units, #self.elements)) + -- Trigger spawned event for all elements. for _,element in pairs(self.elements) do -- Add a little delay or the OnAfterSpawned function is not even initialized and will not be called. self:__ElementSpawned(0.1, element) end - + + else + -- Debug info. + self:I(self.lid..string.format("Found EXISTING but LATE ACTIVATED group %s at start with %d/%d units/elements", group:GetName(), #units, #self.elements)) end end @@ -946,7 +994,7 @@ function FLIGHTGROUP:onafterStart(From, Event, To) -- Start the status monitoring. self:__CheckZone(-1) - --self:__FlightStatus(-1) + self:__FlightStatus(-1) end --- On after Start event. Starts the FLIGHTGROUP FSM and event handlers. @@ -971,6 +1019,7 @@ function FLIGHTGROUP:onafterStop(From, Event, To) _DATABASE.FLIGHTGROUPS[self.groupname]=nil + self:I(self.lid.."STOPPED! Unhandled events, cleared scheduler and removed from database.") end @@ -1037,7 +1086,7 @@ function FLIGHTGROUP:onafterFlightStatus(From, Event, To) end -- Check if element is not dead and we missed an event. - if life<0 and element.status~=FLIGHTGROUP.ElementStatus.DEAD then + if life<0 and element.status~=FLIGHTGROUP.ElementStatus.DEAD and element.status~=FLIGHTGROUP.ElementStatus.INUTERO then self:ElementDead(element) end @@ -1053,7 +1102,7 @@ function FLIGHTGROUP:onafterFlightStatus(From, Event, To) if #self.elements==0 then text=text.." none!" end - self:T(self.lid..text) + self:I(self.lid..text) -- Low fuel? if fuelmin Destination=%s", #self.waypoints, self.homebase and self.homebase:GetName() or "unknown", self.destination and self.destination:GetName() or "uknown")) + -- Update route. if #self.waypoints>0 then self:__UpdateRoute(-1) diff --git a/Moose Development/Moose/Ops/TankerGroup.lua b/Moose Development/Moose/Ops/TankerGroup.lua index e593d6526..f4f80747f 100644 --- a/Moose Development/Moose/Ops/TankerGroup.lua +++ b/Moose Development/Moose/Ops/TankerGroup.lua @@ -77,6 +77,8 @@ TANKERGROUP = { -- @field #string version TANKERGROUP.version="0.0.1" +_TANKERGROUPS={} + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -97,29 +99,31 @@ function TANKERGROUP:New(groupname) -- Inherit everything from TANKERGROUP class. local self=BASE:Inherit(self, FLIGHTGROUP:New(groupname)) -- #TANKERGROUP - - - -- Add FSM transitions. - -- From State --> Event --> To State - self:AddTransition("*", "OnStation", "Ready2Refuel") -- Tanker is on station and ready to refuel. - self:AddTransition("*", "MissionStart", "*") -- Tanker is on station and ready to refuel. - + self.missioncounter=0 self.lid=string.format("TANKERGROUP %s | ", groupname) - --[[ - BASE:TraceOn() - BASE:TraceLevel(3) - BASE:TraceClass(self.ClassName) - ]] - - env.info("FF NEW Tanker!") + if false then + BASE:TraceOn() + BASE:TraceLevel(3) + BASE:TraceClass(self.ClassName) + BASE:TraceClass("FLIGHTGROUP") + --BASE:TraceAll(true) + end + -- Add FSM transitions. + -- From State --> Event --> To State + self:AddTransition("*", "TankerState", "*") -- Tanker is on station and ready to refuel. + self:AddTransition("*", "OnStation", "Ready2Refuel") -- Tanker is on station and ready to refuel. + self:AddTransition("*", "MissionStart", "*") -- Tanker is on station and ready to refuel. -- Call status update. -- TODO: WARNING, if the call is delayed, it is NOT executed! - self:FlightStatus() + self:__TankerState(5) + + --_TANKERGROUPS[groupname]=self + table.insert(_TANKERGROUPS, self) return self end @@ -133,13 +137,36 @@ end -- @param Core.Zone#ZONE Zone The mission zone. Orbit is picked at a random location. -- @param #number Altitude Orbit altitude in feet. -- @param #number Distance Length of the race-track pattern leg in NM. --- @param #number Speed Orbit speed in knots. +-- @param #number Heading Heading of the race-track pattern in degrees. Default is 90, i.e. from West to East. +-- @param #number SpeedOrbit Orbit speed in knots. Default is 280 knots. +-- @param #string ClockStart Time the mission is started, e.g. "05:00" for 5 am. If specified as a #number, it will be relative (in seconds) to the current mission time. +-- @param #string ClockStop Time the mission is stopped, e.g. "13:00" for 1 pm. If mission could not be started at that time, it will be removed from the queue. If specified as a #number it will be relative (in seconds) to the current mission time. +-- @param #string Name Mission name. Default "Aerial Refueling #00X", where "#00X" is a running mission counter index starting at "#001". -- @return #TANKERGROUP.Mission The mission table. -function TANKERGROUP:AddMission(Zone, Altitude, Distance, Speed) +function TANKERGROUP:AddMission(Zone, Altitude, Distance, Heading, SpeedOrbit, ClockStart, ClockStop, Name) -- Increase mission counter. self.missioncounter=self.missioncounter+1 + -- Current mission time. + local Tnow=timer.getAbsTime() + + -- Set start time. Default in 5 sec. + local Tstart=Tnow+5 + if ClockStart and type(ClockStart)=="number" then + Tstart=Tnow+ClockStart + elseif ClockStart and type(ClockStart)=="string" then + Tstart=UTILS.ClockToSeconds(ClockStart) + end + + -- Set stop time. Default nil. + local Tstop=nil + if ClockStop and type(ClockStop)=="number" then + Tstop=Tnow+ClockStop + elseif ClockStop and type(ClockStop)=="string" then + Tstop=UTILS.ClockToSeconds(ClockStop) + end + -- Make mission table. local mission={} --#TANKERGROUP.Mission mission.zone=Zone @@ -147,14 +174,20 @@ function TANKERGROUP:AddMission(Zone, Altitude, Distance, Speed) mission.altitude=UTILS.FeetToMeters(Altitude or 10000) mission.distance=UTILS.NMToMeters(Distance or 25) mission.name="Aerial Refueling" - mission.speed=UTILS.KnotsToMps(Speed or 280) - mission.heading=270 + mission.speed=UTILS.KnotsToMps(SpeedOrbit or 280) + mission.heading=Heading or 270 + mission.Tadded=Tnow + mission.Tstart=Tstart + mission.Tstop=Tstop mission.tid=nil -- Add mission to queue. table.insert(self.Qmissions, mission) - self:I(self.lid..string.format("Added mission")) + local text=string.format("Added mission %s at zone %s. Starting at %s. Stopping at %s. Altitude=%d ft, Leg=%s NM, Heading=%03d, Speed=%d kts", + mission.zone:GetName(), mission.name, UTILS.SecondsToClock(mission.Tstart, true), mission.Tstop and UTILS.SecondsToClock(mission.Tstop, true) or "never", + UTILS.MetersToFeet(mission.altitude), UTILS.MetersToNM(mission.distance), mission.heading, UTILS.MpsToKnots(mission.speed)) + self:I(self.lid..text) return mission end @@ -168,17 +201,16 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -function TANKERGROUP:onafterFlightStatus(From, Event, To) +function TANKERGROUP:onafterTankerState(From, Event, To) -- First call flight status. - self:GetParent(self).onafterFlightStatus(self, From, Event, To) - - env.info("FF Tanker status!") - env.info(string.format("FF current wp=%s", tostring(self.currentwp))) + --self:GetParent(self).onafterFlightStatus(self, From, Event, To) -- FSM state. local fsmstate=self:GetState() + self:I(self.lid.."FF Tanker status "..fsmstate) + -- First check if group is alive? if self.group and self.group:IsAlive()==true and not self.currentmission then @@ -195,10 +227,12 @@ function TANKERGROUP:onafterFlightStatus(From, Event, To) -- Current mission name. local mymission=self.currentmission and self.currentmission.name or "N/A" + -- Current status. local text=string.format("Tanker Status %s: Mission=%s (%d)", fsmstate, mymission, #self.Qmissions) self:I(self.lid..text) - self:__FlightStatus(-30) + -- Nest status update in 30 sec. + self:__TankerState(0.5) end --- On after "MissionStart" event. @@ -209,27 +243,33 @@ end -- @param #TANKERGROUP.Mission Mission The mission table. function TANKERGROUP:onafterMissionStart(From, Event, To, Mission) - -- TODO: figure out in which state the AC is in. - -- Cound be: - -- * late activated - -- * late activated and uncontrolled - -- * parking cold - -- * parking hot - -- * on the runway - -- * in air + -- TODO: need to handle case that group is spawned at a later point in time! + -- Delay for route to mission. Group needs to be activated and controlled. + local delay=nil + + -- Check if group is spawned. if self:IsInUtero() then - elseif self:IsSpawned() then - - elseif self:IsParking() then + -- Activate group if it is late activated. + if self:IsLateActivated() then + self:Activate() + delay=1 + end + + -- Activate group if it is uncontrolled. + if self:IsUncontrolled() then + self:StartUncontrolled(5) + delay=6 + end end + -- Set current mission. self.currentmission=Mission - -- - self:RouteToMission(Mission) + -- Route flight to mission zone. + self:RouteToMission(Mission, delay) end @@ -256,18 +296,29 @@ end --- Route group to mission. Also sets the -- @param #TANKERGROUP self -- @param #TANKERGROUP.Mission mission The mission table. +-- @param #number delay Delay in seconds. -- @return #TANKERGROUP.Mission Next mission or *nil*. -function TANKERGROUP:RouteToMission(mission) +function TANKERGROUP:RouteToMission(mission, delay) - local Coordinate=mission.zone:GetRandomCoordinate():SetAltitude(mission.altitude, true) - - local CoordRaceTrack=Coordinate:Translate(mission.distance, mission.heading, true) + if delay and delay>0 then + -- Delay call. + self:ScheduleOnce(delay, TANKERGROUP.RouteToMission, self, mission) + else - self:AddWaypointAir(Coordinate, nil, self.speedmax*0.8) + -- Get random coordinate in mission zone. + local Coordinate=mission.zone:GetRandomCoordinate():SetAltitude(mission.altitude, true) + + -- Set second coordinate for race track pattern. + local CoordRaceTrack=Coordinate:Translate(mission.distance, mission.heading, true) - local taskorbit=self.group:TaskOrbit(Coordinate, mission.altitude, mission.speed, CoordRaceTrack) - - self:AddTaskWaypoint(taskorbit, 1, "Mission Refuel", 10, mission.duration) - + -- Add waypoint. + self:AddWaypointAir(Coordinate, nil, self.speedmax*0.8) + + -- Create task to orbit. + local taskorbit=self.group:TaskOrbit(Coordinate, mission.altitude, mission.speed, CoordRaceTrack) + + -- Add waypoint task. + self:AddTaskWaypoint(taskorbit, 1, "Mission Refuel", 10, mission.duration) + end end diff --git a/Moose Development/Moose/Wrapper/Unit.lua b/Moose Development/Moose/Wrapper/Unit.lua index d0bd12774..9be25e3f8 100644 --- a/Moose Development/Moose/Wrapper/Unit.lua +++ b/Moose Development/Moose/Wrapper/Unit.lua @@ -663,8 +663,7 @@ end --- Returns relative amount of fuel (from 0.0 to 1.0) the UNIT has in its internal tanks. If there are additional fuel tanks the value may be greater than 1.0. -- @param #UNIT self --- @return #number The relative amount of fuel (from 0.0 to 1.0). --- @return #nil The DCS Unit is not existing or alive. +-- @return #number The relative amount of fuel (from 0.0 to 1.0) or *nil* if the DCS Unit is not existing or alive. function UNIT:GetFuel() self:F3( self.UnitName )