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