mirror of
https://github.com/FlightControl-Master/MOOSE.git
synced 2025-08-15 10:47:21 +00:00
898 lines
30 KiB
Lua
898 lines
30 KiB
Lua
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
--- **Functional** - Pseudo ATC.
|
|
--
|
|
-- 
|
|
--
|
|
-- ====
|
|
--
|
|
-- The pseudo ATC enhances the standard DCS ATC functions.
|
|
--
|
|
-- In particular, a menu entry "Pseudo ATC" is created in the special F10 menu.
|
|
--
|
|
-- ## Features
|
|
--
|
|
-- * Report QFE or QNH pressures at nearby airbases.
|
|
-- * Report wind direction and strength at airbases.
|
|
-- * Report temperature at airbases.
|
|
-- * Report absolute bearing and range to nearest airports.
|
|
-- * Report current altitude AGL of own aircraft.
|
|
-- * Upon request, ATC reports altitude until touchdown.
|
|
-- * Pressure temperature, wind data and BR for mission waypoints.
|
|
-- * Works with static and dynamic weather.
|
|
-- * All maps supported (Caucasus, NTTR, Normandy, and all future maps).
|
|
-- * Multiplayer ready (?) (I suppose yes, but I don't have a server to test or debug. Jumping from client to client works.)
|
|
--
|
|
-- Pressure units: hPa (european aircraft), mmHg (russian aircraft), inHg (american aircraft).
|
|
--
|
|
-- ====
|
|
--
|
|
-- # Demo Missions
|
|
--
|
|
-- ### [ALL Demo Missions pack of the last release](https://github.com/FlightControl-Master/MOOSE_MISSIONS/releases)
|
|
--
|
|
-- ====
|
|
--
|
|
-- # YouTube Channel
|
|
--
|
|
-- ### [MOOSE YouTube Channel](https://www.youtube.com/playlist?list=PL7ZUrU4zZUl1jirWIo4t4YxqN-HxjqRkL)
|
|
--
|
|
-- ===
|
|
--
|
|
-- ### Author: **[funkyfranky](https://forums.eagle.ru/member.php?u=115026)**
|
|
--
|
|
-- ### Contributions: **Sven van de Velde ([FlightControl](https://forums.eagle.ru/member.php?u=89536))**
|
|
--
|
|
-- ====
|
|
-- @module PseudoATC
|
|
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
--- PSEUDOATC class
|
|
-- @type PSEUDOATC
|
|
-- @field #string ClassName Name of the Class.
|
|
-- @field #boolean Debug If true, print debug info to dcs.log file.
|
|
-- @field #table player Table comprising the player info.
|
|
-- @field #number mdur Duration in seconds how low messages to the player are displayed.
|
|
-- @field #boolean eventsmoose If true, events are handled by MOOSE. If false, events are handled directly by DCS eventhandler.
|
|
-- @extends Core.Base#BASE
|
|
|
|
---# PSEUDOATC class, extends @{Base#BASE}
|
|
-- The PSEUDOATC class adds some rudimentary ATC functionality via the radio menu.
|
|
--
|
|
-- ## Scripting:
|
|
--
|
|
-- Scripting is almost trivial. Just add the following line to your script:
|
|
--
|
|
-- PSEUDOATC:Start()
|
|
--
|
|
--
|
|
-- @field #PSEUDOATC
|
|
PSEUDOATC={
|
|
ClassName = "PSEUDOATC",
|
|
Debug=false,
|
|
player={},
|
|
maxairport=9,
|
|
mdur=30,
|
|
mrefresh=120,
|
|
eventsmoose=true,
|
|
}
|
|
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
--- PSEUDOATC unit conversions.
|
|
-- @list unit
|
|
PSEUDOATC.unit={
|
|
hPa2inHg=0.0295299830714,
|
|
hPa2mmHg=0.7500615613030,
|
|
meter2feet=3.28084,
|
|
km2nm=0.539957,
|
|
}
|
|
|
|
--- Some ID to identify who we are in output of the DCS.log file.
|
|
-- @field #string id
|
|
PSEUDOATC.id="PseudoATC | "
|
|
|
|
--- PSEUDOATC version.
|
|
-- @field #list
|
|
PSEUDOATC.version={
|
|
version = "0.6.0",
|
|
print = true,
|
|
}
|
|
|
|
|
|
--- PSEUDOATC contructor. Starts the PseudoATC.
|
|
-- @param #PSEUDOATC self
|
|
-- @return #PSEUDOATC Returns a PSEUDOATC object.
|
|
function PSEUDOATC:Start()
|
|
|
|
-- Inherit BASE.
|
|
local self=BASE:Inherit(self, BASE:New()) -- #PSEUDOATC
|
|
|
|
-- Debug info
|
|
self:E(PSEUDOATC.id..string.format("Creating PseudoATC object. PseudoATC version %s", PSEUDOATC.version.version))
|
|
|
|
-- Handle events.
|
|
if self.eventsmoose then
|
|
self:HandleEvent(EVENTS.Birth, self._OnBirth)
|
|
self:HandleEvent(EVENTS.PlayerLeaveUnit, self._PlayerLeft)
|
|
--self:HandleEvent(EVENTS.PilotDead, self._PlayerLeft)
|
|
self:HandleEvent(EVENTS.Land, self._PlayerLanded)
|
|
--self:HandleEvent(EVENTS.Takeoff, self._PlayerTakeoff)
|
|
else
|
|
-- Events are handled directly by DCS.
|
|
world.addEventHandler(self)
|
|
end
|
|
|
|
-- Return object.
|
|
return self
|
|
end
|
|
|
|
-----------------------------------------------------------------------------------------------------------------------------------------
|
|
-- Event Handling
|
|
|
|
--- Event handler for suppressed groups.
|
|
--@param #PSEUDOATC self
|
|
--@param #table Event Event data table. Holds event.id, event.initiator and event.target etc.
|
|
function PSEUDOATC:onEvent(Event)
|
|
if Event == nil or Event.initiator == nil or Unit.getByName(Event.initiator:getName()) == nil then
|
|
return true
|
|
end
|
|
|
|
local DCSiniunit = Event.initiator
|
|
local DCSplace = Event.place
|
|
local DCSsubplace = Event.subplace
|
|
|
|
local EventData={}
|
|
local _playerunit=nil
|
|
local _playername=nil
|
|
|
|
if Event.initiator then
|
|
EventData.IniUnitName = Event.initiator:getName()
|
|
EventData.IniDCSGroup = Event.initiator:getGroup()
|
|
EventData.IniGroupName = Event.initiator:getGroup():getName()
|
|
-- Get player unit and name. This returns nil,nil if the event was not fired by a player unit. And these are the only events we are interested in.
|
|
_playerunit, _playername = self:_GetPlayerUnitAndName(EventData.IniUnitName)
|
|
end
|
|
|
|
if Event.place then
|
|
EventData.Place=Event.place
|
|
EventData.PlaceName=Event.place:getName()
|
|
end
|
|
if Event.subplace then
|
|
EventData.SubPlace=Event.subplace
|
|
EventData.SubPlaceName=Event.subplace:getName()
|
|
end
|
|
|
|
-- Event info.
|
|
if self.Debug then
|
|
env.info(PSEUDOATC.id..string.format("EVENT: Event in onEvent with ID = %s", tostring(Event.id)))
|
|
env.info(PSEUDOATC.id..string.format("EVENT: Ini unit = %s" , tostring(EventData.IniUnitName)))
|
|
env.info(PSEUDOATC.id..string.format("EVENT: Ini group = %s" , tostring(EventData.IniGroupName)))
|
|
env.info(PSEUDOATC.id..string.format("EVENT: Ini player = %s" , tostring(_playername)))
|
|
env.info(PSEUDOATC.id..string.format("EVENT: Place = %s" , tostring(EventData.PlaceName)))
|
|
env.info(PSEUDOATC.id..string.format("EVENT: SubPlace = %s" , tostring(EventData.SubPlaceName)))
|
|
end
|
|
|
|
-- Event birth.
|
|
if Event.id == world.event.S_EVENT_BIRTH and _playername then
|
|
self:_OnBirth(EventData)
|
|
end
|
|
|
|
-- Event land.
|
|
if Event.id == world.event.S_EVENT_LAND and _playername and EventData.Place then
|
|
self:_PlayerLanded(EventData)
|
|
end
|
|
|
|
-- Event player left unit
|
|
if Event.id == world.event.S_EVENT_PLAYER_LEAVE_UNIT and _playername then
|
|
self:_PlayerLeft(EventData)
|
|
end
|
|
|
|
end
|
|
|
|
--- Function called my MOOSE event handler when a player enters a unit.
|
|
-- @param #PSEUDOATC self
|
|
-- @param Core.Event#EVENTDATA EventData
|
|
function PSEUDOATC:_OnBirth(EventData)
|
|
self:F({EventData=EventData})
|
|
|
|
-- Get unit and player.
|
|
local _unitName=EventData.IniUnitName
|
|
local _unit, _playername=self:_GetPlayerUnitAndName(_unitName)
|
|
|
|
-- Check if a player entered.
|
|
if _unit and _playername then
|
|
self:PlayerEntered(_unit)
|
|
end
|
|
|
|
end
|
|
|
|
--- Function called by MOOSE event handler when a player leaves a unit or dies.
|
|
-- @param #PSEUDOATC self
|
|
-- @param Core.Event#EVENTDATA EventData
|
|
function PSEUDOATC:_PlayerLeft(EventData)
|
|
self:F({EventData=EventData})
|
|
|
|
-- Get unit and player.
|
|
local _unitName=EventData.IniUnitName
|
|
local _unit, _playername=self:_GetPlayerUnitAndName(_unitName)
|
|
|
|
-- Check if a player left.
|
|
if _unit and _playername then
|
|
self:PlayerLeft(_unit)
|
|
end
|
|
end
|
|
|
|
--- Function called by MOOSE event handler when a player landed.
|
|
-- @param #PSEUDOATC self
|
|
-- @param Core.Event#EVENTDATA EventData
|
|
function PSEUDOATC:_PlayerLanded(EventData)
|
|
self:F({EventData=EventData})
|
|
|
|
-- Get unit, player and place.
|
|
local _unitName=EventData.IniUnitName
|
|
local _unit, _playername=self:_GetPlayerUnitAndName(_unitName)
|
|
local _base=EventData.Place
|
|
local _baseName=EventData.PlaceName
|
|
|
|
-- Call landed function.
|
|
if _unit and _playername and _base then
|
|
self:PlayerLanded(_unit, _baseName)
|
|
end
|
|
end
|
|
|
|
-----------------------------------------------------------------------------------------------------------------------------------------
|
|
-- Event Functions
|
|
|
|
--- Function called when a player enters a unit.
|
|
-- @param #PSEUDOATC self
|
|
-- @param Wrapper.Unit#UNIT unit Unit the player entered.
|
|
function PSEUDOATC:PlayerEntered(unit)
|
|
self:F2({unit=unit})
|
|
|
|
-- Get player info.
|
|
local group=unit:GetGroup() --Wrapper.Group#GROUP
|
|
local GID=group:GetID()
|
|
local GroupName=group:GetName()
|
|
local PlayerName=unit:GetPlayerName()
|
|
local UnitName=unit:GetName()
|
|
local CallSign=unit:GetCallsign()
|
|
|
|
-- Init player table.
|
|
self.player[GID]={}
|
|
self.player[GID].group=group
|
|
self.player[GID].unit=unit
|
|
self.player[GID].groupname=GroupName
|
|
self.player[GID].unitname=UnitName
|
|
self.player[GID].playername=PlayerName
|
|
self.player[GID].callsign=CallSign
|
|
self.player[GID].waypoints=group:GetTaskRoute()
|
|
|
|
-- Info message.
|
|
local text=string.format("Player %s entered unit %s of group %s. ID = %d", PlayerName, UnitName, GroupName, GID)
|
|
self:T(PSEUDOATC.id..text)
|
|
MESSAGE:New(text, 30):ToAllIf(self.Debug)
|
|
|
|
-- Create main F10 menu, i.e. "F10/Pseudo ATC"
|
|
self.player[GID].menu_main=missionCommands.addSubMenuForGroup(GID, "Pseudo ATC")
|
|
|
|
-- Create list of nearby airports.
|
|
self:LocalAirports(GID)
|
|
|
|
-- Create submenu My Positon.
|
|
self:MenuAircraft(GID)
|
|
|
|
-- Create submenu airports.
|
|
self:MenuAirports(GID)
|
|
|
|
-- Start scheduler to refresh the F10 menues.
|
|
self.player[GID].scheduler, self.player[GID].schedulerid=SCHEDULER:New(nil, self.MenuRefresh, {self, GID}, self.mrefresh, self.mrefresh)
|
|
|
|
end
|
|
|
|
--- Function called when a player has landed.
|
|
-- @param #PSEUDOATC self
|
|
-- @param Wrapper.Unit#UNIT unit Unit of player which has landed.
|
|
-- @param #string place Name of the place the player landed at.
|
|
function PSEUDOATC:PlayerLanded(unit, place)
|
|
self:F2({unit=unit, place=place})
|
|
|
|
-- Gather some information.
|
|
local group=unit:GetGroup()
|
|
local id=group:GetID()
|
|
local PlayerName=self.player[id].playername
|
|
local UnitName=self.player[id].playername
|
|
local GroupName=self.player[id].groupname
|
|
local CallSign=self.player[id].callsign
|
|
|
|
-- Debug message.
|
|
local text=string.format("Player %s (%s) from group %s with ID %d landed at %s", PlayerName, UnitName, GroupName, place)
|
|
self:T(PSEUDOATC.id..text)
|
|
MESSAGE:New(text, 30):ToAllIf(self.Debug)
|
|
|
|
-- Stop altitude reporting timer if its activated.
|
|
self:AltidudeStopTimer(id)
|
|
|
|
-- Welcome message.
|
|
if place then
|
|
local text=string.format("Touchdown! Welcome to %s. Have a nice day!", place)
|
|
MESSAGE:New(text, self.mdur):ToGroup(group)
|
|
end
|
|
|
|
end
|
|
|
|
--- Function called when a player leaves a unit or dies.
|
|
-- @param #PSEUDOATC self
|
|
-- @param Wrapper.Unit#UNIT unit Player unit which was left.
|
|
function PSEUDOATC:PlayerLeft(unit)
|
|
self:F2({unit=unit})
|
|
|
|
-- Get id.
|
|
local group=unit:GetGroup()
|
|
local id=group:GetID()
|
|
|
|
-- Debug message.
|
|
local text=string.format("Player %s (%s) callsign %s of group %s just left.", self.player[id].playername, self.player[id].unitname, self.player[id].callsign, self.player[id].groupname)
|
|
self:T(PSEUDOATC.id..text)
|
|
MESSAGE:New(text, 30):ToAllIf(self.Debug)
|
|
|
|
-- Stop scheduler for menu updates
|
|
if self.player[id].schedulerid then
|
|
self.player[id].scheduler:Stop(self.player[id].schedulerid)
|
|
end
|
|
|
|
-- Remove main menu.
|
|
missionCommands.removeItem(self.player[id].menu_main)
|
|
|
|
-- Remove player array.
|
|
self.player[id]=nil
|
|
end
|
|
|
|
-----------------------------------------------------------------------------------------------------------------------------------------
|
|
-- Menu Functions
|
|
|
|
--- Refreshes all player menues.
|
|
-- @param #PSEUDOATC self.
|
|
-- @param #number id Group id of player unit.
|
|
function PSEUDOATC:MenuRefresh(id)
|
|
self:F({id=id})
|
|
|
|
-- Debug message.
|
|
local text=string.format("Refreshing menues for player %s in group %s.", self.player[id].playername, self.player[id].groupname)
|
|
self:T(PSEUDOATC.id..text)
|
|
MESSAGE:New(text,30):ToAllIf(self.Debug)
|
|
|
|
-- Clear menu.
|
|
self:MenuClear(id)
|
|
|
|
-- Create list of nearby airports.
|
|
self:LocalAirports(id)
|
|
|
|
-- Create submenu My Positon.
|
|
self:MenuAircraft(id)
|
|
|
|
-- Create submenu airports.
|
|
self:MenuAirports(id)
|
|
end
|
|
|
|
|
|
--- Clear player menues.
|
|
-- @param #PSEUDOATC self.
|
|
-- @param #number id Group id of player unit.
|
|
function PSEUDOATC:MenuClear(id)
|
|
self:F(id)
|
|
|
|
-- Debug message.
|
|
local text=string.format("Clearing menues for player %s in group %s.", self.player[id].playername, self.player[id].groupname)
|
|
self:T(PSEUDOATC.id..text)
|
|
MESSAGE:New(text,30):ToAllIf(self.Debug)
|
|
|
|
|
|
if self.player[id].menu_airports then
|
|
for name,item in pairs(self.player[id].menu_airports) do
|
|
|
|
-- Debug message.
|
|
self:E(PSEUDOATC.id..string.format("Deleting menu item %s for ID %d", name, id))
|
|
|
|
-- Remove menu item.
|
|
missionCommands.removeItemForGroup(id, self.player[id].menu_airports[name])
|
|
end
|
|
|
|
else
|
|
self:T2(PSEUDOATC.id.."No airports to clear menus.")
|
|
end
|
|
|
|
-- Remove
|
|
if self.player[id].menu_aircraft then
|
|
missionCommands.removeItemForGroup(id, self.player[id].menu_aircraft.main)
|
|
end
|
|
|
|
self.player[id].menu_airports=nil
|
|
self.player[id].menu_aircraft=nil
|
|
end
|
|
|
|
--- Create "F10/Pseudo ATC" menu items "Airport Data".
|
|
-- @param #PSEUDOATC self
|
|
-- @param #number id Group id of player unit for which menues are created.
|
|
function PSEUDOATC:MenuAirports(id)
|
|
self:F(id)
|
|
|
|
-- Table for menu entries.
|
|
self.player[id].menu_airports={}
|
|
|
|
local i=0
|
|
for _,airport in pairs(self.player[id].airports) do
|
|
|
|
i=i+1
|
|
if i>self.maxairport then
|
|
break -- Max X<10 airports due to 10 menu items restriction.
|
|
end
|
|
|
|
local name=airport.name
|
|
local d=airport.distance
|
|
local pos=AIRBASE:FindByName(name):GetCoordinate()
|
|
|
|
--F10menu_ATC_airports[ID][name] = missionCommands.addSubMenuForGroup(ID, name, F10menu_ATC)
|
|
local submenu=missionCommands.addSubMenuForGroup(id, name, self.player[id].menu_main)
|
|
self.player[id].menu_airports[name]=submenu
|
|
|
|
-- Create menu reporting commands
|
|
missionCommands.addCommandForGroup(id, "Weather Report", submenu, self.ReportWeather, self, id, pos, name)
|
|
missionCommands.addCommandForGroup(id, "Request QFE", submenu, self.ReportPressure, self, id, "QFE", pos, name)
|
|
missionCommands.addCommandForGroup(id, "Request QNH", submenu, self.ReportPressure, self, id, "QNH", pos, name)
|
|
missionCommands.addCommandForGroup(id, "Request Wind", submenu, self.ReportWind, self, id, pos, name)
|
|
missionCommands.addCommandForGroup(id, "Request Temperature", submenu, self.ReportTemperature, self, id, pos, name)
|
|
missionCommands.addCommandForGroup(id, "Request BR", submenu, self.ReportBR, self, id, pos, name)
|
|
|
|
-- Debug message.
|
|
self:T(string.format(PSEUDOATC.id.."Creating airport menu item %s for ID %d", name, id))
|
|
end
|
|
end
|
|
|
|
--- Create F10/Pseudo ATC menu item "My Plane".
|
|
-- @param #PSEUDOATC self
|
|
-- @param #number id Group id of player unit for which menues are created.
|
|
function PSEUDOATC:MenuAircraft(id)
|
|
self:F(id)
|
|
|
|
-- Table for menu entries.
|
|
self.player[id].menu_aircraft={}
|
|
|
|
local unit=self.player[id].unit --Wrapper.Unit#UNIT
|
|
local callsign=self.player[id].callsign
|
|
local name=string.format("My Aircraft (%s)", callsign)
|
|
|
|
-- Debug info.
|
|
self:T(PSEUDOATC.id..string.format("Creating menu item %s for ID %d", name,id))
|
|
|
|
-- F10/PseudoATC/My Aircraft (callsign)
|
|
self.player[id].menu_aircraft.main = missionCommands.addSubMenuForGroup(id, name, self.player[id].menu_main)
|
|
|
|
-- F10/PseudoATC/My Aircraft (callsign)/Waypoints
|
|
if #self.player[id].waypoints>0 then
|
|
|
|
--F10menu_ATC_waypoints[ID]={}
|
|
self.player[id].menu_aircraft_waypoints={}
|
|
self.player[id].menu_aircraft_waypoints.main=missionCommands.addSubMenuForGroup(id, "Waypoints", self.player[id].menu_aircraft.main)
|
|
|
|
local j=0
|
|
for i, wp in pairs(self.player[id].waypoints) do
|
|
-- Increase counter
|
|
j=j+1
|
|
|
|
if j>10 then
|
|
break -- max ten menu entries
|
|
end
|
|
|
|
local pos=COORDINATE:New(wp.x,wp.alt,wp.z)
|
|
|
|
local fname=string.format("Waypoint %d for %s", i-1, callsign)
|
|
local pname=string.format("Waypoint %d", i-1)
|
|
|
|
-- "F10/PseudoATC/My Aircraft (callsign)/Waypoints/Waypoint X"
|
|
local submenu=missionCommands.addSubMenuForGroup(id, pname, self.player[id].menu_aircraft_waypoints.main)
|
|
self.player[id].menu_aircraft_waypoints.pname=submenu
|
|
|
|
-- Menu commands for each waypoint "F10/PseudoATC/My Aircraft (callsign)/Waypoints/Waypoint X/<Commands>"
|
|
missionCommands.addCommandForGroup(id, "Weather Report", submenu, self.ReportWeather, self, id, pos, pname)
|
|
missionCommands.addCommandForGroup(id, "Request QFE", submenu, self.ReportPressure, self, id, "QFE", pos, pname)
|
|
missionCommands.addCommandForGroup(id, "Request QNH", submenu, self.ReportPressure, self, id, "QNH", pos, pname)
|
|
missionCommands.addCommandForGroup(id, "Request Wind", submenu, self.ReportWind, self, id, pos, pname)
|
|
missionCommands.addCommandForGroup(id, "Request Temperature", submenu, self.ReportTemperature, self, id, pos, pname)
|
|
missionCommands.addCommandForGroup(id, "Request BR", submenu, self.ReportBR, self, id, pos, pname)
|
|
end
|
|
end
|
|
missionCommands.addCommandForGroup(id, "Request current altitude AGL", self.player[id].menu_aircraft.main, self.ReportHeight, self, id)
|
|
missionCommands.addCommandForGroup(id, "Report altitude until touchdown", self.player[id].menu_aircraft.main, self.AltidudeStartTimer, self, id)
|
|
missionCommands.addCommandForGroup(id, "Quit reporting altitude", self.player[id].menu_aircraft.main, self.AltidudeStopTimer, self, id)
|
|
end
|
|
|
|
-----------------------------------------------------------------------------------------------------------------------------------------
|
|
-- Reporting Functions
|
|
|
|
--- Weather Report. Report pressure QFE/QNH, temperature, wind at certain location
|
|
-- @param #PSEUDOATC self
|
|
-- @param #number id Group id to which the report is delivered.
|
|
-- @param Core.Point#COORDINATE position Coordinates at which the pressure is measured.
|
|
-- @param #string location Name of the location at which the pressure is measured.
|
|
function PSEUDOATC:ReportWeather(id, position, location)
|
|
self:F({id=id, position=position, location=location})
|
|
|
|
-- Player unit system settings.
|
|
local settings=_DATABASE:GetPlayerSettings(self.player[id].playername) or _SETTINGS --Core.Settings#SETTINGS
|
|
|
|
local text=string.format("Local weather at %s:\n", location)
|
|
|
|
-- Get pressure in hPa.
|
|
local Pqnh=position:GetPressure(0) -- Get pressure at sea level.
|
|
local Pqfe=position:GetPressure() -- Get pressure at (land) height of position.
|
|
|
|
-- Unit conversion.
|
|
local _Pqnh=string.format("%.2f inHg", Pqnh * PSEUDOATC.unit.hPa2inHg)
|
|
local _Pqfe=string.format("%.2f inHg", Pqfe * PSEUDOATC.unit.hPa2inHg)
|
|
if settings:IsMetric() then
|
|
_Pqnh=string.format("%.1f mmHg", Pqnh * PSEUDOATC.unit.hPa2mmHg)
|
|
_Pqfe=string.format("%.1f mmHg", Pqfe * PSEUDOATC.unit.hPa2mmHg)
|
|
end
|
|
|
|
-- Message text.
|
|
text=text..string.format("QFE %.1f hPa = %s.\n", Pqfe, _Pqfe)
|
|
text=text..string.format("QNH %.1f hPa = %s.\n", Pqnh, _Pqnh)
|
|
|
|
--- convert celsius to fahrenheit
|
|
local function celsius2fahrenheit(degC)
|
|
return degC*1.8+32
|
|
end
|
|
|
|
-- Get temperature at position in degrees Celsius.
|
|
local T=position:GetTemperature()
|
|
|
|
-- Correct unit system.
|
|
local _T=string.format('%d°F', celsius2fahrenheit(T))
|
|
if settings:IsMetric() then
|
|
_T=string.format('%d°C', T)
|
|
end
|
|
|
|
-- Message text.
|
|
local text=text..string.format("Temperature %s\n", _T)
|
|
|
|
-- Get wind direction and speed.
|
|
local Dir,Vel=position:GetWind()
|
|
|
|
-- Get Beaufort wind scale.
|
|
local Bn,Bd=UTILS.BeaufortScale(Vel)
|
|
|
|
-- Formatted wind direction.
|
|
local Ds = string.format('%03d°', Dir)
|
|
|
|
-- Velocity in player units.
|
|
local Vs=string.format('%.1f m/s', Vel)
|
|
if settings:IsImperial() then
|
|
Vs=string.format("%.1f knots", Vel*1.94384)
|
|
end
|
|
|
|
-- Message text.
|
|
local text=text..string.format("Wind from %s at %s (%s).", Ds, Vs, Bd)
|
|
|
|
-- Send message
|
|
self:_DisplayMessageToGroup(self.player[id].unit, text, self.mdur, true)
|
|
|
|
end
|
|
|
|
--- Report pressure.
|
|
-- @param #PSEUDOATC self
|
|
-- @param #number id Group id to which the report is delivered.
|
|
-- @param #string Qcode Can be "QNH" for pressure at sea level or "QFE" for pressure at field elevation. Default is QFE or more precisely pressure at position.
|
|
-- @param Core.Point#COORDINATE position Coordinates at which the pressure is measured.
|
|
-- @param #string location Name of the location at which the pressure is measured.
|
|
function PSEUDOATC:ReportPressure(id, Qcode, position, location)
|
|
self:F({id=id, Qcode=Qcode, position=position, location=location})
|
|
|
|
-- Get pressure in hPa.
|
|
local P
|
|
if Qcode=="QNH" then
|
|
P=position:GetPressure(0) -- Get pressure at sea level.
|
|
else
|
|
P=position:GetPressure() -- Get pressure at (land) height of position.
|
|
end
|
|
|
|
-- Settings.
|
|
local settings=_DATABASE:GetPlayerSettings(self.player[id].playername) or _SETTINGS --Core.Settings#SETTINGS
|
|
|
|
-- Unit conversion.
|
|
local P_inHg=P * PSEUDOATC.unit.hPa2inHg
|
|
local P_mmHg=P * PSEUDOATC.unit.hPa2mmHg
|
|
|
|
local P_set=string.format("%.2f inHg", P_inHg)
|
|
if settings:IsMetric() then
|
|
P_set=string.format("%.1f mmHg", P_mmHg)
|
|
end
|
|
|
|
-- Message text.
|
|
local text=string.format("%s at %s: P = %.1f hPa = %s.", Qcode, location, P, P_set)
|
|
|
|
-- Send message.
|
|
MESSAGE:New(text, self.mdur):ToGroup(self.player[id].group)
|
|
end
|
|
|
|
--- Report temperature.
|
|
-- @param #PSEUDOATC self
|
|
-- @param #number id Group id to the report is delivered.
|
|
-- @param Core.Point#COORDINATE position Coordinates at which the pressure is measured.
|
|
-- @param #string location Name of the location at which the pressure is measured.
|
|
function PSEUDOATC:ReportTemperature(id, position, location)
|
|
self:F({id=id, position=position, location=location})
|
|
|
|
--- convert celsius to fahrenheit
|
|
local function celsius2fahrenheit(degC)
|
|
return degC*1.8+32
|
|
end
|
|
|
|
-- Get temperature at position in degrees Celsius.
|
|
local T=position:GetTemperature()
|
|
|
|
-- Formatted temperature in Celsius and Fahrenheit.
|
|
local Tc=string.format('%d°C', T)
|
|
local Tf=string.format('%d°F', celsius2fahrenheit(T))
|
|
|
|
-- Settings.
|
|
local settings=_DATABASE:GetPlayerSettings(self.player[id].playername) or _SETTINGS --Core.Settings#SETTINGS
|
|
|
|
-- Correct unit system.
|
|
local _T=string.format('%d°F', celsius2fahrenheit(T))
|
|
if settings:IsMetric() then
|
|
_T=string.format('%d°C', T)
|
|
end
|
|
|
|
-- Message text.
|
|
local text=string.format("Temperature at %s is %s", location, _T)
|
|
|
|
-- Send message to player group.
|
|
MESSAGE:New(text, self.mdur):ToGroup(self.player[id].group)
|
|
end
|
|
|
|
--- Report wind direction and strength.
|
|
-- @param #PSEUDOATC self
|
|
-- @param #number id Group id to the report is delivered.
|
|
-- @param Core.Point#COORDINATE position Coordinates at which the pressure is measured.
|
|
-- @param #string location Name of the location at which the pressure is measured.
|
|
function PSEUDOATC:ReportWind(id, position, location)
|
|
self:F({id=id, position=position, location=location})
|
|
|
|
-- Get wind direction and speed.
|
|
local Dir,Vel=position:GetWind()
|
|
|
|
-- Get Beaufort wind scale.
|
|
local Bn,Bd=UTILS.BeaufortScale(Vel)
|
|
|
|
-- Formatted wind direction.
|
|
local Ds = string.format('%03d°', Dir)
|
|
|
|
-- Settings.
|
|
local settings=_DATABASE:GetPlayerSettings(self.player[id].playername) or _SETTINGS --Core.Settings#SETTINGS
|
|
|
|
-- Velocity in player units.
|
|
local Vs=string.format('%.1f m/s', Vel)
|
|
if settings:IsImperial() then
|
|
Vs=string.format("%.1f knots", Vel*1.94384)
|
|
end
|
|
|
|
-- Message text.
|
|
local text=string.format("%s: Wind from %s at %s (%s).", location, Ds, Vs, Bd)
|
|
|
|
-- Send message to player group.
|
|
MESSAGE:New(text, self.mdur):ToGroup(self.player[id].group)
|
|
end
|
|
|
|
--- Report absolute bearing and range form player unit to airport.
|
|
-- @param #PSEUDOATC self
|
|
-- @param #number id Group id to the report is delivered.
|
|
-- @param Core.Point#COORDINATE position Coordinates at which the pressure is measured.
|
|
-- @param #string location Name of the location at which the pressure is measured.
|
|
function PSEUDOATC:ReportBR(id, position, location)
|
|
self:F({id=id, position=position, location=location})
|
|
|
|
-- Current coordinates.
|
|
local unit=self.player[id].unit --Wrapper.Unit#UNIT
|
|
local coord=unit:GetCoordinate()
|
|
|
|
-- Direction vector from current position (coord) to target (position).
|
|
local vec3=coord:GetDirectionVec3(position)
|
|
local angle=coord:GetAngleDegrees(vec3)
|
|
local range=coord:Get2DDistance(position)
|
|
|
|
-- Bearing string.
|
|
local Bs=string.format('%03d°', angle)
|
|
|
|
-- Settings.
|
|
local settings=_DATABASE:GetPlayerSettings(self.player[id].playername) or _SETTINGS --Core.Settings#SETTINGS
|
|
|
|
local Rs=string.format("%.1f km", range/1000)
|
|
if settings:IsImperial() then
|
|
Rs=string.format("%.1f NM", range/1000 * PSEUDOATC.unit.km2nm)
|
|
end
|
|
|
|
-- Message text.
|
|
local text=string.format("%s: Bearing %s, Range %s.", location, Bs, Rs)
|
|
|
|
-- Send message to player group.
|
|
MESSAGE:New(text, self.mdur):ToGroup(self.player[id].group)
|
|
end
|
|
|
|
--- Report altitude above ground level of player unit.
|
|
-- @param #PSEUDOATC self
|
|
-- @param #number id Group id to the report is delivered.
|
|
-- @param #number dt (Optional) Duration the message is displayed.
|
|
-- @param #boolean _clear (Optional) Clear previouse messages.
|
|
-- @return #number Altuitude above ground.
|
|
function PSEUDOATC:ReportHeight(id, dt, _clear)
|
|
self:F({id=id, dt=dt})
|
|
|
|
local dt = dt or self.mdur
|
|
if _clear==nil then
|
|
_clear=false
|
|
end
|
|
|
|
-- Return height [m] above ground level.
|
|
local function get_AGL(p)
|
|
local vec2={x=p.x,y=p.z}
|
|
local ground=land.getHeight(vec2)
|
|
local agl=p.y-ground
|
|
return agl
|
|
end
|
|
|
|
-- Get height AGL.
|
|
local unit=self.player[id].unit --Wrapper.Unit#UNIT
|
|
local position=unit:GetCoordinate()
|
|
local height=get_AGL(position)
|
|
local callsign=unit:GetCallsign()
|
|
|
|
-- Settings.
|
|
local settings=_DATABASE:GetPlayerSettings(self.player[id].playername) or _SETTINGS --Core.Settings#SETTINGS
|
|
|
|
local Hs=string.format("%d m", height)
|
|
if settings:IsMetric() then
|
|
Hs=string.format("%d ft", height*PSEUDOATC.unit.meter2feet)
|
|
end
|
|
|
|
-- Message text.
|
|
local _text=string.format("%s: Your altitude is %s AGL.", callsign, Hs)
|
|
|
|
-- Send message to player group.
|
|
--MESSAGE:New(text, dt):ToGroup(self.player[id].group)
|
|
self:_DisplayMessageToGroup(self.player[id].unit,_text, dt,_clear)
|
|
|
|
-- Return height
|
|
return height
|
|
end
|
|
|
|
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
--- Start altitude reporting scheduler.
|
|
-- @param #PSEUDOATC self.
|
|
-- @param #number id Group id of player unit.
|
|
function PSEUDOATC:AltidudeStartTimer(id)
|
|
self:F(id)
|
|
|
|
-- Debug info.
|
|
self:T(PSEUDOATC.id..string.format("Starting altitude report timer for player ID %d.", id))
|
|
|
|
-- Start timer.
|
|
--self.player[id].altimer=timer.scheduleFunction(self.ReportAltTouchdown, self, id, Tnow+2)
|
|
self.player[id].altimer, self.player[id].altimerid=SCHEDULER:New(nil, self.ReportHeight, {self, id, 0.1, true}, 1, 5)
|
|
end
|
|
|
|
--- Stop/destroy DCS scheduler function for reporting altitude.
|
|
-- @param #PSEUDOATC self.
|
|
-- @param #number id Group id of player unit.
|
|
function PSEUDOATC:AltidudeStopTimer(id)
|
|
|
|
-- Debug info.
|
|
self:T(PSEUDOATC.id..string.format("Stopping altitude report timer for player ID %d.", id))
|
|
|
|
-- Stop timer.
|
|
--timer.removeFunction(self.player[id].alttimer)
|
|
if self.player[id].altimerid then
|
|
self.player[id].altimer:Stop(self.player[id].altimerid)
|
|
end
|
|
|
|
self.player[id].altimer=nil
|
|
self.player[id].altimerid=nil
|
|
end
|
|
|
|
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
-- Misc
|
|
|
|
--- Create list of nearby airports sorted by distance to player unit.
|
|
-- @param #PSEUDOATC self
|
|
-- @param #number id Group id of player unit.
|
|
function PSEUDOATC:LocalAirports(id)
|
|
self:F(id)
|
|
|
|
-- Airports table.
|
|
self.player[id].airports=nil
|
|
self.player[id].airports={}
|
|
|
|
-- Current player position.
|
|
local pos=self.player[id].unit:GetCoordinate()
|
|
|
|
-- Loop over coalitions.
|
|
for i=0,2 do
|
|
|
|
-- Get all airbases of coalition.
|
|
local airports=coalition.getAirbases(i)
|
|
|
|
-- Loop over airbases
|
|
for _,airbase in pairs(airports) do
|
|
|
|
local name=airbase:getName()
|
|
local q=AIRBASE:FindByName(name):GetCoordinate()
|
|
local d=q:Get2DDistance(pos)
|
|
|
|
-- Add to table.
|
|
table.insert(self.player[id].airports, {distance=d, name=name})
|
|
|
|
end
|
|
end
|
|
|
|
--- compare distance (for sorting airports)
|
|
local function compare(a,b)
|
|
return a.distance < b.distance
|
|
end
|
|
|
|
-- Sort airports table w.r.t. distance to player.
|
|
table.sort(self.player[id].airports, compare)
|
|
|
|
end
|
|
|
|
--- Returns the unit of a player and the player name. If the unit does not belong to a player, nil is returned.
|
|
-- @param #PSEUDOATC self
|
|
-- @param #string _unitName Name of the player unit.
|
|
-- @return Wrapper.Unit#UNIT Unit of player.
|
|
-- @return #string Name of the player.
|
|
-- @return nil If player does not exist.
|
|
function PSEUDOATC:_GetPlayerUnitAndName(_unitName)
|
|
self:F(_unitName)
|
|
|
|
if _unitName ~= nil then
|
|
local DCSunit=Unit.getByName(_unitName)
|
|
local playername=DCSunit:getPlayerName()
|
|
|
|
|
|
if DCSunit and playername then
|
|
local unit=UNIT:Find(DCSunit)
|
|
return unit, playername
|
|
end
|
|
end
|
|
|
|
return nil,nil
|
|
end
|
|
|
|
|
|
--- Display message to group.
|
|
-- @param #PSEUDOATC self
|
|
-- @param Wrapper.Unit#UNIT _unit Player unit.
|
|
-- @param #string _text Message text.
|
|
-- @param #number _time Duration how long the message is displayed.
|
|
-- @param #boolean _clear Clear up old messages.
|
|
function PSEUDOATC:_DisplayMessageToGroup(_unit, _text, _time, _clear)
|
|
self:F({unit=_unit, text=_text, time=_time, clear=_clear})
|
|
|
|
_time=_time or self.Tmsg
|
|
if _clear==nil then
|
|
_clear=false
|
|
end
|
|
|
|
-- Group ID.
|
|
local _gid=_unit:GetGroup():GetID()
|
|
|
|
if _gid then
|
|
if _clear == true then
|
|
trigger.action.outTextForGroup(_gid, _text, _time, _clear)
|
|
else
|
|
trigger.action.outTextForGroup(_gid, _text, _time)
|
|
end
|
|
end
|
|
|
|
end
|
|
|