FlightGear next
AIManager.cxx
Go to the documentation of this file.
1/*
2 * SPDX-FileName: AIManager.cxx
3 * SPDX-FileComment: a global management type for AI objects, based on David Luff's AIMgr
4 * SPDX-FileCopyrightText: Written by David Culp, started October 2003 - davidculp2@comcast.net
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 */
7
8#include <algorithm>
9#include <cstring>
10
11#include <simgear/debug/ErrorReportingCallback.hxx>
12#include <simgear/math/sg_geodesy.hxx>
13#include <simgear/props/props_io.hxx>
14#include <simgear/sg_inlines.h>
15#include <simgear/structure/SGBinding.hxx>
16#include <simgear/structure/commands.hxx>
17#include <simgear/structure/exception.hxx>
18
20#include <Airports/airport.hxx>
21#include <Main/fg_props.hxx>
22#include <Main/globals.hxx>
25
26#include "AIAircraft.hxx"
27#include "AIBallistic.hxx"
28#include "AICarrier.hxx"
29#include "AIEscort.hxx"
30#include "AIGroundVehicle.hxx"
31#include "AIManager.hxx"
32#include "AIMultiplayer.hxx"
33#include "AIShip.hxx"
34#include "AIStatic.hxx"
35#include "AIStorm.hxx"
36#include "AITanker.hxx"
37#include "AIThermal.hxx"
38#include "AIWingman.hxx"
39
40
42
44{
45public:
46 Scenario(FGAIManager* man, const std::string& nm, SGPropertyNode* scenarios) : _internalName(nm)
47 {
48 simgear::ErrorReportContext ec("scenario-name", _internalName);
49 for (auto scEntry : scenarios->getChildren("entry")) {
50 FGAIBasePtr ai = man->addObject(scEntry);
51 if (ai) {
52 _objects.push_back(ai);
53 }
54 } // of scenario entry iteration
55
56 SGPropertyNode* nasalScripts = scenarios->getChild("nasal");
57 if (!nasalScripts) {
58 return;
59 }
60
61 _unloadScript = nasalScripts->getStringValue("unload");
62 std::string loadScript = nasalScripts->getStringValue("load");
63 if (!loadScript.empty()) {
64 auto nasalSys = globals->get_subsystem<FGNasalSys>();
65 std::string moduleName = "scenario_" + _internalName;
66 bool ok = nasalSys->createModule(moduleName.c_str(), moduleName.c_str(),
67 loadScript.c_str(), loadScript.size(),
68 nullptr);
69
70 if (!ok) {
71 // TODO: get the Nasal errors logged properly
72 simgear::reportFailure(simgear::LoadFailure::BadData, simgear::ErrorCode::ScenarioLoad,
73 "Failed to parse scenario Nasal");
74 }
75 }
76 }
77
79 {
80 std::for_each(_objects.begin(), _objects.end(),
81 [](FGAIBasePtr ai) { ai->setDie(true); });
82
83
84 auto nasalSys = globals->get_subsystem<FGNasalSys>();
85 if (!nasalSys) // happens during shutdown / reset
86 return;
87
88 std::string moduleName = "scenario_" + _internalName;
89 if (!_unloadScript.empty()) {
90 nasalSys->createModule(moduleName.c_str(), moduleName.c_str(),
91 _unloadScript.c_str(), _unloadScript.size(),
92 nullptr);
93 }
94
95 nasalSys->deleteModule(moduleName.c_str());
96 }
97
98private:
99 std::vector<FGAIBasePtr> _objects;
100 std::string _internalName;
101 std::string _unloadScript;
102};
103
105
106FGAIManager::FGAIManager() : cb_ai_bare(SGPropertyChangeCallback<FGAIManager>(this, &FGAIManager::updateLOD,
107 fgGetNode("/sim/rendering/static-lod/aimp-bare", true))),
108 cb_ai_detailed(SGPropertyChangeCallback<FGAIManager>(this, &FGAIManager::updateLOD,
109 fgGetNode("/sim/rendering/static-lod/aimp-detailed", true))),
110 cb_interior(SGPropertyChangeCallback<FGAIManager>(this, &FGAIManager::updateLOD,
111 fgGetNode("/sim/rendering/static-lod/aimp-interior", true)))
112{
113}
114
116{
117 std::for_each(ai_list.begin(), ai_list.end(), std::mem_fn(&FGAIBase::unbind));
118}
119
121{
122 root = fgGetNode("sim/ai", true);
123
124 enabled = root->getNode("enabled", true);
125
126 thermal_lift_node = fgGetNode("/environment/thermal-lift-fps", true);
127 wind_from_east_node = fgGetNode("/environment/wind-from-east-fps", true);
128 wind_from_north_node = fgGetNode("/environment/wind-from-north-fps", true);
129
130 user_altitude_agl_node = fgGetNode("/position/altitude-agl-ft", true);
131 user_speed_node = fgGetNode("/velocities/uBody-fps", true);
132
133 globals->get_commands()->addCommand("load-scenario", this, &FGAIManager::loadScenarioCommand);
134 globals->get_commands()->addCommand("unload-scenario", this, &FGAIManager::unloadScenarioCommand);
135 globals->get_commands()->addCommand("add-aiobject", this, &FGAIManager::addObjectCommand);
136 globals->get_commands()->addCommand("remove-aiobject", this, &FGAIManager::removeObjectCommand);
137 _environmentVisiblity = fgGetNode("/environment/visibility-m");
138 _groundSpeedKts_node = fgGetNode("/velocities/groundspeed-kt", true);
139
140 // Create an (invisible) AIAircraft representation of the current
141 // users's aircraft, that mimicks the user aircraft's behavior.
142
143 _userAircraft = new FGAIAircraft;
144 _userAircraft->setCallSign(fgGetString("/sim/multiplay/callsign"));
145 _userAircraft->setGeodPos(globals->get_aircraft_position());
146 _userAircraft->setPerformance("", "jet_transport");
147 _userAircraft->setHeading(fgGetDouble("/orientation/heading-deg"));
148 _userAircraft->setSpeed(_groundSpeedKts_node->getDoubleValue());
149
150 // radar properties
151 _simRadarControl = fgGetNode("/sim/controls/radar", true);
152 if (!_simRadarControl->hasValue()) {
153 // default to true, but only if not already set
154 _simRadarControl->setBoolValue(true);
155 }
156 _radarRangeNode = fgGetNode("/instrumentation/radar/range", true);
157 _radarDebugNode = fgGetNode("/instrumentation/radar/debug-mode", true);
158
159 // register scenarios if we didn't do it already
161}
162
163void FGAIManager::registerScenarios(SGPropertyNode_ptr root)
164{
165 if (!root) {
166 // depending on if we're using a carrier startup, this function may get
167 // called early or during normal FGAIManager init, so guard against double
168 // invocation.
169 // we clear this flag on shutdown so reset works as expected
171 return;
172
174 root = globals->get_props();
175 }
176
177 // find all scenarios at standard locations (for driving the GUI)
178 std::vector<SGPath> scenarioSearchPaths;
179 scenarioSearchPaths.push_back(globals->get_fg_root() / "AI");
180 scenarioSearchPaths.push_back(globals->get_fg_home() / "Scenarios");
181 scenarioSearchPaths.push_back(SGPath(fgGetString("/sim/aircraft-dir")) / "Scenarios");
182
183 // add-on scenario directories
184 const auto& addonsManager = flightgear::addons::AddonManager::instance();
185 if (addonsManager) {
186 auto coll = addonsManager->registeredAddons();
187 std::transform(coll.begin(), coll.end(), std::back_inserter(scenarioSearchPaths),
189 return a->getBasePath() / "Scenarios";
190 });
191#if 0
192 for (auto a : addonsManager->registeredAddons()) {
193 scenarioSearchPaths.push_back(a->getBasePath() / "Scenarios");
194 }
195#endif
196 }
197
198 SGPropertyNode_ptr scenariosNode = root->getNode("/sim/ai/scenarios", true);
199 for (auto p : scenarioSearchPaths) {
200 if (!p.exists())
201 continue;
202
203 simgear::Dir dir(p);
204 for (auto xmlPath : dir.children(simgear::Dir::TYPE_FILE, ".xml")) {
205 registerScenarioFile(root, xmlPath);
206 } // of xml files in the scenario dir iteration
207 } // of scenario dirs iteration
208}
209
210SGPropertyNode_ptr FGAIManager::registerScenarioFile(SGPropertyNode_ptr root, const SGPath& xmlPath)
211{
212 if (!xmlPath.exists()) return {};
213
214 auto scenariosNode = root->getNode("/sim/ai/scenarios", true);
215 SGPropertyNode_ptr sNode;
216
217 simgear::ErrorReportContext ectx("scenario-path", xmlPath.utf8Str());
218
219 try {
220 SGPropertyNode_ptr scenarioProps(new SGPropertyNode);
221 readProperties(xmlPath, scenarioProps);
222
223 for (auto xs : scenarioProps->getChildren("scenario")) {
224 if (!xs->hasChild("name") || !xs->hasChild("description")) {
225 SG_LOG(SG_AI, SG_DEV_WARN, "Scenario is missing name/description:" << xmlPath);
226 }
227
228 sNode = scenariosNode->addChild("scenario");
229
230 const auto bareName = xmlPath.file_base();
231 sNode->setStringValue("id", bareName);
232 sNode->setStringValue("path", xmlPath.utf8Str());
233
234 if (xs->hasChild("name")) {
235 sNode->setStringValue("name", xs->getStringValue("name"));
236 } else {
237 auto cleanedName = bareName;
238 // replace _ and - in bareName with spaces
239 // auto s = simgear::strutils::srep
240 sNode->setStringValue("name", cleanedName);
241 }
242
243 if (xs->hasChild("description")) {
244 sNode->setStringValue("description", xs->getStringValue("description"));
245 }
246
248 } // of scenarios in the XML file
249 } catch (sg_exception& e) {
250 SG_LOG(SG_AI, SG_WARN, "Skipping malformed scenario file:" << xmlPath);
251 simgear::reportFailure(simgear::LoadFailure::BadData, simgear::ErrorCode::ScenarioLoad,
252 std::string{"The scenario couldn't be loaded:"} + e.getFormattedMessage(),
253 e.getLocation());
254 sNode.reset();
255 }
256
257 return sNode;
258}
259
261{
262 // postinit, so that it can access the Nasal subsystem
263
264 // scenarios enabled, AI subsystem required
265 if (!enabled->getBoolValue())
266 enabled->setBoolValue(true);
267
268 // process all scenarios
269 for (auto n : root->getChildren("scenario")) {
270 const std::string& name = n->getStringValue();
271 if (name.empty())
272 continue;
273
274 if (_scenarios.find(name) != _scenarios.end()) {
275 SG_LOG(SG_AI, SG_DEV_WARN, "won't load scenario '" << name << "' twice");
276 continue;
277 }
278
279 SG_LOG(SG_AI, SG_INFO, "loading scenario '" << name << '\'');
281 }
282}
283
285{
286 // shutdown scenarios
287 unloadAllScenarios();
288
289 update(0.0);
290 std::for_each(ai_list.begin(), ai_list.end(), std::mem_fn(&FGAIBase::reinit));
291
292 // (re-)load scenarios
293 postinit();
294}
295
297{
298 unloadAllScenarios();
299
300 for (FGAIBase* ai : ai_list) {
301 // other subsystems, especially ATC, may have references. This
302 // lets them detect if the AI object should be skipped
303 ai->setDie(true);
304 ai->unbind();
305 }
306
307 ai_list.clear();
308 _environmentVisiblity.clear();
309
310 if (_userAircraft) {
311 _userAircraft->setDie(true);
312 // we can't unbind() but we do need to clear these
313 _userAircraft->clearATCController();
314 _userAircraft.clear();
315 }
316
318
319 globals->get_commands()->removeCommand("load-scenario");
320 globals->get_commands()->removeCommand("unload-scenario");
321 globals->get_commands()->removeCommand("add-aiobject");
322 globals->get_commands()->removeCommand("remove-aiobject");
323}
324
326{
327 root = globals->get_props()->getNode("ai/models", true);
328 root->tie("count", SGRawValueMethods<FGAIManager, int>(*this,
329 &FGAIManager::getNumAiObjects));
330}
331
333{
334 root->untie("count");
335}
336
337void FGAIManager::removeDeadItem(FGAIBase* base)
338{
339 SGPropertyNode* props = base->_getProps();
340
341 props->setBoolValue("valid", false);
342 base->unbind();
343
344 // for backward compatibility reset properties, so that aircraft,
345 // which don't know the <valid> property, keep working
346 // TODO: remove after a while
347 props->setIntValue("id", -1);
348 props->setBoolValue("radar/in-range", false);
349 props->setIntValue("refuel/tanker", false);
350}
351
352void FGAIManager::update(double dt)
353{
354 // initialize these for finding nearest thermals
355 range_nearest = 10000.0;
356 strength = 0.0;
357
358 if (!enabled->getBoolValue())
359 return;
360
361 fetchUserState(dt);
362
363 // fetch radar state. Ensure we only do this once per frame.
364 _radarEnabled = _simRadarControl->getBoolValue();
365 _radarDebugMode = _radarDebugNode->getBoolValue();
366 _radarRangeM = _radarRangeNode->getDoubleValue() * SG_NM_TO_METER;
367
368 // partition the list into dead followed by alive
369 auto firstAlive =
370 std::stable_partition(ai_list.begin(), ai_list.end(), std::mem_fn(&FGAIBase::getDie));
371
372 // clean up each item and finally remove from the container
373 for (auto it = ai_list.begin(); it != firstAlive; ++it) {
374 removeDeadItem(*it);
375 }
376
377 ai_list.erase(ai_list.begin(), firstAlive);
378
379 // every remaining item is alive. update them in turn, but guard for
380 // exceptions, so a single misbehaving AI object doesn't bring down the
381 // entire subsystem.
382 for (FGAIBase* base : ai_list) {
383 try {
385 processThermal(dt, static_cast<FGAIThermal*>(base));
386 } else {
387 base->update(dt);
388 }
389 } catch (sg_exception& e) {
390 SG_LOG(SG_AI, SG_WARN, "caught exception updating AI model:" << base->_getName() << ", which will be killed."
391 "\n\tError:"
392 << e.getFormattedMessage());
393 base->setDie(true);
394 }
395 } // of live AI objects iteration
396
397 thermal_lift_node->setDoubleValue(strength); // for thermals
398}
399
401void FGAIManager::updateLOD(SGPropertyNode* node)
402{
403 SG_UNUSED(node);
404 std::for_each(ai_list.begin(), ai_list.end(), std::mem_fn(&FGAIBase::updateLOD));
405}
406
407void FGAIManager::attach(const SGSharedPtr<FGAIBase>& model)
408{
409 std::string_view typeString = model->getTypeString();
410 SGPropertyNode* l_root = globals->get_props()->getNode("ai/models", true);
411 SGPropertyNode* p;
412 int i;
413
414 // find free index in the property tree, if we have
415 // more than 10000 mp-aircraft in the property tree we should optimize the mp-server
416 for (i = 0; i < 10000; i++) {
417 p = l_root->getNode(static_cast<std::string>(typeString), i, false);
418
419 if (!p || !p->getBoolValue("valid", false))
420 break;
421
422 if (p->getIntValue("id", -1) == model->getID()) {
423 p->setStringValue("callsign", "***invalid node***"); //debug only, should never set!
424 }
425 }
426
427 p = l_root->getNode(static_cast<std::string>(typeString), i, true);
428 model->setManager(this, p);
429 ai_list.push_back(model);
430
431 model->init(model->getSearchOrder());
432 model->bind();
433 p->setBoolValue("valid", true);
434}
435
436bool FGAIManager::isVisible(const SGGeod& pos) const
437{
438 double visibility_meters = _environmentVisiblity->getDoubleValue();
439 return (dist(globals->get_view_position_cart(), SGVec3d::fromGeod(pos))) <= visibility_meters;
440}
441
442int FGAIManager::getNumAiObjects() const
443{
444 return static_cast<int>(ai_list.size());
445}
446
447void FGAIManager::fetchUserState(double dt)
448{
449 globals->get_aircraft_orientation(user_heading, user_pitch, user_roll);
450 user_speed = user_speed_node->getDoubleValue() * 0.592484;
451 wind_from_east = wind_from_east_node->getDoubleValue();
452 wind_from_north = wind_from_north_node->getDoubleValue();
453 user_altitude_agl = user_altitude_agl_node->getDoubleValue();
454
455 _userAircraft->setGeodPos(globals->get_aircraft_position());
456 _userAircraft->setHeading(user_heading);
457 _userAircraft->setSpeed(_groundSpeedKts_node->getDoubleValue());
458 _userAircraft->update(dt);
459}
460
461// only keep the results from the nearest thermal
462void FGAIManager::processThermal(double dt, FGAIThermal* thermal)
463{
464 thermal->update(dt);
465
466 if (thermal->_getRange() < range_nearest) {
467 range_nearest = thermal->_getRange();
468 strength = thermal->getStrength();
469 }
470}
471
472bool FGAIManager::loadScenarioCommand(const SGPropertyNode* args, SGPropertyNode*)
473{
474 std::string name = args->getStringValue("name");
475 if (args->hasChild("load-property")) {
476 // slightly ugly, to simplify life in the dialogs, make load allow
477 // loading or unloading based on a bool property.
478 bool loadIt = fgGetBool(args->getStringValue("load-property"));
479 if (!loadIt) {
480 // user actually wants to unload, fine.
481 return unloadScenario(name);
482 }
483 }
484
485 if (_scenarios.find(name) != _scenarios.end()) {
486 SG_LOG(SG_AI, SG_WARN, "scenario '" << name << "' already loaded");
487 return false;
488 }
489
490 bool ok = loadScenario(name);
491 if (ok) {
492 // create /sim/ai node for consistency
493 SGPropertyNode* scenarioNode = root->addChild("scenario");
494 scenarioNode->setStringValue(name);
495 }
496
497 return ok;
498}
499
500bool FGAIManager::unloadScenarioCommand(const SGPropertyNode* arg, SGPropertyNode* root)
501{
502 SG_UNUSED(root);
503 std::string name = arg->getStringValue("name");
504 return unloadScenario(name);
505}
506
507bool FGAIManager::addObjectCommand(const SGPropertyNode* arg, const SGPropertyNode* root)
508{
509 SG_UNUSED(root);
510 if (!arg) {
511 return false;
512 }
513 addObject(arg);
514 return true;
515}
516
517FGAIBasePtr FGAIManager::addObject(const SGPropertyNode* definition)
518{
519 const std::string& type = definition->getStringValue("type", "aircraft");
520
521 FGAIBase* ai = nullptr;
522 if (type == "tanker") { // refueling scenarios
523 ai = new FGAITanker;
524 } else if (type == "wingman") {
525 ai = new FGAIWingman;
526 } else if (type == "aircraft") {
527 ai = new FGAIAircraft;
528 } else if (type == "ship") {
529 ai = new FGAIShip;
530 } else if (type == "carrier") {
531 ai = new FGAICarrier;
532 } else if (type == "groundvehicle") {
533 ai = new FGAIGroundVehicle;
534 } else if (type == "escort") {
535 ai = new FGAIEscort;
536 } else if (type == "thunderstorm") {
537 ai = new FGAIStorm;
538 } else if (type == "thermal") {
539 ai = new FGAIThermal;
540 } else if (type == "ballistic") {
541 ai = new FGAIBallistic;
542 } else if (type == "static") {
543 ai = new FGAIStatic;
544 }
545
546 ai->readFromScenario(const_cast<SGPropertyNode*>(definition));
547 if ((ai->isValid())) {
548 attach(ai);
549 SG_LOG(SG_AI, SG_DEBUG, "attached scenario " << ai->_getName());
550 } else {
551 ai->setDie(true);
552 SG_LOG(SG_AI, SG_ALERT, "killed invalid scenario " << ai->_getName());
553 }
554 return ai;
555}
556
557bool FGAIManager::removeObjectCommand(const SGPropertyNode* arg, const SGPropertyNode* root)
558{
559 SG_UNUSED(root);
560 if (!arg) {
561 return false;
562 }
563 return removeObject(arg);
564}
565
566bool FGAIManager::removeObject(const SGPropertyNode* args)
567{
568 int id = args->getIntValue("id");
569 auto coll = get_ai_list();
570 auto it_ai = std::find_if(coll.begin(), coll.end(), [id](FGAIBasePtr ai) {
571 return ai->getID() == id;
572 });
573 if (it_ai != coll.end())
574 (*it_ai)->setDie(true);
575
576#if 0
577 for (FGAIBase* ai : get_ai_list()) {
578 if (ai->getID() == id) {
579 ai->setDie(true);
580 break;
581 }
582 }
583#endif
584
585 return false;
586}
587
588FGAIBasePtr FGAIManager::getObjectFromProperty(const SGPropertyNode* aProp) const
589{
590 auto it = std::find_if(ai_list.begin(), ai_list.end(),
591 [aProp](FGAIBasePtr ai) { return ai->_getProps() == aProp; });
592 if (it == ai_list.end()) {
593 return nullptr;
594 }
595 return *it;
596}
597
598bool FGAIManager::loadScenario(const std::string& id)
599{
600 SGPath path;
601 SGPropertyNode_ptr file = loadScenarioFile(id, path);
602 if (!file) {
603 return false;
604 }
605
606 simgear::ErrorReportContext ec("scenario-path", path.utf8Str());
607 SGPropertyNode_ptr scNode = file->getChild("scenario");
608 if (!scNode) {
609 simgear::reportFailure(simgear::LoadFailure::Misconfigured,
610 simgear::ErrorCode::ScenarioLoad,
611 "No <scenario> element in file", path);
612 return false;
613 }
614
615 assert(_scenarios.find(id) == _scenarios.end());
616 _scenarios[id] = new Scenario(this, id, scNode);
617 return true;
618}
619
620
621bool FGAIManager::unloadScenario(const std::string& filename)
622{
623 auto it = _scenarios.find(filename);
624 if (it == _scenarios.end()) {
625 SG_LOG(SG_AI, SG_WARN, "unload scenario: not found:" << filename);
626 return false;
627 }
628
629 // remove /sim/ai node
630 for (auto n : root->getChildren("scenario")) {
631 if (n->getStringValue() == filename) {
632 root->removeChild(n);
633 break;
634 }
635 }
636
637 delete it->second;
638 _scenarios.erase(it);
639 return true;
640}
641
642void FGAIManager::unloadAllScenarios()
643{
644 std::for_each(_scenarios.begin(), _scenarios.end(),
645 [](const ScenarioDict::value_type& v) { delete v.second; });
646 // remove /sim/ai node
647 if (root) {
648 root->removeChildren("scenario");
649 }
650 _scenarios.clear();
651}
652
653
654SGPropertyNode_ptr
655FGAIManager::loadScenarioFile(const std::string& scenarioName, SGPath& outPath)
656{
657 auto s = fgGetNode("/sim/ai/scenarios");
658 if (!s) return {};
659
660 for (auto n : s->getChildren("scenario")) {
661 if (n->getStringValue("id") == scenarioName) {
662 SGPath path{n->getStringValue("path")};
663 outPath = path;
664 simgear::ErrorReportContext ec("scenario-path", path.utf8Str());
665 try {
666 SGPropertyNode_ptr root = new SGPropertyNode;
667 readProperties(path, root);
668 return root;
669 } catch (const sg_exception& t) {
670 SG_LOG(SG_AI, SG_ALERT, "Failed to load scenario '" << path << "': " << t.getFormattedMessage());
671 simgear::reportFailure(simgear::LoadFailure::BadData, simgear::ErrorCode::ScenarioLoad,
672 "Failed to laod scenario XML:" + t.getFormattedMessage(),
673 t.getLocation());
674 }
675 }
676 }
677
678 return {};
679}
680
681const FGAIBase*
682FGAIManager::calcCollision(double alt, double lat, double lon, double fuse_range)
683{
684 ai_list_iterator ai_list_itr = ai_list.begin();
685 ai_list_iterator end = ai_list.end();
686
687 SGGeod pos(SGGeod::fromDegFt(lon, lat, alt));
688 SGVec3d cartPos(SGVec3d::fromGeod(pos));
689
690 while (ai_list_itr != end) {
691 FGAIBasePtr aiModel = *ai_list_itr;
692 FGAIBase::object_type type = aiModel->getType();
693 double tgt_alt = aiModel->_getAltitude();
694 int l_tgt_ht = aiModel->getCollisionHeight() + fuse_range;
695
696 if (fabs(tgt_alt - alt) > l_tgt_ht || type == FGAIBase::object_type::otBallistic || type == FGAIBase::object_type::otStorm || type == FGAIBase::object_type::otThermal) {
697 //SG_LOG(SG_AI, SG_DEBUG, "AIManager: skipping "
698 // << fabs(tgt_alt - alt)
699 // << " "
700 // << type
701 // );
702 ++ai_list_itr;
703 continue;
704 }
705
706 int id = (*ai_list_itr)->getID();
707
708 double range = calcRangeFt(cartPos, (*ai_list_itr));
709
710 //SG_LOG(SG_AI, SG_DEBUG, "AIManager: AI list size "
711 // << ai_list.size()
712 // << " type " << type
713 // << " ID " << id
714 // << " range " << range
715 // //<< " bearing " << bearing
716 // << " alt " << tgt_alt
717 // );
718
719 int l_tgt_length = aiModel->getCollisionLength() + fuse_range;
720
721 if (range < l_tgt_length) {
722 SG_LOG(SG_AI, SG_DEBUG, "AIManager: HIT! "
723 << " (h:" << l_tgt_ht << ", w:" << l_tgt_length << ")"
724 << " type " << static_cast<int>(type) << " ID " << id << " range " << range << " alt " << tgt_alt);
725 return aiModel.get();
726 }
727 ++ai_list_itr;
728 }
729 return nullptr;
730}
731
732double
733FGAIManager::calcRangeFt(const SGVec3d& aCartPos, const FGAIBase* aObject) const
734{
735 double distM = dist(aCartPos, aObject->getCartPos());
736 return distM * SG_METER_TO_FEET;
737}
738
740{
741 return _userAircraft.get();
742}
743
744// Register the subsystem.
745SGSubsystemMgr::Registrant<FGAIManager> registrantFGAIManager(
746 SGSubsystemMgr::POST_FDM,
747 {{"nasal", SGSubsystemMgr::Dependency::HARD}});
748
749//end AIManager.cxx
static bool static_haveRegisteredScenarios
Definition AIManager.cxx:41
SGSubsystemMgr::Registrant< FGAIManager > registrantFGAIManager(SGSubsystemMgr::POST_FDM, {{"nasal", SGSubsystemMgr::Dependency::HARD}})
SGSharedPtr< FGAIBase > FGAIBasePtr
Definition AIManager.hxx:23
#define p(x)
static FGNasalSys * nasalSys
Definition NasalSys.cxx:82
#define i(x)
virtual void readFromScenario(SGPropertyNode *scFileNode)
Definition AIBase.cxx:249
virtual void unbind()
Definition AIBase.cxx:800
void setDie(bool die)
Definition AIBase.hxx:506
SGVec3d getCartPos() const
Definition AIBase.cxx:899
void setCallSign(const std::string &)
Definition AIBase.hxx:451
SGPropertyNode * _getProps() const
Definition AIBase.cxx:1040
bool isValid() const
Definition AIBase.cxx:1166
void updateLOD()
update LOD properties of the model
Definition AIBase.cxx:388
virtual void update(double dt)
Definition AIBase.cxx:294
bool isa(object_type otype)
Definition AIBase.cxx:709
const char * _getName() const
Definition AIBase.cxx:1136
double _getRange() const
Definition AIBase.hxx:362
bool getDie()
Definition AIBase.hxx:508
virtual void reinit()
Definition AIBase.hxx:74
static void extractCarriersFromScenario(SGPropertyNode_ptr xmlNode, SGPropertyNode_ptr scenario)
for a given scenario node, check for carriers within, and write nodes with names, pennants and initia...
Scenario(FGAIManager *man, const std::string &nm, SGPropertyNode *scenarios)
Definition AIManager.cxx:46
void reinit() override
static SGPropertyNode_ptr loadScenarioFile(const std::string &id, SGPath &outPath)
void unbind() override
FGAIBasePtr getObjectFromProperty(const SGPropertyNode *aProp) const
given a reference to an /ai/models/<foo>[n] node, return the corresponding AIObject implementation,...
static SGPropertyNode_ptr registerScenarioFile(SGPropertyNode_ptr root, const SGPath &p)
void postinit() override
const ai_list_type & get_ai_list() const
Definition AIManager.hxx:77
bool loadScenario(const std::string &id)
void updateLOD(SGPropertyNode *node)
update LOD settings of all AI/MP models
FGAIBasePtr addObject(const SGPropertyNode *definition)
void update(double dt) override
virtual ~FGAIManager()
void init() override
const FGAIBase * calcCollision(double alt, double lat, double lon, double fuse_range)
bool isVisible(const SGGeod &pos) const
double calcRangeFt(const SGVec3d &aCartPos, const FGAIBase *aObject) const
void attach(const SGSharedPtr< FGAIBase > &model)
static void registerScenarios(SGPropertyNode_ptr root={})
Static helper to register scenarios.
void bind() override
FGAIAircraft * getUserAircraft() const
Retrieve the representation of the user's aircraft in the AI manager the position and velocity of thi...
void shutdown() override
An AI tanker for air-air refueling.
Definition AITanker.hxx:27
double getStrength() const
Definition AIThermal.hxx:38
void update(double dt) override
Definition AIThermal.cxx:73
SGGeod get_aircraft_position() const
Definition globals.cxx:611
void get_aircraft_orientation(double &heading, double &pitch, double &roll)
Definition globals.cxx:624
static const std::unique_ptr< AddonManager > & instance()
const char * name
std::string fgGetString(const char *name, const char *defaultValue)
Get a string value for a property.
Definition fg_props.cxx:556
FGGlobals * globals
Definition globals.cxx:142
SGSharedPtr< Addon > AddonRef
Definition addon_fwd.hxx:46
bool fgGetBool(char const *name, bool def)
Get a bool value for a property.
Definition proptest.cpp:25
double fgGetDouble(const char *name, double defaultValue)
Get a double value for a property.
Definition proptest.cpp:30
SGPropertyNode * fgGetNode(const char *path, bool create)
Get a property node.
Definition proptest.cpp:27