diff --git a/Moose Development/Moose/Core/Settings.lua b/Moose Development/Moose/Core/Settings.lua index a61a23ebe..0f371ab55 100644 --- a/Moose Development/Moose/Core/Settings.lua +++ b/Moose Development/Moose/Core/Settings.lua @@ -621,7 +621,7 @@ do -- SETTINGS local text="Messages and Reports" if self.MenuShort then - text="Messages" + text="Messages & Reports" end local MessagesMenu = MENU_GROUP:New( MenuGroup, text, SettingsMenu ):SetTime( MenuTime ) @@ -848,9 +848,9 @@ do -- SETTINGS local text="Messages and Reports" if self.MenuShort then - text="Messages" + text="Messages & Reports" end - local MessagesMenu = MENU_GROUP:New( PlayerGroup, "Messages and Reports", PlayerMenu ) + 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 ) diff --git a/Moose Development/Moose/Ops/FlightControl.lua b/Moose Development/Moose/Ops/FlightControl.lua index 0c9a81303..f4acb35d3 100644 --- a/Moose Development/Moose/Ops/FlightControl.lua +++ b/Moose Development/Moose/Ops/FlightControl.lua @@ -1,4 +1,4 @@ ---- **ATC** - (R2.5) - Manage recovery of aircraft at airdromes and FARPS. +--- **OPS** - (R2.5) - Manage recovery of aircraft at airdromes and FARPS. -- -- -- @@ -9,8 +9,8 @@ -- === -- -- ### Author: **funkyfranky** --- @module ATC.FlightControl --- @image ATC_FlightControl.png +-- @module OPS.FlightControl +-- @image OPS_FlightControl.png --- FLIGHTCONTROL class. @@ -156,6 +156,9 @@ function FLIGHTCONTROL:New(airbasename) BASE:TraceLevel(1) --self.dTstatus=0.1 end + + -- Add to data base. + _DATABASE:AddFlightControl(self) return self end @@ -458,36 +461,6 @@ function FLIGHTCONTROL:OnEventTakeoff(EventData) return end - -- Get airbase name. - local airbasename=tostring(airbase:GetName()) - - -- Check if landed at this airbase. - if airbasename==self.airbasename then - - -- AI always lands ==> remove unit from flight group and queues. - local flight=self:_ElementTookOff(unit) - - if flight then - - self:T(self.lid..string.format("Flight element %s took off.", EventData.IniUnitName)) - - -- Check if everybody is in the air. - local all=true - for _,_elem in pairs(flight.elements) do - local element=_elem --#FLIGHTCONTROL.FlightElement - if not element.tookoff then - all=false - end - end - - -- Remove flight. - if all then - self:T(self.lid..string.format("Flight group %s took off.", flight.groupname)) - self:_RemoveFlightFromQueue(self.Qtakeoff, flight) - end - - end - end end --- Event handler for event engine startup. @@ -506,17 +479,7 @@ function FLIGHTCONTROL:OnEventEngineStartup(EventData) if not unit then return end - - -- Get flight element. - local element, idx, flight=self:_GetFlightElement(EventData.IniUnitName) - - if element then - local parkingspot=self:_GetElementParkingSpot(element) - if parkingspot then - self:_AddFlightToTakeoffQueue(flight) - end - end - + end --- Event handler for event engine shutdown. @@ -541,22 +504,12 @@ end --- Event handler for event crash. -- @param #FLIGHTCONTROL self -- @param Core.Event#EVENTDATA EventData -function FLIGHTCONTROL:OnEventEngineCrash(EventData) +function FLIGHTCONTROL:OnEventCrash(EventData) self:F3({EvendData=EventData}) self:T2(self.lid..string.format("CRASH: unit = %s", tostring(EventData.IniUnitName))) self:T2(self.lid..string.format("CRASH: group = %s", tostring(EventData.IniGroupName))) - - -- Unit that took off. - local unit=EventData.IniUnit - - -- Nil check for unit. - if not unit then - return - end - - self:_RemoveFlightElement(EventData.IniUnitName) - + end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -617,9 +570,7 @@ function FLIGHTCONTROL:_CheckParking() for _,_element in pairs(_flight.elements) do local element=_element --Ops.FlightGroup#FLIGHTGROUP.Element - local unit=element.unit - - if unit and unit:IsAlive() then + if element.unit and element.unit:IsAlive() then -- Distance to parking spot. local dist=element.unit:GetCoordinate():Get3DDistance(spot.position) @@ -632,7 +583,6 @@ function FLIGHTCONTROL:_CheckParking() else self:E(self.lid..string.format("ERROR: Element %s is not alive any more!", element.unitname)) - self:_RemoveFlightElement(element.unitname) end end @@ -651,19 +601,9 @@ function FLIGHTCONTROL:_GetNextWaitingFight() for _,_flight in pairs(self.Qwaiting) do local flight=_flight --Ops.FlightGroup#FLIGHTGROUP - -- Current stack. - local stack=flight.flag + -- TODO: Sort by fuel, holding time. - -- Total marshal time in seconds. - local Tmarshal=timer.getAbsTime()-flight.time - - -- Min time in marshal stack. - local TmarshalMin=3*60 --Three minutes for human players. - - -- Check if conditions are right. - if flight.holding~=nil and Tmarshal>=TmarshalMin then - return flight - end + return flight end @@ -687,12 +627,12 @@ function FLIGHTCONTROL:_PrintQueue(queue, name) local flight=_flight --Ops.FlightGroup#FLIGHTGROUP -- Gather info. - local clock=UTILS.SecondsToClock(timer.getAbsTime()-flight.time) + local clock="0:0" --UTILS.SecondsToClock(timer.getAbsTime()-flight.time) local fuel=flight.group:GetFuelMin()*100 local ai=tostring(flight.ai) - local actype=flight.actype + local actype=tostring(flight.actype) local holding=tostring(flight.holding) - local nunits=flight.nunits + local nunits=flight.nunits or 1 -- Main info. text=text..string.format("\n[%d] %s (%s*%d): ai=%s, timestamp=%s, fuel=%d, inzone=%s, holding=%s", @@ -700,11 +640,11 @@ function FLIGHTCONTROL:_PrintQueue(queue, name) -- Elements info. for j,_element in pairs(flight.elements) do - local element=_element --#FLIGHTCONTROL.FlightElement + local element=_element --Ops.FlightGroup#FLIGHTGROUP.Element local life=element.unit:GetLife() local life0=element.unit:GetLife0() - text=text..string.format("\n (%d) %s (%s): ai=%s, parking=%s, recovered=%s, tookoff=%s, airborne=%s life=%.1f/%.1f", - j, element.onboard, element.unitname, tostring(element.ai), tostring(element.parking), tostring(element.recovered), tostring(element.tookoff), tostring(element.unit:InAir()), life, life0) + text=text..string.format("\n (%d) %s (%s): ai=%s, status=%s,, airborne=%s life=%.1f/%.1f", + j, tostring(element.modex), element.name, tostring(element.ai) ,tostring(element.status), tostring(element.unit:InAir()), life, life0) end end end @@ -727,12 +667,13 @@ function FLIGHTCONTROL:_RemoveFlightFromQueue(queue, flight) -- Check for name. if qflight.groupname==flight.groupname then - self:T(self.lid..string.format("Removing flight group %s from queue.", flight.groupname)) + self:I(self.lid..string.format("Removing flight group %s from queue.", flight.groupname)) table.remove(queue, i) return true, i end end + self:I(self.lid..string.format("Could NOT remove flight group %s from queue.", flight.groupname)) return false, nil end @@ -822,7 +763,7 @@ end -- Flight and Element Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Create a new flight group. Usually when a flight appears in the CCA. +--- Create a new flight group. -- @param #FLIGHTCONTROL self -- @param Wrapper.Group#GROUP group Aircraft group. -- @return Ops.FlightGroup#FLIGHTGROUP Flight group. @@ -834,7 +775,20 @@ function FLIGHTCONTROL:_CreateFlightGroup(group) end -- Debug info. - self:T(self.lid..string.format("Creating new flight for group %s of aircraft type %s.", group:GetName(), group:GetTypeName())) + self:I(self.lid..string.format("Creating new flight for group %s of aircraft type %s.", group:GetName(), group:GetTypeName())) + + -- Get flightgroup from data base. + local flight=_DATABASE:GetFlightGroup(group:GetName()) + + -- If it does not exist yet, create one. + if not flight then + flight=FLIGHTGROUP:New(group:GetName()) + flight:__Start(1) + end + + flight:SetFlightControl(self) + + --[[ -- New flight. local flight={} --Ops.FlightGroup#FLIGHTGROUP @@ -887,6 +841,8 @@ function FLIGHTCONTROL:_CreateFlightGroup(group) flight.onboard=self:_GetOnboardNumberPlayer(group) end + ]] + -- Add to known flights. table.insert(self.flights, flight) @@ -958,24 +914,6 @@ function FLIGHTCONTROL:_GetFlightElement(unitname) return nil, nil, nil end ---- Remove element from flight. --- @param #FLIGHTCONTROL self --- @param #string unitname Name of the unit. --- @return #boolean If true, element could be removed or nil otherwise. -function FLIGHTCONTROL:_RemoveFlightElement(unitname) - - -- Get table index. - local element,idx, flight=self:_GetFlightElement(unitname) - - if idx then - table.remove(flight.elements, idx) - return true - else - self:T("WARNING: Flight element could not be removed from flight group. Index=nil!") - return nil - end -end - --- Get parking spot of flight element. -- @param #FLIGHTCONTROL self -- @param Ops.FlightGroup#FLIGHTGROUP.Element element Element of flight group. @@ -1040,12 +978,12 @@ function FLIGHTCONTROL:_CheckInbound() local unit=_unit --Wrapper.Unit#UNIT -- Necessary conditions to be met: - local airborne=unit:IsAir() + local aircraft=unit:IsAir() local inzone=unit:IsInZone(self.zoneAirbase) local friendly=self:GetCoalition()==unit:GetCoalition() - -- Check if this an aircraft and that it is airborne and closing in. - if airborne and inzone and friendly then + -- Check if this an aircraft and that it is inside the airbase zone and friendly. + if aircraft and inzone and friendly then local group=unit:GetGroup() local groupname=group:GetName() @@ -1058,7 +996,7 @@ function FLIGHTCONTROL:_CheckInbound() end end - -- Find new flights that are inside CCA. + -- Find new flights that are inside the airbase zone. for groupname,_group in pairs(insideZone) do local group=_group --Wrapper.Group#GROUP self:_CreateFlightGroup(group) @@ -1067,47 +1005,16 @@ function FLIGHTCONTROL:_CheckInbound() for _,_flight in pairs(self.flights) do local flight=_flight --Ops.FlightGroup#FLIGHTGROUP - local inzone=flight.group:IsCompletelyInZone(self.zoneAirbase) - - if inzone and not flight.inzone then - flight.inzone=true - flight.dist0=flight.group:GetCoordinate():Get2DDistance(self:GetCoordinate()) - end - - -- Set currently in zone or not. - flight.inzone=inzone - end - - - for _,_flight in pairs(self.flights) do - local flight=_flight --Ops.FlightGroup#FLIGHTGROUP - - --TODO: Check if aircraft has a landing waypoint for this airbase. - - -- Get distance to carrier. - local dist=flight.group:GetCoordinate():Get2DDistance(self:GetCoordinate()) - - -- Close in distance. Is >0 if AC comes closer wrt to first detected distance d0. - local closein=flight.dist0-dist - - -- TODO refine for current case. - - -- Flight closed in. - if closein>UTILS.NMToMeters(5) and flight.group:IsAirborne(true) then - - -- Debug info. - self:T3(self.lid..string.format("AI flight group %s closed in by %.1f NM", flight.groupname, UTILS.MetersToNM(closein))) - - -- Send AI to orbit outside 10 NM zone and wait until the next Marshal stack is available. + if flight and flight.destination and flight.destination:GetName()==self.airbasename then + + -- Send AI to orbit outside 10 NM zone and wait until the next Marshal stack is available. if not (self:_InQueue(self.Qwaiting, flight.group) or self:_InQueue(self.Qlanding, flight.group)) then self:_WaitAI(flight, 1, true) - end - - -- Break the loop to not have all flights at once! Spams the message screen. - break + end + end - - end + + end end @@ -1124,6 +1031,17 @@ function FLIGHTCONTROL:_WaitAI(flight, stack, respawn) -- Add AI flight to waiting queue. table.insert(self.Qwaiting, flight) + -- Holding point. + local holding=self:_GetHoldingpoint(flight) + local altitude=holding.pos0.y + local angels=UTILS.MetersToFeet(altitude)/1000 + + flight:Hold(holding, altitude) + + if true then + return + end + -- Flight group name. local group=flight.group local groupname=flight.groupname @@ -1154,10 +1072,7 @@ function FLIGHTCONTROL:_WaitAI(flight, stack, respawn) -- Task function when arriving at the holding zone. This will set flight.holding=true. local TaskHolding=flight.group:TaskFunction("FLIGHTCONTROL._ReachedHoldingZone", self, flight) - -- Holding point. - local holding=self:_GetHoldingpoint(flight) - local altitude=holding.pos0.y - local angels=UTILS.MetersToFeet(altitude)/1000 + -- Set orbit task. local TaskOrbit=group:TaskOrbit(holding.pos0, altitude, speedOrbitMps, holding.pos1) diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 9d09f66a5..fcf10186e 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -40,6 +40,7 @@ -- @field #boolean fuelcriticalrtb RTB on critical fuel switch. -- @field Ops.Squadron#SQUADRON squadron The squadron the flight group belongs to. -- @field Ops.FlightControl#FLIGHTCONTROL flightcontrol The flightcontrol handling this group. +-- @field Core.UserFlag#USERFLAG flaghold Flag for holding. -- @extends Core.Fsm#FSM --- Be surprised! @@ -81,6 +82,7 @@ FLIGHTGROUP = { fuelcriticalrtb = false, squadron = nil, flightcontrol = nil, + flaghold = nil, } @@ -90,7 +92,8 @@ FLIGHTGROUP = { -- @field #string SPAWNED Element was spawned into the world. -- @field #string PARKING Element is parking after spawned on ramp. -- @field #string TAXIING Element is taxiing after engine startup. --- @field #string AIRBORNE Element is airborne after take off. +-- @field #string TAKEOFF Element took of after takeoff event. +-- @field #string AIRBORNE Element is airborne. Either after takeoff or after air start. -- @field #string LANDED Element landed and is taxiing to its parking spot. -- @field #string ARRIVED Element arrived at its parking spot and shut down its engines. -- @field #string DEAD Element is dead after it crashed, pilot ejected or pilot dead events. @@ -99,6 +102,7 @@ FLIGHTGROUP.ElementStatus={ SPAWNED="spawned", PARKING="parking", TAXIING="taxiing", + TAKEOFF="takeoff", AIRBORNE="airborne", LANDED="landed", ARRIVED="arrived", @@ -176,7 +180,7 @@ FLIGHTGROUP.TaskType={ --- FLIGHTGROUP class version. -- @field #string version -FLIGHTGROUP.version="0.0.9" +FLIGHTGROUP.version="0.1.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -225,11 +229,11 @@ function FLIGHTGROUP:New(groupname) -- Add FSM transitions. - -- From State --> Event --> To State - self:AddTransition("Stopped", "Start", "Running") -- Start FSM. + -- From State --> Event --> To State + self:AddTransition("Stopped", "Start", "Running") -- Start FSM. - self:AddTransition("*", "FlightStatus", "*") -- FLIGHTGROUP status update. - self:AddTransition("*", "QueueUpdate", "*") -- Update task queue. + self:AddTransition("*", "FlightStatus", "*") -- FLIGHTGROUP status update. + self:AddTransition("*", "QueueUpdate", "*") -- Update task queue. self:AddTransition("*", "DetectedUnit", "*") -- Add a newly detected unit to the detected units set. @@ -260,6 +264,7 @@ function FLIGHTGROUP:New(groupname) self:AddTransition("*", "ElementSpawned", "*") -- An element was spawned. self:AddTransition("*", "ElementParking", "*") -- An element was spawned. self:AddTransition("*", "ElementTaxiing", "*") -- An element spooled up the engines. + self:AddTransition("*", "ElementTakeoff", "*") -- An element took off. self:AddTransition("*", "ElementAirborne", "*") -- An element took off. self:AddTransition("*", "ElementLanded", "*") -- An element landed. self:AddTransition("*", "ElementArrived", "*") -- An element arrived. @@ -270,6 +275,7 @@ function FLIGHTGROUP:New(groupname) self:AddTransition("*", "FlightSpawned", "Spawned") -- The whole flight group was spawned. self:AddTransition("*", "FlightParking", "Parking") -- The whole flight group is parking. self:AddTransition("*", "FlightTaxiing", "Taxiing") -- The whole flight group is taxiing. + self:AddTransition("*", "FlightTakeoff", "Airborne") -- The whole flight group is airborne. self:AddTransition("*", "FlightAirborne", "Airborne") -- The whole flight group is airborne. self:AddTransition("*", "FlightLanded", "Landed") -- The whole flight group has landed. self:AddTransition("*", "FlightArrived", "Arrived") -- The whole flight group has arrived. @@ -319,6 +325,9 @@ function FLIGHTGROUP:New(groupname) self.taskcurrent=0 self.taskcounter=0 + self.flaghold=USERFLAG:New(string.format("%s_FlagHold", self.groupname)) + self.flaghold:Set(0) + -- Add to data base. _DATABASE:AddFlightGroup(self) @@ -413,6 +422,7 @@ end -- @param Ops.Squadron#SQUADRON squadron The squadron object. -- @return #FLIGHTGROUP self function FLIGHTGROUP:SetSquadron(squadron) + self:I(self.sid..string.format("Add flight to SQUADRON %s", squadron.squadronname)) self.squadron=squadron return self end @@ -424,6 +434,23 @@ function FLIGHTGROUP:GetSquadron() return self.squadron end +--- Set the FLIGHTCONTROL controlling this flight group. +-- @param #FLIGHTGROUP self +-- @param Ops.FlightControl#FLIGHTCONTROL flightcontrol The FLIGHTCONTROL object. +-- @return #FLIGHTGROUP self +function FLIGHTGROUP:SetFlightControl(flightcontrol) + self:I(self.sid..string.format("Setting FLIGHTCONTROL to airbase %s", flightcontrol.airbasename)) + self.flightcontrol=flightcontrol + return self +end + +--- Get the FLIGHTCONTROL controlling this flight group. +-- @param #FLIGHTGROUP self +-- @return Ops.FlightControl#FLIGHTCONTROL The FLIGHTCONTROL object. +function FLIGHTGROUP:GetFlightControl() + return self.flightcontrol +end + --- Set low fuel threshold. Triggers event "FuelLow" and calls event function "OnAfterFuelLow". -- @param #FLIGHTGROUP self -- @param #number threshold Fuel threshold in percent. Default 25 %. @@ -608,7 +635,8 @@ function FLIGHTGROUP:onafterFlightStatus(From, Event, To) self:_CheckDetectedUnits() -- Short info. - local text=string.format("Flight status %s [%d/%d]. Task=%d/%d. Waypoint=%d/%d. Detected=%d", fsmstate, #self.elements, #self.elements, self.taskcurrent, #self.taskqueue, self.currentwp or 0, #self.waypoints or 0, self.detectedunits:Count()) + local text=string.format("Flight status %s [%d/%d]. Task=%d/%d. Waypoint=%d/%d. Detected=%d. FC=%s. Destination=%s", + fsmstate, #self.elements, #self.elements, self.taskcurrent, #self.taskqueue, self.currentwp or 0, #self.waypoints or 0, self.detectedunits:Count(), self.flightcontrol and self.flightcontrol.airbasename or "none", self.destination and self.destination:GetName() or "unknown") self:I(self.sid..text) -- Element status. @@ -803,7 +831,7 @@ function FLIGHTGROUP:OnEventTakeOff(EventData) if element then self:I(self.sid..string.format("EVENT: Element %s took off ==> airborne", element.name)) - self:ElementAirborne(element) + self:ElementTakeoff(element, EventData.Place) end end @@ -833,7 +861,7 @@ function FLIGHTGROUP:OnEventLanding(EventData) if element then self:I(self.sid..string.format("EVENT: Element %s landed at %s ==> landed", element.name, airbasename)) - self:ElementLanded(element, airbase) + self:ElementLanded(element, airbase) end end @@ -875,7 +903,7 @@ function FLIGHTGROUP:OnEventEngineShutdown(EventData) else self:I(self.sid..string.format("Element %s shut down engines (bug is not alive) ==> dead", element.name)) - self:ElementDead(element) + --self:ElementDead(element) end @@ -959,6 +987,20 @@ function FLIGHTGROUP:onafterElementTaxiing(From, Event, To, Element) self:_UpdateStatus(Element, FLIGHTGROUP.ElementStatus.TAXIING) end +--- On after "ElementTakeoff" event. +-- @param #FLIGHTGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #FLIGHTGROUP.Element Element The flight group element. +-- @param Wrapper.Airbase#AIRBASE airbase The airbase if applicable or nil. +function FLIGHTGROUP:onafterElementTakeoff(From, Event, To, Element, airbase) + self:I(self.sid..string.format("Element airborne %s.", Element.name)) + + -- Set element status. + self:_UpdateStatus(Element, FLIGHTGROUP.ElementStatus.TAKEOFF, airbase) +end + --- On after "ElementAirborne" event. -- @param #FLIGHTGROUP self -- @param #string From From state. @@ -978,11 +1020,12 @@ end -- @param #string Event Event. -- @param #string To To state. -- @param #FLIGHTGROUP.Element Element The flight group element. -function FLIGHTGROUP:onafterElementLanded(From, Event, To, Element) +-- @param Wrapper.Airbase#AIRBASE airbase The airbase if applicable or nil. +function FLIGHTGROUP:onafterElementLanded(From, Event, To, Element, airbase) self:I(self.sid..string.format("Element landed %s.", Element.name)) -- Set element status. - self:_UpdateStatus(Element, FLIGHTGROUP.ElementStatus.LANDED) + self:_UpdateStatus(Element, FLIGHTGROUP.ElementStatus.LANDED, airbase) end --- On after "ElementArrived" event. @@ -998,6 +1041,19 @@ function FLIGHTGROUP:onafterElementArrived(From, Event, To, Element) self:_UpdateStatus(Element, FLIGHTGROUP.ElementStatus.ARRIVED) end +--- On after "ElementDead" event. +-- @param #FLIGHTGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #FLIGHTGROUP.Element Element The flight group element. +function FLIGHTGROUP:onafterElementDead(From, Event, To, Element) + self:I(self.sid..string.format("Element dead %s.", Element.name)) + + -- Set element status. + self:_UpdateStatus(Element, FLIGHTGROUP.ElementStatus.DEAD) +end + --- On after "FlightSpawned" event. Sets the template, initializes the waypoints. -- @param #FLIGHTGROUP self @@ -1005,8 +1061,7 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param Wrapper.Group#GROUP Group The group object. -function FLIGHTGROUP:onafterFlightSpawned(From, Event, To, Group) +function FLIGHTGROUP:onafterFlightSpawned(From, Event, To) -- Get template of group. self.template=self.group:GetTemplate() @@ -1014,8 +1069,48 @@ function FLIGHTGROUP:onafterFlightSpawned(From, Event, To, Group) -- Init waypoints. self:_InitWaypoints() + -- Update route. + self:_UpdateRoute(1) + -- Route flight group but now with passing waypoint tasks. - self.group:Route(self.waypoints) + --self.group:Route(self.waypoints) +end + +--- On after "FlightTaxiing" event. +-- @param #FLIGHTGROUP self +-- @param Wrapper.Group#GROUP Group Flight group. +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function FLIGHTGROUP:onafterFlightTaxiing(From, Event, To) + self:I(self.sid..string.format("Flight is taxxing %s.", self.groupname)) + + -- TODO: need a better check for the airbase. + local airbase=self.group:GetCoordinate():GetClosestAirbase(nil, self.group:GetCoalition()) + + if self.flightcontrol and airbase and self.flightcontrol.airbasename==airbase:GetName() then + self.flightcontrol:_AddFlightToTakeoffQueue(self) + end + +end + +--- On after "FlightTakeoff" event. +-- @param #FLIGHTGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Wrapper.Airbase#AIRBASE airbase The airbase the flight landed. +function FLIGHTGROUP:onafterFlightTakeoff(From, Event, To, airbase) + self:I(self.sid..string.format("Flight takeoff %s at %s.", self.groupname, airbase and airbase:GetName() or "unknown airbase")) + + if self.flightcontrol and airbase and self.flightcontrol.airbasename==airbase:GetName() then + self.flightcontrol:_RemoveFlightFromQueue(self.flightcontrol.Qtakeoff, self) + end + + -- Update + self:FlightAirborne(airbase) + --self:__QueueUpdate(-1) + end --- On after "FlightAirborne" event. @@ -1025,7 +1120,7 @@ end -- @param #string To To state. -- @param Wrapper.Airbase#AIRBASE airbase The airbase the flight landed. function FLIGHTGROUP:onafterFlightAirborne(From, Event, To, airbase) - self:I(self.sid..string.format("Flight airborne %s.", self.groupname)) + self:I(self.sid..string.format("Flight airborne %s at %s.", self.groupname,airbase and airbase:GetName() or "unknown airbase")) if self.flightcontrol and airbase and self.flightcontrol.airbasename==airbase:GetName() then self.flightcontrol:_RemoveFlightFromQueue(self.flightcontrol.Qtakeoff, self) @@ -1043,7 +1138,7 @@ end -- @param #string To To state. -- @param Wrapper.Airbase#AIRBASE airbase The airbase the flight landed. function FLIGHTGROUP:onafterFlightLanded(From, Event, To, airbase) - self:I(self.sid..string.format("Flight landed %s.", self.groupname)) + self:I(self.sid..string.format("Flight landed %s at %s.", self.groupname, airbase and airbase:GetName() or "unknown airbase")) if self.flightcontrol and airbase and self.flightcontrol.airbasename==airbase:GetName() then self.flightcontrol:_RemoveFlightFromQueue(self.flightcontrol.Qlanding, self) @@ -1158,21 +1253,88 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param Core.Point#COORDINATE Coord Coordinate where to orbit. --- @param #number Altitude Altitude in meters. --- @param #number Speed Speed in km/h. -function FLIGHTGROUP:onafterHold(From, Event, To, Coord, Altitude, Speed) +-- @param Wrapper.Airbase#AIRBASE airbase The airbase to hold at. +-- @param #number SpeedTo Speed used for travelling from current position to holding point in knots. +-- @param #number SpeedHold Holding speed in knots. +function FLIGHTGROUP:onafterHold(From, Event, To, airbase, SpeedTo, SpeedHold) -- Debug message. - local text=string.format("Flight group set to hold at altitude %d m and speed %.1f km/h", Altitude, Speed) + local text=string.format("Flight group set to hold at airbase %s", airbase:GetName()) MESSAGE:New(text, 10, "DEBUG"):ToAllIf(self.Debug) self:I(self.sid..text) + self.group:ClearTasks() + + local Coord=airbase:GetZone():GetRandomCoordinate():SetAltitude(UTILS.FeetToMeters(6000)) + if self.flightcontrol then + Coord=self.flightcontrol:_GetHoldingpoint(self) + end - local TaskOrbit=self.group:TaskOrbit(Coord, Altitude, UTILS.KmphToMps(Speed)) + self.flaghold:Set(333) + + local TaskArrived=self.group:TaskFunction("FLIGHTGROUP._ReachedHolding", self) - self.group:SetTask(TaskOrbit) + + local wp=airbase:GetCoordinate():WaypointAir(nil , COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.FlyoverPoint,SpeedTo, true , nil, {}, "Holding Point") + + local Task2Hold=self.group:TaskRoute(wp) + + local TaskOrbit=self.group:TaskOrbit(Coord) + + local TaskStop=self.group:TaskCondition(nil, self.flaghold.UserFlagName, 1) + --local TaskStop=self.group:TaskCondition(nil, nil, nil, nil, 30*60) + + local wp=airbase:GetCoordinate():WaypointAirLanding(SpeedHold, airbase, {}, "Landing") + + local TaskLand=self.group:TaskRoute(wp) + + local TaskControlled=self.group:TaskControlled(TaskOrbit, TaskStop) + + local TaskCombo=self.group:TaskCombo({Task2Hold, TaskArrived, TaskControlled, TaskLand}) + local TaskCombo=self.group:TaskCombo({Task2Hold, TaskArrived, TaskLand}) + + --self.group:SetTask(TaskCombo) + + local coordAB=airbase:GetCoordinate() + + local wp={} + wp[1]=self.group:GetCoordinate():WaypointAir(nil, COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.FlyoverPoint,SpeedTo, true , nil, {}, "Current Pos") + wp[2]=coordAB:WaypointAir(nil, COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.FlyoverPoint,SpeedTo, true , nil, {TaskArrived, TaskControlled}, "Holding Point") + wp[3]=coordAB:Translate(50000, 270):WaypointAir(nil, COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.FlyoverPoint,SpeedTo, true , nil, {}, "Test 50 km") + wp[4]=coordAB:WaypointAirLanding(SpeedHold, airbase, {}, "Landing") + + local respawn=true + + if respawn then + + -- Get group template. + local Template=self.group:GetTemplate() + + -- Set route points. + Template.route.points=wp + + MESSAGE:New("Respawning group"):ToAll() + + + --Respawn the group. + self.group=self.group:Respawn(Template, nil) + + end + + self.group:Route(wp, 0.1) + + --self:RouteRTB(RTBAirbase,Speed,Altitude,TaskOverhead) +end + +--- On after TaskPause event. +-- @param Wrapper.Group#GROUP group +-- @param #FLIGHTGROUP flightgroup +function FLIGHTGROUP._ReachedHolding(group, flightgroup) + env.info(string.format("FF group %s reached holding point", group:GetName())) + group:GetCoordinate():MarkToAll("Holding Point Reached") + --TODO: add flight to FC waiting queue. + flightgroup.flaghold:Set(666) end --- On after TaskExecute event. @@ -1187,6 +1349,11 @@ function FLIGHTGROUP:onafterTaskExecute(From, Event, To, Task) local text=string.format("Task %s ID=%d execute.", Task.description, Task.id) MESSAGE:New(text, 10, "DEBUG"):ToAllIf(self.Debug) self:I(self.sid..text) + + -- Cancel current task if there is any. + if self.taskcurrent>0 then + self:TaskCancel() + end -- Set current task. self.taskcurrent=Task.id @@ -1300,20 +1467,6 @@ function FLIGHTGROUP:onafterDetectedUnit(From, Event, To, Unit) self.detectedunits:AddUnit(Unit) end - ---- On after "ElementDead" event. --- @param #FLIGHTGROUP self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param #FLIGHTGROUP.Element Element The flight group element. -function FLIGHTGROUP:onafterElementDead(From, Event, To, Element) - self:I(self.sid..string.format("Element dead %s.", Element.name)) - - -- Set element status. - self:_UpdateStatus(Element, FLIGHTGROUP.ElementStatus.DEAD) -end - ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Task functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1428,7 +1581,8 @@ end -- @param Wrapper.Airbase#AIRBASE RTBAirbase -- @param #number Speed Speed in km/h. -- @param #number Altitude Altitude in meters. -function FLIGHTGROUP:RouteRTB(RTBAirbase, Speed, Altitude) +-- @param #table TaskOverhead Task to execute when reaching overhead waypoint. +function FLIGHTGROUP:RouteRTB(RTBAirbase, Speed, Altitude, TaskOverhead) -- If speed is not given take 80% of max speed. local Speed=Speed or self.group:GetSpeedMax()*0.8 @@ -1444,7 +1598,7 @@ function FLIGHTGROUP:RouteRTB(RTBAirbase, Speed, Altitude) local PointFrom=coord:WaypointAirTurningPoint(nil, Speed, {}, "Current") -- Overhead pass. - local PointOverhead=RTBAirbase:GetCoordinate():SetAltitude(Altitude or coord.y):WaypointAirTurningPoint(nil, Speed, {}, "Overhead Pass") + local PointOverhead=RTBAirbase:GetCoordinate():SetAltitude(Altitude or coord.y):WaypointAirTurningPoint(nil, Speed, TaskOverhead, "Overhead Pass") -- Landing waypoint. local PointLanding=RTBAirbase:GetCoordinate():SetAltitude(20):WaypointAirLanding(Speed, RTBAirbase, {}, "Landing") @@ -1458,12 +1612,29 @@ function FLIGHTGROUP:RouteRTB(RTBAirbase, Speed, Altitude) -- Set route points. Template.route.points=Points - -- Respawn the group. - self.group=self.group:Respawn(Template, true) + --self.group=self.group:Respawn(Template, true) + + local TaskRTB=self.group:TaskRoute(Points) + + --[[ + self.taskcounter=self.taskcounter+1 + + local task={} --#FLIGHTGROUP.Task + task.dcstask=TaskRTB + task.description="RTB" + task.id=self.taskcounter + task.type=FLIGHTGROUP.TaskType.SCHEDULED + task.prio=0 + task.time=timer.getAbsTime() + ]] + + self:AddTask("RTB", TaskRTB, 0) + + self:TaskCancel() -- Route the group or this will not work. - self.group:Route(Points, 1) + --self.group:Route(Points, 1) end @@ -1568,6 +1739,33 @@ function FLIGHTGROUP:GetElementByName(unitname) return nil end +--- Check if a unit is and element of the flightgroup. +-- @param #FLIGHTGROUP self +-- @return Wrapper.Airbase#AIRBASE Final destination airbase or #nil. +function FLIGHTGROUP:GetHomebaseFromWaypoints() + + local wp=self:GetWaypoint(1) + + if wp then + + if wp and wp.action and wp.action==COORDINATE.WaypointAction.Landing then + + -- Get airbase ID depending on airbase category. + local airbaseID=wp.airdromeId or wp.helipadId + + local airbase=AIRBASE:FindByID(airbaseID) + + return airbase + end + + --TODO: Handle case where e.g. only one WP but that is not landing. + --TODO: Probably other cases need to be taken care of. + + end + + return nil +end + --- Check if a unit is and element of the flightgroup. -- @param #FLIGHTGROUP self -- @return Wrapper.Airbase#AIRBASE Final destination airbase or #nil. @@ -1683,9 +1881,20 @@ function FLIGHTGROUP:_UpdateRoute(n) table.insert(wp, self.waypoints[i]) end + -- Get destination airbase from waypoints. + self.homebase=self:GetHomebaseFromWaypoints() or self.homebase + -- Get destination airbase from waypoints. self.destination=self:GetDestinationFromWaypoints() or self.destination + if self.destination and #wp>0 then + local wpoverhead=self.destination:GetCoordinate():WaypointAir(nil, COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.FlyoverPoint, 500, false, nil, {}, "Destination Overhead") + self:I(self.sid..string.format("Adding overhead waypoint as #%d", #wp)) + table.insert(wp, #wp, wpoverhead) + end + + self:I(self.sid..string.format("Updating route: #WP=%d homebase=%s destination=%s", #wp, self.homebase and self.homebase:GetName() or "unknown", self.destination and self.destination:GetName() or "unknown")) + if #wp>0 then -- Route group to all defined waypoints remaining. @@ -1912,7 +2121,7 @@ function FLIGHTGROUP:_AllSimilarStatus(status) return false end - elseif status==FLIGHTGROUP.ElementStatus.AIRBORNE then + elseif status==FLIGHTGROUP.ElementStatus.TAKEOFF then -- Element AIRBORNE: Check that the other are not stil SPAWNED, PARKING or TAXIING if element.status~=status and @@ -1922,6 +2131,17 @@ function FLIGHTGROUP:_AllSimilarStatus(status) return false end + elseif status==FLIGHTGROUP.ElementStatus.AIRBORNE then + + -- Element AIRBORNE: Check that the other are not stil SPAWNED, PARKING or TAXIING + if element.status~=status and + element.status==FLIGHTGROUP.ElementStatus.SPAWNED or + element.status==FLIGHTGROUP.ElementStatus.PARKING or + element.status==FLIGHTGROUP.ElementStatus.TAXIING or + element.status==FLIGHTGROUP.ElementStatus.TAKEOFF then + return false + end + elseif status==FLIGHTGROUP.ElementStatus.LANDED then -- Element LANDED: check that the others are not stil AIRBORNE @@ -1959,7 +2179,8 @@ end -- @param #FLIGHTGROUP self -- @param #FLIGHTGROUP.Element element Element. -- @param #string newstatus New status of element -function FLIGHTGROUP:_UpdateStatus(element, newstatus) +-- @param Wrapper.Airbase#AIRBASE airbase Airbase if applicable. +function FLIGHTGROUP:_UpdateStatus(element, newstatus, airbase) -- Old status. local oldstatus=element.status @@ -1975,7 +2196,7 @@ function FLIGHTGROUP:_UpdateStatus(element, newstatus) --- if self:_AllSimilarStatus(newstatus) then - self:FlightSpawned(group) + self:FlightSpawned() end --[[ @@ -1992,7 +2213,7 @@ function FLIGHTGROUP:_UpdateStatus(element, newstatus) --- if self:_AllSimilarStatus(newstatus) then - self:FlightParking(group) + self:FlightParking() end elseif newstatus==FLIGHTGROUP.ElementStatus.TAXIING then @@ -2001,7 +2222,17 @@ function FLIGHTGROUP:_UpdateStatus(element, newstatus) --- if self:_AllSimilarStatus(newstatus) then - self:FlightTaxiing(group) + self:FlightTaxiing() + end + + elseif newstatus==FLIGHTGROUP.ElementStatus.TAKEOFF then + --- + -- TAKEOFF + --- + + if self:_AllSimilarStatus(newstatus) then + self:FlightTakeoff(airbase) + --TODO: also trigger Airborne() ? end elseif newstatus==FLIGHTGROUP.ElementStatus.AIRBORNE then @@ -2012,14 +2243,14 @@ function FLIGHTGROUP:_UpdateStatus(element, newstatus) if self:_AllSimilarStatus(newstatus) then if self:IsTaxiing() then - self:FlightAirborne(group) + self:FlightAirborne() elseif self:IsParking() then --self:FlightTaxiing(group) - self:FlightAirborne(group) + self:FlightAirborne() elseif self:IsSpawned() then --self:FlightParking(group) --self:FlightTaxiing(group) - self:FlightAirborne(group) + self:FlightAirborne() end end @@ -2041,10 +2272,10 @@ function FLIGHTGROUP:_UpdateStatus(element, newstatus) if self:_AllSimilarStatus(newstatus) then if self:IsLanded() then - self:FlightArrived(group) + self:FlightArrived() elseif self:IsAirborne() then - self:FlightLanded(group) - self:FlightArrived(group) + self:FlightLanded() + self:FlightArrived() end end @@ -2055,7 +2286,7 @@ function FLIGHTGROUP:_UpdateStatus(element, newstatus) --- if self:_AllSimilarStatus(newstatus) then - self:FlightDead(group) + self:FlightDead() end end diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index b461b2ce8..064acb53b 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -433,7 +433,7 @@ UTILS.tostringLL = function( lat, lon, acc, DMS) secFrmtStr = '%02d' else local width = 3 + acc -- 01.310 - that's a width of 6, for example. Acc is limited to 2 for DMS! - secFrmtStr = '%0' .. width .. '.' .. math.min(acc, 2) .. 'f' + secFrmtStr = '%0' .. width .. '.' .. acc .. 'f' end -- 024° 23' 12"N or 024° 23' 12.03"N diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index b837181b9..762f7358f 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -442,12 +442,12 @@ end --- Return a condition section for a controlled task. -- @param #CONTROLLABLE self --- @param DCS#Time time --- @param #string userFlag --- @param #boolean userFlagValue --- @param #string condition --- @param DCS#Time duration --- @param #number lastWayPoint +-- @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 #string condition Lua string. +-- @param DCS#Time duration Duration in seconds. +-- @param #number lastWayPoint Last waypoint. -- return DCS#Task function CONTROLLABLE:TaskCondition( time, userFlag, userFlagValue, condition, duration, lastWayPoint ) self:F2( { time, userFlag, userFlagValue, condition, duration, lastWayPoint } ) @@ -1079,8 +1079,8 @@ end --- (AIR) Orbit at a position with at a given altitude and speed. Optionally, a race track pattern can be specified. -- @param #CONTROLLABLE self -- @param Core.Point#COORDINATE Coord Coordinate at which the CONTROLLABLE orbits. --- @param #number Altitude Altitude in meters of the orbit pattern. --- @param #number Speed Speed [m/s] flying the orbit pattern. +-- @param #number Altitude Altitude in meters of the orbit pattern. Default y component of Coord. +-- @param #number Speed Speed [m/s] flying the orbit pattern. Default 250 knots. -- @param Core.Point#COORDINATE CoordRaceTrack (Optional) If this coordinate is specified, the CONTROLLABLE will fly a race-track pattern using this and the initial coordinate. -- @return #CONTROLLABLE self function CONTROLLABLE:TaskOrbit(Coord, Altitude, Speed, CoordRaceTrack) @@ -1100,8 +1100,8 @@ function CONTROLLABLE:TaskOrbit(Coord, Altitude, Speed, CoordRaceTrack) pattern = Pattern, point = P1, point2 = P2, - speed = Speed, - altitude = Altitude, + speed = Speed or UTILS.KnotsToMps(250), + altitude = Altitude or Coord.y, } } diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index 8c855be45..db9fb25db 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -1683,7 +1683,7 @@ function GROUP:Respawn( Template, Reset ) end end - else -- Reset=false or nil + elseif Reset==false then -- Reset=false or nil -- Loop over template units. for UnitID, TemplateUnitData in pairs( Template.units ) do @@ -1725,7 +1725,30 @@ function GROUP:Respawn( Template, Reset ) self:F( { UnitID, Template.units[UnitID], Template.units[UnitID] } ) end - end + else + + local units=self:GetUnits() + + -- Loop over template units. + for UnitID, Unit in pairs(Template.units) do + + for _,_unit in pairs(units) do + local unit=_unit --Wrapper.Unit#UNIT + + if unit:GetName()==Unit.name then + local coord=unit:GetCoordinate() + local heading=unit:GetHeading() + Unit.x=coord.x + Unit.z=coord.y + Unit.alt=coord.alt + Unit.heading=math.rad(heading) + Unit.psi=-Unit.heading + end + end + + end + + end end