From 6279f1920ed2d4e7b35b119c27558d598bd20d15 Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 20 May 2022 20:08:28 +0200 Subject: [PATCH] FC **FLIGHTCONTROL** - Improved menus - Added more menus - Improved radio messages overlap - Added options to limit number of fights taxiing and taking off --- Moose Development/Moose/Ops/FlightControl.lua | 951 +++++++++++++----- Moose Development/Moose/Ops/FlightGroup.lua | 151 ++- Moose Development/Moose/Ops/OpsGroup.lua | 8 +- Moose Development/Moose/Wrapper/Marker.lua | 10 + 4 files changed, 852 insertions(+), 268 deletions(-) diff --git a/Moose Development/Moose/Ops/FlightControl.lua b/Moose Development/Moose/Ops/FlightControl.lua index dcdb3388d..e66bf2a2f 100644 --- a/Moose Development/Moose/Ops/FlightControl.lua +++ b/Moose Development/Moose/Ops/FlightControl.lua @@ -36,14 +36,21 @@ -- @field #number activerwyno Number of active runway. -- @field #number frequency ATC radio frequency in MHz. -- @field #number modulation ATC radio modulation, *e.g.* `radio.modulation.AM`. --- @field #number Nlanding Max number of aircraft groups in the landing pattern. +-- @field #number NlandingTot Max number of aircraft groups in the landing pattern. +-- @field #number NlandingTakeoff Max number of groups taking off to allow landing clearance. +-- @field #number NtaxiTot Max number of aircraft groups taxiing to runway for takeoff. +-- @field #boolean NtaxiInbound Include inbound taxiing groups. +-- @field #number NtaxiLanding Max number of aircraft landing for groups taxiing to runway for takeoff. -- @field #number dTlanding Time interval in seconds between landing clearance. +-- @field #number Tlanding Time stamp (abs.) when last flight got landing clearance. -- @field #number Nparkingspots Total number of parking spots. -- @field Core.Spawn#SPAWN parkingGuard Parking guard spawner. -- @field #table holdingpoints Holding points. -- @field #number hpcounter Counter for holding zones. -- @field Sound.SRS#MSRS msrsTower Moose SRS wrapper. -- @field Sound.SRS#MSRS msrsPilot Moose SRS wrapper. +-- @field #number Tlastmessage Time stamp (abs.) of last radio transmission. +-- @field #number dTmessage Time interval between messages. -- @extends Core.Fsm#FSM --- **Ground Control**: Airliner X, Good news, you are clear to taxi to the active. @@ -60,7 +67,6 @@ -- ## Prerequisites -- -- * SRS is used for radio communications --- * -- -- ## Limitations -- @@ -70,6 +76,8 @@ -- * We have no control over the active runway or which runway is used by the AI if there are multiple. -- * Only one player/client per group as we can create menus only for a group and not for a specific unit. -- * Only FLIGHTGROUPS are controlled. This means some older classes, *e.g.* RAT are not supported (yet). +-- * So far only airdromes are handled, *i.e.* no FARPs or ships. +-- * Only fixed wing aircraft are handled until now, *i.e.* no helos. -- -- -- @field #FLIGHTCONTROL @@ -139,16 +147,16 @@ FLIGHTCONTROL = { -- @field #string READYTO Flight is ready for takeoff. -- @field #string TAKEOFF Flight is taking off. FLIGHTCONTROL.FlightStatus={ + PARKING="Parking", + READYTX="Ready To Taxi", + TAXIOUT="Taxi to runway", + READYTO="Ready For Takeoff", + TAKEOFF="Takeoff", INBOUND="Inbound", HOLDING="Holding", LANDING="Landing", TAXIINB="Taxi Inbound", ARRIVED="Arrived", - PARKING="Parking", - TAXIOUT="Taxi to runway", - READYTX="Ready To Taxi", - READYTO="Ready For Takeoff", - TAKEOFF="Takeoff", } --- Runway data. @@ -160,7 +168,7 @@ FLIGHTCONTROL.FlightStatus={ --- FlightControl class version. -- @field #string version -FLIGHTCONTROL.version="0.5.1" +FLIGHTCONTROL.version="0.5.2" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -168,14 +176,14 @@ FLIGHTCONTROL.version="0.5.1" -- TODO: Support airwings. Dont give clearance for Alert5 or if mission has not started. -- TODO: Switch to enable/disable AI messages. --- TODO: Improve TTS messages. --- TODO: Define holding zone. +-- TODO: Improve ATC TTS messages. -- TODO: Add helos. -- TODO: Talk me down option. -- TODO: ATIS option. --- TODO: ATC voice overs. -- TODO: Check runways and clean up. -- TODO: Accept and forbit parking spots. +-- TODO: Define holding zone. +-- DONE: Basic ATC voice overs. -- DONE: Add SRS TTS. -- DONE: Add parking guard. -- NOGO: Add FARPS? @@ -230,7 +238,8 @@ function FLIGHTCONTROL:New(AirbaseName, Frequency, Modulation, PathToSRS) self.alias=self.airbasename.." Tower" -- Defaults: - self:SetLandingMax() + self:SetLimitLanding(2, 0) + self:SetLimitTaxi(1, false, 0) self:SetLandingInterval() self:SetFrequency(Frequency, Modulation) @@ -243,6 +252,9 @@ function FLIGHTCONTROL:New(AirbaseName, Frequency, Modulation, PathToSRS) self.msrsPilot:SetGender("male") self.msrsPilot:SetCulture("en-US") self.msrsPilot:SetLabel("Pilot") + + -- Wait at least 10 seconds after last radio message before calling the next status update. + self.dTmessage=10 -- Init runways. self:_InitRunwayData() @@ -254,10 +266,45 @@ function FLIGHTCONTROL:New(AirbaseName, Frequency, Modulation, PathToSRS) -- From State --> Event --> To State self:AddTransition("Stopped", "Start", "Running") -- Start FSM. self:AddTransition("*", "Status", "*") -- Update status. + self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. -- Add to data base. _DATABASE:AddFlightControl(self) + + ------------------------ + --- Pseudo Functions --- + ------------------------ + + --- Triggers the FSM event "Start". + -- @function [parent=#FLIGHTCONTROL] Start + -- @param #FLIGHTCONTROL self + + --- Triggers the FSM event "Start" after a delay. + -- @function [parent=#FLIGHTCONTROL] __Start + -- @param #FLIGHTCONTROL self + -- @param #number delay Delay in seconds. + + + --- Triggers the FSM event "Stop". + -- @function [parent=#FLIGHTCONTROL] Stop + -- @param #FLIGHTCONTROL self + + --- Triggers the FSM event "Stop" after a delay. + -- @function [parent=#FLIGHTCONTROL] __Stop + -- @param #FLIGHTCONTROL self + -- @param #number delay Delay in seconds. + + + --- Triggers the FSM event "Status". + -- @function [parent=#FLIGHTCONTROL] Status + -- @param #FLIGHTCONTROL self + + --- Triggers the FSM event "Status" after a delay. + -- @function [parent=#FLIGHTCONTROL] __Status + -- @param #FLIGHTCONTROL self + -- @param #number delay Delay in seconds. + return self end @@ -279,12 +326,16 @@ end --- Set the number of aircraft groups, that are allowed to land simultaniously. +-- Note that this restricts AI and human players. -- @param #FLIGHTCONTROL self --- @param #number n Max number of aircraft landing simultaniously. Default 2. +-- @param #number Nlanding Max number of aircraft landing simultaniously. Default 2. +-- @param #number Ntakeoff Allowed number of aircraft taking off for groups to get landing clearance. Default 0. -- @return #FLIGHTCONTROL self -function FLIGHTCONTROL:SetLandingMax(n) +function FLIGHTCONTROL:SetLimitLanding(Nlanding, Ntakeoff) - self.Nlanding=n or 2 + self.NlandingTot=Nlanding or 2 + + self.NlandingTakeoff=Ntakeoff or 0 return self end @@ -301,6 +352,35 @@ function FLIGHTCONTROL:SetLandingInterval(dt) end +--- Set the number of **AI** aircraft groups, that are allowed to taxi simultaniously. +-- If the limit is reached, other AI groups not get taxi clearance to taxi to the runway. +-- +-- By default, this only counts the number of AI that taxi from their parking position to the runway. +-- You can also include inbound AI that taxi from the runway to their parking position. +-- This can be handy for problematic (usually smaller) airdromes, where there is only one taxiway inbound and outbound flights. +-- +-- By default, AI will not get cleared for taxiing if at least one other flight is currently landing. If this is an unproblematic airdrome, you can +-- also allow groups to taxi if planes are landing, *e.g.* if there are two separate runways. +-- +-- NOTE that human players are *not* restricted as they should behave better (hopefully) than the AI. +-- +-- @param #FLIGHTCONTROL self +-- @param #number Ntaxi Max number of groups allowed to taxi. Default 2. +-- @param #boolean IncludeInbound If `true`, the above +-- @param #number Nlanding Max number of landing flights. Default 0. +-- @return #FLIGHTCONTROL self +function FLIGHTCONTROL:SetLimitTaxi(Ntaxi, IncludeInbound, Nlanding) + + self.NtaxiTot=Ntaxi or 2 + + self.NtaxiInbound=IncludeInbound + + self.NtaxiLanding=Nlanding or 0 + + return self +end + + --- Set runway. This clears all auto generated runways. -- @param #FLIGHTCONTROL self -- @param #FLIGHTCONTROL.Runway Runway. @@ -337,36 +417,33 @@ function FLIGHTCONTROL:SetActiveRunwayNumber(no) return self end ---- Add holding zone. +--- Add a holding point. +-- This is a zone where the aircraft... -- @param #FLIGHTCONTROL self -- @param Core.Zone#ZONE ArrivalZone Zone where planes arrive. -- @param #number Heading Heading in degrees. -- @param #number Length Length in nautical miles. Default 15 NM. --- @param #number FlightlevelMin Min flight level. --- @param #number FlightlevelMax Max flight level. +-- @param #number FlightlevelMin Min flight level. Default 5. +-- @param #number FlightlevelMax Max flight level. Default 15. -- @return #FLIGHTCONTROL self function FLIGHTCONTROL:AddHoldingPoint(ArrivalZone, Heading, Length, FlightlevelMin, FlightlevelMax) - local hp={} --#FLIGHTCONTROL.HoldingPoint - - hp.arrivalzone=ArrivalZone + -- Get ZONE if passed as string. + if type(ArrivalZone)=="string" then + ArrivalZone=ZONE:New(ArrivalZone) + end + -- Increase counter. self.hpcounter=self.hpcounter+1 - + + local hp={} --#FLIGHTCONTROL.HoldingPoint + hp.arrivalzone=ArrivalZone hp.uid=self.hpcounter - hp.name=string.format("%s-%d", ArrivalZone:GetName(), hp.uid) - hp.pos0=ArrivalZone:GetCoordinate() - - Length=UTILS.NMToMeters(Length or 15) - - hp.pos1=hp.pos0:Translate(Length, Heading) - - hp.pos0:ArrowToAll(hp.pos1, nil, {1,0,0}, 1, {1,1,0}, 0.5, 2, true) - + hp.pos1=hp.pos0:Translate(UTILS.NMToMeters(Length or 15), Heading) hp.angelsmin=FlightlevelMin or 5 - hp.angelsmax=FlightlevelMax or 10 + hp.angelsmax=FlightlevelMax or 15 hp.stacks={} for i=hp.angelsmin, hp.angelsmax do @@ -381,8 +458,11 @@ function FLIGHTCONTROL:AddHoldingPoint(ArrivalZone, Heading, Length, Flightlevel table.insert(hp.stacks, stack) end + -- Add to table. table.insert(self.holdingpoints, hp) + -- Mark holding point. + hp.pos0:ArrowToAll(hp.pos1, nil, {1,0,0}, 1, {1,1,0}, 0.5, 2, true) ArrivalZone:DrawZone() return self @@ -399,8 +479,20 @@ function FLIGHTCONTROL:SetParkingGuard(TemplateGroupName) -- Need spawn with alias for multiple FCs. self.parkingGuard=SPAWN:NewWithAlias(TemplateGroupName, alias) - - --self.parkingGuard=SPAWNSTATIC:NewFromStatic("Parking Guard"):InitNamePrefix(alias) + + return self +end + +--- Set the parking guard static. This static is used to block (AI) aircraft from taxiing until they get clearance. +-- @param #FLIGHTCONTROL self +-- @param #string TemplateStaticName Name of the template static. +-- @return #FLIGHTCONTROL self +function FLIGHTCONTROL:SetParkingGuardStatic(TemplateStaticName) + + local alias=string.format("Parking Guard %s", self.airbasename) + + -- Need spawn with alias for multiple FCs. + self.parkingGuard=SPAWNSTATIC:NewFromStatic(TemplateStaticName):InitNamePrefix(alias) return self end @@ -447,10 +539,45 @@ function FLIGHTCONTROL:onafterStart() self:__Status(-1) end +--- Update status. +-- @param #FLIGHTCONTROL self +function FLIGHTCONTROL:onbeforeStatus() + + if self.Tlastmessage then + local Tnow=timer.getAbsTime() + + -- Time interval between last radio message. + local dT=Tnow-self.Tlastmessage + + if dT%d sec ago. Status update allowed", dT, self.dTmessage)) + end + end + + return true +end + --- Update status. -- @param #FLIGHTCONTROL self function FLIGHTCONTROL:onafterStatus() + -- Debug message. + self:I(self.lid.."Status update") + -- Check status of all registered flights. self:_CheckFlights() @@ -463,6 +590,7 @@ function FLIGHTCONTROL:onafterStatus() -- Get runway. local runway=self:GetActiveRunway() + -- Count flights. local Nflights= self:CountFlights() local NQparking=self:CountFlights(FLIGHTCONTROL.FlightStatus.PARKING) local NQreadytx=self:CountFlights(FLIGHTCONTROL.FlightStatus.READYTX) @@ -504,19 +632,18 @@ function FLIGHTCONTROL:onafterStatus() self:__Status(-30) end ---- Start FLIGHTCONTROL FSM. Handle events. +--- Stop FLIGHTCONTROL FSM. -- @param #FLIGHTCONTROL self function FLIGHTCONTROL:onafterStop() - -- Handle events. - self:HandleEvent(EVENTS.Birth) - self:HandleEvent(EVENTS.EngineStartup) - self:HandleEvent(EVENTS.Takeoff) - self:HandleEvent(EVENTS.Land) - self:HandleEvent(EVENTS.EngineShutdown) - self:HandleEvent(EVENTS.Crash) - - self.atcradio:Stop() + -- Unhandle events. + self:UnHandleEvent(EVENTS.Birth) + self:UnHandleEvent(EVENTS.EngineStartup) + self:UnHandleEvent(EVENTS.Takeoff) + self:UnHandleEvent(EVENTS.Land) + self:UnHandleEvent(EVENTS.EngineShutdown) + self:UnHandleEvent(EVENTS.Crash) + end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -686,8 +813,8 @@ function FLIGHTCONTROL:_CheckQueues() -- Holding flight -- -------------------- - -- No other flight is taking off and number of landing flights is below threshold. - if ntakeoff==0 and nlandingself.NtaxiLanding then + self:I(self.lid..string.format("AI flight %s [status=%s] NOT cleared for taxi/takeoff as %d>%d flight(s) landing", flight.groupname, status, nlanding, self.NtaxiLanding)) + return false + end + + -- Number of AI flights taxiing/takeoff. + local ntakeoff=self:CountFlights(FLIGHTCONTROL.FlightStatus.TAKEOFF, nil, true) + + local ninbound=0 + if self.NtaxiInbound then + ninbound=self:CountFlights(FLIGHTCONTROL.FlightStatus.TAXIINB, nil, true) + end + + if ntakeoff+ninbound>self.NtaxiTot then + self:I(self.lid..string.format("AI flight %s [status=%s] NOT cleared for taxi/takeoff as %s flight(s) taxi/takeoff", flight.groupname, status, ntakeoff)) + return false + end + + self:I(self.lid..string.format("AI flight %s [status=%s] cleared for taxi/takeoff", flight.groupname, status)) + return true + else + --- + -- Player + -- + -- We allow unlimited number of players to taxi to runway. + -- We do not allow takeoff if at least one flight is landing. + --- + + if status==FLIGHTCONTROL.FlightStatus.READYTO then + + if nlanding>0 then + -- Traffic landing. No permission to + self:I(self.lid..string.format("Player flight %s [status=%s] not cleared for taxi/takeoff as %s flight(s) landing", flight.groupname, status, nlanding)) + return false + end + + end + + self:I(self.lid..string.format("Player flight %s [status=%s] cleared for taxi/takeoff", flight.groupname, status)) + return true + end + + +end + +--- Check if a flight can get clearance for taxi/takeoff. +-- @param #FLIGHTCONTROL self +-- @param Ops.FlightGroup#FLIGHTGROUP flight Flight.. +-- @return #boolean If true, flight can. +function FLIGHTCONTROL:_CheckFlightLanding(flight) + + -- Number of groups landing. + local nlanding=self:CountFlights(FLIGHTCONTROL.FlightStatus.LANDING) + + -- Number of groups taking off. + local ntakeoff=self:CountFlights(FLIGHTCONTROL.FlightStatus.TAKEOFF, nil, true) + + -- Current status. + local status=self:GetFlightStatus(flight) + + if flight.isAi then + --- + -- AI + --- + + if ntakeoff==0 and nlanding60 then + return fg + end end -- Sort flights by low fuel. @@ -929,8 +1189,17 @@ function FLIGHTCONTROL:_GetNextFightHolding() -- Return flight waiting longest. table.sort(Qholding, _sortByTholding) + + -- First flight in line. + local fg=Qholding[1] --Ops.FlightGroup#FLIGHTGROUP + + -- Check holding time. + local T=fg:GetHoldingTime() + if T>60 then + return fg + end - return Qholding[1] + return nil end @@ -983,7 +1252,7 @@ function FLIGHTCONTROL:_GetNextFightParking() local text="Parking flights:" for i,_flight in pairs(Qparking) do local flight=_flight --Ops.FlightGroup#FLIGHTGROUP - text=text..string.format("\n[%d] %s %.1f", i, flight.groupname, flight.Tparking) + text=text..string.format("\n[%d] %s %.1f", i, flight.groupname, flight:GetParkingTime()) end self:I(self.lid..text) @@ -1039,12 +1308,16 @@ function FLIGHTCONTROL:_PrintQueue(queue, name) parking="X" end + -- Number of elements. + local nunits=flight:CountElements() - local nunits=flight.nunits or 1 + -- Status. + local state=flight:GetState() + local status=self:GetFlightStatus(flight) -- Main info. - text=text..string.format("\n[%d] %s (%s*%d): status=%s, ai=%s, fuel=%d, holding=%s, parking=%s", - i, flight.groupname, actype, nunits, flight:GetState(), ai, fuel, holding, parking) + text=text..string.format("\n[%d] %s (%s*%d): status=%s | %s, ai=%s, fuel=%d, holding=%s, parking=%s", + i, flight.groupname, actype, nunits, state, status, ai, fuel, holding, parking) -- Elements info. for j,_element in pairs(flight.elements) do @@ -1081,7 +1354,7 @@ function FLIGHTCONTROL:_RemoveFlightFromQueue(queue, flight, queuename) -- Check for name. if qflight.groupname==flight.groupname then - self:I(self.lid..string.format("Removing flight group %s from %s queue.", flight.groupname, queuename)) + self:I(self.lid..string.format("Removing flight group %s from %s queue", flight.groupname, queuename)) table.remove(queue, i) if not flight.isAI then @@ -1092,7 +1365,7 @@ function FLIGHTCONTROL:_RemoveFlightFromQueue(queue, flight, queuename) end end - self:I(self.lid..string.format("Could NOT remove flight group %s from %s queue.", flight.groupname, queuename)) + self:I(self.lid..string.format("Could NOT remove flight group %s from %s queue", flight.groupname, queuename)) return false, nil end @@ -1269,8 +1542,9 @@ function FLIGHTCONTROL:_InitParkingSpots() self.parking[spot.TerminalID]=spot spot.Marker=MARKER:New(spot.Coordinate, "Spot"):ReadOnly() - spot.Marker.tocoaliton=true - spot.Marker.coalition=self:GetCoalition() + spot.Marker:ToCoalition(self:GetCoalition()) + --spot.Marker.tocoaliton=true + --spot.Marker.coalition=self:GetCoalition() -- Check if spot is initially free or occupied. if spot.Free then @@ -1374,7 +1648,7 @@ function FLIGHTCONTROL:SetParkingOccupied(spot, unitname) end ---- Get free parking spots. +--- Update parking markers. -- @param #FLIGHTCONTROL self -- @param Wrapper.Airbase#AIRBASE.ParkingSpot spot The parking spot data table. function FLIGHTCONTROL:UpdateParkingMarker(spot) @@ -1493,11 +1767,11 @@ end --- Get closest parking spot. -- @param #FLIGHTCONTROL self --- @param Core.Point#COORDINATE coordinate Reference coordinate. --- @param #number terminaltype (Optional) Check only this terminal type. --- @param #boolean free (Optional) If true, check only free spots. +-- @param Core.Point#COORDINATE Coordinate Reference coordinate. +-- @param #number TerminalType (Optional) Check only this terminal type. +-- @param #boolean Status (Optional) Only consider spots that have this status. -- @return #FLIGHTCONTROL.ParkingSpot Closest parking spot. -function FLIGHTCONTROL:GetClosestParkingSpot(coordinate, terminaltype, free) +function FLIGHTCONTROL:GetClosestParkingSpot(Coordinate, TerminalType, Status) local distmin=math.huge local spotmin=nil @@ -1505,19 +1779,19 @@ function FLIGHTCONTROL:GetClosestParkingSpot(coordinate, terminaltype, free) for TerminalID, Spot in pairs(self.parking) do local spot=Spot --Wrapper.Airbase#AIRBASE.ParkingSpot - if (not free) or (free==true and not (self:IsParkingReserved(spot) or self:IsParkingOccupied(spot))) then - if terminaltype==nil or terminaltype==spot.TerminalType then + --env.info(self.lid..string.format("FF Spot %d: %s", spot.TerminalID, spot.Status)) + + if (Status==nil or Status==spot.Status) and AIRBASE._CheckTerminalType(spot.TerminalType, TerminalType) then - -- Get distance from coordinate to spot. - local dist=coordinate:Get2DDistance(spot.Coordinate) - - -- Check if distance is smaller. - if dist0 then + text=text..string.format("\n- Parking %d", NQparking) + end + if NQreadytx>0 then + text=text..string.format("\n- Ready to taxi %d", NQreadytx) + end + if NQtaxiout>0 then + text=text..string.format("\n- Taxi to runway %d", NQtaxiout) + end + if NQreadyto>0 then + text=text..string.format("\n- Ready for takeoff %d", NQreadyto) + end + if NQtakeoff>0 then + text=text..string.format("\n- Taking off %d", NQtakeoff) + end + if NQinbound>0 then + text=text..string.format("\n- Inbound %d", NQinbound) + end + if NQholding>0 then + text=text..string.format("\n- Holding pattern %d", NQholding) + end + if NQlanding>0 then + text=text..string.format("\n- Landing %d", NQlanding) + end + if NQtaxiinb>0 then + text=text..string.format("\n- Taxi to parking %d", NQtaxiinb) + end + if NQarrived>0 then + text=text..string.format("\n- Arrived at parking %d", NQarrived) + end + -- Message to flight - self:TextMessageToFlight(text, flight, 10, true) + self:TextMessageToFlight(text, flight, 15, true) else - MESSAGE:New(string.format("Cannot find flight group %s.", tostring(groupname)), 5):ToAll() + self:E(self.lid..string.format("Cannot find flight group %s.", tostring(groupname))) end end @@ -1931,7 +2271,7 @@ function FLIGHTCONTROL:_PlayerRequestInbound(groupname) self:TransmissionTower(text, flight, 15) -- Create player menu. - self:_CreatePlayerMenu(flight, flight.menu.atc) + flight:_UpdateMenu() else self:E(self.lid..string.format("WARNING: Could not get holding stack for flight %s", flight:GetName())) @@ -1956,7 +2296,7 @@ function FLIGHTCONTROL:_PlayerRequestInbound(groupname) end else - MESSAGE:New(string.format("Cannot find flight group %s.", tostring(groupname)), 5):ToAll() + self:E(self.lid..string.format("Cannot find flight group %s.", tostring(groupname))) end end @@ -2012,7 +2352,7 @@ function FLIGHTCONTROL:_PlayerAbortInbound(groupname) end else - MESSAGE:New(string.format("Cannot find flight group %s.", tostring(groupname)), 5):ToAll() + self:E(self.lid..string.format("Cannot find flight group %s.", tostring(groupname))) end end @@ -2044,7 +2384,7 @@ function FLIGHTCONTROL:_PlayerHolding(groupname) local dist=stack.pos0:Get2DDistance(Coordinate) - if dist<5000 or true then + if dist<5000 then -- Message to flight local text=string.format("Roger, you are added to the holding queue!") @@ -2081,8 +2421,9 @@ function FLIGHTCONTROL:_PlayerHolding(groupname) -- Message to flight self:TextMessageToFlight(text, flight, 10, true) end + else - --TODO: Error + self:E(self.lid..string.format("Cannot find flight group %s.", tostring(groupname))) end end @@ -2141,7 +2482,7 @@ function FLIGHTCONTROL:_PlayerAbortHolding(groupname) end else - MESSAGE:New(string.format("Cannot find flight group %s.", tostring(groupname)), 5):ToAll() + self:E(self.lid..string.format("Cannot find flight group %s.", tostring(groupname))) end end @@ -2192,7 +2533,8 @@ function FLIGHTCONTROL:_PlayerConfirmLanding(groupname) self:TransmissionTower(text, flight, 10) -- Create player menu. - self:_CreatePlayerMenu(flight, flight.menu.atc) + flight:_UpdateMenu() + --self:_CreatePlayerMenu(flight, flight.menu.atc) else @@ -2204,7 +2546,7 @@ function FLIGHTCONTROL:_PlayerConfirmLanding(groupname) end else - MESSAGE:New(string.format("Cannot find flight group %s.", tostring(groupname)), 5):ToAll() + self:E(self.lid..string.format("Cannot find flight group %s.", tostring(groupname))) end end @@ -2261,7 +2603,7 @@ function FLIGHTCONTROL:_PlayerAbortLanding(groupname) end else - MESSAGE:New(string.format("Cannot find flight group %s.", tostring(groupname)), 5):ToAll() + self:E(self.lid..string.format("Cannot find flight group %s.", tostring(groupname))) end end @@ -2284,7 +2626,7 @@ function FLIGHTCONTROL:_PlayerRequestTaxi(groupname) local callsign=flight:GetCallsignName() -- Pilot request for taxi. - local text=string.format("%s, %s, ready for departure. Request taxi to runway.", self.alias, callsign) + local text=string.format("%s, %s, request taxi to runway.", self.alias, callsign) self:TransmissionPilot(text, flight) if flight:IsParking() or flight:IsTaxiing() then @@ -2293,18 +2635,18 @@ function FLIGHTCONTROL:_PlayerRequestTaxi(groupname) local text=string.format("%s, %s, hold position until further notice.", callsign, self.alias) self:TransmissionTower(text, flight, 10) - -- Set flight status to "Ready for Take-off". + -- Set flight status to "Ready to Taxi". self:SetFlightStatus(flight, FLIGHTCONTROL.FlightStatus.READYTX) - self:_CreatePlayerMenu(flight, flight.menu.atc) - + -- Update menu. + flight:_UpdateMenu() + else MESSAGE:New(string.format("Negative, you must be PARKING to request TAXI!"), 5):ToAll() end else - -- Error message. - self:E(self.lid..string.format("Could not clear group %s for taxi!", groupname)) + self:E(self.lid..string.format("Cannot find flight group %s.", tostring(groupname))) end end @@ -2335,7 +2677,8 @@ function FLIGHTCONTROL:_PlayerAbortTaxi(groupname) -- Set flight status to "Ready for Take-off". self:SetFlightStatus(flight, FLIGHTCONTROL.FlightStatus.PARKING) - self:_CreatePlayerMenu(flight, flight.menu.atc) + -- Update menu. + flight:_UpdateMenu() elseif flight:IsTaxiing() then @@ -2343,18 +2686,18 @@ function FLIGHTCONTROL:_PlayerAbortTaxi(groupname) local text=string.format("%s, %s, roger, return to your parking position.", callsign, self.alias) self:TransmissionTower(text, flight, 10) - -- Set flight status to "Ready for Take-off". + -- Set flight status to "Taxi Inbound". self:SetFlightStatus(flight, FLIGHTCONTROL.FlightStatus.TAXIINB) - self:_CreatePlayerMenu(flight, flight.menu.atc) + -- Update menu. + flight:_UpdateMenu() else MESSAGE:New(string.format("Negative, you must be PARKING or TAXIING to abort TAXI!"), 5):ToAll() end else - -- Error message. - self:E(self.lid..string.format("Could not clear group %s for taxi!", groupname)) + self:E(self.lid..string.format("Cannot find flight group %s.", tostring(groupname))) end end @@ -2367,8 +2710,6 @@ end -- @param #FLIGHTCONTROL self -- @param #string groupname Name of the flight group. function FLIGHTCONTROL:_PlayerRequestTakeoff(groupname) - - MESSAGE:New("Request takeoff", 5):ToAll() local flight=_DATABASE:GetOpsGroup(groupname) --Ops.FlightGroup#FLIGHTGROUP @@ -2376,25 +2717,65 @@ function FLIGHTCONTROL:_PlayerRequestTakeoff(groupname) if flight:IsTaxiing() then - local Nlanding=self:CountFlights(FLIGHTCONTROL.FlightStatus.LANDING) - local Ntakeoff=self:CountFlights(FLIGHTCONTROL.FlightStatus.TAKEOFF) + -- Get callsign. + local callsign=flight:GetCallsignName() + + -- Pilot request for taxi. + local text=string.format("%s, %s, ready for departure. Request takeoff.", self.alias, callsign) + self:TransmissionPilot(text, flight) + -- Get number of flights landing. + local Nlanding=self:CountFlights(FLIGHTCONTROL.FlightStatus.LANDING) + + -- Get number of flights taking off. + local Ntakeoff=self:CountFlights(FLIGHTCONTROL.FlightStatus.TAKEOFF) + + --[[ + local text="" if Nlanding==0 and Ntakeoff==0 then - MESSAGE:New("You are cleared for takeoff as there is no one else landing or queueing for takeoff", 5):ToAll() + text="No current traffic. You are cleared for takeoff." self:SetFlightStatus(flight, FLIGHTCONTROL.FlightStatus.TAKEOFF) + elseif Nlanding>0 and Ntakeoff>0 then + text=string.format("Negative, we got %d flights inbound and %d outbound ahead of you. Hold position until futher notice.", Nlanding, Ntakeoff) + self:SetFlightStatus(flight, FLIGHTCONTROL.FlightStatus.READYTO) elseif Nlanding>0 then - MESSAGE:New("Negative ghostrider, other flights are currently landing. Talk to you soon.", 5):ToAll() + if Nlanding==1 then + text=string.format("Negative, we got %d flight inbound before it's your turn. Wait until futher notice.", Nlanding) + else + text=string.format("Negative, we got %d flights inbound. Wait until futher notice.", Nlanding) + end self:SetFlightStatus(flight, FLIGHTCONTROL.FlightStatus.READYTO) elseif Ntakeoff>0 then - MESSAGE:New("Negative ghostrider, other flights are ahead of you. Talk to you soon.", 5):ToAll() + text=string.format("Negative, %d flights ahead of you are waiting for takeoff. Talk to you soon.", Ntakeoff) self:SetFlightStatus(flight, FLIGHTCONTROL.FlightStatus.READYTO) end + ]] - self:_CreatePlayerMenu(flight, flight.menu.atc) + -- We only check for landing flights. + local text="" + if Nlanding==0 then + text="No current traffic. You are cleared for takeoff." + self:SetFlightStatus(flight, FLIGHTCONTROL.FlightStatus.TAKEOFF) + elseif Nlanding>0 then + if Nlanding==1 then + text=string.format("Negative, we got %d flight inbound before it's your turn. Hold position until futher notice.", Nlanding) + else + text=string.format("Negative, we got %d flights inbound. Hold positon until futher notice.", Nlanding) + end + end + + -- Message from tower. + self:TransmissionTower(text, flight, 10) + + -- Update menu. + flight:_UpdateMenu() else MESSAGE:New(string.format("Negative, you must request TAXI before you can request TAKEOFF!"), 5):ToAll() end + + else + self:E(self.lid..string.format("Cannot find flight group %s.", tostring(groupname))) end end @@ -2408,31 +2789,42 @@ function FLIGHTCONTROL:_PlayerAbortTakeoff(groupname) local flight=_DATABASE:GetOpsGroup(groupname) --Ops.FlightGroup#FLIGHTGROUP if flight then - - MESSAGE:New("Abort takeoff", 5):ToAll() -- Flight status. local status=self:GetFlightStatus(flight) - + + -- Check that we are taking off or ready for takeoff. if status==FLIGHTCONTROL.FlightStatus.TAKEOFF or status==FLIGHTCONTROL.FlightStatus.READYTO then + -- Get callsign. + local callsign=flight:GetCallsignName() + + -- Pilot request for taxi. + local text=string.format("%s, %s, abort takeoff.", self.alias, callsign) + self:TransmissionPilot(text, flight) + if flight:IsParking() then + text=string.format("%s, %s, affirm, remain on your parking position.", callsign, self.alias) self:SetFlightStatus(flight, FLIGHTCONTROL.FlightStatus.PARKING) elseif flight:IsTaxiing() then + text=string.format("%s, %s, roger, report whether you want to taxi back or takeoff later.", callsign, self.alias) self:SetFlightStatus(flight, FLIGHTCONTROL.FlightStatus.TAXIOUT) else env.info(self.lid.."ERROR") end - self:_CreatePlayerMenu(flight, flight.menu.atc) + -- Message from tower. + self:TransmissionTower(text, flight, 10) + -- Update menu. + flight:_UpdateMenu() else MESSAGE:New("Negative, You are NOT in the takeoff queue", 5):ToAll() end else - --TODO: Error message. + self:E(self.lid..string.format("Cannot find flight group %s.", tostring(groupname))) end end @@ -2450,36 +2842,65 @@ function FLIGHTCONTROL:_PlayerRequestParking(groupname) local flight=_DATABASE:GetOpsGroup(groupname) --Ops.FlightGroup#FLIGHTGROUP if flight then - - local group=flight:GetGroup() - local coord=flight:GetCoordinate() - - --TODO: terminal type for helos! - local spot=self:GetClosestParkingSpot(coord, nil, true) - - -- Get callsign. + + -- Get callsign. local callsign=flight:GetCallsignName() - -- Message text. - local text=string.format("%s, your assigned parking position is terminal ID %d. Check the F10 map for details.", callsign, spot.TerminalID) + -- Get player element. + local player=flight:GetPlayerElement() - -- Transmit message. - self:TransmissionTower(text, flight) + --TODO: Check if player has already a parking spot assigned. If so, remind him. Should we stick to it or give him a new position? - -- Create mark on F10 map. - if spot.Marker then - spot.Marker:Remove() - end - spot.Marker:SetText("Your assigned parking spot!"):ToGroup(group) - - -- Set parking of player element. - for _,_element in pairs(flight.elements) do - local element=_element --Ops.FlightGroup#FLIGHTGROUP.Element - if not element.ai then - element.parking=spot + --TODO: Check if player is currently parking on a spot. If so, he first needs to leave it. + + -- Set terminal type. + local TerminalType=AIRBASE.TerminalType.FighterAircraft + if flight.isHelo then + TerminalType=AIRBASE.TerminalType.HelicopterUsable + end + -- Current coordinate. + local coord=flight:GetCoordinate(nil, player.name) + + -- Get closest FREE parking spot. + local spot=self:GetClosestParkingSpot(coord, TerminalType, AIRBASE.SpotStatus.FREE) + + if spot then + + -- Message text. + local text=string.format("%s, your assigned parking position is terminal ID %d. Check the F10 map for details.", callsign, spot.TerminalID) + + -- Transmit message. + self:TransmissionTower(text, flight) + + -- Create mark on F10 map. + --[[ + if spot.Marker then + spot.Marker:Remove() end + spot.Marker:SetText("Your assigned parking spot!"):ReadWrite():ToGroup(flight.group) + ]] + + -- If player already has a spot. + if player.parking then + self:SetParkingFree(player.parking) + end + + -- Reserve parking for player. + player.parking=spot + self:SetParkingReserved(spot, player.name) + + else + + -- Message text. + local text=string.format("%s, no free parking spot available. Try again later.", callsign) + + -- Transmit message. + self:TransmissionTower(text, flight) + end + else + self:E(self.lid..string.format("Cannot find flight group %s.", tostring(groupname))) end end @@ -2493,42 +2914,78 @@ function FLIGHTCONTROL:_PlayerArrived(groupname) local flight=_DATABASE:GetOpsGroup(groupname) --Ops.FlightGroup#FLIGHTGROUP if flight then - - local group=flight:GetGroup() - local coord=flight:GetCoordinate() + + -- Player element. + local player=flight:GetPlayerElement() + + -- Get current coordinate. + local coord=flight:GetCoordinate(player.name) --Closest parking spot. - local spot=self:GetClosestParkingSpot(coord, nil, true) + local spot=self:GetClosestParkingSpot(coord) - -- Get callsign. - local callsign=flight:GetCallsignName() + if spot then - -- Message text. - local text=string.format("Tower, %s, arrived at parking position. Terminal ID %d.", callsign, spot.TerminalID) - - -- Transmit message. - self:TransmissionTower(text, flight) - - -- Set flight status to PARKING. - self:SetFlightStatus(flight, FLIGHTCONTROL.FlightStatus.PARKING) - - -- Create player menu. - self:_CreatePlayerMenu(flight, flight.menu.atc) - - -- Create mark on F10 map. - if spot.Marker then - spot.Marker:Remove() - end - spot.Marker:SetText("Your current parking spot!"):ToGroup(group) + -- Get callsign. + local callsign=flight:GetCallsignName() + + -- Distance to parking spot. + local dist=coord:Get2DDistance(spot.Coordinate) + + if dist<20 then + + -- Message text. + local text=string.format("%s, %s, arrived at parking position. Terminal ID %d.", self.alias, callsign, spot.TerminalID) - -- Set parking of player element. - for _,_element in pairs(flight.elements) do - local element=_element --Ops.OpsGroup#OPSGROUP.Element - if not element.ai then - element.parking=spot + -- Transmit message. + self:TransmissionPilot(text, flight) + + -- Set flight status to PARKING. + self:SetFlightStatus(flight, FLIGHTCONTROL.FlightStatus.PARKING) + + -- Create player menu. + flight:_UpdateMenu() + + -- Create mark on F10 map. + --[[ + if spot.Marker then + spot.Marker:Remove() + end + spot.Marker:ReadWrite():SetText("Your current parking spot!"):ToGroup(flight.group) + ]] + + -- Set parking of player element. + player.parking=spot + self:SetParkingOccupied(spot, player.name) + + -- Message text. + local text=string.format("%s, %s, roger. Enjoy a cool bevarage in the officers' club.", callsign, self.alias) + + -- Transmit message. + self:TransmissionTower(text, flight, 10) + + else + + -- Message text. + local text=string.format("%s, %s, arrived at parking position", self.alias, callsign) + + -- Transmit message. + self:TransmissionPilot(text, flight) + + -- Message text. + local text=string.format("%s, %s, you are still %d meters away from the closest parking position. Continue taxiing to a proper spot!", callsign, self.alias, dist) + + -- Transmit message. + self:TransmissionTower(text, flight, 10) + end + + else + -- TODO: No spot end + else + self:E(self.lid..string.format("Cannot find flight group %s.", tostring(groupname))) end end @@ -2819,12 +3276,17 @@ end -- @param #number Delay Delay in seconds before the text is transmitted. Default 0 sec. function FLIGHTCONTROL:TransmissionTower(Text, Flight, Delay) + -- Tower radio call. self.msrsTower:PlayText(Text, Delay) + -- "Subtitle". if Flight and not Flight.isAI then self:TextMessageToFlight(Text, Flight, 5, false, Delay) end + -- Set time stamp. Can be in the future. + self.Tlastmessage=timer.getAbsTime() + (Delay or 0) + -- Debug message. self:T(self.lid..string.format("Radio Tower: %s", Text)) @@ -2837,12 +3299,16 @@ end -- @param #number Delay Delay in seconds before the text is transmitted. Default 0 sec. function FLIGHTCONTROL:TransmissionPilot(Text, Flight, Delay) + -- Pilot radio call. self.msrsPilot:PlayText(Text, Delay) + -- "Subtitle". if Flight and not Flight.isAI then self:TextMessageToFlight(Text, Flight, 5, false, Delay) end + -- Set time stamp. + self.Tlastmessage=timer.getAbsTime() + (Delay or 0) -- Debug message. self:T(self.lid..string.format("Radio Pilot: %s", Text)) @@ -2920,7 +3386,12 @@ function FLIGHTCONTROL:SpawnParkingGuard(unit) local lookat=heading-180 -- Set heading and AI off to save resources. - self.parkingGuard:InitHeading(lookat):InitAIOff() + self.parkingGuard:InitHeading(lookat) + + -- Turn AI Off. + if self.parkingGuard:IsInstanceOf("SPAWN") then + self.parkingGuard:InitAIOff() + end -- Group that is spawned. spot.ParkingGuard=self.parkingGuard:SpawnFromCoordinate(Coordinate) diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 3beccc6e9..fe67072c0 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -182,6 +182,18 @@ FLIGHTGROUP.RadioMessage = { TAXIING={normal="Taxiing", enhanced="Taxiing"}, } +--- Player skill. +-- @type FLIGHTGROUP.PlayerSkill +-- @field #string NOVICE Novice +FLIGHTGROUP.PlayerSkill = { + NOVICE="Novice", +} + +--- Player settings. +-- @type FLIGHTGROUP.PlayerSettings +-- @field #boolean subtitles Display subtitles. +-- @field #string skill Skill level. + --- FLIGHTGROUP class version. -- @field #string version FLIGHTGROUP.version="0.7.3" @@ -1645,8 +1657,6 @@ function FLIGHTGROUP:onafterParking(From, Event, To) local flightcontrol=_DATABASE:GetFlightControl(airbasename) if flightcontrol then - - env.info("FF flight control!") -- Set FC for this flight self:SetFlightControl(flightcontrol) @@ -3167,6 +3177,7 @@ function FLIGHTGROUP:_InitGroup(Template) -- Set callsign. Default is set on spawn if not modified by user. local callsign=template.units[1].callsign + self:I({callsign=callsign}) if type(callsign)=="number" then -- Sometimes callsign is just "101". local cs=tostring(callsign) callsign={} @@ -3174,8 +3185,8 @@ function FLIGHTGROUP:_InitGroup(Template) callsign[2]=cs:sub(2,2) callsign[3]=cs:sub(3,3) end - self.callsign.NumberSquad=callsign[1] - self.callsign.NumberGroup=callsign[2] + self.callsign.NumberSquad=tonumber(callsign[1]) + self.callsign.NumberGroup=tonumber(callsign[2]) self.callsign.NameSquad=UTILS.GetCallsignName(self.callsign.NumberSquad) -- Set default formation. @@ -3195,8 +3206,9 @@ function FLIGHTGROUP:_InitGroup(Template) -- Create Menu. if not self.isAI then self.menu=self.menu or {} - self.menu.atc=self.menu.atc or {} - self.menu.atc.root=self.menu.atc.root or MENU_GROUP:New(self.group, "ATC") + self.menu.atc=self.menu.atc or {} --#table + self.menu.atc.root=self.menu.atc.root or MENU_GROUP:New(self.group, "ATC") --Core.Menu#MENU_GROUP + self.menu.atc.help=self.menu.atc.help or MENU_GROUP:New(self.group, "Help", self.menu.atc.root) --Core.Menu#MENU_GROUP end -- Units of the group. @@ -3624,6 +3636,20 @@ function FLIGHTGROUP:AddWaypointLanding(Airbase, Speed, AfterWaypointWithID, Alt return waypoint end +--- Get player element. +-- @param #FLIGHTGROUP self +-- @return Ops.OpsGroup#OPSGROUP.Element The element. +function FLIGHTGROUP:GetPlayerElement() + + for _,_element in pairs(self.elements) do + local element=_element --Ops.OpsGroup#OPSGROUP.Element + if not element.ai then + return element + end + end + + return nil +end --- Set parking spot of element. -- @param #FLIGHTGROUP self @@ -4119,40 +4145,36 @@ end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - - ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- MENU FUNCTIONS ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Get the proper terminal type based on generalized attribute of the group. +--- Update menu. --@param #FLIGHTGROUP self --@param #number delay Delay in seconds. function FLIGHTGROUP:_UpdateMenu(delay) if delay and delay>0 then - self:T(self.lid..string.format("FF updating menu in %.1f sec", delay)) + -- Delayed call. self:ScheduleOnce(delay, FLIGHTGROUP._UpdateMenu, self) else - self:T(self.lid.."FF updating menu NOW") - -- Get current position of group. local position=self:GetCoordinate() -- Get all FLIGHTCONTROLS local fc={} for airbasename,_flightcontrol in pairs(_DATABASE.FLIGHTCONTROLS) do + local flightcontrol=_flightcontrol --Ops.FlightControl#FLIGHTCONTROL - local airbase=AIRBASE:FindByName(airbasename) - - local coord=airbase:GetCoordinate() + -- Get coord of airbase. + local coord=flightcontrol:GetCoordinate() + -- Distance to flight. local dist=coord:Get2DDistance(position) - local fcitem={airbasename=airbasename, dist=dist} - - table.insert(fc, fcitem) + -- Add to table. + table.insert(fc, {airbasename=airbasename, dist=dist}) end -- Sort table wrt distance to airbases. @@ -4161,18 +4183,20 @@ function FLIGHTGROUP:_UpdateMenu(delay) end table.sort(fc, _sort) - for _,_menu in pairs(self.menu.atc or {}) do - local menu=_menu - - end + -- Remove all submenus. + self.menu.atc.root:RemoveSubMenus() + + self:_CreateMenuAtcHelp(self.menu.atc.root) + + -- Max menu entries. + local N=7 -- If there is a designated FC, we put it first. - local N=8 local gotairbase=nil if self.flightcontrol then - self.flightcontrol:_CreatePlayerMenu(self, self.menu.atc) + self.flightcontrol:_CreatePlayerMenu(self, self.menu.atc.root) gotairbase=self.flightcontrol.airbasename - N=7 + N=N-1 end -- Max 8 entries in F10 menu. @@ -4180,13 +4204,88 @@ function FLIGHTGROUP:_UpdateMenu(delay) local airbasename=fc[i].airbasename if gotairbase==nil or airbasename~=gotairbase then local flightcontrol=_DATABASE:GetFlightControl(airbasename) - flightcontrol:_CreatePlayerMenu(self, self.menu.atc) + flightcontrol:_CreatePlayerMenu(self, self.menu.atc.root) end end end end +--- Create player menu. +-- @param #FLIGHTGROUP self +-- @param #table rootmenu ATC root menu table. +function FLIGHTGROUP:_CreateMenuAtcHelp(rootmenu) + + -- Help menu. + local helpmenu=MENU_GROUP:New(self.group, "Help", rootmenu) + + -- Group name. + local groupname=self.groupname + + --- + -- Skill level menu + --- + local skillmenu=MENU_GROUP:New(self.group, "Skill Level", helpmenu) + MENU_GROUP_COMMAND:New(self.group, "Beginner", skillmenu, self._MenuNotImplemented, self, groupname) + MENU_GROUP_COMMAND:New(self.group, "Student", skillmenu, self._MenuNotImplemented, self, groupname) + MENU_GROUP_COMMAND:New(self.group, "Professional", skillmenu, self._MenuNotImplemented, self, groupname) + + --- + -- Commands + --- + MENU_GROUP_COMMAND:New(self.group, "Subtitles On/Off", helpmenu, self._MenuNotImplemented, self, groupname) + MENU_GROUP_COMMAND:New(self.group, "My Voice On/Off", helpmenu, self._MenuNotImplemented, self, groupname) + MENU_GROUP_COMMAND:New(self.group, "My Status", helpmenu, self._PlayerMyStatus, self, groupname) + +end + +--- Player menu not implemented. +-- @param #FLIGHTGROUP self +-- @param #string groupname Name of the flight group. +function FLIGHTGROUP:_MenuNotImplemented(groupname) + + -- Get flight group. + local flight=_DATABASE:GetOpsGroup(groupname) --Ops.FlightGroup#FLIGHTGROUP + + if flight then + + local text=string.format("Sorry, this feature is not implemented yet!") + + MESSAGE:New(text, 10, nil, true):ToGroup(flight.group) + --self:TextMessageToFlight(text, flight) + + end + +end + +--- Player status. +-- @param #FLIGHTGROUP self +-- @param #string groupname Name of the flight group. +function FLIGHTGROUP:_PlayerMyStatus(groupname) + + -- Get flight group. + local flight=_DATABASE:GetOpsGroup(groupname) --Ops.FlightGroup#FLIGHTGROUP + + if flight then + + local fc=flight.flightcontrol + + -- Status text. + local text=string.format("My Status:") + text=text..string.format("\nCallsign: %s", tostring(flight:GetCallsignName())) + text=text..string.format("\nFlight status: %s", tostring(flight:GetState())) + text=text..string.format("\nFlight control: %s status=%s", tostring(fc and fc.airbasename or "N/A"), tostring(fc and fc:GetFlightStatus(flight) or "N/A")) + + -- Send message. + --self:TextMessageToFlight(text, flight, 10, true) + MESSAGE:New(text, 10, nil, true):ToGroup(flight.group) + + else + --TODO: Error + end + +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 40855ef92..cc52c2c02 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -730,6 +730,7 @@ function OPSGROUP:New(group) ------------------------ --- Triggers the FSM event "Stop". Stops the OPSGROUP and all its event handlers. + -- @function [parent=#OPSGROUP] Stop -- @param #OPSGROUP self --- Triggers the FSM event "Stop" after a delay. Stops the OPSGROUP and all its event handlers. @@ -1610,10 +1611,11 @@ end --- Get current coordinate of the group. If the current position cannot be determined, the last known position is returned. -- @param #OPSGROUP self -- @param #boolean NewObject Create a new coordiante object. +-- @param #string UnitName (Optional) Get position of a specifc unit of the group. Default is the first existing unit in the group. -- @return Core.Point#COORDINATE The coordinate (of the first unit) of the group. -function OPSGROUP:GetCoordinate(NewObject) +function OPSGROUP:GetCoordinate(NewObject, UnitName) - local vec3=self:GetVec3() or self.position --DCS#Vec3 + local vec3=self:GetVec3(UnitName) or self.position --DCS#Vec3 if vec3 then @@ -11260,6 +11262,8 @@ function OPSGROUP:SetDefaultCallsign(CallsignName, CallsignNumber) self.callsignDefault.NumberSquad=CallsignName self.callsignDefault.NumberGroup=CallsignNumber or 1 self.callsignDefault.NameSquad=UTILS.GetCallsignName(self.callsign.NumberSquad) + + self:I(self.lid..string.format("Default callsign=%s", self.callsignDefault.NameSquad)) return self end diff --git a/Moose Development/Moose/Wrapper/Marker.lua b/Moose Development/Moose/Wrapper/Marker.lua index 3dbffa91e..462fde061 100644 --- a/Moose Development/Moose/Wrapper/Marker.lua +++ b/Moose Development/Moose/Wrapper/Marker.lua @@ -322,6 +322,16 @@ function MARKER:ReadOnly() return self end +--- Marker is readonly. Text cannot be changed and marker cannot be removed. +-- @param #MARKER self +-- @return #MARKER self +function MARKER:ReadWrite() + + self.readonly=false + + return self +end + --- Set message that is displayed on screen if the marker is added. -- @param #MARKER self -- @param #string Text Message displayed when the marker is added.