# # Red Griffin ATC - Speaking Air Traffic Controller for FlightGear # # Written and developer by Antonello Biancalana (Red Griffin, IK0TOJ) # # Copyright (C) 2019-2021 Antonello Biancalana # # rgatc.nas # # Main program # # Version 2.3.0 - 7 May 2021 # # Red Griffin ATC is an Open Source project and it is licensed # under the Gnu Public License v3 (GPLv3) # var rgatcKeyboardListener = setlistener("/devices/status/keyboard/event", func(event) { if(!event.getNode("pressed").getValue()) return; var key = event.getNode("key").getValue(); var shift = event.getNode("modifier/shift").getValue(); var ctrl = event.getNode("modifier/ctrl").getValue(); var minCruiseAltidude = getMinCruiseAltitude(); if(key == last_key) return; if(isAtcEnabled() == 0) { #showPopup(RGAtcName ~ " is not enabled.\nTurn it on from the menu"); showPopup(RGAtcName ~ " 未启用.\nTurn it on from the menu"); return; } last_key = key; if(key == binding_key_dialog) { if(initialized == 0) initRgATC(); if(!rgatcTimer.isRunning) rgatcTimer.start(); if(ctrl == 0) { last_key = 0; if(dialogOpened == 0) openATCDialog(); else closeATCDialog(); } else updateATC(update_popup); return; } else if(ctrl == 1 and shift == 0) { if(key == binding_key_msg1) pilotRequest(atcMessageType[0]); else if(key == binding_key_msg2) pilotRequest(atcMessageType[1]); else if(key == binding_key_msg3) pilotRequest(atcMessageType[2]); else if(key == binding_key_msg4) pilotRequest(atcMessageType[3]); else if(key == binding_key_request_ctr) pilotRequest(message_request_ctr); else if(key == binding_key_repeat_last_atc_message) pilotRequest(message_say_again); } else if(ctrl == 1 and shift == 1) { if(key == binding_key_flight_level_1 and aircraft_status == status_flying and flightLevel1 >= minCruiseAltidude) { requestedAltitude = flightLevel1; pilotRequest(message_request_fl); } else if(key == binding_key_flight_level_2 and aircraft_status == status_flying and flightLevel2 >= minCruiseAltidude) { requestedAltitude = flightLevel2; pilotRequest(message_request_fl); } else if(key == binding_key_flight_level_3 and aircraft_status == status_flying and flightLevel3 >= minCruiseAltidude and property_Aircraft_AltitudeFeet.getValue() > flightLevel3) { requestedAltitude = flightLevel3; pilotRequest(message_request_fl); } else if(key == binding_key_abort_approach) pilotRequest(message_abort_approach); } }, 1); var rgatcTimer = maketimer(timer_interval, func() { var isTerrainSafe = 1; var radioIsTunedToApprovedCtrRadio = 0; var minCruiseAltidude = getMinCruiseAltitude(); if(isSimulationPaused() == 1 or isAtcEnabled() == 0) return; if(initialized == 0) initRgATC(); updateATC((dialogOpened == 1) ? update_dialog : update_data_only); if(atc_callback_wait_seconds != auto_reply_off) { atc_callback_seconds_counter += timer_interval; if(atc_callback_seconds_counter >= atc_callback_wait_seconds) { atc_callback_seconds_counter = 0; atc_callback_wait_seconds = auto_reply_off; atcAutoMessage(); } } if(pilot_message_wait_seconds > 0) { pilot_message_counter += timer_interval; if(pilot_message_counter >= pilot_message_wait_seconds) { pilot_message_wait_seconds = -1; atcReplyToRequest(pilotMessageType); pilotMessageType = message_none; } } if(pilot_response_wait_seconds > 0) { pilot_response_counter += timer_interval; if(pilot_response_counter >= pilot_response_wait_seconds) { pilot_response_wait_seconds = -1; pilotResponse(pilotResponseType); pilotResponseType = message_none; } } key_press_counter += timer_interval; ctr_update_counter += timer_interval; ctr_check_counter += timer_interval; if(aircraft_status == status_flying or aircraft_status == status_took_off) flight_time_seconds += timer_interval; if(key_press_counter > reset_key_interval) { key_press_counter = 0; last_key = 0; } radioIsTunedToApprovedCtrRadio = isRadioTunedToApprovedCtrRadio(); if(ctr_check_counter > ctr_check_interval) { if(currentCtr != nil and approvedCtr == nil and aircraft_status == status_flying and radioIsTunedToApprovedCtrRadio == 1) { atcReplyToRequest(message_not_in_this_ctr); } ctr_check_counter = 0; } if(ctr_update_counter > ctr_update_interval) { if(approvedCtr != nil) { currentCtr = getCtrData(airportinfo(approvedCtr.ident)); if(currentCtr.distance > (currentCtr.range - ctr_leaving_range)) { if(radioIsTunedToApprovedCtrRadio == 1) atcReplyToRequest(message_leaving_ctr); ctr_leaving_warning = 1; approvedCtr = nil; altitude_check_counter = 0; resetAircraftStatus(); setCtrButtons(); } } else currentCtr = getNearbyCtr(ctr_search_range); ctr_update_counter = 0; var aircraftPosition = geo.aircraft_position(); var terrainInfo = geodinfo(aircraftPosition.lat(), aircraftPosition.lon()); isTerrainSafe = isTerrainSafeAhead(); if(terrainInfo != nil) altitudeFromTerrain = property_Aircraft_AltitudeFeet.getValue() - (terrainInfo[0] * M2FT); else altitudeFromTerrain = -1; if((altitudeFromTerrain < min_altitude_from_terrain or isTerrainSafe == 0) and terrainWarning == 0 and (aircraft_status == status_flying or aircraft_status == status_requested_approach or aircraft_status == status_requested_ils) and flight_time_seconds > take_off_max_seconds) { if(approvedCtr.ident == currentCtr.ident and radioIsTunedToApprovedCtrRadio == 1) { if(altitudeFromTerrain > min_altitude_too_low_warning) { if(altitudeFromTerrain < min_altitude_from_terrain and tooLowWarningMode == "On") atcReplyToRequest(message_flying_too_low); else if(isTerrainSafe == 0 and terrainWarningMode == "On") atcReplyToRequest(message_terrain_ahead); } terrainWarning = 1; terrain_warning_counter = 0; } } if(aircraft_status == status_flying) { var heading = property_Aircraft_HeadingDeg.getValue(); flightLevel2 = normalizeAltitudeHeading(property_Aircraft_AltitudeFeet.getValue(), heading, minCruiseAltidude); flightLevel1 = flightLevel2 + flight_level_step; flightLevel3 = flightLevel2 - flight_level_step; if(flightLevel2 < minCruiseAltidude) flightLevel2 = normalizeAltitudeHeading(minCruiseAltidude, heading, minCruiseAltidude); if(flightLevel3 < minCruiseAltidude) flightLevel3 = normalizeAltitudeHeading(minCruiseAltidude, heading, minCruiseAltidude); } } if(terrainWarning == 1) { terrain_warning_counter += timer_interval; if(terrain_warning_counter >= terrain_warning_interval) { if(pilot_message_wait_seconds > 0) pilot_message_wait_seconds += 10; if(altitude_change_wait_seconds > 0) altitude_change_wait_seconds += 15; altitude_check_counter = 0; if(altitudeFromTerrain > min_altitude_too_low_warning) { if(altitudeFromTerrain < min_altitude_from_terrain) atcReplyToRequest(message_flying_too_low); else if(isTerrainSafe == 0) atcReplyToRequest(message_terrain_ahead); else terrainWarning = 0; } else terrainWarning = 0; terrain_warning_counter = 0; } } if(assignedAltitude != -1 and aircraft_status == status_flying) { altitude_check_counter += timer_interval; if(approvedCtr != nil) { if(altitude_check_counter >= altitude_check_interval and radioIsTunedToApprovedCtrRadio == 1) { var fpalt = getFlightPlanAltitude(); if(fpalt != -1 and assignedAltitude != fpalt) { atcReplyToRequest(message_change_altitude); } else { if(property_Aircraft_HeadingDeg.getValue() > 180) altitudeZone = altitude_zone_even; else altitudeZone = altitude_zone_odd; if(currentAltitudeZone != altitudeZone) { currentAltitudeZone = altitudeZone; atcReplyToRequest(message_change_altitude); } else if(abs(property_Aircraft_AltitudeFeet.getValue() - assignedAltitude) > assigned_altitude_delta) atcReplyToRequest(message_check_altitude); } altitude_check_counter = 0; altitude_check_interval = flightLevelChangeSeconds(assignedAltitude); } } } if(altitude_change_wait_seconds > 0) { altitude_change_counter += timer_interval; if(approvedCtr != nil) { if(altitude_change_counter >= altitude_change_wait_seconds and aircraft_status == status_flying and radioIsTunedToApprovedCtrRadio == 1) { altitude_change_wait_seconds = -1; atcReplyToRequest(message_change_altitude); } } } if(aircraft_status == status_requested_approach or aircraft_status == status_requested_ils) { approach_check_counter += timer_interval; if(approach_check_counter > approach_check_interval and radioIsTunedToApprovedCtrRadio == 1) { assistedAtcApproach(); approach_check_counter = 0; } } if(squawkingMode == "On") { if(aircraft_status == status_flying or aircraft_status == status_requested_approach or aircraft_status == status_requested_ils) { squawk_check_counter += timer_interval; if(checkTransponderIdent(selectedTransponder) == 1) squawkIdentButtonPushed = 1; if(squawk_check_counter > squawk_check_interval) { if(squawkIdent != squawk_ident_off) { if(checkTransponder(selectedTransponder) == 1 and squawkIdentButtonPushed == 1) setSquawkIdentMode(squawk_ident_off); else atcReplyToRequest(message_check_transponder); } else if(checkTransponder(selectedTransponder) == 0) atcReplyToRequest(message_check_transponder); squawk_check_counter = 0; } } } }); var initRgATC = func() { initSettings(); var aircraftDescription = split(" ", getprop("/sim/description")); RGAtcName = getprop(RGAtcAddonProp ~ "name"); RGAtcVersion = getprop(RGAtcAddonProp ~ "version"); setRGTitle(); if(size(aircraftDescription) > 0) aircraftManufacturer = aircraftDescription[0]; else aircraftManufacturer = ""; rgatcTimer.start(); initialized = 1; setsize(radioButton, max_radios); setsize(radioButtonFrequency, max_radios); for(var i = 0; i < max_radios; i += 1) { radioButton[i] = nil; radioButtonFrequency[i] = 0; } var pos = geo.aircraft_position(); var range = min_airport_range; if(currentCtr != nil) range += int((currentCtr.range / 1.5) / 10); var airport = findAirportsWithinRange(pos, range); if(size(airport) > 0) { currentAirport = airport[0]; getAvailableRadios(); currentRadioStationId = currentAirport.id; } aircraftIsDeparting = 1; approachStatus = approach_status_none; approach_check_interval = approach_check_interval_initial; currentCtr = getNearbyCtr(ctr_search_range); (atcCallsignText, atcCallsignVoice) = getCallSignForAtc(1); pilotVoice = getPilotVoice(); initAtcVoice(); print(RGAtcName ~ " successfully initialized and ready"); RGAtcEnabled = 1; clearAtcLog(); } var enableAtc = func() { if(!rgatcTimer.isRunning) rgatcTimer.start(); RGAtcEnabled = 1; openATCDialog(); showPopup(RGAtcName ~ " is on"); } var disableAtc = func() { if(rgatcTimer.isRunning) rgatcTimer.stop(); RGAtcEnabled = 0; closeATCDialog(); showPopup(RGAtcName ~ " is off"); } var isAtcEnabled = func() { return RGAtcEnabled; } var toggleAtc = func() { if(isAtcEnabled() == 1) disableAtc(); else enableAtc(); } var setRGTitle = func() { RGAtcTitle = "Red Griffin ATC"; if(RGAtcName != nil and RGAtcName != "") { RGAtcTitle = RGAtcName; if(RGAtcVersion != nil and RGAtcVersion != "") RGAtcTitle ~= " " ~ RGAtcVersion; } else RGAtcName = RGAtcTitle; RGAtcTitle ~= " - Aircraft: " ~ aircraftType; if(dlgWindow != nil) dlgWindow.setTitle(RGAtcTitle); } var initRgATCDialog = func() { dlgWindow = canvas.Window.new([dialogWidth, dialogHeight], "dialog") .setTitle(RGAtcTitle); dlgWindow.del = func() { dialogInitialized = 0; dialogOpened = 0; call(canvas.Window.del, [], me); }; dlgCanvas = dlgWindow.createCanvas().set("background", canvas.style.getColor("bg_color")); dlgCanvas.setColorBackground(0.5, 0.5, 0.5, 0.9); dlgRoot = dlgCanvas.createGroup(); dlgLayout = canvas.VBoxLayout.new(); dlgCanvas.setLayout(dlgLayout); txtAirport = dlgRoot.createChild("text") .setText("") .setFont("LiberationFonts/LiberationSans-Bold.ttf") .setFontSize(14, 0.9) .setColor(1,1,1,1) .setAlignment("left-center") .setTranslation(10, 20); txtAircraftPosition = dlgRoot.createChild("text") .setText("") .setFont("LiberationFonts/LiberationSans-Bold.ttf") .setFontSize(14, 0.9) .setColor(1,1,1,1) .setAlignment("left-center") .setTranslation(10, 36); txtCurrentCtr = dlgRoot.createChild("text") .setText("") .setFont("LiberationFonts/LiberationSans-Bold.ttf") .setFontSize(14, 0.9) .setColor(0.6,1,1,1) .setAlignment("left-center") .setTranslation(10, 60); txtCurrentCtrSpecs = dlgRoot.createChild("text") .setText("") .setFont("LiberationFonts/LiberationSans-Bold.ttf") .setFontSize(14, 0.9) .setColor(0.6,1,1,1) .setAlignment("left-center") .setTranslation(10, 76); txtCurrentRadio = dlgRoot.createChild("text") .setText("") .setFont("LiberationFonts/LiberationSans-Bold.ttf") .setFontSize(14, 0.9) .setColor(1,1,0,1) .setAlignment("left-center") .setTranslation(10, 100); txtCurrentRadioSpecs = dlgRoot.createChild("text") .setText("") .setFont("LiberationFonts/LiberationSans-Bold.ttf") .setFontSize(14, 0.9) .setColor(1,1,0,1) .setAlignment("left-center") .setTranslation(10, 116); var buttonBox = canvas.HBoxLayout.new(); var middleBox = canvas.HBoxLayout.new(); var messageBox = canvas.VBoxLayout.new(); var topButtonBox = canvas.HBoxLayout.new(); btnRepeatATCMessage = canvas.gui.widgets.Button.new(dlgRoot, canvas.style, {}) .setText("R") .setFixedSize(repeatBtnWidth, 26); btnRepeatATCMessage.hide(); btnRepeatATCMessage.listen("clicked", func { pilotRequest(message_say_again); dlgWindow.clearFocus(); }); btnRepeatATCMessage.hide(); btnMessage[0] = canvas.gui.widgets.Button.new(dlgRoot, canvas.style, {}) .setText("") .setFixedSize((dialogWidth / 2) - 20, 26); btnMessage[0].listen("clicked", func { pilotRequest(atcMessageType[0]); dlgWindow.clearFocus(); }); btnMessage[1] = canvas.gui.widgets.Button.new(dlgRoot, canvas.style, {}) .setText("") .setFixedSize((dialogWidth / 2) - 20, 26); btnMessage[1].listen("clicked", func { pilotRequest(atcMessageType[1]); dlgWindow.clearFocus(); }); btnMessage[2] = canvas.gui.widgets.Button.new(dlgRoot, canvas.style, {}) .setText("") .setFixedSize((dialogWidth / 2) - 20, 26); btnMessage[2].listen("clicked", func { pilotRequest(atcMessageType[2]); dlgWindow.clearFocus(); }); btnMessage[3] = canvas.gui.widgets.Button.new(dlgRoot, canvas.style, {}) .setText("") .setFixedSize((dialogWidth / 2) - 20, 26); btnMessage[3].listen("clicked", func { pilotRequest(atcMessageType[3]); dlgWindow.clearFocus(); }); for(var i = 0; i < 4; i += 1) btnMessage[i].hide(); topButtonBox.addItem(btnRepeatATCMessage); topButtonBox.addItem(btnMessage[0]); messageBox.addItem(topButtonBox); messageBox.addItem(btnMessage[1]); messageBox.addItem(btnMessage[2]); messageBox.addItem(btnMessage[3]); radioBox = canvas.VBoxLayout.new(); radioBox.hide(); radioScroll = canvas.gui.widgets.ScrollArea.new(dlgRoot, canvas.style, {size: [(dialogWidth / 2) - 20, 80]}); radioScroll.setColorBackground(1.0, 1.0, 0.9, 0.9); radioBox.addItem(radioScroll, 1); radioScrollContent = radioScroll.getContent() .set("font", "LiberationFonts/LiberationSans-Bold.ttf") .set("character-size", 16) .set("alignment", "left-center"); middleBox.addItem(messageBox); middleBox.addItem(radioBox); btnRequestCtr = canvas.gui.widgets.Button.new(dlgRoot, canvas.style, {}) .setText("Request CTR") .setFixedSize(114, 26); btnRequestCtr.setEnabled(0); btnRequestCtr.listen("clicked", func { pilotRequest(message_request_ctr); dlgWindow.clearFocus(); }); var minCruiseAltidude = getMinCruiseAltitude(); btnRequestFlightLevel1 = canvas.gui.widgets.Button.new(dlgRoot, canvas.style, {}) .setText("--") .setFixedSize(58, 26); btnRequestFlightLevel1.setEnabled(0); btnRequestFlightLevel1.listen("clicked", func { if(flightLevel1 >= minCruiseAltidude) { requestedAltitude = flightLevel1; pilotRequest(message_request_fl); } dlgWindow.clearFocus(); }); btnRequestFlightLevel2 = canvas.gui.widgets.Button.new(dlgRoot, canvas.style, {}) .setText("--") .setFixedSize(58, 26); btnRequestFlightLevel2.setEnabled(0); btnRequestFlightLevel2.listen("clicked", func { if(flightLevel2 >= minCruiseAltidude) { requestedAltitude = flightLevel2; pilotRequest(message_request_fl); } dlgWindow.clearFocus(); }); btnRequestFlightLevel3 = canvas.gui.widgets.Button.new(dlgRoot, canvas.style, {}) .setText("--") .setFixedSize(58, 26); btnRequestFlightLevel3.setEnabled(0); btnRequestFlightLevel3.listen("clicked", func { if(flightLevel3 >= minCruiseAltidude) { requestedAltitude = flightLevel3; pilotRequest(message_request_fl); } dlgWindow.clearFocus(); }); btnAbortApproach = canvas.gui.widgets.Button.new(dlgRoot, canvas.style, {}) .setText("--") .setFixedSize(140, 26); btnAbortApproach.setEnabled(0); btnAbortApproach.listen("clicked", func { pilotRequest(message_abort_approach); dlgWindow.clearFocus(); }); btnAvailableRadio = canvas.gui.widgets.Button.new(dlgRoot, canvas.style, {}) .setText("Radios") .setFixedSize(74, 26); btnAvailableRadio.setEnabled(0); btnAvailableRadio.listen("clicked", func { availableRadioDialog(); dlgWindow.clearFocus(); }); var btnClose = canvas.gui.widgets.Button.new(dlgRoot, canvas.style, {}) .setText("Close") .setFixedSize(70, 26); btnClose.listen("clicked", func { closeATCDialog(); }); setApproachButtons(); buttonBox.addStretch(1); buttonBox.addItem(btnRequestCtr); buttonBox.addStretch(1); buttonBox.addItem(btnAbortApproach); buttonBox.addItem(btnRequestFlightLevel1); buttonBox.addStretch(1); buttonBox.addItem(btnRequestFlightLevel2); buttonBox.addStretch(1); buttonBox.addItem(btnRequestFlightLevel3); buttonBox.addStretch(1); buttonBox.addItem(btnAvailableRadio); buttonBox.addStretch(1); buttonBox.addItem(btnClose); buttonBox.addStretch(1); dlgLayout.addStretch(1); dlgLayout.addItem(middleBox); dlgLayout.addItem(buttonBox); dialogInitialized = 1; setDialogPosition(); showRepeatATCMessageButton(0); updateATC(update_dialog); } var setAtcTextPosition = func() { if(atcTextPosition == "Top Left") { atc_popup_x_position = 20; atc_popup_y_position = -(10 + menu_bar_height); atc_popup_align = "left"; } else if(atcTextPosition == "Top Center") { atc_popup_x_position = nil; atc_popup_y_position = -(10 + menu_bar_height); atc_popup_align = "center"; } else if(atcTextPosition == "Top Right") { atc_popup_x_position = -20; atc_popup_y_position = -(10 + menu_bar_height); atc_popup_align = "left"; } else if(atcTextPosition == "Middle Left") { atc_popup_x_position = 20; atc_popup_y_position = nil; atc_popup_align = "left"; } else if(atcTextPosition == "Middle Center") { atc_popup_x_position = nil; atc_popup_y_position = nil; atc_popup_align = "center"; } else if(atcTextPosition == "Middle Right") { atc_popup_x_position = -20; atc_popup_y_position = nil; atc_popup_align = "left"; } else if(atcTextPosition == "Bottom Left") { atc_popup_x_position = 20; atc_popup_y_position = 10 + menu_bar_height; atc_popup_align = "left"; } else if(atcTextPosition == "Bottom Center") { atc_popup_x_position = nil; atc_popup_y_position = 10 + menu_bar_height; atc_popup_align = "center"; } else if(atcTextPosition == "Bottom Right") { atc_popup_x_position = -20; atc_popup_y_position = 10 + menu_bar_height; atc_popup_align = "left"; } else { atc_popup_x_position = nil; atc_popup_y_position = -(10 + menu_bar_height); atc_popup_align = "center"; } if(atcTextTransparency == "Off") popup_window_bg_color[3] = 1; else if(atcTextTransparency == "Very low") popup_window_bg_color[3] = 0.8; else if(atcTextTransparency == "Low") popup_window_bg_color[3] = 0.6; else if(atcTextTransparency == "Medium") popup_window_bg_color[3] = 0.4; else if(atcTextTransparency == "High") popup_window_bg_color[3] = 0.2; else if(atcTextTransparency == "Very high") popup_window_bg_color[3] = 0; else popup_window_bg_color[3] = 0.4; } var setDialogPosition = func() { var canvasWidth = property_Canvas_Width.getValue(); var canvasHeight = property_Canvas_Height.getValue(); if(canvasWidth != nil and canvasHeight != nil) { if(dialogPosition == "Top Left") { dialogPosX = 0; dialogPosY = menu_bar_height; } else if(dialogPosition == "Top Right") { dialogPosX = canvasWidth - maxDialogWidth; dialogPosY = menu_bar_height; } else if(dialogPosition == "Bottom Left") { dialogPosX = 0; dialogPosY = canvasHeight - maxDialogHeight; } else if(dialogPosition == "Bottom Right") { dialogPosX = canvasWidth - maxDialogWidth; dialogPosY = canvasHeight - maxDialogHeight; } else { dialogPosX = 0; dialogPosY = menu_bar_height; } } else { dialogPosX = 0; dialogPosY = menu_bar_height; } if(dlgWindow != nil and dialogInitialized == 1) dlgWindow.setPosition(dialogPosX, dialogPosY); } var setApproachButtons = func() { if(dialogOpened == 0) return; if(aircraft_status == status_requested_approach or aircraft_status == status_requested_ils or aircraft_status == status_cleared_for_land_approach or aircraft_status == status_cleared_for_land_ils) { btnRequestFlightLevel1.setEnabled(0); btnRequestFlightLevel2.setEnabled(0); btnRequestFlightLevel3.setEnabled(0); btnRequestFlightLevel1.hide(); btnRequestFlightLevel2.hide(); btnRequestFlightLevel3.hide(); btnAbortApproach.setEnabled(1); btnAbortApproach.show(); if(aircraft_status == status_requested_approach) btnAbortApproach.setText("Abort Approach"); else if(aircraft_status == status_requested_ils) btnAbortApproach.setText("Abort ILS"); else if(aircraft_status == status_cleared_for_land_approach or aircraft_status == status_cleared_for_land_ils) btnAbortApproach.setText("Abort Landing"); else { btnAbortApproach.setEnabled(0); btnAbortApproach.hide(); } } else { btnAbortApproach.setEnabled(0); btnAbortApproach.hide(); btnRequestFlightLevel1.setEnabled(1); btnRequestFlightLevel2.setEnabled(1); btnRequestFlightLevel3.setEnabled(1); btnRequestFlightLevel1.show(); btnRequestFlightLevel2.show(); btnRequestFlightLevel3.show(); } } var setCtrButtons = func() { if(dialogOpened == 0) return; if(selectedComFrequency > 0) btnRequestCtr.setEnabled(1); else btnRequestCtr.setEnabled(0); if(aircraft_status == status_flying) { if(approvedCtr != nil) { btnRequestFlightLevel1.show(); btnRequestFlightLevel2.show(); btnRequestFlightLevel3.show(); } else { btnRequestFlightLevel1.hide(); btnRequestFlightLevel2.hide(); btnRequestFlightLevel3.hide(); } } } var openATCDialog = func() { if(isAtcEnabled() == 0) { showPopup(RGAtcName ~ " is not enabled.\nTurn it on from the menu"); return; } if(initialized == 0) initRgATC(); if(dlgWindow == nil or dialogInitialized == 0) initRgATCDialog(); if(dlgWindow != nil and dialogInitialized == 1) { if(dialogOpened == 0) { dlgWindow.show(); dlgWindow.clearFocus(); setRGTitle(); dialogOpened = 1; updateATC(update_dialog); updateRadioList(); setApproachButtons(); setCtrButtons(); } else closeATCDialog(); } } var closeATCDialog = func() { if(initialized == 0) initRgATC(); if(dlgWindow == nil or dialogInitialized == 0) initRgATCDialog(); if(dialogInitialized == 1 and dialogOpened == 1 and dlgWindow != nil) { dlgWindow.hide(); dialogOpened = 0; dlgWindow.clearFocus(); } } var openAtcLogDialog = func() { if(atcLogWindow == nil) { atcLogWindow = canvas.Window.new([320, 180], "dialog") .setTitle(RGAtcName ~ ": Log"); var atcLogCanvas = atcLogWindow.createCanvas().set("background", canvas.style.getColor("bg_color")); atcLogCanvas.setColorBackground(0.4, 0.4, 0.4, 0.9); var atcLogRoot = atcLogCanvas.createGroup(); var atcLogLayout = canvas.VBoxLayout.new(); atcLogCanvas.setLayout(atcLogLayout); atcLogScroll = canvas.gui.widgets.ScrollArea.new(atcLogRoot, canvas.style, {}); atcLogScroll.setColorBackground(1.0, 1.0, 0.9, 0.9); atcLogScroll.setFixedSize(320, 148); atcLogLayout.addItem(atcLogScroll, 1); atcLogScrollContent = atcLogScroll.getContent() .set("font", "LiberationFonts/LiberationSans-Bold.ttf") .set("character-size", 16) .set("alignment", "left-center"); txtAtcLog = canvas.gui.widgets.Label.new(atcLogScrollContent, canvas.style, {}); var btnClear = canvas.gui.widgets.Button.new(atcLogRoot, canvas.style, {}) .setText("Clear") .setFixedSize(75, 26); btnClear.listen("clicked", func { clearAtcLog(); }); var btnClose = canvas.gui.widgets.Button.new(atcLogRoot, canvas.style, {}) .setText("Close") .setFixedSize(75, 26); btnClose.listen("clicked", func { atcLogWindow.hide(); atcLogOpened = 0; }); var buttonBox = canvas.HBoxLayout.new(); buttonBox.addStretch(1); buttonBox.addItem(btnClear); buttonBox.addStretch(1); buttonBox.addItem(btnClose); buttonBox.addStretch(1); atcLogLayout.addStretch(1); atcLogLayout.addItem(buttonBox); } else atcLogWindow.show(); atcLogOpened = 1; txtAtcLog.setText(atcLogText); atcLogWindow.raise(1); atcLogScroll.scrollTo(0, 32768); atcLogWindow.clearFocus(); } var addAtcLog = func(text) { rowlen = 45; text = string.replace(text, "\n", " "); var word = split(" ", text); var wlen = size(word); var row = property_TimeGmtString.getValue() ~ "Z: "; for(var i = 0; i < wlen; i += 1) { if(size(row) + size(word[i]) <= rowlen) row ~= word[i] ~ " "; else { atcLogText ~= row ~ "\n"; row = word[i] ~ " "; } } atcLogText ~= row ~ "\n\n"; if(txtAtcLog != nil) txtAtcLog.setText(atcLogText); if(atcLogOpened == 1 and atcLogWindow != nil and atcLogScroll != nil) atcLogScroll.scrollTo(0, 32768); } var clearAtcLog = func() { atcLogText = "\n"; if(txtAtcLog != nil) txtAtcLog.setText(atcLogText); if(atcLogOpened == 1 and atcLogWindow != nil and atcLogScroll != nil) atcLogScroll.scrollTo(0, 32768); } var updateATC = func(update_mode) { var type = nil; var aircraftPosition = ""; var txt = ""; var degTag = "°"; var popupText = RGAtcTitle ~ "\n\n"; if(update_mode == update_dialog) degTag = "°"; else degTag = "°"; if(property_COM1_Serviceable != nil) { if(property_COM1_Quality != nil) com1quality = property_COM1_Quality.getValue(); else com1quality = 5; com1volume = getRadioVolume(property_COM1_Volume, property_COM1_VolumeSelected); com1serviceable = property_COM1_Serviceable.getValue(); if(property_COM1_PowerButton != nil) com1PowerStatus = property_COM1_PowerButton.getValue(); else com1PowerStatus = 1; if(property_COM1_Operable != nil) com1Operable = property_COM1_Operable.getValue(); else com1Operable = 1; } else { com1quality = 0; com1volume = 0; com1serviceable = 0; com1PowerStatus = 0; com1Operable = 0; } if(property_COM2_Serviceable != nil) { if(property_COM2_Quality != nil) com2quality = property_COM2_Quality.getValue(); else com2quality = 5; com2volume = getRadioVolume(property_COM2_Volume, property_COM2_VolumeSelected); com2serviceable = property_COM1_Serviceable.getValue(); if(property_COM2_PowerButton != nil) com2PowerStatus = property_COM2_PowerButton.getValue(); else com2PowerStatus = 1; if(property_COM2_Operable != nil) com2Operable = property_COM2_Operable.getValue(); else com2Operable = 1; } else { com2quality = 0; com2volume = 0; com2serviceable = 0; com2PowerStatus = 0; com2Operable = 0; } if(property_COM3_Serviceable != nil) { if(property_COM3_Quality != nil) com3quality = property_COM3_Quality.getValue(); else com3quality = 5; com3volume = getRadioVolume(property_COM3_Volume, property_COM3_VolumeSelected); com3serviceable = property_COM3_Serviceable.getValue(); if(property_COM3_PowerButton != nil) com3PowerStatus = property_COM3_PowerButton.getValue(); else com3PowerStatus = 1; if(property_COM3_Operable != nil) com3Operable = property_COM3_Operable.getValue(); else com3Operable = 1; } else { com3quality = 0; com3volume = 0; com3serviceable = 0; com3PowerStatus = 0; com3Operable = 0; } if(com1serviceable == 1 and com1PowerStatus == 1 and com1Operable == 1 and (atcRadioMode == "Auto" or atcRadioMode == "COM1")) { if(property_COM1_StationName != nil) selectedComStationType = getRadioType(property_COM1_StationName.getValue()); else { selectedComStationType = radio_station_type_unknown; com1serviceable = 0; } if(property_COM1_AirportID != nil) selectedComAirportId = property_COM1_AirportID.getValue(); else { selectedComAirportId = ""; com1serviceable = 0; } if(property_COM1_StationName != nil) { if(selectedComAirportId != "") selectedComStationName = getRadioFullName(selectedComAirportId, property_COM1_StationName.getValue()); else selectedComStationName = property_COM1_StationName.getValue(); } else { selectedComStationName = ""; com1serviceable = 0; } selectedComFrequency = getRadioFrequency(property_COM1_Frequency, property_COM1_RealFrequency); if(selectedComFrequency <= 0) com1serviceable = 0; if(property_COM1_Distance != nil) selectedComStationDistance = property_COM1_Distance.getValue(); else { selectedComStationDistance = ""; com1serviceable = 0; } if(property_COM1_Bearing != nil) selectedComStationBearing = property_COM1_Bearing.getValue(); else { selectedComStationBearing = ""; com1serviceable = 0; } if(com1serviceable == 1) { selectedComRadio = "COM1"; selectedComSignalQuality = com1quality; selectedComVolume = com1volume; selectedComServiceable = com1serviceable; selectedComServiceableProperty = property_COM1_Serviceable; selectedComPowerStatus = com1PowerStatus; selectedComOperable = com1Operable; } else { selectedComRadio = ""; selectedComServiceableProperty = nil; selectedComSignalQuality = 0; } } else if(com2serviceable == 1 and com2PowerStatus == 1 and com2Operable == 1 and (atcRadioMode == "Auto" or atcRadioMode == "COM2")) { if(property_COM2_StationName != nil) selectedComStationType = getRadioType(property_COM2_StationName.getValue()); else { selectedComStationType = radio_station_type_unknown; com2serviceable = 0; } if(property_COM2_AirportID != nil) selectedComAirportId = property_COM2_AirportID.getValue(); else { selectedComAirportId = ""; com2serviceable = 0; } if(property_COM2_StationName != nil) { if(selectedComAirportId != "") selectedComStationName = getRadioFullName(selectedComAirportId, property_COM2_StationName.getValue()); else selectedComStationName = property_COM2_StationName.getValue(); } else { selectedComStationName = ""; com2serviceable = 0; } selectedComFrequency = getRadioFrequency(property_COM2_Frequency, property_COM2_RealFrequency); if(selectedComFrequency <= 0) com2serviceable = 0; if(property_COM2_Distance != nil) selectedComStationDistance = property_COM2_Distance.getValue(); else { selectedComStationDistance = ""; com2serviceable = 0; } if(property_COM2_Bearing != nil) selectedComStationBearing = property_COM2_Bearing.getValue(); else { selectedComStationBearing = ""; com2serviceable = 0; } if(com2serviceable == 1) { selectedComRadio = "COM2"; selectedComSignalQuality = com2quality; selectedComVolume = com2volume; selectedComServiceable = com2serviceable; selectedComServiceableProperty = property_COM2_Serviceable; selectedComPowerStatus = com2PowerStatus; selectedComOperable = com2Operable; } else { selectedComRadio = ""; selectedComServiceableProperty = nil; selectedComSignalQuality = 0; } } else if(com3serviceable == 1 and com3PowerStatus == 1 and com3Operable == 1 and (atcRadioMode == "Auto" or atcRadioMode == "COM3")) { if(property_COM3_StationName != nil) selectedComStationType = getRadioType(property_COM3_StationName.getValue()); else { selectedComStationType = radio_station_type_unknown; com3serviceable = 0; } if(property_COM3_AirportID != nil) selectedComAirportId = property_COM3_AirportID.getValue(); else { selectedComAirportId = ""; com3serviceable = 0; } if(property_COM3_StationName != nil) { if(selectedComAirportId != "") selectedComStationName = getRadioFullName(selectedComAirportId, property_COM3_StationName.getValue()); else selectedComStationName = property_COM3_StationName.getValue(); } else { selectedComStationName = ""; com3serviceable = 0; } selectedComFrequency = getRadioFrequency(property_COM3_Frequency, property_COM3_RealFrequency); if(selectedComFrequency <= 0) com3serviceable = 0; if(property_COM3_Distance != nil) selectedComStationDistance = property_COM3_Distance.getValue(); else { selectedComStationDistance = ""; com3serviceable = 0; } if(property_COM3_Bearing != nil) selectedComStationBearing = property_COM3_Bearing.getValue(); else { selectedComStationBearing = ""; com3serviceable = 0; } if(com3serviceable == 1) { selectedComRadio = "COM3"; selectedComSignalQuality = com3quality; selectedComVolume = com3volume; selectedComServiceable = com3serviceable; selectedComServiceableProperty = property_COM3_Serviceable; selectedComPowerStatus = com3PowerStatus; selectedComOperable = com3Operable; } else { selectedComRadio = ""; selectedComServiceableProperty = nil; selectedComSignalQuality = 0; } } else { selectedComSignalQuality = 0; selectedComAirportId = ""; selectedComStationName = ""; selectedComFrequency = 0; selectedComStationDistance = ""; selectedComStationBearing = ""; if(property_COM1_Serviceable != nil) { selectedComRadio = "COM1"; selectedComVolume = com1volume; selectedComServiceable = com1serviceable; selectedComServiceableProperty = property_COM1_Serviceable; selectedComPowerStatus = com1PowerStatus; selectedComOperable = com1Operable; } else { selectedComRadio = ""; selectedComVolume = ""; selectedComServiceable = 0; selectedComServiceableProperty = nil; selectedComPowerStatus = 0; selectedComOperable = 0; } } if(selectedComStationType != radio_station_type_unknown) selectedComStationName = normalizeRadioStationName(selectedComStationName); else selectedComStationName = atcMessageAction.unknown_radio_type; if(selectedComAirportId == "") selectedComAirportId = atcMessageAction.bad_airport_radio; if(selectedComFrequency <= 0) selectedComFrequency = 999.99; if(selectedComStationDistance == "") selectedComStationDistance = 9999; if(selectedComStationBearing == "") selectedComStationBearing = 999; selectedTransponder = getOperatingTransponder(); var pos = geo.aircraft_position(); var range = min_airport_range; if(currentCtr != nil) range += int((currentCtr.range / 1.5) / 10); if(update_mode == update_dialog) setCtrButtons(); var airport = findAirportsWithinRange(pos, range); var altitude = property_Aircraft_AltitudeAglFeet.getValue(); if(size(airport) > 0 and aircraft_status != status_requested_approach and aircraft_status != status_requested_ils) { if(update_mode == update_dialog) txtAirport.setText(airport[0].id ~ " " ~ airport[0].name); else if(update_mode == update_popup) popupText ~= airport[0].id ~ " " ~ airport[0].name ~ "\n"; var aPos = getPositionInAirport(airport[0], pos); if(aPos == position_ground) { aircraftPosition = "Ground"; if(aircraft_status == status_cleared_for_land_approach or aircraft_status == status_cleared_for_land_ils) { aircraft_status = status_landed; cancelAtcPendingMessage(); setAtcAutoCallbackAfterSeconds(welcome_airport_secs); } else if(aircraft_status != status_landed and aircraft_status != status_cleared_for_takeoff) aircraft_status = status_going_around; assignedAltitude = -1; } else if(aPos == position_flying) { aircraftPosition = "Flying over"; airportRunwayInUse = ""; airportRunwayInUseILS = ""; if(aircraft_status == status_cleared_for_takeoff) { aircraft_status = status_took_off; flight_time_seconds = 0; cancelAtcPendingMessage(); setAtcAutoCallbackAfterSeconds(wait_after_take_off); } else if(aircraft_status == status_going_around) aircraft_status = status_flying; } else if(aPos == position_runway) aircraftPosition = "Runway " ~ currentRunway; else aircraftPosition = "???"; if(aPos == position_runway) { atcMessageType[0] = message_none; atcMessageType[1] = message_none; atcMessageType[2] = message_none; atcMessageType[3] = message_none; if(nearRunway == 1) { aircraftPosition = "Near " ~ aircraftPosition; atcMessageType[0] = message_radio_check; atcMessageType[1] = message_departure_information; if(aircraft_status == status_cleared_for_takeoff) atcMessageType[2] = message_abort_departure; else atcMessageType[2] = message_ready_for_departure; atcMessageType[3] = message_none; } else if(alignedOnRunway == 1) { aircraftPosition ~= " - Aligned"; atcMessageType[0] = message_radio_check; atcMessageType[1] = message_departure_information; if(aircraft_status == status_cleared_for_takeoff) atcMessageType[2] = message_abort_departure; else atcMessageType[2] = message_ready_for_departure; atcMessageType[3] = message_none; } else { aircraftPosition = aircraftPosition; atcMessageType[0] = message_radio_check; atcMessageType[1] = message_departure_information; atcMessageType[2] = message_request_taxi; if(aircraft_status == status_cleared_for_takeoff) atcMessageType[3] = message_abort_departure; else atcMessageType[3] = message_ready_for_departure; } if(aircraft_status == status_cleared_for_land_approach or aircraft_status == status_cleared_for_land_ils) { aircraft_status = status_landed; cancelAtcPendingMessage(); setAtcAutoCallbackAfterSeconds(welcome_airport_secs); } assignedAltitude = -1; } else if(aPos == position_flying) { var approachingRunway = ""; var distance = -1; var degreeDiff = 0; var turnToDiff = ""; var posTag = ""; var slopeCourseMarker = ""; (approachingRunway, distance) = getApproachingRunway(currentAirport); if(approachingRunway != "") { var course = 0; var dist = 0; var alignPoint = geo.Coord.new().set_latlon(currentAirport.runways[approachingRunway].lat, currentAirport.runways[approachingRunway].lon); (course, dist) = courseAndDistance(alignPoint); alignPoint.apply_course_distance(currentAirport.runways[approachingRunway].heading, -(dist * NM2M)); (course, dist) = courseAndDistance(alignPoint); (degreeDiff, turnToDiff) = degreesDifference(currentAirport.runways[approachingRunway].heading, course); dist = int(dist * NM2M); slopeCourseMarker = " ["; altMarker = ""; altSlope = altitudeApproachSlope(currentAirport.elevation, distance); altitudeFt = property_Aircraft_AltitudeFeet.getValue(); altOk = 0; crsOk = 0; if(altitudeFt > altSlope) { altMarker = "v"; altOk = 0; } else if(altitudeFt < altSlope) { altMarker = "^"; altOk = 0; } else { altMarker = "o"; altOk = 1; } if(dist > 3) { if(dist > 60) dist = 60; if(turnToDiff == "left") slopeCourseMarker ~= "<"; else slopeCourseMarker ~= altMarker; if(dist > 10) { for(var n = 0; n < dist; n += 30) slopeCourseMarker ~= "="; } if(turnToDiff == "right") slopeCourseMarker ~= ">"; else slopeCourseMarker ~= altMarker; crsOk = 0; } else { slopeCourseMarker ~= altMarker; crsOk = 1; } if(altOk == 1 and crsOk == 1) slopeCourseMarker ~= "ok"; slopeCourseMarker ~= "]"; aircraftPosition = sprintf("APPR Runway %s%s DST %.1f nm AGL %.1f ft (%.1f m)", approachingRunway, slopeCourseMarker, distance, altitude, altitude * FT2M); } else aircraftPosition = sprintf("Flying over @ AGL %.1f ft (%.1f m)", altitude, altitude * FT2M); (airportRunwayInUse, airportRunwayInUseILS) = runwayInUse(airportinfo(selectedComAirportId), runway_landing); atcMessageType[0] = message_radio_check; atcMessageType[1] = message_request_approach; if(airportRunwayInUseILS != "") { atcMessageType[2] = message_request_ils; atcMessageType[3] = message_airfield_in_sight; } else { atcMessageType[2] = message_airfield_in_sight; atcMessageType[3] = message_none; } if(aircraft_status == status_cleared_for_takeoff) { aircraft_status = status_took_off; flight_time_seconds = 0; cancelAtcPendingMessage(); setAtcAutoCallbackAfterSeconds(wait_after_take_off); } } else if(aPos == position_ground) { var speed = property_Aircraft_GroundSpeedKnots.getValue(); aircraftPosition ~= sprintf(" @ %.1f kt (%.1f km/h)", speed, speed * KT2KMH); atcMessageType[0] = message_radio_check; atcMessageType[1] = message_departure_information; atcMessageType[2] = message_engine_start; atcMessageType[3] = message_request_taxi; } if(airportRunwayInUse != "" and aPos != position_flying) aircraftPosition ~= " - Departure runway " ~ airportRunwayInUse; if(update_mode == update_dialog) txtAircraftPosition.setText(aircraftPosition); else if(update_mode == update_popup) popupText ~= aircraftPosition ~ "\n\n"; currentAirport = airport[0]; if(update_mode == update_dialog) { btnAvailableRadio.setEnabled(1); if(selectedComAirportId != "" or currentCtr != nil) btnRequestCtr.setEnabled(1); else btnRequestCtr.setEnabled(0); } } else { var upperText = ""; var bottomText = ""; var altitude = property_Aircraft_AltitudeFeet.getValue(); var airSpeed = property_Aircraft_AirSpeedKnots.getValue(); if(patternPoint != nil and approachPoint != nil and (aircraft_status == status_requested_approach or aircraft_status == status_requested_ils)) { var courseToPoint = nil; var distanceToPoint = 0; (courseToPoint, distanceToPoint) = courseAndDistance(patternPoint); upperText = sprintf("PTTN: HDG %d%s - DST %.2f nm - ALT %d ft", courseToPoint, degTag, distanceToPoint, altitude); (courseToPoint, distanceToPoint) = courseAndDistance(approachPoint); bottomText = sprintf("APPR: HDG %d%s - DST %.2f nm - IAS %d kt - Runway %s", courseToPoint, degTag, distanceToPoint, airSpeed, airportLandingRunway); } else { var agl = property_Aircraft_AltitudeAglFeet.getValue(); var heading = property_Aircraft_HeadingDeg.getValue(); var groundSpeed = property_Aircraft_GroundSpeedKnots.getValue(); var mach = property_Aircraft_MachSpeed.getValue(); upperText = sprintf("ALT %d ft (%d m) - AGL %d ft (%d m) - HDG %d%s", altitude, altitude * FT2M, agl, agl * FT2M, heading, degTag); bottomText = sprintf("IAS %d kt (%d km/h) - SPD %d kt (%d km/h) - Mach %.2f", airSpeed, airSpeed * KT2KMH, groundSpeed, groundSpeed * 1.852, mach); } if(update_mode == update_dialog) { if(txtAirport != nil) txtAirport.setText(upperText); if(txtAircraftPosition != nil) txtAircraftPosition.setText(bottomText); } else if(update_mode == update_popup) popupText ~= upperText ~ "\n" ~ bottomText ~ "\n\n"; currentAirport = nil; } if(currentCtr != nil) { if(currentCtr.status != ctr_status_outside) { var frequency = 0; var ctrDistance = 0; var ctrStatus = ""; txt = sprintf("CTR: %s %s", currentCtr.ident, currentCtr.airport); if(update_mode == update_dialog) txtCurrentCtr.setText(txt); else if(update_mode == update_popup) popupText ~= txt ~ "\n"; var radio = getCtrRadio(currentCtr); if(radio != nil) frequency = radio.frequency; else frequency = 0; if(currentCtr.status == ctr_status_inside) { ctrDistance = currentCtr.distance; if(approvedCtr != nil and approvedCtr.ident == currentCtr.ident) ctrStatus = "Approved"; else ctrStatus = "Inside"; } else if(currentCtr.status == ctr_status_in_range) { ctrDistance = currentCtr.distance_from_range; ctrStatus = "Flying to"; } txt = sprintf("RNG %d nm DST %.2f nm CRS %d%s - %.3f MHz - %s", currentCtr.range, ctrDistance, currentCtr.course, degTag, frequency, ctrStatus); if(update_mode == update_dialog) txtCurrentCtrSpecs.setText(txt); else if(update_mode == update_popup) popupText ~= txt ~ "\n\n"; } else { if(update_mode == update_dialog) { txtCurrentCtr.setText("CTR: --"); txtCurrentCtrSpecs.setText("--"); } else if(update_mode == update_popup) popupText ~= "\nCTR: --\n\n"; } } else { if(update_mode == update_dialog) { txtCurrentCtr.setText("CTR: --"); txtCurrentCtrSpecs.setText("--"); } else if(update_mode == update_popup) popupText ~= "\nCTR: --\n\n"; } var aircraftPosition = ""; if(radioSignalQuality() > 0 and selectedComVolume >= 0.1) { if(btnAvailableRadio != nil) btnAvailableRadio.setEnabled(1); if(selectedComAirportId != currentRadioStationId) { getAvailableRadios(); radioListHasChanged = 1; currentRadioStationId = selectedComAirportId; (atcCallsignText, atcCallsignVoice) = getCallSignForAtc(1); showRepeatATCMessageButton(0); } if(altitude > min_ground_altitude) { (airportRunwayInUse, airportRunwayInUseILS) = runwayInUse(airportinfo(selectedComAirportId), runway_landing); atcMessageType[0] = message_radio_check; atcMessageType[1] = message_request_approach; if(aircraft_status == status_requested_ils or aircraft_status == status_cleared_for_land_ils) { atcMessageType[2] = message_request_ils; atcMessageType[3] = message_ils_established; } else { if(airportRunwayInUseILS != "") { atcMessageType[2] = message_request_ils; atcMessageType[3] = message_airfield_in_sight; } else { atcMessageType[2] = message_airfield_in_sight; atcMessageType[3] = message_none; } } } txt = sprintf("%s: %s %s", selectedComRadio, selectedComAirportId, selectedComStationName); if(update_mode == update_dialog) txtCurrentRadio.setText(txt); else if(update_mode == update_popup) popupText ~= txt ~ "\n"; txt = sprintf("%.3f MHz - Distance: %.2f nm @ %d%s - Quality: %d", selectedComFrequency, selectedComStationDistance * M2NM , int(selectedComStationBearing), degTag, radioSignalQuality()); if(update_mode == update_dialog) txtCurrentRadioSpecs.setText(txt); else if(update_mode == update_popup) popupText ~= txt ~ "\n"; if(update_mode == update_dialog) { var wh = dialogHeight; var minCruiseAltidude = getMinCruiseAltitude(); for(var i = 0; i < size(atcMessageType); i += 1) { if(atcMessageType[i] != message_none) { wh += 32; var btnTag = getMessageText(atcMessageType[i]); if(btnTag != "") { btnMessage[i].setText(btnTag); btnMessage[i].show(); } } else btnMessage[i].hide(); } if(radioListHasChanged == 1) { radioBox.show(); updateRadioList(); radioListHasChanged = 0; showRepeatATCMessageButton(0); } radioBox.show(); if(aircraft_status == status_flying) { if(flightLevel1 >= minCruiseAltidude) { btnRequestFlightLevel1.setText("FL" ~ getFlightLevelCode(flightLevel1)); btnRequestFlightLevel1.setEnabled(1); } else { btnRequestFlightLevel1.setText("--"); btnRequestFlightLevel1.setEnabled(0); } if(flightLevel2 >= minCruiseAltidude) { btnRequestFlightLevel2.setText("FL" ~ getFlightLevelCode(flightLevel2)); btnRequestFlightLevel2.setEnabled(1); } else { btnRequestFlightLevel2.setText("--"); btnRequestFlightLevel2.setEnabled(0); } if(flightLevel3 >= minCruiseAltidude and flightLevel3 != flightLevel2) { btnRequestFlightLevel3.setText("FL" ~ getFlightLevelCode(flightLevel3)); btnRequestFlightLevel3.setEnabled(1); } else { btnRequestFlightLevel3.setText("--"); btnRequestFlightLevel3.setEnabled(0); } } else { btnRequestFlightLevel1.setText("--"); btnRequestFlightLevel1.setEnabled(0); btnRequestFlightLevel2.setText("--"); btnRequestFlightLevel2.setEnabled(0); btnRequestFlightLevel3.setText("--"); btnRequestFlightLevel3.setEnabled(0); } if(dialogInitialized == 1 and dialogOpened == 1 and dlgWindow != nil) dlgWindow.setSize(dialogWidth, wh); } else if(update_mode == update_popup) { var minCruiseAltidude = getMinCruiseAltitude(); for(var i = 0; i < size(atcMessageType); i += 1) { if(atcMessageType[i] != message_none) popupText ~= sprintf("\n %s", i+4, getMessageText(atcMessageType[i])); } if(lastAtcText != "" or lastAtcVoice != "") popupText ~= "\n Repeat last ATC message"; popupText ~= "\n Request CTR"; if(aircraft_status == status_flying) { popupText ~= "\n"; if(flightLevel1 >= minCruiseAltidude) popupText ~= " Request FL" ~ getFlightLevelCode(flightLevel1) ~ "\n"; if(flightLevel2 >= minCruiseAltidude) popupText ~= " Request FL" ~ getFlightLevelCode(flightLevel2) ~ "\n"; if(flightLevel3 >= minCruiseAltidude and flightLevel3 != flightLevel2) popupText ~= " Request FL" ~ getFlightLevelCode(flightLevel3) ~ "\n"; } else if(aircraft_status == status_requested_approach) popupText ~= "\n Abort Approach"; else if(aircraft_status == status_requested_ils) popupText ~= "\n Abort ILS"; else if(aircraft_status == status_cleared_for_land_approach or aircraft_status == status_cleared_for_land_ils) popupText ~= "\n Abort Landing"; showPopup(popupText); } } else { if(selectedComServiceableProperty == nil) txt = atcMessageAction.bad_radio_data; else if(selectedComServiceable == 0 or selectedComPowerStatus == 0 or selectedComOperable == 0) txt = atcMessageAction.turn_radio_on; else if(selectedComVolume < 0.1) { if(selectedComRadio != "") txt = selectedComRadio ~ ": "; else txt = ""; txt ~= atcMessageAction.radio_volume_up; } else if(radioSignalQuality() == 0) { if(selectedComRadio != "") txt = selectedComRadio ~ ": "; else txt = ""; txt ~= atcMessageAction.no_radio_tuned; } else txt = atcMessageAction.bad_radio_data; if(update_mode == update_dialog) txtCurrentRadio.setText(txt); else if(update_mode == update_popup) popupText ~= txt ~ "\n"; if(update_mode == update_dialog) { txtCurrentRadioSpecs.setText("--"); btnRequestFlightLevel1.setText("--"); btnRequestFlightLevel1.setEnabled(0); btnRequestFlightLevel2.setText("--"); btnRequestFlightLevel2.setEnabled(0); btnRequestFlightLevel3.setText("--"); btnRequestFlightLevel3.setEnabled(0); } selectedComStationType = radio_station_type_unknown; if(update_mode == update_dialog) clearATCButtons(); else if(update_mode == update_popup) showPopup(popupText); if(radioBox != nil) radioBox.hide(); getAvailableRadios(); radioListHasChanged = 1; (atcCallsignText, atcCallsignVoice) = getCallSignForAtc(1); showRepeatATCMessageButton(0); } if(dialogInitialized == 1 and dialogOpened == 1 and dlgWindow != nil) dlgWindow.clearFocus(); if(atcLogOpened == 1 and atcLogWindow != nil) atcLogWindow.clearFocus(); } var getPositionInAirport = func(airport, pos) { var distance = 9999; var course = 0; var dist = 0; var runwayPos = 0; var position = position_ground; var degreeDiff = 360; var turnToDiff = ""; currentRunway = ""; alignedOnRunway = 0; nearRunway = 0; if(airport == nil) return position_unknown; if(property_Aircraft_AltitudeAglFeet.getValue() > min_ground_altitude) return position_flying; if(size(airport.runways) > 0) { var aircraftHeading = property_Aircraft_HeadingDeg.getValue(); foreach(var runway; keys(airport.runways)) { min_distance = airport.runways[runway].width * 1.5; min_near_distance = min_distance + airport.runways[runway].width * 2; var angleDeviation = abs(airport.runways[runway].heading - aircraftHeading); runwayPos = geo.Coord.new().set_latlon(airport.runways[runway].lat, airport.runways[runway].lon); runwayLength = airport.runways[runway].length; if(airport.runways[runway].stopway > 0) { runwayPos.apply_course_distance(airport.runways[runway].heading, -airport.runways[runway].stopway); runwayLength += airport.runways[runway].stopway; } if(airport.runways[runway].threshold > 0) { runwayPos.apply_course_distance(airport.runways[runway].heading, -airport.runways[runway].threshold); runwayLength += airport.runways[runway].threshold; } if(airport.runways[runway].reciprocal.stopway > 0) { runwayPos.apply_course_distance(airport.runways[runway].heading, -airport.runways[runway].reciprocal.stopway); runwayLength += airport.runways[runway].reciprocal.stopway; } if(airport.runways[runway].reciprocal.threshold > 0) { runwayPos.apply_course_distance(airport.runways[runway].heading, -airport.runways[runway].reciprocal.threshold); runwayLength += airport.runways[runway].reciprocal.threshold; } dist = minPathDistance(pos, runwayPos, airport.runways[runway].heading, runwayLength / 2, min_distance); if(dist < min_near_distance) { position = position_runway; currentRunway = runway; nearRunway = 1; } if(dist < min_distance and dist < distance) { distance = dist; position = position_runway; currentRunway = runway; nearRunway = 0; } } if(position == position_runway) { (degreeDiff, turnToDiff) = degreesDifference(aircraftHeading, airport.runways[currentRunway].heading); if(degreeDiff < max_runway_alignment_degrees) alignedOnRunway = 1; else alignedOnRunway = 0; } } return position; } var runwayInUse = func(airport, runway_type) { var degreesBest = 360; var lengthBest = 0; var degreeDiff = 360; var turnToDiff = ""; var windDirection = 0; if(isRealMeteoEnabled() == 1) { if(property_Weather_Metar_WindDirectionDeg != nil) windDirection = property_Weather_Metar_WindDirectionDeg.getValue(); else windDirection = 0; } else { if(property_Weather_WindDirectionDeg != nil) windDirection = property_Weather_WindDirectionDeg.getValue(); else windDirection = 0; } var atcRunway = property_AtcRunway.getValue(); if(airport == nil) return ["", nil]; airportRunway = ""; airportRunwayILS = ""; if(property_Autopilot_RouteManagerActive.getValue() and property_Autopilot_RouteManagerDepartureRunway.getValue() != "" and runway_type == runway_take_off) { airportRunway = property_Autopilot_RouteManagerDepartureRunway.getValue(); } else if(atcRunway != "" and airport.runways[atcRunway] != nil) { airportRunway = atcRunway; if(airport.runways[airportRunway].ils_frequency_mhz != nil) airportRunwayILS = airport.runways[airportRunway].id; } else { foreach(var runway; keys(airport.runways)) { var runwayDeg = airport.runways[runway].heading; (degreeDiff, turnToDiff) = degreesDifference(runwayDeg, windDirection); if(degreeDiff < degreesBest or (degreeDiff == degreesBest and airport.runways[runway].length > lengthBest)) { airportRunway = airport.runways[runway].id; degreesBest = degreeDiff; lengthBest = airport.runways[runway].length; } } } if(airportRunwayILS == "") { degreesBest = 360; lengthBest = 0; degreeDiff = 360; foreach(var runway; keys(airport.runways)) { var runwayDeg = airport.runways[runway].heading; (degreeDiff, turnToDiff) = degreesDifference(runwayDeg, windDirection); if(airport.runways[runway].ils_frequency_mhz != nil and (degreeDiff < degreesBest or (degreeDiff == degreesBest and airport.runways[runway].length > lengthBest))) { airportRunwayILS = airport.runways[runway].id; degreesBest = degreeDiff; lengthBest = airport.runways[runway].length; } } } return [airportRunway, airportRunwayILS]; } var getApproachingRunway = func(airport) { var degreesBest = 360; var lengthBest = 0; var heading = property_Aircraft_HeadingDeg.getValue(); var approachingRunway = ""; var distance = 9999; var degreeDiff = 0; var turnToDiff = ""; var runwayPos = nil; var runwayVector = nil; if(airport == nil) return ["", 0]; foreach(var runway; keys(airport.runways)) { if(courseWithinDegrees(heading, airport.runways[runway].heading, max_runway_align_angle) == 1) { runwayPos = geo.Coord.new().set_latlon(airport.runways[runway].lat, airport.runways[runway].lon); if(airport.runways[runway].stopway > 0) runwayPos.apply_course_distance(airport.runways[runway].heading, -airport.runways[runway].stopway); if(airport.runways[runway].threshold > 0) runwayPos.apply_course_distance(airport.runways[runway].heading, -airport.runways[runway].threshold); if(airport.runways[runway].reciprocal.stopway > 0) runwayPos.apply_course_distance(airport.runways[runway].heading, -airport.runways[runway].reciprocal.stopway); if(airport.runways[runway].reciprocal.threshold > 0) runwayPos.apply_course_distance(airport.runways[runway].heading, -airport.runways[runway].reciprocal.threshold); (course, dist) = courseAndDistance(runwayPos); if(courseWithinDegrees(heading, course, max_runway_align_angle) == 1) { runwayVector = runwayPos; runwayVector.apply_course_distance(airport.runways[runway].heading, -(dist * NM2M)); (courseVector, distVector) = courseAndDistance(runwayVector); if(distVector < distance) { distance = distVector; approachingRunway = runway; } } } } return [approachingRunway, distance]; } var clearATCButtons = func() { if(btnMessage[0] == nil) return; btnMessage[0].hide(); btnMessage[1].hide(); btnMessage[2].hide(); btnMessage[3].hide(); if(dialogInitialized == 1 and dialogOpened == 1 and dlgWindow != nil) dlgWindow.setSize(dialogWidth, dialogHeight); } var showRepeatATCMessageButton = func(enable) { var w = (dialogWidth / 2) - 20; if(btnRepeatATCMessage != nil) { if(enable == 1) { btnRepeatATCMessage.show(); w = (dialogWidth / 2) - 28 - repeatBtnWidth; } else { lastAtcText = ""; lastAtcVoice = ""; btnRepeatATCMessage.hide(); } } if(btnMessage[0] != nil) btnMessage[0].setFixedSize(w, 26); } var availableRadioDialog = func() { if(size(availableRadio) == 0) { showPopup(RGAtcName ~ ": " ~ atcMessageAction.no_radio_available ~ "."); return; } if(selectedComStationType != radio_station_type_unknown and selectedComAirportId != "") airport = airportinfo(selectedComAirportId); else if(currentAirport != nil) airport = currentAirport; else { showPopup(RGAtcName ~ ": " ~ atcMessageAction.no_radio_available ~ "."); return; } var h = (size(availableRadio) * 14) + 120; var (width, height) = (320, h); var radioWindow = canvas.Window.new([width,height], "dialog") .setTitle(RGAtcName ~ ": Available Radios"); var radioCanvas = radioWindow.createCanvas().set("background", canvas.style.getColor("bg_color")); radioCanvas.setColorBackground(0.4, 0.4, 0.4, 0.9); var radioRoot = radioCanvas.createGroup(); var radioLayout = canvas.VBoxLayout.new(); radioCanvas.setLayout(radioLayout); var btnRadioClose = canvas.gui.widgets.Button.new(radioRoot, canvas.style, {}) .setText("Close") .setFixedSize(75, 26); btnRadioClose.listen("clicked", func { radioWindow.hide(); }); radioLayout.addStretch(1); radioLayout.addItem(btnRadioClose); var txtAirport = radioRoot.createChild("text") .setText(airport.name) .setFont("LiberationFonts/LiberationSans-Bold.ttf") .setFontSize(14, 0.9) .setColor(1, 1, 1, 1) .setAlignment("left-center") .setTranslation(10, 20); var txtComTitle = radioRoot.createChild("text") .setText("Communication Radios") .setFont("LiberationFonts/LiberationSans-Bold.ttf") .setFontSize(14, 0.9) .setColor(1, 1, 0, 1) .setAlignment("left-center") .setTranslation(10, 48); var txtRadios = radioRoot.createChild("text") .setText("") .setFont("LiberationFonts/LiberationSans-Bold.ttf") .setFontSize(14, 0.9) .setColor(1, 1, 1, 1) .setAlignment("left") .setTranslation(10, 88); var radioDesc = ""; var i = 0; for(i = 0; i < size(availableRadio); i += 1) { var radio = availableRadio[i]; radioDesc ~= sprintf("%s: %.3f MHz\n", radio.ident, radio.frequency); } txtRadios.setText(radioDesc); radioWindow.raise(1); radioWindow.clearFocus(); } var updateRadioList = func() { var vndx = 0; var voiceIsValid = 0; getAvailableRadios(); numRadios = size(availableRadio); if(numRadios == 0) return; stationVoice = {}; radioList = canvas.VBoxLayout.new(); for(var i = 0; i < numRadios; i += 1) { var radio = availableRadio[i]; radioButton[i] = canvas.gui.widgets.Button.new(radioScrollContent, canvas.style, {}) .setText(sprintf("%s: %.3f MHz", radio.ident, radio.frequency)) .setFixedSize((dialogWidth / 2) - 4, 24); radioButton[i].show(); radioButtonFrequency[i] = radio.frequency; radioList.addItem(radioButton[i]); stationVoice[radio.frequency] = getSynthVoice(radio.frequency); } radioList.addStretch(1); radioScroll.setLayout(radioList); for(; i < max_radios; i += 1) { if(radioButton[i] != nil) { radioButton[i].hide(); radioButton[i] = nil; } } if(numRadios > 0) { radioButton[0].listen("clicked", func() { setCurrentComRadioFrequency(radioButtonFrequency[0]); }); } if(numRadios > 1) { radioButton[1].listen("clicked", func() { setCurrentComRadioFrequency(radioButtonFrequency[1]); }); } if(numRadios > 2) { radioButton[2].listen("clicked", func() { setCurrentComRadioFrequency(radioButtonFrequency[2]); }); } if(numRadios > 3) { radioButton[3].listen("clicked", func() { setCurrentComRadioFrequency(radioButtonFrequency[3]); }); } if(numRadios > 4) { radioButton[4].listen("clicked", func() { setCurrentComRadioFrequency(radioButtonFrequency[4]); }); } if(numRadios > 5) { radioButton[5].listen("clicked", func() { setCurrentComRadioFrequency(radioButtonFrequency[5]); }); } if(numRadios > 6) { radioButton[6].listen("clicked", func() { setCurrentComRadioFrequency(radioButtonFrequency[6]); }); } if(numRadios > 7) { radioButton[7].listen("clicked", func() { setCurrentComRadioFrequency(radioButtonFrequency[7]); }); } if(numRadios > 8) { radioButton[8].listen("clicked", func() { setCurrentComRadioFrequency(radioButtonFrequency[8]); }); } if(numRadios > 9) { radioButton[9].listen("clicked", func() { setCurrentComRadioFrequency(radioButtonFrequency[9]); }); } if(numRadios > 10) { radioButton[10].listen("clicked", func() { setCurrentComRadioFrequency(radioButtonFrequency[10]); }); } if(numRadios > 11) { radioButton[11].listen("clicked", func() { setCurrentComRadioFrequency(radioButtonFrequency[11]); }); } if(numRadios > 12) { radioButton[12].listen("clicked", func() { setCurrentComRadioFrequency(radioButtonFrequency[12]); }); } if(numRadios > 13) { radioButton[13].listen("clicked", func() { setCurrentComRadioFrequency(radioButtonFrequency[13]); }); } if(numRadios > 14) { radioButton[14].listen("clicked", func() { setCurrentComRadioFrequency(radioButtonFrequency[14]); }); } if(numRadios > 15) { radioButton[15].listen("clicked", func() { setCurrentComRadioFrequency(radioButtonFrequency[15]); }); } if(numRadios > 16) { radioButton[16].listen("clicked", func() { setCurrentComRadioFrequency(radioButtonFrequency[16]); }); } if(numRadios > 17) { radioButton[17].listen("clicked", func() { setCurrentComRadioFrequency(radioButtonFrequency[17]); }); } if(numRadios > 18) { radioButton[18].listen("clicked", func() { setCurrentComRadioFrequency(radioButtonFrequency[18]); }); } if(numRadios > 19) { radioButton[19].listen("clicked", func() { setCurrentComRadioFrequency(radioButtonFrequency[19]); }); } if(numRadios > 20) { radioButton[20].listen("clicked", func() { setCurrentComRadioFrequency(radioButtonFrequency[20]); }); } if(numRadios > 21) { radioButton[21].listen("clicked", func() { setCurrentComRadioFrequency(radioButtonFrequency[21]); }); } if(numRadios > 22) { radioButton[22].listen("clicked", func() { setCurrentComRadioFrequency(radioButtonFrequency[22]); }); } if(numRadios > 23) { radioButton[23].listen("clicked", func() { setCurrentComRadioFrequency(radioButtonFrequency[23]); }); } if(numRadios > 24) { radioButton[24].listen("clicked", func() { setCurrentComRadioFrequency(radioButtonFrequency[24]); }); } if(numRadios > 25) { radioButton[25].listen("clicked", func() { setCurrentComRadioFrequency(radioButtonFrequency[25]); }); } if(numRadios > 26) { radioButton[26].listen("clicked", func() { setCurrentComRadioFrequency(radioButtonFrequency[26]); }); } if(numRadios > 27) { radioButton[27].listen("clicked", func() { setCurrentComRadioFrequency(radioButtonFrequency[27]); }); } if(numRadios > 28) { radioButton[28].listen("clicked", func() { setCurrentComRadioFrequency(radioButtonFrequency[28]); }); } if(numRadios > 29) { radioButton[29].listen("clicked", func() { setCurrentComRadioFrequency(radioButtonFrequency[29]); }); } } var setCurrentComRadioFrequency = func(frequency) { if(selectedComRadio == "COM1") { if(property_COM1_RealFrequency != nil) property_COM1_RealFrequency.setValue(frequency); if(property_COM1_Frequency != nil) property_COM1_Frequency.setValue(frequency); } else if(selectedComRadio == "COM2") { if(property_COM2_RealFrequency != nil) property_COM2_RealFrequency.setValue(frequency); if(property_COM2_Frequency != nil) property_COM2_Frequency.setValue(frequency); } else if(selectedComRadio == "COM3") { if(property_COM3_RealFrequency != nil) property_COM3_RealFrequency.setValue(frequency); if(property_COM3_Frequency != nil) property_COM3_Frequency.setValue(frequency); } if(dialogInitialized == 1 and dialogOpened == 1 and dlgWindow != nil) { dlgWindow.clearFocus(); showRepeatATCMessageButton(0); } } var showPopup = func(text) { var window = screen.window.new(atc_popup_x_position, atc_popup_y_position, 20, 8); window.bg = popup_window_bg_color; window.fg = popup_window_fg_atc_color; window.align = atc_popup_align; window.write(text); } var atcReplyToRequest = func(messageType) { var text = ""; var voice = ""; var signal = -1; var extraText = ""; var extraVoice = ""; var towerContactText = ""; var towerContactVoice = ""; var minCruiseAltidude = getMinCruiseAltitude(); if(initialized == 0) initRgATC(); if(isRadioServiceable() == 0) return; if(messageType == message_none or selectedComAirportId == "") return; multiplayMessageType = message_type_local; (atcCallsignText, atcCallsignVoice) = getCallSignForAtc(0); if(messageType == message_say_again) { if(lastAtcText == "" and lastAtcVoice == "") return; multiplayMessageType = message_type_local; text = lastAtcText; voice = lastAtcVoice; pilotResponseType = message_roger; } else if(messageType == message_radio_check) { multiplayMessageType = message_type_local; signal = radioSignalQuality(); var atisRadio = getRadio(radio_station_type_atis, radio_type_exact_match); if(atisRadio != nil) { extraText = sprintf(atcMessageReply.radio_available_at, "\nATIS", sprintf("%.3f", atisRadio.frequency) ); extraVoice = sprintf(atcMessageReply.radio_available_at, "\nA T I S", spellToPhonetic(sprintf("%.3f", atisRadio.frequency), spell_number) ); } text = sprintf(atcMessageReply.radio_check, atcCallsignText, selectedComStationName, sprintf("%d", signal), extraText ); voice = sprintf(atcMessageReply.radio_check, atcCallsignVoice, selectedComStationName, spellToPhonetic(sprintf("%d", signal), spell_number), extraVoice ); pilotResponseType = message_none; } else if(messageType == message_request_ctr) { var ctrData = getCtrData(airportinfo(selectedComAirportId)); var radio = nil; multiplayMessageType = message_type_local; if(getCtrData != nil) { radio = getCtrRadio(ctrData); if(radio != nil) { if(approvedCtr != nil and approvedCtr.ident == ctrData.ident) { (text, voice) = getCtrAtcMessage(ctr_already_approved); pilotResponseType = message_roger; } else if(ctrData.status == ctr_status_in_range or ctrData.status == ctr_status_inside) { if(selectedComAirportId == ctrData.ident and selectedComFrequency == radio.frequency) { (text, voice) = getCtrAtcMessage(ctr_request_approved); currentCtr = ctrData; approvedCtr = ctrData; ctr_leaving_warning = 0; altitude_check_counter = 0; altitude_change_wait_seconds = int(rand() * max_altitude_change_secs) + min_altitude_change_secs; assignedAltitude = -1; aircraftIsDeparting = 0; resetAircraftStatus(); pilotResponseType = message_ctr_approved; } else { text = sprintf("%s, %s, ", atcCallsignText, selectedComStationName ); voice = sprintf("%s, %s, ", atcCallsignVoice, selectedComStationName ); text ~= sprintf(atcMessageReply.contact_radio, normalizeRadioStationName(radio.name), sprintf("%.3f", radio.frequency) ); voice ~= sprintf(atcMessageReply.contact_radio, normalizeRadioStationName(radio.name), spellToPhonetic(sprintf("%.3f", radio.frequency), spell_number) ); pilotResponseType = message_roger; } } else { (text, voice) = getCtrAtcMessage(ctr_request_denied); assignedAltitude = -1; pilotResponseType = message_roger; } } else { (text, voice) = getCtrAtcMessage(ctr_request_denied); assignedAltitude = -1; pilotResponseType = message_roger; } } else { (text, voice) = getCtrAtcMessage(ctr_request_denied); assignedAltitude = -1; pilotResponseType = message_roger; } setApproachButtons(); setCtrButtons(); } else if(messageType == message_engine_start) { if(checkRadioIsType(radio_station_type_ground) == 0) return; var airport = ""; multiplayMessageType = message_type_local; if(selectedComAirportId == property_ClosestAirportId.getValue()) { airport = airportinfo(selectedComAirportId); (airportRunwayInUse, airportRunwayInUseILS) = runwayInUse(airport, runway_take_off); (qnhValue, qnhLiteralValue) = getQNH(); text = sprintf(atcMessageReply.engine_start, atcCallsignText, selectedComStationName, airportRunwayInUse, "QNH", qnhValue ); voice = sprintf(atcMessageReply.engine_start, atcCallsignVoice, selectedComStationName, spellToPhonetic(airportRunwayInUse, spell_runway), "Q N H", qnhLiteralValue ); aircraftIsDeparting = 1; pilotResponseType = message_engine_start; } else { reason = " "; if(rand() < 0.5) reason ~= atcMessageAction.unable_approve; else reason ~= atcMessageAction.negative; reason ~= " " ~ atcMessageAction.startup ~ "."; text = sprintf(atcMessageReply.not_in_this_airfield, atcCallsignText, selectedComStationName, reason ); voice = sprintf(atcMessageReply.not_in_this_airfield, atcCallsignVoice, selectedComStationName, reason ); pilotResponseType = message_roger; } setApproachButtons(); } else if(messageType == message_departure_information) { if(checkRadioIsType(radio_station_type_ground) == 0) return; var airport = ""; multiplayMessageType = message_type_local; if(selectedComAirportId == property_ClosestAirportId.getValue()) { airport = airportinfo(selectedComAirportId); (airportRunwayInUse, airportRunwayInUseILS) = runwayInUse(airport, runway_take_off); var currentTime = substr(property_TimeGmtString.getValue(), 0, 5) ~ "Z"; if(selectedComAirportId != departureInformationAirport) { departureInformationAirport = selectedComAirportId; letter = chr(int(rand() * (size(phoneticLetter) - 1)) + 65); departureInformation = phoneticLetter[letter]; } text = sprintf(atcMessageReply.departure_information, atcCallsignText, selectedComStationName, airportRunwayInUse, getMeteoText(meteo_type_full, 0), currentTime, departureInformation ); voice = sprintf(atcMessageReply.departure_information, atcCallsignVoice, selectedComStationName, spellToPhonetic(airportRunwayInUse, spell_runway), getMeteoText(meteo_type_full, 1), spellToPhonetic(currentTime, spell_number), departureInformation ); pilotResponseType = message_departure_information; aircraftIsDeparting = 1; } else { text = sprintf(atcMessageReply.not_in_this_airfield, atcCallsignText, selectedComStationName, "" ); voice = sprintf(atcMessageReply.not_in_this_airfield, atcCallsignVoice, selectedComStationName, "" ); pilotResponseType = message_roger; } setApproachButtons(); } else if(messageType == message_request_taxi) { if(checkRadioIsType(radio_station_type_ground) == 0) return; var airport = ""; multiplayMessageType = message_type_important; if(selectedComAirportId == property_ClosestAirportId.getValue()) { var towerRadio = getRadio(radio_station_type_tower, radio_type_any); var reportTag = ""; var reportVoice = ""; if(towerRadio == nil) { showPopup(RGAtcName ~ ": " ~ atcMessageAction.tower_not_reachable ~ "."); return; } if(selectedComFrequency == towerRadio.frequency) { reportTag = atcMessageReply.report_ready_departure; reportVoice = reportTag; } else { reportTag = sprintf(atcMessageAction.contact_when_ready, normalizeRadioStationName(towerRadio.name), sprintf("%.3f", towerRadio.frequency) ); reportVoice = sprintf(atcMessageAction.contact_when_ready, normalizeRadioStationName(towerRadio.name), spellToPhonetic(sprintf("%.3f", towerRadio.frequency), spell_number) ); } airport = airportinfo(selectedComAirportId); (airportRunwayInUse, airportRunwayInUseILS) = runwayInUse(airport, runway_take_off); extraText = ""; extraVoice = ""; if(squawkingMode == "On") { squawkCode = getSquawkCode(); squawkChange = 1; extraText ~= sprintf(" Squawk %s.", squawkCode); extraVoice ~= sprintf(" Squawk %s.", spellToPhonetic(squawkCode, spell_number)); setSquawkIdentMode(squawk_ident_off); } else { extraText = "."; extraVoice = "."; } if(departureInformation != "") { if(squawkingMode == "Off") { extraText ~= "."; extraVoice ~= "."; } extraText ~= sprintf("\nYou have information %s.", departureInformation ); extraVoice ~= sprintf("\nYou have information %s.", departureInformation ); } text = sprintf(atcMessageReply.request_taxi, atcCallsignText, selectedComStationName, airportRunwayInUse, reportTag, extraText ); voice = sprintf(atcMessageReply.request_taxi, atcCallsignVoice, selectedComStationName, spellToPhonetic(airportRunwayInUse, spell_runway), reportVoice, extraVoice ); aircraftIsDeparting = 1; pilotResponseType = message_request_taxi; } else { reason = " "; if(rand() < 0.5) reason ~= atcMessageAction.unable_approve; else reason ~= atcMessageAction.negative; reason ~= " " ~ atcMessageAction.taxi_request ~ "."; text = sprintf(atcMessageReply.not_in_this_airfield, atcCallsignText, selectedComStationName, reason ); voice = sprintf(atcMessageReply.not_in_this_airfield, atcCallsignVoice, selectedComStationName, reason ); pilotResponseType = message_roger; } setApproachButtons(); } else if(messageType == message_ready_for_departure) { if(checkRadioIsType(radio_station_type_tower) == 0) return; var airport = nil; multiplayMessageType = message_type_important; if(selectedComAirportId == airportinfo().id) { airport = airportinfo(selectedComAirportId); (airportRunwayInUse, airportRunwayInUseILS) = runwayInUse(airport, runway_take_off); if(nearRunway == 1) { extraText = atcMessageAction.lineup_and_wait; extraVoice = atcMessageAction.lineup_and_wait; } else { extraText = ""; extraVoice = ""; } if(currentRunway == airportRunwayInUse) { text = sprintf(atcMessageReply.ready_departure, atcCallsignText, selectedComStationName, extraText, airportRunwayInUse ); voice = sprintf(atcMessageReply.ready_departure, atcCallsignVoice, selectedComStationName, extraVoice, spellToPhonetic(airportRunwayInUse, spell_runway) ); if(squawkingMode == "On" and squawkChange == 1) { squawkCode = getSquawkCode(); text ~= sprintf(" Squawk %s.", squawkCode); voice ~= sprintf(" Squawk %s.", spellToPhonetic(squawkCode, spell_number)); setSquawkIdentMode(squawk_ident_off); } aircraft_status = status_ready_for_departure; cancelAtcPendingMessage(); setAtcAutoCallbackAfterSeconds(int(rand() * max_cleared_takeoff_secs) + min_cleared_takeoff_secs); pilotResponseType = message_wait_for_departure; } else { text = sprintf(atcMessageReply.wrong_runway, atcCallsignText, selectedComStationName, airportRunwayInUse ); voice = sprintf(atcMessageReply.wrong_runway, atcCallsignVoice, selectedComStationName, spellToPhonetic(airportRunwayInUse, spell_runway) ); pilotResponseType = message_roger; } aircraftIsDeparting = 1; } else { reason = " "; if(rand() < 0.5) reason ~= atcMessageAction.unable_approve; else reason ~= atcMessageAction.negative; reason ~= " " ~ atcMessageAction.departure_request ~ "."; text = sprintf(atcMessageReply.not_in_this_airfield, atcCallsignText, selectedComStationName, reason ); voice = sprintf(atcMessageReply.not_in_this_airfield, atcCallsignVoice, selectedComStationName, reason ); pilotResponseType = message_roger; } setApproachButtons(); } else if(messageType == message_abort_departure) { if(checkRadioIsType(radio_station_type_tower) == 0) return; multiplayMessageType = message_type_important; airport = airportinfo(selectedComAirportId); (airportRunwayInUse, airportRunwayInUseILS) = runwayInUse(airport, runway_take_off); text = sprintf(atcMessageReply.abort_departure, atcCallsignText, selectedComStationName, airportRunwayInUse ); voice = sprintf(atcMessageReply.abort_departure, atcCallsignVoice, selectedComStationName, spellToPhonetic(airportRunwayInUse, spell_runway) ); aircraft_status = status_going_around; aircraftIsDeparting = 0; pilotResponseType = message_going_around; setApproachButtons(); } else if(messageType == message_request_approach or messageType == message_request_ils) { var towerRadio = nil; var initialReportLetter = "R"; var finalTurn = ""; var ilsText = ""; var ilsVoice = ""; if(isRadioTunedToApprovedCtrRadio() == 0) { atcReplyToRequest(message_not_in_this_ctr); return; } if(checkRadioIsType(radio_station_type_approach) == 0) return; multiplayMessageType = message_type_important; if(approvedCtr != nil) airport = airportinfo(approvedCtr.ident); else airport = airportinfo(selectedComAirportId); (airportLandingRunway, airportRunwayInUseILS) = runwayInUse(airport, runway_landing); if(messageType == message_request_ils) { patternFormat = atcMessageReply.request_pattern_ils; approachFormat = atcMessageReply.request_ils; ilsText = "ILS"; ilsVoice = "I L S"; airportLandingRunway = airportRunwayInUseILS; } else { patternFormat = atcMessageReply.request_pattern_approach; approachFormat = atcMessageReply.request_approach; ilsText = ""; ilsVoice = ""; } var (courseToRunway, distanceToRunway) = courseAndDistance(airport.runways[airportLandingRunway]); approachPoint = geo.Coord.new().set_latlon(airport.runways[airportLandingRunway].lat, airport.runways[airportLandingRunway].lon); approachPoint.apply_course_distance(airport.runways[airportLandingRunway].heading, approach_point_distance * NM2M); var heading = property_Aircraft_HeadingMagneticDeg.getValue(); patternPoint = geo.Coord.new().set_latlon(approachPoint.lat(), approachPoint.lon()); if(geo.normdeg(courseToRunway - heading) < 180) { patternCircuit = atcMessageAction.right_tag; courseToApproachPoint = geo.normdeg(airport.runways[airportLandingRunway].heading - 90); } else { patternCircuit = atcMessageAction.left_tag; courseToApproachPoint = geo.normdeg(airport.runways[airportLandingRunway].heading + 90); } if(messageType == message_request_approach) finalTurn = patternCircuit; else finalTurn = ""; patternPoint.apply_course_distance(courseToApproachPoint, pattern_point_distance * NM2M); var (courseToPattern, distanceToPattern) = courseAndDistance(patternPoint); var (courseToApproach, distanceToApproach) = courseAndDistance(approachPoint); airportElevation = airport.elevation * M2FT; approachAltitude = altitudeApproachSlope(airport.elevation, abs(approach_point_distance), 1); var cruiseAltitude = approachAltitude; var altitudeInstructionText = ""; var altitudeInstructionVoice = ""; if(distanceToPattern > min_distance_for_altitude_change) { var altSteps = int(distanceToPattern / miles_altitude_step); cruiseAltitude += (feet_altitude_step * altSteps); cruiseAltitude = normalizeAltitudeHeading(cruiseAltitude, courseToPattern, approachAltitude); if(property_Aircraft_AltitudeFeet.getValue() > cruiseAltitude) actionTag = atcMessageAction.descend; else actionTag = atcMessageAction.climb; altitudeInstructionText = sprintf(atcMessageAction.altitude_change, actionTag, sprintf("%d", cruiseAltitude) ); altitudeInstructionVoice = sprintf(atcMessageAction.altitude_change, actionTag, spellToPhonetic(sprintf("%d", cruiseAltitude), spell_number_to_literal) ); assignedAltitude = cruiseAltitude; altitude_check_interval = flightLevelChangeSeconds(assignedAltitude); } if(distanceToPattern < distanceToRunway or distanceToApproach < distanceToRunway) { # Route plane to runway approach point (courseToApproach, distanceToApproach) = courseAndDistance(approachPoint); text = sprintf(approachFormat, atcCallsignText, selectedComStationName, altitudeInstructionText, sprintf("%d", courseToApproach), sprintf("%d", distanceToApproach), finalTurn, ilsText, airportLandingRunway, sprintf("%d", approachAltitude), getMeteoText(meteo_type_qnh, 0) ); voice = sprintf(approachFormat, atcCallsignVoice, selectedComStationName, altitudeInstructionVoice, spellToPhonetic(sprintf("%d", courseToApproach), spell_number), spellToPhonetic(sprintf("%d", distanceToApproach), spell_number_to_literal), finalTurn, ilsVoice, spellToPhonetic(airportLandingRunway, spell_runway), spellToPhonetic(sprintf("%d", approachAltitude), spell_number_to_literal), getMeteoText(meteo_type_qnh, 1) ); assignedAltitude = approachAltitude; altitude_check_interval = flightLevelChangeSeconds(assignedAltitude); approachStatus = approach_status_to_approach; approach_check_counter = 0; } else if(distanceToApproach > 3) { # Route plane to pattern point text = sprintf(patternFormat, atcCallsignText, selectedComStationName, altitudeInstructionText, sprintf("%d", courseToPattern), sprintf("%d", distanceToPattern), patternCircuit, sprintf("%d", courseToApproachPoint), finalTurn, ilsText, airportLandingRunway, sprintf("%d", approachAltitude), getMeteoText(meteo_type_qnh, 0) ); voice = sprintf(patternFormat, atcCallsignVoice, selectedComStationName, altitudeInstructionVoice, spellToPhonetic(sprintf("%d", courseToPattern), spell_number), spellToPhonetic(sprintf("%d", distanceToPattern), spell_number_to_literal), patternCircuit, spellToPhonetic(sprintf("%d", courseToApproachPoint), spell_number), finalTurn, ilsVoice, spellToPhonetic(airportLandingRunway, spell_runway), spellToPhonetic(sprintf("%d", approachAltitude), spell_number_to_literal), getMeteoText(meteo_type_qnh, 1) ); approachStatus = approach_status_to_pattern; approach_check_counter = 0; } else { # Route plane to approach point text = sprintf(approachFormat, atcCallsignText, selectedComStationName, altitudeInstructionText, sprintf("%d", courseToApproachPoint), sprintf("%d", distanceToApproach), finalTurn, ilsText, airportLandingRunway, sprintf("%d", approachAltitude), getMeteoText(meteo_type_qnh, 0) ); voice = sprintf(approachFormat, atcCallsignVoice, selectedComStationName, altitudeInstructionVoice, spellToPhonetic(sprintf("%d", courseToApproachPoint), spell_number), spellToPhonetic(sprintf("%d", distanceToApproach), spell_number_to_literal), finalTurn, ilsVoice, spellToPhonetic(airportLandingRunway, spell_runway), spellToPhonetic(sprintf("%d", approachAltitude), spell_number_to_literal), getMeteoText(meteo_type_qnh, 1) ); approachStatus = approach_status_to_approach; approach_check_counter = 0; } if(messageType == message_request_approach) pilotResponseType = message_approved_approach; else pilotResponseType = message_approved_ils; if(approvedCtr == nil or approvedCtr.ident != currentCtr.ident) { (text, voice) = getCtrAtcMessage(ctr_request_denied); assignedAltitude = -1; pilotResponseType = message_roger; } else { if(messageType == message_request_approach) aircraft_status = status_requested_approach; else aircraft_status = status_requested_ils; aircraftIsDeparting = 0; } setApproachButtons(); setCtrButtons(); cancelAtcPendingMessage(); approach_check_interval = approach_check_interval_initial; } else if(messageType == message_approach_route_instructions) { text = sprintf(atcMessageReply.approach_route_instructions, atcCallsignText, selectedComStationName, approachRouteTextInstructions ); voice = sprintf(atcMessageReply.approach_route_instructions, atcCallsignVoice, selectedComStationName, approachRouteVoiceInstructions ); multiplayMessageType = message_type_local; pilotResponseType = message_approach_route_instructions; } else if(messageType == message_airfield_in_sight or messageType == message_ils_established) { if(checkRadioIsType(radio_station_type_tower) == 0) return; multiplayMessageType = message_type_important; if(aircraft_status == status_requested_approach or aircraft_status == status_requested_ils or aircraft_status == status_cleared_for_land_approach or aircraft_status == status_cleared_for_land_ils) { airport = airportinfo(selectedComAirportId); (airportLandingRunway, airportRunwayInUseILS) = runwayInUse(airport, runway_landing); if(aircraft_status == status_requested_ils or aircraft_status == status_cleared_for_land_ils) landingRunway = airportRunwayInUseILS; else landingRunway = airportLandingRunway; text = sprintf(atcMessageReply.airfield_in_sight, atcCallsignText, selectedComStationName, getMeteoText(meteo_type_wind, 0), landingRunway ); voice = sprintf(atcMessageReply.airfield_in_sight, atcCallsignVoice, selectedComStationName, getMeteoText(meteo_type_wind, 1), spellToPhonetic(landingRunway, spell_runway) ); if(aircraft_status == status_requested_ils) aircraft_status = status_cleared_for_land_ils; else aircraft_status = status_cleared_for_land_approach; setApproachButtons(); cancelAtcPendingMessage(); setAtcAutoCallbackAfterSeconds(land_check_secs); aircraftIsDeparting = 0; pilotResponseType = message_cleared_landing; } else { text = sprintf(atcMessageReply.not_allowed_to_land, atcCallsignText, selectedComStationName ); voice = sprintf(atcMessageReply.not_allowed_to_land, atcCallsignVoice, selectedComStationName ); setApproachButtons(); pilotResponseType = message_roger; } if(approvedCtr == nil or approvedCtr.ident != currentCtr.ident) { (text, voice) = getCtrAtcMessage(ctr_request_denied); assignedAltitude = -1; pilotResponseType = message_roger; setApproachButtons(); setCtrButtons(); } } else if(messageType == message_flying_too_low) { multiplayMessageType = message_type_local; text = sprintf(atcMessageReply.flying_too_low, atcCallsignText ); voice = sprintf(atcMessageReply.flying_too_low, atcCallsignVoice ); pilotResponseType = message_none; } else if(messageType == message_terrain_ahead) { multiplayMessageType = message_type_local; text = sprintf(atcMessageReply.terrain_ahead, atcCallsignText ); voice = sprintf(atcMessageReply.terrain_ahead, atcCallsignVoice ); pilotResponseType = message_none; } else if(messageType == message_leaving_ctr) { multiplayMessageType = message_type_local; var nearestCtr = getNearbyCtr(ctr_search_range); if(nearestCtr != nil) { var radio = getCtrRadio(nearestCtr); extraText = sprintf(atcMessageAction.contact_ctr, nearestCtr.airport, "CTR", sprintf("%.3f", radio.frequency) ); extraVoice = sprintf(atcMessageAction.contact_ctr, nearestCtr.airport, "C T R", spellToPhonetic(sprintf("%.3f", radio.frequency), spell_number) ); } text = sprintf(atcMessageReply.leaving_ctr, atcCallsignText, selectedComStationName, "CTR", extraText ); voice = sprintf(atcMessageReply.leaving_ctr, atcCallsignVoice, selectedComStationName, "C T R", extraVoice ); aircraftIsDeparting = 0; pilotResponseType = message_roger; resetAircraftStatus(); setApproachButtons(); } else if(messageType == message_not_in_this_ctr) { multiplayMessageType = message_type_local; text = sprintf(atcMessageReply.not_in_this_ctr, atcCallsignText, selectedComStationName, "CTR", "CTR" ); voice = sprintf(atcMessageReply.not_in_this_ctr, atcCallsignVoice, selectedComStationName, "C T R", "C T R" ); aircraftIsDeparting = 0; pilotResponseType = message_roger; resetAircraftStatus(); setApproachButtons(); } if(messageType == message_request_fl) { multiplayMessageType = message_type_important; text = sprintf(atcMessageReply.fl_approved, atcCallsignText, selectedComStationName, "FL", getFlightLevelCode(requestedAltitude) ); voice = sprintf(atcMessageReply.fl_approved, atcCallsignVoice, selectedComStationName, "Flight Level", spellToPhonetic(getFlightLevelCode(requestedAltitude), spell_number) ); resetAircraftStatus(); setApproachButtons(); setCtrButtons(); assignedAltitude = requestedAltitude; altitude_check_interval = flightLevelChangeSeconds(assignedAltitude); pilotResponseType = message_fl_approved; if(approvedCtr == nil or approvedCtr.ident != currentCtr.ident) { (text, voice) = getCtrAtcMessage(ctr_request_denied); assignedAltitude = -1; pilotResponseType = message_roger; } } else if(messageType == message_change_altitude) { multiplayMessageType = message_type_important; assignedAltitude = getFlightPlanAltitude(); if(assignedAltitude == -1) { assignedAltitude = normalizeAltitudeHeading(property_Aircraft_AltitudeFeet.getValue(), property_Aircraft_HeadingDeg.getValue(), minCruiseAltidude); if(rand() < 0.5 and (assignedAltitude - flight_level_step) >= minCruiseAltidude) assignedAltitude -= flight_level_step; } if(property_Aircraft_AltitudeFeet.getValue() > assignedAltitude) actionTag = atcMessageAction.descend; else actionTag = atcMessageAction.climb; text = sprintf(atcMessageReply.change_altitude, atcCallsignText, selectedComStationName, actionTag, sprintf("%d", assignedAltitude) ); voice = sprintf(atcMessageReply.change_altitude, atcCallsignVoice, selectedComStationName, actionTag, spellToPhonetic(sprintf("%d", assignedAltitude), spell_number_to_literal) ); altitude_check_interval = flightLevelChangeSeconds(assignedAltitude); pilotResponseType = message_change_altitude; } else if(messageType == message_check_altitude) { multiplayMessageType = message_type_local; if(property_Aircraft_AltitudeFeet.getValue() > assignedAltitude) actionTag = atcMessageAction.descend; else actionTag = atcMessageAction.climb; text = sprintf(atcMessageReply.check_altitude, atcCallsignText, selectedComStationName, actionTag, sprintf("%d", assignedAltitude) ); voice = sprintf(atcMessageReply.check_altitude, atcCallsignVoice, selectedComStationName, actionTag, spellToPhonetic(sprintf("%d", assignedAltitude), spell_number_to_literal) ); altitude_check_interval = flightLevelChangeSeconds(assignedAltitude); pilotResponseType = message_change_altitude; } else if(messageType == message_check_transponder) { multiplayMessageType = message_type_local; if(squawkIdent != squawk_ident_code) { if(squawkCode == "") squawkCode = getSquawkCode(); extraText = sprintf("%s and ", squawkCode); extraVoice = sprintf("%s and ", spellToPhonetic(squawkCode, spell_number)); setSquawkIdentMode(squawk_ident_code); } else { extraText = ""; extraVoice = ""; setSquawkIdentMode(squawk_ident_only); } text = sprintf(atcMessageReply.check_transponder, atcCallsignText, selectedComStationName, extraText ); voice = sprintf(atcMessageReply.check_transponder, atcCallsignVoice, selectedComStationName, extraVoice ); pilotResponseType = message_check_transponder; } else if(messageType == message_abort_approach) { var msg = ""; var rwy = ""; multiplayMessageType = message_type_important; if(aircraft_status == status_requested_approach or aircraft_status == status_requested_ils) { msg = atcMessageReply.abort_approach; rwy = airportLandingRunway; } else if(aircraft_status == status_cleared_for_land_approach or aircraft_status == status_cleared_for_land_ils) { msg = atcMessageReply.abort_landing; rwy = landingRunway; } else return; text = sprintf(msg, atcCallsignText, selectedComStationName, rwy ); voice = sprintf(msg, atcCallsignVoice, selectedComStationName, spellToPhonetic(rwy, spell_runway) ); aircraftIsDeparting = 0; approachStatus = approach_status_none; approach_check_interval = approach_check_interval_initial; resetAircraftStatus(); setApproachButtons(); pilotResponseType = message_going_around; } if(text != "" or voice != "") atcMessage(text, voice); } var atcAutoMessage = func() { var text = ""; var voice = ""; var minCruiseAltidude = getMinCruiseAltitude(); if(isRadioServiceable() == 0) return; multiplayMessageType = message_type_local; (atcCallsignText, atcCallsignVoice) = getCallSignForAtc(0); if(aircraft_status == status_ready_for_departure) { multiplayMessageType = message_type_important; text = sprintf(atcMessageReply.cleared_for_takeoff, atcCallsignText, selectedComStationName, airportRunwayInUse, getMeteoText(meteo_type_qnh, 0) ); voice = sprintf(atcMessageReply.cleared_for_takeoff, atcCallsignVoice, selectedComStationName, spellToPhonetic(airportRunwayInUse, spell_runway), getMeteoText(meteo_type_qnh, 1) ); aircraft_status = status_cleared_for_takeoff; cancelAtcPendingMessage(); setAtcAutoCallbackAfterSeconds(max_departure_secs); departure_extra_time = 0; approvedCtr = currentCtr; altitude_check_counter = 0; aircraftIsDeparting = 1; setApproachButtons(); setCtrButtons(); pilotResponseType = message_cleared_take_off; } else if(aircraft_status == status_cleared_for_takeoff) { multiplayMessageType = message_type_important; if(departure_extra_time == 0 and alignedOnRunway == 1) { addSecondsToAtcAutoCallback(extra_departure_secs); departure_extra_time = 1; } else { text = sprintf(atcMessageReply.vacate_runway, atcCallsignText, selectedComStationName ); voice = sprintf(atcMessageReply.vacate_runway, atcCallsignVoice, selectedComStationName ); cancelAtcPendingMessage(); setAtcAutoCallbackAfterSeconds(max_take_off_secs); } approvedCtr = currentCtr; altitude_check_counter = 0; aircraftIsDeparting = 1; setApproachButtons(); setCtrButtons(); pilotResponseType = message_roger; } else if(aircraft_status == status_took_off) { var airport = airportinfo(); var extraText = ""; var extraVoice = ""; multiplayMessageType = message_type_local; aircraftIsDeparting = 1; var cruiseAltitude = getFlightPlanAltitude(); if(cruiseAltitude == -1) cruiseAltitude = normalizeAltitudeHeading(airport.elevation * M2FT, airport.runways[airportRunwayInUse].heading, minCruiseAltidude); var radio = getCtrRadio(currentCtr); if(radio.frequency != selectedComFrequency) { extraText = sprintf(atcMessageReply.contact_radio, normalizeRadioStationName(radio.name), sprintf("%.3f", radio.frequency) ); extraVoice = sprintf(atcMessageReply.contact_radio, normalizeRadioStationName(radio.name), spellToPhonetic(sprintf("%.3f", radio.frequency), spell_number) ); } if(squawkingMode == "On") { if(checkTransponder(selectedTransponder) == 0) { if(squawkCode == "") squawkCode = getSquawkCode(); if(extraText != "") { extraText ~= "."; extraVoice ~= "."; } extraText ~= " " ~ sprintf(atcMessageAction.check_transponder, squawkCode); extraVoice ~= " " ~ sprintf(atcMessageAction.check_transponder, spellToPhonetic(squawkCode, spell_number)); setSquawkIdentMode(squawk_ident_code); } } text = sprintf(atcMessageReply.leaving_airport, atcCallsignText, selectedComStationName, getMeteoText(meteo_type_wind, 0), sprintf("%d", cruiseAltitude), extraText ); voice = sprintf(atcMessageReply.leaving_airport, atcCallsignVoice, selectedComStationName, getMeteoText(meteo_type_wind, 1), spellToPhonetic(sprintf("%d", cruiseAltitude), spell_number_to_literal), extraVoice ); cancelAtcPendingMessage(); aircraft_status = status_flying; approachStatus = approach_status_none; approach_check_interval = approach_check_interval_initial; approvedCtr = currentCtr; assignedAltitude = cruiseAltitude; altitude_check_counter = 0; altitude_check_interval = flightLevelChangeSeconds(assignedAltitude); setApproachButtons(); setCtrButtons(); pilotResponseType = message_leaving_airport; } else if(aircraft_status == status_cleared_for_land_approach or aircraft_status == status_cleared_for_land_ils) { var approachingRunway = ""; var distance = 9999; multiplayMessageType = message_type_important; (approachingRunway, distance) = getApproachingRunway(currentAirport); airport = airportinfo(selectedComAirportId); (airportLandingRunway, airportRunwayInUseILS) = runwayInUse(airport, runway_landing); if(aircraft_status == status_cleared_for_land_ils) landingRunway = airportRunwayInUseILS; else landingRunway = airportLandingRunway; if(approachingRunway != "" and approachingRunway != landingRunway) { text = sprintf(atcMessageReply.wrong_approach_runway, atcCallsignText, selectedComStationName, landingRunway ); voice = sprintf(atcMessageReply.wrong_approach_runway, atcCallsignVoice, selectedComStationName, spellToPhonetic(landingRunway, spell_runway) ); pilotResponseType = message_wilco; } else pilotResponseType = message_none; aircraftIsDeparting = 0; cancelAtcPendingMessage(); setAtcAutoCallbackAfterSeconds(land_check_secs); setApproachButtons(); } else if(aircraft_status == status_landed) { airport = airportinfo(); multiplayMessageType = message_type_important; text = sprintf(atcMessageReply.welcome_to_airport, atcCallsignText, selectedComStationName, airport.name ); voice = sprintf(atcMessageReply.welcome_to_airport, atcCallsignVoice, selectedComStationName, airport.name ); cancelAtcPendingMessage(); aircraft_status = status_going_around; assignedAltitude = -1; aircraftIsDeparting = 0; approachStatus = approach_status_none; approach_check_interval = approach_check_interval_initial; squawkCode = ""; squawkChange = 0; setSquawkIdentMode(squawk_ident_off); setApproachButtons(); pilotResponseType = message_roger; } if(text != "" or voice != "") atcMessage(text, voice); } var pilotRequest = func(messageType) { if(isRadioServiceable() == 0) return; if(isSimulationPaused() == 1) return; var text = ""; var voice = ""; multiplayMessageType = message_type_local; if(messageType == message_say_again) { multiplayMessageType = message_type_local; text = sprintf(pilotMessageRequest.say_again, selectedComStationName, atcCallsignText ); voice = sprintf(pilotMessageRequest.say_again, selectedComStationName, atcCallsignVoice ); } else if(messageType == message_radio_check) { multiplayMessageType = message_type_local; text = sprintf(pilotMessageRequest.radio_check, selectedComStationName, atcCallsignText, sprintf("%.3f", selectedComFrequency) ); voice = sprintf(pilotMessageRequest.radio_check, selectedComStationName, atcCallsignVoice, spellToPhonetic(sprintf("%.3f", selectedComFrequency), spell_number) ); } else if(messageType == message_engine_start) { multiplayMessageType = message_type_local; text = sprintf(pilotMessageRequest.engine_start, selectedComStationName, atcCallsignText ); voice = sprintf(pilotMessageRequest.engine_start, selectedComStationName, atcCallsignVoice ); } else if(messageType == message_departure_information) { multiplayMessageType = message_type_local; text = sprintf(pilotMessageRequest.departure_information, selectedComStationName, atcCallsignText ); voice = sprintf(pilotMessageRequest.departure_information, selectedComStationName, atcCallsignVoice ); } else if(messageType == message_request_taxi) { multiplayMessageType = message_type_important; if(departureInformation != "") { extraText = sprintf(" with information %s", departureInformation ); extraVoice = extraText; } else { extraText = ""; extraVoice = ""; } text = sprintf(pilotMessageRequest.request_taxi, selectedComStationName, atcCallsignText, extraText ); voice = sprintf(pilotMessageRequest.request_taxi, selectedComStationName, atcCallsignVoice, extraVoice ); } else if(messageType == message_ready_for_departure) { multiplayMessageType = message_type_important; text = sprintf(pilotMessageRequest.ready_departure, selectedComStationName, atcCallsignText, currentRunway ); voice = sprintf(pilotMessageRequest.ready_departure, selectedComStationName, atcCallsignVoice, spellToPhonetic(currentRunway, spell_runway) ); if(squawkingMode == "On" and squawkCode == "") squawkChange = 1; } else if(messageType == message_abort_departure) { multiplayMessageType = message_type_important; text = sprintf(pilotMessageRequest.abort_departure, selectedComStationName, atcCallsignText, currentRunway ); voice = sprintf(pilotMessageRequest.abort_departure, selectedComStationName, atcCallsignVoice, spellToPhonetic(currentRunway, spell_runway) ); } else if(messageType == message_request_approach) { multiplayMessageType = message_type_important; text = sprintf(pilotMessageRequest.request_approach, selectedComStationName, atcCallsignText ); voice = sprintf(pilotMessageRequest.request_approach, selectedComStationName, atcCallsignVoice ); } else if(messageType == message_request_ils) { multiplayMessageType = message_type_important; text = sprintf(pilotMessageRequest.request_ils, selectedComStationName, atcCallsignText, "ILS" ); voice = sprintf(pilotMessageRequest.request_ils, selectedComStationName, atcCallsignVoice, "I L S" ); } else if(messageType == message_airfield_in_sight) { multiplayMessageType = message_type_important; text = sprintf(pilotMessageRequest.airfield_in_sight, selectedComStationName, atcCallsignText ); voice = sprintf(pilotMessageRequest.airfield_in_sight, selectedComStationName, atcCallsignVoice ); } else if(messageType == message_ils_established) { multiplayMessageType = message_type_important; text = sprintf(pilotMessageRequest.ils_established, selectedComStationName, atcCallsignText, "ILS", airportRunwayInUseILS ); voice = sprintf(pilotMessageRequest.ils_established, selectedComStationName, atcCallsignVoice, "I L S", spellToPhonetic(airportRunwayInUseILS, spell_runway) ); } else if(messageType == message_request_ctr) { multiplayMessageType = message_type_local; text = sprintf(pilotMessageRequest.request_ctr, selectedComStationName, atcCallsignText, "CTR" ); voice = sprintf(pilotMessageRequest.request_ctr, selectedComStationName, atcCallsignVoice, "C T R" ); } else if(messageType == message_request_fl) { multiplayMessageType = message_type_important; text = sprintf(pilotMessageRequest.request_fl, selectedComStationName, atcCallsignText, "FL", getFlightLevelCode(requestedAltitude) ); voice = sprintf(pilotMessageRequest.request_fl, selectedComStationName, atcCallsignVoice, "Flight Level", spellToPhonetic(getFlightLevelCode(requestedAltitude), spell_number) ); } else if(messageType == message_abort_approach) { multiplayMessageType = message_type_important; if(aircraft_status == status_requested_approach) { text = sprintf(pilotMessageRequest.abort_approach, selectedComStationName, atcCallsignText, airportLandingRunway ); voice = sprintf(pilotMessageRequest.abort_approach, selectedComStationName, atcCallsignVoice, spellToPhonetic(airportLandingRunway, spell_runway) ); } else if(aircraft_status == status_requested_ils) { text = sprintf(pilotMessageRequest.abort_ils, selectedComStationName, atcCallsignText, "ILS", airportLandingRunway ); voice = sprintf(pilotMessageRequest.abort_ils, selectedComStationName, atcCallsignVoice, "I L S", spellToPhonetic(airportLandingRunway, spell_runway) ); } else if(aircraft_status == status_cleared_for_land_approach or aircraft_status == status_cleared_for_land_ils) { text = sprintf(pilotMessageRequest.abort_landing, selectedComStationName, atcCallsignText, landingRunway ); voice = sprintf(pilotMessageRequest.abort_landing, selectedComStationName, atcCallsignVoice, spellToPhonetic(landingRunway, spell_runway) ); } } else return; if(text != "" or voice != "") { cancelAtcPendingMessage(); var secs = 0; pilotMessageType = messageType; if(pilotRequestMode != "Disabled") secs = int(size(text) / 12) + 1; if((pilotRequestMode == "Voice only" or pilotRequestMode == "Voice and text") and voice != "") { speak(voice, pilotVoice['text'], pilotVoice['type']); secs += 2; } echoMultiplayerChat(text, multiplayMessageType); if((pilotRequestMode == "Text only" or pilotRequestMode == "Voice and text") and text != "") { var window = screen.window.new(atc_popup_x_position, atc_popup_y_position, 10, secs); window.bg = popup_window_bg_color; window.fg = popup_window_fg_pilot_color; window.align = atc_popup_align; window.write(text); addAtcLog(text); } setPilotMessageSeconds(secs + pilot_message_pause_atc_seconds); autoATCMessageDelay(secs + pilot_message_pause_atc_seconds); } } var pilotResponse = func(messageType) { var text = ""; var voice = ""; var extraText = ""; var extraVoice = ""; var initialReportLetter = "R"; var radio = nil; if(initialized == 0) initRgATC(); if(isRadioServiceable() == 0) return; if(messageType == message_none or selectedComAirportId == "") return; multiplayMessageType = message_type_local; (atcCallsignText, atcCallsignVoice) = getCallSignForAtc(0); if(messageType == message_roger) { multiplayMessageType = message_type_local; text = sprintf(pilotMessageResponse.roger, selectedComStationName, atcCallsignText ); voice = sprintf(pilotMessageResponse.roger, selectedComStationName, atcCallsignVoice ); } else if(messageType == message_wilco) { multiplayMessageType = message_type_local; text = sprintf(pilotMessageResponse.wilco, selectedComStationName, atcCallsignText ); voice = sprintf(pilotMessageResponse.wilco, selectedComStationName, atcCallsignVoice ); } else if(messageType == message_departure_information) { if(departureInformation == "") return; multiplayMessageType = message_type_local; text = sprintf(pilotMessageResponse.departure_information, selectedComStationName, atcCallsignText, departureInformation ); voice = sprintf(pilotMessageResponse.departure_information, selectedComStationName, atcCallsignVoice, departureInformation ); } else if(messageType == message_engine_start) { multiplayMessageType = message_type_local; text = sprintf(pilotMessageResponse.engine_start, selectedComStationName, atcCallsignText, airportRunwayInUse ); voice = sprintf(pilotMessageResponse.engine_start, selectedComStationName, atcCallsignVoice, spellToPhonetic(airportRunwayInUse, spell_runway) ); } else if(messageType == message_ctr_approved) { multiplayMessageType = message_type_local; if(squawkingMode == "On") { if(squawkChange == 1) { extraText = sprintf(", Squawk %s and IDENT", squawkCode); extraVoice = sprintf(", Squawk %s and ident", spellToPhonetic(squawkCode, spell_number)); setSquawkIdentMode(squawk_ident_code); } else { extraText = ", Squawk IDENT"; extraVoice = ", Squawk ident"; setSquawkIdentMode(squawk_ident_only); } squawkChange = 0; } else { extraText = ""; extraVoice = ""; } text = sprintf(pilotMessageResponse.approved_ctr, selectedComStationName, atcCallsignText, "CTR", extraText ); voice = sprintf(pilotMessageResponse.approved_ctr, selectedComStationName, atcCallsignVoice, "C T R", extraVoice ); } else if(messageType == message_request_taxi) { multiplayMessageType = message_type_important; if(squawkingMode == "On") { extraText = sprintf(", Squawk %s", squawkCode); extraVoice = sprintf(", Squawk %s", spellToPhonetic(squawkCode, spell_number)); squawkChange = 0; } else { extraText = ""; extraVoice = ""; } text = sprintf(pilotMessageResponse.taxi, selectedComStationName, atcCallsignText, airportRunwayInUse, extraText ); voice = sprintf(pilotMessageResponse.taxi, selectedComStationName, atcCallsignVoice, spellToPhonetic(airportRunwayInUse, spell_runway), extraVoice ); } else if(messageType == message_wait_for_departure) { multiplayMessageType = message_type_important; if(nearRunway == 1) { extraText = atcMessageAction.lineup_and_wait; extraVoice = atcMessageAction.lineup_and_wait; } else { extraText = ""; extraVoice = ""; } text = sprintf(pilotMessageResponse.wait_departure, selectedComStationName, atcCallsignText, extraText, airportRunwayInUse ); voice = sprintf(pilotMessageResponse.wait_departure, selectedComStationName, atcCallsignVoice, extraVoice, spellToPhonetic(airportRunwayInUse, spell_runway) ); if(squawkingMode == "On" and squawkChange == 1) { text ~= sprintf(". Squawk %s.", squawkCode); voice ~= sprintf(". Squawk %s.", spellToPhonetic(squawkCode, spell_number)); squawkChange = 0; } } else if(messageType == message_cleared_take_off) { multiplayMessageType = message_type_important; text = sprintf(pilotMessageResponse.cleared_take_off, selectedComStationName, atcCallsignText, airportRunwayInUse ); voice = sprintf(pilotMessageResponse.cleared_take_off, selectedComStationName, atcCallsignVoice, spellToPhonetic(airportRunwayInUse, spell_runway) ); } else if(messageType == message_leaving_airport) { radio = getCtrRadio(currentCtr); multiplayMessageType = message_type_local; if(radio != nil and radio.frequency != selectedComFrequency) { extraText = sprintf(atcMessageReply.contact_radio, normalizeRadioStationName(radio.name), sprintf("%.3f", radio.frequency) ); extraVoice = sprintf(atcMessageReply.contact_radio, normalizeRadioStationName(radio.name), spellToPhonetic(sprintf("%.3f", radio.frequency), spell_number) ); } if(squawkingMode == "On") { if(checkTransponder(selectedTransponder) == 0) { if(squawkCode == "") squawkCode = getSquawkCode(); if(extraText != "") { extraText ~= "."; extraVoice ~= "."; } extraText ~= " " ~ sprintf(atcMessageAction.check_transponder, squawkCode); extraVoice ~= " " ~ sprintf(atcMessageAction.check_transponder, spellToPhonetic(squawkCode, spell_number)); setSquawkIdentMode(squawk_ident_code); } else setSquawkIdentMode(squawk_ident_off); } text = sprintf(pilotMessageResponse.leaving_airport, selectedComStationName, atcCallsignText, sprintf("%d", assignedAltitude), extraText ); voice = sprintf(pilotMessageResponse.leaving_airport, selectedComStationName, atcCallsignVoice, spellToPhonetic(sprintf("%d", assignedAltitude), spell_number_to_literal), extraVoice ); altitude_check_interval = flightLevelChangeSeconds(assignedAltitude); } else if(messageType == message_approved_approach) { multiplayMessageType = message_type_important; text = sprintf(pilotMessageResponse.approved_approach, selectedComStationName, atcCallsignText, airportLandingRunway ); voice = sprintf(pilotMessageResponse.approved_approach, selectedComStationName, atcCallsignVoice, spellToPhonetic(airportLandingRunway, spell_runway) ); } else if(messageType == message_approved_ils) { multiplayMessageType = message_type_important; text = sprintf(pilotMessageResponse.approved_ils, selectedComStationName, atcCallsignText, "ILS", airportLandingRunway ); voice = sprintf(pilotMessageResponse.approved_ils, selectedComStationName, atcCallsignVoice, "I L S", spellToPhonetic(airportLandingRunway, spell_runway) ); } else if(messageType == message_approach_route_instructions) { multiplayMessageType = message_type_local; text = sprintf(pilotMessageResponse.approach_route_instructions, selectedComStationName, atcCallsignText, approachRouteTextInstructions ); voice = sprintf(pilotMessageResponse.approach_route_instructions, selectedComStationName, atcCallsignVoice, approachRouteVoiceInstructions ); } else if(messageType == message_cleared_landing) { multiplayMessageType = message_type_important; text = sprintf(pilotMessageResponse.cleared_landing, selectedComStationName, atcCallsignText, landingRunway ); voice = sprintf(pilotMessageResponse.cleared_landing, selectedComStationName, atcCallsignVoice, spellToPhonetic(landingRunway, spell_runway) ); } else if(messageType == message_fl_approved) { multiplayMessageType = message_type_important; text = sprintf(pilotMessageResponse.approved_fl, selectedComStationName, atcCallsignText, "FL", getFlightLevelCode(requestedAltitude) ); voice = sprintf(pilotMessageResponse.approved_fl, selectedComStationName, atcCallsignVoice, "Flight Level", spellToPhonetic(getFlightLevelCode(requestedAltitude), spell_number) ); } else if(messageType == message_change_altitude) { var actionTag = ""; multiplayMessageType = message_type_important; if(property_Aircraft_AltitudeFeet.getValue() > assignedAltitude) actionTag = atcMessageAction.descend; else actionTag = atcMessageAction.climb; text = sprintf(pilotMessageResponse.change_altitude, selectedComStationName, atcCallsignText, actionTag, sprintf("%d", assignedAltitude) ); voice = sprintf(pilotMessageResponse.change_altitude, selectedComStationName, atcCallsignVoice, actionTag, spellToPhonetic(sprintf("%d", assignedAltitude), spell_number_to_literal) ); altitude_check_interval = flightLevelChangeSeconds(assignedAltitude); } else if(messageType == message_check_transponder) { if(squawkIdent == squawk_ident_code) { extraText = sprintf("%s and ", squawkCode); extraVoice = sprintf("%s and ", spellToPhonetic(squawkCode, spell_number)); } else { extraText = ""; extraVoice = ""; } text = sprintf(pilotMessageResponse.check_transponder, selectedComStationName, atcCallsignText, extraText ); voice = sprintf(pilotMessageResponse.check_transponder, selectedComStationName, atcCallsignVoice, extraVoice ); } else if(messageType == message_going_around) { multiplayMessageType = message_type_important; text = sprintf(pilotMessageResponse.going_around, selectedComStationName, atcCallsignText ); voice = sprintf(pilotMessageResponse.going_around, selectedComStationName, atcCallsignVoice ); } else if(messageType == message_contact_radio) { multiplayMessageType = message_type_local; if(contactRadio != nil) { text = sprintf(pilotMessageResponse.contact_radio, atcCallsignText, selectedComStationName, normalizeRadioStationName(contactRadio.name), sprintf("%.3f", contactRadio.frequency) ); voice = sprintf(pilotMessageResponse.contact_radio, atcCallsignVoice, selectedComStationName, normalizeRadioStationName(contactRadio.name), spellToPhonetic(sprintf("%.3f", contactRadio.frequency), spell_number) ); } } pilotResponseType = message_none; pilot_response_wait_seconds = -1; if(text != "" or voice != "") { var secs = 0; if(pilotResponseMode != "Disabled") secs = int(size(text) / 12) + 1; if((pilotResponseMode == "Voice only" or pilotResponseMode == "Voice and text") and voice != "") { speak(voice, pilotVoice['text'], pilotVoice['type']); secs += 2; } if((pilotResponseMode == "Text only" or pilotResponseMode == "Voice and text") and text != "") { var window = screen.window.new(atc_popup_x_position, atc_popup_y_position, 10, secs); window.bg = popup_window_bg_color; window.fg = popup_window_fg_pilot_color; window.align = atc_popup_align; window.write(text); addAtcLog(text); } autoATCMessageDelay(secs); echoMultiplayerChat(text, multiplayMessageType); } } var atcMessage = func(text, voice) { var secs = int(size(voice) / 12) + 1; if((atcMessageMode == "Voice only" or atcMessageMode == "Voice and text") and voice != "") { if(stationVoice[selectedComFrequency] != nil) speak(voice, stationVoice[selectedComFrequency]['text'], stationVoice[selectedComFrequency]['type']); else showPopup(atcMessageAction.inconsistent_radio_data); } if((atcMessageMode == "Text only" or atcMessageMode == "Voice and text") and text != "") { var window = screen.window.new(atc_popup_x_position, atc_popup_y_position, 10, secs); window.bg = popup_window_bg_color; window.fg = popup_window_fg_atc_color; window.align = atc_popup_align; window.write(text); addAtcLog(text); } autoATCMessageDelay(secs); echoMultiplayerChat(text, multiplayMessageType); lastAtcText = text; lastAtcVoice = voice; showRepeatATCMessageButton(1); pilot_response_counter = 0; if(pilotResponseType != message_none) pilot_response_wait_seconds = secs + pilot_response_pause_atc_seconds; else pilot_response_wait_seconds = -1; } var echoMultiplayerChat = func(text, type) { if(multiplayerChatEcho == "Off") return; var airport = airportinfo(selectedComAirportId); var message = "[" ~ RGAtcName; if(airport != nil) message ~= "@" ~ airport.id; message ~= "] "; message ~= text; if((multiplayerChatEcho == "Important messages only" and type == message_type_important) or (multiplayerChatEcho == "All messages")) property_Multiplay_Chat.setValue(message); } var assistedAtcApproach = func() { var courseToPoint = nil; var distanceToPoint = nil; var degreeDiff = 0; var turnToDiff = ""; var requiredSpeed = 0; var actionTag = ""; var aircraftHeading = property_Aircraft_HeadingDeg.getValue(); var point = nil; if(approachStatus == approach_status_none or approachStatus == approach_status_landing) return; if(approachStatus == approach_status_to_pattern) { (courseToPoint, distanceToPoint) = courseAndDistance(patternPoint); requiredSpeed = pattern_speed; } else { (courseToPoint, distanceToPoint) = courseAndDistance(approachPoint); requiredSpeed = approach_speed; } if(distanceToPoint < abs(approach_point_distance / 2)) approach_check_interval = approach_check_interval_near_point; if(distanceToPoint < approach_turn_distance) { if(approachStatus == approach_status_to_pattern) approachStatus = approach_status_to_approach; else approachStatus = approach_status_to_final; approach_check_interval = approach_check_interval_final; } if(approachStatus == approach_status_to_pattern or approachStatus == approach_status_to_approach) { approachRouteTextInstructions = ""; approachRouteVoiceInstructions = ""; if(courseWithinDegrees(aircraftHeading, courseToPoint, 10) == 0) { (degreeDiff, turnToDiff) = degreesDifference(aircraftHeading, courseToPoint); approachRouteTextInstructions = sprintf(atcMessageReply.turn_to_heading, turnToDiff, sprintf("%d", courseToPoint) ); approachRouteVoiceInstructions = sprintf(atcMessageReply.turn_to_heading, turnToDiff, spellToPhonetic(sprintf("%d", courseToPoint), spell_number) ); } if(distanceToPoint < abs(approach_point_distance / 2) and abs(property_Aircraft_AltitudeFeet.getValue() - approachAltitude) > (assigned_altitude_delta / 2)) { if(approachRouteTextInstructions != "") { approachRouteTextInstructions ~= ". "; approachRouteVoiceInstructions ~= ". "; } if(property_Aircraft_AltitudeFeet.getValue() > approachAltitude) actionTag = atcMessageAction.descend; else actionTag = atcMessageAction.climb; approachRouteTextInstructions ~= sprintf("%s and maintain %s", actionTag, sprintf("%d", approachAltitude) ); approachRouteVoiceInstructions ~= sprintf("%s and maintain %s", actionTag, spellToPhonetic(sprintf("%d", approachAltitude), spell_number_to_literal) ); } if(distanceToPoint < abs(approach_point_distance / 2) and abs(property_Aircraft_AirSpeedKnots.getValue() - requiredSpeed) > approach_speed_delta) { if(approachRouteTextInstructions != "") { approachRouteTextInstructions ~= ". "; approachRouteVoiceInstructions ~= ". "; } if(property_Aircraft_AirSpeedKnots.getValue() > requiredSpeed) actionTag = atcMessageAction.reduce; else actionTag = atcMessageAction.increase; approachRouteTextInstructions ~= sprintf("%s speed %s knots", actionTag, sprintf("%d", requiredSpeed) ); approachRouteVoiceInstructions ~= sprintf("%s speed %s knots", actionTag, spellToPhonetic(sprintf("%d", requiredSpeed), spell_number_to_literal) ); } if(approachRouteTextInstructions != "") atcReplyToRequest(message_approach_route_instructions); } else if(approachStatus == approach_status_to_final) { var initialReportLetter = "R"; var contactMsgText = ""; var contactMsgVoice = ""; var towerContactText = ""; var towerContactVoice = ""; airport = airportinfo(selectedComAirportId); runwayHeading = airport.runways[airportLandingRunway].heading; (degreeDiff, turnToDiff) = degreesDifference(aircraftHeading, runwayHeading); approachRouteTextInstructions = sprintf(atcMessageReply.turn_to_final, turnToDiff, sprintf("%d", runwayHeading), airportLandingRunway ); approachRouteVoiceInstructions = sprintf(atcMessageReply.turn_to_final, turnToDiff, spellToPhonetic(sprintf("%d", runwayHeading), spell_number), spellToPhonetic(airportLandingRunway, spell_runway) ); if(abs(property_Aircraft_AirSpeedKnots.getValue() - final_speed) > approach_speed_delta) { if(approachRouteTextInstructions != "") { approachRouteTextInstructions ~= ". "; approachRouteVoiceInstructions ~= ". "; } if(property_Aircraft_AirSpeedKnots.getValue() > final_speed) actionTag = atcMessageAction.reduce; else actionTag = atcMessageAction.increase; approachRouteTextInstructions ~= sprintf("%s speed %s knots", actionTag, sprintf("%d", final_speed) ); approachRouteVoiceInstructions ~= sprintf("%s speed %s knots", actionTag, spellToPhonetic(sprintf("%d", final_speed), spell_number_to_literal) ); } if(isRadioFrequencyOfType(selectedComFrequency, radio_station_type_tower) == 0) { towerRadio = getRadio(radio_station_type_tower, radio_type_any); if(towerRadio == nil) { showPopup(RGAtcName ~ ": " ~ atcMessageAction.tower_not_reachable ~ "."); return; } if(selectedComFrequency != towerRadio.frequency) { towerContactText = sprintf(atcMessageReply.contact_radio, normalizeRadioStationName(towerRadio.name), sprintf("%.3f", towerRadio.frequency) ); towerContactText ~= " and "; towerContactVoice = sprintf(atcMessageReply.contact_radio, normalizeRadioStationName(towerRadio.name), spellToPhonetic(sprintf("%.3f", towerRadio.frequency), spell_number) ); towerContactVoice ~= " and "; initialReportLetter = "r"; } } if(aircraft_status == status_requested_approach) { contactMsgText = sprintf(atcMessageReply.report_airfield_in_sight, initialReportLetter ); contactMsgText = towerContactText ~ contactMsgText; contactMsgVoice = sprintf(atcMessageReply.report_airfield_in_sight, initialReportLetter ); contactMsgVoice = towerContactVoice ~ contactMsgVoice; } else { contactMsgText = sprintf(atcMessageReply.report_established_ils, initialReportLetter, "ILS", airportLandingRunway ); contactMsgText = towerContactText ~ contactMsgText; contactMsgVoice = sprintf(atcMessageReply.report_established_ils, initialReportLetter, "I L S", spellToPhonetic(airportLandingRunway, spell_runway) ); contactMsgVoice = towerContactVoice ~ contactMsgVoice; } approachRouteTextInstructions ~= "\n" ~ contactMsgText; approachRouteVoiceInstructions ~= "\n" ~ contactMsgVoice; atcReplyToRequest(message_approach_route_instructions); approachStatus = approach_status_landing; } } var getCallSign = func(mode) { var cs = property_Setting_Callsign.getValue(); if(mode != "Complete" and size(cs) > 3) { if(mode == "Last three letters") cs = substr(cs, size(cs) - 3); else cs = left(cs, 1) ~ substr(cs, size(cs) - 2); } return cs; } var getCallSignForAtc = func(initial) { var cs = property_Setting_Callsign.getValue(); var atcText = ""; var atcVoice = ""; if(includeManufacturer == "Yes" and aircraftManufacturer != "") { atcText ~= aircraftManufacturer ~ " "; atcVoice ~= aircraftManufacturer ~ " "; } if(initial == 0) cs = getCallSign(callsignMode); atcText ~= cs; atcVoice ~= spellToPhonetic(cs, spell_text); return [atcText, atcVoice]; } var checkRadioIsType = func(radioType) { if(selectedComStationType == radioType) return 1; var requestedRadio = nil; var towerRadio = nil; var i = 0; var check = 0; var radio = nil; for(i = 0; i < size(availableRadio); i += 1) { radio = availableRadio[i]; if(radio.type == radioType and requestedRadio == nil) requestedRadio = radio; if(radio.type == radio_station_type_tower and towerRadio == nil) towerRadio = radio; } if(requestedRadio != nil) radio = requestedRadio; else if(towerRadio != nil) radio = towerRadio; else radio = nil; if(radio != nil) { if(selectedComFrequency == radio.frequency) return 1; if(radio == towerRadio and selectedComStationType == radio_station_type_tower) return 1; var text = sprintf(atcMessageReply.wrong_radio, atcCallsignText, selectedComStationName, normalizeRadioStationName(radio.name), sprintf("%.3f", radio.frequency) ); var voice = sprintf(atcMessageReply.wrong_radio, atcCallsignVoice, selectedComStationName, normalizeRadioStationName(radio.name), spellToPhonetic(sprintf("%.3f", radio.frequency), spell_number) ); pilotResponseType = message_contact_radio; contactRadio = radio; atcMessage(text, voice); check = 0; } else check = 1; return check; } var getAvailableRadios = func() { availableRadio = {}; if(selectedComStationType != nil and selectedComStationType != radio_station_type_unknown and selectedComAirportId != "") airport = airportinfo(selectedComAirportId); else if(currentAirport != nil) airport = currentAirport; else return; if(size(airport.comms()) == 0) return; var i = 0; var radioType = radio_station_type_unknown; foreach(var radio; airport.comms()) { radioType = getRadioType(radio.ident); radioData = {name:getRadioFullName(airport.id, radio.ident), airport:airport.name, location:split(" ", airport.name)[0], ident:radio.ident, frequency:radio.frequency, type:radioType }; availableRadio[i] = radioData; i += 1; } } var getRadio = func(radioType, type) { var requestedRadio = nil; var i = 0; var radio = nil; var nradio = size(availableRadio); if(nradio > 0) { for(i = 0; i < nradio; i += 1) { radio = availableRadio[i]; if(radio.type == radioType and requestedRadio == nil) requestedRadio = radio; } if(requestedRadio == nil and type == radio_type_any) requestedRadio = availableRadio[0]; } return requestedRadio; } var getRadioType = func(radioIdent) { if(radioIdent == nil) return radio_station_type_unknown; var radioType = radio_station_type_unknown; var name = string.lc(radioIdent); if(find("TWR", radioIdent) != -1 or find("tower", name) != -1) radioType = radio_station_type_tower; else if(find("APP", radioIdent) != -1 or find("approach", name) != -1) radioType = radio_station_type_approach; else if(find("GND", radioIdent) != -1 or find("ground", name) != -1 or find("grd", name) != -1) radioType = radio_station_type_ground; else if(find("ARR", radioIdent) != -1 or find("arrival", name) != -1 or find("director", name) != -1) radioType = radio_station_type_approach; else if(find("DEP", radioIdent) != -1 or find("departure", name) != -1) radioType = radio_station_type_departure; else if(find("DEL", radioIdent) != -1 or find("delivery", name) != -1 or find("clnc", name) != -1) radioType = radio_station_type_clearance; else if(find("apron", name) != -1 or find("APR", radioIdent) != -1) radioType = radio_station_type_approach; else if(find("traffic", name) != -1) radioType = radio_station_type_approach; else if(find("ATIS", radioIdent) != -1) radioType = radio_station_type_atis; else if(find("AFIS", radioIdent) != -1 or find("SERVICE", radioIdent) != -1) radioType = radio_station_type_atis; else if(find("CTAF", radioIdent) != -1) radioType = radio_station_type_ground; else if(find("CON", radioIdent) != -1) radioType = radio_station_type_approach; else if(find("radio", name) != -1 or find("rdo", name) != -1) radioType = radio_station_type_tower; else if(find("info", name) != -1 or find("ATC", radioIdent) != -1) radioType = radio_station_type_tower; else if(find("unicom", name) != -1) radioType = radio_station_type_tower; else if(find("centre", name) != -1 or find("cntr", name) != -1 or find("ctr", name) != -1) radioType = radio_station_type_tower; else if(find("MF", radioIdent) != -1 or find("MULT", radioIdent) != -1) radioType = radio_station_type_tower; else if(find("FSS", radioIdent) != -1 or find("FIS", radioIdent) != -1 or find("FS", radioIdent) != -1) radioType = radio_station_type_tower; else if(find("start", name) != -1 or find("taxi", name) != -1) radioType = radio_station_type_ground; else radioType = radio_station_type_tower; return radioType; } var getRadioFullName = func(airportID, radioName) { if(airportID == nil or radioName == nil) return ""; if(airportID == "" or radioName == "") return ""; var airport = airportinfo(airportID); var radioFullName = ""; if(airport != nil) { var airportCity = split(" ", airport.name)[0]; if(string.lc(airportCity) != string.lc(split(" ", radioName)[0])) radioFullName = airportCity ~ " "; radioFullName ~= radioName; } else radioFullName = ""; return radioFullName; } var getAirportRadio = func(airport, radioType) { var requestedRadio = nil; var i = 0; var radio = nil; if(airport == nil) return nil; var nradio = size(airport.comms()); if(nradio > 0) { foreach(radio; airport.comms()) { if(getRadioType(radio.ident) == radioType and requestedRadio == nil) requestedRadio = radio; } if(requestedRadio == nil) { foreach(radio; airport.comms()) { if(getRadioType(radio.ident) == radio_station_type_tower and requestedRadio == nil) requestedRadio = radio; } } if(requestedRadio == nil) requestedRadio = airport.comms()[0]; } if(requestedRadio != nil) { requestedRadio.name = getRadioFullName(airport.id, requestedRadio.ident); requestedRadio.airport = airport.name; requestedRadio.location = split(" ", airport.name)[0]; } return requestedRadio; } var isRadioFrequencyOfType = func(frequency, radioType) { var result = 0; var i = 0; var radio = nil; var nradio = size(availableRadio); if(nradio > 0) { for(i = 0; i < nradio and result == 0; i += 1) { radio = availableRadio[i]; if(radio.type == radioType and radio.frequency == frequency) result = 1; } } return result; } var getRadioVolume = func(volumeProperty, volumeSelectedProperty) { var commVolume = 0; if(volumeProperty != nil) commVolume = volumeProperty.getValue(); if(commVolume == 0 and volumeSelectedProperty != nil) commVolume = volumeSelectedProperty.getValue(); return commVolume; } var getRadioFrequency = func(frequency, realFrequency) { var commFrequency = 0; if(realFrequency != nil) commFrequency = realFrequency.getValue(); if(commFrequency == 0 and frequency != nil) commFrequency = frequency.getValue(); return commFrequency; } var isRadioServiceable = func() { var msg = ""; if(selectedComServiceableProperty == nil) msg = atcMessageAction.bad_radio_data; else if(selectedComServiceable == 0 or selectedComPowerStatus == 0 or selectedComOperable == 0) msg = atcMessageAction.turn_radio_on; else if(selectedComVolume < 0.1) { if(selectedComRadio != "") msg = selectedComRadio ~ ": "; else msg = ""; msg ~= atcMessageAction.radio_volume_up; } else if(radioSignalQuality() == 0) { if(selectedComRadio != "") msg = selectedComRadio ~ ": "; else msg = ""; msg ~= atcMessageAction.no_radio_tuned; } if(msg != "") { showPopup(RGAtcName ~ ": " ~ msg); return 0; } return 1; } var isRadioTunedToApprovedCtrRadio = func() { var radio = nil; var isTuned = 0; if(approvedCtr == nil) return 0; radio = getCtrRadio(approvedCtr); if(radio.frequency == selectedComFrequency) isTuned = 1; return isTuned; } var isRealMeteoEnabled = func() { return property_RealWeather.getValue(); } var getMeteoText = func(meteoType, voice) { var meteo = ""; var windFromDegrees = 0; var windSpeed = 0; var temperature = 0; var dewpoint = 0; var visibility = 0; var rain = 0; var snow = 0; var clouds = ""; var cloudsElevation = 0; if(isRealMeteoEnabled() == 1) { windFromDegrees = int(property_Weather_Metar_WindDirectionDeg.getValue()); windSpeed = int(property_Weather_Metar_WindSpeedKnots.getValue()); temperature = int(property_Weather_Metar_Temperature.getValue()); dewpoint = int(property_Weather_Metar_Dewpoint.getValue()); visibility = int(property_Weather_Metar_Visibility.getValue() / 1000); if(property_Weather_Metar_Rain != nil) rain = property_Weather_Metar_Rain.getValue(); else rain = 0; if(property_Weather_Metar_Snow != nil) snow = property_Weather_Metar_Snow.getValue(); else snow = 0; if(property_Weather_Metar_CloudsCoverage != nil) clouds = property_Weather_Metar_CloudsCoverage.getValue(); else clouds = "clear"; if(property_Weather_Metar_CloudsElevationFeet != nil) cloudsElevation = property_Weather_Metar_CloudsElevationFeet.getValue(); else cloudsElevation = 0; } else { windFromDegrees = int(property_Weather_WindDirectionDeg.getValue()); windSpeed = int(property_Weather_WindSpeedKnots.getValue()); temperature = int(property_Weather_Temperature.getValue()); dewpoint = int(property_Weather_Dewpoint.getValue()); visibility = int(property_Weather_Visibility.getValue() / 1000); if(property_Weather_Rain != nil) rain = property_Weather_Rain.getValue(); else rain = 0; if(property_Weather_Snow != nil) snow = property_Weather_Snow.getValue(); else snow = 0; if(property_Weather_CloudsCoverage != nil) clouds = property_Weather_CloudsCoverage.getValue(); else clouds = "clear"; if(property_Weather_CloudsElevationFeet != nil) cloudsElevation = property_Weather_CloudsElevationFeet.getValue(); else cloudsElevation = 0; } (qnhValue, qnhLiteralValue) = getQNH(); if(windSpeed > 0) { if(voice == 0) { meteo = sprintf(meteoMessage.wind_report, sprintf("%d", windFromDegrees), sprintf("%d", windSpeed) ); } else { meteo = sprintf(meteoMessage.wind_report, spellToPhonetic(sprintf("%d", windFromDegrees), spell_number), spellToPhonetic(sprintf("%d", windSpeed), spell_number) ); } } else meteo = meteoMessage.wind_calm; if(meteoType == meteo_type_full) { meteo ~= ", "; if(voice == 0) { meteo ~= sprintf(meteoMessage.full, "QNH", qnhValue, sprintf("%d", temperature), sprintf("%d", dewpoint), sprintf("%d", visibility) ); } else { meteo ~= sprintf(meteoMessage.full, "Q N H", qnhLiteralValue, spellToPhonetic(sprintf("%d", temperature), spell_number), spellToPhonetic(sprintf("%d", dewpoint), spell_number), spellToPhonetic(sprintf("%d", visibility), spell_number) ); } if(clouds != "clear") { meteo ~= ", "; if(voice == 0) { meteo ~= sprintf(meteoMessage.cloud_report, clouds, sprintf("%d", cloudsElevation) ); } else { meteo ~= sprintf(meteoMessage.cloud_report, clouds, spellToPhonetic(sprintf("%d", cloudsElevation), spell_number_to_literal) ); } } else meteo ~= ", " ~ meteoMessage.sky_clear; if(rain > 0) { var rainTag = ""; if(rain < 0.3) rainTag = meteoMessage.light; else if(rain < 0.6) rainTag = meteoMessage.moderate; else rainTag = meteoMessage.heavy; meteo ~= ", "; meteo ~= sprintf(meteoMessage.rain_report, rainTag ); } if(snow > 0) { var snowTag = ""; if(snow < 0.3) snowTag = meteoMessage.light; else if(snow < 0.6) snowTag = meteoMessage.moderate; else snowTag = meteoMessage.heavy; meteo ~= ", "; meteo ~= sprintf(meteoMessage.snow_report, snowTag ); if(isRealMeteoEnabled() == 1 and property_Weather_Metar_SnowCover != nil) { if(property_Weather_Metar_SnowCover.getValue() > 0) meteo ~= " " ~ meteoMessage.cover; } } } else if(meteoType == meteo_type_qnh) { meteo ~= ", "; if(voice == 0) { meteo ~= sprintf(meteoMessage.qnh_report, "QNH", qnhValue ); } else { meteo ~= sprintf(meteoMessage.qnh_report, "Q N H", qnhLiteralValue ); } } return meteo; } var radioSignalQuality = func() { var signal = 0; if(selectedComSignalQuality == nil) return 0; if(selectedComSignalQuality > 0) { if(selectedComSignalQuality < 0.05) signal = 0; else if(selectedComSignalQuality < 0.2) signal = 1; else if(selectedComSignalQuality < 0.3) signal = 2; else if(selectedComSignalQuality < 0.4) signal = 3; else if(selectedComSignalQuality < 0.5) signal = 4; else signal = 5; } else signal = 0; return signal; } var minPathDistance = func(pos, pathPos, pathHeading, pathLength, minDistance) { var minDist = 9999; var course = 0; var dist = 0; var step = minDistance / 4; var cDist = 0; var cPos = pathPos; for(cDist = 0; cDist < pathLength; cDist += step) { cPos.apply_course_distance(pathHeading, step); (course, dist) = courseAndDistance(pos, cPos); if(dist < minDist) minDist = dist; } return minDist * NM2M; } var normalizeAltitudeHeading = func(altitude, heading, minAltitude) { var normalizedAltitude = altitude; if(normalizedAltitude < minAltitude) normalizedAltitude = minAltitude; var normalizedAltitude = int(normalizedAltitude / 1000) * 1000; if(heading < 180) { if(math.mod(int(normalizedAltitude / 1000), 2) == 0) normalizedAltitude += 1000; } else { if(math.mod(int(normalizedAltitude / 1000), 2) == 1) normalizedAltitude += 1000; } normalizedAltitude += 500; return normalizedAltitude; } var getFlightPlanAltitude = func() { var altitude = -1; var distance = 9999; var minCruiseAltidude = getMinCruiseAltitude(); if(!property_Autopilot_RouteManagerActive.getValue()) return -1; var wpnum = property_Autopilot_RouteManagerRouteNum.getValue() - 1; for(var i = property_Autopilot_RouteManagerCurrentWayPoint.getValue(); i < wpnum; i += 1) { var prop = sprintf("/autopilot/route-manager/route/wp[%d]/", i); var wpalt = getprop(prop ~ "altitude-ft"); if(wpalt > 0) { var (wpcourse, wpdist) = courseAndDistance(geo.Coord.new().set_latlon(getprop(prop ~ "latitude-deg"), getprop(prop ~ "longitude-deg"))); if(wpdist < distance and wpdist < flight_plan_min_distance) { distance = wpdist; altitude = wpalt; } } } if(altitude == -1) { altitude = property_Autopilot_RouteManagerCruiseAltitudeFeet.getValue(); if(altitude < minCruiseAltidude) altitude = normalizeAltitudeHeading(property_Aircraft_AltitudeFeet.getValue(), heading, minCruiseAltidude); } return math.round(altitude, 100); } var getFlightLevelCode = func(level) { var len = 0; if(level >= 10000) len = 3; else if(level >= 1000) len = 2; else len = 1; return substr(sprintf("%d", level), 0, len); } var flightLevelChangeSeconds = func(finalAltitude) { var altitudeChange = abs(property_Aircraft_AltitudeFeet.getValue() - finalAltitude); return int((altitudeChange / flight_level_rate) * 60); } var getMinCruiseAltitude = func() { var minCruiseAltidude = min_cruise_altitude; var safeAltitude = property_Aircraft_AltitudeFeet.getValue() - property_Aircraft_AltitudeAglFeet.getValue() + min_safe_altitude; if(minCruiseAltidude < safeAltitude) minCruiseAltidude = safeAltitude; return minCruiseAltidude; } var courseWithinDegrees = func(course, degrees, range) { var inRange = 0; var degreeDiff = 360; var turnToDiff = ""; (degreeDiff, turnToDiff) = degreesDifference(course, degrees); if(degreeDiff <= (range / 2)) inRange = 1; return inRange; } var degreesDifference = func(fromDegrees, toDegrees) { var degreeDiff = 0; var turnToDiff = ""; degreeDiff = int(math.mod(((toDegrees - fromDegrees) + 180), 360) - 180); if(degreeDiff == 0) turnToDiff = ""; else if(degreeDiff < 0) turnToDiff = "left"; else turnToDiff = "right"; return [abs(degreeDiff), turnToDiff]; } var resetAircraftStatus = func() { if(property_Aircraft_AltitudeAglFeet.getValue() > min_ground_altitude) aircraft_status = status_flying; else aircraft_status = status_going_around; } var spellToPhonetic = func(text, spellMode) { var phoneticText = ""; var length = size(text); if(spellMode == spell_number_to_literal) phoneticText = numberToLiteral(int(text)); else { for(var i = 0; i < length; i += 1) { var character = chr(text[i]); if(string.isalpha(text[i])) { character = chr(string.toupper(text[i])); if(spellMode == spell_text or spellMode == spell_number) phoneticText ~= phoneticLetter[character] ~ " "; else if(spellMode == spell_runway) { var rwTag = ""; if(character == "L") rwTag = "left"; else if(character == "R") rwTag = "right"; else if(character == "C") rwTag = "center"; else rwTag = character; phoneticText ~= rwTag ~ " "; } } else if(string.isdigit(text[i])) phoneticText ~= phoneticDigit[text[i] - 48] ~ " "; else if(character == ".") phoneticText ~= "decimal"; else if(character == "-") { if(spellMode == spell_number) phoneticText ~= "minus"; } else if(character == "/") { # ignore slash } else if(character == "\n") { phoneticText ~= " "; } else phoneticText ~= character; if(i < length - 1) phoneticText ~= " "; } } return phoneticText; } var numberToLiteral = func(number) { var text = ""; var digit = 0; var tens = 0; var units = 0; digit = int(number / 1000); if(digit > 0) { if(digit < 20) text ~= phoneticNumber[digit]; else { tens = int(digit / 10); if(tens > 0) text ~= phoneticTen[tens] ~ " "; units = digit - (tens * 10); if(units < 10 and units > 0) text ~= phoneticNumber[units]; } text ~= " Thousand "; } number -= digit * 1000; digit = int(number / 100); if(digit > 0) text ~= phoneticNumber[digit] ~ " Hundred "; number -= digit * 100; if(number > 0) { if(size(text) > 0) text ~= "and "; if(number < 20) text ~= phoneticNumber[number]; else { digit = int(number / 10); if(digit > 0) text ~= phoneticTen[digit] ~ " "; number -= digit * 10; if(number < 10 and number > 0) text ~= phoneticNumber[number]; } } return text; } var getMessageText = func(messageType) { var message = ""; if(messageType == message_radio_check) message = atcMessageRequest.radio_check; else if(messageType == message_engine_start) message = atcMessageRequest.engine_start; else if(messageType == message_departure_information) message = atcMessageRequest.departure_information; else if(messageType == message_request_taxi) message = atcMessageRequest.request_taxi; else if(messageType == message_ready_for_departure) message = atcMessageRequest.ready_departure; else if(messageType == message_abort_departure) message = atcMessageRequest.abort_departure; else if(messageType == message_request_approach) message = atcMessageRequest.request_approach; else if(messageType == message_request_ils) message = atcMessageRequest.request_ils; else if(messageType == message_airfield_in_sight) message = atcMessageRequest.airfield_in_sight; else if(messageType == message_ils_established) message = atcMessageRequest.ils_established; return message; } var normalizeRadioStationName = func(text) { if(text == nil or text == "") return ""; var word = split(" ", text); var wlen = size(word); var wspace = wlen - 1; var stationName = ""; for(var i = 0; i < wlen; i += 1) { var w = string.lc(word[i]); if(w == "twr") w = "tower"; else if(w == "app") w = "approach"; else if(w == "apron") w = "approach On"; else if(w == "gnd") w = "ground"; else if(w == "gnd/apron") w = "ground"; else if(w == "arr") w = "arrival"; else if(w == "dep") w = "departure"; else if(w == "del") w = "delivery"; else if(w == "clnc") w = "clearance"; else if(w == "ctaf") w = "CTAF"; else if(w == "awos") w = "AWOS"; else if(w == "afis") w = "AFIS"; else if(w == "atis") w = "ATIS"; else if(w == "unicom") w = "Universal communications"; stationName ~= string.uc(left(w, 1)) ~ substr(w, 1) ; if(i < wspace) stationName ~= " "; } return stationName; } var setPilotMessageSeconds = func(delay) { pilot_message_wait_seconds = delay; pilot_message_counter = 0; } var cancelPilotPendingMessage = func() { pilot_message_wait_seconds = -1; pilot_message_counter = 0; pilot_response_wait_seconds = -1; pilot_response_counter = 0; } var setAtcAutoCallbackAfterSeconds = func(delay) { atc_callback_wait_seconds = delay; atc_callback_seconds_counter = 0; } var addSecondsToAtcAutoCallback = func(delay) { atc_callback_wait_seconds += delay; } var cancelAtcPendingMessage = func() { atc_callback_seconds_counter = 0; altitude_check_counter = 0; altitude_change_counter = 0; } var getNearbyCtr = func(range) { var airport = nil; var nearByAirports = nil; var minDistance = range; var nearestCtr = nil; var ctrData = nil; var radio = nil; airport = airportinfo(); if(airport != nil) { ctrData = getCtrData(airport); if(ctrData != nil and ctrData.status == ctr_status_inside) { if(approvedCtr != nil) { if(ctrData.ident != approvedCtr.ident or isRadioTunedToApprovedCtrRadio == 0) return ctrData; } else return ctrData; } } nearByAirports = findAirportsWithinRange(range); foreach(airport; nearByAirports) { ctrData = getCtrData(airport); if(ctrData != nil) { if(approvedCtr != nil and ctrData.ident == approvedCtr.ident) { # ignore currently approved CTR } else if(ctrData.status == ctr_status_inside) { nearestCtr = ctrData; minDistance = 0; } else if(ctrData.status == ctr_status_in_range and ctrData.distance_from_range < minDistance) { minDistance = ctrData.distance_from_range; nearestCtr = ctrData; } } } return nearestCtr; } var getCtrData = func(airport) { var course = 0; var distance = 0; var range = 0; var rangeDistance = 0; var status = ctr_status_outside; var aircraftHeading = property_Aircraft_HeadingDeg.getValue(); if(airport == nil) return nil; var nradios = size(airport.comms()); if(nradios == 0) return nil; var pos = geo.Coord.new().set_latlon(airport.lat, airport.lon); (course, distance) = courseAndDistance(pos); if(nradios > 5) range = ctr_range_wide; else if(nradios > 1) range = ctr_range_medium; else range = ctr_range_short; if(distance < range) { status = ctr_status_inside; rangeDistance = 0; } else if(courseWithinDegrees(aircraftHeading, course, ctr_maximum_range_angle) == 1) { if((distance - range) < ctr_maximum_range_distance) { status = ctr_status_in_range; rangeDistance = distance - range; } else { status = ctr_status_outside; rangeDistance = 0; } } else { status = ctr_status_outside; rangeDistance = 0; } var ctrData = { ident:airport.id, airport:airport.name, lat:airport.lat, lon:airport.lon, range:range, course:course, distance:distance, distance_from_range:rangeDistance, status:status }; return ctrData; } var getCtrRadio = func(ctr) { var radioType = radio_station_type_approach; var radio = nil; if(ctr == nil) return nil; if(aircraftIsDeparting == 1) radioType = radio_station_type_departure; radio = getAirportRadio(airportinfo(ctr.ident), radioType); if(getRadioType(radio.ident) != radioType) radio = getAirportRadio(airportinfo(ctr.ident), radio_station_type_approach); return radio; } var getQNH = func() { var qnhinhg = 0; var qnhhpa = 0; var qnhValue = ""; var qnhLiteralValue = ""; if(isRealMeteoEnabled() == 1) { if(property_Weather_QnhInHg != nil) qnhinhg = property_Weather_Metar_QnhInHg.getValue(); else qnhinhg = 0; } else { if(property_Weather_QnhInHg != nil) qnhinhg = property_Weather_QnhInHg.getValue(); else qnhinhg = 0; } qnhhpa = int(qnhinhg / 0.02953); if(qnhUnitMeasure == "hPa") { qnhValue = sprintf("%d hPa", qnhhpa); qnhLiteralValue = spellToPhonetic(sprintf("%d", qnhhpa), spell_number) ~ " hecto pascal"; } else if(qnhUnitMeasure == "inHg") { qnhValue = sprintf("%.2f inHg", qnhinhg); qnhLiteralValue = spellToPhonetic(sprintf("%.2f" ,qnhinhg), spell_number) ~ " inches of mercury"; } else { qnhValue = sprintf("%d hPa or %.2f inHg", qnhhpa, qnhinhg); qnhLiteralValue = spellToPhonetic(sprintf("%d", qnhhpa), spell_number) ~ " hecto pascal or "; qnhLiteralValue ~= spellToPhonetic(sprintf("%.2f", qnhinhg), spell_number) ~ " inches of mercury"; } return [qnhValue, qnhLiteralValue]; } var isInsideCtr = func(airport) { if(airport == nil) return 0; var ctrData = getCtrData(airport); var isInside = 0; if(ctrData != nil and ctrData.status == ctr_status_inside) isInside = 1; else isInside = 0; return isInside; } var getCtrAtcMessage = func(request) { var text = ""; var voice = ""; var extraText = ""; var extraVoice = ""; (atcCallsignText, atcCallsignVoice) = getCallSignForAtc(0); if(request == ctr_request_approved) { if(squawkingMode == "On") { var code = getSquawkCode(); if(code != squawkCode) { squawkCode = code; extraText = sprintf(", Squawk %s and IDENT", squawkCode); extraVoice = sprintf(", Squawk %s and ident", spellToPhonetic(squawkCode, spell_number)); squawkChange = 1; setSquawkIdentMode(squawk_ident_code); } else { extraText = ", Squawk IDENT"; extraVoice = ", Squawk ident"; squawkChange = 0; setSquawkIdentMode(squawk_ident_only); } } else { extraText = ""; extraVoice = ""; } text = sprintf(atcMessageReply.ctr_approved, atcCallsignText, selectedComStationName, "CTR", extraText ); voice = sprintf(atcMessageReply.ctr_approved, atcCallsignVoice, selectedComStationName, "C T R", extraVoice ); } else if(request == ctr_already_approved) { if(squawkingMode == "On") { if(squawkCode == "") squawkCode = getSquawkCode(); extraText = sprintf(", Squawk %s", squawkCode); extraVoice = sprintf(", Squawk %s", spellToPhonetic(squawkCode, spell_number)); setSquawkIdentMode(squawk_ident_code); } else { extraText = ""; extraVoice = ""; setSquawkIdentMode(squawk_ident_off); } text = sprintf(atcMessageReply.ctr_already_approved, atcCallsignText, selectedComStationName, "CTR", extraText ); voice = sprintf(atcMessageReply.ctr_already_approved, atcCallsignVoice, selectedComStationName, "C T R", extraVoice ); } else { text = sprintf(atcMessageReply.ctr_not_approved, atcCallsignText, selectedComStationName, "CTR", "CTR" ); voice = sprintf(atcMessageReply.ctr_not_approved, atcCallsignVoice, selectedComStationName, "C T R", "C T R" ); } return [text, voice]; } var isTerrainSafeAhead = func() { var safe = 0; var aircraftPosition = geo.aircraft_position(); var pathPosition = geo.aircraft_position().apply_course_distance(property_Aircraft_HeadingDeg.getValue(), min_safety_distance_from_terrain_miles * NM2M); var intersection = get_cart_ground_intersection({ "x":aircraftPosition.x(), "y":aircraftPosition.y(), "z":aircraftPosition.z() }, { "x":pathPosition.x()-aircraftPosition.x(), "y":pathPosition.y()-aircraftPosition.y(), "z":pathPosition.z()-aircraftPosition.z() } ); if(intersection == nil) safe = 1; else { var terrainPos = geo.Coord.new(); terrainPos.set_latlon(intersection.lat, intersection.lon, intersection.elevation); var maxDist = aircraftPosition.direct_distance_to(pathPosition); var terrainDist = aircraftPosition.direct_distance_to(terrainPos); if(terrainDist < maxDist) safe = 0; else safe = 1; } return safe; } var isSimulationPaused = func() { var paused = 0; if(property_Simulator_Freeze.getValue() == 1 or property_Simulator_Replay.getValue() == 1) paused = 1; return paused; } var altitudeApproachSlope = func(altitude, distance, roundHundred = 0) { if(altitude < 0 or distance < 0) return -1; slopeRadians = approach_slope_angle * D2R; ralt = math.sin(slopeRadians); rdist = math.cos(slopeRadians); slopeAltitude = ((distance / rdist) * ralt) * NM2FT; slopeAltitude += altitude * M2FT; if(roundHundred == 1) slopeAltitude = (100 * int(slopeAltitude / 100)); return slopeAltitude; } var getSquawkCode = func() { code = squawkCode; if(code == "" or rand() < 0.3) { codeOk = 0; while(codeOk == 0) { tempCode = int(rand() * 7776) + 1; codeOk = 1; foreach(var c; reservedSquawkCodes) { if(tempCode == c) codeOk = 0; } newCode = sprintf("0000%d", tempCode); newCode = substr(newCode, size(newCode) - 4); clen = size(newCode); for(var i = 0; i < clen; i += 1) { if(int(chr(newCode[i])) > 7) codeOk = 0; } } code = newCode; } return code; } var getOperatingTransponder = func() { if(property_Transponder1_Serviceable != nil) { if(property_Transponder1_Serviceable.getValue() == 1 and property_Transponder1_Operable.getValue() == 1 and property_Transponder1_KnobMode.getValue() > 3) return 1; } if(property_Transponder2_Serviceable != nil) { if(property_Transponder2_Serviceable.getValue() == 1 and property_Transponder2_Operable.getValue() == 1 and property_Transponder2_KnobMode.getValue() > 3) return 2; } if(property_Transponder3_Serviceable != nil) { if(property_Transponder3_Serviceable.getValue() == 1 and property_Transponder3_Operable.getValue() == 1 and property_Transponder3_KnobMode.getValue() > 3) return 3; } return 0; } var checkTransponder = func(transponder) { var serviceable = nil; var operable = nil; var idcode = nil; if(transponder == 1 and property_Transponder1_Serviceable != nil) { serviceable = property_Transponder1_Serviceable.getValue(); operable = property_Transponder1_Operable.getValue(); idcode = property_Transponder1_IdCode.getValue(); } else if(transponder == 2 and property_Transponder2_Serviceable != nil) { serviceable = property_Transponder2_Serviceable.getValue(); operable = property_Transponder2_Operable.getValue(); idcode = property_Transponder2_IdCode.getValue(); } else if(transponder == 3 and property_Transponder3_Serviceable != nil) { serviceable = property_Transponder3_Serviceable.getValue(); operable = property_Transponder3_Operable.getValue(); idcode = property_Transponder3_IdCode.getValue(); } else return 0; if(serviceable == 0) return 0; if(operable == 0) return 0; if(idcode != squawkCode) return 0; return 1; } var checkTransponderIdent = func(transponder) { var ident = 0; if(transponder == 1 and property_Transponder1_Ident != nil) ident = property_Transponder1_Ident.getValue(); else if(transponder == 2 and property_Transponder2_Ident != nil) ident = property_Transponder2_Ident.getValue(); else if(transponder == 3 and property_Transponder3_Ident != nil) ident = property_Transponder3_Ident.getValue(); else return 0; return ident; } var setSquawkIdentMode = func(mode) { squawkIdent = mode; if(mode == squawk_ident_off) squawkIdentButtonPushed = 0; squawk_check_counter = 0; } var getPilotVoice = func() { var done = 0; var i = 0; var pvoice = ""; var ptype = ""; var vdesc = ""; while(done == 0) { if(i == 0) vprop = "/sim/sound/voices/voice"; else vprop = sprintf("/sim/sound/voices/voice[%d]", i); vdesc = vprop ~ "/desc"; if(getprop(vdesc) == nil) { done = 1; pvoice = "/sim/sound/voices/pilot"; } else { if(string.lc(getprop(vdesc)) == "pilot") { pvoice = props.globals.getNode(vprop ~ "/text"); if(getprop(sprintf("%s/festival", vprop)) == 1) { if(string.match(getprop(sprintf("%s/preamble", vprop)), "*(audio_mode 'async)*") == 1) ptype = "festival-async"; else ptype = "festival"; } else ptype = "flite"; done = 1; } } i += 1; } return { text:pvoice, type:ptype }; } var initAtcVoice = func() { synthVoice = []; var done = 0; var i = 0; var vdesc = ""; synthVoice = []; usedSynthVoice = []; stationVoice = {}; stationVoicePool = []; while(done == 0) { if(i == 0) vprop = "/sim/sound/voices/voice"; else vprop = sprintf("/sim/sound/voices/voice[%d]", i); vdesc = vprop ~ "/desc"; if(getprop(vdesc) == nil) done = 1; else { if(string.lc(getprop(vdesc)) != "pilot") { var pvoice = {}; pvoice['text'] = props.globals.getNode(vprop ~ "/text"); if(getprop(sprintf("/sim/sound/voices/voice[%d]/festival", i)) == 1) { if(string.match(getprop(sprintf("/sim/sound/voices/voice[%d]/preamble", i)), "*(audio_mode 'async)*") == 1) pvoice['type'] = "festival-async"; else pvoice['type'] = "festival"; } else pvoice['type'] = "flite"; append(synthVoice, pvoice); } } i += 1; } } var getSynthVoice = func(frequency) { var voiceIsValid = 0; var svoice = {}; var i = 0; var vndx = 0; var svsize = size(synthVoice); var usvsize = size(usedSynthVoice); var svpsize = size(stationVoicePool); if(frequency == "") { if(svsize > 0) return synthVoice[0]; else return { text:"/sim/sound/voices/voice/atc", type:"flite" }; } if(svpsize > 0) { for(i = 0; i < svpsize; i += 1) { svoice = stationVoicePool[i]; if(svoice['frequency'] == frequency) return svoice; } } voiceIsValid = 0; while(voiceIsValid == 0) { vndx = int(rand() * svsize); voiceIsValid = 1; foreach(var usv; usedSynthVoice) { if(usv['index'] == vndx) voiceIsValid = 0; } } svoice = {}; svoice['index'] = vndx; svoice['text'] = synthVoice[vndx]['text']; svoice['type'] = synthVoice[vndx]['type']; svoice['frequency'] = frequency; append(usedSynthVoice, svoice); append(stationVoicePool, svoice); if(size(usedSynthVoice) == svsize) usedSynthVoice = []; return svoice; } var speak = func(text, voice, type) { if(type == "festival-async") { var tpart = split(",", text); foreach(var t; tpart) { var tpart1 = split("\n", t); foreach(var t1; tpart1) voice.setValue(t1 ~ "."); } } else voice.setValue(text); } var autoATCMessageDelay = func(seconds) { ctr_update_counter -= seconds; ctr_check_counter -= seconds; terrain_warning_counter -= seconds; if(terrain_warning_counter < 0) terrain_warning_counter = 0; altitude_check_counter -= seconds; if(altitude_check_counter < 0) altitude_check_counter = 0; squawk_check_counter -= seconds; if(squawk_check_counter < 0) squawk_check_counter = 0; approach_check_counter -= seconds; if(approach_check_counter < 0) approach_check_counter = 0; }