From 3a5b53c0e3c3fc04c177468fbb448dd7e8f10241 Mon Sep 17 00:00:00 2001 From: Ryan Miller Date: Tue, 7 Jun 2011 13:28:22 -0700 Subject: [PATCH 1/5] (Animated jetways) Updated core files and jetway definition files to use relative elevations, moved menubar item to AI/ menu, "enabled" and "debug" settings are now saved over multiple sessions, core files are now part of a Nasal subsystem, use airline-specific model files, put less load on the loop by using an _active property for each jetway object --- Nasal/jetways/jetways.nas | 897 ++++++++++++++++++++++++++++ Nasal/jetways_edit/jetways_edit.nas | 594 ++++++++++++++++++ gui/dialogs/jetways-adjust.xml | 3 +- gui/dialogs/jetways.xml | 25 +- gui/menubar.xml | 11 + preferences.xml | 13 +- 6 files changed, 1524 insertions(+), 19 deletions(-) create mode 100644 Nasal/jetways/jetways.nas create mode 100644 Nasal/jetways_edit/jetways_edit.nas diff --git a/Nasal/jetways/jetways.nas b/Nasal/jetways/jetways.nas new file mode 100644 index 000000000..9d697f799 --- /dev/null +++ b/Nasal/jetways/jetways.nas @@ -0,0 +1,897 @@ +############################################################################### +## +## Animated Jetway System. Spawns and manages interactive jetway models. +## +## Copyright (C) 2011 Ryan Miller +## This file is licensed under the GPL license version 2 or later. +## +############################################################################### + +############################################################################### +# (See http://wiki.flightgear.org/Howto:_Animated_jetways) +# +# Special jetway definition files located in $FG_ROOT/Airports/Jetways/XXXX.xml +# for each airport are loaded when the user's aircraft is within 50 nm of the +# airport. The script dynamically generates runtime model files, writes them to +# $FG_ROOT/Models/Airport/Jetway/runtimeX.xml, and places them into the +# simulator using the model manager. +# +# Different jetway models can be defined and are placed under +# $FG_ROOT/Models/Airport/Jetway/XXX.xml. +# +# Jetways can be extended/retracted independently either by user operation or +# by automatic extension for AI models and multiplayer aircraft. +# +# UTILITY FUNCTIONS +# ----------------- +# +# print_debug() - prints debug messages +# - message to print +# +# print_error() - prints error messages +# - error to print +# +# alert() - displays an alert message in-sim +# - the message +# +# normdeg() - normalizes angle measures between -180° and 180° +# - angle to normalize +# +# remove(, ) - removes an element from a vector +# - vector +# - item +# +# isin(, ) - checks if an item exists in a vector +# - vector +# - item +# +# putmodel(, , , , ) - add a model to the scene graph (unlike geo.put_model(), models added with this function can be adjusted) +# - model path +# - latitude +# - longitude +# - altitude in m +# - heading +# +# interpolate_table(, ) - interpolates a value within a table +#
- interpolation table/vector, in the format of [[, ], [, ], ... ] +# - value +# +# get_relative_filepath(, ) - gets a relative file path from a directory +# - directory path should be relative to +# - target directory +# +# find_airports() - gets a list of nearest airports +# - maximum search distance in nm (currently unused) +# +# JETWAY CLASS +# ------------ +# +# Jetway. - creates a new jetway object/model +# new(, , , , +# , , , , +# , [, ] +# [, ] [, ] +# [, ]) +# - ICAO of associated airport +# - jetway model definition (i.e. Models/Airport/Jetway/generic.xml) +# - gate number (i.e. "A1") +# - door number (i.e. 0) +# - airline code (i.e. "AAL") +# - latitude location of model +# - longitude location of model +# - elevation of model in m +# - (optional) heading of model +# - (optional) initial extension of tunnel in m +# - (optional) initial rotation of tunnel along the Z axis +# - (optional) initial pitch of tunnel (rotation along Y axis) +# - (optional) initial rotation of entrance along the Z axis +# +# toggle(, , - extends/retracts a jetway +# [, ]) +# - whether or not jetway is toggled by user command (0/1) +# - heading of aircraft to connect to +# - a geo.Coord of the target aircraft's door +# - (optional) amount to rotate jetway hood (only required when != 1) +# +# extend(, , - extends a jetway (should be called by Jetway.toggle()) +# [, ]) +# - whether or not jetway is toggled by user command (0/1) +# - heading of aircraft to connect to +# - a geo.Coord of the target aircraft's door +# - (optional) amount to rotate jetway hood (only required when != 1) +# +# retract() - retracts a jetway (should be called by Jetway.toggle()) +# - whether or not a jetway is toggled by user command (0/1) +# +# remove() - removes a jetway object and its model +# +# reload() - reloads a jetway object and its model +# +# setpos(, , , ) - moves a jetway to a new location +# - new latitude +# - new longitude +# - new heading +# - new altitude in m +# +# setmodel(, , ) - changes the jetway model +# - new model +# - new airline sign code +# - new gate number +# +# INTERACTION FUNCTIONS +# --------------------- +# +# dialog() - open settings dialog +# +# toggle_jetway() - toggles a jetway by user command (should be called by a pick animation in a jetway model) +# - id number of jetway to toggle +# +# toggle_jetway_from_coord(, , - toggles a jetway with the target door at the specified coordinates +# , [, +# ] []) +# - door number (i.e. 0) +# - amount to rotate jetway hood +# - (required or ) latitude location of door +# - (required or ) longitude location of door +# - (required or , ) a geo.Coord of the door +# +# toggle_jetway_from_model() - toggles a jetway using an AI model instead of the user's aircraft +# - path of AI model (i.e. /ai/models/aircraft[0])- can be the path in a string or a props.Node +# +# INTERNAL FUNCTIONS +# ------------------ +# +# load_airport_jetways() - loads jetways at an airport +# - ICAO of airport +# +# unload_airport_jetways() - unloads jetways at an airport +# - ICAO of airport +# +# update_jetways() - interpolates model animation values and keeps the system tidy +# +# load_jetways() - loads new jetway models and unloads out-of-range models every 30 seconds +# + +## Utility functions +#################### + +# prints debug messages +var print_debug = func(msg) + { + if (debug_switch.getBoolValue()) + { + print(msg); + } + }; +# prints error messages +var print_error = func(msg) + { + print("\x1b[31m" ~ msg ~ "\x1b[m"); + }; +# alerts the user +var alert = func(msg) + { + setprop("/sim/messages/ground", msg); + }; +# normalizes headings between -180 and 180 +var normdeg = func(x) + { + while (x >= 180) + { + x -= 360; + } + while (x <= -180) + { + x += 360; + } + return x; + }; +# deletes an item in a vector +var remove = func(vector, item) + { + var s = size(vector); + var found = 0; + for (var i = 0; i < s; i += 1) + { + if (found) + { + vector[i - 1] = vector[i]; + } + elsif (vector[i] == item) + { + found = 1; + } + } + if (found) setsize(vector, s - 1); + return vector; + }; +# checks if an item is in a vector +var isin = func(vector, v) + { + for (var i = 0; i < size(vector); i += 1) + { + if (vector[i] == v) return 1; + } + return 0; + }; +# adds a model +var putmodel = func(path, lat, lon, alt, hdg) + { + var models = props.globals.getNode("/models"); + var model = nil; + for (var i = 0; 1; i += 1) + { + if (models.getChild("model", i, 0) == nil) + { + model = models.getChild("model", i, 1); + break; + } + } + var model_path = model.getPath(); + model.getNode("path", 1).setValue(path); + model.getNode("latitude-deg", 1).setDoubleValue(lat); + model.getNode("latitude-deg-prop", 1).setValue(model_path ~ "/latitude-deg"); + model.getNode("longitude-deg", 1).setDoubleValue(lon); + model.getNode("longitude-deg-prop", 1).setValue(model_path ~ "/longitude-deg"); + model.getNode("elevation-ft", 1).setDoubleValue(alt * M2FT); + model.getNode("elevation-ft-prop", 1).setValue(model_path ~ "/elevation-ft"); + model.getNode("heading-deg", 1).setDoubleValue(hdg); + model.getNode("heading-deg-prop", 1).setValue(model_path ~ "/heading-deg"); + model.getNode("pitch-deg", 1).setDoubleValue(0); + model.getNode("pitch-deg-prop", 1).setValue(model_path ~ "/pitch-deg"); + model.getNode("roll-deg", 1).setDoubleValue(0); + model.getNode("roll-deg-prop", 1).setValue(model_path ~ "/roll-deg"); + model.getNode("load", 1).remove(); + return model; + }; + +# interpolates a value +var interpolate_table = func(table, v) + { + var x = 0; + forindex (i; table) + { + if (v >= table[i][0]) + { + x = i + 1 < size(table) ? (v - table[i][0]) / (table[i + 1][0] - table[i][0]) * (table[i + 1][1] - table[i][1]) + table[i][1] : table[i][1]; + } + } + return x; + }; +# gets a relative file path +var get_relative_filepath = func(path, target) + { + var newpath = ""; + for (var i = size(path) - 1; i >= 0; i -= 1) + { + var char = substr(path, i, 1); + if (char == "/") newpath ~= "../"; + } + # we can just append the target path for UNIX systems, but we need to remove the drive letter prefix for DOS systems + return newpath ~ (string.match(substr(target, 0, 3), "?:/") ? substr(target, 2, size(target) - 2) : target); + }; +# gets a list of nearest airports +# TODO: Don't use /sim/airport/nearest-airport-id, which restricts the list to 1 airport +var find_airports = func(max_distance) + { + var apt = getprop("/sim/airport/closest-airport-id"); + return apt == "" ? nil : [apt]; + }; + +## Global variables +################### + +var root = nil; +var home = nil; +var scenery = []; + +var UPDATE_PERIOD = 1 / 30; +var LOAD_PERIOD = 30; +var LOAD_DISTANCE = 50; # in nautical miles +var LOAD_JETWAY_PERIOD = 0.05; +var NUMBER_OF_JETWAYS = 2000; # approx max number of jetways loadable in FG +var runtime_files = NUMBER_OF_JETWAYS / LOAD_PERIOD * LOAD_JETWAY_PERIOD; +runtime_files = int(runtime_files) == runtime_files ? runtime_files : int(runtime_files) + 1; +var runtime_file = 0; +var update_loopid = -1; +var load_loopid = -1; +var load_listenerid = nil; +var loadids = {}; +var dialog_object = nil; +var loaded_airports = []; +var jetways = []; + +# properties +var on_switch = "/nasal/jetways/enabled"; +var debug_switch = "/sim/jetways/debug"; +var mp_switch = "/sim/jetways/interact-with-multiplay"; +var jetway_id_prop = "/sim/jetways/last-loaded-jetway"; + +# interpolation tables +var extend_rate = 0.5; +var extend_table = [ + [0.0, 0.0], + [0.2, 0.3], + [0.6, 0.3], + [0.8, 1.0], + [1.0, 1.0] + ]; +var pitch_rate = 1; +var pitch_table = [ + [0.0, 0.0], + [0.4, 0.7], + [0.7, 1.0], + [1.0, 1.0] + ]; +var heading_rate = 1; +var heading_table = [ + [0.0, 0.0], + [0.2, 0.0], + [0.6, 0.7], + [0.9, 1.0], + [1.0, 1.0] + ]; +var heading_entrance_rate = 5; +var heading_entrance_table = [ + [0.0, 0.0], + [0.3, 0.0], + [0.6, 0.7], + [0.8, 1.0], + [1.0, 1.0] + ]; +var hood_rate = 1; +var hood_table = [ + [0.0, 0.0], + [0.9, 0.0], + [1.0, 1.0] + ]; + +## Classes +########## + +# main jetway class +var Jetway = + { + new: func(airport, model, gate, door, airline, lat, lon, alt, heading, init_extend = 0, init_heading = 0, init_pitch = 0, init_ent_heading = 0) + { + var id = 0; + for (var i = 0; 1; i += 1) + { + if (i == size(jetways)) + { + setsize(jetways, i + 1); + id = i; + break; + } + elsif (jetways[i] == nil) + { + id = i; + } + } + # locate the jetway model directory and load the model tree + var model_tree = nil; + var model_file = ""; + var model_dir = ""; + var airline_file = ""; + if (props.globals.getNode("/sim/paths/use-custom-scenery-data", 1).getBoolValue()) + { + # search in scenery directories + foreach (var scenery_path; scenery) + { + model_dir = scenery_path ~ "/Models/Airport/Jetway"; + model_file = model_dir ~ "/" ~ model ~ ".xml"; + airline_file = model_dir ~ "/" ~ model ~ ".airline." ~ airline ~ ".xml"; + print_debug("Trying to load a jetway model from " ~ model_file); + if (io.stat(model_file) == nil) continue; + model_tree = io.read_properties(model_file); + if (io.stat(airline_file) != nil) props.copy(io.read_properties(airline_file), model_tree); + break; + } + } + if (model_tree == nil) + { + model_dir = root ~ "/Models/Airport/Jetway"; + model_file = model_dir ~ "/" ~ model ~ ".xml"; + airline_file = model_dir ~ "/" ~ model ~ ".airline." ~ airline ~ ".xml"; + print_debug("Falling back to " ~ model_file); + if (io.stat(model_file) == nil) + { + print_error("Failed to load jetway model: " ~ model); + return; + } + model_tree = io.read_properties(model_file); + if (io.stat(airline_file) != nil) props.copy(io.read_properties(airline_file), model_tree); + } + + var m = + { + parents: [Jetway] + }; + m._active = 1; # set this to 'true' on the first run so that the offsets can take effect + m.airport = airport; + m.gate = gate; + m.airline = airline; + m.id = id; + m.model = model; + m.extended = 0; + m.door = door; + m.lat = lat; + m.lon = lon; + m.alt = alt; + m.heading = geo.normdeg(180 - heading); + m.init_extend = init_extend; + m.init_heading = init_heading; + m.init_pitch = init_pitch; + m.init_ent_heading = init_ent_heading; + m.target_extend = 0; + m.target_pitch = 0; + m.target_heading = 0; + m.target_ent_heading = 0; + m.target_hood = 0; + m.rotunda_x = model_tree.getNode("rotunda/x-m").getValue(); + m.rotunda_y = model_tree.getNode("rotunda/y-m").getValue(); + m.rotunda_z = model_tree.getNode("rotunda/z-m").getValue(); + m.offset_extend = model_tree.getNode("extend-offset-m").getValue(); + m.offset_entrance = model_tree.getNode("entrance-offset-m").getValue(); + m.min_extend = model_tree.getNode("min-extend-m").getValue(); + m.max_extend = model_tree.getNode("max-extend-m").getValue(); + + # get the runtime file path + if (runtime_file == runtime_files) + { + runtime_file = 0; + } + var runtime_file_path = home ~ "/runtime-jetways/" ~ runtime_file ~ ".xml"; + runtime_file += 1; + + # create the model node and the door object + m.node = putmodel(runtime_file_path, lat, lon, alt, geo.normdeg(360 - heading)); + var node_path = m.node.getPath(); + m.door_object = aircraft.door.new(node_path ~ "/jetway-position", 0); + + # manipulate the model tree + model_tree.getNode("path").setValue(model_dir ~ "/" ~ model_tree.getNode("path").getValue()); + model_tree.getNode("toggle-action-script").setValue("jetways.toggle_jetway(" ~ id ~ ");"); + model_tree.getNode("gate").setValue(m.gate); + model_tree.getNode("extend-m").setValue(props.globals.initNode(node_path ~ "/jetway-position/extend-m", 0, "DOUBLE").getPath()); + model_tree.getNode("pitch-deg").setValue(props.globals.initNode(node_path ~ "/jetway-position/pitch-deg", 0, "DOUBLE").getPath()); + model_tree.getNode("heading-deg").setValue(props.globals.initNode(node_path ~ "/jetway-position/heading-deg", 0, "DOUBLE").getPath()); + model_tree.getNode("entrance-heading-deg").setValue(props.globals.initNode(node_path ~ "/jetway-position/entrance-heading-deg", 0, "DOUBLE").getPath()); + model_tree.getNode("hood-deg").setValue(props.globals.initNode(node_path ~ "/jetway-position/hood-deg", 0, "DOUBLE").getPath()); + # airline texture + var airline_node = model_tree.getNode(model_tree.getNode("airline-prop-path", 1).getValue()); + if (airline_node != nil) + { + airline_node.setValue(get_relative_filepath(home ~ "/runtime-jetways", model_dir ~ "/Airlines/" ~ airline ~ ".png")); + } + # write the model tree + io.write_properties(runtime_file_path, model_tree); + + jetways[id] = m; + print_debug("Loaded jetway #" ~ id); + jetway_id_prop.setValue(id); + return m; + }, + toggle: func(user, heading, coord, hood = 0) + { + me._active = 1; + if (me.extended) + { + me.retract(user, heading, coord); + } + else + { + me.extend(user, heading, coord, hood); + } + }, + extend: func(user, heading, door_coord, hood = 0) + { + me.extended = 1; + + # get the coordinates of the jetway and offset for the rotunda position + var jetway_coord = geo.Coord.new(); + jetway_coord.set_latlon(me.lat, me.lon); + jetway_coord.apply_course_distance(me.heading, me.rotunda_x); + jetway_coord.apply_course_distance(me.heading - 90, me.rotunda_y); + jetway_coord.set_alt(me.alt + me.rotunda_z); + + if (debug_switch.getBoolValue()) + { + # place UFO cursors at the calculated door and jetway positions for debugging purposes + geo.put_model("Aircraft/ufo/Models/cursor.ac", door_coord); + geo.put_model("Aircraft/ufo/Models/cursor.ac", jetway_coord); + } + + # offset the door for the length of the jetway entrance + door_coord.apply_course_distance(heading - 90, me.offset_entrance); + + # calculate the bearing to the aircraft and the distance from the door + me.target_heading = normdeg(jetway_coord.course_to(door_coord) - me.heading - me.init_heading); + me.target_extend = jetway_coord.distance_to(door_coord) - me.offset_extend - me.init_extend; + + # check if distance exceeds maximum jetway extension length + if (me.target_extend + me.init_extend > me.max_extend) + { + me.extended = 0; + me.target_extend = 0; + me.target_heading = 0; + if (user) alert("Your aircraft is too far from this jetway."); + print_debug("Jetway #" ~ me.id ~ " is too far from the door"); + return; + } + # check if distance fails to meet minimum jetway extension length + if (me.target_extend + me.init_extend < me.min_extend) + { + me.extended = 0; + me.target_extend = 0; + me.target_heading = 0; + if (user) alert("Your aircraft is too close to this jetway."); + print_debug("Jetway #" ~ me.id ~ " is too close to the door"); + return; + } + + # calculate the jetway pitch, entrance heading, and hood + me.target_pitch = math.atan2((door_coord.alt() - jetway_coord.alt()) / (me.target_extend + me.offset_extend + me.init_extend), 1) * R2D - me.init_pitch; + me.target_ent_heading = normdeg((heading + 90) - (me.heading + (me.target_heading + me.init_heading) + me.init_ent_heading)); + me.target_hood = user ? getprop("/sim/model/door[" ~ me.door ~ "]/jetway-hood-deg") : hood; + + # fire up the animation + if (user) alert("Extending jetway."); + var animation_time = math.abs(me.target_extend / extend_rate) + math.abs(me.target_pitch / pitch_rate) + math.abs(me.target_heading / heading_rate) + math.abs(me.target_ent_heading / heading_entrance_rate) + math.abs(me.target_hood / hood_rate); + me.door_object.swingtime = animation_time; + me.door_object.open(); + + print_debug("************************************************"); + print_debug("Activated jetway #" ~ me.id); + print_debug("Using door #" ~ me.door); + print_debug("Jetway heading: " ~ me.heading ~ " deg"); + print_debug("Extension: " ~ me.target_extend ~ " m"); + print_debug("Pitch: " ~ me.target_pitch ~ " deg"); + print_debug("Heading: " ~ me.target_heading ~ " deg"); + print_debug("Entrance heading: " ~ me.target_ent_heading ~ " deg"); + print_debug("Hood: " ~ me.target_hood ~ " deg"); + print_debug("Total animation time: " ~ animation_time ~ " sec"); + print_debug("Jetway extending"); + print_debug("************************************************"); + }, + retract: func(user) + { + if (user) alert("Retracting jetway."); + me.door_object.close(); + me.extended = 0; + + print_debug("************************************************"); + print_debug("Activated jetway #" ~ me.id); + print_debug("Total animation time: " ~ me.door_object.swingtime ~ " sec"); + print_debug("Jetway retracting"); + print_debug("************************************************"); + }, + remove: func + { + me.node.remove(); + var id = me.id; + jetways[me.id] = nil; + print_debug("Unloaded jetway #" ~ id); + }, + reload: func + { + var airport = me.airport; + var model = me.model; + var gate = me.gate; + var door = me.door; + var airline = me.airline; + var lat = me.lat; + var lon = me.lon; + var alt = me.alt; + var heading = geo.normdeg(180 - (me.heading - 360)); + var init_extend = me.init_extend; + var init_heading = me.init_heading; + var init_pitch = me.init_pitch; + var init_ent_heading = me.init_ent_heading; + me.remove(); + Jetway.new(airport, model, gate, door, airline, lat, lon, alt, heading, init_extend, init_heading, init_pitch, init_ent_heading); + }, + setpos: func(lat, lon, hdg, alt) + { + me.node.getNode("latitude-deg").setValue(lat); + me.lat = lat; + me.node.getNode("longitude-deg").setValue(lon); + me.lon = lon; + me.node.getNode("heading-deg").setValue(geo.normdeg(hdg - 180)); + me.heading = hdg; + me.node.getNode("elevation-ft").setValue(alt * M2FT); + me.alt = alt; + }, + setmodel: func(model, airline, gate) + { + me.airline = airline; + me.gate = gate; + me.model = model; + me.extended = 0; + me.target_extend = 0; + me.target_pitch = 0; + me.target_heading = 0; + me.target_ent_heading = 0; + me.target_hood = 0; + me.door_object.setpos(0); + me.reload(); + } + }; + +## Interaction functions +######################## + +var dialog = func + { + if (dialog_object == nil) dialog_object = gui.Dialog.new("/sim/gui/dialogs/jetways/dialog", "gui/dialogs/jetways.xml"); + dialog_object.open(); + }; + +var toggle_jetway = func(n) + { + var jetway = jetways[n]; + if (jetway == nil) return; + var door = props.globals.getNode("/sim/model/door[" ~ jetway.door ~ "]"); + if (door == nil) + { + alert("Your aircraft does not define the location of door " ~ (jetway.door + 1) ~ ", cannot extend this jetway."); + return; + } + + # get the coordinates of the user's aircraft and offset for the door position and aircraft pitch + var coord = geo.aircraft_position(); + var heading = getprop("/orientation/heading-deg"); + var pitch = getprop("/orientation/pitch-deg"); + coord.apply_course_distance(heading, -door.getChild("position-x-m").getValue()); + coord.apply_course_distance(heading + 90, door.getChild("position-y-m").getValue()); + coord.set_alt(coord.alt() + door.getChild("position-z-m").getValue()); + coord.set_alt(coord.alt() + math.tan(pitch * D2R) * -door.getChild("position-x-m").getValue()); + + jetway.toggle(1, heading, coord); + }; +var toggle_jetway_from_coord = func(door, hood, heading, lat, lon = nil) + { + if (isa(lat, geo.Coord)) + { + var coord = lat; + } + else + { + var coord = geo.Coord.new(); + coord.set_latlon(lat, lon); + } + var closest_jetway = nil; + var closest_jetway_dist = nil; + var closest_jetway_coord = nil; + for (var i = 0; i < size(jetways); i += 1) + { + if (jetways[i] == nil) continue; + var jetway = jetways[i]; + var jetway_coord = geo.Coord.new(); + jetway_coord.set_latlon(jetway.lat, jetway.lon); + + var distance = jetway_coord.distance_to(coord); + if ((closest_jetway_dist == nil or distance < closest_jetway_dist) and jetway.door == door) + { + closest_jetway = jetway; + closest_jetway_dist = distance; + closest_jetway_coord = jetway_coord; + } + } + if (closest_jetway == nil) + { + print_debug("No jetways available"); + } + elsif (!closest_jetway.extended) + { + closest_jetway.toggle(0, heading, coord, hood); + } + }; +var toggle_jetway_from_model = func(model) + { + model = aircraft.makeNode(model); + var doors = model.getChildren("door"); + if (doors == nil or size(doors) == 0) return; + for (var i = 0; i < size(doors); i += 1) + { + var coord = geo.Coord.new(); + var hdg = model.getNode("orientation/true-heading-deg").getValue(); + var lat = model.getNode("position/latitude-deg").getValue(); + var lon = model.getNode("position/longitude-deg").getValue(); + var alt = model.getNode("position/altitude-ft").getValue() * FT2M + doors[i].getNode("position-z-m").getValue(); + coord.set_latlon(lat, lon, alt); + coord.apply_course_distance(hdg, -doors[i].getNode("position-x-m").getValue()); + coord.apply_course_distance(hdg + 90, doors[i].getNode("position-y-m").getValue()); + print_debug("Connecting a jetway to door #" ~ i ~ " for model " ~ model.getPath()); + toggle_jetway_from_coord(i, doors[i].getNode("jetway-hood-deg").getValue(), hdg, coord); + } + }; + +## Internal functions +##################### + +# loads jetways at an airport +var load_airport_jetways = func(airport) + { + if (isin(loaded_airports, airport)) return; + var tree = props.globals.getNode("/sim/paths/use-custom-scenery-data", 1).getBoolValue() ? io.read_airport_properties(airport, "jetways") : (io.stat(root ~ "/AI/Airports/" ~ airport ~ "/jetways.xml") == nil ? nil : io.read_properties(root ~ "/AI/Airports/" ~ airport ~ "/jetways.xml")); + if (tree == nil) return; + print_debug("Loading jetways for airport " ~ airport); + var nodes = tree.getChildren("jetway"); + + loadids[airport] = loadids[airport] == nil ? 0 : loadids[airport] + 1; + var i = 0; + var loop = func(id) + { + if (id != loadids[airport]) return; + if (i >= size(nodes)) + { + append(loaded_airports, airport); + return; + } + var jetway = nodes[i]; + var model = jetway.getNode("model", 1).getValue() or return; + var gate = jetway.getNode("gate", 1).getValue() or ""; + var door = jetway.getNode("door", 1).getValue() or 0; + var airline = jetway.getNode("airline", 1).getValue() or "None"; + var lat = jetway.getNode("latitude-deg", 1).getValue() or return; + var lon = jetway.getNode("longitude-deg", 1).getValue() or return; + var elev = jetway.getNode("elevation-m", 1).getValue() or 0; + var alt = geo.elevation(lat, lon) + elev; + var heading = jetway.getNode("heading-deg", 1).getValue() or 0; + var init_extend = jetway.getNode("initial-position/jetway-extension-m", 1).getValue() or 0; + var init_heading = jetway.getNode("initial-position/jetway-heading-deg", 1).getValue() or 0; + var init_pitch = jetway.getNode("initial-position/jetway-pitch-deg", 1).getValue() or 0; + var init_ent_heading = jetway.getNode("initial-position/entrance-heading-deg", 1).getValue() or 0; + Jetway.new(airport, model, gate, door, airline, lat, lon, alt, heading, init_extend, init_heading, init_pitch, init_ent_heading); + + i += 1; + settimer(func loop(id), LOAD_JETWAY_PERIOD); + }; + settimer(func loop(loadids[airport]), 0); + }; +# unloads jetways at an airport +var unload_airport_jetways = func(airport) + { + print_debug("Unloading jetways for airport " ~ airport); + foreach (var jetway; jetways) + { + if (jetway != nil and jetway.airport == airport) jetway.remove(); + } + remove(loaded_airports, airport); + }; + +# restarts the main update loop +var restart = func() + { + update_loopid += 1; + update_jetways(update_loopid); + settimer(func + { + load_loopid += 1; + load_jetways(load_loopid); + }, 2); + print("Animated jetways ... initialized"); + }; +# main update loop (runs when jetways are enabled) +var update_jetways = func(loopid) + { + # terminate if loopid does not match + if (loopid != update_loopid) return; + # if jetways disabled, unload jetways and terminate + if (!on_switch.getBoolValue()) + { + for (var i = 0; i < size(jetways); i += 1) + { + if (jetways[i] != nil) jetways[i].remove(); + } + setsize(jetways, 0); + setsize(loaded_airports, 0); + return; + } + + var nearest_airport = airportinfo(); + nearest_airport = nearest_airport == nil ? nil : nearest_airport.id; + if (isin(loaded_airports, nearest_airport)) + { + # loop through the AI aircraft and extend/retract jetways + var ai_aircraft = props.globals.getNode("ai/models").getChildren("aircraft"); + foreach (var aircraft; ai_aircraft) + { + if (!aircraft.getNode("valid", 1).getBoolValue()) continue; + var connected = aircraft.getNode("connected-to-jetways", 1); + var velocity = aircraft.getNode("velocities/true-airspeed-kt").getValue(); + # TODO: Find a better way to know when the aircraft is "parked" + if (velocity > -1 and velocity < 1) + { + if (!connected.getBoolValue()) toggle_jetway_from_model(aircraft); + connected.setBoolValue(1); + } + else + { + if (connected.getBoolValue()) toggle_jetway_from_model(aircraft); + connected.setBoolValue(0); + } + } + # loop through the multiplayer aircraft and extend/retract jetways + # TODO: In the future, broadcast jetway properties over MP, making this part obselete + if (mp_switch.getBoolValue()) + { + var multiplayers = props.globals.getNode("ai/models").getChildren("multiplayer"); + foreach (var aircraft; multiplayers) + { + if (!aircraft.getNode("valid", 1).getBoolValue()) continue; + var connected = aircraft.getNode("connected-to-jetways", 1); + var velocity = aircraft.getNode("velocities/true-airspeed-kt").getValue(); + if (velocity > -1 and velocity < 1) + { + if (!connected.getBoolValue()) toggle_jetway_from_model(aircraft); + connected.setBoolValue(1); + } + else + { + if (connected.getBoolValue()) toggle_jetway_from_model(aircraft); + connected.setBoolValue(0); + } + } + } + } + # interpolate jetway values + for (var i = 0; i < size(jetways); i += 1) + { + var jetway = jetways[i]; + if (jetway == nil or !jetway._active) continue; + var position = jetway.door_object.getpos(); + if (position == 0 or position == 1) jetway._active = 0; + jetway.node.getNode("jetway-position/extend-m").setValue(interpolate_table(extend_table, position) * jetway.target_extend + jetway.init_extend); + jetway.node.getNode("jetway-position/pitch-deg").setValue(interpolate_table(pitch_table, position) * jetway.target_pitch + jetway.init_pitch); + jetway.node.getNode("jetway-position/heading-deg").setValue(interpolate_table(heading_table, position) * jetway.target_heading + jetway.init_heading); + jetway.node.getNode("jetway-position/entrance-heading-deg").setValue(interpolate_table(heading_entrance_table, position) * jetway.target_ent_heading + jetway.init_ent_heading); + jetway.node.getNode("jetway-position/hood-deg").setValue(interpolate_table(hood_table, position) * jetway.target_hood); + } + settimer(func update_jetways(loopid), UPDATE_PERIOD); + }; +# loading/unloading loop (runs continuously) +var load_jetways = func(loopid) + { + if (load_listenerid != nil) removelistener(load_listenerid); + # terminate if loopid does not match + # unloading jetways if jetways are disabled is handled by update loop + if (loopid != load_loopid or !on_switch.getBoolValue()) return; + var airports = find_airports(LOAD_DISTANCE); + if (airports == nil) return; + # search for any airports out of range and unload their jetways + foreach (var airport; loaded_airports) + { + if (!isin(airports, airport)) + { + unload_airport_jetways(airport); + } + } + # load any airports in range + foreach (var airport; airports) + { + load_airport_jetways(airport); + } + settimer(func load_jetways(loopid), LOAD_PERIOD); + }; +## fire it up +_setlistener("/nasal/jetways/loaded", func + { + # global variables + root = string.normpath(getprop("/sim/fg-root")); + home = string.normpath(getprop("/sim/fg-home")); + foreach (var scenery_path; props.globals.getNode("/sim").getChildren("fg-scenery")) + { + append(scenery, string.normpath(scenery_path.getValue())); + } + if (size(scenery) == 0) append(scenery, root ~ "/Scenery"); + + # properties + on_switch = props.globals.getNode(on_switch, 1); + debug_switch = props.globals.getNode(debug_switch, 1); + mp_switch = props.globals.getNode(mp_switch, 1); + + jetway_id_prop = props.globals.getNode(jetway_id_prop, 1); + restart(); + }); diff --git a/Nasal/jetways_edit/jetways_edit.nas b/Nasal/jetways_edit/jetways_edit.nas new file mode 100644 index 000000000..ed0f53a58 --- /dev/null +++ b/Nasal/jetways_edit/jetways_edit.nas @@ -0,0 +1,594 @@ +############################################################################### +## +## Animated Jetway System. Allows the user to edit jetways during runtime. +## +## Copyright (C) 2011 Ryan Miller +## This file is licensed under the GPL license version 2 or later. +## +############################################################################### + +############################################################################### +# (See http://wiki.flightgear.org/Howto:_Animated_jetways) +# + +### Static jetway model profiles ### +### This class specifies the offsets used when converting static jetways using the STG converter ### +var Static_jetway = + [ + # Models/Airport/jetway-movable.ac + # Models/Airport/jetway-movable.xml + # Models/Airport/jetway-movable-2.ac + # Models/Airport/jetway-movable-2.xml + # Models/Airport/jetway-movable-3.ac + # Models/Airport/jetway-movable-3.xml + { + models: + [ + "Models/Airport/jetway-movable.ac", + "Models/Airport/jetway-movable.xml", + "Models/Airport/jetway-movable-2.ac", + "Models/Airport/jetway-movable-2.xml", + "Models/Airport/jetway-movable-3.ac", + "Models/Airport/jetway-movable-3.xml" + ], + offsets: + { + x: -2.042, + y: 0, + z: 0, + heading: 0 + }, + init_pos: + { + extend: 7.24, + heading: 0, + pitch: 0, + ent_heading: -90 + }, + model: "generic", + airline: "None" + }, + # Models/Airport/jetway.xml + # Models/Airport/jetway-ba.ac + # Models/Airport/jetway-ba.xml + { + models: + [ + "Models/Airport/jetway.xml", + "Models/Airport/jetway-ba.ac", + "Models/Airport/jetway-ba.xml" + ], + offsets: + { + x: 0, + y: 0, + z: -0.25, + heading: 0 + }, + init_pos: + { + extend: 7.24, + heading: -6.7, + pitch: -3.6, + ent_heading: -83.3 + }, + model: "generic", + airline: "None" + }, + # Models/Airport/jetway-737-ba.ac + # Models/Airport/jetway-737-ba.xml + { + models: + [ + "Models/Airport/jetway-737-ba.ac", + "Models/Airport/jetway-737-ba.xml" + ], + offsets: + { + x: 0, + y: 0, + z: -0.25, + heading: 0 + }, + init_pos: + { + extend: 7.24, + heading: -6.7, + pitch: -4, + ent_heading: -83.3 + }, + model: "generic", + airline: "None" + }, + # Models/Airport/jetway-747-ba.ac + # Models/Airport/jetway-747-ba.xml + { + models: + [ + "Models/Airport/jetway-747-ba.ac", + "Models/Airport/jetway-747-ba.xml" + ], + offsets: + { + x: 0, + y: 0, + z: -0.25, + heading: 0 + }, + init_pos: + { + extend: 7.24, + heading: -6.7, + pitch: 2, + ent_heading: -83.3 + }, + model: "generic", + airline: "None" + }, + # Models/Airport/jetway-a320-ba.ac + # Models/Airport/jetway-a320-ba.xml + { + models: + [ + "Models/Airport/jetway-a320-ba.ac", + "Models/Airport/jetway-a320-ba.xml" + ], + offsets: + { + x: 0, + y: 0, + z: -0.25, + heading: 0 + }, + init_pos: + { + extend: 7.24, + heading: -6.7, + pitch: -1.6, + ent_heading: -83.3 + }, + model: "generic", + airline: "None" + }, + # Models/Airport/AutoGate-ba.ac + # Models/Airport/AutoGate.xml + { + models: + [ + "Models/Airport/AutoGate-ba.ac", + "Models/Airport/AutoGate.xml" + ], + offsets: + { + x: -10, + y: 25, + z: 0, + heading: -90 + }, + init_pos: + { + extend: 7.68, + heading: 0, + pitch: 0, + ent_heading: -90 + }, + model: "generic", + airline: "None" + }, + # Models/Airport/DockingGate-ba.ac + # Models/Airport/DockingGate.xml + { + models: + [ + "Models/Airport/DockingGate-ba.ac", + "Models/Airport/DockingGate.xml" + ], + offsets: + { + x: -10, + y: 5, + z: 0, + heading: -90 + }, + init_pos: + { + extend: 7.68, + heading: 0, + pitch: 0, + ent_heading: -90 + }, + model: "generic", + airline: "None" + } + ]; + +### Rest of script follows below ### +### Watch your step! :) ### +var dialog_object = nil; +var selected_jetway = nil; +var mouse_mmb = 0; +var kbd_shift = nil; +var kbd_ctrl = nil; +var kbd_alt = nil; +var enabled = nil; +var FLASH_PERIOD = 0.3; +var FLASH_NUM = 3; +var filedialog_listener = 0; + +var click = func(pos) + { + if (kbd_alt.getBoolValue()) + { + if (selected_jetway == nil) return; + selected_jetway.setpos(pos.lat(), pos.lon(), selected_jetway.heading, pos.alt()); + } + elsif (kbd_shift.getBoolValue()) + { + selected_jetway = nil; + } + elsif (kbd_ctrl.getBoolValue()) + { + var nearest_jetway = nil; + var min_dist = geo.ERAD; + for (var i = 0; i < size(jetways.jetways); i += 1) + { + var jetway = jetways.jetways[i]; + if (jetway == nil) continue; + var dist = geo.Coord.new().set_latlon(jetway.lat, jetway.lon, jetway.alt).direct_distance_to(pos); + if (dist < min_dist) + { + min_dist = dist; + nearest_jetway = jetway; + } + } + if (nearest_jetway != nil) + { + selected_jetway = nearest_jetway; + setprop("/sim/jetways/adjust/model", selected_jetway.model); + setprop("/sim/jetways/adjust/door", selected_jetway.door); + setprop("/sim/jetways/adjust/airline", selected_jetway.airline); + setprop("/sim/jetways/adjust/gate", selected_jetway.gate); + flash(nearest_jetway); + } + } + else + { + var airport = getprop("/sim/airport/closest-airport-id"); + if (airport == "") return; + selected_jetway = jetways.Jetway.new(airport, "generic", "FG", 0, "FGFS", pos.lat(), pos.lon(), pos.alt(), 0); + if (!jetways.isin(jetways.loaded_airports, airport)) append(jetways.loaded_airports, airport); + setprop("/sim/jetways/adjust/model", selected_jetway.model); + setprop("/sim/jetways/adjust/door", selected_jetway.door); + setprop("/sim/jetways/adjust/airline", selected_jetway.airline); + setprop("/sim/jetways/adjust/gate", selected_jetway.gate); + flash(selected_jetway); + } + }; +var delete = func + { + if (selected_jetway == nil) return; + selected_jetway.remove(); + selected_jetway = nil; + }; +var adjust = func(name, value) + { + if (selected_jetway == nil) return; + if (name == "longitudinal") + { + var jetway_pos = geo.Coord.new(); + jetway_pos.set_latlon(selected_jetway.lat, selected_jetway.lon, selected_jetway.alt); + var dir = geo.aircraft_position().course_to(jetway_pos); + jetway_pos.apply_course_distance(dir, value); + selected_jetway.setpos(jetway_pos.lat(), jetway_pos.lon(), selected_jetway.heading, selected_jetway.alt); + } + elsif (name == "transversal") + { + var jetway_pos = geo.Coord.new(); + jetway_pos.set_latlon(selected_jetway.lat, selected_jetway.lon, selected_jetway.alt); + var dir = geo.aircraft_position().course_to(jetway_pos) + 90; + jetway_pos.apply_course_distance(dir, value); + selected_jetway.setpos(jetway_pos.lat(), jetway_pos.lon(), selected_jetway.heading, selected_jetway.alt); + } + elsif (name == "altitude") + { + var alt = selected_jetway.alt + value * 0.4; + selected_jetway.setpos(selected_jetway.lat, selected_jetway.lon, selected_jetway.heading, alt); + } + elsif (name == "heading") + { + var hdg = geo.normdeg(selected_jetway.heading + value * 4); + selected_jetway.setpos(selected_jetway.lat, selected_jetway.lon, hdg, selected_jetway.alt); + } + elsif (name == "initial-extension") + { + var newvalue = selected_jetway.init_extend + value; + if (newvalue > selected_jetway.max_extend) + { + gui.popupTip("Value exceeds maximum jetway extension limit"); + } + elsif (newvalue < selected_jetway.min_extend) + { + gui.popupTip("Value lower than minimum jetway extension limit"); + } + else + { + selected_jetway.init_extend = newvalue; + } + } + elsif (name == "initial-pitch") + { + selected_jetway.init_pitch += value; + } + elsif (name == "initial-heading") + { + selected_jetway.init_heading += value; + } + elsif (name == "initial-entrance-heading") + { + selected_jetway.init_ent_heading += value; + } + elsif (name == "model") + { + selected_jetway.setmodel(value, selected_jetway.airline, selected_jetway.gate); + selected_jetway = jetways.jetways[getprop("/sim/jetways/last-loaded-jetway")]; + } + elsif (name == "door") + { + selected_jetway.door = value; + } + elsif (name == "airline") + { + selected_jetway.setmodel(selected_jetway.model, value, selected_jetway.gate); + selected_jetway = jetways.jetways[getprop("/sim/jetways/last-loaded-jetway")]; + } + elsif (name == "gate") + { + selected_jetway.setmodel(selected_jetway.model, selected_jetway.airline, value); + selected_jetway = jetways.jetways[getprop("/sim/jetways/last-loaded-jetway")]; + } + }; +var export = func + { + var path = getprop("/sim/fg-home") ~ "/Export/"; + var airports = {}; + var airportarray = []; + foreach (var jetway; jetways.jetways) + { + if (jetway == nil) continue; + if (airports[jetway.airport] == nil) + { + airports[jetway.airport] = []; + append(airportarray, jetway.airport); + } + var node = props.Node.new(); + node.getNode("model", 1).setValue(jetway.model); + node.getNode("gate", 1).setValue(jetway.gate); + node.getNode("door", 1).setIntValue(jetway.door); + node.getNode("airline", 1).setValue(jetway.airline); + node.getNode("latitude-deg", 1).setDoubleValue(jetway.lat); + node.getNode("longitude-deg", 1).setDoubleValue(jetway.lon); + node.getNode("elevation-m", 1).setDoubleValue(jetway.alt - geo.elevation(jetway.lat, jetway.lon)); + node.getNode("heading-deg", 1).setDoubleValue(geo.normdeg(180 - jetway.heading)); + node.getNode("initial-position/jetway-extension-m", 1).setDoubleValue(jetway.init_extend); + node.getNode("initial-position/jetway-heading-deg", 1).setDoubleValue(jetway.init_heading); + node.getNode("initial-position/jetway-pitch-deg", 1).setDoubleValue(jetway.init_pitch); + node.getNode("initial-position/entrance-heading-deg", 1).setDoubleValue(jetway.init_ent_heading); + append(airports[jetway.airport], node); + } + foreach (var airport; airportarray) + { + var file = path ~ airport ~ ".xml"; + var args = props.Node.new({ filename: file }); + var nodes = airports[airport]; + foreach (var node; nodes) + { + var data = args.getNode("data", 1); + for (var i = 0; 1; i += 1) + { + if (data.getChild("jetway", i, 0) == nil) + { + props.copy(node, data.getChild("jetway", i, 1)); + break; + } + } + } + fgcommand("savexml", args); + print("jetway definitions for airport " ~ airport ~ " exported to " ~ file); + } + }; +var convert_stg = func + { + fgcommand("dialog-show", props.Node.new({ "dialog-name": "file-select" })); + setprop("/sim/gui/dialogs/file-select/path", ""); + filedialog_listener = setlistener("/sim/gui/dialogs/file-select/path", func(n) + { + removelistener(filedialog_listener); + var path = n.getValue(); + if (path == "") return; + var stg = io.readfile(path); + var stg_lines = [[]]; + var current_word = ""; + for (var i = 0; i < size(stg); i += 1) + { + var char = substr(stg, i, 1); + if (char == " " or char == "\n") + { + append(stg_lines[size(stg_lines) - 1], current_word); + current_word = ""; + if (char == "\n") append(stg_lines, []); + } + else + { + current_word ~= char; + } + } + + var jetway_array = []; + foreach (var line; stg_lines) + { + if (size(line) < 6 or line[0] != "OBJECT_SHARED") continue; + var foundmodel = 0; + var jetway = nil; + foreach (var profile; Static_jetway) + { + foreach (var model; profile.models) + { + if (model == line[1]) foundmodel = 1; + } + if (foundmodel) + { + jetway = profile; + break; + } + } + if (jetway == nil) continue; + var heading = num(line[5]); + var coord = geo.Coord.new(); + coord.set_latlon(line[3], line[2], line[4]); + coord.apply_course_distance(360 - heading, -jetway.offsets.x); + coord.apply_course_distance(360 - heading + 90, jetway.offsets.y); + coord.set_alt(coord.alt() + jetway.offsets.z); + var hash = {}; + hash.coord = coord; + hash.heading = heading + jetway.offsets.heading; + hash.init_extend = jetway.init_pos.extend; + hash.init_heading = jetway.init_pos.heading; + hash.init_pitch = jetway.init_pos.pitch; + hash.init_ent_heading = jetway.init_pos.ent_heading; + hash.model = jetway.model; + hash.airline = jetway.airline; + append(jetway_array, hash); + } + + var airport = getprop("/sim/jetways/closest-airport-id"); + if (airport == "") return; + var i = 0; + var loop = func + { + if (i >= size(jetway_array)) return; + var jetway = jetway_array[i]; + jetways.Jetway.new(airport, jetway.model, "", 0, jetway.airline, jetway.coord.lat(), jetway.coord.lon(), jetway.coord.alt(), jetway.heading, jetway.init_extend, jetway.init_heading, jetway.init_pitch, jetway.init_ent_heading); + if (!jetways.isin(jetways.loaded_airports, airport)) append(jetways.loaded_airports, airport); + i += 1; + settimer(loop, jetways.LOAD_JETWAY_PERIOD); + }; + settimer(loop, 0); + jetways.alert("Creating " ~ size(jetway_array) ~ " jetways for airport " ~ airport); + }, 0, 1); + }; +var flash = func(jetway) + { + if (!contains(jetway, "_flashnum") or jetway._flashnum == -1) + { + jetway._alt = jetway.alt; + jetway.setpos(jetway.lat, jetway.lon, jetway.heading, -geo.ERAD); + jetway._flashnum = 0; + settimer(func flash(jetway), FLASH_PERIOD); + } + elsif (!contains(jetway, "_alt")) + { + jetway._flashnum = -1; + jetway.setpos(jetway.lat, jetway.lon, jetway.heading, geo.elevation(jetway.lat, jetway.lon)); + return; + } + elsif (jetway._flashnum == FLASH_NUM + 1) + { + jetway.setpos(jetway.lat, jetway.lon, jetway.heading, jetway._alt); + jetway._alt = nil; + jetway._flashnum = -1; + } + else + { + if (jetway.alt == -geo.ERAD) + { + jetway.setpos(jetway.lat, jetway.lon, jetway.heading, jetway._alt); + } + else + { + jetway.setpos(jetway.lat, jetway.lon, jetway.heading, -geo.ERAD); + } + jetway._flashnum += 1; + settimer(func flash(jetway), FLASH_PERIOD); + } + }; + +var dialog = func + { + if (dialog_object == nil) dialog_object = gui.Dialog.new("/sim/gui/dialogs/jetways-adjust/dialog", "gui/dialogs/jetways-adjust.xml"); + dialog_object.open(); + }; +var print_help = func + { + print("JETWAY EDITOR HELP"); + print("*******************************************************"); + print("See: http://wiki.flightgear.org/Howto:_Animated_jetways"); + print(""); + print("Adjust position, heading, and altitude with top sliders"); + print("Adjust initial jetway positions with bottom sliders"); + print(""); + print(" model of selected jetway"); + print(" aircraft door number of selected jetway"); + print(" airline sign code of selected jetway"); + print(" gate number of selected jetway"); + print(""); + print("[Center sliders] apply slider offsets and return sliders to 0"); + print("[Export] export jetway definition file(s)"); + print("[STG converter] convert static jetways in STG files to animated jetways"); + print("[?] show this help text"); + print(""); + print("Click add jetway on click position"); + print("Alt-click move selected jetway to click position"); + print("Ctrl-click select a jetway near click position"); + print("Shift-click deselect selected jetway"); + print("Backspace delete selected jetway"); + print("*******************************************************"); + }; + +_setlistener("/nasal/jetways_edit/loaded", func + { + print("Animated jetway editor ... loaded"); + kbd_shift = props.globals.getNode("/devices/status/keyboard/shift"); + kbd_ctrl = props.globals.getNode("/devices/status/keyboard/ctrl"); + kbd_alt = props.globals.getNode("/devices/status/keyboard/alt"); + enabled = props.globals.getNode("/nasal/jetways_edit/enabled"); + + setlistener("/sim/jetways/adjust/model", func(n) + { + var v = n.getValue(); + if (selected_jetway != nil and v != selected_jetway.model) + { + adjust("model", v); + } + }, 0, 0); + setlistener("/sim/jetways/adjust/door", func(n) + { + var v = n.getValue(); + if (selected_jetway != nil and v != selected_jetway.door) + { + adjust("door", v); + } + }, 0, 0); + setlistener("/sim/jetways/adjust/airline", func(n) + { + var v = n.getValue(); + if (selected_jetway != nil and v != selected_jetway.airline) + { + adjust("airline", v); + } + }, 0, 0); + setlistener("/sim/jetways/adjust/gate", func(n) + { + var v = n.getValue(); + if (selected_jetway != nil and v != selected_jetway.gate) + { + adjust("gate", v); + } + }, 0, 0); + setlistener("/devices/status/keyboard/event", func(event) + { + if (!event.getNode("pressed").getValue()) return; + if (enabled.getBoolValue() and event.getNode("key").getValue() == 8) delete(); + }); + setlistener("/devices/status/mice/mouse/button[1]", func(n) mouse_mmb = n.getBoolValue(), 1, 0); + setlistener("/sim/signals/click", func if (!mouse_mmb and enabled.getBoolValue()) click(geo.click_position())); + }); diff --git a/gui/dialogs/jetways-adjust.xml b/gui/dialogs/jetways-adjust.xml index 692acc7b9..a76bb7cb9 100644 --- a/gui/dialogs/jetways-adjust.xml +++ b/gui/dialogs/jetways-adjust.xml @@ -18,7 +18,8 @@ { if (substr(file, -3) == "xml") { - append(models, substr(file, 0, size(file) - 4)); + var is_airline = string.match(file, "*.airline.*"); + if (!is_airline) append(models, substr(file, 0, size(file) - 4)); } } for (var i = 0; i < size(models); i += 1) diff --git a/gui/dialogs/jetways.xml b/gui/dialogs/jetways.xml index 8215178fa..43dbeb4fd 100644 --- a/gui/dialogs/jetways.xml +++ b/gui/dialogs/jetways.xml @@ -9,9 +9,8 @@ left - /sim/jetways/enabled + /nasal/jetways/enabled true dialog-apply @@ -69,7 +68,7 @@ /sim/jetways/interact-with-multiplay true - /sim/jetways/enabled + /nasal/jetways/enabled dialog-apply @@ -78,10 +77,10 @@ left - /sim/jetways/enable-editor + /nasal/jetways_edit/enabled true - /sim/jetways/enabled + /nasal/jetways/enabled dialog-apply @@ -93,7 +92,7 @@ /sim/jetways/debug true - /sim/jetways/enabled + /nasal/jetways/enabled dialog-apply @@ -106,15 +105,13 @@ Open editor - /sim/jetways/enabled - /sim/jetways/enable-editor + /nasal/jetways/enabled + /nasal/jetways_edit/enabled - nasal - + dialog-show + jetways-adjust @@ -124,7 +121,7 @@ left - /sim/gui/dialogs/jetways/dialog/loaded-airports + /sim/gui/dialogs/jetways/loaded-airports true diff --git a/gui/menubar.xml b/gui/menubar.xml index cdefe0ab4..80e96c8de 100644 --- a/gui/menubar.xml +++ b/gui/menubar.xml @@ -504,6 +504,17 @@ + + + + + dialog-show + jetways + + diff --git a/preferences.xml b/preferences.xml index cbc3ae3dc..795aed316 100644 --- a/preferences.xml +++ b/preferences.xml @@ -748,10 +748,9 @@ Started September 2000 by David Megginson, david@megginson.com - false - false - true - false + false + true + false @@ -1243,6 +1242,12 @@ Started September 2000 by David Megginson, david@megginson.com false + + false + + + false + From a6acba8a87fd925622cac8f061a9fa05af03bfe5 Mon Sep 17 00:00:00 2001 From: Ryan Miller Date: Thu, 16 Jun 2011 14:42:39 -0700 Subject: [PATCH 2/5] (Animated jetways) Move AI and MP connecting code to load loop, increase load loop frequency to 10 seconds, fix nil velocities in AI aircraft crashing the whole thing --- Nasal/jetways/jetways.nas | 111 +++++++++++++++++++------------------- 1 file changed, 56 insertions(+), 55 deletions(-) diff --git a/Nasal/jetways/jetways.nas b/Nasal/jetways/jetways.nas index 9d697f799..0db3f8e68 100644 --- a/Nasal/jetways/jetways.nas +++ b/Nasal/jetways/jetways.nas @@ -147,9 +147,9 @@ # unload_airport_jetways() - unloads jetways at an airport # - ICAO of airport # -# update_jetways() - interpolates model animation values and keeps the system tidy +# update_jetways() - interpolates model animation values # -# load_jetways() - loads new jetway models and unloads out-of-range models every 30 seconds +# load_jetways() - loads new jetway models and unloads out-of-range models every 10 seconds; also connects AI and MP aircraft # ## Utility functions @@ -285,11 +285,11 @@ var root = nil; var home = nil; var scenery = []; -var UPDATE_PERIOD = 1 / 30; -var LOAD_PERIOD = 30; +var UPDATE_PERIOD = 0; +var LOAD_PERIOD = 10; var LOAD_DISTANCE = 50; # in nautical miles var LOAD_JETWAY_PERIOD = 0.05; -var NUMBER_OF_JETWAYS = 2000; # approx max number of jetways loadable in FG +var NUMBER_OF_JETWAYS = 1000; # approx max number of jetways loadable in FG var runtime_files = NUMBER_OF_JETWAYS / LOAD_PERIOD * LOAD_JETWAY_PERIOD; runtime_files = int(runtime_files) == runtime_files ? runtime_files : int(runtime_files) + 1; var runtime_file = 0; @@ -459,10 +459,11 @@ var Jetway = model_tree.getNode("entrance-heading-deg").setValue(props.globals.initNode(node_path ~ "/jetway-position/entrance-heading-deg", 0, "DOUBLE").getPath()); model_tree.getNode("hood-deg").setValue(props.globals.initNode(node_path ~ "/jetway-position/hood-deg", 0, "DOUBLE").getPath()); # airline texture + var airline_tex = model_tree.getNode("airline-texture-path", 1).getValue(); var airline_node = model_tree.getNode(model_tree.getNode("airline-prop-path", 1).getValue()); - if (airline_node != nil) + if (airline_tex != nil and airline_node != nil) { - airline_node.setValue(get_relative_filepath(home ~ "/runtime-jetways", model_dir ~ "/Airlines/" ~ airline ~ ".png")); + airline_node.setValue(get_relative_filepath(home ~ "/runtime-jetways", model_dir ~ "/" ~ airline_tex)); } # write the model tree io.write_properties(runtime_file_path, model_tree); @@ -773,7 +774,7 @@ var restart = func() }, 2); print("Animated jetways ... initialized"); }; -# main update loop (runs when jetways are enabled) +# main update loop (runs when jetways are enable and actived) var update_jetways = func(loopid) { # terminate if loopid does not match @@ -789,53 +790,6 @@ var update_jetways = func(loopid) setsize(loaded_airports, 0); return; } - - var nearest_airport = airportinfo(); - nearest_airport = nearest_airport == nil ? nil : nearest_airport.id; - if (isin(loaded_airports, nearest_airport)) - { - # loop through the AI aircraft and extend/retract jetways - var ai_aircraft = props.globals.getNode("ai/models").getChildren("aircraft"); - foreach (var aircraft; ai_aircraft) - { - if (!aircraft.getNode("valid", 1).getBoolValue()) continue; - var connected = aircraft.getNode("connected-to-jetways", 1); - var velocity = aircraft.getNode("velocities/true-airspeed-kt").getValue(); - # TODO: Find a better way to know when the aircraft is "parked" - if (velocity > -1 and velocity < 1) - { - if (!connected.getBoolValue()) toggle_jetway_from_model(aircraft); - connected.setBoolValue(1); - } - else - { - if (connected.getBoolValue()) toggle_jetway_from_model(aircraft); - connected.setBoolValue(0); - } - } - # loop through the multiplayer aircraft and extend/retract jetways - # TODO: In the future, broadcast jetway properties over MP, making this part obselete - if (mp_switch.getBoolValue()) - { - var multiplayers = props.globals.getNode("ai/models").getChildren("multiplayer"); - foreach (var aircraft; multiplayers) - { - if (!aircraft.getNode("valid", 1).getBoolValue()) continue; - var connected = aircraft.getNode("connected-to-jetways", 1); - var velocity = aircraft.getNode("velocities/true-airspeed-kt").getValue(); - if (velocity > -1 and velocity < 1) - { - if (!connected.getBoolValue()) toggle_jetway_from_model(aircraft); - connected.setBoolValue(1); - } - else - { - if (connected.getBoolValue()) toggle_jetway_from_model(aircraft); - connected.setBoolValue(0); - } - } - } - } # interpolate jetway values for (var i = 0; i < size(jetways); i += 1) { @@ -873,6 +827,53 @@ var load_jetways = func(loopid) { load_airport_jetways(airport); } + + var nearest_airport = airportinfo(); + nearest_airport = nearest_airport == nil ? nil : nearest_airport.id; + if (isin(loaded_airports, nearest_airport)) + { + # loop through the AI aircraft and extend/retract jetways + var ai_aircraft = props.globals.getNode("ai/models").getChildren("aircraft"); + foreach (var aircraft; ai_aircraft) + { + if (!aircraft.getNode("valid", 1).getBoolValue()) continue; + var connected = aircraft.getNode("connected-to-jetways", 1); + var velocity = aircraft.getNode("velocities/true-airspeed-kt").getValue(); + # TODO: Find a better way to know when the aircraft is "parked" + if (velocity > -1 and velocity < 1) + { + if (!connected.getBoolValue()) toggle_jetway_from_model(aircraft); + connected.setBoolValue(1); + } + else + { + if (connected.getBoolValue()) toggle_jetway_from_model(aircraft); + connected.setBoolValue(0); + } + } + # loop through the multiplayer aircraft and extend/retract jetways + # TODO: In the future, broadcast jetway properties over MP, making this part obselete + if (mp_switch.getBoolValue()) + { + var multiplayers = props.globals.getNode("ai/models").getChildren("multiplayer"); + foreach (var aircraft; multiplayers) + { + if (!aircraft.getNode("valid", 1).getBoolValue()) continue; + var connected = aircraft.getNode("connected-to-jetways", 1); + var velocity = aircraft.getNode("velocities/true-airspeed-kt", 1).getValue(); + if (velocity != nil and velocity > -1 and velocity < 1) + { + if (!connected.getBoolValue()) toggle_jetway_from_model(aircraft); + connected.setBoolValue(1); + } + else + { + if (connected.getBoolValue()) toggle_jetway_from_model(aircraft); + connected.setBoolValue(0); + } + } + } + } settimer(func load_jetways(loopid), LOAD_PERIOD); }; ## fire it up From 2cb19bc34eef84f80639d67c4bf6c0b43d3ef55a Mon Sep 17 00:00:00 2001 From: Ryan Miller Date: Thu, 16 Jun 2011 14:46:29 -0700 Subject: [PATCH 3/5] (Animated jetways) Fix nil velocities in AI aircraft crashing the whole thing (previous commit fixed MP aircraft, not AI) --- Nasal/jetways/jetways.nas | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Nasal/jetways/jetways.nas b/Nasal/jetways/jetways.nas index 0db3f8e68..7497390fe 100644 --- a/Nasal/jetways/jetways.nas +++ b/Nasal/jetways/jetways.nas @@ -838,9 +838,9 @@ var load_jetways = func(loopid) { if (!aircraft.getNode("valid", 1).getBoolValue()) continue; var connected = aircraft.getNode("connected-to-jetways", 1); - var velocity = aircraft.getNode("velocities/true-airspeed-kt").getValue(); + var velocity = aircraft.getNode("velocities/true-airspeed-kt", 1).getValue(); # TODO: Find a better way to know when the aircraft is "parked" - if (velocity > -1 and velocity < 1) + if (velocity != nil and velocity > -1 and velocity < 1) { if (!connected.getBoolValue()) toggle_jetway_from_model(aircraft); connected.setBoolValue(1); From 887b9704b564f34aaa3eb4c2ae629034f7b7b75f Mon Sep 17 00:00:00 2001 From: Ryan Miller Date: Thu, 16 Jun 2011 15:24:31 -0700 Subject: [PATCH 4/5] (Animated jetways) Fix dialog "can't find object" Nasal errors --- gui/dialogs/jetways.xml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/gui/dialogs/jetways.xml b/gui/dialogs/jetways.xml index 43dbeb4fd..83ba471b6 100644 --- a/gui/dialogs/jetways.xml +++ b/gui/dialogs/jetways.xml @@ -13,12 +13,15 @@ var UPDATE_PERIOD = 5; var update = func { - var list = ""; - foreach (var apt; jetways.loaded_airports) + if (jetways != nil) { + var list = ""; + foreach (var apt; jetways.loaded_airports) + { list ~= apt ~ " "; + } + aptlist.setValue(list == "" ? "No airports loaded" : "Loaded airports: " ~ list); } - aptlist.setValue(list == "" ? "No airports loaded" : "Loaded airports: " ~ list); settimer(update, UPDATE_PERIOD); }; settimer(update, 0); From 3e027a45bee8bb2e1a88e59bfdf558df819d6c9f Mon Sep 17 00:00:00 2001 From: James Turner Date: Thu, 16 Jun 2011 23:33:06 +0100 Subject: [PATCH 5/5] Initial work on multiplayer config dialog. --- gui/dialogs/multiplayer.xml | 116 ++++++++++++++++++++++++++++++++++++ gui/menubar.xml | 8 +++ 2 files changed, 124 insertions(+) create mode 100644 gui/dialogs/multiplayer.xml diff --git a/gui/dialogs/multiplayer.xml b/gui/dialogs/multiplayer.xml new file mode 100644 index 000000000..713336ada --- /dev/null +++ b/gui/dialogs/multiplayer.xml @@ -0,0 +1,116 @@ + + + + + multiplayer + vbox + true + + + + + + + + + + + + hbox + 1 + + + + + + 1 + + + + + + + table + center + + 00 + right + + + + 01 + /sim/multiplay/callsign + + + 10 + right + + + + host + 11 + 120 + /sim/multiplay/txhost + true + /sim/multiplay/servers + + + 12 + /sim/multiplay/txport + + + + + + /sim/multiplay/online + + 2 + 1 + + + + + + /sim/multiplay/online + + 2 + 1 + + + Connected to %s + /sim/multiplay/txhost + true + + + + + hbox + 10 + true + + + + + \ No newline at end of file diff --git a/gui/menubar.xml b/gui/menubar.xml index cdefe0ab4..9af71b182 100644 --- a/gui/menubar.xml +++ b/gui/menubar.xml @@ -512,6 +512,14 @@ multiplayer + + + + dialog-show + multiplayer + + +