]> git.mxchange.org Git - flightgear.git/blob - src/Main/fg_commands.cxx
Attempt #1 to sort out confusion between left / right / parking brake
[flightgear.git] / src / Main / fg_commands.cxx
1 // fg_commands.cxx - internal FGFS commands.
2
3 #include <string.h>             // strcmp()
4
5 #include <simgear/compiler.h>
6
7 #include STL_STRING
8 #include STL_FSTREAM
9
10 #include <simgear/sg_inlines.h>
11 #include <simgear/debug/logstream.hxx>
12 #include <simgear/math/sg_random.h>
13 #include <simgear/structure/exception.hxx>
14 #include <simgear/structure/commands.hxx>
15 #include <simgear/props/props.hxx>
16
17 #include <Cockpit/panel.hxx>
18 #include <Cockpit/panel_io.hxx>
19 #include <Environment/environment.hxx>
20 #include <FDM/flight.hxx>
21 #include <GUI/gui.h>
22 #include <GUI/new_gui.hxx>
23 #include <GUI/dialog.hxx>
24 #include <Replay/replay.hxx>
25 #include <Scenery/tilemgr.hxx>
26 #if defined(HAVE_PLIB_PSL)
27 #  include <Scripting/scriptmgr.hxx>
28 #endif
29 #include <Scripting/NasalSys.hxx>
30 #include <Time/sunsolver.hxx>
31 #include <Time/tmp.hxx>
32
33 #include "fg_init.hxx"
34 #include "fg_io.hxx"
35 #include "fg_commands.hxx"
36 #include "fg_props.hxx"
37 #include "globals.hxx"
38 #include "logger.cxx"
39 #include "util.hxx"
40 #include "viewmgr.hxx"
41
42 SG_USING_STD(string);
43 SG_USING_STD(ifstream);
44 SG_USING_STD(ofstream);
45
46
47 \f
48 ////////////////////////////////////////////////////////////////////////
49 // Static helper functions.
50 ////////////////////////////////////////////////////////////////////////
51
52
53 static inline SGPropertyNode *
54 get_prop (const SGPropertyNode * arg)
55 {
56     return fgGetNode(arg->getStringValue("property[0]", "/null"), true);
57 }
58
59 static inline SGPropertyNode *
60 get_prop2 (const SGPropertyNode * arg)
61 {
62     return fgGetNode(arg->getStringValue("property[1]", "/null"), true);
63 }
64
65
66 /**
67  * Get a double value and split it as required.
68  */
69 static void
70 split_value (double full_value, const char * mask,
71              double * unmodifiable, double * modifiable)
72 {
73     if (!strcmp("integer", mask)) {
74         *modifiable = (full_value < 0 ? ceil(full_value) : floor (full_value));
75         *unmodifiable = full_value - *modifiable;
76     } else if (!strcmp("decimal", mask)) {
77         *unmodifiable = (full_value < 0 ? ceil(full_value) : floor(full_value));
78         *modifiable = full_value - *unmodifiable;
79     } else {
80         if (strcmp("all", mask))
81             SG_LOG(SG_GENERAL, SG_ALERT, "Bad value " << mask << " for mask;"
82                    << " assuming 'all'");
83         *unmodifiable = 0;
84         *modifiable = full_value;
85     }
86 }
87
88
89 /**
90  * Clamp or wrap a value as specified.
91  */
92 static void
93 limit_value (double * value, const SGPropertyNode * arg)
94 {
95     const SGPropertyNode * min_node = arg->getChild("min");
96     const SGPropertyNode * max_node = arg->getChild("max");
97
98     bool wrap = arg->getBoolValue("wrap");
99
100     if (min_node == 0 || max_node == 0)
101         wrap = false;
102   
103     if (wrap) {                 // wrap such that min <= x < max
104         double min_val = min_node->getDoubleValue();
105         double max_val = max_node->getDoubleValue();
106         double resolution = arg->getDoubleValue("resolution");
107         if (resolution > 0.0) {
108             // snap to (min + N*resolution), taking special care to handle imprecision
109             int n = (int)floor((*value - min_val) / resolution + 0.5);
110             int steps = (int)floor((max_val - min_val) / resolution + 0.5);
111             SG_NORMALIZE_RANGE(n, 0, steps);
112             *value = min_val + resolution * n;
113         } else {
114             // plain circular wrapping
115             SG_NORMALIZE_RANGE(*value, min_val, max_val);
116         }
117     } else {                    // clamp such that min <= x <= max
118         if ((min_node != 0) && (*value < min_node->getDoubleValue()))
119             *value = min_node->getDoubleValue();
120         else if ((max_node != 0) && (*value > max_node->getDoubleValue()))
121             *value = max_node->getDoubleValue();
122     }
123 }
124
125 static bool
126 compare_values (SGPropertyNode * value1, SGPropertyNode * value2)
127 {
128     switch (value1->getType()) {
129     case SGPropertyNode::BOOL:
130         return (value1->getBoolValue() == value2->getBoolValue());
131     case SGPropertyNode::INT:
132         return (value1->getIntValue() == value2->getIntValue());
133     case SGPropertyNode::LONG:
134         return (value1->getLongValue() == value2->getLongValue());
135     case SGPropertyNode::FLOAT:
136         return (value1->getFloatValue() == value2->getFloatValue());
137     case SGPropertyNode::DOUBLE:
138         return (value1->getDoubleValue() == value2->getDoubleValue());
139     default:
140         return !strcmp(value1->getStringValue(), value2->getStringValue());
141     }
142 }
143
144
145 \f
146 ////////////////////////////////////////////////////////////////////////
147 // Command implementations.
148 ////////////////////////////////////////////////////////////////////////
149
150
151 /**
152  * Built-in command: do nothing.
153  */
154 static bool
155 do_null (const SGPropertyNode * arg)
156 {
157   return true;
158 }
159
160 #if defined(HAVE_PLIB_PSL)
161 /**
162  * Built-in command: run a PSL script.
163  *
164  * script: the PSL script to execute
165  */
166 static bool
167 do_script (const SGPropertyNode * arg)
168 {
169     FGScriptMgr * mgr = (FGScriptMgr *)globals->get_subsystem("scripting");
170     return mgr->run(arg->getStringValue("script"));
171 }
172 #endif // HAVE_PLIB_PSL
173
174 /**
175  * Built-in command: run a Nasal script.
176  */
177 static bool
178 do_nasal (const SGPropertyNode * arg)
179 {
180     return ((FGNasalSys*)globals->get_subsystem("nasal"))->handleCommand(arg);
181 }
182
183 /**
184  * Built-in command: exit FlightGear.
185  *
186  * status: the exit status to return to the operating system (defaults to 0)
187  */
188 static bool
189 do_exit (const SGPropertyNode * arg)
190 {
191   SG_LOG(SG_INPUT, SG_INFO, "Program exit requested.");
192   fgExit(arg->getIntValue("status", 0));
193   return true;
194 }
195
196
197 /**
198  * Built-in command: reinitialize one or more subsystems.
199  *
200  * subsystem[*]: the name(s) of the subsystem(s) to reinitialize; if
201  * none is specified, reinitialize all of them.
202  */
203 static bool
204 do_reinit (const SGPropertyNode * arg)
205 {
206     bool result = true;
207
208     vector<SGPropertyNode_ptr> subsystems = arg->getChildren("subsystem");
209     if (subsystems.size() == 0) {
210         globals->get_subsystem_mgr()->reinit();
211     } else {
212         for ( unsigned int i = 0; i < subsystems.size(); i++ ) {
213             const char * name = subsystems[i]->getStringValue();
214             SGSubsystem * subsystem = globals->get_subsystem(name);
215             if (subsystem == 0) {
216                 result = false;
217                 SG_LOG( SG_GENERAL, SG_ALERT,
218                         "Subsystem " << name << "not found" );
219             } else {
220                 subsystem->reinit();
221             }
222         }
223     }
224
225     globals->get_event_mgr()->reinit();
226
227     return result;
228 }
229
230 /**
231  * Built-in command: suspend one or more subsystems.
232  *
233  * subsystem[*] - the name(s) of the subsystem(s) to suspend.
234  */
235 static bool
236 do_suspend (const SGPropertyNode * arg)
237 {
238     bool result = true;
239
240     vector<SGPropertyNode_ptr> subsystems = arg->getChildren("subsystem");
241     for ( unsigned int i = 0; i < subsystems.size(); i++ ) {
242         const char * name = subsystems[i]->getStringValue();
243         SGSubsystem * subsystem = globals->get_subsystem(name);
244         if (subsystem == 0) {
245             result = false;
246             SG_LOG(SG_GENERAL, SG_ALERT, "Subsystem " << name << "not found");
247         } else {
248             subsystem->suspend();
249         }
250     }
251     return result;
252 }
253
254 /**
255  * Built-in command: suspend one or more subsystems.
256  *
257  * subsystem[*] - the name(s) of the subsystem(s) to suspend.
258  */
259 static bool
260 do_resume (const SGPropertyNode * arg)
261 {
262     bool result = true;
263
264     vector<SGPropertyNode_ptr> subsystems = arg->getChildren("subsystem");
265     for ( unsigned int i = 0; i < subsystems.size(); i++ ) {
266         const char * name = subsystems[i]->getStringValue();
267         SGSubsystem * subsystem = globals->get_subsystem(name);
268         if (subsystem == 0) {
269             result = false;
270             SG_LOG(SG_GENERAL, SG_ALERT, "Subsystem " << name << "not found");
271         } else {
272             subsystem->resume();
273         }
274     }
275     return result;
276 }
277
278
279 /**
280  * Built-in command: load flight.
281  *
282  * file (optional): the name of the file to load (relative to current
283  *   directory).  Defaults to "fgfs.sav"
284  */
285 static bool
286 do_load (const SGPropertyNode * arg)
287 {
288   const string &file = arg->getStringValue("file", "fgfs.sav");
289   ifstream input(file.c_str());
290   if (input.good() && fgLoadFlight(input)) {
291     input.close();
292     SG_LOG(SG_INPUT, SG_INFO, "Restored flight from " << file);
293     return true;
294   } else {
295     SG_LOG(SG_INPUT, SG_ALERT, "Cannot load flight from " << file);
296     return false;
297   }
298 }
299
300
301 /**
302  * Built-in command: save flight.
303  *
304  * file (optional): the name of the file to save (relative to the
305  * current directory).  Defaults to "fgfs.sav".
306  */
307 static bool
308 do_save (const SGPropertyNode * arg)
309 {
310   const string &file = arg->getStringValue("file", "fgfs.sav");
311   bool write_all = arg->getBoolValue("write-all", false);
312   SG_LOG(SG_INPUT, SG_INFO, "Saving flight");
313   ofstream output(file.c_str());
314   if (output.good() && fgSaveFlight(output, write_all)) {
315     output.close();
316     SG_LOG(SG_INPUT, SG_INFO, "Saved flight to " << file);
317     return true;
318   } else {
319     SG_LOG(SG_INPUT, SG_ALERT, "Cannot save flight to " << file);
320     return false;
321   }
322 }
323
324
325 /**
326  * Built-in command: (re)load the panel.
327  *
328  * path (optional): the file name to load the panel from 
329  * (relative to FG_ROOT).  Defaults to the value of /sim/panel/path,
330  * and if that's unspecified, to "Panels/Default/default.xml".
331  */
332 static bool
333 do_panel_load (const SGPropertyNode * arg)
334 {
335   string panel_path =
336     arg->getStringValue("path",
337                         fgGetString("/sim/panel/path",
338                                     "Panels/Default/default.xml"));
339   FGPanel * new_panel = fgReadPanel(panel_path);
340   if (new_panel == 0) {
341     SG_LOG(SG_INPUT, SG_ALERT,
342            "Error reading new panel from " << panel_path);
343     return false;
344   }
345   SG_LOG(SG_INPUT, SG_INFO, "Loaded new panel from " << panel_path);
346   globals->get_current_panel()->unbind();
347   delete globals->get_current_panel();
348   globals->set_current_panel( new_panel );
349   globals->get_current_panel()->bind();
350   return true;
351 }
352
353
354 /**
355  * Built-in command: pass a mouse click to the panel.
356  *
357  * button: the mouse button number, zero-based.
358  * is-down: true if the button is down, false if it is up.
359  * x-pos: the x position of the mouse click.
360  * y-pos: the y position of the mouse click.
361  */
362 static bool
363 do_panel_mouse_click (const SGPropertyNode * arg)
364 {
365   if (globals->get_current_panel() != 0)
366     return globals->get_current_panel()
367       ->doMouseAction(arg->getIntValue("button"),
368                       arg->getBoolValue("is-down") ? PU_DOWN : PU_UP,
369                       arg->getIntValue("x-pos"),
370                       arg->getIntValue("y-pos"));
371   else
372     return false;
373 }
374
375
376 /**
377  * Built-in command: (re)load preferences.
378  *
379  * path (optional): the file name to load the panel from (relative
380  * to FG_ROOT). Defaults to "preferences.xml".
381  */
382 static bool
383 do_preferences_load (const SGPropertyNode * arg)
384 {
385   try {
386     fgLoadProps(arg->getStringValue("path", "preferences.xml"),
387                 globals->get_props());
388   } catch (const sg_exception &e) {
389     guiErrorMessage("Error reading global preferences: ", e);
390     return false;
391   }
392   SG_LOG(SG_INPUT, SG_INFO, "Successfully read global preferences.");
393   return true;
394 }
395
396
397 static void
398 fix_hud_visibility()
399 {
400   if ( !strcmp(fgGetString("/sim/flight-model"), "ada") ) {
401       globals->get_props()->setBoolValue( "/sim/hud/visibility", true );
402       if ( globals->get_viewmgr()->get_current() == 1 ) {
403           globals->get_props()->setBoolValue( "/sim/hud/visibility", false );
404       }
405   }
406 }
407
408 static void
409 do_view_next( bool )
410 {
411     globals->get_current_view()->setHeadingOffset_deg(0.0);
412     globals->get_viewmgr()->next_view();
413     fix_hud_visibility();
414     globals->get_tile_mgr()->refresh_view_timestamps();
415 }
416
417 static void
418 do_view_prev( bool )
419 {
420     globals->get_current_view()->setHeadingOffset_deg(0.0);
421     globals->get_viewmgr()->prev_view();
422     fix_hud_visibility();
423     globals->get_tile_mgr()->refresh_view_timestamps();
424 }
425
426 /**
427  * Built-in command: cycle view.
428  */
429 static bool
430 do_view_cycle (const SGPropertyNode * arg)
431 {
432   globals->get_current_view()->setHeadingOffset_deg(0.0);
433   globals->get_viewmgr()->next_view();
434   fix_hud_visibility();
435   globals->get_tile_mgr()->refresh_view_timestamps();
436 //   fgReshape(fgGetInt("/sim/startup/xsize"), fgGetInt("/sim/startup/ysize"));
437   return true;
438 }
439
440 /**
441  * Built-in command: capture screen.
442  */
443 static bool
444 do_screen_capture (const SGPropertyNode * arg)
445 {
446   fgDumpSnapShot();
447   return true;
448 }
449
450
451 /**
452  * Reload the tile cache.
453  */
454 static bool
455 do_tile_cache_reload (const SGPropertyNode * arg)
456 {
457     static const SGPropertyNode *master_freeze
458         = fgGetNode("/sim/freeze/master");
459     bool freeze = master_freeze->getBoolValue();
460     SG_LOG(SG_INPUT, SG_INFO, "ReIniting TileCache");
461     if ( !freeze ) {
462         fgSetBool("/sim/freeze/master", true);
463     }
464     // BusyCursor(0);
465     if ( globals->get_tile_mgr()->init() ) {
466         // Load the local scenery data
467         double visibility_meters = fgGetDouble("/environment/visibility-m");
468         globals->get_tile_mgr()->update( visibility_meters );
469     } else {
470         SG_LOG( SG_GENERAL, SG_ALERT, 
471                 "Error in Tile Manager initialization!" );
472         exit(-1);
473     }
474     // BusyCursor(1);
475     if ( !freeze ) {
476         fgSetBool("/sim/freeze/master", false);
477     }
478     return true;
479 }
480
481
482 /**
483  * Set the outside air temperature at the "current" altitude by first
484  * calculating the corresponding sea level temp, and assigning that to
485  * all boundary and aloft environment layers.
486  */
487 static bool
488 do_set_oat_degc (const SGPropertyNode * arg)
489 {
490     const string &temp_str = arg->getStringValue("temp-degc", "15.0");
491
492     static const SGPropertyNode *altitude_ft
493       = fgGetNode("/position/altitude-ft");
494
495     FGEnvironment dummy;        // instantiate a dummy so we can leech a method
496     dummy.set_elevation_ft( altitude_ft->getDoubleValue() );
497     dummy.set_temperature_degc( atof( temp_str.c_str() ) );
498     double temp_sea_level_degc = dummy.get_temperature_sea_level_degc();
499
500     cout << "Altitude = " << altitude_ft->getDoubleValue() << endl;
501     cout << "Temp at alt (C) = " << atof( temp_str.c_str() ) << endl;
502     cout << "Temp sea level (C) = " << temp_sea_level_degc << endl;
503  
504     SGPropertyNode *node, *child;
505
506     // boundary layers
507     node = fgGetNode( "/environment/config/boundary" );
508     if ( node != NULL ) {
509       int i = 0;
510       while ( ( child = node->getNode( "entry", i ) ) != NULL ) {
511         child->setDoubleValue( "temperature-sea-level-degc",
512                                temp_sea_level_degc );
513         ++i;
514       }
515     }
516
517     // aloft layers
518     node = fgGetNode( "/environment/config/aloft" );
519     if ( node != NULL ) {
520       int i = 0;
521       while ( ( child = node->getNode( "entry", i ) ) != NULL ) {
522         child->setDoubleValue( "temperature-sea-level-degc",
523                                temp_sea_level_degc );
524         ++i;
525       }
526     }
527
528     return true;
529 }
530
531 /**
532  * Update the lighting manually.
533  */
534 static bool
535 do_timeofday (const SGPropertyNode * arg)
536 {
537     const string &offset_type = arg->getStringValue("timeofday", "noon");
538
539     static const SGPropertyNode *longitude
540         = fgGetNode("/position/longitude-deg");
541     static const SGPropertyNode *latitude
542         = fgGetNode("/position/latitude-deg");
543     static const SGPropertyNode *cur_time_override
544         = fgGetNode("/sim/time/cur-time-override", true);
545
546     int orig_warp = globals->get_warp();
547     SGTime *t = globals->get_time_params();
548     time_t cur_time = t->get_cur_time();
549     // cout << "cur_time = " << cur_time << endl;
550     // cout << "orig_warp = " << orig_warp << endl;
551
552     int warp = 0;
553     if ( offset_type == "real" ) {
554         warp = -orig_warp;
555     } else if ( offset_type == "dawn" ) {
556         warp = fgTimeSecondsUntilSunAngle( cur_time,
557                                            longitude->getDoubleValue()
558                                              * SGD_DEGREES_TO_RADIANS,
559                                            latitude->getDoubleValue()
560                                              * SGD_DEGREES_TO_RADIANS,
561                                            90.0, true ); 
562     } else if ( offset_type == "morning" ) {
563         warp = fgTimeSecondsUntilSunAngle( cur_time,
564                                            longitude->getDoubleValue()
565                                              * SGD_DEGREES_TO_RADIANS,
566                                            latitude->getDoubleValue()
567                                              * SGD_DEGREES_TO_RADIANS,
568                                            75.0, true ); 
569     } else if ( offset_type == "noon" ) {
570         warp = fgTimeSecondsUntilSunAngle( cur_time,
571                                            longitude->getDoubleValue()
572                                              * SGD_DEGREES_TO_RADIANS,
573                                            latitude->getDoubleValue()
574                                              * SGD_DEGREES_TO_RADIANS,
575                                            0.0, true ); 
576     } else if ( offset_type == "afternoon" ) {
577         warp = fgTimeSecondsUntilSunAngle( cur_time,
578                                            longitude->getDoubleValue()
579                                              * SGD_DEGREES_TO_RADIANS,
580                                            latitude->getDoubleValue()
581                                              * SGD_DEGREES_TO_RADIANS,
582                                            60.0, false ); 
583      } else if ( offset_type == "dusk" ) {
584         warp = fgTimeSecondsUntilSunAngle( cur_time,
585                                            longitude->getDoubleValue()
586                                              * SGD_DEGREES_TO_RADIANS,
587                                            latitude->getDoubleValue()
588                                              * SGD_DEGREES_TO_RADIANS,
589                                            90.0, false ); 
590      } else if ( offset_type == "evening" ) {
591         warp = fgTimeSecondsUntilSunAngle( cur_time,
592                                            longitude->getDoubleValue()
593                                              * SGD_DEGREES_TO_RADIANS,
594                                            latitude->getDoubleValue()
595                                              * SGD_DEGREES_TO_RADIANS,
596                                            100.0, false ); 
597     } else if ( offset_type == "midnight" ) {
598         warp = fgTimeSecondsUntilSunAngle( cur_time,
599                                            longitude->getDoubleValue()
600                                              * SGD_DEGREES_TO_RADIANS,
601                                            latitude->getDoubleValue()
602                                              * SGD_DEGREES_TO_RADIANS,
603                                            180.0, false ); 
604     }
605     // cout << "warp = " << warp << endl;
606     globals->set_warp( orig_warp + warp );
607
608     t->update( longitude->getDoubleValue() * SGD_DEGREES_TO_RADIANS,
609                latitude->getDoubleValue() * SGD_DEGREES_TO_RADIANS,
610                cur_time_override->getLongValue(),
611                globals->get_warp() );
612
613     return true;
614 }
615
616
617 /**
618  * Built-in command: toggle a bool property value.
619  *
620  * property: The name of the property to toggle.
621  */
622 static bool
623 do_property_toggle (const SGPropertyNode * arg)
624 {
625   SGPropertyNode * prop = get_prop(arg);
626   return prop->setBoolValue(!prop->getBoolValue());
627 }
628
629
630 /**
631  * Built-in command: assign a value to a property.
632  *
633  * property: the name of the property to assign.
634  * value: the value to assign; or
635  * property[1]: the property to copy from.
636  */
637 static bool
638 do_property_assign (const SGPropertyNode * arg)
639 {
640   SGPropertyNode * prop = get_prop(arg);
641   const SGPropertyNode * prop2 = get_prop2(arg);
642   const SGPropertyNode * value = arg->getNode("value");
643
644   if (value != 0)
645       return prop->setUnspecifiedValue(value->getStringValue());
646   else if (prop2)
647       return prop->setUnspecifiedValue(prop2->getStringValue());
648   else
649       return false;
650 }
651
652
653 /**
654  * Built-in command: increment or decrement a property value.
655  *
656  * If the 'step' argument is present, it will be used; otherwise,
657  * the command uses 'offset' and 'factor', usually from the mouse.
658  *
659  * property: the name of the property to increment or decrement.
660  * step: the amount of the increment or decrement (default: 0).
661  * offset: offset from the current setting (used for the mouse; multiplied 
662  *         by factor)
663  * factor: scaling amount for the offset (defaults to 1).
664  * min: the minimum allowed value (default: no minimum).
665  * max: the maximum allowed value (default: no maximum).
666  * mask: 'integer' to apply only to the left of the decimal point, 
667  *       'decimal' to apply only to the right of the decimal point,
668  *       or 'all' to apply to the whole number (the default).
669  * wrap: true if the value should be wrapped when it passes min or max;
670  *       both min and max must be present for this to work (default:
671  *       false).
672  */
673 static bool
674 do_property_adjust (const SGPropertyNode * arg)
675 {
676   SGPropertyNode * prop = get_prop(arg);
677
678   double amount = 0;
679   if (arg->hasValue("step"))
680       amount = arg->getDoubleValue("step");
681   else
682       amount = (arg->getDoubleValue("factor", 1)
683                 * arg->getDoubleValue("offset"));
684           
685   double unmodifiable, modifiable;
686   split_value(prop->getDoubleValue(), arg->getStringValue("mask", "all"),
687               &unmodifiable, &modifiable);
688   modifiable += amount;
689   limit_value(&modifiable, arg);
690
691   prop->setDoubleValue(unmodifiable + modifiable);
692
693   return true;
694 }
695
696
697 /**
698  * Built-in command: multiply a property value.
699  *
700  * property: the name of the property to multiply.
701  * factor: the amount by which to multiply.
702  * min: the minimum allowed value (default: no minimum).
703  * max: the maximum allowed value (default: no maximum).
704  * mask: 'integer' to apply only to the left of the decimal point, 
705  *       'decimal' to apply only to the right of the decimal point,
706  *       or 'all' to apply to the whole number (the default).
707  * wrap: true if the value should be wrapped when it passes min or max;
708  *       both min and max must be present for this to work (default:
709  *       false).
710  */
711 static bool
712 do_property_multiply (const SGPropertyNode * arg)
713 {
714   SGPropertyNode * prop = get_prop(arg);
715   double factor = arg->getDoubleValue("factor", 1);
716
717   double unmodifiable, modifiable;
718   split_value(prop->getDoubleValue(), arg->getStringValue("mask", "all"),
719               &unmodifiable, &modifiable);
720   modifiable *= factor;
721   limit_value(&modifiable, arg);
722
723   prop->setDoubleValue(unmodifiable + modifiable);
724
725   return true;
726 }
727
728
729 /**
730  * Built-in command: swap two property values.
731  *
732  * property[0]: the name of the first property.
733  * property[1]: the name of the second property.
734  */
735 static bool
736 do_property_swap (const SGPropertyNode * arg)
737 {
738   SGPropertyNode * prop1 = get_prop(arg);
739   SGPropertyNode * prop2 = get_prop2(arg);
740
741                                 // FIXME: inefficient
742   const string & tmp = prop1->getStringValue();
743   return (prop1->setUnspecifiedValue(prop2->getStringValue()) &&
744           prop2->setUnspecifiedValue(tmp.c_str()));
745 }
746
747
748 /**
749  * Built-in command: Set a property to an axis or other moving input.
750  *
751  * property: the name of the property to set.
752  * setting: the current input setting, usually between -1.0 and 1.0.
753  * offset: the offset to shift by, before applying the factor.
754  * factor: the factor to multiply by (use negative to reverse).
755  */
756 static bool
757 do_property_scale (const SGPropertyNode * arg)
758 {
759   SGPropertyNode * prop = get_prop(arg);
760   double setting = arg->getDoubleValue("setting");
761   double offset = arg->getDoubleValue("offset", 0.0);
762   double factor = arg->getDoubleValue("factor", 1.0);
763   bool squared = arg->getBoolValue("squared", false);
764   int power = arg->getIntValue("power", (squared ? 2 : 1));
765
766   int sign = (setting < 0 ? -1 : 1);
767
768   switch (power) {
769   case 1:
770       break;
771   case 2:
772       setting = setting * setting * sign;
773       break;
774   case 3:
775       setting = setting * setting * setting;
776       break;
777   case 4:
778       setting = setting * setting * setting * setting * sign;
779       break;
780   default:
781       setting =  pow(setting, power);
782       if ((power % 2) == 0)
783           setting *= sign;
784       break;
785   }
786
787   return prop->setDoubleValue((setting + offset) * factor);
788 }
789
790
791 /**
792  * Built-in command: cycle a property through a set of values.
793  *
794  * If the current value isn't in the list, the cycle will
795  * (re)start from the beginning.
796  *
797  * property: the name of the property to cycle.
798  * value[*]: the list of values to cycle through.
799  */
800 static bool
801 do_property_cycle (const SGPropertyNode * arg)
802 {
803     SGPropertyNode * prop = get_prop(arg);
804     vector<SGPropertyNode_ptr> values = arg->getChildren("value");
805     int selection = -1;
806     int nSelections = values.size();
807
808     if (nSelections < 1) {
809         SG_LOG(SG_GENERAL, SG_ALERT, "No values for property-cycle");
810         return false;
811     }
812
813                                 // Try to find the current selection
814     for (int i = 0; i < nSelections; i++) {
815         if (compare_values(prop, values[i])) {
816             selection = i + 1;
817             break;
818         }
819     }
820
821                                 // Default or wrap to the first selection
822     if (selection < 0 || selection >= nSelections)
823         selection = 0;
824
825     prop->setUnspecifiedValue(values[selection]->getStringValue());
826     return true;
827 }
828
829
830 /**
831  * Built-in command: randomize a numeric property value.
832  *
833  * property: the name of the property value to randomize.
834  * min: the minimum allowed value.
835  * max: the maximum allowed value.
836  */
837 static bool
838 do_property_randomize (const SGPropertyNode * arg)
839 {
840     SGPropertyNode * prop = get_prop(arg);
841     double min = arg->getDoubleValue("min", DBL_MIN);
842     double max = arg->getDoubleValue("max", DBL_MAX);
843     prop->setDoubleValue(sg_random() * (max - min) + min);
844     return true;
845 }
846
847
848 /**
849  * Built-in command: reinit the data logging system based on the
850  * current contents of the /logger tree.
851  */
852 static bool
853 do_data_logging_commit (const SGPropertyNode * arg)
854 {
855     FGLogger *log = (FGLogger *)globals->get_subsystem("logger");
856     log->reinit();
857     return true;
858 }
859
860 /**
861  * Built-in command: Add a dialog to the GUI system.  Does *not*
862  * display the dialog.  The property node should have the same format
863  * as a dialog XML configuration.  It must include:
864  *
865  * name: the name of the GUI dialog for future reference.
866  */
867 static bool
868 do_dialog_new (const SGPropertyNode * arg)
869 {
870     NewGUI * gui = (NewGUI *)globals->get_subsystem("gui");
871
872     // Note the casting away of const: this is *real*.  Doing a
873     // "dialog-apply" command later on will mutate this property node.
874     // I'm not convinced that this isn't the Right Thing though; it
875     // allows client to create a node, pass it to dialog-new, and get
876     // the values back from the dialog by reading the same node.
877     // Perhaps command arguments are not as "const" as they would
878     // seem?
879     gui->newDialog((SGPropertyNode*)arg);
880     return true;
881 }
882
883 /**
884  * Built-in command: Show an XML-configured dialog.
885  *
886  * dialog-name: the name of the GUI dialog to display.
887  */
888 static bool
889 do_dialog_show (const SGPropertyNode * arg)
890 {
891     NewGUI * gui = (NewGUI *)globals->get_subsystem("gui");
892     gui->showDialog(arg->getStringValue("dialog-name"));
893     return true;
894 }
895
896
897 /**
898  * Built-in Command: Hide the active XML-configured dialog.
899  */
900 static bool
901 do_dialog_close (const SGPropertyNode * arg)
902 {
903     NewGUI * gui = (NewGUI *)globals->get_subsystem("gui");
904     if(arg->hasValue("dialog-name"))
905         return gui->closeDialog(arg->getStringValue("dialog-name"));
906     return gui->closeActiveDialog();
907 }
908
909
910 /**
911  * Update a value in the active XML-configured dialog.
912  *
913  * object-name: The name of the GUI object(s) (all GUI objects if omitted).
914  */
915 static bool
916 do_dialog_update (const SGPropertyNode * arg)
917 {
918     NewGUI * gui = (NewGUI *)globals->get_subsystem("gui");
919     FGDialog * dialog = gui->getActiveDialog();
920     if (dialog != 0) {
921         if (arg->hasValue("object-name")) {
922             dialog->updateValue(arg->getStringValue("object-name"));
923         } else {
924             dialog->updateValues();
925         }
926         return true;
927     } else {
928         return false;
929     }
930 }
931
932
933 /**
934  * Apply a value in the active XML-configured dialog.
935  *
936  * object-name: The name of the GUI object(s) (all GUI objects if omitted).
937  */
938 static bool
939 do_dialog_apply (const SGPropertyNode * arg)
940 {
941     NewGUI * gui = (NewGUI *)globals->get_subsystem("gui");
942     FGDialog * dialog = gui->getActiveDialog();
943     if (dialog != 0) {
944         if (arg->hasValue("object-name")) {
945             const char * name = arg->getStringValue("object-name");
946             dialog->applyValue(name);
947             dialog->updateValue(name);
948         } else {
949             dialog->applyValues();
950             dialog->updateValues();
951         }
952         return true;
953     } else {
954         return false;
955     }
956 }
957
958
959 /**
960  * Built-in command: commit presets (read from in /sim/presets/)
961  */
962 static bool
963 do_presets_commit (const SGPropertyNode * arg)
964 {
965     // unbind the current fdm state so property changes
966     // don't get lost when we subsequently delete this fdm
967     // and create a new one.
968     cur_fdm_state->unbind();
969         
970     // set position from presets
971     fgInitPosition();
972
973     // BusyCursor(0);
974     fgReInitSubsystems();
975
976     globals->get_tile_mgr()->update( fgGetDouble("/environment/visibility-m") );
977
978 #if 0
979     if ( ! fgGetBool("/sim/presets/onground") ) {
980         fgSetBool( "/sim/freeze/master", true );
981         fgSetBool( "/sim/freeze/clock", true );
982     }
983 #endif
984
985     return true;
986 }
987
988 /**
989  * Built-in command: set log level (0 ... 7)
990  */
991 static bool
992 do_log_level (const SGPropertyNode * arg)
993 {
994    sglog().setLogLevels( SG_ALL, (sgDebugPriority)arg->getIntValue() );
995
996    return true;
997 }
998
999 /**
1000  * Built-in command: replay the FDR buffer
1001  */
1002 static bool
1003 do_replay (const SGPropertyNode * arg)
1004 {
1005     // freeze the master fdm
1006     fgSetBool( "/sim/freeze/replay", true );
1007
1008     FGReplay *r = (FGReplay *)(globals->get_subsystem( "replay" ));
1009
1010     fgSetDouble( "/sim/replay/start-time", r->get_start_time() );
1011     fgSetDouble( "/sim/replay/end-time", r->get_end_time() );
1012     double duration = fgGetDouble( "/sim/replay/duration" );
1013     if( duration && duration < (r->get_end_time() - r->get_start_time()) ) {
1014         fgSetDouble( "/sim/replay/time", r->get_end_time() - duration );
1015     } else {
1016         fgSetDouble( "/sim/replay/time", r->get_start_time() );
1017     }
1018
1019     cout << "start = " << r->get_start_time()
1020          << "  end = " << r->get_end_time() << endl;
1021
1022     return true;
1023 }
1024
1025
1026
1027 static bool
1028 do_decrease_visibility (const SGPropertyNode * arg)
1029 {
1030     double new_value = fgGetDouble("/environment/visibility-m") * 0.9;
1031     fgSetDouble("/environment/visibility-m", new_value);
1032     fgDefaultWeatherValue("visibility-m", new_value);
1033     globals->get_subsystem("environment")->reinit();
1034
1035     return true;
1036 }
1037  
1038 static bool
1039 do_increase_visibility (const SGPropertyNode * arg)
1040 {
1041     double new_value = fgGetDouble("/environment/visibility-m") * 1.1;
1042     fgSetDouble("/environment/visibility-m", new_value);
1043     fgDefaultWeatherValue("visibility-m", new_value);
1044     globals->get_subsystem("environment")->reinit();
1045
1046     return true;
1047 }
1048
1049
1050 ////////////////////////////////////////////////////////////////////////
1051 // Command setup.
1052 ////////////////////////////////////////////////////////////////////////
1053
1054
1055 /**
1056  * Table of built-in commands.
1057  *
1058  * New commands do not have to be added here; any module in the application
1059  * can add a new command using globals->get_commands()->addCommand(...).
1060  */
1061 static struct {
1062   const char * name;
1063   SGCommandMgr::command_t command;
1064 } built_ins [] = {
1065     { "null", do_null },
1066 #if defined(HAVE_PLIB_PSL)
1067     { "script", do_script },
1068 #endif // HAVE_PLIB_PSL
1069     { "nasal", do_nasal },
1070     { "exit", do_exit },
1071     { "reinit", do_reinit },
1072     { "suspend", do_reinit },
1073     { "resume", do_reinit },
1074     { "load", do_load },
1075     { "save", do_save },
1076     { "panel-load", do_panel_load },
1077     { "panel-mouse-click", do_panel_mouse_click },
1078     { "preferences-load", do_preferences_load },
1079     { "view-cycle", do_view_cycle },
1080     { "screen-capture", do_screen_capture },
1081     { "tile-cache-reload", do_tile_cache_reload },
1082     { "set-outside-air-temp-degc", do_set_oat_degc },
1083     { "timeofday", do_timeofday },
1084     { "property-toggle", do_property_toggle },
1085     { "property-assign", do_property_assign },
1086     { "property-adjust", do_property_adjust },
1087     { "property-multiply", do_property_multiply },
1088     { "property-swap", do_property_swap },
1089     { "property-scale", do_property_scale },
1090     { "property-cycle", do_property_cycle },
1091     { "property-randomize", do_property_randomize },
1092     { "data-logging-commit", do_data_logging_commit },
1093     { "dialog-new", do_dialog_new },
1094     { "dialog-show", do_dialog_show },
1095     { "dialog-close", do_dialog_close },
1096     { "dialog-show", do_dialog_show },
1097     { "dialog-update", do_dialog_update },
1098     { "dialog-apply", do_dialog_apply },
1099     { "presets-commit", do_presets_commit },
1100     { "log-level", do_log_level },
1101     { "replay", do_replay },
1102     { "decrease-visibility", do_decrease_visibility },
1103     { "increase-visibility", do_increase_visibility },
1104     { 0, 0 }                    // zero-terminated
1105 };
1106
1107
1108 /**
1109  * Initialize the default built-in commands.
1110  *
1111  * Other commands may be added by other parts of the application.
1112  */
1113 void
1114 fgInitCommands ()
1115 {
1116   SG_LOG(SG_GENERAL, SG_INFO, "Initializing basic built-in commands:");
1117   for (int i = 0; built_ins[i].name != 0; i++) {
1118     SG_LOG(SG_GENERAL, SG_INFO, "  " << built_ins[i].name);
1119     globals->get_commands()->addCommand(built_ins[i].name,
1120                                         built_ins[i].command);
1121   }
1122
1123   typedef bool (*dummy)();
1124   fgTie( "/command/view/next", dummy(0), do_view_next );
1125   fgTie( "/command/view/prev", dummy(0), do_view_prev );
1126 }
1127
1128 // end of fg_commands.cxx