From cbdbf36f32ba9f7037cb77b948e9439b1bfb9f0c Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 20 Sep 2023 11:52:14 +0200 Subject: [PATCH] FC - Improved taxi phrases --- Moose Development/Moose/Core/Pathline.lua | 68 ++-- Moose Development/Moose/Ops/FlightControl.lua | 313 ++++++++++++++---- 2 files changed, 280 insertions(+), 101 deletions(-) diff --git a/Moose Development/Moose/Core/Pathline.lua b/Moose Development/Moose/Core/Pathline.lua index c015d8e9b..bde36d467 100644 --- a/Moose Development/Moose/Core/Pathline.lua +++ b/Moose Development/Moose/Core/Pathline.lua @@ -92,9 +92,9 @@ PATHLINE.version="0.3.0" -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- TODO: A lot... -- TODO: Read/write to JSON file -- TODO: Translate/rotate pathline +-- TODO: Add color. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Constructor @@ -111,7 +111,7 @@ function PATHLINE:New(Name) self.name=Name or "Unknown Path" - self.lid=string.format("PATHLINE %s | ", Name) + self.lid=string.format("PATHLINE %s | ", self.name) return self end @@ -570,7 +570,8 @@ end --- Write PATHLINE to JSON file. -- **NOTE**: Requires `io` and `lfs` to be de-sanitized! -- @param #PATHLINE self --- @param #string FileName Name of the file. +-- @param #string FileName Name of the file. Default is the name of the pathline. +-- @return #PATHLINE self function PATHLINE:WriteJSON(FileName) if io and lfs then @@ -580,18 +581,19 @@ function PATHLINE:WriteJSON(FileName) local data={} - data.name=self.name - + -- We store the name and the points. + data.name=self.name data.points=self.points for i,_point in pairs(self.points) do local point=_point --#PATHLINE.Point --point.markerID=nil end - -- Encode data to raw JSON. + -- Encode data to raw JSON. Encode converts a lua table into JSON string that can be written to file. local raw_json=json:encode(data) - self:I(data) + -- Debug data. + self:T(data) -- Write in "User/Saved Games/" Folder. local filepath=lfs.writedir() .. FileName @@ -601,7 +603,7 @@ function PATHLINE:WriteJSON(FileName) if f then f:write(raw_json) f:close() - self:I(self.lid .. string.format("FF Saving PATHLINE %s file %s", self.name, tostring(filepath))) + self:T(self.lid .. string.format("Saving PATHLINE %s file %s", self.name, tostring(filepath))) else self:E(self.lid .. string.format( "ERROR: Could not save PATHLINE to file %s", tostring(filepath))) end @@ -619,7 +621,6 @@ end -- @return #PATHLINE self function PATHLINE:NewFromJSON(FileName) - if io and lfs then -- JSON script. @@ -630,38 +631,47 @@ function PATHLINE:NewFromJSON(FileName) -- Write in "User/Saved Games/" Folder. local filepath=lfs.writedir() .. FileName - env.info(filepath) + --env.info(filepath) - local f = io.open( filepath, "rb" ) + -- Open file in binary mode for reading. + local f = io.open(filepath, "rb") if f then - -- self:I(self.lid..string.format("Loading player results from file %s", tostring(filename))) data = f:read("*all") - f:close() + f:close() else - env.info(string.format( "WARNING: Could not load player results from file %s. File might not exist just yet.", tostring( filepath ) ) ) + env.info(string.format("WARNING: Could not load PATHLINE from file %s!", tostring(filepath))) return nil end - BASE:I(data) + -- Decode JSON data to get a lua table. + local data=json:decode(data) - local _data=json:decode(data) - BASE:I(_data) + if data and data.name then - local self=PATHLINE:New(data.name) - - for i=1,#data.points do - local point=data.points[i] --#PATHLINE.Point - local p=pathline:AddPointFromVec3(point.vec3) - p.name=point.name - p.markerID=nil + -- Create a new pathline instance. + local self=PATHLINE:New(data.name) + + for i=1,#data.points do + local point=data.points[i] --#PATHLINE.Point + + -- Create new point from data. + local p=self:AddPointFromVec3(point.vec3) + + -- Set name. + p.name=point.name + + -- Remove marker ID. + p.markerID=nil + end + + return self + else + BASE:E("ERROR: Cannot find pathline name in data from JSON file. File may be corrupted!") end - - return self else - + BASE:E("ERROR: IO and/or LFS not de-sanitized! Cannot read file.") end - - + end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/FlightControl.lua b/Moose Development/Moose/Ops/FlightControl.lua index 15ce0524c..dd9e2c17e 100644 --- a/Moose Development/Moose/Ops/FlightControl.lua +++ b/Moose Development/Moose/Ops/FlightControl.lua @@ -333,17 +333,20 @@ FLIGHTCONTROL.FlightStatus={ --- Violations -- @type FLIGHTCONTROL.Violation +-- @field #string Speeding Speed violation while taxiing. +-- @field #string Altitude Altitude deviation (300 ft from assigned). +-- @field #string Runway Runway incursion. FLIGHTCONTROL.Violation={ Speeding="Speeding", - AltitudeDeviation="Altitude Deviation", --300 feet from assigned alt. - RunwayIncursion="Runway Incursion", + Altitude="Altitude Deviation", --300 feet from assigned alt. + Runway="Runway Incursion", } --- Warning. -- @type FLIGHTCONTROL.Warning -- @field Ops.FlightGroup#FLIGHTGROUP flight The flight group. -- @field #string type Type of warning. --- @field #number time Time stamp when warning was issued. +-- @field #number time Abs. time stamp when warning was issued. --- FlightControl class version. -- @field #string version @@ -1501,7 +1504,7 @@ function FLIGHTCONTROL:_CheckQueues() -- Get taxi way text. local taxiroute=self:_GetTaxiwayText(taxipath, false) - text=text.." via "..taxiroute + text=text.." via "..taxiroute end -- Hold short instructions. @@ -1522,20 +1525,25 @@ function FLIGHTCONTROL:_CheckQueues() --- -- Read back runway. - local text=string.format("Runway %s, ", runway) + local text=string.format("Runway %s", runway) + + -- Read back taxi ways (if available). + if flight.taxipath then + text=text..string.format(" via %s", self:_GetTaxiwayText(flight.taxipath)) + end -- Start uncontrolled aircraft. if flight:IsUncontrolled() then -- Message. - text=text..string.format("starting engines, ") + text=text..string.format(", starting engines") -- Start uncontrolled aircraft. flight:StartUncontrolled() end -- Append call sign. - text=text..callsign + text=text..string.format(", %s.", callsign) -- Transmit message. self:TransmissionPilot(text, flight, 10) @@ -3608,18 +3616,28 @@ function FLIGHTCONTROL:_PlayerConfirmTaxi(groupname) -- Get callsign. local callsign=self:_GetCallsignName(flight) - -- Runway for takeoff. - local runway=self:GetActiveRunwayText(true) + -- Runway for takeoff. + local runway=self:GetActiveRunwayText(true) - -- Tell pilot to wait until cleared. - local text=string.format("Taxi to runway %s, %s", runway, callsign) - self:TransmissionPilot(text, flight) + -- Read back runway. + local text=string.format("Taxi to runway %s", runway) + + -- Read back taxi ways (if available). + if flight.taxipath then + text=text..string.format(" via %s", self:_GetTaxiwayText(flight.taxipath)) + end + + -- Append call sign. + text=text..string.format(", %s.", callsign) + + -- Transmission. + self:TransmissionPilot(text, flight) - -- Taxi out. - self:SetFlightStatus(flight, FLIGHTCONTROL.FlightStatus.TAXIOUT) + -- Taxi out. + self:SetFlightStatus(flight, FLIGHTCONTROL.FlightStatus.TAXIOUT) + end - end --- Player aborts taxi. @@ -4173,8 +4191,6 @@ function FLIGHTCONTROL:_CheckFlights() -- Current flight status. local flightstatus=self:GetFlightStatus(flight) - - --- -- Track flight --- @@ -4219,86 +4235,131 @@ function FLIGHTCONTROL:_CheckFlights() end if not flight.isAI then - - -- Check if speeding while taxiing. - if (flightstatus==FLIGHTCONTROL.FlightStatus.TAXIINB or flightstatus==FLIGHTCONTROL.FlightStatus.TAXIOUT) then + + if flightstatus==FLIGHTCONTROL.FlightStatus.PARKING then + + -- Check if still on parking spot! + + elseif flightstatus==FLIGHTCONTROL.FlightStatus.READYTX then + + -- Flight is ready to taxi to the runway: ? + + elseif flightstatus==FLIGHTCONTROL.FlightStatus.TAXIOUT then if self.speedLimitTaxi then self:_CheckFlightSpeeding(flight) - end - - end + end - if (flightstatus==FLIGHTCONTROL.FlightStatus.TAXIINB or flightstatus==FLIGHTCONTROL.FlightStatus.TAXIOUT) then - - end + elseif flightstatus==FLIGHTCONTROL.FlightStatus.READYTO then - if (flightstatus==FLIGHTCONTROL.FlightStatus.LANDING) then - --Talk down - end + -- Flight is ready for taking-off: ? + + elseif flightstatus==FLIGHTCONTROL.FlightStatus.TAKEOFF then + + -- Flight is taking-off: Check correct runway + + elseif flightstatus==FLIGHTCONTROL.FlightStatus.INBOUND then + + -- Flight is inbound to holding pattern: Nothing to do! + + elseif flightstatus==FLIGHTCONTROL.FlightStatus.HOLDING then - if (flightstatus==FLIGHTCONTROL.FlightStatus.PARKING) then - --Check if still on parking spot! - end - - if (flightstatus==FLIGHTCONTROL.FlightStatus.HOLDING) then --Check altitude and position - local stack=flight.stack if stack then - local altitude=stack.angels + local altitude=stack.angels*1000 - local alt=flight:GetAltitude() + local alt=UTILS.MetersToFeet(flight:GetAltitude()) local diff=alt-altitude + local callsign=self:_GetCallsignName(flight) + -- TODO: issue warning and keep track. otherwise this will repeat unncessarily -- TODO: make a good warning structure and save it in the flightgroup or here. -- needs the kind of warning and the time stamp at least -- - - if diff<300 then - - local timestamp=self:_GetLastWarning(WARNING.Type.AltitudeLow) - if timestamp==nil or timer.getAbsTime()-timestamp>5*60 then - - end - - -- Get number of warnings issued in the last 30 min. - local N=self:_GetWarnings(WARNING.Type.AltitudeLow, Tstart, Tend) - if N>1 then - elseif N>5 then - --Possible pilot deviation. - end - - - local text=string.format("%s, you are too low. Climb to the assigned altitude!") - self:TransmissionTower(text, flight) - - elseif diff>300 then - local text=string.format("%s, you are too high. Descent to the assigned altitude!") - self:TransmissionTower(text, flight) + -- Check if absolute difference > 300 ft (lower or higher). + if math.abs(diff)>300 then - end - end + -- Get last altitude warning. + local warningAlt=self:_WarningGetLast(flight, FLIGHTCONTROL.Violation.Altitude) + + -- Check if no warning so far or last warning more than 5 min ago. + if warningAlt==nil or timer.getAbsTime()-warningAlt.time>5*60 then + + -- Number of warnings. + local N=self:_WarningCount(flight, FLIGHTCONTROL.Violation.Altitude) - end - - if (flightstatus==FLIGHTCONTROL.FlightStatus.ARRIVED) then + local text=string.format("%s, you are ", callsign) + + if diff<300 then + + -- Get number of warnings issued in the last 30 min. + if N==0 then + text=text..string.format("too low. Climb to the assigned altitude.") + elseif N==1 then + text=text..string.format("still too low. Climb to the assigned altitude!") + elseif N==2 then + text=text..string.format("still too low. Climb to the assigned altitude immediately!") + end + + elseif diff>300 then + + if N==0 then + text=text..string.format("you are too high. Descent to the assigned altitude!") + elseif N==1 then + text=text..string.format("still too high. Descent to the assigned altitude!") + elseif N==2 then + text=text..string.format("still too high. Descent to the assigned altitude immediately!") + end + + end + + if N==1 then + text=text..string.format("This was your second warning for an altitude deviation!") + elseif N==2 then + text=text..string.format("This was your third and last warning.") + elseif N==3 then + text=text..string.format("Possible pilot deviation. Call 5 5 5 - 1 2 3 4 5") + else + text=nil + end + + -- Transmission. + if text then + self:TransmissionTower(text, flight) + end + + -- Issue another warning. + self:_WarningIssue(flight, FLIGHTCONTROL.Violation.Altitude) + + end -- No warning or warning more then threshold + end -- Alt diff > 300 ft + end -- Stack~=nil + + elseif flightstatus==FLIGHTCONTROL.FlightStatus.LANDING then + + --Talk down if on final + + elseif flightstatus==FLIGHTCONTROL.FlightStatus.TAXIINB then + + if self.speedLimitTaxi then + self:_CheckFlightSpeeding(flight) + end + + elseif flightstatus==FLIGHTCONTROL.FlightStatus.ARRIVED then + --Check if still on correct parking spot! - end - if (flightstatus==FLIGHTCONTROL.FlightStatus.READYTX) then - -- ? + elseif flightstatus==FLIGHTCONTROL.FlightStatus.UNKNOWN then + -- Status Unknown: Nothing to do! + else + self:E(self.lid..string.format("ERROR: Unknown flight status!")) end - - if (flightstatus==FLIGHTCONTROL.FlightStatus.READYTO) then - -- ? - end - end end @@ -4340,6 +4401,9 @@ function FLIGHTCONTROL:_CheckFlightSpeeding(flight) -- Get player data. local PlayerData=flight:_GetPlayerData() + -- Issue warning. + local warning=self:_WarningIssue(flight, FLIGHTCONTROL.Violation.Speeding) + -- Trigger FSM speeding event. self:PlayerSpeeding(PlayerData) @@ -4510,6 +4574,111 @@ function FLIGHTCONTROL:_FlightOnFinal(flight) return self end +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Warning Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Issue a warning to a flight for a given violation. +-- @param #FLIGHTCONTROL self +-- @param Ops.Flightgroup#FLIGHTGROUP Flight The flight group. +-- @param #string Violation Violation of flight. +-- @return #FLIGHTCONTROL.Warning The warning data that was issued. +function FLIGHTCONTROL:_WarningIssue(Flight, Violation) + + --TODO: Violation should be issued for a player not the flight. + -- AI gets no violations + + local name=Flight:GetName() + + self.warnings[name]=self.warnings[name] or {} + + -- Create a new warning. + local warning={} --#FLIGHTCONTROL.Warning + warning.flight=Flight + warning.type=Violation + warning.time=timer.getAbsTime() + + table.insert(self.warnings[name], warning) + + return warning +end + + +--- Get last warning of a given flight. +-- @param #FLIGHTCONTROL self +-- @param Ops.Flightgroup#FLIGHTGROUP Flight The flight group. +-- @param #string Violation Violation of flight. If not specified, the last violation independent of type is returned. +-- @return #FLIGHTCONTROL.Warning Last warning data or `nil` if no warning was issued so far. +function FLIGHTCONTROL:_WarningGetLast(Flight, Violation) + + -- Get flight name. + local name=Flight:GetName() + + -- All warnings of flight + local warnings=self.warnings[name] or {} + + -- Go backwards because the warnings are appended, so the last warning is at the end of the table. + for i=#warnings,1,-1 do + local warning=warnings[i] --#FLIGHTCONTROL.Warning + if Violation==nil or warning.type==Violation then + return warning + end + end + + return nil +end + +--- Count warnings of a given flight. +-- @param #FLIGHTCONTROL self +-- @param Ops.Flightgroup#FLIGHTGROUP Flight The flight group. +-- @param #string Violation Violation of flight. If not specified, all violations independent of type are counted. +-- @return #number Numer of warnings. +function FLIGHTCONTROL:_WarningCount(Flight, Violation) + + -- Get flight name. + local name=Flight:GetName() + + -- All warnings of flight + local warnings=self.warnings[name] or {} + + -- Init counter. + local N=0 + + -- Go backwards because the warnings are appended, so the last warning is at the end of the table. + for i=#warnings,1,-1 do + local warning=warnings[i] --#FLIGHTCONTROL.Warning + if Violation==nil or warning.type==Violation then + N=N+1 + end + end + + return N +end + + +--- Clear all warnings of a given flight. +-- @param #FLIGHTCONTROL self +-- @param Ops.Flightgroup#FLIGHTGROUP Flight The flight group. +-- @param #string Violation Violation of flight. If not specified, all violations independent of type are cleared. +function FLIGHTCONTROL:_WarningClear(Flight, Violation) + + + -- Get flight name. + local name=Flight:GetName() + + -- All warnings of flight + local warnings=self.warnings[name] or {} + + -- Go backwards because otherwise remove by index gets confused. + for i=#warnings,1,-1 do + local warning=warnings[i] --#FLIGHTCONTROL.Warning + if Violation==nil or warning.type==Violation then + table.remove(warnings, i) + end + end + +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Radio Functions -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------