1 ###############################################################################
3 ## Animated Jetway System. Spawns and manages interactive jetway models.
5 ## Copyright (C) 2011 Ryan Miller
6 ## This file is licensed under the GPL license version 2 or later.
8 ###############################################################################
10 ###############################################################################
11 # (See http://wiki.flightgear.org/Howto:_Animated_jetways)
13 # Special jetway definition files located in $FG_ROOT/Airports/Jetways/XXXX.xml
14 # for each airport are loaded when the user's aircraft is within 50 nm of the
15 # airport. The script dynamically generates runtime model files, writes them to
16 # $FG_ROOT/Models/Airport/Jetway/runtimeX.xml, and places them into the
17 # simulator using the model manager.
19 # Different jetway models can be defined and are placed under
20 # $FG_ROOT/Models/Airport/Jetway/XXX.xml.
22 # Jetways can be extended/retracted independently either by user operation or
23 # by automatic extension for AI models and multiplayer aircraft.
28 # print_debug(<message>) - prints debug messages
29 # <message> - message to print
31 # print_error(<messsage>) - prints error messages
32 # <message> - error to print
34 # alert(<message>) - displays an alert message in-sim
35 # <message> - the message
37 # normdeg(<angle>) - normalizes angle measures between -180° and 180°
38 # <angle> - angle to normalize
40 # remove(<vector>, <item>) - removes an element from a vector
44 # isin(<vector>, <item>) - checks if an item exists in a vector
48 # putmodel(<path>, <lat>, <lon>, <alt>, <hdg>) - add a model to the scene graph (unlike geo.put_model(), models added with this function can be adjusted)
52 # <alt> - altitude in m
55 # interpolate_table(<table>, <value>) - interpolates a value within a table
56 # <table> - interpolation table/vector, in the format of [[<ind>, <dep>], [<ind>, <dep>], ... ]
59 # get_relative_filepath(<path>, <target>) - gets a relative file path from a directory
60 # <path> - directory path should be relative to
61 # <target> - target directory
63 # find_airports(<max dist>) - gets a list of nearest airports
64 # <max dist> - maximum search distance in nm (currently unused)
69 # Jetway. - creates a new jetway object/model
70 # new(<airport>, <model>, <gate>, <door>,
71 # <airline>, <lat>, <lon>, <alt>,
72 # <heading>, [, <init_extend>]
73 # [, <init_heading>] [, <init_pitch>]
74 # [, <init_ent_heading>])
75 # <airport> - ICAO of associated airport
76 # <model> - jetway model definition (i.e. Models/Airport/Jetway/generic.xml)
77 # <gate> - gate number (i.e. "A1")
78 # <door> - door number (i.e. 0)
79 # <airline> - airline code (i.e. "AAL")
80 # <lat> - latitude location of model
81 # <lon> - longitude location of model
82 # <alt> - elevation of model in m
83 # <heading> - (optional) heading of model
84 # <init_extend> - (optional) initial extension of tunnel in m
85 # <init_heading> - (optional) initial rotation of tunnel along the Z axis
86 # <init_pitch> - (optional) initial pitch of tunnel (rotation along Y axis)
87 # <init_ent_heading> - (optional) initial rotation of entrance along the Z axis
89 # toggle(<user>, <heading>, <coord> - extends/retracts a jetway
91 # <user> - whether or not jetway is toggled by user command (0/1)
92 # <heading> - heading of aircraft to connect to
93 # <coord> - a geo.Coord of the target aircraft's door
94 # <hood> - (optional) amount to rotate jetway hood (only required when <user> != 1)
96 # extend(<user>, <heading>, <coord> - extends a jetway (should be called by Jetway.toggle())
98 # <user> - whether or not jetway is toggled by user command (0/1)
99 # <heading> - heading of aircraft to connect to
100 # <coord> - a geo.Coord of the target aircraft's door
101 # <hood> - (optional) amount to rotate jetway hood (only required when <user> != 1)
103 # retract(<user>) - retracts a jetway (should be called by Jetway.toggle())
104 # <user> - whether or not a jetway is toggled by user command (0/1)
106 # remove() - removes a jetway object and its model
108 # reload() - reloads a jetway object and its model
110 # setpos(<lat>, <lon>, <heading>, <alt>) - moves a jetway to a new location
111 # <lat> - new latitude
112 # <lon> - new longitude
113 # <heading> - new heading
114 # <alt> - new altitude in m
116 # setmodel(<model>, <airline>, <gate>) - changes the jetway model
117 # <model> - new model
118 # <airline> - new airline sign code
119 # <gate> - new gate number
121 # INTERACTION FUNCTIONS
122 # ---------------------
124 # dialog() - open settings dialog
126 # toggle_jetway(<id>) - toggles a jetway by user command (should be called by a pick animation in a jetway model)
127 # <id> - id number of jetway to toggle
129 # toggle_jetway_from_coord(<door>, <hood>, - toggles a jetway with the target door at the specified coordinates
132 # <door> - door number (i.e. 0)
133 # <hood> - amount to rotate jetway hood
134 # <lat> - (required or <coord>) latitude location of door
135 # <lon> - (required or <coord>) longitude location of door
136 # <coord> - (required or <lat>, <lon>) a geo.Coord of the door
138 # toggle_jetway_from_model(<model node>) - toggles a jetway using an AI model instead of the user's aircraft
139 # <model node> - path of AI model (i.e. /ai/models/aircraft[0])- can be the path in a string or a props.Node
144 # load_airport_jetways(<airport>) - loads jetways at an airport
145 # <airport> - ICAO of airport
147 # unload_airport_jetways(<airport>) - unloads jetways at an airport
148 # <airport> - ICAO of airport
150 # update_jetways() - interpolates model animation values
152 # load_jetways() - loads new jetway models and unloads out-of-range models every 10 seconds; also connects AI and MP aircraft
158 # prints debug messages
159 var print_debug = func(msg)
161 if (debug_switch.getBoolValue())
166 # prints error messages
167 var print_error = func(msg)
169 print("\x1b[31m" ~ msg ~ "\x1b[m");
172 var alert = func(msg)
174 setprop("/sim/messages/ground", msg);
176 # normalizes headings between -180 and 180
177 var normdeg = func(x)
189 # deletes an item in a vector
190 var remove = func(vector, item)
192 var s = size(vector);
194 for (var i = 0; i < s; i += 1)
198 vector[i - 1] = vector[i];
200 elsif (vector[i] == item)
205 if (found) setsize(vector, s - 1);
208 # checks if an item is in a vector
209 var isin = func(vector, v)
211 foreach (var item; vector)
213 if (item == v) return 1;
218 var putmodel = func(path, lat, lon, alt, hdg)
220 var models = props.globals.getNode("/models");
222 for (var i = 0; 1; i += 1)
224 if (models.getChild("model", i, 0) == nil)
226 model = models.getChild("model", i, 1);
230 var model_path = model.getPath();
231 model.getNode("path", 1).setValue(path);
232 model.getNode("latitude-deg", 1).setDoubleValue(lat);
233 model.getNode("latitude-deg-prop", 1).setValue(model_path ~ "/latitude-deg");
234 model.getNode("longitude-deg", 1).setDoubleValue(lon);
235 model.getNode("longitude-deg-prop", 1).setValue(model_path ~ "/longitude-deg");
236 model.getNode("elevation-ft", 1).setDoubleValue(alt * M2FT);
237 model.getNode("elevation-ft-prop", 1).setValue(model_path ~ "/elevation-ft");
238 model.getNode("heading-deg", 1).setDoubleValue(hdg);
239 model.getNode("heading-deg-prop", 1).setValue(model_path ~ "/heading-deg");
240 model.getNode("pitch-deg", 1).setDoubleValue(0);
241 model.getNode("pitch-deg-prop", 1).setValue(model_path ~ "/pitch-deg");
242 model.getNode("roll-deg", 1).setDoubleValue(0);
243 model.getNode("roll-deg-prop", 1).setValue(model_path ~ "/roll-deg");
244 model.getNode("load", 1).remove();
248 # interpolates a value
249 var interpolate_table = func(table, v)
254 if (v >= table[i][0])
256 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];
261 # gets a relative file path
262 var get_relative_filepath = func(path, target)
265 for (var i = size(path) - 1; i >= 0; i -= 1)
267 var char = substr(path, i, 1);
268 if (char == "/") newpath ~= "../";
270 # we can just append the target path for UNIX systems, but we need to remove the drive letter prefix for DOS systems
271 return newpath ~ (string.match(substr(target, 0, 3), "?:/") ? substr(target, 2, size(target) - 2) : target);
273 # gets a list of nearest airports
274 # TODO: Don't use /sim/airport/nearest-airport-id, which restricts the list to 1 airport
275 var find_airports = func(max_distance)
277 var apt = getprop("/sim/airport/closest-airport-id");
278 return apt == "" ? nil : [apt];
288 var UPDATE_PERIOD = 0;
289 var LOAD_PERIOD = 10;
290 var LOAD_DISTANCE = 50; # in nautical miles
291 var LOAD_JETWAY_PERIOD = 0.05;
292 var NUMBER_OF_JETWAYS = 1000; # approx max number of jetways loadable in FG
293 var runtime_files = NUMBER_OF_JETWAYS / LOAD_PERIOD * LOAD_JETWAY_PERIOD;
294 runtime_files = int(runtime_files) == runtime_files ? runtime_files : int(runtime_files) + 1;
295 var runtime_file = 0;
296 var update_loopid = -1;
297 var load_loopid = -1;
298 var load_listenerid = nil;
300 var dialog_object = nil;
301 var loaded_airports = [];
306 var debug_switch = nil;
308 var jetway_id_prop = "/sim/jetways/last-loaded-jetway";
310 # interpolation tables
311 var extend_rate = 0.5;
326 var heading_rate = 1;
327 var heading_table = [
334 var heading_entrance_rate = 5;
335 var heading_entrance_table = [
355 new: func(airport, model, gate, door, airline, lat, lon, alt, heading, init_extend = 0, init_heading = 0, init_pitch = 0, init_ent_heading = 0)
358 for (var i = 0; 1; i += 1)
360 if (i == size(jetways))
362 setsize(jetways, i + 1);
366 elsif (jetways[i] == nil)
371 # locate the jetway model directory and load the model tree
372 var model_tree = nil;
375 var airline_file = "";
376 # search in scenery directories
377 foreach (var scenery_path; scenery)
379 model_dir = scenery_path ~ "/Models/Airport/Jetway";
380 model_file = model_dir ~ "/" ~ model ~ ".xml";
381 airline_file = model_dir ~ "/" ~ model ~ ".airline." ~ airline ~ ".xml";
382 print_debug("Trying to load a jetway model from " ~ model_file);
383 if (io.stat(model_file) == nil) continue;
384 model_tree = io.read_properties(model_file);
385 if (io.stat(airline_file) != nil) props.copy(io.read_properties(airline_file), model_tree);
388 if (model_tree == nil)
390 model_dir = root ~ "/Models/Airport/Jetway";
391 model_file = model_dir ~ "/" ~ model ~ ".xml";
392 airline_file = model_dir ~ "/" ~ model ~ ".airline." ~ airline ~ ".xml";
393 print_debug("Falling back to " ~ model_file);
394 if (io.stat(model_file) == nil)
396 print_error("Failed to load jetway model: " ~ model);
399 model_tree = io.read_properties(model_file);
400 if (io.stat(airline_file) != nil) props.copy(io.read_properties(airline_file), model_tree);
407 m._active = 1; # set this to 'true' on the first run so that the offsets can take effect
419 m.heading = geo.normdeg(180 - heading);
420 m.init_extend = init_extend;
421 m.init_heading = init_heading;
422 m.init_pitch = init_pitch;
423 m.init_ent_heading = init_ent_heading;
426 m.target_heading = 0;
427 m.target_ent_heading = 0;
429 m.rotunda_x = model_tree.getNode("rotunda/x-m").getValue();
430 m.rotunda_y = model_tree.getNode("rotunda/y-m").getValue();
431 m.rotunda_z = model_tree.getNode("rotunda/z-m").getValue();
432 m.offset_extend = model_tree.getNode("extend-offset-m").getValue();
433 m.offset_entrance = model_tree.getNode("entrance-offset-m").getValue();
434 m.min_extend = model_tree.getNode("min-extend-m").getValue();
435 m.max_extend = model_tree.getNode("max-extend-m").getValue();
437 # get the runtime file path
438 if (runtime_file == runtime_files)
442 var runtime_file_path = home ~ "/runtime-jetways/" ~ runtime_file ~ ".xml";
445 # create the model node and the door object
446 m.node = putmodel(runtime_file_path, lat, lon, alt, geo.normdeg(360 - heading));
447 var node_path = m.node.getPath();
448 m.door_object = aircraft.door.new(node_path ~ "/jetway-position", 0);
450 # manipulate the model tree
451 model_tree.getNode("path").setValue(model_dir ~ "/" ~ model_tree.getNode("path").getValue());
452 model_tree.getNode("toggle-action-script").setValue("jetways.toggle_jetway(" ~ id ~ ");");
453 model_tree.getNode("gate").setValue(m.gate);
454 model_tree.getNode("extend-m").setValue(props.globals.initNode(node_path ~ "/jetway-position/extend-m", 0, "DOUBLE").getPath());
455 model_tree.getNode("pitch-deg").setValue(props.globals.initNode(node_path ~ "/jetway-position/pitch-deg", 0, "DOUBLE").getPath());
456 model_tree.getNode("heading-deg").setValue(props.globals.initNode(node_path ~ "/jetway-position/heading-deg", 0, "DOUBLE").getPath());
457 model_tree.getNode("entrance-heading-deg").setValue(props.globals.initNode(node_path ~ "/jetway-position/entrance-heading-deg", 0, "DOUBLE").getPath());
458 model_tree.getNode("hood-deg").setValue(props.globals.initNode(node_path ~ "/jetway-position/hood-deg", 0, "DOUBLE").getPath());
460 var airline_tex = model_tree.getNode("airline-texture-path", 1).getValue();
461 var airline_node = model_tree.getNode(model_tree.getNode("airline-prop-path", 1).getValue());
462 if (airline_tex != nil and airline_node != nil)
464 airline_node.setValue(get_relative_filepath(home ~ "/runtime-jetways", model_dir ~ "/" ~ airline_tex));
466 # write the model tree
467 io.write_properties(runtime_file_path, model_tree);
470 print_debug("Loaded jetway #" ~ id);
471 jetway_id_prop.setValue(id);
474 toggle: func(user, heading, coord, hood = 0)
479 me.retract(user, heading, coord);
483 me.extend(user, heading, coord, hood);
486 extend: func(user, heading, door_coord, hood = 0)
490 # get the coordinates of the jetway and offset for the rotunda position
491 var jetway_coord = geo.Coord.new();
492 jetway_coord.set_latlon(me.lat, me.lon);
493 jetway_coord.apply_course_distance(me.heading, me.rotunda_x);
494 jetway_coord.apply_course_distance(me.heading - 90, me.rotunda_y);
495 jetway_coord.set_alt(me.alt + me.rotunda_z);
497 if (debug_switch.getBoolValue())
499 # place UFO cursors at the calculated door and jetway positions for debugging purposes
500 geo.put_model("Aircraft/ufo/Models/cursor.ac", door_coord);
501 geo.put_model("Aircraft/ufo/Models/cursor.ac", jetway_coord);
504 # offset the door for the length of the jetway entrance
505 door_coord.apply_course_distance(heading - 90, me.offset_entrance);
507 # calculate the bearing to the aircraft and the distance from the door
508 me.target_heading = normdeg(jetway_coord.course_to(door_coord) - me.heading - me.init_heading);
509 me.target_extend = jetway_coord.distance_to(door_coord) - me.offset_extend - me.init_extend;
511 # check if distance exceeds maximum jetway extension length
512 if (me.target_extend + me.init_extend > me.max_extend)
515 me.target_extend = 0;
516 me.target_heading = 0;
517 if (user) alert("Your aircraft is too far from this jetway.");
518 print_debug("Jetway #" ~ me.id ~ " is too far from the door");
521 # check if distance fails to meet minimum jetway extension length
522 if (me.target_extend + me.init_extend < me.min_extend)
525 me.target_extend = 0;
526 me.target_heading = 0;
527 if (user) alert("Your aircraft is too close to this jetway.");
528 print_debug("Jetway #" ~ me.id ~ " is too close to the door");
532 # calculate the jetway pitch, entrance heading, and hood
533 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;
534 me.target_ent_heading = normdeg((heading + 90) - (me.heading + (me.target_heading + me.init_heading) + me.init_ent_heading));
535 me.target_hood = user ? getprop("/sim/model/door[" ~ me.door ~ "]/jetway-hood-deg") : hood;
537 # fire up the animation
538 if (user) alert("Extending jetway.");
539 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);
540 me.door_object.swingtime = animation_time;
541 me.door_object.open();
543 print_debug("************************************************");
544 print_debug("Activated jetway #" ~ me.id);
545 print_debug("Using door #" ~ me.door);
546 print_debug("Jetway heading: " ~ me.heading ~ " deg");
547 print_debug("Extension: " ~ me.target_extend ~ " m");
548 print_debug("Pitch: " ~ me.target_pitch ~ " deg");
549 print_debug("Heading: " ~ me.target_heading ~ " deg");
550 print_debug("Entrance heading: " ~ me.target_ent_heading ~ " deg");
551 print_debug("Hood: " ~ me.target_hood ~ " deg");
552 print_debug("Total animation time: " ~ animation_time ~ " sec");
553 print_debug("Jetway extending");
554 print_debug("************************************************");
558 if (user) alert("Retracting jetway.");
559 me.door_object.close();
562 print_debug("************************************************");
563 print_debug("Activated jetway #" ~ me.id);
564 print_debug("Total animation time: " ~ me.door_object.swingtime ~ " sec");
565 print_debug("Jetway retracting");
566 print_debug("************************************************");
572 jetways[me.id] = nil;
573 print_debug("Unloaded jetway #" ~ id);
577 var airport = me.airport;
578 var model = me.model;
581 var airline = me.airline;
585 var heading = geo.normdeg(180 - (me.heading - 360));
586 var init_extend = me.init_extend;
587 var init_heading = me.init_heading;
588 var init_pitch = me.init_pitch;
589 var init_ent_heading = me.init_ent_heading;
591 Jetway.new(airport, model, gate, door, airline, lat, lon, alt, heading, init_extend, init_heading, init_pitch, init_ent_heading);
593 setpos: func(lat, lon, hdg, alt)
595 me.node.getNode("latitude-deg").setValue(lat);
597 me.node.getNode("longitude-deg").setValue(lon);
599 me.node.getNode("heading-deg").setValue(geo.normdeg(hdg - 180));
601 me.node.getNode("elevation-ft").setValue(alt * M2FT);
604 setmodel: func(model, airline, gate)
606 me.airline = airline;
610 me.target_extend = 0;
612 me.target_heading = 0;
613 me.target_ent_heading = 0;
615 me.door_object.setpos(0);
620 ## Interaction functions
621 ########################
625 if (dialog_object == nil) dialog_object = gui.Dialog.new("/sim/gui/dialogs/jetways/dialog", "gui/dialogs/jetways.xml");
626 dialog_object.open();
629 var toggle_jetway = func(n)
631 var jetway = jetways[n];
632 if (jetway == nil) return;
633 var door = props.globals.getNode("/sim/model/door[" ~ jetway.door ~ "]");
636 alert("Your aircraft does not define the location of door " ~ (jetway.door + 1) ~ ", cannot extend this jetway.");
640 # get the coordinates of the user's aircraft and offset for the door position and aircraft pitch
641 var coord = geo.aircraft_position();
642 var heading = getprop("/orientation/heading-deg");
643 var pitch = getprop("/orientation/pitch-deg");
644 coord.apply_course_distance(heading, -door.getChild("position-x-m").getValue());
645 coord.apply_course_distance(heading + 90, door.getChild("position-y-m").getValue());
646 coord.set_alt(coord.alt() + door.getChild("position-z-m").getValue());
647 coord.set_alt(coord.alt() + math.tan(pitch * D2R) * -door.getChild("position-x-m").getValue());
649 jetway.toggle(1, heading, coord);
651 var toggle_jetway_from_coord = func(door, hood, heading, lat, lon = nil)
653 if (isa(lat, geo.Coord))
659 var coord = geo.Coord.new();
660 coord.set_latlon(lat, lon);
662 var closest_jetway = nil;
663 var closest_jetway_dist = nil;
664 var closest_jetway_coord = nil;
665 foreach (var jetway; jetways)
667 if (jetway == nil) continue;
668 var jetway_coord = geo.Coord.new();
669 jetway_coord.set_latlon(jetway.lat, jetway.lon);
671 var distance = jetway_coord.distance_to(coord);
672 if ((closest_jetway_dist == nil or distance < closest_jetway_dist) and jetway.door == door)
674 closest_jetway = jetway;
675 closest_jetway_dist = distance;
676 closest_jetway_coord = jetway_coord;
679 if (closest_jetway == nil)
681 print_debug("No jetways available");
683 elsif (!closest_jetway.extended)
685 closest_jetway.toggle(0, heading, coord, hood);
688 var toggle_jetway_from_model = func(model)
690 model = aircraft.makeNode(model);
691 var doors = model.getChildren("door");
692 if (doors == nil or size(doors) == 0) return;
693 for (var i = 0; i < size(doors); i += 1)
695 var coord = geo.Coord.new();
696 var hdg = model.getNode("orientation/true-heading-deg").getValue();
697 var lat = model.getNode("position/latitude-deg").getValue();
698 var lon = model.getNode("position/longitude-deg").getValue();
699 var alt = model.getNode("position/altitude-ft").getValue() * FT2M + doors[i].getNode("position-z-m").getValue();
700 coord.set_latlon(lat, lon, alt);
701 coord.apply_course_distance(hdg, -doors[i].getNode("position-x-m").getValue());
702 coord.apply_course_distance(hdg + 90, doors[i].getNode("position-y-m").getValue());
703 print_debug("Connecting a jetway to door #" ~ i ~ " for model " ~ model.getPath());
704 toggle_jetway_from_coord(i, doors[i].getNode("jetway-hood-deg").getValue(), hdg, coord);
708 ## Internal functions
709 #####################
711 # loads jetways at an airport
712 var load_airport_jetways = func(airport)
714 if (isin(loaded_airports, airport)) return;
715 var tree = io.read_airport_properties(airport, "jetways");
718 tree = io.read_properties(root ~ "/AI/Airports/" ~ airport ~ "/jetways.xml");
719 if (tree == nil) return;
721 append(loaded_airports, airport);
722 print_debug("Loading jetways for airport " ~ airport);
723 var nodes = tree.getChildren("jetway");
725 loadids[airport] = loadids[airport] == nil ? 0 : loadids[airport] + 1;
729 if (id != loadids[airport]) return;
730 if (i >= size(nodes)) return;
731 var jetway = nodes[i];
732 var model = jetway.getNode("model", 1).getValue() or return;
733 var gate = jetway.getNode("gate", 1).getValue() or "";
734 var door = jetway.getNode("door", 1).getValue() or 0;
735 var airline = jetway.getNode("airline", 1).getValue() or "None";
736 var lat = jetway.getNode("latitude-deg", 1).getValue() or return;
737 var lon = jetway.getNode("longitude-deg", 1).getValue() or return;
738 var elev = jetway.getNode("elevation-m", 1).getValue() or 0;
739 var alt = geo.elevation(lat, lon) + elev;
740 var heading = jetway.getNode("heading-deg", 1).getValue() or 0;
741 var init_extend = jetway.getNode("initial-position/jetway-extension-m", 1).getValue() or 0;
742 var init_heading = jetway.getNode("initial-position/jetway-heading-deg", 1).getValue() or 0;
743 var init_pitch = jetway.getNode("initial-position/jetway-pitch-deg", 1).getValue() or 0;
744 var init_ent_heading = jetway.getNode("initial-position/entrance-heading-deg", 1).getValue() or 0;
745 Jetway.new(airport, model, gate, door, airline, lat, lon, alt, heading, init_extend, init_heading, init_pitch, init_ent_heading);
748 settimer(func loop(id), LOAD_JETWAY_PERIOD);
750 settimer(func loop(loadids[airport]), 0);
752 # unloads jetways at an airport
753 var unload_airport_jetways = func(airport)
755 print_debug("Unloading jetways for airport " ~ airport);
756 foreach (var jetway; jetways)
758 if (jetway != nil and jetway.airport == airport) jetway.remove();
760 remove(loaded_airports, airport);
763 # restarts the main update loop
767 update_jetways(update_loopid);
771 load_jetways(load_loopid);
773 print("Animated jetways ... initialized");
775 # main update loop (runs when jetways are enable and actived)
776 var update_jetways = func(loopid)
778 # terminate if loopid does not match
779 if (loopid != update_loopid) return;
780 # if jetways disabled, unload jetways and terminate
781 if (!on_switch.getBoolValue())
783 foreach (var jetway; jetways)
785 if (jetway != nil) jetway.remove();
788 setsize(loaded_airports, 0);
791 # interpolate jetway values
792 foreach (var jetway; jetways)
794 if (jetway == nil) continue;
795 if (jetway._active or jetway._edit)
797 var position = jetway.door_object.getpos();
798 if (position == 0 or position == 1) jetway._active = 0;
799 jetway.node.getNode("jetway-position/extend-m").setValue(interpolate_table(extend_table, position) * jetway.target_extend + jetway.init_extend);
800 jetway.node.getNode("jetway-position/pitch-deg").setValue(interpolate_table(pitch_table, position) * jetway.target_pitch + jetway.init_pitch);
801 jetway.node.getNode("jetway-position/heading-deg").setValue(interpolate_table(heading_table, position) * jetway.target_heading + jetway.init_heading);
802 jetway.node.getNode("jetway-position/entrance-heading-deg").setValue(interpolate_table(heading_entrance_table, position) * jetway.target_ent_heading + jetway.init_ent_heading);
803 jetway.node.getNode("jetway-position/hood-deg").setValue(interpolate_table(hood_table, position) * jetway.target_hood);
806 settimer(func update_jetways(loopid), UPDATE_PERIOD);
808 # loading/unloading loop (runs continuously)
809 var load_jetways = func(loopid)
811 if (load_listenerid != nil) removelistener(load_listenerid);
812 # terminate if loopid does not match
813 # unloading jetways if jetways are disabled is handled by update loop
814 if (loopid != load_loopid or !on_switch.getBoolValue()) return;
815 var airports = find_airports(LOAD_DISTANCE);
816 if (airports == nil) return;
817 # search for any airports out of range and unload their jetways
818 foreach (var airport; loaded_airports)
820 if (!isin(airports, airport))
822 unload_airport_jetways(airport);
825 # load any airports in range
826 foreach (var airport; airports)
828 load_airport_jetways(airport);
831 var nearest_airport = airportinfo();
832 nearest_airport = nearest_airport == nil ? nil : nearest_airport.id;
833 if (isin(loaded_airports, nearest_airport))
835 # loop through the AI aircraft and extend/retract jetways
836 var ai_aircraft = props.globals.getNode("ai/models").getChildren("aircraft");
837 foreach (var aircraft; ai_aircraft)
839 if (!aircraft.getNode("valid", 1).getBoolValue()) continue;
840 var connected = aircraft.getNode("connected-to-jetways", 1);
841 var velocity = aircraft.getNode("velocities/true-airspeed-kt", 1).getValue();
842 # TODO: Find a better way to know when the aircraft is "parked"
843 if (velocity != nil and velocity > -1 and velocity < 1)
845 if (!connected.getBoolValue()) toggle_jetway_from_model(aircraft);
846 connected.setBoolValue(1);
850 if (connected.getBoolValue()) toggle_jetway_from_model(aircraft);
851 connected.setBoolValue(0);
854 # loop through the multiplayer aircraft and extend/retract jetways
855 # TODO: In the future, broadcast jetway properties over MP, making this part obselete
856 if (mp_switch.getBoolValue())
858 var multiplayers = props.globals.getNode("ai/models").getChildren("multiplayer");
859 foreach (var aircraft; multiplayers)
861 if (!aircraft.getNode("valid", 1).getBoolValue()) continue;
862 var connected = aircraft.getNode("connected-to-jetways", 1);
863 var velocity = aircraft.getNode("velocities/true-airspeed-kt", 1).getValue();
864 if (velocity != nil and velocity > -1 and velocity < 1)
866 if (!connected.getBoolValue()) toggle_jetway_from_model(aircraft);
867 connected.setBoolValue(1);
871 if (connected.getBoolValue()) toggle_jetway_from_model(aircraft);
872 connected.setBoolValue(0);
877 settimer(func load_jetways(loopid), LOAD_PERIOD);
880 _setlistener("/nasal/jetways/loaded", func
883 root = string.normpath(getprop("/sim/fg-root"));
884 home = string.normpath(getprop("/sim/fg-home"));
885 foreach (var scenery_path; props.globals.getNode("/sim").getChildren("fg-scenery"))
887 append(scenery, string.normpath(scenery_path.getValue()));
889 if (size(scenery) == 0) append(scenery, root ~ "/Scenery");
892 on_switch = props.globals.getNode("/nasal/jetways/enabled", 1);
893 debug_switch = props.globals.getNode("/sim/jetways/debug", 1);
894 mp_switch = props.globals.getNode("/sim/jetways/interact-with-multiplay", 1);
896 jetway_id_prop = props.globals.getNode(jetway_id_prop, 1);