1
0
Fork 0

David Culp:

Here's a new batch of AI code which includes a working radar instrument.

I put the radar calculations into the existing AIAircraft class.  It was
easier that way, and it can always be migrated out later if we have to.
Every tenth sim cycle the AIManager makes a copy of the current user state
information.  When the AIAircraft updates it uses this information to
calculate the radar numbers.  It calculates:

1) bearing from user to target
2) range to target in nautical miles
3) "horizontal offset" to target.  This is the angle from the nose to the
   target, in degrees, from -180 to 180.  This will be useful later for a HUD.
4) elevation, in degrees (vertical angle from user's position to target
   position)
5) vertical offset, in degrees (this is elevation corrected for user's pitch)
6) rdot (range rate in knots, note:  not working yet, so I commented it out)

and three items used by the radar instrument to place the "blip"

7) y_shift, in nautical miles
8) x_shift, in nautical miles
9) rotation, in degrees

The radar instrument uses the above three items, and applies a scale factor to
the x-shift and y-shift in order to match the instrument's scale.  Changing
the display scale can be done entirely in the XML code for the instrument.
Right now it's set up only to display a 40 mile scale.

The radar is an AWACS view, which is not very realistic, but it is useful and
demonstrates the technology.  With just a little more work I can get a HUD
marker.  All I need to do there is make a bank angle adjustment to the
current values.
This commit is contained in:
ehofman 2004-02-27 10:20:17 +00:00
parent ef12314346
commit 85a1e5cc98
10 changed files with 392 additions and 77 deletions

View file

