]> git.mxchange.org Git - flightgear.git/blob - src/AIModel/AIManager.cxx
[AIModel] Fix a crash when starting at the poles and reduce property reading
[flightgear.git] / src / AIModel / AIManager.cxx
1 // AIManager.cxx  Based on David Luff's AIMgr:
2 // - a global management type for AI objects
3 //
4 // Written by David Culp, started October 2003.
5 // - davidculp2@comcast.net 
6 //
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License as
9 // published by the Free Software Foundation; either version 2 of the
10 // License, or (at your option) any later version.
11 //
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 // General Public License for more details.
16 //
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software
19 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
20
21 #include <cstring>
22 #include <algorithm>
23
24 #include <simgear/sg_inlines.h>
25 #include <simgear/math/sg_geodesy.hxx>
26 #include <simgear/props/props_io.hxx>
27 #include <simgear/structure/exception.hxx>
28 #include <simgear/structure/commands.hxx>
29 #include <simgear/structure/SGBinding.hxx>
30
31 #include <boost/mem_fn.hpp>
32 #include <boost/foreach.hpp>
33
34 #include <Main/globals.hxx>
35 #include <Airports/airport.hxx>
36 #include <Scripting/NasalSys.hxx>
37
38 #include "AIManager.hxx"
39 #include "AIAircraft.hxx"
40 #include "AIShip.hxx"
41 #include "AIBallistic.hxx"
42 #include "AIStorm.hxx"
43 #include "AIThermal.hxx"
44 #include "AICarrier.hxx"
45 #include "AIStatic.hxx"
46 #include "AIMultiplayer.hxx"
47 #include "AITanker.hxx"
48 #include "AIWingman.hxx"
49 #include "AIGroundVehicle.hxx"
50 #include "AIEscort.hxx"
51
52 class FGAIManager::Scenario
53 {
54 public:
55     Scenario(FGAIManager* man, const std::string& nm, SGPropertyNode* scenarios) :
56         _internalName(nm)
57     {
58         BOOST_FOREACH(SGPropertyNode* scEntry, scenarios->getChildren("entry")) {
59             FGAIBasePtr ai = man->addObject(scEntry);
60             if (ai) {
61                 _objects.push_back(ai);
62             }
63         } // of scenario entry iteration
64         
65         SGPropertyNode* nasalScripts = scenarios->getChild("nasal");
66         if (!nasalScripts) {
67             return;
68         }
69         
70         _unloadScript = nasalScripts->getStringValue("unload");
71         std::string loadScript = nasalScripts->getStringValue("load");
72         if (!loadScript.empty()) {
73             FGNasalSys* nasalSys = (FGNasalSys*) globals->get_subsystem("nasal");
74             std::string moduleName = "scenario_" + _internalName;
75             nasalSys->createModule(moduleName.c_str(), moduleName.c_str(),
76                                    loadScript.c_str(), loadScript.size(),
77                                    0);
78         }
79     }
80     
81     ~Scenario()
82     {
83         BOOST_FOREACH(FGAIBasePtr ai, _objects) {
84             ai->setDie(true);
85         }
86         
87         FGNasalSys* nasalSys = (FGNasalSys*) globals->get_subsystem("nasal");
88         if (!nasalSys)
89             return;
90         
91         std::string moduleName = "scenario_" + _internalName;
92         if (!_unloadScript.empty()) {
93             nasalSys->createModule(moduleName.c_str(), moduleName.c_str(),
94                                    _unloadScript.c_str(), _unloadScript.size(),
95                                    0);
96         }
97         
98         nasalSys->deleteModule(moduleName.c_str());
99     }
100 private:
101     std::vector<FGAIBasePtr> _objects;
102     std::string _internalName;
103     std::string _unloadScript;
104 };
105
106 ///////////////////////////////////////////////////////////////////////////////
107
108 FGAIManager::FGAIManager() :
109     cb_ai_bare(SGPropertyChangeCallback<FGAIManager>(this,&FGAIManager::updateLOD,
110                fgGetNode("/sim/rendering/static-lod/ai-bare", true))),
111     cb_ai_detailed(SGPropertyChangeCallback<FGAIManager>(this,&FGAIManager::updateLOD,
112                    fgGetNode("/sim/rendering/static-lod/ai-detailed", true)))
113 {
114
115 }
116
117 FGAIManager::~FGAIManager()
118 {
119     std::for_each(ai_list.begin(), ai_list.end(), boost::mem_fn(&FGAIBase::unbind));
120 }
121
122 void
123 FGAIManager::init() {
124     root = fgGetNode("sim/ai", true);
125
126     enabled = root->getNode("enabled", true);
127
128     thermal_lift_node = fgGetNode("/environment/thermal-lift-fps", true);
129     wind_from_east_node  = fgGetNode("/environment/wind-from-east-fps",true);
130     wind_from_north_node = fgGetNode("/environment/wind-from-north-fps",true);
131
132     user_altitude_agl_node  = fgGetNode("/position/altitude-agl-ft", true);
133     user_speed_node     = fgGetNode("/velocities/uBody-fps", true);
134     
135     globals->get_commands()->addCommand("load-scenario", this, &FGAIManager::loadScenarioCommand);
136     globals->get_commands()->addCommand("unload-scenario", this, &FGAIManager::unloadScenarioCommand);
137     _environmentVisiblity = fgGetNode("/environment/visibility-m");
138 }
139
140 void
141 FGAIManager::postinit()
142 {
143     // postinit, so that it can access the Nasal subsystem
144
145     // scenarios enabled, AI subsystem required
146     if (!enabled->getBoolValue())
147         enabled->setBoolValue(true);
148
149     // process all scenarios
150     BOOST_FOREACH(SGPropertyNode* n, root->getChildren("scenario")) {
151         const string& name = n->getStringValue();
152         if (name.empty())
153             continue;
154
155         if (_scenarios.find(name) != _scenarios.end()) {
156             SG_LOG(SG_AI, SG_WARN, "won't load scenario '" << name << "' twice");
157             continue;
158         }
159
160         SG_LOG(SG_AI, SG_INFO, "loading scenario '" << name << '\'');
161         loadScenario(name);
162     }
163 }
164
165 void
166 FGAIManager::reinit()
167 {
168     // shutdown scenarios
169     unloadAllScenarios();
170     
171     update(0.0);
172     std::for_each(ai_list.begin(), ai_list.end(), boost::mem_fn(&FGAIBase::reinit));
173     
174     // (re-)load scenarios
175     postinit();
176 }
177
178 void
179 FGAIManager::shutdown()
180 {
181     unloadAllScenarios();
182     
183     BOOST_FOREACH(FGAIBase* ai, ai_list) {
184         ai->unbind();
185     }
186     
187     ai_list.clear();
188     _environmentVisiblity.clear();
189     
190     globals->get_commands()->removeCommand("load-scenario");
191     globals->get_commands()->removeCommand("unload-scenario");
192 }
193
194 void
195 FGAIManager::bind() {
196     root = globals->get_props()->getNode("ai/models", true);
197     root->tie("count", SGRawValueMethods<FGAIManager, int>(*this,
198         &FGAIManager::getNumAiObjects));
199 }
200
201 void
202 FGAIManager::unbind() {
203     root->untie("count");
204 }
205
206 void FGAIManager::removeDeadItem(FGAIBase* base)
207 {
208     SGPropertyNode *props = base->_getProps();
209     
210     props->setBoolValue("valid", false);
211     base->unbind();
212     
213     // for backward compatibility reset properties, so that aircraft,
214     // which don't know the <valid> property, keep working
215     // TODO: remove after a while
216     props->setIntValue("id", -1);
217     props->setBoolValue("radar/in-range", false);
218     props->setIntValue("refuel/tanker", false);
219 }
220
221 void
222 FGAIManager::update(double dt) {
223     // initialize these for finding nearest thermals
224     range_nearest = 10000.0;
225     strength = 0.0;
226
227     if (!enabled->getBoolValue())
228         return;
229
230     fetchUserState();
231
232     // partition the list into dead followed by alive
233     ai_list_iterator firstAlive =
234       std::stable_partition(ai_list.begin(), ai_list.end(), boost::mem_fn(&FGAIBase::getDie));
235     
236     // clean up each item and finally remove from the container
237     for (ai_list_iterator it=ai_list.begin(); it != firstAlive; ++it) {
238         removeDeadItem(*it);
239     }
240   
241     ai_list.erase(ai_list.begin(), firstAlive);
242   
243     // every remaining item is alive. update them in turn, but guard for
244     // exceptions, so a single misbehaving AI object doesn't bring down the
245     // entire subsystem.
246     BOOST_FOREACH(FGAIBase* base, ai_list) {
247         try {
248             if (base->isa(FGAIBase::otThermal)) {
249                 processThermal(dt, (FGAIThermal*)base);
250             } else {
251                 base->update(dt);
252             }
253         } catch (sg_exception& e) {
254             SG_LOG(SG_AI, SG_WARN, "caught exception updating AI model:" << base->_getName()<< ", which will be killed."
255                    "\n\tError:" << e.getFormattedMessage());
256             base->setDie(true);
257         }
258     } // of live AI objects iteration
259
260     thermal_lift_node->setDoubleValue( strength );  // for thermals
261 }
262
263 /** update LOD settings of all AI/MP models */
264 void
265 FGAIManager::updateLOD(SGPropertyNode* node)
266 {
267     SG_UNUSED(node);
268     std::for_each(ai_list.begin(), ai_list.end(), boost::mem_fn(&FGAIBase::updateLOD));
269 }
270
271 void
272 FGAIManager::attach(FGAIBase *model)
273 {
274     const char* typeString = model->getTypeString();
275     SGPropertyNode* root = globals->get_props()->getNode("ai/models", true);
276     SGPropertyNode* p;
277     int i;
278
279     // find free index in the property tree, if we have
280     // more than 10000 mp-aircrafts in the property tree we should optimize the mp-server
281     for (i = 0; i < 10000; i++) {
282         p = root->getNode(typeString, i, false);
283
284         if (!p || !p->getBoolValue("valid", false))
285             break;
286
287         if (p->getIntValue("id",-1)==model->getID()) {
288             p->setStringValue("callsign","***invalid node***"); //debug only, should never set!
289         }
290     }
291
292     p = root->getNode(typeString, i, true);
293     model->setManager(this, p);
294     ai_list.push_back(model);
295
296     model->init(model->getType()==FGAIBase::otAircraft
297         || model->getType()==FGAIBase::otMultiplayer
298         || model->getType()==FGAIBase::otStatic);
299     model->bind();
300     p->setBoolValue("valid", true);
301 }
302
303 bool FGAIManager::isVisible(const SGGeod& pos) const
304 {
305   double visibility_meters = _environmentVisiblity->getDoubleValue();
306   return ( dist(globals->get_view_position_cart(), SGVec3d::fromGeod(pos)) ) <= visibility_meters;
307 }
308
309 int
310 FGAIManager::getNumAiObjects() const
311 {
312     return ai_list.size();
313 }
314
315 void
316 FGAIManager::fetchUserState( void ) {
317
318     globals->get_aircraft_orientation(user_heading, user_pitch, user_roll);
319     user_speed     = user_speed_node->getDoubleValue() * 0.592484;
320     wind_from_east = wind_from_east_node->getDoubleValue();
321     wind_from_north   = wind_from_north_node->getDoubleValue();
322     user_altitude_agl = user_altitude_agl_node->getDoubleValue();
323
324 }
325
326 // only keep the results from the nearest thermal
327 void
328 FGAIManager::processThermal( double dt, FGAIThermal* thermal ) {
329     thermal->update(dt);
330
331     if ( thermal->_getRange() < range_nearest ) {
332         range_nearest = thermal->_getRange();
333         strength = thermal->getStrength();
334     }
335
336 }
337
338 bool FGAIManager::loadScenarioCommand(const SGPropertyNode* args)
339 {
340     std::string name = args->getStringValue("name");
341     if (args->hasChild("load-property")) {
342         // slightly ugly, to simplify life in the dialogs, make load allow
343         // loading or unloading based on a bool property.
344         bool loadIt = fgGetBool(args->getStringValue("load-property"));
345         if (!loadIt) {
346             // user actually wants to unload, fine.
347             return unloadScenario(name);
348         }
349     }
350     
351     if (_scenarios.find(name) != _scenarios.end()) {
352         SG_LOG(SG_AI, SG_WARN, "scenario '" << name << "' already loaded");
353         return false;
354     }
355     
356     bool ok = loadScenario(name);
357     if (ok) {
358         // create /sim/ai node for consistency
359         int index = 0;
360         for (; root->hasChild("scenario", index); ++index) {}
361         
362         SGPropertyNode* scenarioNode = root->getChild("scenario", index, true);
363         scenarioNode->setStringValue(name);
364     }
365     
366     return ok;
367 }
368
369 bool FGAIManager::unloadScenarioCommand(const SGPropertyNode* args)
370 {
371     std::string name = args->getStringValue("name");
372     return unloadScenario(name);
373 }
374
375 bool FGAIManager::addObjectCommand(const SGPropertyNode* definition)
376 {
377     addObject(definition);
378     return true;
379 }
380
381 FGAIBasePtr FGAIManager::addObject(const SGPropertyNode* definition)
382 {
383     const std::string& type = definition->getStringValue("type", "aircraft");
384     
385     FGAIBase* ai = NULL;
386     if (type == "tanker") { // refueling scenarios
387         ai = new FGAITanker; 
388     } else if (type == "wingman") {
389         ai = new FGAIWingman;
390     } else if (type == "aircraft") {
391         ai = new FGAIAircraft;
392     } else if (type == "ship") {
393         ai = new FGAIShip;
394     } else if (type == "carrier") {
395         ai = new FGAICarrier;
396     } else if (type == "groundvehicle") {
397         ai = new FGAIGroundVehicle;
398     } else if (type == "escort") {
399         ai = new FGAIEscort;
400     } else if (type == "thunderstorm") {
401         ai = new FGAIStorm;
402     } else if (type == "thermal") {
403         ai = new FGAIThermal;
404     } else if (type == "ballistic") {
405         ai = new FGAIBallistic;
406     } else if (type == "static") {
407         ai = new FGAIStatic;
408     }
409
410     ai->readFromScenario(const_cast<SGPropertyNode*>(definition));
411     attach(ai);
412     return ai;
413 }
414
415 bool FGAIManager::removeObject(const SGPropertyNode* args)
416 {
417     int id = args->getIntValue("id");
418     BOOST_FOREACH(FGAIBase* ai, get_ai_list()) {
419         if (ai->getID() == id) {
420             ai->setDie(true);
421             break;
422         }
423     }
424     
425     return false;
426 }
427
428 FGAIBasePtr FGAIManager::getObjectFromProperty(const SGPropertyNode* aProp) const
429 {
430     BOOST_FOREACH(FGAIBase* ai, get_ai_list()) {
431         if (ai->_getProps() == aProp) {
432             return ai;
433         }
434     } // of AI objects iteration
435     
436     return NULL;
437 }
438
439 bool
440 FGAIManager::loadScenario( const string &filename )
441 {
442     SGPropertyNode_ptr file = loadScenarioFile(filename);
443     if (!file) {
444         return false;
445     }
446     
447     SGPropertyNode_ptr scNode = file->getChild("scenario");
448     if (!scNode) {
449         return false;
450     }
451     
452     _scenarios[filename] = new Scenario(this, filename, scNode);
453     return true;
454 }
455
456
457 bool
458 FGAIManager::unloadScenario( const string &filename)
459 {
460     ScenarioDict::iterator it = _scenarios.find(filename);
461     if (it == _scenarios.end()) {
462         SG_LOG(SG_AI, SG_WARN, "unload scenario: not found:" << filename);
463         return false;
464     }
465     
466 // remove /sim/ai node
467     unsigned int index = 0;
468     for (SGPropertyNode* n = NULL; (n = root->getChild("scenario", index)) != NULL; ++index) {
469         if (n->getStringValue() == filename) {
470             root->removeChild("scenario", index);
471             break;
472         }
473     }
474     
475     delete it->second;
476     _scenarios.erase(it);
477     return true;
478 }
479
480 void
481 FGAIManager::unloadAllScenarios()
482 {
483     ScenarioDict::iterator it = _scenarios.begin();
484     for (; it != _scenarios.end(); ++it) {
485         delete it->second;
486     } // of scenarios iteration
487     
488     
489     // remove /sim/ai node
490     root->removeChildren("scenario");
491     _scenarios.clear();
492 }
493
494
495 SGPropertyNode_ptr
496 FGAIManager::loadScenarioFile(const std::string& filename)
497 {
498     SGPath path(globals->get_fg_root());
499     path.append("AI/" + filename + ".xml");
500     try {
501         SGPropertyNode_ptr root = new SGPropertyNode;
502         readProperties(path.str(), root);
503         return root;
504     } catch (const sg_exception &t) {
505         SG_LOG(SG_AI, SG_ALERT, "Failed to load scenario '"
506             << path.str() << "': " << t.getFormattedMessage());
507     }
508     return 0;
509 }
510
511 bool
512 FGAIManager::getStartPosition(const string& id, const string& pid,
513                               SGGeod& geodPos, double& hdng, SGVec3d& uvw)
514 {
515     bool found = false;
516     SGPropertyNode* root = fgGetNode("sim/ai", true);
517     if (!root->getNode("enabled", true)->getBoolValue())
518         return found;
519
520     for (int i = 0 ; (!found) && i < root->nChildren() ; i++) {
521         SGPropertyNode *aiEntry = root->getChild( i );
522         if ( !strcmp( aiEntry->getName(), "scenario" ) ) {
523             const string& filename = aiEntry->getStringValue();
524             SGPropertyNode_ptr scenarioTop = loadScenarioFile(filename);
525             if (scenarioTop) {
526                 SGPropertyNode* scenarios = scenarioTop->getChild("scenario");
527                 if (scenarios) {
528                     for (int i = 0; i < scenarios->nChildren(); i++) {
529                         SGPropertyNode* scEntry = scenarios->getChild(i);
530                         const std::string& type = scEntry->getStringValue("type");
531                         const std::string& pnumber = scEntry->getStringValue("pennant-number");
532                         const std::string& name = scEntry->getStringValue("name");
533                         if (type == "carrier" && (pnumber == id || name == id)) {
534                             SGSharedPtr<FGAICarrier> carrier = new FGAICarrier;
535                             carrier->readFromScenario(scEntry);
536
537                             if (carrier->getParkPosition(pid, geodPos, hdng, uvw)) {
538                                 found = true;
539                                 break;
540                             }
541                         }
542                     }
543                 }
544             }
545         }
546     }
547     return found;
548 }
549
550 const FGAIBase *
551 FGAIManager::calcCollision(double alt, double lat, double lon, double fuse_range)
552 {
553     // we specify tgt extent (ft) according to the AIObject type
554     double tgt_ht[]     = {0,  50, 100, 250, 0, 100, 0, 0,  50,  50, 20, 100,  50};
555     double tgt_length[] = {0, 100, 200, 750, 0,  50, 0, 0, 200, 100, 40, 200, 100};
556     ai_list_iterator ai_list_itr = ai_list.begin();
557     ai_list_iterator end = ai_list.end();
558
559     SGGeod pos(SGGeod::fromDegFt(lon, lat, alt));
560     SGVec3d cartPos(SGVec3d::fromGeod(pos));
561     
562     while (ai_list_itr != end) {
563         double tgt_alt = (*ai_list_itr)->_getAltitude();
564         int type       = (*ai_list_itr)->getType();
565         tgt_ht[type] += fuse_range;
566
567         if (fabs(tgt_alt - alt) > tgt_ht[type] || type == FGAIBase::otBallistic
568             || type == FGAIBase::otStorm || type == FGAIBase::otThermal ) {
569                 //SG_LOG(SG_AI, SG_DEBUG, "AIManager: skipping "
570                 //    << fabs(tgt_alt - alt)
571                 //    << " "
572                 //    << type
573                 //    );
574                 ++ai_list_itr;
575                 continue;
576         }
577
578         int id         = (*ai_list_itr)->getID();
579
580         double range = calcRangeFt(cartPos, (*ai_list_itr));
581
582         //SG_LOG(SG_AI, SG_DEBUG, "AIManager:  AI list size "
583         //    << ai_list.size()
584         //    << " type " << type
585         //    << " ID " << id
586         //    << " range " << range
587         //    //<< " bearing " << bearing
588         //    << " alt " << tgt_alt
589         //    );
590
591         tgt_length[type] += fuse_range;
592
593         if (range < tgt_length[type]){
594             SG_LOG(SG_AI, SG_DEBUG, "AIManager: HIT! "
595                 << " type " << type
596                 << " ID " << id
597                 << " range " << range
598                 << " alt " << tgt_alt
599                 );
600             return (*ai_list_itr).get();
601         }
602         ++ai_list_itr;
603     }
604     return 0;
605 }
606
607 double
608 FGAIManager::calcRangeFt(const SGVec3d& aCartPos, FGAIBase* aObject) const
609 {
610     double distM = dist(aCartPos, aObject->getCartPos());
611     return distM * SG_METER_TO_FEET;
612 }
613
614 //end AIManager.cxx