Indicate these models want to use their own terrein handling code, until they switch...
[fg:toms-fgdata.git] / Aircraft / Short_Empire / Systems / mooring.nas
1 ###############################################################################
2 ##
3 ## Short S.23 'C'-class Empire flying boat
4 ##
5 ##  Copyright (C) 2010 - 2012  Anders Gidenstam  (anders(at)gidenstam.org)
6 ##  This file is licensed under the GPL license v2 or later.
7 ##
8 ###############################################################################
9
10 # Do terrain modelling ourselves.
11 setprop("sim/fdm/surface/override-level", 1);
12
13
14 ###########################################################################
15 ## Initialization and reset.
16
17 var init = func(reinit=0) {
18     mooring.init(reinit);
19     if (getprop("/sim/presets/onground")) {
20         settimer(func {
21             if (!mooring.pick_up_mooring()) {
22                 # No mooring available, set up a local mooring.
23                 mooring.add_fixed_mooring(geo.aircraft_position(), 0.0);
24             }
25             # We need the FDM to run in between.
26             settimer(func {
27                 mooring.pick_up_mooring();
28             }, 0.5);
29         }, 0.4);
30     }
31
32     if (!reinit) {
33         # Add the predefined moorings.
34         foreach (var m; EAMS_MOORINGS_EUROPE ~
35                  EAMS_MOORINGS_EAST ~
36                  EAMS_MOORINGS_SOUTH ~
37                  NORTH_ATLANTIC ~
38                  TEAL) {
39             var pos = geo.Coord.new().set_latlon(m[1], m[2]);
40             mooring.add_fixed_mooring(pos, 0.0, m[0]);
41         }
42
43
44         # Enable Alt+Click to place the mooring
45         setlistener("/sim/signals/click", func {
46             var click_pos = geo.click_position();
47             if (__kbd.alt.getBoolValue()) {
48                 mooring.add_fixed_mooring(click_pos, 0.0);
49             }
50         });
51     }
52 }
53
54 var _mooring_initialized = 0;
55 setlistener("/sim/signals/fdm-initialized", func {
56     init(_mooring_initialized);
57     _mooring_initialized = 1;
58 });
59
60 ###########################################################################
61 ## Mooring location support.
62 var mooring = {
63     ##################################################
64     init : func(reinit) {
65         me.UPDATE_INTERVAL = 0.0;
66         me.MP_ANNOUNCE_INTERVAL = 60.0;
67         me.loopid = 0;
68         me.last_mp_announce = systime(); 
69         ## Hash containing all supported mooring locations.
70         ## Format:
71         ##   Fixed {position : <coord>, alt_offset : <m>}
72         ##   AI    {base : <node>, alt_offset : <m>}
73         if (!reinit) {
74             me.moorings = {};
75             me.mooring_model  = {local : nil};
76             me.fairway_model  = {local : nil};
77
78             me.mooring_model_path =
79                 me.find_model_path("Short_Empire/Models/Moorings/buoy.xml");
80             me.fairway_model_path =
81                 me.find_model_path("Short_Empire/Models/Moorings/flare_path.xml");
82         }
83         me.active_mooring = props.globals.getNode("/fdm/jsbsim/mooring");
84         me.selected = "";
85         me.reset();
86         print("Short Empire Mooring ... Standing by.");
87     },
88     ##################################################
89     add_fixed_mooring : func(pos, alt_offset, name="local") {
90         me.moorings[name] = { position   : pos,
91                               alt_offset : alt_offset };
92         me.display_mooring(name);
93         if (name == "local") {
94             #announce_fixed_mooring(pos, alt_offset);
95         }
96     },
97     ##################################################
98     display_mooring : func(name) {
99         if (!contains(me.moorings[name], "position")) return;
100         var geo_info = geodinfo(me.moorings[name].position.lat(),
101                                 me.moorings[name].position.lon());
102         if (geo_info == nil) return;
103         me.moorings[name].position.set_alt(geo_info[0]);
104         # Put a mooring buoy model here.
105         if (me.mooring_model[name] != nil) me.mooring_model[name].remove();
106         me.mooring_model[name] =
107             geo.put_model(me.mooring_model_path,
108                           me.moorings[name].position);
109         # Display the associated fairway, if any.
110         if (FAIRWAY[name] != nil) {
111             if (me.fairway_model[name] != nil) me.fairway_model[name].remove();
112             if (FAIRWAY[name][3] == 0.0) {
113                 me.fairway_model[name] =
114                     geo.put_model
115                     (me.fairway_model_path,
116                      FAIRWAY[name][1], FAIRWAY[name][2],
117                      nil,
118                      -getprop("/environment/wind-from-heading-deg"));
119             } else {
120                 me.fairway_model[name] =
121                     geo.put_model
122                     (me.fairway_model_path,
123                      FAIRWAY[name][1], FAIRWAY[name][2],
124                      nil,
125                      FAIRWAY[name][3]);                
126             }
127         }
128     },
129     ##################################################
130     remove_fixed_mooring : func(name) {
131         if (me.mooring_model[name] != nil) me.mooring_model[name].remove();
132         delete(me.moorings, name);
133     },
134     ##################################################
135     add_ai_mooring : func(ai, alt_offset) {
136         if (ai == nil) return;
137         var name = ai.getNode("callsign").getValue();
138         if (name == "") { name = ai.getNode("name").getValue(); }
139         me.moorings[name] = { base       : ai,
140                               alt_offset : alt_offset };
141     },
142     ##################################################
143     remove_ai_mooring : func(ai) {
144         if (ai == nil) return;
145         foreach (var name; keys(me.moorings)) {
146             if (contains(me.moorings[name], "base") and
147                 me.moorings[name].base == ai) {
148                 delete(me.moorings, name);
149                 return;
150             }
151         }
152     },
153     ##################################################
154     pick_up_mooring : func {
155         if (me.active_mooring.getNode("mooring-connected").getBoolValue())
156             return;
157         var dist = me.active_mooring.getNode("total-distance-ft").getValue();
158         var rope_length =
159             me.active_mooring.getNode("rope-length-ft").getValue();
160         if (dist < rope_length/FT2M) {
161             me.active_mooring.getNode("mooring-connected").setValue(1.0);
162             setprop("controls/lighting/anchor-light", 1.0);
163             copilot.announce("We picked up the mooring.");
164             return 1;
165         } else {
166             copilot.announce("We are too far from the buoy.");
167             return 0;
168         }
169     },
170     ##################################################
171     release_mooring : func {
172         if (me.active_mooring.getNode("mooring-connected").getValue() >= 1.0) {
173             me.active_mooring.getNode("mooring-connected").setValue(0.0);
174             setprop("controls/lighting/anchor-light", 0.0);
175             copilot.announce("Mooring slipped.");
176         }
177     },
178     ##################################################
179     # filename should include the aircraft's directory.
180     find_model_path : func (filename) {
181         # FIXME WORKAROUND: Search for the model in all aircraft dirs.
182         var base = "/" ~ filename;
183         var file = props.globals.getNode("/sim/fg-root").getValue() ~
184             "/Aircraft" ~ base;
185         if (io.stat(file) != nil) {
186             return file;
187         }
188         foreach (var d;
189                  props.globals.getNode("/sim").getChildren("fg-aircraft")) {
190             file = d.getValue() ~ base;
191             if (io.stat(file) != nil) {
192                 return file;
193             }
194         }
195     },
196     ##################################################
197     update : func {
198         var ac_pos = geo.aircraft_position();
199         
200         # Compute distance to the current mooring.
201         var distance = 1000000;
202         var cur_pos  = geo.Coord.new();
203         var cur_name = me.selected;
204         if (contains(me.moorings, me.selected)) {
205             if (contains(me.moorings[me.selected], "position")) {
206                 cur_pos = geo.Coord.new(me.moorings[me.selected].position);
207             } else {
208                 var ai = me.moorings[me.selected].base;
209                 cur_pos = geo.Coord.new().set_latlon
210                     (ai.getNode("position/latitude-deg").getValue(),
211                      ai.getNode("position/longitude-deg").getValue(),
212                      FT2M * ai.getNode("position/altitude-ft").getValue());
213             }
214             distance = cur_pos.distance_to(ac_pos);
215         }
216
217         # Break connection if too far way.
218         var rope_length =
219             me.active_mooring.getNode("rope-length-ft").getValue();
220         if (distance > 2.0 * rope_length and
221             me.active_mooring.getNode("mooring-connected").getBoolValue()) {
222             me.release_mooring();
223         }
224
225         # Find the closest mooring position.
226         foreach (var name; keys(me.moorings)) {
227             var pos = {};
228             if (contains(me.moorings[name], "position")) {
229                 pos = me.moorings[name].position;
230             } else {
231                 var ai = me.moorings[name].base;
232                 pos = geo.Coord.new().set_latlon
233                     (ai.getNode("position/latitude-deg").getValue(),
234                      ai.getNode("position/longitude-deg").getValue(),
235                      FT2M * ai.getNode("position/altitude-ft").getValue());
236             }
237             if (pos.direct_distance_to(ac_pos) < distance) {
238                 cur_name  = name;
239                 cur_pos   = geo.Coord.new(pos);
240                 distance  = pos.distance_to(ac_pos);
241             }
242         }
243
244         if (cur_pos.is_defined()) {
245             if (cur_name != me.selected) {
246                 print("Short Empire Mooring: Switched mooring to " ~
247                       cur_name ~ ".");
248                 me.selected = cur_name;
249                 me.display_mooring(cur_name);
250             }
251
252             # The position might be new, so update active mooring.
253             me.active_mooring.getNode("latitude-deg").setValue(cur_pos.lat());
254             me.active_mooring.getNode("longitude-deg").setValue(cur_pos.lon());
255             # First check if the offset is fixed or a AI/MP property.
256             var offset = 0;
257             if (dual_control_tools.is_num(me.moorings[name].alt_offset)) {
258                 offset = me.moorings[name].alt_offset;
259             } else {
260                 offset =
261                     me.moorings[name].base.
262                     getNode(me.moorings[name].alt_offset).getValue();
263             }
264             me.active_mooring.getNode("altitude-ft").
265                 setValue(M2FT * (cur_pos.alt() + offset));
266         }
267
268         # Announce local mooring.
269         var now = systime();
270         if (now > me.last_mp_announce + me.MP_ANNOUNCE_INTERVAL) {
271             #announce_fixed_mooring(me.moorings["local"].position,
272             #                       me.moorings["local"].alt_offset);
273             me.last_mp_announce = now;
274         }
275     },
276     ##################################################
277     reset : func {
278         me.loopid += 1;
279         me._loop_(me.loopid);
280     },
281     ##################################################
282     _loop_ : func(id) {
283         id == me.loopid or return;
284         me.update();
285         settimer(func { me._loop_(id); }, me.UPDATE_INTERVAL);
286     }
287 };
288
289 ###############################################################################
290 ## Lists containing EAMS mooring locations. See also ROUTES.txt.
291 ## Format:
292 ##   [[name, lat, lon]]
293 var EAMS_MOORINGS_EUROPE =
294     [
295      ["Hythe 1",                 50.872210,   -1.390830],
296      ["Hythe 2",                 50.872349,   -1.387512],
297      ["Saint-Nazaire",           47.297423,   -2.134309],
298      ["Bordeaux/Biscarosse",     44.383172,   -1.184227],
299      ["Macon",                   46.290932,    4.830000],
300      ["Marseille/Marignane",     43.446937,    5.185410],
301      ["Rome/Lake Bracciano",     42.113768,   12.187239],
302      ["Brindisi",                40.652277,   17.959625],
303      ["Corfu",                   39.614731,   19.929714],
304      ["Athens/Phaleron Bay",     37.939527,   23.666230],
305      ["Heraklion",               35.344386,   25.140345],
306      ["Mirabella Bay",           35.200427,   25.723003],
307      ["Alexandria/East Harbour", 31.196233,   29.872788]
308     ];
309 var EAMS_MOORINGS_EAST =
310     [
311      ["Tiberias",              32.804,      35.547],
312      ["Lake Habbaniyeh",       33.34625,    43.547359],
313      ["Basra/Margil",          30.5203,     47.8455],
314      ["Kuwait",                29.354583,   47.934932],
315      ["Bahrein",               26.240,      50.623],
316      ["Dubai",                 25.237,      55.325],
317      ["Jiwani",                25.053,      61.723],
318      ["Karachi",               24.780,      67.138],
319      ["Raj Samand",            25.071,      73.888],
320      ["Lake Udaipur",          24.574,      73.680],
321      ["Gwalior",               26.214,      77.994],
322      ["Jhansi/Parichha Reservoir", 25.505293,78.755804],
323      ["Allahabad",             25.425375,   81.86501],
324      ["Calcutta",              22.588625,   88.353534],
325      ["Akyab (Sittwe)",        20.139,      92.908],
326      ["Rangoon (Yangon)",      16.733619,   96.209164],
327      ["Bangkok",               13.657976,  100.550448],
328      ["Ko Samu (Ko Samui)",     9.5633,    100.054464],
329      ["Penang (Pinang)",        5.415,     100.350],
330      ["Sinagpore/Kallang",      1.302283,  103.870003],
331      ["Batavia (Jakarta)",     -6.118777,  106.835255],
332      ["Sourabaya (Surabaya)",  -7.18759,   112.733803],
333      ["Bima",                  -8.450368,  118.713026],
334      ["Koepang (Kupang)",     -10.152625,  123.587474],
335      ["Darwin",               -12.473265,  130.846809],
336      ["Groote",               -13.953581,  136.411035],
337      ["Karumba",              -17.472431,  140.834674],
338      ["Townsville",           -19.248107,  146.826745],
339      ["Gladstone",            -23.828888,  151.252084],
340      ["Brisbane",             -27.427547,  153.128225],
341      ["Sydney/Rose Bay",      -33.868387,  151.263121]
342     ];
343 var EAMS_MOORINGS_SOUTH =
344     [
345      ["Cairo",                 30.0327,     31.222],
346      ["Luxor",                 25.7011,     32.6365],
347      ["Wadi Halfa",            21.805,      31.300],
348      ["Khartoum",              15.606,      32.537],
349      ["Kosti",                 13.169,      32.665],
350      ["Malakal",                9.528,      31.652],
351      ["Bor",                    6.220,      31.543],
352      ["Juba",                   4.834,      31.614],
353      ["Laropi",                 3.54863,    31.812716],
354      ["Butabia",                1.829127,   31.329403],
355      ["Kampala/Port Bell",      0.287703,   32.652279],
356      ["Kisumu",                -0.090,      34.744],
357      ["Nairobi/Lake Naivasha", -0.7806,     36.4002],
358      ["Mombasa",               -4.0524,     39.6799],
359      ["Dar-es-Salaam",         -6.820,      39.292],
360      ["Lindi",                -10.001025,   39.721466],
361      ["Mozambique",           -15.038182,   40.704099],
362      ["Beira",                -19.826,      34.829],
363      ["Inhambane",            -23.8687,     35.3722],
364      ["Lourenco Morques",     -25.9689,     32.5376],
365      ["Durban",               -29.870,      31.033]
366     ];
367 var NORTH_ATLANTIC =
368     [
369      ["New York/Port Washington", 40.832081, -73.719479],
370      ["Bermuda/Darrel's Island",  32.273,    -64.825],
371      ["Foynes",                   52.618,     -9.130],
372      ["Botwood",                  49.133,    -55.334]
373     ];
374 var TEAL =
375     [
376      ["Auckland/Mechanics Bay",  -36.8440,   174.7942],
377      ["Wellington/Evans Bay",    -41.3142,   174.8065]
378     ];
379
380 ###############################################################################
381 ## Hash containing fairway locations. See also ROUTES.txt.
382 ## Format:
383 ##   {"mooring : [name, lat, lon, heading]}
384 var FAIRWAY = {
385     # Europe
386     "Hythe 1":                 ["Netley",        50.873,   -1.365, 0.0],
387     "Hythe 2":                 ["Netley",        50.873,   -1.365, 0.0],
388     "Saint-Nazaire":           ["",              47.289,   -2.152, 0.0],
389     "Bordeaux/Biscarosse":     ["",              44.379,   -1.186, 0.0],
390     "Macon":                   ["",              46.291,    4.831, 5.0],
391     "Marseille/Marignane":     ["",              43.452,    5.181, 0.0],
392     "Rome/Lake Bracciano":     ["",              42.119,   12.196, 0.0],
393     "Brindisi":                ["",              40.651,   17.962, 0.0],
394     "Athens/Phaleron Bay":     ["",              37.930,   23.673, 0.0],
395     "Mirabella Bay":           ["",              35.210,   25.740, 0.0],
396     # Africa
397     "Alexandria/East Harbour": ["",              31.193,   29.874,   0.0],
398     "Cairo":                   ["",              30.036,   31.225,  10.0],
399     "Luxor":                   ["",              25.710,   32.642,  30.0],
400     "Wadi Halfa":              ["",              21.810,   31.292,   0.0],
401     "Khartoum":                ["",              15.610,   32.537,  90.0],
402     "Kosti":                   ["",              13.172,   32.665, 315.0],
403     "Malakal":                 ["",               9.528,   31.650, 340.0],
404     "Bor":                     ["",               6.225,   31.537, 315.0],
405     "Juba":                    ["",               4.834,   31.617,  30.0],
406     #"Laropi":                  ["",               3.548,   31.812,  90.0],
407     "Butabia":                 ["",               1.837,   31.329,   0.0],
408     "Kampala/Port Bell":       ["",               0.280,   32.652,   0.0],
409     "Kisumu":                  ["",              -0.094,   34.740,   0.0],
410     "Nairobi/Lake Naivasha":   ["",              -0.780,   36.392,   0.0],
411     "Mombasa":                 ["",              -4.050,   39.682, 335.0],
412     "Dar-es-Salaam":           ["",              -6.823,   39.294,   0.0],
413     "Lindi":                   ["",             -10.006,   39.720,   0.0],
414     "Mozambique":              ["",             -15.038,   40.710,   0.0],
415     "Beira":                   ["",             -19.820,   34.825,   0.0],
416     "Inhambane":               ["",             -23.864,   35.363,   0.0],
417     "Lourenco Morques":        ["",             -25.965,   32.535,   0.0],
418     "Durban":                  ["Congella Bay", -29.873,   31.028,   0.0],
419     # Middle East
420     "Tiberias":                ["",              32.804,   35.560,   0.0],
421     "Lake Habbaniyeh":         ["",              33.338,   43.546,   0.0],
422     "Basra/Margil":            ["",              30.530,   47.839, 330.0],
423     "Kuwait":                  ["",              29.363,   47.938,   0.0],
424     "Bahrein":                 ["",              26.233,   50.624,   0.0],
425     "Dubai":                   ["Dubai Creek",   25.230,   55.330, 130.0],
426     # India
427     "Jiwani":                  ["",              25.057,   61.717,   0.0],
428     "Karachi":                 ["Karangi Creek", 24.775,   67.144,   0.0],
429     "Raj Samand":              ["",              25.075,   73.882,   0.0],
430     "Lake Udaipur":            ["",              24.575,   73.676,   0.0],
431     "Gwalior":                 ["",              26.217,   77.989,   0.0],
432     "Jhansi/Parichha Reservoir":["",             25.508,   78.768,  75.0],
433     "Allahabad":               ["",              25.423,   81.872, 100.0],
434     "Calcutta":                ["River Hooghly", 22.593,   88.356,  30.0],
435     # Far East
436     "Akyab (Sittwe)":          ["",              20.138,   92.913,   0.0],
437     "Rangoon (Yangon)":        ["",              16.756,   96.203,   0.0],
438     "Bangkok":                 ["",              13.657,  100.550,  95.0],
439     "Ko Samu (Ko Samui)":      ["",               9.566,  100.051,   0.0],
440     "Penang (Pinang)":         ["",               5.412,  100.353,   0.0],
441     "Sinagpore/Kallang":       ["",               1.289,  103.867, 165.0],
442     "Batavia (Jakarta)":       ["",              -6.108,  106.835,   0.0],
443     "Sourabaya (Surabaya)":    ["",              -7.184,  112.743,   0.0],
444     "Bima":                    ["",              -8.444,  118.705,   0.0],
445     "Koepang (Kupang)":        ["",             -10.150,  123.579,   0.0],
446     # Australia
447     "Darwin":                  ["",             -12.471,  130.856,   0.0],
448     "Groote":                  ["",             -13.946,  136.407,   0.0],
449     "Karumba":                 ["",             -17.469,  140.829, 135.0],
450     "Townsville":              ["",             -19.243,  146.822,   0.0],
451     "Gladstone":               ["",             -23.816,  151.260,   0.0],
452     "Brisbane":                ["",             -27.430,  153.128,  65.0],
453     "Sydney/Rose Bay":         ["Rose Bay",     -33.863,  151.260,   0.0],
454     # North Atlantic
455     "Bermuda/Darrel's Island": ["",              32.271,  -64.840,   0.0],
456     "New York/Port Washington":["",              40.828,  -73.719,   0.0],
457     "Foynes":                  ["",              52.618,   -9.140,   0.0],
458     "Botwood":                 ["",              49.140,  -55.324,   0.0],
459     # Teal
460     "Auckland/Mechanics Bay":  ["Mechanics Bay",-36.840,  174.805,   0.0],
461     "Wellington/Evans Bay":    ["Evans Bay",    -41.309,  174.808,   0.0]
462 };