Talk:Scripted AI Objects

From FlightGear wiki
Revision as of 21:06, 29 October 2014 by 5H1N0B1 (talk | contribs)
Jump to navigation Jump to search

Implementation

As Hooray said, this is the talk started here : http://wiki.flightgear.org/User_talk:Red_Leader We agree on the general way of doing it, and the goal we want.

For now, the script use 2 objects :

  1. the missile itself
  2. A "target" object which is an object with all variable available on the target aircraft/ship whatever...

The aircraft very specific variables are those used to setup the inital position of the missile : You give a pylon number and the script go look threw your aircraft to geo positionning the missile object and make it fire.( and the missile type...)

Instead of using this :

var DestinationOfFiring = Target.new(whatever);
var missile = Missile.new(pylon, DestinationOfFiring );

a very, very simple way of doing it could be just feed the missile with 2 object and have someting like that:

 var SourceOfFiring = geo.Coord.new(whatever);
 var DestinationOfFiring = geo.Coord.new(whatever);
 var myMissile = new missile(origin:SourceOfFiring , target:DestinationOfFiring, type:missileType);

This is a quick way of doing the job. Perhaps the "Target" object needs to be completed, with "pylon number" or "pylon position" (and it also could be renamed). This will allow the script to be independant of any aircraft.

a target really is really just representing a position with lat/lon/alt - thus, it would make sense to simply use the existing geo.nas helpers here (geo.Coord) - if you need additional data, you can just sub-class geo.Coord to add your own fields - we are using this method in the MapStructre/TFC (traffic) layer. So I wouldn't make this more complicated than necessary. Obviously, targets/positions may be dynamic/changing, but that can also be realized using a geo.Coord sub-class, or a helper class with a geo.Coord member. Equally, missileType should probably just be another hash with a well-defined set of interfaces/methods. Overall, a simple MVC separation would definitely make sense here, which would allow you to have a custom 3D model, add animations to it, while having a plug&play FDM for different purposes. --Hooray (talk) 16:52, 28 October 2014 (UTC)

This is not only a quick way of doing it, this also something that can allow multiplayer firing : If (imagine a checkbox) the "MP firing property" is activated, it could reproduce a "AI model" on both side : On the SourceOfFiring's computer, but also a second "AI model" in the DestinationOfFiring's computer...(this a way of doing it...)

And then once this is done, we could work later on a real FDM for the missile and what so ever. (Anyways, just my 2 cents opinion...) —5H1N0B1 (Talk | contribs) 14:08, 27 October 2014 (UTC)

Hi 5H1N0B1. Sorry I've not been able to get back to you sooner. Your suggestion seems quite good. Also, having MP firing would be a great feature. One thing we need to consider is how we coordinate code, i.e. use of version control etc.. I think that we should also write down stuff that would be configurable on the missile (i.e. size and weight, but also seeker and guidance type). A while ago, I found a PDF that I think will be very useful http://cdn.preterhuman.net/texts/terrorism_and_pyrotechnics/rocketry/Missiles_and_Warheads/Missile%20Guidance%20&%20Control%20Systems.pdf.
Red Leader (Talk | contribs) 12:59, 28 October 2014 (UTC)

We've had a number of users on the forum interested in these things, especially the FGUK guys would probably love to be involved in this. Coordination-wise, you should consider refactoring the existing code into separate modules/files and classes that can be independently maintained without requiring a ton of communication-obviously, being able to use git/gitorious would still help. For MP support, you'll want to use an intermediate class that uses the mp_broadcast.nas script to set up a comm channel for events (see bombable). If you keep maintaining this article, I'll send heads-up to others who might be interested in this - even if just to provide feedback and advice. A few weeks ago, I talked with Algernon about these kinds of features so the FGUK guys should be all game - and a few others recently posted MapStructure/Canvas screen shots showing Combat-related developments[1].--Hooray (talk) 16:00, 28 October 2014 (UTC)

The more the better, Hooray. I think what we should do first is to put down stuff that affects the missiles specs, such as guidance types, seeker type, etc., then split these into categories, such as FDM, Seeker, etc., and work from there.
Red Leader (Talk | contribs) 16:52, 28 October 2014 (UTC)
Sure, but you'll find that most of these requirements will basically evolve automatically once you start refactoring the existing code into separate classes and files, while still supporting additional aircraft/use-cases. That is basically what we did with Gijs' NavDisplay code - and it can now be used on arbitrary aircraft, while also supporting an arbitrary number of instances per aircraft. We are even supporting an "AI aircraft" mode-which means that you can get a ND view for an arbitrary AI aircraft (including even AI nodes like a Bombable bot or AI missile). To make things sufficiently flexible and configurable we're using "behavior hashes" that can override default behavior (fields/methods). With efforts like these, over-engineering is a very real danger - it will probably get you farther to just refactor the existing code and incrementally make it support additional use-cases/aircraft, and extend the code over time. Currently, the main issue still is that the module is aircraft specific-once this restriction is removed, you'll see other aircraft developers join the effort over time, especially those without any stakes in the m2000-5, but still interested in combat feature.--Hooray (talk) 17:04, 28 October 2014 (UTC)

