--- **Functional** - Basic ATC. -- -- ![Banner Image](..\Presentations\PSEUDOATC\PSEUDOATC_Main.jpg) -- -- ==== -- -- The pseudo ATC enhances the standard DCS ATC functions. -- -- In particular, a menu entry "Pseudo ATC" is created in the "F10 Other..." radiomenu. -- -- ## Features: -- -- * Weather report at nearby airbases and mission waypoints. -- * Report absolute bearing and range to nearest airports and mission waypoints. -- * Report current altitude AGL of own aircraft. -- * Upon request, ATC reports altitude until touchdown. -- * Works with static and dynamic weather. -- * Player can select the unit system (metric or imperial) in which information is reported. -- * All maps supported (Caucasus, NTTR, Normandy, Persian Gulf and all future maps). -- -- ==== -- -- # YouTube Channel -- -- ### [MOOSE YouTube Channel](https://www.youtube.com/channel/UCjrA9j5LQoWsG4SpS8i79Qg) -- -- === -- -- ### Author: **funkyfranky** -- -- ### Contributions: FlightControl, Applevangelist -- -- ==== -- @module Functional.PseudoATC -- @image Pseudo_ATC.JPG ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- PSEUDOATC class -- @type PSEUDOATC -- @field #string ClassName Name of the Class. -- @field #table player Table comprising each player info. -- @field #boolean Debug If true, print debug info to dcs.log file. -- @field #number mdur Duration in seconds how low messages to the player are displayed. -- @field #number mrefresh Interval in seconds after which the F10 menu is refreshed. E.g. by the closest airports. Default is 120 sec. -- @field #number talt Interval in seconds between reporting altitude until touchdown. Default 3 sec. -- @field #boolean chatty Display some messages on events like take-off and touchdown. -- @field #boolean eventsmoose [Deprecated] If true, events are handled by MOOSE. If false, events are handled directly by DCS eventhandler. -- @field #boolean reportplayername If true, use playername not callsign on callouts -- @extends Core.Base#BASE --- Adds some rudimentary ATC functionality via the radio menu. -- -- Local weather reports can be requested for nearby airports and player's mission waypoints. -- The weather report includes -- -- * QFE and QNH pressures, -- * Temperature, -- * Wind direction and strength. -- -- The list of airports is updated every 60 seconds. This interval can be adjusted by the function @{#PSEUDOATC.SetMenuRefresh}(*interval*). -- -- Likewise, absolute bearing and range to the close by airports and mission waypoints can be requested. -- -- The player can switch the unit system in which all information is displayed during the mission with the MOOSE settings radio menu. -- The unit system can be set to either imperial or metric. Altitudes are reported in feet or meter, distances in kilometers or nautical miles, -- temperatures in degrees Fahrenheit or Celsius and QFE/QNH pressues in inHg or mmHg. -- Note that the pressures are also reported in hPa independent of the unit system setting. -- -- In bad weather conditions, the ATC can "talk you down", i.e. will continuously report your altitude on the final approach. -- Default reporting time interval is 3 seconds. This can be adjusted via the @{#PSEUDOATC.SetReportAltInterval}(*interval*) function. -- The reporting stops automatically when the player lands or can be stopped manually by clicking on the radio menu item again. -- So the radio menu item acts as a toggle to switch the reporting on and off. -- -- ## Scripting -- -- Scripting is almost trivial. Just add the following two lines to your script: -- -- pseudoATC=PSEUDOATC:New() -- pseudoATC:Start() -- -- -- @field #PSEUDOATC PSEUDOATC={ ClassName = "PSEUDOATC", group={}, Debug=false, mdur=30, mrefresh=120, talt=3, chatty=true, eventsmoose=true, reportplayername = false, } ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Some ID to identify who we are in output of the DCS.log file. -- @field #string id PSEUDOATC.id="PseudoATC | " --- PSEUDOATC version. -- @field #number version PSEUDOATC.version="0.10.6" ----------------------------------------------------------------------------------------------------------------------------------------- -- TODO list -- DONE: Add takeoff event. -- DONE: Add user functions. -- DONE: Refactor to use Moose event handling only ----------------------------------------------------------------------------------------------------------------------------------------- --- PSEUDOATC contructor. -- @param #PSEUDOATC self -- @return #PSEUDOATC Returns a PSEUDOATC object. function PSEUDOATC:New() -- Inherit BASE. local self=BASE:Inherit(self, BASE:New()) -- #PSEUDOATC -- Debug info self:E(PSEUDOATC.id..string.format("PseudoATC version %s", PSEUDOATC.version)) -- Return object. return self end --- Starts the PseudoATC event handlers. -- @param #PSEUDOATC self function PSEUDOATC:Start() self:F() -- Debug info self:I(PSEUDOATC.id.."Starting PseudoATC") -- Handle events. self:HandleEvent(EVENTS.Birth, self._OnBirth) self:HandleEvent(EVENTS.Land, self._PlayerLanded) self:HandleEvent(EVENTS.Takeoff, self._PlayerTakeOff) self:HandleEvent(EVENTS.PlayerLeaveUnit, self._PlayerLeft) self:HandleEvent(EVENTS.Crash, self._PlayerLeft) end ----------------------------------------------------------------------------------------------------------------------------------------- -- User Functions --- Debug mode on. Send messages to everone. -- @param #PSEUDOATC self function PSEUDOATC:DebugOn() self.Debug=true end --- Debug mode off. This is the default setting. -- @param #PSEUDOATC self function PSEUDOATC:DebugOff() self.Debug=false end --- Chatty mode on. Display some messages on take-off and touchdown. -- @param #PSEUDOATC self function PSEUDOATC:ChattyOn() self.chatty=true end --- Chatty mode off. Don't display some messages on take-off and touchdown. -- @param #PSEUDOATC self function PSEUDOATC:ChattyOff() self.chatty=false end --- Set duration how long messages are displayed. -- @param #PSEUDOATC self -- @param #number duration (Optional) Time in seconds. Default is 30 sec. function PSEUDOATC:SetMessageDuration(duration) self.mdur=duration or 30 end --- Use player name, not call sign, in callouts -- @param #PSEUDOATC self function PSEUDOATC:SetReportPlayername() self.reportplayername = true return self end --- Set time interval after which the F10 radio menu is refreshed. -- @param #PSEUDOATC self -- @param #number interval (Optional) Interval in seconds. Default is every 120 sec. function PSEUDOATC:SetMenuRefresh(interval) self.mrefresh=interval or 120 end --- [Deprecated] Enable/disable event handling by MOOSE or DCS. -- @param #PSEUDOATC self -- @param #boolean switch (Optional) If true, events are handled by MOOSE (default). If false, events are handled directly by DCS. function PSEUDOATC:SetEventsMoose(switch) self.eventsmoose=switch end --- Set time interval for reporting altitude until touchdown. -- @param #PSEUDOATC self -- @param #number interval (Optional) Interval in seconds. Default is every 3 sec. function PSEUDOATC:SetReportAltInterval(interval) self.talt=interval or 3 end ----------------------------------------------------------------------------------------------------------------------------------------- -- Event Handling --- 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) local _unit = EventData.IniUnit local _playername = EventData.IniPlayerName -- 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) local _unit = EventData.IniUnit local _playername = EventData.IniPlayerName -- 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 = EventData.IniUnit local _playername = EventData.IniPlayerName --local _unit, _playername=self:_GetPlayerUnitAndName(_unitName) local _base=nil local _baseName=nil if EventData.place then _base=EventData.place _baseName=EventData.place:getName() end -- Call landed function. if _unit and _playername and _base then self:PlayerLanded(_unit, _baseName) end end --- Function called by MOOSE/DCS event handler when a player took off. -- @param #PSEUDOATC self -- @param Core.Event#EVENTDATA EventData function PSEUDOATC:_PlayerTakeOff(EventData) self:F({EventData=EventData}) -- Get unit, player and place. local _unitName=EventData.IniUnitName local _unit = EventData.IniUnit local _playername = EventData.IniPlayerName --local _unit,_playername=self:_GetPlayerUnitAndName(_unitName) local _base=nil local _baseName=nil if EventData.place then _base=EventData.place _baseName=EventData.place:getName() end -- Call take-off function. if _unit and _playername and _base then self:PlayerTakeOff(_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() local UID=unit:GetDCSObject():getID() if not self.group[GID] then self.group[GID]={} self.group[GID].player={} end -- Init player table. self.group[GID].player[UID]={} self.group[GID].player[UID].group=group self.group[GID].player[UID].unit=unit self.group[GID].player[UID].groupname=GroupName self.group[GID].player[UID].unitname=UnitName self.group[GID].player[UID].playername=PlayerName self.group[GID].player[UID].callsign=CallSign self.group[GID].player[UID].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" local countPlayerInGroup = 0 for _ in pairs(self.group[GID].player) do countPlayerInGroup = countPlayerInGroup + 1 end if countPlayerInGroup <= 1 then self.group[GID].menu_main=missionCommands.addSubMenuForGroup(GID, "Pseudo ATC") end -- Create/update custom menu for player self:MenuCreatePlayer(GID,UID) -- Create/update list of nearby airports. self:LocalAirports(GID,UID) -- Create submenu of local airports. self:MenuAirports(GID,UID) -- Create submenu Waypoints. self:MenuWaypoints(GID,UID) -- Start scheduler to refresh the F10 menues. self.group[GID].player[UID].scheduler, self.group[GID].player[UID].schedulerid=SCHEDULER:New(nil, self.MenuRefresh, {self, GID, UID}, 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 GID=group:GetID() local UID=unit:GetDCSObject():getID() local PlayerName = unit:GetPlayerName() or "Ghost" local UnitName = unit:GetName() or "Ghostplane" local GroupName = group:GetName() or "Ghostgroup" if self.Debug then -- Debug message. local text=string.format("Player %s in unit %s of group %s landed at %s.", PlayerName, UnitName, GroupName, place) self:T(PSEUDOATC.id..text) MESSAGE:New(text, 30):ToAllIf(self.Debug) end -- Stop altitude reporting timer if its activated. self:AltitudeTimerStop(GID,UID) -- Welcome message. if place and self.chatty then local text=string.format("Touchdown! Welcome to %s pilot %s. Have a nice day!", place,PlayerName) MESSAGE:New(text, self.mdur):ToGroup(group) end end --- Function called when a player took off. -- @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:PlayerTakeOff(unit, place) self:F2({unit=unit, place=place}) -- Gather some information. local group=unit:GetGroup() local PlayerName = unit:GetPlayerName() or "Ghost" local UnitName = unit:GetName() or "Ghostplane" local GroupName = group:GetName() or "Ghostgroup" local CallSign = unit:GetCallsign() or "Ghost11" if self.Debug then -- Debug message. local text=string.format("Player %s in unit %s of group %s took off at %s.", PlayerName, UnitName, GroupName, place) self:T(PSEUDOATC.id..text) MESSAGE:New(text, 30):ToAllIf(self.Debug) end -- Bye-Bye message. if place and self.chatty then local text=string.format("%s, %s, you are airborne. Have a safe trip!", place, CallSign) if self.reportplayername then text=string.format("%s, %s, you are airborne. Have a safe trip!", place, PlayerName) end 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:F({unit=unit}) -- Get id. local group=unit:GetGroup() local GID=group:GetID() local UID=unit:GetDCSObject():getID() if self.group[GID] and self.group[GID].player and self.group[GID].player[UID] then local PlayerName=self.group[GID].player[UID].playername local CallSign=self.group[GID].player[UID].callsign local UnitName=self.group[GID].player[UID].unitname local GroupName=self.group[GID].player[UID].groupname -- Debug message. local text=string.format("Player %s (callsign %s) of group %s just left unit %s.", PlayerName, CallSign, GroupName, UnitName) self:T(PSEUDOATC.id..text) MESSAGE:New(text, 30):ToAllIf(self.Debug) -- Stop scheduler for menu updates if self.group[GID].player[UID].schedulerid then self.group[GID].player[UID].scheduler:Stop(self.group[GID].player[UID].schedulerid) end -- Stop scheduler for reporting alt if it runs. self:AltitudeTimerStop(GID,UID) -- Remove own menu. if self.group[GID].player[UID].menu_own then missionCommands.removeItemForGroup(GID,self.group[GID].player[UID].menu_own) end -- Remove main menu. -- WARNING: Remove only if last human element of group local countPlayerInGroup = 0 for _ in pairs(self.group[GID].player) do countPlayerInGroup = countPlayerInGroup + 1 end if self.group[GID].menu_main and countPlayerInGroup==1 then missionCommands.removeItemForGroup(GID,self.group[GID].menu_main) end -- Remove player array. self.group[GID].player[UID]=nil end end ----------------------------------------------------------------------------------------------------------------------------------------- -- Menu Functions --- Refreshes all player menues. -- @param #PSEUDOATC self. -- @param #number GID Group id of player unit. -- @param #number UID Unit id of player. function PSEUDOATC:MenuRefresh(GID,UID) self:F({GID=GID,UID=UID}) -- Debug message. local text=string.format("Refreshing menues for player %s in group %s.", self.group[GID].player[UID].playername, self.group[GID].player[UID].groupname) self:T(PSEUDOATC.id..text) MESSAGE:New(text,30):ToAllIf(self.Debug) -- Clear menu. self:MenuClear(GID,UID) -- Create list of nearby airports. self:LocalAirports(GID,UID) -- Create submenu Local Airports. self:MenuAirports(GID,UID) -- Create submenu Waypoints etc. self:MenuWaypoints(GID,UID) end --- Create player menus. -- @param #PSEUDOATC self. -- @param #number GID Group id of player unit. -- @param #number UID Unit id of player. function PSEUDOATC:MenuCreatePlayer(GID,UID) self:F({GID=GID,UID=UID}) -- Table for menu entries. local PlayerName=self.group[GID].player[UID].playername self.group[GID].player[UID].menu_own=missionCommands.addSubMenuForGroup(GID, PlayerName, self.group[GID].menu_main) end --- Clear player menus. -- @param #PSEUDOATC self. -- @param #number GID Group id of player unit. -- @param #number UID Unit id of player. function PSEUDOATC:MenuClear(GID,UID) self:F({GID=GID,UID=UID}) -- Debug message. local text=string.format("Clearing menus for player %s in group %s.", self.group[GID].player[UID].playername, self.group[GID].player[UID].groupname) self:T(PSEUDOATC.id..text) MESSAGE:New(text,30):ToAllIf(self.Debug) -- Delete Airports menu. if self.group[GID].player[UID].menu_airports then missionCommands.removeItemForGroup(GID, self.group[GID].player[UID].menu_airports) self.group[GID].player[UID].menu_airports=nil else self:T2(PSEUDOATC.id.."No airports to clear menus.") end -- Delete waypoints menu. if self.group[GID].player[UID].menu_waypoints then missionCommands.removeItemForGroup(GID, self.group[GID].player[UID].menu_waypoints) self.group[GID].player[UID].menu_waypoints=nil end -- Delete report alt until touchdown menu command. if self.group[GID].player[UID].menu_reportalt then missionCommands.removeItemForGroup(GID, self.group[GID].player[UID].menu_reportalt) self.group[GID].player[UID].menu_reportalt=nil end -- Delete request current alt menu command. if self.group[GID].player[UID].menu_requestalt then missionCommands.removeItemForGroup(GID, self.group[GID].player[UID].menu_requestalt) self.group[GID].player[UID].menu_requestalt=nil end end --- Create "F10/Pseudo ATC/Local Airports/Airport Name/" menu items each containing weather report and BR request. -- @param #PSEUDOATC self -- @param #number GID Group id of player unit. -- @param #number UID Unit id of player. function PSEUDOATC:MenuAirports(GID,UID) self:F({GID=GID,UID=UID}) -- Table for menu entries. self.group[GID].player[UID].menu_airports=missionCommands.addSubMenuForGroup(GID, "Local Airports", self.group[GID].player[UID].menu_own) local i=0 for _,airport in pairs(self.group[GID].player[UID].airports) do i=i+1 if i > 10 then break -- Max 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(GID, name, self.group[GID].player[UID].menu_airports) -- Create menu reporting commands missionCommands.addCommandForGroup(GID, "Weather Report", submenu, self.ReportWeather, self, GID, UID, pos, name) missionCommands.addCommandForGroup(GID, "Request BR", submenu, self.ReportBR, self, GID, UID, pos, name) if self.radios then missionCommands.addCommandForGroup(GID, "Radios", submenu, self.ReportRadios, self, GID, UID, pos, name) end -- Debug message. self:T(string.format(PSEUDOATC.id.."Creating airport menu item %s for ID %d", name, GID)) end end --- Create "F10/Pseudo ATC/Waypoints/ menu items. -- @param #PSEUDOATC self -- @param #number GID Group id of player unit. -- @param #number UID Unit id of player. function PSEUDOATC:MenuWaypoints(GID, UID) self:F({GID=GID, UID=UID}) -- Player unit and callsign. -- local unit=self.group[GID].player[UID].unit --Wrapper.Unit#UNIT local callsign=self.group[GID].player[UID].callsign -- Debug info. self:T(PSEUDOATC.id..string.format("Creating waypoint menu for %s (ID %d).", callsign, GID)) if #self.group[GID].player[UID].waypoints>0 then -- F10/PseudoATC/Waypoints self.group[GID].player[UID].menu_waypoints=missionCommands.addSubMenuForGroup(GID, "Waypoints", self.group[GID].player[UID].menu_own) local j=0 for i, wp in pairs(self.group[GID].player[UID].waypoints) do -- Increase counter j=j+1 if j>10 then break -- max ten menu entries end -- Position of Waypoint local pos=COORDINATE:New(wp.x, wp.alt, wp.y) local name=string.format("Waypoint %d", i-1) if wp.name and wp.name ~= "" then name = string.format("Waypoint %s",wp.name) end -- "F10/PseudoATC/Waypoints/Waypoint X" local submenu=missionCommands.addSubMenuForGroup(GID, name, self.group[GID].player[UID].menu_waypoints) -- Menu commands for each waypoint "F10/PseudoATC/My Aircraft (callsign)/Waypoints/Waypoint X/" missionCommands.addCommandForGroup(GID, "Weather Report", submenu, self.ReportWeather, self, GID, UID, pos, name) missionCommands.addCommandForGroup(GID, "Request BR", submenu, self.ReportBR, self, GID, UID, pos, name) end end self.group[GID].player[UID].menu_reportalt = missionCommands.addCommandForGroup(GID, "Talk me down", self.group[GID].player[UID].menu_own, self.AltidudeTimerToggle, self, GID, UID) self.group[GID].player[UID].menu_requestalt = missionCommands.addCommandForGroup(GID, "Request altitude", self.group[GID].player[UID].menu_own, self.ReportHeight, self, GID, UID) end ----------------------------------------------------------------------------------------------------------------------------------------- -- Reporting Functions --- Weather Report. Report pressure QFE/QNH, temperature, wind at certain location. -- @param #PSEUDOATC self -- @param #number GID Group id of player unit. -- @param #number UID Unit id of player. -- @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(GID, UID, position, location) self:F({GID=GID, UID=UID, position=position, location=location}) -- Player unit system settings. local settings=_DATABASE:GetPlayerSettings(self.group[GID].player[UID].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. -- Pressure conversion local hPa2inHg=0.0295299830714 local hPa2mmHg=0.7500615613030 -- Unit conversion. local _Pqnh=string.format("%.2f inHg", Pqnh * hPa2inHg) local _Pqfe=string.format("%.2f inHg", Pqfe * hPa2inHg) if settings:IsMetric() then _Pqnh=string.format("%.1f mmHg", Pqnh * hPa2mmHg) _Pqfe=string.format("%.1f mmHg", Pqfe * 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) -- Get temperature at position in degrees Celsius. local T=position:GetTemperature() -- Correct unit system. local _T=string.format('%d°F', UTILS.CelsiusToFahrenheit(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 knots", UTILS.MpsToKnots(Vel)) if settings:IsMetric() then Vs=string.format('%.1f m/s', Vel) end -- Message text. local text=text..string.format("%s, Wind from %s at %s (%s).", self.group[GID].player[UID].playername, Ds, Vs, Bd) -- Send message self:_DisplayMessageToGroup(self.group[GID].player[UID].unit, text, self.mdur, true) end --- Report airport radio information. -- @param #PSEUDOATC self -- @param #number GID Group id of player unit. -- @param #number UID Unit id of player. -- @param Core.Point#COORDINATE position Coordinate of the airport. -- @param #string location Name of the airport. function PSEUDOATC:ReportRadios(GID, UID, position, location) self:F({GID=GID, UID=UID, position=position, location=location}) if self.radios then local Text="" local radio = self.radios:GetClosestRadio(position,9) --Navigation.Radios#RADIOS.Radio if radio then Text=self.radios:_GetMarkerText(radio) else Text=self.group[GID].player[UID].playername..", no radio information found!" end -- Send message self:_DisplayMessageToGroup(self.group[GID].player[UID].unit, Text, self.mdur, true) end return self end --- Report absolute bearing and range from player unit to airport. -- @param #PSEUDOATC self -- @param #number GID Group id of player unit. -- @param #number UID Unit id of player. -- @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(GID, UID, position, location) self:F({GID=GID, UID=UID, position=position, location=location}) -- Current coordinates. local unit=self.group[GID].player[UID].unit --Wrapper.Unit#UNIT local coord=unit:GetCoordinate() -- Direction vector from current position (coord) to target (position). local angle=coord:HeadingTo(position) -- Range from current to local range=coord:Get2DDistance(position) -- Bearing string. local Bs=string.format('%03d°', angle) -- Settings. local settings=_DATABASE:GetPlayerSettings(self.group[GID].player[UID].playername) or _SETTINGS --Core.Settings#SETTINGS local Rs=string.format("%.1f NM", UTILS.MetersToNM(range)) if settings:IsMetric() then Rs=string.format("%.1f km", range/1000) end -- Message text. local text=string.format("%s: Bearing %s, Range %s.", location, Bs, Rs) -- Send message self:_DisplayMessageToGroup(self.group[GID].player[UID].unit, text, self.mdur, true) end --- Report altitude above ground level of player unit. -- @param #PSEUDOATC self -- @param #number GID Group id of player unit. -- @param #number UID Unit id of player. -- @param #number dt (Optional) Duration the message is displayed. -- @param #boolean _clear (Optional) Clear previouse messages. -- @return #number Altitude above ground. function PSEUDOATC:ReportHeight(GID, UID, dt, _clear) self:F({GID=GID, UID=UID, 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 agl=0 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.group[GID].player[UID].unit --Wrapper.Unit#UNIT if unit and unit:IsAlive() then local position=unit:GetCoordinate() local height=get_AGL(position) local callsign=unit:GetCallsign() local PlayerName=self.group[GID].player[UID].playername -- Settings. local settings=_DATABASE:GetPlayerSettings(self.group[GID].player[UID].playername) or _SETTINGS --Core.Settings#SETTINGS -- Height string. local Hs=string.format("%d ft", UTILS.MetersToFeet(height)) if settings:IsMetric() then Hs=string.format("%d m", height) end -- Message text. local _text=string.format("%s, your altitude is %s AGL.", callsign, Hs) if self.reportplayername then _text=string.format("%s, your altitude is %s AGL.", PlayerName, Hs) end -- Append flight level. if _clear==false then _text=_text..string.format(" FL%03d.", position.y/30.48) end -- Send message to player group. self:_DisplayMessageToGroup(self.group[GID].player[UID].unit,_text, dt,_clear) -- Return height return height end return 0 end ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Toggle report altitude reporting on/off. -- @param #PSEUDOATC self. -- @param #number GID Group id of player unit. -- @param #number UID Unit id of player. function PSEUDOATC:AltidudeTimerToggle(GID,UID) self:F({GID=GID, UID=UID}) if self.group[GID].player[UID].altimerid then -- If the timer is on, we turn it off. self:AltitudeTimerStop(GID, UID) else -- If the timer is off, we turn it on. self:AltitudeTimeStart(GID, UID) end end --- Start altitude reporting scheduler. -- @param #PSEUDOATC self. -- @param #number GID Group id of player unit. -- @param #number UID Unit id of player. function PSEUDOATC:AltitudeTimeStart(GID, UID) self:F({GID=GID, UID=UID}) -- Debug info. self:T(PSEUDOATC.id..string.format("Starting altitude report timer for player ID %d.", UID)) -- Start timer. Altitude is reported every ~3 seconds. self.group[GID].player[UID].altimer, self.group[GID].player[UID].altimerid=SCHEDULER:New(nil, self.ReportHeight, {self, GID, UID, 1, true}, 1, 3) end --- Stop/destroy DCS scheduler function for reporting altitude. -- @param #PSEUDOATC self. -- @param #number GID Group id of player unit. -- @param #number UID Unit id of player. function PSEUDOATC:AltitudeTimerStop(GID, UID) self:F({GID=GID,UID=UID}) -- Debug info. self:T(PSEUDOATC.id..string.format("Stopping altitude report timer for player ID %d.", UID)) -- Stop timer. if self.group[GID].player[UID].altimerid then self.group[GID].player[UID].altimer:Stop(self.group[GID].player[UID].altimerid) end self.group[GID].player[UID].altimer=nil self.group[GID].player[UID].altimerid=nil end ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Misc --- Create list of nearby airports sorted by distance to player unit. -- @param #PSEUDOATC self -- @param #number GID Group id of player unit. -- @param #number UID Unit id of player. function PSEUDOATC:LocalAirports(GID, UID) self:F({GID=GID, UID=UID}) -- Airports table. self.group[GID].player[UID].airports=nil self.group[GID].player[UID].airports={} -- Current player position. local pos=self.group[GID].player[UID].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 a=AIRBASE:FindByName(name) if a then local q=a:GetCoordinate() local d=q:Get2DDistance(pos) -- Add to table. table.insert(self.group[GID].player[UID].airports, {distance=d, name=name}) end 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.group[GID].player[UID].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 -- Get DCS unit from its name. local DCSunit=Unit.getByName(_unitName) if DCSunit then -- Get the player name to make sure a player entered. local playername=DCSunit:getPlayerName() local unit=UNIT:Find(DCSunit) -- Debug output. self:T2({DCSunit=DCSunit, unit=unit, playername=playername}) if unit and playername then -- Return MOOSE unit and player name return unit, playername end 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 --- Returns a string which consits of this callsign and the player name. -- @param #PSEUDOATC self -- @param #string unitname Name of the player unit. function PSEUDOATC:_myname(unitname) self:F2(unitname) local unit=UNIT:FindByName(unitname) local pname=unit:GetPlayerName() local csign=unit:GetCallsign() return string.format("%s (%s)", csign, pname) end --- Returns a string which consits of this callsign and the player name. -- @param #PSEUDOATC self -- @param #string path Path to map data, e.g. `[[\Mods\terrains\\Radio.lua]]` (replace with correct path). -- Needs `lfs` and `io` to be desanitized in the `MissionScripting.lua` in `\Scripts` -- @return #PSEUDOATC self -- @usage -- -- mypseudoatc:SetUsingRadioInformationFromMap([[C:\Program Files\Eagle Dynamics\DCS World.Openbeta\Mods\terrains\Caucasus\Radio.lua]]) -- function PSEUDOATC:SetUsingRadioInformationFromMap(path) if RADIOS and lfs and io then self.radios = RADIOS:NewFromFile(path) else self:E("PSEUDOATC:SetUsingRadioInformationFromMap Needs `lfs`and `io` to be desanitized in the `MissionScripting.lua` in `/Scripts`") end return self end