chat-menu 10 -30 vbox false 1 HELVETICA_12 0 0 0 0 left 0.9 0.2 0.2 1 left 0.9 0.2 0.2 1 left /sim/multiplay/chat-menu/entry[0] true 0.9 0.2 0.2 1 left /sim/multiplay/chat-menu/entry[1] true 0.9 0.2 0.2 1 left /sim/multiplay/chat-menu/entry[2] true 0.9 0.2 0.2 1 left /sim/multiplay/chat-menu/entry[3] true 0.9 0.2 0.2 1 left /sim/multiplay/chat-menu/entry[4] true 0.9 0.2 0.2 1 left /sim/multiplay/chat-menu/entry[5] true 0.9 0.2 0.2 1 left /sim/multiplay/chat-menu/entry[6] true 0.9 0.2 0.2 1 left /sim/multiplay/chat-menu/entry[7] true 0.9 0.2 0.2 1 = 180) course -= 360; while (course < -180) course += 360; var deviation = math.abs(course); if (deviation < max) { active_runway = r; max = deviation; } } # It may be the case that we're taxiing down the runway, past the mid-point. # If the aircraft is facing the "wrong" way for this runway, then take the # reciprocal runway. var heading = getprop("/orientation/heading-magnetic-deg"); var deviation = heading - airport.runways[active_runway].heading; while (deviation >= 180) deviation -= 360; while (deviation < -180) deviation += 360; deviation = math.abs(deviation); if (deviation > 90) { var number = math.mod(num(substr(active_runway, 0, 2)) + 18, 36); var side = substr(active_runway, 2, 1); active_runway = sprintf("%02d%s", number, side == "R" ? "L" : side == "L" ? "R" : side); } } else { var wind_speed = getprop("/environment/wind-speed-kt"); var wind_from = wind_speed ? getprop("/environment/wind-from-heading-deg") : 270; var max = -1; foreach (var r; keys(airport.runways)) { var curr = airport.runways[r]; var wind = wind_from - curr.heading; while (wind >= 180) wind -= 360; while (wind < -180) wind += 360; var deviation = math.abs(wind) + 1e-20; var v = (0.01 * curr.length + 0.01 * curr.width) / deviation; if (v > max) { max = v; active_runway = r; } } } # Find our distance and cardinal direction to the airport. var directions = split(",", "North,North East,East,South East,South,South West,West,North West"); var loc = geo.aircraft_position(); var airportloc = geo.Coord.new(); airportloc.set_latlon(airport.lat, airport.lon, airport.elevation); # We want the course _from_ the airport to the aircraft - for reporting ".. approaching from the SW" var airport_course = airportloc.course_to(loc); var airport_distance = int((airportloc.distance_to(loc) / 1609) + 0.5); # Get an index into our array of directions. var dir = int(math.mod((airport_course + 22.5), 360) / 45); var airport_direction = directions[dir]; # Now launch the keyboard listener. var kbdevent = setlistener("/devices/status/keyboard/event", func (event) { # Only check the key when pressed. if (!event.getNode("pressed").getValue()) return; var key = event.getNode("key"); if (handle_key(key.getValue())) key.setValue(-1); # drop key event }); var handle_key = func (key) { # We only handle keys 0-9 and Esc if (key == 27) { # escape -> cancel removelistener(kbdevent); gui.popdown(); fgcommand("dialog-close", props.Node.new({"dialog-name": "chat-menu"})); return 1; } if ((key < `0`) or (key > `9`)) { # pass the event back. return 0; } if (key == `0`) { # Go back one level. text = ""; if (pos.getName() != "config") { # Build up the chat string again. pos = pos.getParent(); var p = pos; while ((p.getName() != "config") and (p.getChild("name").getValue() != nil)) { var t = string.trim(p.getChild("name").getValue()); # Entries that begin with "[" are silent. if (t[0] != `[`) { text = t ~ " " ~ text; } p = p.getParent(); } } updateDialog(); } if (key == `1`) { # Go to edit mode using the inline editor. removelistener(kbdevent); gui.popdown(); multiplayer.compose_message(text); fgcommand("dialog-close", props.Node.new({"dialog-name": "chat-menu"})); } if ((key > `1`) and (key <= `9`)) { # Select the appropriate new node and update. # The index starts from position 2. var i = key - `2`; if (i > (size(pos.getChildren("menu")) -1)) { # Drop out if the user has entered a too large value. return 0; } var t = entryNodes[i].getValue(); t = string.trim(substr(t, 3, size(t) -3)); # Entries that begin with "[" are silent. if (t[0] != `[`) { text = text ~ " " ~ t; } pos = pos.getChildren("menu")[i]; if (size(pos.getChildren("menu")) == 0) { # We've come to the end of the tree - send the message and close setprop("/sim/multiplay/chat", text); removelistener(kbdevent); gui.popdown(); fgcommand("dialog-close", props.Node.new({"dialog-name": "chat-menu"})); } else { # We've got more tree to traverse. updateDialog(); } } # If we got here, we consumed the event return 1; } # Substitute simple values into the string. # The values we handle are # # % - Type (first word of /sim/description) # # - callsign # $ - altitude # * - airport ID. # & - course from airport to aircraft - "North West" # ! - distance from aircraft to airport in miles. # var subvals = func (str) { var t = ""; for (var p = 0; p < size(str); p += 1) { if (str[p] == `!`) t ~= airport_direction; elsif (str[p] == `^`) t ~= airport_distance; elsif (str[p] == `(`) t ~= active_runway; elsif (str[p] == `*`) t ~= airport.name; elsif (str[p] == `%`) t ~= type; elsif (str[p] == `#`) t ~= callsign; elsif (str[p] == `$`) t ~= altitude; else t ~= chr(str[p]); } return t; } var updateDialog = func { var children = pos.getChildren("menu"); var i = 0; foreach(var c; children) { var p = i + 2; var txt = p ~ ': ' ~ subvals(c.getChild("name").getValue()); if (i < 9) { entryNodes[i].setValue(txt); } i = i + 1; } # Set the rest of the dialog to blank. while (i <= 9) { entryNodes[i].setValue(""); i = i + 1; } # Write the popup. gui.popupTip(text, 1000000); } # Start by updating the dialog. updateDialog(); ]]>