I think that we should instead call this the "AI guided weapons system" or "AI guided munitions." After all, we aren't just talking about missile, we're also talking about guided bombs. The same system can and should be used for both. That's what I think anyway. Red Leader (Talk | contribs) 19:56, 28 October 2014 (UTC)

From a functional standpoint, I would suggest to look at the way FlightGear's subsystems are divided into distinct components and then simply model this code accordingly by introducing helper classes for components like 1) the FDM, 2) the autopilot, 3) the "route managerh". That way, a guidance system would simply implement the route manager system's interface. As I stated elsewhere, it would make a lot of sense to maintain parity with the property tree-level FDM/AP and RM systems. Primarily, this will ensure consistency - secondly, it will ensure that future updates would provide a sane migration path, i.e. for using C++ level hooks that are currently not exposed to scripting space - we do have a number of contributors who tinkered with supporting multiple instances of the FDM/AP and RM subsystems - in fact, the AP system already supports multiple instances (see the property-rules system). And multiple FDM instances can also be supported for JSBSim FDMs - the route manager (RM) seems to be not sufficiently generic, but Durk and Zakalawe have repeatedly stated being interested in generalizing this part, too[2]. Which basically means that there will be less repetitive/integration work necessary if/once those changes should materialize at some point. Thus, it would be a good idea not to re-invent the wheel entirely in scripting space - there's useful existing C++ code for these things (FDM, AP, RM), and exposing things to scripting space using Nasal/CppBind also has never been so easy. The other benefit is obviously that people will intuitively know how to deal with scripted AI objects because the property tree interfaces would be closely modeled after existing conventions.--Hooray (talk) 13:36, 29 October 2014 (UTC)
Hi Hooray. Thanks for the suggestions. I think that, for now, we'll concentrate on keeping it in Nasal. At a later stage, we could look at implementing parts of the system in other parts of FG.
Red Leader (Talk | contribs) 16:34, 29 October 2014 (UTC)

right, but you misunderstood - I wasn't suggesting to move this out of Nasal anytime soon, but to just keep the design sufficiently generic so that components can be replaced at a later time, no matter if that involves other Nasal modules or really existing C++ code. The main problems that flug, xiii and others had when implementing these features was working around hard-coded design restrictions-those can be entirely prevented once a modular design is used like the one discussed above - without making things any more difficult. In fact, separation of concerns will be greatly simplified-and it does make sense to treat any entity like an airborne vehicle with a fdm, autopilot and route manager/guidance system. All this can be done entirely in scripting space using Nasal. The key is just the layered design using the property tree for I/O between systems--Hooray (talk) 16:42, 29 October 2014 (UTC)

OK, so the way I understand it is that the code should generic enough so that, for example, in the future YASim supports multiple instances, we could take out the Nasal FDM and instead translate the missile through the sky according to properties generated by a YASim instance.
Red Leader (Talk | contribs) 17:01, 29 October 2014 (UTC)
correct, but this is not specific to FDMs - all the autopilot/routing logic that people tend to reinvent in Nasal is significantly overlapping with existing, generic, C++ subsystems that are currently not yet exposed to Nasal. Thus, the main thing is using properties analogous to the actual C++ subsystems for interacting with the FDM/AP and RM components, which will ensure that a reusable and generic design is established, while preparing it for future updates. Otherwise, there will be more and more Nasal code doing what the C++ code is known to be very good at already.--Hooray (talk) 18:00, 29 October 2014 (UTC)

A suggestion that I have is to expand geo.Coord to hold orientation values. That would mean we could handle target and have their lat/lon/alt/pitch/roll/heading at our fingertips. Red Leader (Talk | contribs) 17:03, 29 October 2014 (UTC)

like I said, this is trivial to do using sub-classing, so need to touch geo.nas at all - just create your own geo.Coord sub-class and add helpers for getting the corresponding properties via props.nas --Hooray (talk) 17:55, 29 October 2014 (UTC)

Progress for tonight as follows. I've created missile.nas and ai.nas, both in $FGDATA/Nasal, with the code as follows.

ai.nas:

# This module enables generic AI objects to be created and updated

