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>
48 simgear::ErrorReportContext ec(
"scenario-name", _internalName);
49 for (
auto scEntry : scenarios->getChildren(
"entry")) {
52 _objects.push_back(ai);
56 SGPropertyNode* nasalScripts = scenarios->getChild(
"nasal");
61 _unloadScript = nasalScripts->getStringValue(
"unload");
62 std::string loadScript = nasalScripts->getStringValue(
"load");
63 if (!loadScript.empty()) {
65 std::string moduleName =
"scenario_" + _internalName;
66 bool ok =
nasalSys->createModule(moduleName.c_str(), moduleName.c_str(),
67 loadScript.c_str(), loadScript.size(),
72 simgear::reportFailure(simgear::LoadFailure::BadData, simgear::ErrorCode::ScenarioLoad,
73 "Failed to parse scenario Nasal");
80 std::for_each(_objects.begin(), _objects.end(),
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(),
95 nasalSys->deleteModule(moduleName.c_str());
99 std::vector<FGAIBasePtr> _objects;
100 std::string _internalName;
101 std::string _unloadScript;
107 fgGetNode(
"/sim/rendering/static-lod/aimp-bare", true))),
109 fgGetNode(
"/sim/rendering/static-lod/aimp-detailed", true))),
111 fgGetNode(
"/sim/rendering/static-lod/aimp-interior", true)))
117 std::for_each(ai_list.begin(), ai_list.end(), std::mem_fn(&
FGAIBase::unbind));
124 enabled = root->getNode(
"enabled",
true);
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);
130 user_altitude_agl_node =
fgGetNode(
"/position/altitude-agl-ft",
true);
131 user_speed_node =
fgGetNode(
"/velocities/uBody-fps",
true);
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);
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());
151 _simRadarControl =
fgGetNode(
"/sim/controls/radar",
true);
152 if (!_simRadarControl->hasValue()) {
154 _simRadarControl->setBoolValue(
true);
156 _radarRangeNode =
fgGetNode(
"/instrumentation/radar/range",
true);
157 _radarDebugNode =
fgGetNode(
"/instrumentation/radar/debug-mode",
true);
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");
186 auto coll = addonsManager->registeredAddons();
187 std::transform(coll.begin(), coll.end(), std::back_inserter(scenarioSearchPaths),
189 return a->getBasePath() /
"Scenarios";
192 for (
auto a : addonsManager->registeredAddons()) {
193 scenarioSearchPaths.push_back(a->getBasePath() /
"Scenarios");
198 SGPropertyNode_ptr scenariosNode = root->getNode(
"/sim/ai/scenarios",
true);
199 for (
auto p : scenarioSearchPaths) {
204 for (
auto xmlPath : dir.children(simgear::Dir::TYPE_FILE,
".xml")) {
212 if (!xmlPath.exists())
return {};
214 auto scenariosNode = root->getNode(
"/sim/ai/scenarios",
true);
215 SGPropertyNode_ptr sNode;
217 simgear::ErrorReportContext ectx(
"scenario-path", xmlPath.utf8Str());
220 SGPropertyNode_ptr scenarioProps(
new SGPropertyNode);
221 readProperties(xmlPath, scenarioProps);
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);
228 sNode = scenariosNode->addChild(
"scenario");
230 const auto bareName = xmlPath.file_base();
231 sNode->setStringValue(
"id", bareName);
232 sNode->setStringValue(
"path", xmlPath.utf8Str());
234 if (xs->hasChild(
"name")) {
235 sNode->setStringValue(
"name", xs->getStringValue(
"name"));
237 auto cleanedName = bareName;
240 sNode->setStringValue(
"name", cleanedName);
243 if (xs->hasChild(
"description")) {
244 sNode->setStringValue(
"description", xs->getStringValue(
"description"));
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(),
265 if (!enabled->getBoolValue())
266 enabled->setBoolValue(
true);
269 for (
auto n : root->getChildren(
"scenario")) {
270 const std::string&
name = n->getStringValue();
274 if (_scenarios.find(
name) != _scenarios.end()) {
275 SG_LOG(SG_AI, SG_DEV_WARN,
"won't load scenario '" <<
name <<
"' twice");
279 SG_LOG(SG_AI, SG_INFO,
"loading scenario '" <<
name <<
'\'');
287 unloadAllScenarios();
290 std::for_each(ai_list.begin(), ai_list.end(), std::mem_fn(&
FGAIBase::reinit));
298 unloadAllScenarios();
308 _environmentVisiblity.clear();
311 _userAircraft->setDie(
true);
313 _userAircraft->clearATCController();
314 _userAircraft.clear();
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");
327 root =
globals->get_props()->getNode(
"ai/models",
true);
328 root->tie(
"count", SGRawValueMethods<FGAIManager, int>(*
this,
329 &FGAIManager::getNumAiObjects));
334 root->untie(
"count");
337void FGAIManager::removeDeadItem(
FGAIBase* base)
339 SGPropertyNode* props = base->
_getProps();
341 props->setBoolValue(
"valid",
false);
347 props->setIntValue(
"id", -1);
348 props->setBoolValue(
"radar/in-range",
false);
349 props->setIntValue(
"refuel/tanker",
false);
355 range_nearest = 10000.0;
358 if (!enabled->getBoolValue())
364 _radarEnabled = _simRadarControl->getBoolValue();
365 _radarDebugMode = _radarDebugNode->getBoolValue();
366 _radarRangeM = _radarRangeNode->getDoubleValue() * SG_NM_TO_METER;
370 std::stable_partition(ai_list.begin(), ai_list.end(), std::mem_fn(&
FGAIBase::getDie));
373 for (
auto it = ai_list.begin(); it != firstAlive; ++it) {
377 ai_list.erase(ai_list.begin(), firstAlive);
385 processThermal(dt,
static_cast<FGAIThermal*
>(base));
389 }
catch (sg_exception& e) {
390 SG_LOG(SG_AI, SG_WARN,
"caught exception updating AI model:" << base->
_getName() <<
", which will be killed."
392 << e.getFormattedMessage());
397 thermal_lift_node->setDoubleValue(strength);
409 std::string_view typeString = model->getTypeString();
410 SGPropertyNode* l_root =
globals->get_props()->getNode(
"ai/models",
true);
416 for (
i = 0;
i < 10000;
i++) {
417 p = l_root->getNode(
static_cast<std::string
>(typeString),
i,
false);
419 if (!
p || !
p->getBoolValue(
"valid",
false))
422 if (
p->getIntValue(
"id", -1) == model->getID()) {
423 p->setStringValue(
"callsign",
"***invalid node***");
427 p = l_root->getNode(
static_cast<std::string
>(typeString),
i,
true);
428 model->setManager(
this,
p);
429 ai_list.push_back(model);
431 model->init(model->getSearchOrder());
433 p->setBoolValue(
"valid",
true);
438 double visibility_meters = _environmentVisiblity->getDoubleValue();
439 return (dist(
globals->get_view_position_cart(), SGVec3d::fromGeod(pos))) <= visibility_meters;
442int FGAIManager::getNumAiObjects()
const
444 return static_cast<int>(ai_list.size());
447void FGAIManager::fetchUserState(
double dt)
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();
456 _userAircraft->setHeading(user_heading);
457 _userAircraft->setSpeed(_groundSpeedKts_node->getDoubleValue());
458 _userAircraft->update(dt);
462void FGAIManager::processThermal(
double dt,
FGAIThermal* thermal)
466 if (thermal->
_getRange() < range_nearest) {
472bool FGAIManager::loadScenarioCommand(
const SGPropertyNode* args, SGPropertyNode*)
474 std::string
name = args->getStringValue(
"name");
475 if (args->hasChild(
"load-property")) {
478 bool loadIt =
fgGetBool(args->getStringValue(
"load-property"));
481 return unloadScenario(
name);
485 if (_scenarios.find(
name) != _scenarios.end()) {
486 SG_LOG(SG_AI, SG_WARN,
"scenario '" <<
name <<
"' already loaded");
493 SGPropertyNode* scenarioNode = root->addChild(
"scenario");
494 scenarioNode->setStringValue(
name);
500bool FGAIManager::unloadScenarioCommand(
const SGPropertyNode* arg, SGPropertyNode* root)
503 std::string
name = arg->getStringValue(
"name");
504 return unloadScenario(
name);
507bool FGAIManager::addObjectCommand(
const SGPropertyNode* arg,
const SGPropertyNode* root)
519 const std::string& type = definition->getStringValue(
"type",
"aircraft");
522 if (type ==
"tanker") {
524 }
else if (type ==
"wingman") {
526 }
else if (type ==
"aircraft") {
528 }
else if (type ==
"ship") {
530 }
else if (type ==
"carrier") {
532 }
else if (type ==
"groundvehicle") {
534 }
else if (type ==
"escort") {
536 }
else if (type ==
"thunderstorm") {
538 }
else if (type ==
"thermal") {
540 }
else if (type ==
"ballistic") {
542 }
else if (type ==
"static") {
549 SG_LOG(SG_AI, SG_DEBUG,
"attached scenario " << ai->
_getName());
552 SG_LOG(SG_AI, SG_ALERT,
"killed invalid scenario " << ai->
_getName());
557bool FGAIManager::removeObjectCommand(
const SGPropertyNode* arg,
const SGPropertyNode* root)
563 return removeObject(arg);
566bool FGAIManager::removeObject(
const SGPropertyNode* args)
568 int id = args->getIntValue(
"id");
570 auto it_ai = std::find_if(coll.begin(), coll.end(), [
id](
FGAIBasePtr ai) {
571 return ai->getID() == id;
573 if (it_ai != coll.end())
574 (*it_ai)->setDie(
true);
578 if (ai->getID() ==
id) {
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()) {
606 simgear::ErrorReportContext ec(
"scenario-path", path.utf8Str());
607 SGPropertyNode_ptr scNode = file->getChild(
"scenario");
609 simgear::reportFailure(simgear::LoadFailure::Misconfigured,
610 simgear::ErrorCode::ScenarioLoad,
611 "No <scenario> element in file", path);
615 assert(_scenarios.find(
id) == _scenarios.end());
616 _scenarios[id] =
new Scenario(
this,
id, scNode);
621bool FGAIManager::unloadScenario(
const std::string& filename)
623 auto it = _scenarios.find(filename);
624 if (it == _scenarios.end()) {
625 SG_LOG(SG_AI, SG_WARN,
"unload scenario: not found:" << filename);
630 for (
auto n : root->getChildren(
"scenario")) {
631 if (n->getStringValue() == filename) {
632 root->removeChild(n);
638 _scenarios.erase(it);
642void FGAIManager::unloadAllScenarios()
644 std::for_each(_scenarios.begin(), _scenarios.end(),
645 [](
const ScenarioDict::value_type& v) { delete v.second; });
648 root->removeChildren(
"scenario");
660 for (
auto n : s->getChildren(
"scenario")) {
661 if (n->getStringValue(
"id") == scenarioName) {
662 SGPath path{n->getStringValue(
"path")};
664 simgear::ErrorReportContext ec(
"scenario-path", path.utf8Str());
666 SGPropertyNode_ptr root =
new SGPropertyNode;
667 readProperties(path, 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(),
684 ai_list_iterator ai_list_itr = ai_list.begin();
685 ai_list_iterator end = ai_list.end();
687 SGGeod pos(SGGeod::fromDegFt(lon, lat, alt));
688 SGVec3d cartPos(SGVec3d::fromGeod(pos));
690 while (ai_list_itr != end) {
693 double tgt_alt = aiModel->_getAltitude();
694 int l_tgt_ht = aiModel->getCollisionHeight() + fuse_range;
706 int id = (*ai_list_itr)->getID();
708 double range =
calcRangeFt(cartPos, (*ai_list_itr));
719 int l_tgt_length = aiModel->getCollisionLength() + fuse_range;
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();
735 double distM = dist(aCartPos, aObject->
getCartPos());
736 return distM * SG_METER_TO_FEET;
741 return _userAircraft.get();
746 SGSubsystemMgr::POST_FDM,
747 {{
"nasal", SGSubsystemMgr::Dependency::HARD}});
static bool static_haveRegisteredScenarios
SGSubsystemMgr::Registrant< FGAIManager > registrantFGAIManager(SGSubsystemMgr::POST_FDM, {{"nasal", SGSubsystemMgr::Dependency::HARD}})
SGSharedPtr< FGAIBase > FGAIBasePtr
static FGNasalSys * nasalSys
virtual void readFromScenario(SGPropertyNode *scFileNode)
SGVec3d getCartPos() const
void setCallSign(const std::string &)
SGPropertyNode * _getProps() const
void updateLOD()
update LOD properties of the model
virtual void update(double dt)
bool isa(object_type otype)
const char * _getName() const
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)
static SGPropertyNode_ptr loadScenarioFile(const std::string &id, SGPath &outPath)
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)
const ai_list_type & get_ai_list() const
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
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.
FGAIAircraft * getUserAircraft() const
Retrieve the representation of the user's aircraft in the AI manager the position and velocity of thi...
An AI tanker for air-air refueling.
double getStrength() const
void update(double dt) override
SGGeod get_aircraft_position() const
void get_aircraft_orientation(double &heading, double &pitch, double &roll)
static const std::unique_ptr< AddonManager > & instance()
std::string fgGetString(const char *name, const char *defaultValue)
Get a string value for a property.
SGSharedPtr< Addon > AddonRef
bool fgGetBool(char const *name, bool def)
Get a bool value for a property.
double fgGetDouble(const char *name, double defaultValue)
Get a double value for a property.
SGPropertyNode * fgGetNode(const char *path, bool create)
Get a property node.