Howto:Working with AI and MP properties

From FlightGear wiki
Jump to navigation Jump to search
This article is a stub. You can help the wiki by expanding it.

Objective: Use Nasal to process properties from the AI Traffic/Multiplayer systems by accessing the property tree.

Getting started

Enable AI and/or MP. Then, just open the property browser, go to /ai/models - that's where all models are listed. The total count of instantiated models is in /ai/models/count - beginning at 0.

Keep in mind that even non-aircraft models may be listed there, such as e.g. the carrier. So if you are only interested in certain types of AI models, you need to do some further checking.

To access a certain model, you add the index number to the path - for example /ai/models/aircraft[1] or /ai/models/aircraft[2].

All models have their own sub branches, such as "orientation", "position" or "velocities" - which contain the corresponding values.

When you use the property browser to navigate the property tree, you can see the full path to the corresponding property at the top of the browser.

Iterating through the list of AI/MP vehicles is then just a matter of changing the index number and getting out the corresponding position/field (node) for the vehicle.

Coding

Actually, the code to do this is already readily available in FlightGear. $FG_ROOT/tanker.nas is a good demonstration for code that iterates over the AI properties. Another good example is the multiplayer pilot list, which also iterates over the property tree. You only need to combine snippets from these two files to come up with what you need.

So getting started should not pose a real problem at all, even if you don't understand everything in the $FG_ROOT/Nasal directory, or the tanker.nas example

Iteration is just a matter of using a loop to examine the property tree.

For example, to get a list of all aircraft entries in /ai/models and dump it to the console, use something like this:

foreach(var a; props.globals.getNode("/ai/models").getChildren("aircraft")){
    props.dump(a);
}

Obviously, you could repeat this using maketimer() , and instead of dumping all nodes to the console, you could just as well process them in a different way.

So to get a list of all "aircraft" entries under /ai/models, just use a call like this:

var list = props.globals.getNode("/ai/models").getChildren("aircraft");

You can get the size of the list (i.e. number of elements in vector) by using the size() function:

size(list);

Each element in the list then contains a complete props node object for the corresponding aircraft. You can access each element by using the array notation:

var callsign = list[0].getNode("callsign").getValue();
print(callsign);

Where 0 is the index of the list. So if you want to do this in a loop, you can use foreach or use a conventional for loop and a size() call.

var list = props.globals.getNode("/ai/models").getChildren("aircraft");
var total = size(list);

for(var i = 0; i < total; i += 1) {
    var callsign = list[i].getNode("callsign").getValue();
    print(callsign);
}


Note that you need to look at the value of the boolean 'valid' property to know if the entry is still valid. The AI Manager can invalidate some entries, but they are not removed from the list.

All properties can be easily accessed by using the helpers provided in $FG_ROOT/Nasal/props.nas

To see what properties are commonly available for AI or MP nodes, just browse the property tree as previously suggested already.

Each valid aircraft node then gets its own sub trees for position, orientation, velocities respectively.

  • /ai/models/aircraft[n]/position
  • /ai/models/aircraft[n]/orientation
  • /ai/models/aircraft[n]/velocities

So, a small Nasal script to dump the callsign of each AI/MP aircraft to the console might look like this:

var dumper = func(){
    var aircraft_list = props.globals.getNode("/ai/models").getChildren("aircraft");
    foreach(var ai_aircraft; aicraft_list){
        print(ai_aircraft.getNode("callsign").getValue());
    }
}

var timer = maketimer(1, dumper); # call this function again with a 1 second delay

setlistener("/sim/signals/nasal-dir-initialized", func {
    timer.start();
}); # register the dumper function to be invoked

Related content