var Obj = {
	new: func(type = nil, model_path = "Models/Geometry/null.ac"){
		if(type == nil){
			die("ai.nas: Error! 'type' is not defined");
		}
		
		var m = { parents: [Obj] };
	
		var n = props.globals.getNode("models", 1);
		for(var i = 0; 1; i += 1){
			if(n.getChild("model", i, 0) == nil){
				break;
			}
		}
		m.model_node = n.getChild("model", i, 1);		
		
		var n = props.globals.getNode("ai/models", 1);
		for(var i = 0; 1; i += 1){
			if(n.getChild(type, i, 0) == nil){
				break;
			}
		}		
		m.ai_node = n.getChild(type, i, 1);
		
		m.ai_node.getNode("valid", 1).setBoolValue(1);
		m.latN    = m.ai_node.getNode("position/latitude-deg", 1);
		m.lonN    = m.ai_node.getNode("position/longitude-deg", 1);
		m.altN    = m.ai_node.getNode("position/altitude-ft", 1);
		m.hdgN    = m.ai_node.getNode("orientation/true-heading-deg", 1);
		m.pitchN  = m.ai_node.getNode("orientation/pitch-deg", 1);
		m.rollN   = m.ai_node.getNode("orientation/roll-deg", 1);
		
		m.latN.setDoubleValue(0);
		m.lonN.setDoubleValue(0);
		m.altN.setDoubleValue(0);
		m.hdgN.setDoubleValue(0);
		m.pitchN.setDoubleValue(0);
		m.rollN.setDoubleValue(0);
		
		m.model_node.getNode("path", 1).setValue(model_path);
		m.model_node.getNode("latitude-deg-prop", 1).setValue(m.latN.getPath());
		m.model_node.getNode("longitude-deg-prop", 1).setValue(m.lonN.getPath());
		m.model_node.getNode("elevation-ft-prop", 1).setValue(m.altN.getPath());
		m.model_node.getNode("heading-deg-prop", 1).setValue(m.hdgN.getPath());
		m.model_node.getNode("pitch-deg-prop", 1).setValue(m.pitchN.getPath());
		m.model_node.getNode("roll-deg-prop", 1).setValue(m.rollN.getPath());
		m.model_node.getNode("load", 1).remove();
		
		return m;
	},
	
	del: func(){
		me.model_node.remove();
		me.ai_node.remove();
	},
	
	reload_model: func(model_path = "Models/Geometry/null.ac"){
		me.model_node.remove();
		
		var n = props.globals.getNode("models", 1);
		for(var i = 0; 1; i += 1){
			if(n.getChild("model", i, 0) == nil){
				break;
			}
		}
		me.model_node = n.getChild("model", i, 1);
		
		me.model_node.getNode("path", 1).setValue(model_path);
		me.model_node.getNode("latitude-deg-prop", 1).setValue(me.latN.getPath());
		me.model_node.getNode("longitude-deg-prop", 1).setValue(me.lonN.getPath());
		me.model_node.getNode("elevation-ft-prop", 1).setValue(me.altN.getPath());
		me.model_node.getNode("heading-deg-prop", 1).setValue(me.hdgN.getPath());
		me.model_node.getNode("pitch-deg-prop", 1).setValue(me.pitchN.getPath());
		me.model_node.getNode("roll-deg-prop", 1).setValue(me.rollN.getPath());
	},
	
	set_pos: func(lat, lon, alt = 0, roll = 0, ptch = 0, hdg = 0){
		me.latN.setDoubleValue(lat);
		me.lonN.setDoubleValue(lon);
		me.altN.setDoubleValue(alt);
		me.hdgN.setDoubleValue(hdg);
		me.pitchN.setDoubleValue(ptch);
		me.rollN.setDoubleValue(roll);
	}
};

missile.nas:

var Missile = {
	new: func(tgt = "none", src = "none", type = "none"){
	
		if(tgt == "none" or src == "none" or type == "none"){
			die("missile.nas: Error! Arguments not defined.");
		}
		
		if(isa(tgt, geo.Coord) == 0 or isa(src, geo.Coord) == 0){
			die("missile.nas: Error! Arguments not correct.");
		}
		
		var m = { parents: [Missile] };
		
		m.tgt = tgt;
		m.src = src;
		m.type = check_lib(type);
		
		if(m.type == 0){
			die("missile.nas: Error! Unsupported missile type.");
		}
		
		return m;
	},
	
	launch: func(off_x = 0, off_y = 0, off_z = 0){
		me.model = ai.Obj.new("missile", "Aircraft/Generic/Stores/AIM-9/AIM-9.xml");
		me.update();
	},
	
	update: func(){
		settimer(func{
			me.update();
		}, 0);
	}
};

var check_lib = func(type){
	return type;
}

PS Hooary, how do you sub-class an existing Nasal class? Red Leader (Talk | contribs) 20:15, 29 October 2014 (UTC)

No special syntax/techniques involved - it just involves adding fields/methods to the new object, either directly - or via inheritance (the parents vector). I've added stubs to Philosopher's OO article at: http://wiki.flightgear.org/Object_Oriented_Programming_with_Nasal#Sub-Classing --Hooray (talk) 21:00, 29 October 2014 (UTC)

Hi guys, you just go really too fast for me but I really like the way it goes. Seems to be clear code and could be really generic. I'll try to contribute a little but time, sadly is missing here ! —5H1N0B1 (Talk | contribs) 22:02, 29 October 2014 (UTC)