]> git.mxchange.org Git - flightgear.git/blob - src/Main/fg_commands.cxx
Added a <solve-weight> subtag of the approach/cruise parameters that can
[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.hxx"
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 sea level outside air temperature and assigning that to all
484  * boundary and aloft environment layers.
485  */
486 static bool
487 do_set_sea_level_degc (const SGPropertyNode * arg)
488 {
489     double temp_sea_level_degc = arg->getDoubleValue("temp-degc", 15.0);
490
491     SGPropertyNode *node, *child;
492
493     // boundary layers
494     node = fgGetNode( "/environment/config/boundary" );
495     if ( node != NULL ) {
496       int i = 0;
497       while ( ( child = node->getNode( "entry", i ) ) != NULL ) {
498         child->setDoubleValue( "temperature-sea-level-degc",
499                                temp_sea_level_degc );
500         ++i;
501       }
502     }
503
504     // aloft layers
505     node = fgGetNode( "/environment/config/aloft" );
506     if ( node != NULL ) {
507       int i = 0;
508       while ( ( child = node->getNode( "entry", i ) ) != NULL ) {
509         child->setDoubleValue( "temperature-sea-level-degc",
510                                temp_sea_level_degc );
511         ++i;
512       }
513     }
514
515     return true;
516 }
517
518
519 /**
520  * Set the outside air temperature at the "current" altitude by first
521  * calculating the corresponding sea level temp, and assigning that to
522  * all boundary and aloft environment layers.
523  */
524 static bool
525 do_set_oat_degc (const SGPropertyNode * arg)
526 {
527     const string &temp_str = arg->getStringValue("temp-degc", "15.0");
528
529     static const SGPropertyNode *altitude_ft
530       = fgGetNode("/position/altitude-ft");
531
532     FGEnvironment dummy;        // instantiate a dummy so we can leech a method
533     dummy.set_elevation_ft( altitude_ft->getDoubleValue() );
534     dummy.set_temperature_degc( atof( temp_str.c_str() ) );
535     double temp_sea_level_degc = dummy.get_temperature_sea_level_degc();
536
537     cout << "Altitude = " << altitude_ft->getDoubleValue() << endl;
538     cout << "Temp at alt (C) = " << atof( temp_str.c_str() ) << endl;
539     cout << "Temp sea level (C) = " << temp_sea_level_degc << endl;
540  
541     SGPropertyNode *node, *child;
542
543     // boundary layers
544     node = fgGetNode( "/environment/config/boundary" );
545     if ( node != NULL ) {
546       int i = 0;
547       while ( ( child = node->getNode( "entry", i ) ) != NULL ) {
548         child->setDoubleValue( "temperature-sea-level-degc",
549                                temp_sea_level_degc );
550         ++i;
551       }
552     }
553
554     // aloft layers
555     node = fgGetNode( "/environment/config/aloft" );
556     if ( node != NULL ) {
557       int i = 0;
558       while ( ( child = node->getNode( "entry", i ) ) != NULL ) {
559         child->setDoubleValue( "temperature-sea-level-degc",
560                                temp_sea_level_degc );
561         ++i;
562       }
563     }
564
565     return true;
566 }
567
568 /**
569  * Update the lighting manually.
570  */
571 static bool
572 do_timeofday (const SGPropertyNode * arg)
573 {
574     const string &offset_type = arg->getStringValue("timeofday", "noon");
575
576     static const SGPropertyNode *longitude
577         = fgGetNode("/position/longitude-deg");
578     static const SGPropertyNode *latitude
579         = fgGetNode("/position/latitude-deg");
580     static const SGPropertyNode *cur_time_override
581         = fgGetNode("/sim/time/cur-time-override", true);
582
583     int orig_warp = globals->get_warp();
584     SGTime *t = globals->get_time_params();
585     time_t cur_time = t->get_cur_time();
586     // cout << "cur_time = " << cur_time << endl;
587     // cout << "orig_warp = " << orig_warp << endl;
588
589     int warp = 0;
590     if ( offset_type == "real" ) {
591         warp = -orig_warp;
592     } else if ( offset_type == "dawn" ) {
593         warp = fgTimeSecondsUntilSunAngle( cur_time,
594                                            longitude->getDoubleValue()
595                                              * SGD_DEGREES_TO_RADIANS,
596                                            latitude->getDoubleValue()
597                                              * SGD_DEGREES_TO_RADIANS,
598                                            90.0, true ); 
599     } else if ( offset_type == "morning" ) {
600         warp = fgTimeSecondsUntilSunAngle( cur_time,
601                                            longitude->getDoubleValue()
602                                              * SGD_DEGREES_TO_RADIANS,
603                                            latitude->getDoubleValue()
604                                              * SGD_DEGREES_TO_RADIANS,
605                                            75.0, true ); 
606     } else if ( offset_type == "noon" ) {
607         warp = fgTimeSecondsUntilSunAngle( cur_time,
608                                            longitude->getDoubleValue()
609                                              * SGD_DEGREES_TO_RADIANS,
610                                            latitude->getDoubleValue()
611                                              * SGD_DEGREES_TO_RADIANS,
612                                            0.0, true ); 
613     } else if ( offset_type == "afternoon" ) {
614         warp = fgTimeSecondsUntilSunAngle( cur_time,
615                                            longitude->getDoubleValue()
616                                              * SGD_DEGREES_TO_RADIANS,
617                                            latitude->getDoubleValue()
618                                              * SGD_DEGREES_TO_RADIANS,
619                                            60.0, false ); 
620      } else if ( offset_type == "dusk" ) {
621         warp = fgTimeSecondsUntilSunAngle( cur_time,
622                                            longitude->getDoubleValue()
623                                              * SGD_DEGREES_TO_RADIANS,
624                                            latitude->getDoubleValue()
625                                              * SGD_DEGREES_TO_RADIANS,
626                                            90.0, false ); 
627      } else if ( offset_type == "evening" ) {
628         warp = fgTimeSecondsUntilSunAngle( cur_time,
629                                            longitude->getDoubleValue()
630                                              * SGD_DEGREES_TO_RADIANS,
631                                            latitude->getDoubleValue()
632                                              * SGD_DEGREES_TO_RADIANS,
633                                            100.0, false ); 
634     } else if ( offset_type == "midnight" ) {
635         warp = fgTimeSecondsUntilSunAngle( cur_time,
636                                            longitude->getDoubleValue()
637                                              * SGD_DEGREES_TO_RADIANS,
638                                            latitude->getDoubleValue()
639                                              * SGD_DEGREES_TO_RADIANS,
640                                            180.0, false ); 
641     }
642     // cout << "warp = " << warp << endl;
643     globals->set_warp( orig_warp + warp );
644
645     t->update( longitude->getDoubleValue() * SGD_DEGREES_TO_RADIANS,
646                latitude->getDoubleValue() * SGD_DEGREES_TO_RADIANS,
647                cur_time_override->getLongValue(),
648                globals->get_warp() );
649
650     return true;
651 }
652
653
654 /**
655  * Built-in command: toggle a bool property value.
656  *
657  * property: The name of the property to toggle.
658  */
659 static bool
660 do_property_toggle (const SGPropertyNode * arg)
661 {
662   SGPropertyNode * prop = get_prop(arg);
663   return prop->setBoolValue(!prop->getBoolValue());
664 }
665
666
667 /**
668  * Built-in command: assign a value to a property.
669  *
670  * property: the name of the property to assign.
671  * value: the value to assign; or
672  * property[1]: the property to copy from.
673  */
674 static bool
675 do_property_assign (const SGPropertyNode * arg)
676 {
677   SGPropertyNode * prop = get_prop(arg);
678   const SGPropertyNode * prop2 = get_prop2(arg);
679   const SGPropertyNode * value = arg->getNode("value");
680
681   if (value != 0)
682       return prop->setUnspecifiedValue(value->getStringValue());
683   else if (prop2)
684       return prop->setUnspecifiedValue(prop2->getStringValue());
685   else
686       return false;
687 }
688
689
690 /**
691  * Built-in command: increment or decrement a property value.
692  *
693  * If the 'step' argument is present, it will be used; otherwise,
694  * the command uses 'offset' and 'factor', usually from the mouse.
695  *
696  * property: the name of the property to increment or decrement.
697  * step: the amount of the increment or decrement (default: 0).
698  * offset: offset from the current setting (used for the mouse; multiplied 
699  *         by factor)
700  * factor: scaling amount for the offset (defaults to 1).
701  * min: the minimum allowed value (default: no minimum).
702  * max: the maximum allowed value (default: no maximum).
703  * mask: 'integer' to apply only to the left of the decimal point, 
704  *       'decimal' to apply only to the right of the decimal point,
705  *       or 'all' to apply to the whole number (the default).
706  * wrap: true if the value should be wrapped when it passes min or max;
707  *       both min and max must be present for this to work (default:
708  *       false).
709  */
710 static bool
711 do_property_adjust (const SGPropertyNode * arg)
712 {
713   SGPropertyNode * prop = get_prop(arg);
714
715   double amount = 0;
716   if (arg->hasValue("step"))
717       amount = arg->getDoubleValue("step");
718   else
719       amount = (arg->getDoubleValue("factor", 1)
720                 * arg->getDoubleValue("offset"));
721           
722   double unmodifiable, modifiable;
723   split_value(prop->getDoubleValue(), arg->getStringValue("mask", "all"),
724               &unmodifiable, &modifiable);
725   modifiable += amount;
726   limit_value(&modifiable, arg);
727
728   prop->setDoubleValue(unmodifiable + modifiable);
729
730   return true;
731 }
732
733
734 /**
735  * Built-in command: multiply a property value.
736  *
737  * property: the name of the property to multiply.
738  * factor: the amount by which to multiply.
739  * min: the minimum allowed value (default: no minimum).
740  * max: the maximum allowed value (default: no maximum).
741  * mask: 'integer' to apply only to the left of the decimal point, 
742  *       'decimal' to apply only to the right of the decimal point,
743  *       or 'all' to apply to the whole number (the default).
744  * wrap: true if the value should be wrapped when it passes min or max;
745  *       both min and max must be present for this to work (default:
746  *       false).
747  */
748 static bool
749 do_property_multiply (const SGPropertyNode * arg)
750 {
751   SGPropertyNode * prop = get_prop(arg);
752   double factor = arg->getDoubleValue("factor", 1);
753
754   double unmodifiable, modifiable;
755   split_value(prop->getDoubleValue(), arg->getStringValue("mask", "all"),
756               &unmodifiable, &modifiable);
757   modifiable *= factor;
758   limit_value(&modifiable, arg);
759
760   prop->setDoubleValue(unmodifiable + modifiable);
761
762   return true;
763 }
764
765
766 /**
767  * Built-in command: swap two property values.
768  *
769  * property[0]: the name of the first property.
770  * property[1]: the name of the second property.
771  */
772 static bool
773 do_property_swap (const SGPropertyNode * arg)
774 {
775   SGPropertyNode * prop1 = get_prop(arg);
776   SGPropertyNode * prop2 = get_prop2(arg);
777
778                                 // FIXME: inefficient
779   const string & tmp = prop1->getStringValue();
780   return (prop1->setUnspecifiedValue(prop2->getStringValue()) &&
781           prop2->setUnspecifiedValue(tmp.c_str()));
782 }
783
784
785 /**
786  * Built-in command: Set a property to an axis or other moving input.
787  *
788  * property: the name of the property to set.
789  * setting: the current input setting, usually between -1.0 and 1.0.
790  * offset: the offset to shift by, before applying the factor.
791  * factor: the factor to multiply by (use negative to reverse).
792  */
793 static bool
794 do_property_scale (const SGPropertyNode * arg)
795 {
796   SGPropertyNode * prop = get_prop(arg);
797   double setting = arg->getDoubleValue("setting");
798   double offset = arg->getDoubleValue("offset", 0.0);
799   double factor = arg->getDoubleValue("factor", 1.0);
800   bool squared = arg->getBoolValue("squared", false);
801   int power = arg->getIntValue("power", (squared ? 2 : 1));
802
803   int sign = (setting < 0 ? -1 : 1);
804
805   switch (power) {
806   case 1:
807       break;
808   case 2:
809       setting = setting * setting * sign;
810       break;
811   case 3:
812       setting = setting * setting * setting;
813       break;
814   case 4:
815       setting = setting * setting * setting * setting * sign;
816       break;
817   default:
818       setting =  pow(setting, power);
819       if ((power % 2) == 0)
820           setting *= sign;
821       break;
822   }
823
824   return prop->setDoubleValue((setting + offset) * factor);
825 }
826
827
828 /**
829  * Built-in command: cycle a property through a set of values.
830  *
831  * If the current value isn't in the list, the cycle will
832  * (re)start from the beginning.
833  *
834  * property: the name of the property to cycle.
835  * value[*]: the list of values to cycle through.
836  */
837 static bool
838 do_property_cycle (const SGPropertyNode * arg)
839 {
840     SGPropertyNode * prop = get_prop(arg);
841     vector<SGPropertyNode_ptr> values = arg->getChildren("value");
842     int selection = -1;
843     int nSelections = values.size();
844
845     if (nSelections < 1) {
846         SG_LOG(SG_GENERAL, SG_ALERT, "No values for property-cycle");
847         return false;
848     }
849
850                                 // Try to find the current selection
851     for (int i = 0; i < nSelections; i++) {
852         if (compare_values(prop, values[i])) {
853             selection = i + 1;
854             break;
855         }
856     }
857
858                                 // Default or wrap to the first selection
859     if (selection < 0 || selection >= nSelections)
860         selection = 0;
861
862     prop->setUnspecifiedValue(values[selection]->getStringValue());
863     return true;
864 }
865
866
867 /**
868  * Built-in command: randomize a numeric property value.
869  *
870  * property: the name of the property value to randomize.
871  * min: the minimum allowed value.
872  * max: the maximum allowed value.
873  */
874 static bool
875 do_property_randomize (const SGPropertyNode * arg)
876 {
877     SGPropertyNode * prop = get_prop(arg);
878     double min = arg->getDoubleValue("min", DBL_MIN);
879     double max = arg->getDoubleValue("max", DBL_MAX);
880     prop->setDoubleValue(sg_random() * (max - min) + min);
881     return true;
882 }
883
884
885 /**
886  * Built-in command: reinit the data logging system based on the
887  * current contents of the /logger tree.
888  */
889 static bool
890 do_data_logging_commit (const SGPropertyNode * arg)
891 {
892     FGLogger *log = (FGLogger *)globals->get_subsystem("logger");
893     log->reinit();
894     return true;
895 }
896
897 /**
898  * Built-in command: Add a dialog to the GUI system.  Does *not*
899  * display the dialog.  The property node should have the same format
900  * as a dialog XML configuration.  It must include:
901  *
902  * name: the name of the GUI dialog for future reference.
903  */
904 static bool
905 do_dialog_new (const SGPropertyNode * arg)
906 {
907     NewGUI * gui = (NewGUI *)globals->get_subsystem("gui");
908
909     // Note the casting away of const: this is *real*.  Doing a
910     // "dialog-apply" command later on will mutate this property node.
911     // I'm not convinced that this isn't the Right Thing though; it
912     // allows client to create a node, pass it to dialog-new, and get
913     // the values back from the dialog by reading the same node.
914     // Perhaps command arguments are not as "const" as they would
915     // seem?
916     gui->newDialog((SGPropertyNode*)arg);
917     return true;
918 }
919
920 /**
921  * Built-in command: Show an XML-configured dialog.
922  *
923  * dialog-name: the name of the GUI dialog to display.
924  */
925 static bool
926 do_dialog_show (const SGPropertyNode * arg)
927 {
928     NewGUI * gui = (NewGUI *)globals->get_subsystem("gui");
929     gui->showDialog(arg->getStringValue("dialog-name"));
930     return true;
931 }
932
933
934 /**
935  * Built-in Command: Hide the active XML-configured dialog.
936  */
937 static bool
938 do_dialog_close (const SGPropertyNode * arg)
939 {
940     NewGUI * gui = (NewGUI *)globals->get_subsystem("gui");
941     if(arg->hasValue("dialog-name"))
942         return gui->closeDialog(arg->getStringValue("dialog-name"));
943     return gui->closeActiveDialog();
944 }
945
946
947 /**
948  * Update a value in the active XML-configured dialog.
949  *
950  * object-name: The name of the GUI object(s) (all GUI objects if omitted).
951  */
952 static bool
953 do_dialog_update (const SGPropertyNode * arg)
954 {
955     NewGUI * gui = (NewGUI *)globals->get_subsystem("gui");
956     FGDialog * dialog = gui->getActiveDialog();
957     if (dialog != 0) {
958         if (arg->hasValue("object-name")) {
959             dialog->updateValue(arg->getStringValue("object-name"));
960         } else {
961             dialog->updateValues();
962         }
963         return true;
964     } else {
965         return false;
966     }
967 }
968
969
970 /**
971  * Apply a value in the active XML-configured dialog.
972  *
973  * object-name: The name of the GUI object(s) (all GUI objects if omitted).
974  */
975 static bool
976 do_dialog_apply (const SGPropertyNode * arg)
977 {
978     NewGUI * gui = (NewGUI *)globals->get_subsystem("gui");
979     FGDialog * dialog = gui->getActiveDialog();
980     if (dialog != 0) {
981         if (arg->hasValue("object-name")) {
982             const char * name = arg->getStringValue("object-name");
983             dialog->applyValue(name);
984             dialog->updateValue(name);
985         } else {
986             dialog->applyValues();
987             dialog->updateValues();
988         }
989         return true;
990     } else {
991         return false;
992     }
993 }
994
995
996 /**
997  * Built-in command: commit presets (read from in /sim/presets/)
998  */
999 static bool
1000 do_presets_commit (const SGPropertyNode * arg)
1001 {
1002     // unbind the current fdm state so property changes
1003     // don't get lost when we subsequently delete this fdm
1004     // and create a new one.
1005     cur_fdm_state->unbind();
1006         
1007     // set position from presets
1008     fgInitPosition();
1009
1010     // BusyCursor(0);
1011     fgReInitSubsystems();
1012
1013     globals->get_tile_mgr()->update( fgGetDouble("/environment/visibility-m") );
1014
1015 #if 0
1016     if ( ! fgGetBool("/sim/presets/onground") ) {
1017         fgSetBool( "/sim/freeze/master", true );
1018         fgSetBool( "/sim/freeze/clock", true );
1019     }
1020 #endif
1021
1022     return true;
1023 }
1024
1025 /**
1026  * Built-in command: set log level (0 ... 7)
1027  */
1028 static bool
1029 do_log_level (const SGPropertyNode * arg)
1030 {
1031    sglog().setLogLevels( SG_ALL, (sgDebugPriority)arg->getIntValue() );
1032
1033    return true;
1034 }
1035
1036 /**
1037  * Built-in command: replay the FDR buffer
1038  */
1039 static bool
1040 do_replay (const SGPropertyNode * arg)
1041 {
1042     // freeze the master fdm
1043     fgSetBool( "/sim/freeze/replay", true );
1044
1045     FGReplay *r = (FGReplay *)(globals->get_subsystem( "replay" ));
1046
1047     fgSetDouble( "/sim/replay/start-time", r->get_start_time() );
1048     fgSetDouble( "/sim/replay/end-time", r->get_end_time() );
1049     double duration = fgGetDouble( "/sim/replay/duration" );
1050     if( duration && duration < (r->get_end_time() - r->get_start_time()) ) {
1051         fgSetDouble( "/sim/replay/time", r->get_end_time() - duration );
1052     } else {
1053         fgSetDouble( "/sim/replay/time", r->get_start_time() );
1054     }
1055
1056     cout << "start = " << r->get_start_time()
1057          << "  end = " << r->get_end_time() << endl;
1058
1059     return true;
1060 }
1061
1062
1063
1064 static bool
1065 do_decrease_visibility (const SGPropertyNode * arg)
1066 {
1067     double new_value = fgGetDouble("/environment/visibility-m") * 0.9;
1068     fgSetDouble("/environment/visibility-m", new_value);
1069     fgDefaultWeatherValue("visibility-m", new_value);
1070     globals->get_subsystem("environment")->reinit();
1071
1072     return true;
1073 }
1074  
1075 static bool
1076 do_increase_visibility (const SGPropertyNode * arg)
1077 {
1078     double new_value = fgGetDouble("/environment/visibility-m") * 1.1;
1079     fgSetDouble("/environment/visibility-m", new_value);
1080     fgDefaultWeatherValue("visibility-m", new_value);
1081     globals->get_subsystem("environment")->reinit();
1082
1083     return true;
1084 }
1085
1086
1087 ////////////////////////////////////////////////////////////////////////
1088 // Command setup.
1089 ////////////////////////////////////////////////////////////////////////
1090
1091
1092 /**
1093  * Table of built-in commands.
1094  *
1095  * New commands do not have to be added here; any module in the application
1096  * can add a new command using globals->get_commands()->addCommand(...).
1097  */
1098 static struct {
1099   const char * name;
1100   SGCommandMgr::command_t command;
1101 } built_ins [] = {
1102     { "null", do_null },
1103 #if defined(HAVE_PLIB_PSL)
1104     { "script", do_script },
1105 #endif // HAVE_PLIB_PSL
1106     { "nasal", do_nasal },
1107     { "exit", do_exit },
1108     { "reinit", do_reinit },
1109     { "suspend", do_reinit },
1110     { "resume", do_reinit },
1111     { "load", do_load },
1112     { "save", do_save },
1113     { "panel-load", do_panel_load },
1114     { "panel-mouse-click", do_panel_mouse_click },
1115     { "preferences-load", do_preferences_load },
1116     { "view-cycle", do_view_cycle },
1117     { "screen-capture", do_screen_capture },
1118     { "tile-cache-reload", do_tile_cache_reload },
1119     { "set-sea-level-air-temp-degc", do_set_sea_level_degc },
1120     { "set-outside-air-temp-degc", do_set_oat_degc },
1121     { "timeofday", do_timeofday },
1122     { "property-toggle", do_property_toggle },
1123     { "property-assign", do_property_assign },
1124     { "property-adjust", do_property_adjust },
1125     { "property-multiply", do_property_multiply },
1126     { "property-swap", do_property_swap },
1127     { "property-scale", do_property_scale },
1128     { "property-cycle", do_property_cycle },
1129     { "property-randomize", do_property_randomize },
1130     { "data-logging-commit", do_data_logging_commit },
1131     { "dialog-new", do_dialog_new },
1132     { "dialog-show", do_dialog_show },
1133     { "dialog-close", do_dialog_close },
1134     { "dialog-show", do_dialog_show },
1135     { "dialog-update", do_dialog_update },
1136     { "dialog-apply", do_dialog_apply },
1137     { "presets-commit", do_presets_commit },
1138     { "log-level", do_log_level },
1139     { "replay", do_replay },
1140     { "decrease-visibility", do_decrease_visibility },
1141     { "increase-visibility", do_increase_visibility },
1142     { 0, 0 }                    // zero-terminated
1143 };
1144
1145
1146 /**
1147  * Initialize the default built-in commands.
1148  *
1149  * Other commands may be added by other parts of the application.
1150  */
1151 void
1152 fgInitCommands ()
1153 {
1154   SG_LOG(SG_GENERAL, SG_INFO, "Initializing basic built-in commands:");
1155   for (int i = 0; built_ins[i].name != 0; i++) {
1156     SG_LOG(SG_GENERAL, SG_INFO, "  " << built_ins[i].name);
1157     globals->get_commands()->addCommand(built_ins[i].name,
1158                                         built_ins[i].command);
1159   }
1160
1161   typedef bool (*dummy)();
1162   fgTie( "/command/view/next", dummy(0), do_view_next );
1163   fgTie( "/command/view/prev", dummy(0), do_view_prev );
1164 }
1165
1166 // end of fg_commands.cxx