diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index af0f55fd4..f85b815a1 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -5606,7 +5606,7 @@ function WAREHOUSE:_SpawnAssetAircraft(alias, asset, request, parking, uncontrol self:T2({airtemplate=template}) -- Create a flight group. - FLIGHTGROUP:New(template.name) + --FLIGHTGROUP:New(template.name) -- Spawn group. local group=_DATABASE:Spawn(template) --Wrapper.Group#GROUP @@ -5823,7 +5823,7 @@ function WAREHOUSE:_RouteAir(aircraft) local fc=_DATABASE:GetFlightControl(self.airbasename) if fc then - --local flightgroup=FLIGHTGROUP:New(aircraft:GetName()) + -- Nothing to do. FC will start the aircraft once it gets taxi clearance. else aircraft:StartUncontrolled(starttime) end diff --git a/Moose Development/Moose/Ops/ATIS.lua b/Moose Development/Moose/Ops/ATIS.lua index 38fcb3401..c74e57fc7 100644 --- a/Moose Development/Moose/Ops/ATIS.lua +++ b/Moose Development/Moose/Ops/ATIS.lua @@ -1655,6 +1655,13 @@ end -- Misc Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--- Get runway from user supplied magnetic heading. +-- @param #ATIS self +-- @return #string Runway magnetic heading divided by ten (and rounded). Eg, "13" for 130°. +function ATIS:GetActiveRunway() + +end + --- Get runway from user supplied magnetic heading. -- @param #ATIS self -- @param #number windfrom Wind direction (from) in degrees. diff --git a/Moose Development/Moose/Ops/FlightControl.lua b/Moose Development/Moose/Ops/FlightControl.lua index 36f7c10a5..0ee4386b4 100644 --- a/Moose Development/Moose/Ops/FlightControl.lua +++ b/Moose Development/Moose/Ops/FlightControl.lua @@ -33,6 +33,7 @@ -- @field #number activerwyno Number of active runway. -- @field #number atcfreq ATC radio frequency. -- @field Core.RadioQueue#RADIOQUEUE atcradio ATC radio queue. +-- @field #table playermenu Player Menu. -- @extends Core.Fsm#FSM --- Be surprised! @@ -66,6 +67,7 @@ FLIGHTCONTROL = { atcfreq = nil, atcradio = nil, atcradiounitname = nil, + playermenu = nil, } --- Holding point @@ -75,6 +77,11 @@ FLIGHTCONTROL = { -- @field #number angelsmin Smallest holding altitude in angels. -- @field #number angelsmax Largest holding alitude in angels. +--- Player menu data. +-- @type FLIGHTCONTROL.PlayerMenu +-- @field Core.Menu#MENU_GROUP root Root menu. +-- @field Core.Menu#MENU_GROUP_COMMAND RequestTaxi Request taxi. + --- Parking spot data. -- @type FLIGHTCONTROL.ParkingSpot -- @field #number index Parking index. @@ -95,7 +102,7 @@ FLIGHTCONTROL = { --- FlightControl class version. -- @field #string version -FLIGHTCONTROL.version="0.0.6" +FLIGHTCONTROL.version="0.0.7" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -122,14 +129,17 @@ function FLIGHTCONTROL:New(airbasename) -- Inherit everything from FSM class. local self=BASE:Inherit(self, FSM:New()) -- #FLIGHTCONTROL + -- Try to get the airbase. self.airbase=AIRBASE:FindByName(airbasename) + -- Check if the airbase exists. if not self.airbase then self:E(string.format("ERROR: Could not find airbase %s!", tostring(airbasename))) return nil end + -- Check if airbase is an airdrome. if self.airbase:GetAirbaseCategory()~=Airbase.Category.AIRDROME then - self:E(string.format("ERROR: Airbase %s is not an AIRDROMOE! Script does not handle FARPS or ships.", tostring(airbasename))) + self:E(string.format("ERROR: Airbase %s is not an AIRDROME! Script does not handle FARPS or ships.", tostring(airbasename))) return nil end @@ -154,6 +164,8 @@ function FLIGHTCONTROL:New(airbasename) -- Init parking spots. self:_InitParkingSpots() + self.playermenu={} + -- Start State. self:SetStartState("Stopped") @@ -168,7 +180,6 @@ function FLIGHTCONTROL:New(airbasename) BASE:TraceOnOff(true) BASE:TraceClass(self.ClassName) BASE:TraceLevel(1) - --self.dTstatus=0.1 end -- Add to data base. @@ -236,7 +247,7 @@ function FLIGHTCONTROL:onafterStart() self:HandleEvent(EVENTS.EngineShutdown) self:HandleEvent(EVENTS.Crash) - self.atcradio=RADIOQUEUE:New(self.atcfreq or 305) + self.atcradio=RADIOQUEUE:New(self.atcfreq or 305, nil, string.format("FC %s", self.airbasename)) self.atcradio:Start(1, 0.01) -- Init status updates. @@ -265,7 +276,7 @@ function FLIGHTCONTROL:onafterStatus() local nfree=self:_GetFreeParkingSpots() -- Info text. - local text=string.format("State %s - Active Runway %s - Free Parking %d", self:GetState(), runway.idx, nfree) + local text=string.format("State %s - Runway %s - Parking %d/%d - Qpark=%d Qtakeoff=%d Qland=%d Qhold=%d", self:GetState(), runway.idx, nfree, #self.parking, #self.Qparking, #self.Qtakeoff, #self.Qlanding, #self.Qwaiting) self:I(self.lid..text) -- Next status update in ~30 seconds. @@ -282,8 +293,15 @@ end function FLIGHTCONTROL:OnEventBirth(EventData) self:F3({EvendData=EventData}) - self:T2(self.lid..string.format("BIRTH: unit = %s", tostring(EventData.IniUnitName))) - self:T2(self.lid..string.format("BIRTH: group = %s", tostring(EventData.IniGroupName))) + self:I(self.lid..string.format("BIRTH: unit = %s", tostring(EventData.IniUnitName))) + self:I(self.lid..string.format("BIRTH: group = %s", tostring(EventData.IniGroupName))) + + if EventData and EventData.IniGroupName and EventData.Place and EventData.Place:GetName()==self.airbasename then + + -- We delay this, to have all elements of the group in the game. + self:ScheduleOnce(0.1, self._CreateFlightGroup, self, EventData.IniGroup) + + end end @@ -407,21 +425,34 @@ function FLIGHTCONTROL:_CheckQueues() -- Message. local text=string.format("Flight %s, you are cleared to land.", flight.groupname) MESSAGE:New(text, 5, "FLIGHTCONTROL"):ToAll() - - flight:FlightLanding() - self:_LandAI(flight) + + -- Give AI the landing signal. + -- TODO: Humans have to confirm via F10 menu. + if flight.ai then + flight:FlightLanding() + self:_LandAI(flight) + end else - - -- Message. - local text=string.format("Flight %s, you are cleared to taxi to runway.", flight.groupname) - MESSAGE:New(text, 5, "FLIGHTCONTROL"):ToAll() + + -- Check if flight is AI. Humans have to request taxi via F10 menu. + if flight.ai then - flight.group:StartUncontrolled() - - flight:_UpdateRoute(1) - - self:_RemoveFlightFromQueue(self.Qparking, flight, "parking") + -- Message. + -- TODO: Which runway! + local text=string.format("Flight %s, you are cleared to taxi to runway.", flight.groupname) + self:I(self.lid..text) + MESSAGE:New(text, 5, "FLIGHTCONTROL"):ToAll() + + flight.group:StartUncontrolled() + + flight:_UpdateRoute(1) + + self:_RemoveFlightFromQueue(self.Qparking, flight, "parking") + + self:_AddFlightToTakeoffQueue(flight) + + end end @@ -512,9 +543,9 @@ function FLIGHTCONTROL:_GetNextFightHolding() -- Return flight waiting longest. - table.sort(self.Qwaiting, _sortByTholding) + table.sort(self.Qwaiting, _sortByTholding) + return self.Qwaiting[1] - end @@ -886,6 +917,52 @@ end -- ATC Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Human Player Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Create player menu. +-- @param #FLIGHTCONTROL self +-- @param Ops.FlightGroup#FLIGHTGROUP flight Flight group. +function FLIGHTCONTROL:_CreatePlayerMenu(flight) + + local group=flight.group + local gid=group:GetID() + + if not self.playermenu[gid] then + self.playermenu[gid]={} + end + + local playermenu=self.playermenu[gid] --#FLIGHTCONTROL.PlayerMenu + + group:SmokeRed() + + playermenu.root=MENU_GROUP:New(group, "ATC") + playermenu.RequestTaxi=MENU_GROUP_COMMAND:New(group, "Request Taxi", playermenu.root, self._PlayerRequestTaxi, flight.groupname) + +end + +--- Create player menu. +-- @param #FLIGHTCONTROL self +-- @param #string groupname Name of the flight group. +function FLIGHTCONTROL:_PlayerRequestTaxi(groupname) + + MESSAGE:New("Request taxi to runway", 5):ToAll() + + MESSAGE:New("You are cleared to taxi", 5):ToAll() + + local flight=_DATABASE:GetFlightGroup(groupname) + + if flight then + + for _,_element in pairs(flight.elements) do + local element=_element --Ops.FlightGroup#FLIGHTGROUP.Element + flight:ElementTaxiing(element) + end + end + +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Flight and Element Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -898,6 +975,7 @@ function FLIGHTCONTROL:_CreateFlightGroup(group) -- Check if not already in flights if self:_InQueue(self.flights, group) then + self:E(self.lid..string.format("WARNING: Flight group %s does already exist!", group:GetName())) return end @@ -915,64 +993,17 @@ function FLIGHTCONTROL:_CreateFlightGroup(group) if flight.destination and flight.destination:GetName()==self.airbasename then flight:SetFlightControl(self) end - - --[[ - - -- New flight. - local flight={} --Ops.FlightGroup#FLIGHTGROUP - - -- Flight group name - local groupname=group:GetName() - local human, playername=self:_IsHuman(group) - - -- Queue table item. - flight.group=group - flight.groupname=group:GetName() - flight.nunits=#group:GetUnits() - flight.time=timer.getAbsTime() - flight.dist0=group:GetCoordinate():Get2DDistance(self:GetCoordinate()) - flight.flag=-100 - flight.ai=not human - flight.actype=group:GetTypeName() - flight.onboardnumbers=self:_GetOnboardNumbers(group) - flight.inzone=flight.group:IsCompletelyInZone(self.zoneAirbase) - flight.holding=nil - - -- Flight elements. - local text=string.format("Flight elements of group %s:", flight.groupname) - - flight.elements={} - local units=group:GetUnits() - for i,_unit in pairs(units) do - local unit=_unit --Wrapper.Unit#UNIT - local element={} --#FLIGHTCONTROL.FlightElement - element.unit=unit - element.unitname=unit:GetName() - element.onboard=flight.onboardnumbers[element.unitname] - element.sizemax=unit:GetObjectSize() - element.ai=not self:_IsHumanUnit(unit) - - -- Debug text. - text=text..string.format("\n[%d] %s onboard #%s, AI=%s", i, element.unitname, tostring(element.onboard), tostring(element.ai)) - - -- Add to table. - table.insert(flight.elements, element) - end - self:T(self.lid..text) - - -- Onboard. - if flight.ai then - local onboard=flight.onboardnumbers[flight.seclead] - flight.onboard=onboard - else - flight.onboard=self:_GetOnboardNumberPlayer(group) - end - - ]] - -- Add to known flights. table.insert(self.flights, flight) + + -- Create player menu. + if flight.ai then + self:E("FF AI no player menu") + else + self:E("FF Not purly AI ==> player menu") + self:_CreatePlayerMenu(flight) + end return flight end diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 1fbefd8ca..35335377e 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -342,7 +342,7 @@ function FLIGHTGROUP:New(groupname) -- Debug trace. - if false then + if true then self.Debug=true BASE:TraceOnOff(true) BASE:TraceClass(self.ClassName) @@ -362,7 +362,8 @@ function FLIGHTGROUP:New(groupname) _DATABASE:AddFlightGroup(self) -- Autostart. - self:__Start(0.1) + --self:__Start(0.1) + self:Start() return self end @@ -664,19 +665,25 @@ function FLIGHTGROUP:onafterStart(From, Event, To) if group and group:IsAlive() then -- Set group object. - env.info("FF setting self.group object") self.group=group + -- Get units of group. local units=group:GetUnits() + + -- Debug info. + self:I(self.sid..string.format("FF Found alive group %s at start with %d units", group:GetName(), #units)) - for _,_unit in pairs(units) do - local unit=_unit --Wrapper.Unit#UNIT + + -- Add elemets. + for _,unit in pairs(units) do local element=self:AddElementByName(unit:GetName()) - - -- Trigger Spawned event. - self:ElementSpawned(element) - end + + -- Trigger spawned event for all elements. + for _,element in pairs(self.elements) do + self:ElementSpawned(element) + end + end -- Handle events: @@ -866,9 +873,9 @@ function FLIGHTGROUP:OnEventBirth(EventData) element=self:AddElementByName(unitname) end - self:T3(self.sid..string.format("EVENT: Element %s born ==> spawned", element.name)) - - self:ElementSpawned(element) + -- Set element to spawned state. + self:T(self.sid..string.format("EVENT: Element %s born ==> spawned", element.name)) + self:ElementSpawned(element) end @@ -890,9 +897,15 @@ function FLIGHTGROUP:OnEventEngineStartup(EventData) if element then if self:IsAirborne() or self:IsHolding() then + -- TODO: what? else - self:T3(self.sid..string.format("EVENT: Element %s started engines ==> taxiing", element.name)) - self:ElementTaxiing(element) + self:T(self.sid..string.format("EVENT: Element %s started engines ==> taxiing (if AI)", element.name)) + -- TODO: could be that this element is part of a human flight group. + -- Problem: when player starts hot, the AI does too and starts to taxi immidiately :( + -- when player starts cold, ? + if self.ai then + self:ElementTaxiing(element) + end end end @@ -1186,7 +1199,7 @@ end -- @param #string Event Event. -- @param #string To To state. function FLIGHTGROUP:onafterFlightSpawned(From, Event, To) - self:I("FF Flight group spawned!") + self:I(string.format("FF Flight group %s spawned!", tostring(self.groupname))) -- Get template of group. self.template=self.group:GetTemplate() @@ -1202,17 +1215,23 @@ function FLIGHTGROUP:onafterFlightSpawned(From, Event, To) self.ceiling=self.descriptors.Hmax - self.ai=self:_IsHuman(self.group) + self.ai=not self:_IsHuman(self.group) for _,_element in pairs(self.elements) do local element=_element --#FLIGHTGROUP.Element - element.ai=self:_IsHumanUnit(element.unit) + element.ai=self:_IsHumanUnit(element.unit) end -- Init waypoints. if not self.waypoints then self:InitWaypoints() end + + if not self.ai then + if self.flightcontrol then + --self.flightcontrol:_CreatePlayerMenu(self) + end + end end @@ -2045,6 +2064,9 @@ function FLIGHTGROUP:AddElementByName(unitname) element.ai=true end + local text=string.format("Adding element %s: status=%s, skill=%s, modex=%s, fuelmass=%.1f, category=%d, categoryname=%s, callsign=%s, ai=%s", + element.name, element.status, element.skill, element.modex, element.fuelmass, element.category, element.categoryname, element.callsign, tostring(element.ai)) + self:I(self.sid..text) -- Add element to table. table.insert(self.elements, element) @@ -2203,6 +2225,7 @@ end -- @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 @@ -2239,7 +2262,7 @@ function FLIGHTGROUP:_UpdateRoute(n) -- Get destination airbase from waypoints. self.destination=self:GetDestinationFromWaypoints() or self.destination - if self.destination and #wp>0 and _DATABASE:GetFlightControl(self.destination:GetName())then + if self.destination and #wp>0 and _DATABASE:GetFlightControl(self.destination:GetName()) then -- Task to hold. local TaskOverhead=self.group:TaskFunction("FLIGHTGROUP._DestinationOverhead", self, self.destination) @@ -2493,16 +2516,18 @@ function FLIGHTGROUP:_AllSimilarStatus(status) if status==FLIGHTGROUP.ElementStatus.SPAWNED then - -- Element SPAWNED: Nothing to check. - if element.status~=status then - + -- Element SPAWNED: Check that others are not still IN UTERO + if element.status~=status and + element.status==FLIGHTGROUP.ElementStatus.INUTERO then + return false end elseif status==FLIGHTGROUP.ElementStatus.PARKING then -- Element PARKING: Check that the other are not stil SPAWNED - if element.status~=status and - element.status==FLIGHTGROUP.ElementStatus.SPAWNED then + if element.status~=status or + (element.status==FLIGHTGROUP.ElementStatus.INUTERO or + element.status==FLIGHTGROUP.ElementStatus.SPAWNED) then return false end @@ -2510,7 +2535,8 @@ function FLIGHTGROUP:_AllSimilarStatus(status) -- Element TAXIING: Check that the other are not stil SPAWNED or PARKING if element.status~=status and - (element.status==FLIGHTGROUP.ElementStatus.SPAWNED or + (element.status==FLIGHTGROUP.ElementStatus.INUTERO or + element.status==FLIGHTGROUP.ElementStatus.SPAWNED or element.status==FLIGHTGROUP.ElementStatus.PARKING) then return false end @@ -2519,7 +2545,8 @@ function FLIGHTGROUP:_AllSimilarStatus(status) -- Element TAKEOFF: 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.INUTERO or + element.status==FLIGHTGROUP.ElementStatus.SPAWNED or element.status==FLIGHTGROUP.ElementStatus.PARKING or element.status==FLIGHTGROUP.ElementStatus.TAXIING) then self:T(self.sid..string.format("Status=%s, element %s status=%s ==> returning FALSE", status, element.name, element.status)) @@ -2530,7 +2557,8 @@ function FLIGHTGROUP:_AllSimilarStatus(status) -- Element AIRBORNE: Check that the other are not stil SPAWNED, PARKING, TAXIING or TAKEOFF if element.status~=status and - (element.status==FLIGHTGROUP.ElementStatus.SPAWNED or + (element.status==FLIGHTGROUP.ElementStatus.INUTERO or + element.status==FLIGHTGROUP.ElementStatus.SPAWNED or element.status==FLIGHTGROUP.ElementStatus.PARKING or element.status==FLIGHTGROUP.ElementStatus.TAXIING or element.status==FLIGHTGROUP.ElementStatus.TAKEOFF) then @@ -2564,7 +2592,7 @@ function FLIGHTGROUP:_AllSimilarStatus(status) end - self:T(self.sid..string.format("Status=%s ==> returning TRUE", status)) + self:T(self.sid..string.format("All similar status %s ==> returning TRUE", status)) return true end @@ -2582,8 +2610,6 @@ function FLIGHTGROUP:_UpdateStatus(element, newstatus, airbase) -- Update status of element. element.status=newstatus - local group=element.group - if newstatus==FLIGHTGROUP.ElementStatus.SPAWNED then --- -- SPAWNED