@ -51,7 +51,8 @@ const FGAIAircraft::PERF_STRUCT FGAIAircraft::settings[] = {
FGAIAircraft *FGAIAircraft::_self = NULL;
FGAIAircraft::FGAIAircraft() {
FGAIAircraft::FGAIAircraft(FGAIManager* mgr) {
manager = mgr;
_self = this;
// set heading and altitude locks
@ -173,10 +174,10 @@ void FGAIAircraft::Run(double dt) {
// adjust altitude (meters) based on current vertical speed (fpm)
altitude += vs * 0.0166667 * dt * SG_FEET_TO_METER;
double altitude_ft = altitude * SG_METER_TO_FEET;
// find target vertical speed if altitude lock engaged
if (alt_lock) {
double altitude_ft = altitude * SG_METER_TO_FEET;
if (altitude_ft < tgt_altitude) {
tgt_vs = tgt_altitude - altitude_ft;
if (tgt_vs > performance->climb_rate)
@ -203,6 +204,74 @@ void FGAIAircraft::Run(double dt) {
// match pitch angle to vertical speed
pitch = vs * 0.005;
//###########################//
// do calculations for radar //
//###########################//
// copy values from the AIManager
double user_latitude = manager->get_user_latitude();
double user_longitude = manager->get_user_longitude();
double user_altitude = manager->get_user_altitude();
double user_heading = manager->get_user_heading();
double user_pitch = manager->get_user_pitch();
double user_yaw = manager->get_user_yaw();
double user_speed = manager->get_user_speed();
// calculate range to target in feet and nautical miles
double lat_range = fabs(pos.lat() - user_latitude) * ft_per_deg_lat;
double lon_range = fabs(pos.lon() - user_longitude) * ft_per_deg_lon;
double range_ft = sqrt( lat_range*lat_range + lon_range*lon_range );
range = range_ft / 6076.11549;
// calculate bearing to target
if (pos.lat() >= user_latitude) {
bearing = atan2(lat_range, lon_range) * SG_RADIANS_TO_DEGREES;
if (pos.lon() >= user_longitude) {
bearing = 90.0 - bearing;
} else {
bearing = 270.0 + bearing;
}
} else {
bearing = atan2(lon_range, lat_range) * SG_RADIANS_TO_DEGREES;
if (pos.lon() >= user_longitude) {
bearing = 180.0 - bearing;
} else {
bearing = 180.0 + bearing;
}
}
// calculate look left/right to target, without yaw correction
horiz_offset = bearing - user_heading;
if (horiz_offset > 180.0) horiz_offset -= 360.0;
if (horiz_offset < -180.0) horiz_offset += 360.0;
// calculate elevation to target
elevation = atan2( altitude_ft - user_altitude, range_ft )
* SG_RADIANS_TO_DEGREES;
// calculate look up/down to target
vert_offset = elevation + user_pitch;
/* this calculation needs to be fixed
// calculate range rate
double recip_bearing = bearing + 180.0;
if (recip_bearing > 360.0) recip_bearing -= 360.0;
double my_horiz_offset = recip_bearing - hdg;
if (my_horiz_offset > 180.0) my_horiz_offset -= 360.0;
if (my_horiz_offset < -180.0) my_horiz_offset += 360.0;
rdot = (-user_speed * cos( horiz_offset * SG_DEGREES_TO_RADIANS ))
+ (-speed * 1.686 * cos( my_horiz_offset * SG_DEGREES_TO_RADIANS ));
*/
// now correct look left/right for yaw
horiz_offset += user_yaw;
// calculate values for radar display
y_shift = range * cos( horiz_offset * SG_DEGREES_TO_RADIANS);
x_shift = range * sin( horiz_offset * SG_DEGREES_TO_RADIANS);
rotation = hdg - user_heading;
if (rotation < 0.0) rotation += 360.0;
}

View file

@ -49,7 +49,7 @@ public:
enum aircraft_e {LIGHT=0, WW2_FIGHTER, JET_TRANSPORT, JET_FIGHTER};
static const PERF_STRUCT settings[];
FGAIAircraft();
FGAIAircraft(FGAIManager* mgr);
~FGAIAircraft();
bool init();

View file

@ -27,7 +27,8 @@
#include "AIBallistic.hxx"
FGAIBallistic::FGAIBallistic() {
FGAIBallistic::FGAIBallistic(FGAIManager* mgr) {
manager = mgr;
_type_str = "ballistic";
}

View file

@ -28,7 +28,7 @@ class FGAIBallistic : public FGAIBase {
public:
FGAIBallistic();
FGAIBallistic(FGAIManager* mgr);
~FGAIBallistic();
bool init();

View file

@ -48,9 +48,12 @@ FGAIBase::FGAIBase() {
_self = this;
_type_str = "model";
tgt_roll = roll = tgt_pitch = tgt_yaw = tgt_vs = vs = pitch = 0.0;
bearing = elevation = range = rdot = 0.0;
x_shift = y_shift = rotation = 0.0;
}
FGAIBase::~FGAIBase() {
unbind();
_self = NULL;
}
@ -91,7 +94,8 @@ bool FGAIBase::init() {
}
void FGAIBase::bind() {
props->tie("velocities/airspeed-kt", SGRawValuePointer<double>(&speed));
props->tie("id", SGRawValuePointer<int>(&id));
props->tie("velocities/true-airspeed-kt", SGRawValuePointer<double>(&speed));
props->tie("velocities/vertical-speed-fps",
SGRawValueFunctions<double>(FGAIBase::_getVS_fps,
FGAIBase::_setVS_fps));
@ -108,7 +112,17 @@ void FGAIBase::bind() {
props->tie("orientation/pitch-deg", SGRawValuePointer<double>(&pitch));
props->tie("orientation/roll-deg", SGRawValuePointer<double>(&roll));
props->tie("orientation/heading-deg", SGRawValuePointer<double>(&hdg));
props->tie("orientation/true-heading-deg", SGRawValuePointer<double>(&hdg));
props->tie("radar/bearing-deg", SGRawValueFunctions<double>(FGAIBase::_getBearing));
props->tie("radar/elevation-deg", SGRawValueFunctions<double>(FGAIBase::_getElevation));
props->tie("radar/range-nm", SGRawValueFunctions<double>(FGAIBase::_getRange));
// props->tie("radar/rdot-kts", SGRawValueFunctions<double>(FGAIBase::_getRdot));
props->tie("radar/h-offset", SGRawValueFunctions<double>(FGAIBase::_getH_offset));
props->tie("radar/v-offset", SGRawValueFunctions<double>(FGAIBase::_getV_offset));
props->tie("radar/x-shift", SGRawValueFunctions<double>(FGAIBase::_getX_shift));
props->tie("radar/y-shift", SGRawValueFunctions<double>(FGAIBase::_getY_shift));
props->tie("radar/rotation", SGRawValueFunctions<double>(FGAIBase::_getRotation));
props->tie("controls/lighting/nav-lights",
SGRawValueFunctions<bool>(FGAIBase::_isNight));
@ -117,7 +131,8 @@ void FGAIBase::bind() {
}
void FGAIBase::unbind() {
props->untie("velocities/airspeed-kt");
props->untie("id");
props->untie("velocities/true-airspeed-kt");
props->untie("velocities/vertical-speed-fps");
props->untie("position/altitude-ft");
@ -126,7 +141,17 @@ void FGAIBase::unbind() {
props->untie("orientation/pitch-deg");
props->untie("orientation/roll-deg");
props->untie("orientation/heading-deg");
props->untie("orientation/true-heading-deg");
props->untie("radar/bearing-deg");
props->untie("radar/elevation-deg");
props->untie("radar/range-nm");
// props->untie("radar/rdot-kts");
props->untie("radar/h-offset");
props->untie("radar/v-offset");
props->untie("radar/x-shift");
props->untie("radar/y-shift");
props->untie("radar/rotation");
props->untie("controls/controls/lighting/nav-lights");
}

View file

@ -30,6 +30,8 @@
SG_USING_STD(string);
class FGAIManager;
class FGAIBase {
public:
@ -51,13 +53,17 @@ public:
void setLongitude( double longitude );
void setBank( double bank );
void setID( int ID );
int getID();
void setDie( bool die );
bool getDie();
protected:
SGPropertyNode *props;
FGAIManager* manager;
// these describe the model's actual state
Point3D pos; // WGS84 lat & lon in degrees, elev above sea-level in meters
double hdg; // True heading in degrees
double roll; // degrees, left is negative
@ -66,6 +72,7 @@ protected:
double altitude; // meters above sea level
double vs; // vertical speed, feet per minute
// these describe the model's desired state
double tgt_heading; // target heading, degrees true
double tgt_altitude; // target altitude, *feet* above sea level
double tgt_speed; // target speed, KTAS
@ -74,10 +81,22 @@ protected:
double tgt_yaw;
double tgt_vs;
// these describe radar information for the user
double bearing; // true bearing from user to this model
double elevation; // elevation in degrees from user to this model
double range; // range from user to this model, nm
double rdot; // range rate, in knots
double horiz_offset; // look left/right from user to me, deg
double vert_offset; // look up/down from user to me, deg
double x_shift; // value used by radar display instrument
double y_shift; // value used by radar display instrument
double rotation; // value used by radar display instrument
string model_path; //Path to the 3D model
SGModelPlacement aip;
bool delete_me;
int id;
void Transform();
@ -98,6 +117,16 @@ public:
static double _getLongitude();
static double _getLatitude ();
static double _getBearing();
static double _getElevation();
static double _getRange();
static double _getRdot();
static double _getH_offset();
static double _getV_offset();
static double _getX_shift();
static double _getY_shift();
static double _getRotation();
static bool _isNight();
};
@ -143,6 +172,16 @@ inline void FGAIBase::_setLatitude ( double latitude ) {
inline double FGAIBase::_getLongitude() { return _self->pos.lon(); }
inline double FGAIBase::_getLatitude () { return _self->pos.lat(); }
inline double FGAIBase::_getBearing() { return _self->bearing; }
inline double FGAIBase::_getElevation() { return _self->elevation; }
inline double FGAIBase::_getRange() { return _self->range; }
inline double FGAIBase::_getRdot() { return _self->rdot; }
inline double FGAIBase::_getH_offset() { return _self->horiz_offset; }
inline double FGAIBase::_getV_offset() { return _self->vert_offset; }
inline double FGAIBase::_getX_shift() { return _self->x_shift; }
inline double FGAIBase::_getY_shift() { return _self->y_shift; }
inline double FGAIBase::_getRotation() { return _self->rotation; }
inline double FGAIBase::_getVS_fps() { return _self->vs*60.0; }
inline void FGAIBase::_setVS_fps( double _vs ) { _self->vs = _vs/60.0; }
@ -157,5 +196,8 @@ inline bool FGAIBase::_isNight() {
return (fgGetFloat("/sim/time/sun-angle-rad") > 1.57);
}
inline void FGAIBase::setID( int ID ) { id = ID; }
inline int FGAIBase::getID() { return id; }
#endif // _FG_AIBASE_HXX

View file

@ -34,72 +34,61 @@ SG_USING_STD(list);
FGAIManager::FGAIManager() {
initDone = false;
numObjects = 0;
dt_count = 9;
}
FGAIManager::~FGAIManager() {
ai_list_itr = ai_list.begin();
while(ai_list_itr != ai_list.end()) {
delete (*ai_list_itr);
++ai_list_itr;
}
ai_list.clear();
ids.clear();
}
void FGAIManager::init() {
SGPropertyNode * node = fgGetNode("sim/ai", true);
for (int i = 0; i < node->nChildren(); i++) {
const SGPropertyNode * entry = node->getChild(i);
void FGAIManager::init() {
int rval;
root = fgGetNode("sim/ai", true);
for (int i = 0; i < root->nChildren(); i++) {
const SGPropertyNode * entry = root->getChild(i);
if (!strcmp(entry->getName(), "entry")) {
if (!strcmp(entry->getStringValue("type", ""), "aircraft")) {
FGAIAircraft* ai_plane = new FGAIAircraft;
ai_list.push_back(ai_plane);
string model_class = entry->getStringValue("class", "");
if (model_class == "light") {
ai_plane->SetPerformance(&FGAIAircraft::settings[FGAIAircraft::LIGHT]);
} else if (model_class == "ww2_fighter") {
ai_plane->SetPerformance(&FGAIAircraft::settings[FGAIAircraft::WW2_FIGHTER]);
} else if (model_class == "jet_transport") {
ai_plane->SetPerformance(&FGAIAircraft::settings[FGAIAircraft::JET_TRANSPORT]);
} else if (model_class == "jet_fighter") {
ai_plane->SetPerformance(&FGAIAircraft::settings[FGAIAircraft::JET_FIGHTER]);
}
ai_plane->setHeading(entry->getDoubleValue("heading"));
ai_plane->setSpeed(entry->getDoubleValue("speed-KTAS"));
ai_plane->setPath(entry->getStringValue("path"));
ai_plane->setAltitude(entry->getDoubleValue("altitude-ft"));
ai_plane->setLongitude(entry->getDoubleValue("longitude"));
ai_plane->setLatitude(entry->getDoubleValue("latitude"));
ai_plane->setBank(entry->getDoubleValue("bank"));
ai_plane->init();
ai_plane->bind();
rval = createAircraft( entry->getStringValue("class", ""),
entry->getStringValue("path"),
entry->getDoubleValue("latitude"),
entry->getDoubleValue("longitude"),
entry->getDoubleValue("altitude-ft"),
entry->getDoubleValue("heading"),
entry->getDoubleValue("speed-KTAS"),
0.0,
entry->getDoubleValue("bank") );
} else if (!strcmp(entry->getStringValue("type", ""), "ship")) {
FGAIShip* ai_ship = new FGAIShip;
ai_list.push_back(ai_ship);
ai_ship->setHeading(entry->getDoubleValue("heading"));
ai_ship->setSpeed(entry->getDoubleValue("speed-KTAS"));
ai_ship->setPath(entry->getStringValue("path"));
ai_ship->setAltitude(entry->getDoubleValue("altitude-ft"));
ai_ship->setLongitude(entry->getDoubleValue("longitude"));
ai_ship->setLatitude(entry->getDoubleValue("latitude"));
ai_ship->setBank(entry->getDoubleValue("rudder"));
ai_ship->init();
ai_ship->bind();
rval = createShip( entry->getStringValue("path"),
entry->getDoubleValue("latitude"),
entry->getDoubleValue("longitude"),
entry->getDoubleValue("altitude-ft"),
entry->getDoubleValue("heading"),
entry->getDoubleValue("speed-KTAS"),
entry->getDoubleValue("rudder") );
} else if (!strcmp(entry->getStringValue("type", ""), "ballistic")) {
FGAIBallistic* ai_ballistic = new FGAIBallistic;
ai_list.push_back(ai_ballistic);
ai_ballistic->setAzimuth(entry->getDoubleValue("azimuth"));
ai_ballistic->setElevation(entry->getDoubleValue("elevation"));
ai_ballistic->setSpeed(entry->getDoubleValue("speed-fps"));
ai_ballistic->setPath(entry->getStringValue("path"));
ai_ballistic->setAltitude(entry->getDoubleValue("altitude-ft"));
ai_ballistic->setLongitude(entry->getDoubleValue("longitude"));
ai_ballistic->setLatitude(entry->getDoubleValue("latitude"));
ai_ballistic->init();
ai_ballistic->bind();
rval = createBallistic( entry->getStringValue("path"),
entry->getDoubleValue("latitude"),
entry->getDoubleValue("longitude"),
entry->getDoubleValue("altitude-ft"),
entry->getDoubleValue("azimuth"),
entry->getDoubleValue("elevation"),
entry->getDoubleValue("speed") );
}
}
}
@ -109,33 +98,166 @@ void FGAIManager::init() {
void FGAIManager::bind() {
root = globals->get_props()->getNode("ai/models", true);
root->tie("count", SGRawValuePointer<int>(&numObjects));
}
void FGAIManager::unbind() {
ai_list_itr = ai_list.begin();
while(ai_list_itr != ai_list.end()) {
(*ai_list_itr)->unbind();
++ai_list_itr;
}
root->untie("count");
}
void FGAIManager::update(double dt) {
#if 0
if(!initDone) {
init();
SG_LOG(SG_ATC, SG_WARN, "Warning - AIManager::update(...) called before AIManager::init()");
}
#endif
ai_list_itr = ai_list.begin();
while(ai_list_itr != ai_list.end()) {
if ((*ai_list_itr)->getDie()) {
ai_list.erase(ai_list_itr, ai_list_itr);
freeID((*ai_list_itr)->getID());
delete (*ai_list_itr);
ai_list.erase(ai_list_itr);
--ai_list_itr;
--numObjects;
} else {
fetchUserState();
(*ai_list_itr)->update(dt);
}
++ai_list_itr;
}
}
// This function returns the next available ID
int FGAIManager::assignID() {
int maxint = 30000;
int x;
bool used;
for (x=0; x<maxint; x++) {
used = false;
id_itr = ids.begin();
while( id_itr != ids.end() ) {
if ((*id_itr) == x) used = true;
++id_itr;
}
if (!used) {
ids.push_back(x);
return x;
}
}
return -1; // no available ID's
}
// This function removes an ID from the ID array, making it
// available for assignment to another AI object
void FGAIManager::freeID( int ID ) {
id_itr = ids.begin();
while( id_itr != ids.end() ) {
if (*id_itr == ID) {
ids.erase( id_itr );
return;
}
++id_itr;
}
}
int FGAIManager::createAircraft( string model_class, string path,
double latitude, double longitude, double altitude,
double heading, double speed, double pitch, double roll ) {
FGAIAircraft* ai_plane = new FGAIAircraft(this);
ai_list.push_back(ai_plane);
ai_plane->setID( assignID() );
++numObjects;
if (model_class == "light") {
ai_plane->SetPerformance(&FGAIAircraft::settings[FGAIAircraft::LIGHT]);
} else if (model_class == "ww2_fighter") {
ai_plane->SetPerformance(&FGAIAircraft::settings[FGAIAircraft::WW2_FIGHTER]);
} else if (model_class == "jet_transport") {
ai_plane->SetPerformance(&FGAIAircraft::settings[FGAIAircraft::JET_TRANSPORT]);
} else if (model_class == "jet_fighter") {
ai_plane->SetPerformance(&FGAIAircraft::settings[FGAIAircraft::JET_FIGHTER]);
}
ai_plane->setHeading(heading);
ai_plane->setSpeed(speed);
ai_plane->setPath(path.c_str());
ai_plane->setAltitude(altitude);
ai_plane->setLongitude(longitude);
ai_plane->setLatitude(latitude);
ai_plane->setBank(roll);
ai_plane->init();
ai_plane->bind();
return ai_plane->getID();
}
int FGAIManager::createShip( string path, double latitude, double longitude,
double altitude, double heading, double speed,
double rudder ) {
FGAIShip* ai_ship = new FGAIShip(this);
ai_list.push_back(ai_ship);
ai_ship->setID( assignID() );
++numObjects;
ai_ship->setHeading(heading);
ai_ship->setSpeed(speed);
ai_ship->setPath(path.c_str());
ai_ship->setAltitude(altitude);
ai_ship->setLongitude(longitude);
ai_ship->setLatitude(latitude);
ai_ship->setBank(rudder);
ai_ship->init();
ai_ship->bind();
return ai_ship->getID();
}
int FGAIManager::createBallistic( string path, double latitude, double longitude,
double altitude, double azimuth, double elevation,
double speed ) {
FGAIBallistic* ai_ballistic = new FGAIBallistic(this);
ai_list.push_back(ai_ballistic);
ai_ballistic->setID( assignID() );
++numObjects;
ai_ballistic->setAzimuth(azimuth);
ai_ballistic->setElevation(elevation);
ai_ballistic->setSpeed(speed);
ai_ballistic->setPath(path.c_str());
ai_ballistic->setAltitude(altitude);
ai_ballistic->setLongitude(longitude);
ai_ballistic->setLatitude(latitude);
ai_ballistic->init();
ai_ballistic->bind();
return ai_ballistic->getID();
}
void FGAIManager::destroyObject( int ID ) {
ai_list_itr = ai_list.begin();
while(ai_list_itr != ai_list.end()) {
if ((*ai_list_itr)->getID() == ID) {
freeID( ID );
delete (*ai_list_itr);
ai_list.erase(ai_list_itr);
--ai_list_itr;
--numObjects;
return;
}
++ai_list_itr;
}
}
// fetch the user's state every 10 sim cycles
void FGAIManager::fetchUserState( void ) {
++dt_count;
if (dt_count == 10) {
user_latitude = fgGetDouble("/position/latitude-deg");
user_longitude = fgGetDouble("/position/longitude-deg");
user_altitude = fgGetDouble("/position/altitude-ft");
user_heading = fgGetDouble("/orientation/heading-deg");
user_pitch = fgGetDouble("/orientation/pitch-deg");
user_yaw = fgGetDouble("/orientation/side-slip-deg");
user_speed = fgGetDouble("/velocities/uBody-fps") * 0.592484;
dt_count = 0;
}
}

View file

@ -47,6 +47,11 @@ private:
ai_list_type ai_list;
ai_list_iterator ai_list_itr;
// array of already-assigned ID's
typedef vector <int> id_vector_type;
id_vector_type ids;
id_vector_type::iterator id_itr;
public:
enum object_type { otAircraft, otShip, otBallistic, otRocket };
@ -59,10 +64,61 @@ public:
void unbind();
void update(double dt);
int assignID();
void freeID(int ID);
int createAircraft( string model_class, // see FGAIAircraft.hxx for possible classes
string path, // path to exterior model
double latitude, // in degrees -90 to 90
double longitude, // in degrees -180 to 180
double altitude, // in feet
double heading, // true heading in degrees
double speed, // in knots true airspeed (KTAS)
double pitch = 0, // in degrees
double roll = 0 ); // in degrees
int createShip( string path, // path to exterior model
double latitude, // in degrees -90 to 90
double longitude, // in degrees -180 to 180
double altitude, // in feet (ex. for a lake!)
double heading, // true heading in degrees
double speed, // in knots true
double rudder ); // in degrees (between 0 and 5 works best)
int createBallistic( string path, // path to exterior model
double latitude, // in degrees -90 to 90
double longitude, // in degrees -180 to 180
double altitude, // in feet
double azimuth, // in degrees (same as heading)
double elevation, // in degrees (same as pitch)
double speed ); // in feet per second
void destroyObject( int ID );
inline double get_user_latitude() { return user_latitude; }
inline double get_user_longitude() { return user_longitude; }
inline double get_user_altitude() { return user_altitude; }
inline double get_user_heading() { return user_heading; }
inline double get_user_pitch() { return user_pitch; }
inline double get_user_yaw() { return user_yaw; }
inline double get_user_speed() {return user_speed; }
private:
bool initDone;
int numObjects;
SGPropertyNode* root;
double user_latitude;
double user_longitude;
double user_altitude;
double user_heading;
double user_pitch;
double user_yaw;
double user_speed;
int dt_count;
void fetchUserState( void );
};

View file

@ -27,7 +27,8 @@
#include "AIShip.hxx"
FGAIShip::FGAIShip() {
FGAIShip::FGAIShip(FGAIManager* mgr) {
manager = mgr;
hdg_lock = false;
rudder = 0.0;
_type_str = "ship";

View file

@ -21,15 +21,14 @@
#ifndef _FG_AISHIP_HXX
#define _FG_AISHIP_HXX
#include "AIManager.hxx"
#include "AIBase.hxx"
class FGAIManager;
class FGAIShip : public FGAIBase {
public:
FGAIShip();
FGAIShip(FGAIManager* mgr);
~FGAIShip();
bool init();