From 8880bf858767860a9051893d1d8027b05c939bf3 Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 21 Sep 2019 00:02:10 +0200 Subject: [PATCH] ATIS --- Moose Development/Moose/Core/Point.lua | 2 +- Moose Development/Moose/Core/RadioQueue.lua | 4 +- Moose Development/Moose/Ops/ATIS.lua | 139 +++++++++++++--- Moose Development/Moose/Wrapper/Airbase.lua | 170 +++++++++++++++++++- 4 files changed, 288 insertions(+), 27 deletions(-) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index caeb36f49..5655d9759 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -562,7 +562,7 @@ do -- COORDINATE --- Return the height of the land at the coordinate. -- @param #COORDINATE self - -- @return #number + -- @return #number Land height (ASL) in meters. function COORDINATE:GetLandHeight() local Vec2 = { x = self.x, y = self.z } return land.getHeight( Vec2 ) diff --git a/Moose Development/Moose/Core/RadioQueue.lua b/Moose Development/Moose/Core/RadioQueue.lua index 8a60773d6..d8c07b103 100644 --- a/Moose Development/Moose/Core/RadioQueue.lua +++ b/Moose Development/Moose/Core/RadioQueue.lua @@ -56,6 +56,7 @@ RADIOQUEUE = { -- @field #number Tstarted Mission time (abs) in seconds when the transmission started. -- @field #boolean isplaying If true, transmission is currently playing. -- @field #number Tplay Mission time (abs) in seconds when the transmission should be played. +-- @field #number interval Interval in seconds before next transmission. --- Create a new RADIOQUEUE object for a given radio frequency/modulation. @@ -219,6 +220,7 @@ function RADIOQUEUE:NewTransmission(filename, duration, path, tstart, interval, transmission.path=path or "l10n/DEFAULT/" transmission.Tplay=tstart or timer.getAbsTime() transmission.subtitle=subtitle + transmission.interval=interval or 0 if transmission.subtitle then transmission.subduration=subduration or 5 else @@ -322,7 +324,7 @@ function RADIOQUEUE:Broadcast(transmission) else -- Broadcasting from carrier. No subtitle possible. Need to send messages to players. - self:I(self.lid..string.format("Broadcasting from carrier via trigger.action.radioTransmission().")) + self:I(self.lid..string.format("Broadcasting via trigger.action.radioTransmission().")) -- Position from where to transmit. local vec3=nil diff --git a/Moose Development/Moose/Ops/ATIS.lua b/Moose Development/Moose/Ops/ATIS.lua index f9a3661a0..e9e62a409 100644 --- a/Moose Development/Moose/Ops/ATIS.lua +++ b/Moose Development/Moose/Ops/ATIS.lua @@ -22,6 +22,7 @@ -- @field #number modulation Radio modulation 0=AM or 1=FM. -- @field Core.RadioQueue#RADIOQUEUE radioqueue Radio queue for broadcasing messages. -- @field #string soundpath Path to sound files. +-- @field #string relayunitname Name of the radio relay unit. -- @extends Core.Fsm#FSM --- Be surprised! @@ -46,6 +47,7 @@ ATIS = { modulation = nil, radioqueue = nil, soundpath = nil, + relayunitname = nil, } --- ATIS class version. @@ -92,8 +94,8 @@ function ATIS:New(airbasename, frequency, modulation) -- From State --> Event --> To State self:AddTransition("Stopped", "Start", "Running") -- Start FSM. self:AddTransition("*", "Status", "*") -- Update status. - self:AddTransition("*", "Broadcast", "*") -- Update status. - self:AddTransition("*", "CheckQueue", "*") -- Update status. + self:AddTransition("*", "Broadcast", "*") -- Broadcast ATIS message. + self:AddTransition("*", "CheckQueue", "*") -- Check if radio queue is empty. ------------------------ --- Pseudo Functions --- @@ -123,8 +125,6 @@ function ATIS:New(airbasename, frequency, modulation) BASE:TraceClass(self.ClassName) BASE:TraceLevel(1) end - - self:__Start(1) return self end @@ -142,6 +142,14 @@ function ATIS:SetSoundfilesPath(path) self:I(self.lid..string.format("Setting sound files path to %s", self.soundpath)) end +--- Set sound files folder within miz file. +-- @param #ATIS self +-- @param #string unitname +-- @return #ATIS self +function ATIS:SetRadioRelayUnitName(unitname) + self.relayunitname=unitname + self:I(self.lid..string.format("Setting radio relay unit to %s", self.relayunitname)) +end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Start & Status @@ -158,24 +166,24 @@ function ATIS:onafterStart(From, Event, To) self.radioqueue=RADIOQUEUE:New(self.frequency, self.modulation) self.radioqueue:SetSenderCoordinate(self.airbase:GetCoordinate()) + self.radioqueue:SetSenderUnitName(self.relayunitname) - self.radioqueue:Start(1, 0.01) - - self.radioqueue:SetDigit(0, "0-continue.wav", 0.8, self.soundpath) - self.radioqueue:SetDigit(1, "1-continue.wav", 0.8, self.soundpath) - self.radioqueue:SetDigit(2, "2-continue.wav", 0.8, self.soundpath) - self.radioqueue:SetDigit(3, "3-continue.wav", 0.8, self.soundpath) - self.radioqueue:SetDigit(4, "4-continue.wav", 0.8, self.soundpath) - self.radioqueue:SetDigit(5, "5-continue.wav", 0.8, self.soundpath) - self.radioqueue:SetDigit(6, "6-continue.wav", 0.8, self.soundpath) - self.radioqueue:SetDigit(7, "7-continue.wav", 0.8, self.soundpath) - self.radioqueue:SetDigit(8, "8-continue.wav", 0.8, self.soundpath) - self.radioqueue:SetDigit(9, "9-continue.wav", 0.8, self.soundpath) + self.radioqueue:SetDigit(0, "N-0.ogg", 0.55, self.soundpath) + self.radioqueue:SetDigit(1, "N-1.ogg", 0.40, self.soundpath) + self.radioqueue:SetDigit(2, "N-2.ogg", 0.35, self.soundpath) + self.radioqueue:SetDigit(3, "N-3.ogg", 0.40, self.soundpath) + self.radioqueue:SetDigit(4, "N-4.ogg", 0.36, self.soundpath) + self.radioqueue:SetDigit(5, "N-5.ogg", 0.42, self.soundpath) + self.radioqueue:SetDigit(6, "N-6.ogg", 0.53, self.soundpath) + self.radioqueue:SetDigit(7, "N-7.ogg", 0.42, self.soundpath) + self.radioqueue:SetDigit(8, "N-8.ogg", 0.37, self.soundpath) + self.radioqueue:SetDigit(9, "N-9.ogg", 0.38, self.soundpath) + self.radioqueue:Start(1, 0.05) -- Init status updates. - self:__Status(-1) - self:__CheckQueue(-2) + self:__Status(-2) + self:__CheckQueue(-3) end --- Update status. @@ -188,7 +196,7 @@ function ATIS:onafterStatus(From, Event, To) local text=string.format("State %s", fsmstate) self:I(self.lid..text) - self:__Status(-30) + self:__Status(30) end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -202,17 +210,106 @@ function ATIS:onafterCheckQueue(From, Event, To) if #self.radioqueue.queue==0 then self:I(self.lid..string.format("Radio queue empty. Repeating message.")) self:Broadcast() + else + self:I(self.lid..string.format("Radio queue %d transmissions queued.", #self.radioqueue.queue)) end -- Check back in 5 seconds. - self:__CheckQueue(-5) + self:__CheckQueue(5) end --- Update status. -- @param #ATIS self function ATIS:onafterBroadcast(From, Event, To) - self.radioqueue:Number2Transmission("0123456789", 0, 0) + local coord=self.airbase:GetCoordinate() + + local height=coord:GetLandHeight() + + ---------------- + --- Pressure --- + ---------------- + local qfe=coord:GetPressure(height+5) + local qnh=coord:GetPressure(0) + + -- Convert to inHg + qfe=UTILS.hPa2inHg(qfe) + qnh=UTILS.hPa2inHg(qnh) + + local QFE=UTILS.Split(string.format("%.2f", qfe), ".") + local QNH=UTILS.Split(string.format("%.2f", qnh), ".") + + + local runway=self.airbase:GetActiveRunway() + + local windFrom, windSpeed=coord:GetWind() + local WINDFROM=string.format("%03d", windFrom) + local WINDSPEED=string.format("%d", UTILS.MpsToKnots(windSpeed)) + + -- Time + local time=timer.getAbsTime() + + -- TODO add zulu time correction. + + local clock=UTILS.SecondsToClock(time) + local zulu=UTILS.Split(clock, ":") + local ZULU=string.format("%s%s", zulu[1], zulu[2]) + + -- Temperature + local temperature=coord:GetTemperature(height+5) + local TEMPERATURE=string.format("%d", temperature) + + -- Weather + local clouds, visibility, fog, dust=self:GetStaticWeather() + local cloudbase=clouds.base + local cloudceiling=clouds.base+clouds.thickness + local clouddensity=clouds.density + + local subduration=10 + + -- Batumi Airbase. + self.radioqueue:NewTransmission("Batumi.ogg", 0.52, self.soundpath, nil, nil, "Batumi Airport", subduration) + self.radioqueue:NewTransmission("Airport.ogg", 0.65, self.soundpath) + + -- 1300 Zulu + self.radioqueue:Number2Transmission(ZULU) + self.radioqueue:NewTransmission("TimeZulu.ogg", 0.89, self.soundpath, nil, nil, string.format("%s Zulu Time", ZULU), subduration) + + -- Visibility + local visibility="3" + self.radioqueue:NewTransmission("Visibility.ogg", 0.8, self.soundpath, nil, 1.0, string.format("Visibility %s NM", visibility), subduration) + self.radioqueue:Number2Transmission(visibility) + -- TODO: 0.8 + self.radioqueue:NewTransmission("NauticalMiles.ogg", 0.8, self.soundpath, nil, 0.2) + + -- Altimeter QFE. + self.radioqueue:NewTransmission("Altimeter.ogg", 0.7, self.soundpath, nil, 2.0, string.format("Altimeter QFE %s.%s inHg, QNH %s.%s inHg", QFE[1], QFE[2], QNH[1], QNH[2]), subduration) + self.radioqueue:NewTransmission("QFE.ogg", 0.62, self.soundpath) + self.radioqueue:Number2Transmission(QFE[1]) + self.radioqueue:NewTransmission("Decimal.ogg", 0.58, self.soundpath, nil, 0.2) + self.radioqueue:Number2Transmission(QFE[2]) + self.radioqueue:NewTransmission("QNH.ogg", 0.70, self.soundpath, nil, 2.0) + self.radioqueue:Number2Transmission(QNH[1]) + self.radioqueue:NewTransmission("Decimal.ogg", 0.58, self.soundpath, nil, 0.2) + self.radioqueue:Number2Transmission(QNH[2]) + + -- Temperature + self.radioqueue:NewTransmission("Temperature.ogg", 0.7, self.soundpath, nil, 2.0, string.format("Temperature %s C", TEMPERATURE), subduration) + self.radioqueue:Number2Transmission(TEMPERATURE) + -- TODO: Celcius --> Celsius, 0.9? + self.radioqueue:NewTransmission("DegreesCelsius.ogg", 0.9, self.soundpath, nil, 0.2) + + -- Wind + self.radioqueue:NewTransmission("WindFrom.ogg", 0.7, self.soundpath, nil, 2.0, string.format("Wind from %s at %s knots", WINDFROM, WINDSPEED), subduration) + self.radioqueue:Number2Transmission(WINDFROM) + self.radioqueue:NewTransmission("At.ogg", 0.7, self.soundpath, nil, 0.2) + self.radioqueue:Number2Transmission(WINDSPEED) + self.radioqueue:NewTransmission("Knots.ogg", 0.7, self.soundpath, nil, 0.2) + + -- Active runway. + self.radioqueue:NewTransmission("ActiveRunway.ogg", 1.05, self.soundpath, nil, 2.0, string.format("Active runway %s", runway.idx), subduration) + self.radioqueue:Number2Transmission(runway.idx) + end diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index 27544d7c3..180ff7dea 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -15,6 +15,8 @@ --- @type AIRBASE -- @field #string ClassName Name of the class, i.e. "AIRBASE". -- @field #table CategoryName Names of airbase categories. +-- @field #table runways Table of runway data. +-- @field #number activerwyno Active runway number (forced). -- @extends Wrapper.Positionable#POSITIONABLE --- Wrapper class to handle the DCS Airbase objects: @@ -56,6 +58,8 @@ AIRBASE = { [Airbase.Category.HELIPAD] = "Helipad", [Airbase.Category.SHIP] = "Ship", }, + runways=nil, + activerwyno=nil, } --- Enumeration to identify the airbases in the Caucasus region. @@ -326,6 +330,14 @@ AIRBASE.TerminalType = { FighterAircraft=244, } +--- Runway data. +-- @type AIRBASE.Runway +-- @field #number heading Heading of the runway in degrees. +-- @field #string idx Runway ID: heading 070° ==> idx="07". +-- @field #number length Length of runway in meters. +-- @field Core.Point#COORDINATE position Position of runway start. +-- @field Core.Point#COORDINATE endpoint End point of runway. + -- Registration. --- Create a new AIRBASE from DCSAirbase. @@ -405,13 +417,17 @@ end --- Get all airbases of the current map. This includes ships and FARPS. -- @param DCS#Coalition coalition (Optional) Return only airbases belonging to the specified coalition. By default, all airbases of the map are returned. +-- @param #number category (Optional) Return only airbases of a certain category, e.g. Airbase.Category.FARP -- @return #table Table containing all airbase objects of the current map. -function AIRBASE.GetAllAirbases(coalition) +function AIRBASE.GetAllAirbases(coalition, category) local airbases={} - for _,airbase in pairs(_DATABASE.AIRBASES) do + for _,_airbase in pairs(_DATABASE.AIRBASES) do + local airbase=_airbase --#AIRBASE if (coalition~=nil and airbase:GetCoalition()==coalition) or coalition==nil then - table.insert(airbases, airbase) + if category==nil or category==airbase:GetAirbaseCategory() then + table.insert(airbases, airbase) + end end end @@ -993,7 +1009,153 @@ function AIRBASE._CheckTerminalType(Term_Type, termtype) return match end - +--- Get runways data. Only for airdromes! +-- @param #AIRBASE self +-- @return #table Runway data. +function AIRBASE:GetRunwayData() + + -- Check if data is already initialized. + if self.runways then + return self.runways + else + self.runways={} + end + + if self:GetAirbaseCategory()~=Airbase.Category.AIRDROME then + return {} + end + + -- Get spawn points on runway. + local runwaycoords=self:GetParkingSpotsCoordinates(AIRBASE.TerminalType.Runway) + + self:T(string.format("Runway coords # = %d", #runwaycoords)) + + for i=1,#runwaycoords,2 do + + -- Assuming each runway has two points. + local j=(i+1)/2 + + -- Coordinates of the two runway points. + local c1=runwaycoords[i] --Core.Point#COORDINATES + local c2=runwaycoords[i+1] --Core.Point#COORDINATES + + -- Debug mark + --c1:MarkToAll("Runway Point 1") + --c2:MarkToAll("Runway Point 2") + + -- Heading of runway. + local hdg=c1:HeadingTo(c2) + + -- Runway ID: heading=070° ==> idx="07" + local idx=string.format("%02d", UTILS.Round(hdg/10, 0)) + + -- Debug info. + self:T(string.format("Runway %d heading=%03d", j, hdg)) + + -- Runway table. + local runway={} --#AIRBASE.Runway + runway.heading=hdg + runway.idx=idx + runway.length=c1:Get2DDistance(c2) + runway.position=c1 + runway.endpoint=c2 + + self:I(string.format("Airbase %s: Adding runway #%d: id=%s, heading=%03d, lendth=%d m", self:GetName(), #self.runways+1, runway.idx, runway.heading, runway.length)) + + -- Add runway. + table.insert(self.runways, runway) + + -- Inverse runway. + local hdg=hdg-180 + if hdg<0 then + hdg=hdg+360 + end + + -- Runway ID: heading=070° ==> ID="07" + local idx=string.format("%02d", UTILS.Round(hdg/10, 0)) + + local runway={} --#AIRBASE.Runway + runway.heading=hdg + runway.idx=idx + runway.length=c1:Get2DDistance(c2) + runway.position=c2 + runway.endpoint=c1 + + self:I(string.format("Airbase %s: Adding runway #%d: id=%s, heading=%03d, lendth=%d m", self:GetName(), #self.runways+1, runway.idx, runway.heading, runway.length)) + + -- Add inverse runway. + table.insert(self.runways, runway) + end + + return self.runways +end + +--- Set the active runway in case it cannot be determined by the wind direction. +-- @param #AIRBASE self +-- @param #number iactive Number of the active runway in the runway data table. +function AIRBASE:SetActiveRunway(iactive) + self.activerwyno=iactive +end + +--- Get the active runway based on current wind direction. +-- @param #AIRBASE self +-- @return #AIRBASE.Runway Active runway data table. +function AIRBASE:GetActiveRunway() + + -- Get runways data (initialize if necessary). + local runways=self:GetRunwayData() + + -- Return user forced active runway if it was set. + if self.activerwyno then + return runways[self.activerwyno] + end + + -- Get wind vector. + local Vwind=self:GetCoordinate():GetWindWithTurbulenceVec3() + local norm=UTILS.VecNorm(Vwind) + + -- Active runway number. + local iact=1 + + -- Check if wind is blowing (norm>0). + if norm>0 then + + -- Normalize wind (not necessary). + Vwind.x=Vwind.x/norm + Vwind.y=0 + Vwind.z=Vwind.z/norm + + -- Debug. + self:T3({Vwind=Vwind}) + + -- Loop over runways. + local dotmin=nil + for i,_runway in pairs(runways) do + local runway=_runway --#AIRBASE.Runway + + -- Angle in rad. + local alpha=math.rad(runway.heading) + + -- Runway vector. + local Vrunway={x=math.cos(alpha), y=0, z=math.sin(alpha)} + + -- Dot product: parallel component of the two vectors. + local dot=UTILS.VecDot(Vwind, Vrunway) + + -- Debug. + --env.info(string.format("runway=%03d° dot=%.3f", runway.heading, dot)) + + -- New min? + if dotmin==nil or dot