remove README.Protocol and add a README that refers to the "real"
[fg:toms-fgdata.git] / Nasal / controls.nas
1 startEngine = func {
2     sel = props.globals.getNode("/sim/input/selected");
3     engs = props.globals.getNode("/controls/engines").getChildren("engine");
4     for(i=0; i<size(engs); i=i+1) {
5         select = sel.getChild("engine", i);
6         if(select != nil and select.getValue() != 0) {
7             engs[i].getNode("starter").setBoolValue(1);
8         }
9     }
10 }
11
12 # Initialization hack (called after initialization via a timeout), to
13 # make sure that the number of engine properties in the selection tree
14 # match the actual number of engines.  This should probably be fixed in a
15 # more elegant way...
16 initSelectProps = func {
17     engs = props.globals.getNode("/controls/engines").getChildren("engine");
18     sel = props.globals.getNode("/sim/input/selected");
19     for(i=0; i<size(engs); i=i+1) {
20         if(sel.getChild("engine", i) == nil) {
21             sel.getNode("engine[" ~ i ~ "]", 1); }}
22 }
23 settimer(initSelectProps, 0);
24
25 selectEngine = func {
26     sel = props.globals.getNode("/sim/input/selected").getChildren("engine");
27     foreach(node; sel) { node.setBoolValue(node.getIndex() == arg[0]); }
28 }
29
30 # Selects (state=1) or deselects (state=0) a list of engines, or all
31 # engines if no list is specified. Example:  selectEngines(1, 1, 3, 5);
32 #
33 var selectEngines = func (state, engines...) {
34     var sel = props.globals.getNode("/sim/input/selected");
35     if(size(engines)) {
36         foreach(var e; engines)
37             sel.getChild("engine", e, 1).setBoolValue(state);
38     } else {
39         foreach(var e; sel.getChildren("engine"))
40             e.setBoolValue(state);
41     }
42 }
43
44 selectAllEngines = func {
45     sel = props.globals.getNode("/sim/input/selected").getChildren("engine");
46     foreach(node; sel) { node.setBoolValue(1); }
47 }
48
49 stepMagnetos = func {
50     change = arg[0];
51     engs = props.globals.getNode("/controls/engines").getChildren("engine");
52     sel = props.globals.getNode("/sim/input/selected");
53     for(i=0; i<size(engs); i=i+1) {
54         select = sel.getChild("engine", i);
55         if(select != nil and select.getValue() != 0) {
56             mag = engs[i].getNode("magnetos", 1);
57             mag.setIntValue(mag.getValue() + change);
58         }
59     }
60 }
61
62 centerFlightControls = func {
63     setprop("/controls/flight/elevator", 0);
64     setprop("/controls/flight/aileron", 0);
65     setprop("/controls/flight/rudder", 0);
66 }
67
68 var throttleMouse = func {
69     if(!getprop("/devices/status/mice/mouse[0]/button[1]")) return;
70     var sel = props.globals.getNode("/sim/input/selected").getChildren("engine");
71     var delta = cmdarg().getNode("offset").getValue() * -4;
72     foreach(var n; sel) {
73         if(!n.getValue()) continue;
74         var throttle = "/controls/engines/engine[" ~ n.getIndex() ~ "]/throttle";
75         var val = getprop(throttle) + delta;
76         if(size(arg) > 0) val = -val;
77         setprop(throttle, val);
78     }
79 }
80
81 # Joystick axis handlers (uses cmdarg).  Shouldn't be called from
82 # other contexts.
83 var throttleAxis = func {
84     var val = cmdarg().getNode("setting").getValue();
85     if(size(arg) > 0) val = -val;
86     var sel = props.globals.getNode("/sim/input/selected").getChildren("engine");
87     foreach(var n; sel)
88         if(n.getValue())
89             setprop("/controls/engines/engine[" ~ n.getIndex() ~ "]/throttle",
90                     (1 - val)/2);
91 }
92 mixtureAxis = func {
93     val = cmdarg().getNode("setting").getValue();
94     if(size(arg) > 0) { val = -val; }
95     props.setAll("/controls/engines/engine", "mixture", (1 - val)/2);
96 }
97 propellerAxis = func {
98     val = cmdarg().getNode("setting").getValue();
99     if(size(arg) > 0) { val = -val; }
100     props.setAll("/controls/engines/engine", "propeller-pitch", (1 - val)/2);
101 }
102 carbHeatAxis = func {
103     val = cmdarg().getNode("setting").getValue();
104     if(size(arg) > 0) { val = -val; }
105     props.setAll("/controls/anti-ice/engine", "carb-heat", (1 - val)/2);
106 }
107
108
109 ##
110 # Wrapper around stepProps() which emulates the "old" flap behavior for
111 # configurations that aren't using the new mechanism.
112 #
113 flapsDown = func {
114     if(arg[0] == 0) { return; }
115     if(props.globals.getNode("/sim/flaps") != nil) {
116         stepProps("/controls/flight/flaps", "/sim/flaps", arg[0]);
117         return;
118     }
119     # Hard-coded flaps movement in 3 equal steps:
120     val = 0.3333334 * arg[0] + getprop("/controls/flight/flaps");
121     if(val > 1) { val = 1 } elsif(val < 0) { val = 0 }
122     setprop("/controls/flight/flaps", val);
123 }
124
125 stepSpoilers = func {
126     if(props.globals.getNode("/sim/spoilers") != nil) {
127         stepProps("/controls/flight/spoilers", "/sim/spoilers", arg[0]);
128         return;
129     }
130     # Hard-coded spoilers movement in 4 equal steps:
131     val = 0.25 * arg[0] + getprop("/controls/flight/spoilers");
132     if(val > 1) { val = 1 } elsif(val < 0) { val = 0 }
133     setprop("/controls/flight/spoilers", val);
134 }
135
136 stepSlats = func {
137     if(props.globals.getNode("/sim/slats") != nil) {
138         stepProps("/controls/flight/slats", "/sim/slats", arg[0]);
139         return;
140     }
141     # Hard-coded slats movement in 4 equal steps:
142     val = 0.25 * arg[0] + getprop("/controls/flight/slats");
143     if(val > 1) { val = 1 } elsif(val < 0) { val = 0 }
144     setprop("/controls/flight/slats", val);
145 }
146
147 ##
148 # Steps through an "array" of property settings.  The first argument
149 # specifies a destination property.  The second is a string containing
150 # a global property tree.  This tree should contain an array of
151 # indexed <setting> children.  This function will maintain a
152 # <current-setting> child, which contains the index of the currently
153 # active setting.  The third argument specifies an integer delta,
154 # indicating how many steps to move through the setting array.
155 # Note that because of the magic of the property system, this
156 # mechanism works for all scalar property types (bool, int, double,
157 # string).
158 #
159 # TODO: This interface could easily be extended to allow for wrapping,
160 # in addition to clamping, allowing a "cycle" of settings to be
161 # defined.  It could also be hooked up with the interpolate() call,
162 # which would allow the removal of the transition-time feature from
163 # YASim.  Finally, other pre-existing features (the views and engine
164 # magnetos, for instance), work similarly but not compatibly, and
165 # could be integrated.
166 #
167 stepProps = func {
168     dst = props.globals.getNode(arg[0]);
169     array = props.globals.getNode(arg[1]);
170     delta = arg[2];
171     if(dst == nil or array == nil) { return; }
172
173     sets = array.getChildren("setting");
174
175     curr = array.getNode("current-setting", 1).getValue();
176     if(curr == nil) { curr = 0; }
177     curr = curr + delta;
178     if   (curr < 0)           { curr = 0; }
179     elsif(curr >= size(sets)) { curr = size(sets) - 1; }
180
181     array.getNode("current-setting").setIntValue(curr);
182     dst.setValue(sets[curr].getValue());
183 }
184
185 ##
186 # "Slews" a property smoothly, without dependence on the simulator
187 # frame rate.  The first argument is the property name.  The second is
188 # a rate, in units per second.  NOTE: this modifies the property for
189 # the current frame only; it is intended to be called by bindings
190 # which repeat each frame.  If you want to cause motion over time, see
191 # interpolate().
192 #
193 slewProp = func {
194     prop = arg[0];
195     delta = arg[1] * getprop("/sim/time/delta-realtime-sec");
196     setprop(prop, getprop(prop) + delta);
197 }
198
199 # Standard trim rate, in units per second.  Remember that the full
200 # range of a trim axis is 2.0.  Should probably read this out of a
201 # property...
202 TRIM_RATE = 0.045;
203
204 ##
205 # Handlers.  These are suitable for binding to repeatable button press
206 # events.  They are *not* good for binding to the keyboard, since (at
207 # least) X11 synthesizes its own key repeats.
208 #
209 elevatorTrim = func {
210     slewProp("/controls/flight/elevator-trim", arg[0] * TRIM_RATE); }
211 aileronTrim = func {
212     slewProp("/controls/flight/aileron-trim", arg[0] * TRIM_RATE); }
213 rudderTrim = func {
214     slewProp("/controls/flight/rudder-trim", arg[0] * TRIM_RATE); }
215
216 THROTTLE_RATE = 0.33;
217
218 adjThrottle = func {
219     adjEngControl("throttle", arg[0]); }
220 adjMixture = func {
221     adjEngControl("mixture", arg[0]); }
222 adjCondition = func {
223     adjEngControl("condition", arg[0]); }
224 adjPropeller = func {
225     adjEngControl("propeller-pitch", arg[0]); }
226
227 adjEngControl = func {
228     engs = props.globals.getNode("/controls/engines").getChildren("engine");
229     selected = props.globals.getNode("/sim/input/selected");
230     delta = arg[1] * THROTTLE_RATE * getprop("/sim/time/delta-realtime-sec");
231     foreach(e; engs) {
232         if(selected.getChild("engine", e.getIndex(), 1).getBoolValue()) {
233             node = e.getNode(arg[0], 1);
234             node.setValue(node.getValue() + delta);
235         }
236     }
237 }
238
239 ##
240 # arg[0] is the throttle increment
241 # arg[1] is the auto-throttle target speed increment
242 var incThrottle = func {
243     var auto = props.globals.getNode("/autopilot/locks/speed", 1);
244     var sel = props.globals.getNode("/sim/input/selected");
245     if ( !auto.getValue() ) {
246       var engs = props.globals.getNode("/controls/engines").getChildren("engine");
247       foreach(var e; engs) {
248         if(sel.getChild("engine", e.getIndex(), 1).getBoolValue()) {
249           var node = e.getNode("throttle", 1);
250           var val = node.getValue() + arg[0];
251           node.setValue(val < -1.0 ? -1.0 : val > 1.0 ? 1.0 : val);
252         }
253       }
254     } else {
255       node = props.globals.getNode("/autopilot/settings/target-speed-kt", 1);
256       if ( node.getValue() == nil ) {
257         node.setValue( 0.0 );
258       }
259       node.setValue(node.getValue() + arg[1]);
260       if ( node.getValue() < 0.0 ) {
261         node.setValue( 0.0 );
262       }
263     }
264 }
265
266 ##
267 # arg[0] is the aileron increment
268 # arg[1] is the autopilot target heading increment
269 incAileron = func {
270     auto = props.globals.getNode("/autopilot/locks/heading", 1);
271     if ( !auto.getValue() ) {
272       aileron = props.globals.getNode("/controls/flight/aileron");
273       if ( aileron.getValue() == nil ) {
274         aileron.setValue( 0.0 );
275       }
276       aileron.setValue(aileron.getValue() + arg[0]);
277       if ( aileron.getValue() < -1.0 ) {
278         aileron.setValue( -1.0 );
279       }
280       if ( aileron.getValue() > 1.0 ) {
281         aileron.setValue( 1.0 );
282       }
283     }
284     if ( auto.getValue() == "dg-heading-hold" ) {
285       node = props.globals.getNode("/autopilot/settings/heading-bug-deg", 1);
286       if ( node.getValue() == nil ) {
287         node.setValue( 0.0 );
288       }
289       node.setValue(node.getValue() + arg[1]);
290       if ( node.getValue() < 0.0 ) {
291         node.setValue( node.getValue() + 360.0 );
292       }
293       if ( node.getValue() > 360.0 ) {
294         node.setValue( node.getValue() - 360.0 );
295       }
296     }
297     if ( auto.getValue() == "true-heading-hold" ) {
298       node = props.globals.getNode("/autopilot/settings/true-heading-deg", 1);
299       if ( node.getValue() == nil ) {
300         node.setValue( 0.0 );
301       }
302       node.setValue(node.getValue() + arg[1]);
303       if ( node.getValue() < 0.0 ) {
304         node.setValue( node.getValue() + 360.0 );
305       }
306       if ( node.getValue() > 360.0 ) {
307         node.setValue( node.getValue() - 360.0 );
308       }
309     }
310 }
311
312 ##
313 # arg[0] is the elevator increment
314 # arg[1] is the autopilot target altitude increment
315 incElevator = func {
316     auto = props.globals.getNode("/autopilot/locks/altitude", 1);
317     if ( !auto.getValue() or auto.getValue() == 0 ) {
318       elevator = props.globals.getNode("/controls/flight/elevator");
319       if ( elevator.getValue() == nil ) {
320         elevator.setValue( 0.0 );
321       }
322       elevator.setValue(elevator.getValue() + arg[0]);
323       if ( elevator.getValue() < -1.0 ) {
324         elevator.setValue( -1.0 );
325       }
326       if ( elevator.getValue() > 1.0 ) {
327         elevator.setValue( 1.0 );
328       }
329     } elsif ( auto.getValue() == "altitude-hold" ) {
330       node = props.globals.getNode("/autopilot/settings/target-altitude-ft", 1);
331       if ( node.getValue() == nil ) {
332         node.setValue( 0.0 );
333       }
334       node.setValue(node.getValue() + arg[1]);
335       if ( node.getValue() < 0.0 ) {
336         node.setValue( 0.0 );
337       }
338     }
339 }
340
341 ##
342 # Joystick axis handlers.  Don't call from other contexts.
343 #
344 elevatorTrimAxis = func { elevatorTrim(cmdarg().getNode("value").getValue()); }
345 aileronTrimAxis = func { aileronTrim(cmdarg().getNode("value").getValue()); }
346 rudderTrimAxis = func { rudderTrim(cmdarg().getNode("value").getValue()); }
347
348 ##
349 # Gear handling.
350 #
351 var gearDown = func {
352     if (arg[0] < 0) {
353       setprop("/controls/gear/gear-down", 0);
354     } elsif (arg[0] > 0) {
355       setprop("/controls/gear/gear-down", 1);
356     }
357 }
358 var gearToggle = func { gearDown(getprop("/controls/gear/gear-down") > 0 ? -1 : 1); }
359
360 ##
361 # Brake handling.
362 #
363 var fullBrakeTime = 0.5;
364 var applyBrakes = func(v, which = 0) {
365     if (which <= 0) { interpolate("/controls/gear/brake-left", v, fullBrakeTime); }
366     if (which >= 0) { interpolate("/controls/gear/brake-right", v, fullBrakeTime); }
367 }
368
369 var applyParkingBrake = func(v) {
370     if (!v) { return; }
371     var p = "/controls/gear/brake-parking";
372     setprop(p, var i = !getprop(p));
373     return i;
374 }
375
376 ##
377 # Weapon handling.
378 #
379 var trigger = func(b) setprop("/controls/armament/trigger", b);
380 var weaponSelect = func(d) {
381     var ws = props.globals.getNode("/controls/armament/selected", 1);
382     var n = ws.getValue();
383     if (n == nil) { n = 0; }
384     ws.setIntValue(n + d);
385 }
386
387 ##
388 # Communication.
389 #
390 var ptt = func(b) setprop("/instrumentation/comm/ptt", b);
391