]> git.mxchange.org Git - flightgear.git/commitdiff
Lots of changes to the ATC/AI system for initial revision of random AI GA VFR traffic
authordaveluff <daveluff>
Fri, 23 Jan 2004 17:18:24 +0000 (17:18 +0000)
committerdaveluff <daveluff>
Fri, 23 Jan 2004 17:18:24 +0000 (17:18 +0000)
25 files changed:
src/ATC/AIEntity.cxx
src/ATC/AIEntity.hxx
src/ATC/AILocalTraffic.cxx
src/ATC/AILocalTraffic.hxx
src/ATC/AIMgr.cxx
src/ATC/AIMgr.hxx
src/ATC/AIPlane.cxx
src/ATC/AIPlane.hxx
src/ATC/ATC.cxx
src/ATC/ATC.hxx
src/ATC/ATCDialog.cxx
src/ATC/ATCdisplay.cxx
src/ATC/ATCmgr.cxx
src/ATC/ATCmgr.hxx
src/ATC/ATCutils.cxx
src/ATC/ATCutils.hxx
src/ATC/Makefile.am
src/ATC/approach.cxx
src/ATC/approach.hxx
src/ATC/atis.cxx
src/ATC/atis.hxx
src/ATC/ground.cxx
src/ATC/ground.hxx
src/ATC/tower.cxx
src/ATC/tower.hxx

index 2b333cfdbae1593c33c6a8c3be373ab003d8cdad..d87b86dbfcc76225ad7695a8654096d272f186da 100644 (file)
 
 #include "AIEntity.hxx"
 
+FGAIEntity::FGAIEntity() {
+}
+
 FGAIEntity::~FGAIEntity() {
+       //cout << "FGAIEntity dtor called..." << endl;
+       _model->deRef();        // Ought to check valid?
+       //cout << "Removing model from scene graph..." << endl;
+       globals->get_scenery()->get_scene_graph()->removeKid(_aip.getSceneGraph());
+       //cout << "Done!" << endl;
+}
+
+void FGAIEntity::SetModel(ssgBranch* model) {
+       _model = model;
+       _model->ref();
+       _aip.init(_model);
+       _aip.setVisible(false);
+       globals->get_scenery()->get_scene_graph()->addKid(_aip.getSceneGraph());
 }
 
 void FGAIEntity::Update(double dt) {
 }
 
+string FGAIEntity::GetCallsign() {
+       return("");
+}
+
 void FGAIEntity::RegisterTransmission(int code) {
 }
 
 // Run the internal calculations
 //void FGAIEntity::Update() {
 void FGAIEntity::Transform() {
-    aip.setPosition(pos.lon(), pos.lat(), pos.elev() * SG_METER_TO_FEET);
-    aip.setOrientation(roll, pitch, hdg);
-    aip.update( globals->get_scenery()->get_center() );    
+    _aip.setPosition(_pos.lon(), _pos.lat(), _pos.elev() * SG_METER_TO_FEET);
+    _aip.setOrientation(_roll, _pitch, _hdg);
+    _aip.update( globals->get_scenery()->get_center() );    
 }
index 436eb186d7233a087d225ceb0f9dc51b04f507f8..d3d60ac2db2d38d286fe4ccf4ac2759d80197bfa 100644 (file)
@@ -41,7 +41,11 @@ class FGAIEntity {
 
 public:
 
+       FGAIEntity();
     virtual ~FGAIEntity();
+       
+       // Set the 3D model to use (Must be called)
+       void SetModel(ssgBranch* model);
 
     // Run the internal calculations
     virtual void Update(double dt);
@@ -50,17 +54,20 @@ public:
     // FIXME int code is a hack - eventually this will receive Alexander's coded messages.
     virtual void RegisterTransmission(int code);
        
-       inline Point3D GetPos() { return(pos); }
-
+       inline Point3D GetPos() { return(_pos); }
+       
+       virtual string GetCallsign();
+       
 protected:
 
-    Point3D pos;       // WGS84 lat & lon in degrees, elev above sea-level in meters
-    double hdg;                //True heading in degrees
-    double roll;       //degrees
-    double pitch;      //degrees
+    Point3D _pos;      // WGS84 lat & lon in degrees, elev above sea-level in meters
+    double _hdg;               //True heading in degrees
+    double _roll;      //degrees
+    double _pitch;     //degrees
 
-    char* model_path;  //Path to the 3D model
-    SGModelPlacement aip;
+    char* _model_path; //Path to the 3D model
+       ssgBranch* _model;      // Pointer to the model
+    SGModelPlacement _aip;
 
     void Transform();
 };
index e427bb382cd5c8fa62f3943a91a3938f1f7a6809..76a294f60c1547f80c60233b800d23ea03cd48bd 100644 (file)
@@ -51,17 +51,28 @@ SG_USING_STD(string);
 #include "ATCmgr.hxx"
 #include "AILocalTraffic.hxx"
 #include "ATCutils.hxx"
+#include "AIMgr.hxx"
 
 FGAILocalTraffic::FGAILocalTraffic() {
+       /*ssgBranch *model = sgLoad3DModel( globals->get_fg_root(),
+                                         planepath.c_str(),
+                                         globals->get_props(),
+                                         globals->get_sim_time_sec() );
+       *//*
+       _model = model;
+       _model->ref();
+       _aip.init(_model);
+       */
+       //SetModel(model);
+       
        ATC = globals->get_ATC_mgr();
        
-       // TODO - unhardwire this - possibly let the AI manager set the callsign
-       plane.callsign = "Trainer-two-five-charlie";
+       // TODO - unhardwire this
        plane.type = GA_SINGLE;
        
-       roll = 0.0;
-       pitch = 0.0;
-       hdg = 270.0;
+       _roll = 0.0;
+       _pitch = 0.0;
+       _hdg = 270.0;
        
        //Hardwire initialisation for now - a lot of this should be read in from config eventually
        Vr = 70.0;
@@ -90,23 +101,66 @@ FGAILocalTraffic::FGAILocalTraffic() {
        holdingShort = false;
        clearedToLineUp = false;
        clearedToTakeOff = false;
+       _clearedToLand = false;
        reportReadyForDeparture = false;
        contactTower = false;
        contactGround = false;
+       _taxiToGA = false;
        
        descending = false;
        targetDescentRate = 0.0;
        goAround = false;
        goAroundCalled = false;
+       
+       transmitted = false;
+       
+       freeTaxi = false;
+       _savedSlope = 0.0;
+       
+       _controlled = false;
 }
 
 FGAILocalTraffic::~FGAILocalTraffic() {
+       //_model->deRef();
 }
 
+void FGAILocalTraffic::GetAirportDetails(string id) {
+       AirportATC a;
+       if(ATC->GetAirportATCDetails(airportID, &a)) {
+               if(a.tower_freq) {      // Has a tower - TODO - check the opening hours!!!
+                       tower = (FGTower*)ATC->GetATCPointer(airportID, TOWER); // Maybe need some error checking here
+                       if(tower == NULL) {
+                               // Something has gone wrong - abort or carry on with un-towered operation?
+                               SG_LOG(SG_ATC, SG_ALERT, "ERROR - can't get a tower pointer from tower control for " << airportID << " in FGAILocalTraffic::GetAirportDetails() :-(");
+                               _controlled = false;
+                       } else {
+                               _controlled = true;
+                       }
+                       if(tower) {
+                               ground = tower->GetGroundPtr();
+                               if(ground == NULL) {
+                                       // Something has gone wrong :-(
+                                       SG_LOG(SG_ATC, SG_ALERT, "ERROR - can't get a ground pointer from tower control in FGAILocalTraffic::GetAirportDetails() :-(");
+                               }
+                       }
+               } else {
+                       _controlled = false;
+                       // TODO - Check CTAF, unicom etc
+               }
+       } else {
+               SG_LOG(SG_ATC, SG_ALERT, "Unable to find airport details in for " << airportID << " in FGAILocalTraffic::GetAirportDetails() :-(");
+               _controlled = false;
+       }
+       // Get the airport elevation
+       aptElev = dclGetAirportElev(airportID.c_str());
+       //cout << "Airport elev in AILocalTraffic = " << aptElev << '\n';
+       // WARNING - we use this elev for the whole airport - some assumptions in the code 
+       // might fall down with very slopey airports.
+}
 
 // Get details of the active runway
 // It is assumed that by the time this is called the tower control and airport code will have been set up.
-void FGAILocalTraffic::GetRwyDetails() {
+void FGAILocalTraffic::GetRwyDetails(string id) {
        //cout << "GetRwyDetails called" << endl;
        
        rwy.rwyID = tower->GetActiveRunway();
@@ -114,12 +168,9 @@ void FGAILocalTraffic::GetRwyDetails() {
        // Now we need to get the threshold position and rwy heading
        
        FGRunway runway;
-       bool rwyGood = globals->get_runways()->search(airportID, rwy.rwyID,
-                                                      &runway);
+       bool rwyGood = globals->get_runways()->search(id, rwy.rwyID, &runway);
        if(rwyGood) {
-               // Get the threshold position
-       hdg = runway.heading;   // TODO - check - is this our heading we are setting here, and if so should we be?
-               //cout << "hdg reset to " << hdg << '\n';
+       double hdg = runway.heading;
                double other_way = hdg - 180.0;
                while(other_way <= 0.0) {
                        other_way += 360.0;
@@ -132,6 +183,7 @@ void FGAILocalTraffic::GetRwyDetails() {
        double tshlon, tshlat, tshr;
                double tolon, tolat, tor;
                rwy.length = runway.length * SG_FEET_TO_METER;
+               rwy.width = runway.width * SG_FEET_TO_METER;
        geo_direct_wgs_84 ( aptElev, ref.lat(), ref.lon(), other_way, 
                                rwy.length / 2.0 - 25.0, &tshlat, &tshlon, &tshr );
        geo_direct_wgs_84 ( aptElev, ref.lat(), ref.lon(), hdg, 
@@ -144,6 +196,7 @@ void FGAILocalTraffic::GetRwyDetails() {
                //cout << "Takeoff position = " << tolon << ", " << tolat << ", " << aptElev << '\n';
                rwy.hdg = hdg;
                // Set the projection for the local area
+               //cout << "Initing ortho for airport " << id << '\n';
                ortho.Init(rwy.threshold_pos, rwy.hdg); 
                rwy.end1ortho = ortho.ConvertToLocal(rwy.threshold_pos);        // should come out as zero
                rwy.end2ortho = ortho.ConvertToLocal(takeoff_end);
@@ -164,56 +217,38 @@ To a certain extent it's FGAIMgr that has to worry about this, but we need to pr
 sufficient initialisation functionality within the plane classes to allow the manager
 to initially position them where and how required.
 */
-bool FGAILocalTraffic::Init(string ICAO, OperatingState initialState, PatternLeg initialLeg) {
+bool FGAILocalTraffic::Init(const string& callsign, string ICAO, OperatingState initialState, PatternLeg initialLeg) {
        //cout << "FGAILocalTraffic.Init(...) called" << endl;
-       // Hack alert - Hardwired path!!
-       string planepath = "Aircraft/c172/Models/c172-dpm.ac";
-       ssgBranch *model = sgLoad3DModel( globals->get_fg_root(),
-                                         planepath.c_str(),
-                                         globals->get_props(),
-                                         globals->get_sim_time_sec() );
-       aip.init( model );
-       aip.setVisible(false);          // This will be set to true once a valid ground elevation has been determined
-       globals->get_scenery()->get_scene_graph()->addKid(aip.getSceneGraph());
-       
-       // Find the tower frequency - this is dependent on the ATC system being initialised before the AI system
        airportID = ICAO;
-       AirportATC a;
-       if(ATC->GetAirportATCDetails(airportID, &a)) {
-               if(a.tower_freq) {      // Has a tower
-                       tower = (FGTower*)ATC->GetATCPointer(airportID, TOWER); // Maybe need some error checking here
-                       if(tower == NULL) {
-                               // Something has gone wrong - abort or carry on with un-towered operation?
-                               return(false);
-                       }
-                       freq = (double)tower->get_freq() / 100.0;
-                       ground = tower->GetGroundPtr();
-                       if(ground == NULL) {
-                               // Something has gone wrong :-(
-                               SG_LOG(SG_ATC, SG_ALERT, "ERROR - can't get a ground pointer from tower control in FGAILocalTraffic::Init() :-(");
-                               return(false);
-                       } else if((initialState == PARKED) || (initialState == TAXIING)) {
-                               freq = (double)ground->get_freq() / 100.0;
-                       }
-                       //cout << "AILocalTraffic freq is " << freq << '\n';
-               } else {
-                       // TODO - Check CTAF, unicom etc
-               }
-       } else {
-               //cout << "Unable to find airport details in FGAILocalTraffic::Init()\n";
+       
+       plane.callsign = callsign;
+       
+       if(initialState == EN_ROUTE) return(true);
+       
+       // Get the ATC pointers and airport elev
+       GetAirportDetails(airportID);
+       
+       // Get the active runway details (and copy them into rwy)
+       GetRwyDetails(airportID);
+       //cout << "Runway is " << rwy.rwyID << '\n';
+       
+       // FIXME TODO - pattern direction is still hardwired
+       patternDirection = -1;          // Left
+       // At the bare minimum we ought to make sure it goes the right way at dual parallel rwy airports!
+       if(rwy.rwyID.size() == 3) {
+               patternDirection = (rwy.rwyID.substr(2,1) == "R" ? 1 : -1);
        }
        
-               // Get the active runway details (and copy them into rwy)
-               GetRwyDetails();
-
-       // Get the airport elevation
-       aptElev = dclGetAirportElev(airportID.c_str()) * SG_FEET_TO_METER;
-       //cout << "Airport elev in AILocalTraffic = " << aptElev << '\n';
-       // WARNING - we use this elev for the whole airport - some assumptions in the code 
-       // might fall down with very slopey airports.
+       // TODO - this assumes a controlled airport - make sure we revert to CTAF etc if uncontrolled or after-hours.
+       if((initialState == PARKED) || (initialState == TAXIING)) {
+               freq = (double)ground->get_freq() / 100.0;
+       } else {
+               freq = (double)tower->get_freq() / 100.0;
+       }
 
        //cout << "In Init(), initialState = " << initialState << endl;
        operatingState = initialState;
+       Point3D orthopos;
        switch(operatingState) {
        case PARKED:
                tuned_station = ground;
@@ -224,13 +259,13 @@ bool FGAILocalTraffic::Init(string ICAO, OperatingState initialState, PatternLeg
                        SG_LOG(SG_ATC, SG_ALERT, "No gate found by FGAILocalTraffic whilst attempting Init at " << airportID << '\n');
                        return(false);
                }
-               pitch = 0.0;
-               roll = 0.0;
+               _pitch = 0.0;
+               _roll = 0.0;
                vel = 0.0;
                slope = 0.0;
-               pos = ourGate->pos;
-               pos.setelev(aptElev);
-               hdg = ourGate->heading;
+               _pos = ourGate->pos;
+               _pos.setelev(aptElev);
+               _hdg = ourGate->heading;
                
                // Now we've set the position we can do the ground elev
                elevInitGood = false;
@@ -240,9 +275,36 @@ bool FGAILocalTraffic::Init(string ICAO, OperatingState initialState, PatternLeg
                Transform();
                break;
        case TAXIING:
-               tuned_station = ground;
+               //tuned_station = ground;
                // FIXME - implement this case properly
-               return(false);  // remove this line when fixed!
+               // For now we'll assume that the plane should start at the hold short in this case
+               // and that we're working without ground network elements.  Ie. an airport with no facility file.
+               tuned_station = tower;
+               freeTaxi = true;
+               // Set a position and orientation in an approximate place for hold short.
+               //cout << "rwy.width = " << rwy.width << '\n';
+               orthopos = Point3D((rwy.width / 2.0 + 10.0) * -1.0, 0.0, 0.0);
+               // TODO - set the x pos to be +ve if a RH parallel rwy.
+               _pos = ortho.ConvertFromLocal(orthopos);
+               _pos.setelev(aptElev);
+               _hdg = rwy.hdg + 90.0;
+               // TODO - reset the heading if RH rwy.
+               _pitch = 0.0;
+               _roll = 0.0;
+               vel = 0.0;
+               slope = 0.0;
+               elevInitGood = false;
+               inAir = false;
+               DoGroundElev();
+               Transform();
+               
+               responseCounter = 0.0;
+               contactTower = false;
+               changeFreq = true;
+               holdingShort = true;
+               clearedToLineUp = false;
+               changeFreqType = TOWER;
+               
                break;
        case IN_PATTERN:
                // For now we'll always start the in_pattern case on the threshold ready to take-off
@@ -257,34 +319,28 @@ bool FGAILocalTraffic::Init(string ICAO, OperatingState initialState, PatternLeg
                
                circuitsToFly = 0;              // ie just fly this circuit and then stop
                touchAndGo = false;
-               // FIXME TODO - pattern direction is still hardwired
-               patternDirection = -1;          // Left
-               // At the bare minimum we ought to make sure it goes the right way at dual parallel rwy airports!
-               if(rwy.rwyID.size() == 3) {
-                       patternDirection = (rwy.rwyID.substr(2,1) == "R" ? 1 : -1);
-               }
 
                if(initialLeg == DOWNWIND) {
-                       pos = ortho.ConvertFromLocal(Point3D(1000*patternDirection, 800, 0.0));
-                       pos.setelev(rwy.threshold_pos.elev() + 1000 * SG_FEET_TO_METER);
-                       hdg = rwy.hdg + 180.0;
+                       _pos = ortho.ConvertFromLocal(Point3D(1000*patternDirection, 800, 0.0));
+                       _pos.setelev(rwy.threshold_pos.elev() + 1000 * SG_FEET_TO_METER);
+                       _hdg = rwy.hdg + 180.0;
                        leg = DOWNWIND;
                        elevInitGood = false;
                        inAir = true;
                        track = rwy.hdg - (180 * patternDirection);     //should tend to bring track back into the 0->360 range
                        slope = 0.0;
-                       pitch = 0.0;
-                       roll = 0.0;
+                       _pitch = 0.0;
+                       _roll = 0.0;
                        IAS = 90.0;
                        descending = false;
-                       aip.setVisible(true);
+                       _aip.setVisible(true);
                        tower->RegisterAIPlane(plane, this, CIRCUIT, DOWNWIND);
                } else {                        
                        // Default to initial position on threshold for now
-                       pos.setlat(rwy.threshold_pos.lat());
-                       pos.setlon(rwy.threshold_pos.lon());
-                       pos.setelev(rwy.threshold_pos.elev());
-                       hdg = rwy.hdg;
+                       _pos.setlat(rwy.threshold_pos.lat());
+                       _pos.setlon(rwy.threshold_pos.lon());
+                       _pos.setelev(rwy.threshold_pos.elev());
+                       _hdg = rwy.hdg;
                        
                        // Now we've set the position we can do the ground elev
                        // This might not always be necessary if we implement in-air start
@@ -292,8 +348,8 @@ bool FGAILocalTraffic::Init(string ICAO, OperatingState initialState, PatternLeg
                        inAir = false;
                        DoGroundElev();
                        
-                       pitch = 0.0;
-                       roll = 0.0;
+                       _pitch = 0.0;
+                       _roll = 0.0;
                        leg = TAKEOFF_ROLL;
                        vel = 0.0;
                        slope = 0.0;
@@ -303,6 +359,9 @@ bool FGAILocalTraffic::Init(string ICAO, OperatingState initialState, PatternLeg
                
                Transform();
                break;
+       case EN_ROUTE:
+               // This implies we're being init'd by AIGAVFRTraffic - simple return now
+               return(true);
        default:
                SG_LOG(SG_ATC, SG_ALERT, "Attempt to set unknown operating state in FGAILocalTraffic.Init(...)\n");
                return(false);
@@ -313,6 +372,41 @@ bool FGAILocalTraffic::Init(string ICAO, OperatingState initialState, PatternLeg
 }
 
 
+// Set up downwind state - this is designed to be called from derived classes who are already tuned to tower
+void FGAILocalTraffic::DownwindEntry() {
+       circuitsToFly = 0;              // ie just fly this circuit and then stop
+       touchAndGo = false;
+       operatingState = IN_PATTERN;
+       leg = DOWNWIND;
+       elevInitGood = false;
+       inAir = true;
+       track = rwy.hdg - (180 * patternDirection);     //should tend to bring track back into the 0->360 range
+       slope = 0.0;
+       _pitch = 0.0;
+       _roll = 0.0;
+       IAS = 90.0;
+       descending = false;
+}
+
+void FGAILocalTraffic::StraightInEntry(bool des) {
+       //cout << "************ STRAIGHT-IN ********************\n";
+       circuitsToFly = 0;              // ie just fly this circuit and then stop
+       touchAndGo = false;
+       operatingState = IN_PATTERN;
+       leg = FINAL;
+       elevInitGood = false;
+       inAir = true;
+       track = rwy.hdg;
+       transmitted = true;     // TODO - fix this hack.
+       // TODO - set up the next 5 properly for a descent!
+       slope = -5.5;
+       _pitch = 0.0;
+       _roll = 0.0;
+       IAS = 90.0;
+       descending = des;
+}
+
+
 // Return what type of landing we're doing on this circuit
 LandingType FGAILocalTraffic::GetLandingOption() {
        //cout << "circuitsToFly = " << circuitsToFly << '\n';
@@ -334,19 +428,23 @@ void FGAILocalTraffic::FlyCircuits(int numCircuits, bool tag) {
                return;
                break;
        case TAXIING:
-               // TODO - For now we'll punt this and do nothing
+               // HACK - assume that we're taxiing out for now
+               circuitsToFly += numCircuits;
+               touchAndGo = tag;
                break;
        case PARKED:
                circuitsToFly = numCircuits;    // Note that one too many circuits gets flown because we only test and decrement circuitsToFly after landing
                                                                                // thus flying one too many circuits.  TODO - Need to sort this out better!
                touchAndGo = tag;
                break;
+       case EN_ROUTE:
+               break;
        }
 }   
 
 // Run the internal calculations
 void FGAILocalTraffic::Update(double dt) {
-       //cout << "A" << flush;
+       //cout << "U" << flush;
        //double responseTime = 10.0;           // seconds - this should get more sophisticated at some point
        responseCounter += dt;
        if((contactTower) && (responseCounter >= 8.0)) {
@@ -366,6 +464,35 @@ void FGAILocalTraffic::Update(double dt) {
                changeFreqType = TOWER;
        }
        
+       if((contactGround) && (responseCounter >= 8.0)) {
+               // Acknowledge request before changing frequency so it gets rendered if the user is on the same freq
+               string trns = "Ground ";
+               double f = globals->get_ATC_mgr()->GetFrequency(airportID, GROUND) / 100.0;     
+               char buf[10];
+               sprintf(buf, "%.2f", f);
+               trns += buf;
+               trns += " ";
+               trns += "Good Day";
+               pending_transmission = trns;
+               ConditionalTransmit(5.0);
+               responseCounter = 0.0;
+               contactGround = false;
+               changeFreq = true;
+               changeFreqType = GROUND;
+       }
+       
+       if((_taxiToGA) && (responseCounter >= 8.0)) {
+               // Acknowledge request before changing frequency so it gets rendered if the user is on the same freq
+               string trns = "GA Parking, Thank you and Good Day";
+               //double f = globals->get_ATC_mgr()->GetFrequency(airportID, GROUND) / 100.0;   
+               pending_transmission = trns;
+               ConditionalTransmit(5.0);
+               tower->DeregisterAIPlane(plane.callsign);
+               _taxiToGA = false;
+               // HACK - check if we are at a simple airport or not first
+               globals->get_AI_mgr()->ScheduleRemoval(plane.callsign);
+       }               
+       
        if((changeFreq) && (responseCounter > 8.0)) {
                switch(changeFreqType) {
                case TOWER:
@@ -373,7 +500,6 @@ void FGAILocalTraffic::Update(double dt) {
                        freq = (double)tower->get_freq() / 100.0;
                        //Transmit("DING!");
                        // Contact the tower, even if only virtually
-                       changeFreq = false;
                        pending_transmission = plane.callsign;
                        pending_transmission += " at hold short for runway ";
                        pending_transmission += ConvertRwyNumToSpokenString(rwy.rwyID);
@@ -387,8 +513,12 @@ void FGAILocalTraffic::Update(double dt) {
                        Transmit(2);
                        break;
                case GROUND:
+                       tower->DeregisterAIPlane(plane.callsign);
                        tuned_station = ground;
                        freq = (double)ground->get_freq() / 100.0;
+                       // HACK - check if we are at a simple airport or not first
+                       // TODO FIXME TODO FIXME !!!!!!!
+                       if(airportID != "KEMT") globals->get_AI_mgr()->ScheduleRemoval(plane.callsign);
                        break;
                // And to avoid compiler warnings...
                case APPROACH:  break;
@@ -397,9 +527,10 @@ void FGAILocalTraffic::Update(double dt) {
                case DEPARTURE: break;
                case INVALID:   break;
                }
+               changeFreq = false;
        }
        
-       //cout << "." << flush;
+       //cout << "," << flush;
                
        switch(operatingState) {
        case IN_PATTERN:
@@ -407,11 +538,11 @@ void FGAILocalTraffic::Update(double dt) {
                if(!inAir) {
                        DoGroundElev();
                        if(!elevInitGood) {
-                               if(aip.getSGLocation()->get_cur_elev_m() > -9990.0) {
-                                       pos.setelev(aip.getSGLocation()->get_cur_elev_m() + wheelOffset);
+                               if(_aip.getSGLocation()->get_cur_elev_m() > -9990.0) {
+                                       _pos.setelev(_aip.getSGLocation()->get_cur_elev_m() + wheelOffset);
                                        //cout << "TAKEOFF_ROLL, POS = " << pos.lon() << ", " << pos.lat() << ", " << pos.elev() << '\n';
                                        //Transform();
-                                       aip.setVisible(true);
+                                       _aip.setVisible(true);
                                        //cout << "Making plane visible!\n";
                                        elevInitGood = true;
                                }
@@ -425,17 +556,17 @@ void FGAILocalTraffic::Update(double dt) {
                //cout << "*" << flush;
                if(!elevInitGood) {
                        //DoGroundElev();
-                       if(aip.getSGLocation()->get_cur_elev_m() > -9990.0) {
-                               pos.setelev(aip.getSGLocation()->get_cur_elev_m() + wheelOffset);
+                       if(_aip.getSGLocation()->get_cur_elev_m() > -9990.0) {
+                               _pos.setelev(_aip.getSGLocation()->get_cur_elev_m() + wheelOffset);
                                //Transform();
-                               aip.setVisible(true);
+                               _aip.setVisible(true);
                                //Transform();
                                //cout << "Making plane visible!\n";
                                elevInitGood = true;
                        }
                }
                DoGroundElev();
-               //cout << "," << flush;
+               //cout << "~" << flush;
                if(!((holdingShort) && (!clearedToLineUp))) {
                        //cout << "|" << flush;
                        Taxi(dt);
@@ -445,7 +576,18 @@ void FGAILocalTraffic::Update(double dt) {
                        // possible assumption that we're at the hold short here - may not always hold
                        // TODO - sort out the case where we're cleared to line-up first and then cleared to take-off on the rwy.
                        taxiState = TD_LINING_UP;
+                       //cout << "A" << endl;
                        path = ground->GetPath(holdShortNode, rwy.rwyID);
+                       //cout << "B" << endl;
+                       if(!path.size()) {      // Assume no facility file so we'll just taxi to a point on the runway near the threshold
+                               //cout << "C" << endl;
+                               node* np = new node;
+                               np->struct_type = NODE;
+                               np->pos = ortho.ConvertFromLocal(Point3D(0.0, 10.0, 0.0));
+                               path.push_back(np);
+                       } else {
+                               //cout << "D" << endl;
+                       }
                        /*
                        cout << "path returned was:" << endl;
                        for(unsigned int i=0; i<path.size(); ++i) {
@@ -474,10 +616,10 @@ void FGAILocalTraffic::Update(double dt) {
                //cout << "In PARKED\n";
                if(!elevInitGood) {
                        DoGroundElev();
-                       if(aip.getSGLocation()->get_cur_elev_m() > -9990.0) {
-                               pos.setelev(aip.getSGLocation()->get_cur_elev_m() + wheelOffset);
+                       if(_aip.getSGLocation()->get_cur_elev_m() > -9990.0) {
+                               _pos.setelev(_aip.getSGLocation()->get_cur_elev_m() + wheelOffset);
                                //Transform();
-                               aip.setVisible(true);
+                               _aip.setVisible(true);
                                //Transform();
                                //cout << "Making plane visible!\n";
                                elevInitGood = true;
@@ -488,7 +630,7 @@ void FGAILocalTraffic::Update(double dt) {
                        if((taxiRequestPending) && (taxiRequestCleared)) {
                                //cout << "&" << flush;
                                // Get the active runway details (in case they've changed since init)
-                               GetRwyDetails();
+                               GetRwyDetails(airportID);
                                
                                // Get the takeoff node for the active runway, get a path to it and start taxiing
                                path = ground->GetPathToHoldShort(ourGate, rwy.rwyID);
@@ -550,10 +692,10 @@ void FGAILocalTraffic::Update(double dt) {
        }
        //cout << "I " << flush;
        
-       // Convienience output for AI debugging user the property logger
-       fgSetDouble("/AI/Local1/ortho-x", (ortho.ConvertToLocal(pos)).x());
-       fgSetDouble("/AI/Local1/ortho-y", (ortho.ConvertToLocal(pos)).y());
-       fgSetDouble("/AI/Local1/elev", pos.elev() * SG_METER_TO_FEET);
+       // Convienience output for AI debugging using the property logger
+       //fgSetDouble("/AI/Local1/ortho-x", (ortho.ConvertToLocal(_pos)).x());
+       //fgSetDouble("/AI/Local1/ortho-y", (ortho.ConvertToLocal(_pos)).y());
+       //fgSetDouble("/AI/Local1/elev", _pos.elev() * SG_METER_TO_FEET);
        
        // And finally, call parent for transmission rendering
        FGAIPlane::Update(dt);
@@ -580,9 +722,25 @@ void FGAILocalTraffic::RegisterTransmission(int code) {
                clearedToTakeOff = true;
                SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " cleared to take-off...");
                break;
+       case 5: // contact ground
+               responseCounter = 0;
+               contactGround = true;
+               SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " told to contact ground...");
+               break;
+       // case 6 is a temporary mega-hack for controlled airports without separate ground control
+       case 6: // taxi to the GA parking
+               responseCounter = 0;
+               _taxiToGA = true;
+               SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " told to taxi to the GA parking...");
+               break;
+       case 7:  // Cleared to land (also implies cleared for the option
+               _clearedToLand = true;
+               SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " cleared to land...");
+               break;
        case 13: // Go around!
                responseCounter = 0;
                goAround = true;
+               _clearedToLand = false;
                SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " told to go-around!!");
                break;
        default:
@@ -597,8 +755,6 @@ void FGAILocalTraffic::FlyTrafficPattern(double dt) {
        // Need to differentiate between in-air (IAS governed) and on-ground (vel governed)
        // Take-off is an interesting case - we are on the ground but takeoff speed is IAS governed.
        
-       static bool transmitted = false;        // FIXME - this is a hack
-
        // WIND
        // Wind has two effects - a mechanical one in that IAS translates to a different vel, and the hdg != track,
        // but also a piloting effect, in that the AI must be able to descend at a different rate in order to hit the threshold.
@@ -609,9 +765,9 @@ void FGAILocalTraffic::FlyTrafficPattern(double dt) {
        double turn_time = 60.0;        // seconds - TODO - check this guess
        double turn_circumference;
        double turn_radius;
-       Point3D orthopos = ortho.ConvertToLocal(pos);   // ortho position of the plane
+       Point3D orthopos = ortho.ConvertToLocal(_pos);  // ortho position of the plane
        //cout << "runway elev = " << rwy.threshold_pos.elev() << ' ' << rwy.threshold_pos.elev() * SG_METER_TO_FEET << '\n';
-       //cout << "elev = " << pos.elev() << ' ' << pos.elev() * SG_METER_TO_FEET << '\n';
+       //cout << "elev = " << _pos.elev() << ' ' << _pos.elev() * SG_METER_TO_FEET << '\n';
 
        // HACK FOR TESTING - REMOVE
        //cout << "Calling ExitRunway..." << endl;
@@ -633,13 +789,13 @@ void FGAILocalTraffic::FlyTrafficPattern(double dt) {
                        double dveldt = 5.0;
                        vel += dveldt * dt;
                }
-               if(aip.getSGLocation()->get_cur_elev_m() > -9990.0) {
-                       pos.setelev(aip.getSGLocation()->get_cur_elev_m() + wheelOffset);
+               if(_aip.getSGLocation()->get_cur_elev_m() > -9990.0) {
+                       _pos.setelev(_aip.getSGLocation()->get_cur_elev_m() + wheelOffset);
                }
-               IAS = vel + (cos((hdg - wind_from) * DCL_DEGREES_TO_RADIANS) * wind_speed);
+               IAS = vel + (cos((_hdg - wind_from) * DCL_DEGREES_TO_RADIANS) * wind_speed);
                if(IAS >= 70) {
                        leg = CLIMBOUT;
-                       pitch = 10.0;
+                       _pitch = 10.0;
                        IAS = best_rate_of_climb_speed;
                        //slope = 7.0;  
                        slope = 6.0;    // Reduced it slightly since it's climbing a lot steeper than I can in the JSBSim C172.
@@ -652,24 +808,24 @@ void FGAILocalTraffic::FlyTrafficPattern(double dt) {
                // (decided in FGTower and accessed through GetCrosswindConstraint(...)).
                // According to AIM, traffic should climb to within 300ft of pattern altitude before commencing crosswind turn.
                // TODO - At hot 'n high airports this may be 500ft AGL though - need to make this a variable.
-               if((pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET > 700) {
+               if((_pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET > 700) {
                        double cc = 0.0;
                        if(tower->GetCrosswindConstraint(cc)) {
                                if(orthopos.y() > cc) {
-                                       cout << "Turning to crosswind, distance from threshold = " << orthopos.y() << '\n'; 
+                                       //cout << "Turning to crosswind, distance from threshold = " << orthopos.y() << '\n'; 
                                        leg = TURN1;
                                }
                        } else if(orthopos.y() > 1500.0) {   // Added this constraint as a hack to prevent turning too early when going around.
                                // TODO - We should be doing it as a distance from takeoff end, not theshold end though.
-                               cout << "Turning to crosswind, distance from threshold = " << orthopos.y() << '\n'; 
+                               //cout << "Turning to crosswind, distance from threshold = " << orthopos.y() << '\n'; 
                                leg = TURN1;
                        }
                }
                // Need to check for levelling off in case we can't turn crosswind as soon
                // as we would like due to other traffic.
-               if((pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET > 1000) {
+               if((_pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET > 1000) {
                        slope = 0.0;
-                       pitch = 0.0;
+                       _pitch = 0.0;
                        IAS = 80.0;             // FIXME - use smooth transistion to new speed and attitude.
                }
                if(goAround && !goAroundCalled) {
@@ -692,21 +848,21 @@ void FGAILocalTraffic::FlyTrafficPattern(double dt) {
                goAround = false;
                LevelWings();
                track = rwy.hdg + (90.0 * patternDirection);
-               if((pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET > 1000) {
+               if((_pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET > 1000) {
                        slope = 0.0;
-                       pitch = 0.0;
+                       _pitch = 0.0;
                        IAS = 80.0;             // FIXME - use smooth transistion to new speed
                }
                // turn 1000m out for now, taking other traffic into accout
-               if(fabs(orthopos.x()) > 980) {
+               if(fabs(orthopos.x()) > 900) {
                        double dd = 0.0;
                        if(tower->GetDownwindConstraint(dd)) {
                                if(fabs(orthopos.x()) > fabs(dd)) {
-                                       cout << "Turning to downwind, distance from centerline = " << fabs(orthopos.x()) << '\n'; 
+                                       //cout << "Turning to downwind, distance from centerline = " << fabs(orthopos.x()) << '\n'; 
                                        leg = TURN2;
                                }
                        } else {
-                               cout << "Turning to downwind, distance from centerline = " << fabs(orthopos.x()) << '\n'; 
+                               //cout << "Turning to downwind, distance from centerline = " << fabs(orthopos.x()) << '\n'; 
                                leg = TURN2;
                        }
                }
@@ -715,9 +871,9 @@ void FGAILocalTraffic::FlyTrafficPattern(double dt) {
                track += (360.0 / turn_time) * dt * patternDirection;
                Bank(25.0 * patternDirection);
                // just in case we didn't make height on crosswind
-               if((pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET > 1000) {
+               if((_pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET > 1000) {
                        slope = 0.0;
-                       pitch = 0.0;
+                       _pitch = 0.0;
                        IAS = 80.0;             // FIXME - use smooth transistion to new speed
                }
                if((track < (rwy.hdg - 179.0)) || (track > (rwy.hdg + 179.0))) {
@@ -730,9 +886,14 @@ void FGAILocalTraffic::FlyTrafficPattern(double dt) {
                LevelWings();
                track = rwy.hdg - (180 * patternDirection);     //should tend to bring track back into the 0->360 range
                // just in case we didn't make height on crosswind
-               if((pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET > 1000) {
+               if(((_pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET > 995) && ((_pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET < 1015)) {
                        slope = 0.0;
-                       pitch = 0.0;
+                       _pitch = 0.0;
+                       IAS = 90.0;             // FIXME - use smooth transistion to new speed
+               }
+               if((_pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET >= 1015) {
+                       slope = -1.0;
+                       _pitch = -1.0;
                        IAS = 90.0;             // FIXME - use smooth transistion to new speed
                }
                if((orthopos.y() < 0) && (!transmitted)) {
@@ -740,6 +901,7 @@ void FGAILocalTraffic::FlyTrafficPattern(double dt) {
                        transmitted = true;
                }
                if((orthopos.y() < -100) && (!descending)) {
+                       //cout << "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDdddd\n";
                        // Maybe we should think about when to start descending.
                        // For now we're assuming that we aim to follow the same glidepath regardless of wind.
                        double d1;
@@ -752,7 +914,7 @@ void FGAILocalTraffic::FlyTrafficPattern(double dt) {
                }
                if(descending) {
                        slope = -5.5;   // FIXME - calculate to descent at 500fpm and hit the desired point on the runway (taking wind into account as well!!)
-                       pitch = -3.0;
+                       _pitch = -3.0;
                        IAS = 85.0;
                }
                
@@ -766,13 +928,13 @@ void FGAILocalTraffic::FlyTrafficPattern(double dt) {
                        double bb = 0.0;
                        if(tower->GetBaseConstraint(bb)) {
                                if(fabs(orthopos.y()) > fabs(bb)) {
-                                       cout << "Turning to base, distance from threshold = " << fabs(orthopos.y()) << '\n'; 
+                                       //cout << "Turning to base, distance from threshold = " << fabs(orthopos.y()) << '\n'; 
                                        leg = TURN3;
                                        transmitted = false;
                                        IAS = 80.0;
                                }
                        } else {
-                               cout << "Turning to base, distance from threshold = " << fabs(orthopos.y()) << '\n'; 
+                               //cout << "Turning to base, distance from threshold = " << fabs(orthopos.y()) << '\n'; 
                                leg = TURN3;
                                transmitted = false;
                                IAS = 80.0;
@@ -789,7 +951,8 @@ void FGAILocalTraffic::FlyTrafficPattern(double dt) {
        case BASE:
                LevelWings();
                if(!transmitted) {
-                       TransmitPatternPositionReport();
+                       // Base report should only be transmitted at uncontrolled airport - not towered.
+                       if(!_controlled) TransmitPatternPositionReport();
                        transmitted = true;
                }
                
@@ -805,7 +968,7 @@ void FGAILocalTraffic::FlyTrafficPattern(double dt) {
                }
                if(descending) {
                        slope = -5.5;   // FIXME - calculate to descent at 500fpm and hit the threshold (taking wind into account as well!!)
-                       pitch = -4.0;
+                       _pitch = -4.0;
                        IAS = 70.0;
                }
                
@@ -833,16 +996,17 @@ void FGAILocalTraffic::FlyTrafficPattern(double dt) {
        case FINAL:
                if(goAround && responseCounter > 2.0) {
                        leg = CLIMBOUT;
-                       pitch = 8.0;
+                       _pitch = 8.0;
                        IAS = best_rate_of_climb_speed;
                        slope = 5.0;    // A bit less steep than the initial climbout.
                        inAir = true;
                        goAroundCalled = false;
+                       descending = false;
                        break;
                }
                LevelWings();
                if(!transmitted) {
-                       TransmitPatternPositionReport();
+                       if((!_controlled) || (!_clearedToLand)) TransmitPatternPositionReport();
                        transmitted = true;
                }
                if(!descending) {
@@ -855,33 +1019,70 @@ void FGAILocalTraffic::FlyTrafficPattern(double dt) {
 
                }
                if(descending) {
-                       slope = -5.5;   // FIXME - calculate to descent at 500fpm and hit the threshold (taking wind into account as well!!)
-                       pitch = -4.0;
-                       IAS = 70.0;
+                       if(orthopos.y() < -50.0) {
+                               double thesh_offset = 30.0;
+                               slope = atan((_pos.elev() - dclGetAirportElev(airportID)) / (orthopos.y() - thesh_offset)) * DCL_RADIANS_TO_DEGREES;
+                               //cout << "slope = " << slope << ", elev = " << _pos.elev() << ", apt_elev = " << dclGetAirportElev(airportID) << ", op.y = " << orthopos.y() << '\n';
+                               if(slope < -10.0) slope = -10.0;
+                               _savedSlope = slope;
+                               _pitch = -4.0;
+                               IAS = 70.0;
+                       } else {
+                               if(_pos.elev() < (rwy.threshold_pos.elev()+10.0+wheelOffset)) {
+                                       if(_aip.getSGLocation()->get_cur_elev_m() > -9990.0) {
+                                               if(_pos.elev() < (_aip.getSGLocation()->get_cur_elev_m() + wheelOffset + 1.0)) {
+                                                       slope = -2.0;
+                                                       _pitch = 1.0;
+                                                       IAS = 55.0;
+                                               } else if(_pos.elev() < (_aip.getSGLocation()->get_cur_elev_m() + wheelOffset + 5.0)) {
+                                                       slope = -4.0;
+                                                       _pitch = -2.0;
+                                                       IAS = 60.0;
+                                               } else {
+                                                       slope = _savedSlope;
+                                                       _pitch = -3.0;
+                                                       IAS = 65.0;
+                                               }
+                                       } else {
+                                               // Elev not determined
+                                               slope = _savedSlope;
+                                               _pitch = -3.0;
+                                               IAS = 65.0;
+                                       }
+                               } else {
+                                       slope = _savedSlope;
+                                       _pitch = -3.0;
+                                       IAS = 65.0;
+                               }
+                       }
                }
                // Try and track the extended centreline
                track = rwy.hdg - (0.2 * orthopos.x());
                //cout << "orthopos.x() = " << orthopos.x() << " hdg = " << hdg << '\n';
-               if(pos.elev() < (rwy.threshold_pos.elev()+20.0+wheelOffset)) {
+               if(_pos.elev() < (rwy.threshold_pos.elev()+20.0+wheelOffset)) {
                        DoGroundElev(); // Need to call it here expicitly on final since it's only called
                                        // for us in update(...) when the inAir flag is false.
                }
-               if(pos.elev() < (rwy.threshold_pos.elev()+10.0+wheelOffset)) {
-                       if(aip.getSGLocation()->get_cur_elev_m() > -9990.0) {
-                               if((aip.getSGLocation()->get_cur_elev_m() + wheelOffset) > pos.elev()) {
+               if(_pos.elev() < (rwy.threshold_pos.elev()+10.0+wheelOffset)) {
+                       //slope = -1.0;
+                       //_pitch = 1.0;
+                       if(_aip.getSGLocation()->get_cur_elev_m() > -9990.0) {
+                               if((_aip.getSGLocation()->get_cur_elev_m() + wheelOffset) > _pos.elev()) {
                                        slope = 0.0;
-                                       pitch = 0.0;
+                                       _pitch = 0.0;
                                        leg = LANDING_ROLL;
                                        inAir = false;
                                }
                        }       // else need a fallback position based on arpt elev in case ground elev determination fails?
-               }
+               } else {
+                       // TODO
+               }                       
                break;
        case LANDING_ROLL:
                //inAir = false;
                descending = false;
-               if(aip.getSGLocation()->get_cur_elev_m() > -9990.0) {
-                       pos.setelev(aip.getSGLocation()->get_cur_elev_m() + wheelOffset);
+               if(_aip.getSGLocation()->get_cur_elev_m() > -9990.0) {
+                       _pos.setelev(_aip.getSGLocation()->get_cur_elev_m() + wheelOffset);
                }
                track = rwy.hdg;
                dveldt = -5.0;
@@ -912,7 +1113,7 @@ void FGAILocalTraffic::FlyTrafficPattern(double dt) {
                // Does it really matter?
                
                // Apply wind to ground-relative velocity if in the air
-               vel = IAS - (cos((hdg - wind_from) * DCL_DEGREES_TO_RADIANS) * wind_speed);
+               vel = IAS - (cos((_hdg - wind_from) * DCL_DEGREES_TO_RADIANS) * wind_speed);
                //crab = f(track, wind, vel);
                // The vector we need to fly is our desired vector minus the wind vector
                // TODO - we probably ought to use plib's built in vector types and operations for this
@@ -946,9 +1147,11 @@ void FGAILocalTraffic::FlyTrafficPattern(double dt) {
                crab = 0.0;
        }
        
-       hdg = track + crab;
+       //cout << "X " << orthopos.x() << "  Y " << orthopos.y() << "  SLOPE " << slope << "  elev " << _pos.elev() * SG_METER_TO_FEET << '\n';
+       
+       _hdg = track + crab;
        dist = vel * 0.514444 * dt;
-       pos = dclUpdatePosition(pos, track, slope, dist);
+       _pos = dclUpdatePosition(_pos, track, slope, dist);
 }
 
 // Pattern direction is true for right, false for left
@@ -1025,11 +1228,13 @@ void FGAILocalTraffic::TransmitPatternPositionReport(void) {
        }
        trns += ConvertRwyNumToSpokenString(rwy.rwyID);
        
+       trns += " ";
+       
        // And add the airport name again
        trns += tower->get_name();
        
-       pending_transmission = trns;    // FIXME - make up pending_transmission natively        
-       ConditionalTransmit(90.0, code);                // Assume a report of this leg will be invalid if we can't transmit within a minute and a half.
+       pending_transmission = trns;
+       ConditionalTransmit(60.0, code);                // Assume a report of this leg will be invalid if we can't transmit within a minute.
 }
 
 // Callback handler
@@ -1037,6 +1242,7 @@ void FGAILocalTraffic::TransmitPatternPositionReport(void) {
 void FGAILocalTraffic::ProcessCallback(int code) {
        // 1 - Request Departure from ground
        // 2 - Report at hold short
+       // 3 - Report runway vacated
        // 10 - report crosswind
        // 11 - report downwind
        // 12 - report base
@@ -1045,6 +1251,8 @@ void FGAILocalTraffic::ProcessCallback(int code) {
                ground->RequestDeparture(plane, this);
        } else if(code == 2) {
                tower->ContactAtHoldShort(plane, this, CIRCUIT);
+       } else if(code == 3) {
+               tower->ReportRunwayVacated(plane.callsign);
        } else if(code == 11) {
                tower->ReportDownwind(plane.callsign);
        } else if(code == 13) {
@@ -1055,6 +1263,9 @@ void FGAILocalTraffic::ProcessCallback(int code) {
 void FGAILocalTraffic::ExitRunway(Point3D orthopos) {
        //cout << "In ExitRunway" << endl;
        //cout << "Runway ID is " << rwy.ID << endl;
+       
+       _clearedToLand = false;
+       
        node_array_type exitNodes = ground->GetExits(rwy.rwyID);        //I suppose we ought to have some fallback for rwy with no defined exits?
        /*
        cout << "Node ID's of exits are ";
@@ -1072,7 +1283,7 @@ void FGAILocalTraffic::ExitRunway(Point3D orthopos) {
                node* rwyExit = *(exitNodes.begin());
                //int gateID;           //This might want to be more persistant at some point
                while(nItr != exitNodes.end()) {
-                       d = ortho.ConvertToLocal((*nItr)->pos).y() - ortho.ConvertToLocal(pos).y();     //FIXME - consider making orthopos a class variable
+                       d = ortho.ConvertToLocal((*nItr)->pos).y() - ortho.ConvertToLocal(_pos).y();    //FIXME - consider making orthopos a class variable
                        if(d > 0.0) {
                                if(d < dist) {
                                        dist = d;
@@ -1091,7 +1302,8 @@ void FGAILocalTraffic::ExitRunway(Point3D orthopos) {
                        // Implies no available gates - what shall we do?
                        // For now just vanish the plane - possibly we can make this more elegant in the future
                        SG_LOG(SG_ATC, SG_ALERT, "No gate found by FGAILocalTraffic whilst landing at " << airportID << '\n');
-                       aip.setVisible(false);
+                       //_aip.setVisible(false);
+                       //cout << "Setting visible false\n";
                        operatingState = PARKED;
                        return;
                }
@@ -1113,9 +1325,16 @@ void FGAILocalTraffic::ExitRunway(Point3D orthopos) {
                StartTaxi();
        } else {
                // Something must have gone wrong with the ground network file - or there is only a rwy here and no exits defined
-               SG_LOG(SG_ATC, SG_ALERT, "No exits found by FGAILocalTraffic from runway " << rwy.rwyID << " at " << airportID << '\n');
+               SG_LOG(SG_ATC, SG_INFO, "No exits found by FGAILocalTraffic from runway " << rwy.rwyID << " at " << airportID << '\n');
+               //cout << "No exits found by " << plane.callsign << " from runway " << rwy.rwyID << " at " << airportID << '\n';
                // What shall we do - just remove the plane from sight?
-               aip.setVisible(false);
+               _aip.setVisible(false);
+               //cout << "Setting visible false\n";
+               //tower->ReportRunwayVacated(plane.callsign);
+               string trns = "Clear of the runway ";
+               trns += plane.callsign;
+               pending_transmission = trns;
+               Transmit(3);
                operatingState = PARKED;
        }
 }
@@ -1159,6 +1378,7 @@ void FGAILocalTraffic::GetNextTaxiNode() {
 void FGAILocalTraffic::StartTaxi() {
        //cout << "StartTaxi called" << endl;
        operatingState = TAXIING;
+       
        taxiPathPos = 0;
        
        //Set the desired heading
@@ -1166,7 +1386,7 @@ void FGAILocalTraffic::StartTaxi() {
        //Eventually we may need to consider the fact that we might start on a curved arc and
        //not be able to head directly for the first node.
        GetNextTaxiNode();      // sets the class variable nextTaxiNode to the next taxi node!
-       desiredTaxiHeading = GetHeadingFromTo(pos, nextTaxiNode->pos);
+       desiredTaxiHeading = GetHeadingFromTo(_pos, nextTaxiNode->pos);
        //cout << "First taxi heading is " << desiredTaxiHeading << endl;
 }
 
@@ -1211,7 +1431,7 @@ void FGAILocalTraffic::Taxi(double dt) {
        // Look out for the finish!!
 
        //Point3D orthopos = ortho.ConvertToLocal(pos); // ortho position of the plane
-       desiredTaxiHeading = GetHeadingFromTo(pos, nextTaxiNode->pos);
+       desiredTaxiHeading = GetHeadingFromTo(_pos, nextTaxiNode->pos);
        
        bool lastNode = (taxiPathPos == path.size() ? true : false);
        if(lastNode) {
@@ -1221,7 +1441,7 @@ void FGAILocalTraffic::Taxi(double dt) {
        // HACK ALERT! - for now we will taxi at constant speed for straights and turns
        
        // Remember that hdg is always equal to track when taxiing so we don't have to consider them both
-       double dist_to_go = dclGetHorizontalSeparation(pos, nextTaxiNode->pos); // we may be able to do this more cheaply using orthopos
+       double dist_to_go = dclGetHorizontalSeparation(_pos, nextTaxiNode->pos);        // we may be able to do this more cheaply using orthopos
        //cout << "dist_to_go = " << dist_to_go << endl;
        if((nextTaxiNode->type == GATE) && (dist_to_go <= 0.1)) {
                // This might be more robust to outward paths starting with a gate if we check for either
@@ -1233,18 +1453,18 @@ void FGAILocalTraffic::Taxi(double dt) {
                // ((s.dt)/(PI.r)) x 180 degrees
                // or alternatively (s.dt)/r radians
                //cout << "hdg = " << hdg << " desired taxi heading = " << desiredTaxiHeading << '\n';
-               hdg = TaxiTurnTowardsHeading(hdg, desiredTaxiHeading, nominalTaxiSpeed, taxiTurnRadius, dt);
+               _hdg = TaxiTurnTowardsHeading(_hdg, desiredTaxiHeading, nominalTaxiSpeed, taxiTurnRadius, dt);
                double vel = nominalTaxiSpeed;
                //cout << "vel = " << vel << endl;
                double dist = vel * 0.514444 * dt;
                //cout << "dist = " << dist << endl;
-               double track = hdg;
+               double track = _hdg;
                //cout << "track = " << track << endl;
                double slope = 0.0;
-               pos = dclUpdatePosition(pos, track, slope, dist);
+               _pos = dclUpdatePosition(_pos, track, slope, dist);
                //cout << "Updated position...\n";
-               if(aip.getSGLocation()->get_cur_elev_m() > -9990) {
-                       pos.setelev(aip.getSGLocation()->get_cur_elev_m() + wheelOffset);
+               if(_aip.getSGLocation()->get_cur_elev_m() > -9990) {
+                       _pos.setelev(_aip.getSGLocation()->get_cur_elev_m() + wheelOffset);
                } // else don't change the elev until we get a valid ground elev again!
        } else if(lastNode) {
                if(taxiState == TD_LINING_UP) {
@@ -1252,20 +1472,20 @@ void FGAILocalTraffic::Taxi(double dt) {
                                liningUp = true;
                        }
                        if(liningUp) {
-                               hdg = TaxiTurnTowardsHeading(hdg, rwy.hdg, nominalTaxiSpeed, taxiTurnRadius, dt);
+                               _hdg = TaxiTurnTowardsHeading(_hdg, rwy.hdg, nominalTaxiSpeed, taxiTurnRadius, dt);
                                double vel = nominalTaxiSpeed;
                                //cout << "vel = " << vel << endl;
                                double dist = vel * 0.514444 * dt;
                                //cout << "dist = " << dist << endl;
-                               double track = hdg;
+                               double track = _hdg;
                                //cout << "track = " << track << endl;
                                double slope = 0.0;
-                               pos = dclUpdatePosition(pos, track, slope, dist);
+                               _pos = dclUpdatePosition(_pos, track, slope, dist);
                                //cout << "Updated position...\n";
-                               if(aip.getSGLocation()->get_cur_elev_m() > -9990) {
-                                       pos.setelev(aip.getSGLocation()->get_cur_elev_m() + wheelOffset);
+                               if(_aip.getSGLocation()->get_cur_elev_m() > -9990) {
+                                       _pos.setelev(_aip.getSGLocation()->get_cur_elev_m() + wheelOffset);
                                } // else don't change the elev until we get a valid ground elev again!
-                               if(fabs(hdg - rwy.hdg) <= 1.0) {
+                               if(fabs(_hdg - rwy.hdg) <= 1.0) {
                                        operatingState = IN_PATTERN;
                                        leg = TAKEOFF_ROLL;
                                        inAir = false;
@@ -1301,9 +1521,9 @@ void FGAILocalTraffic::DoGroundElev() {
        
        double visibility_meters = fgGetDouble("/environment/visibility-m");
        //globals->get_tile_mgr()->prep_ssg_nodes( acmodel_location,
-       globals->get_tile_mgr()->prep_ssg_nodes( aip.getSGLocation(),   visibility_meters );
+       globals->get_tile_mgr()->prep_ssg_nodes( _aip.getSGLocation(),  visibility_meters );
         Point3D scenery_center = globals->get_scenery()->get_center();
-       globals->get_tile_mgr()->update( aip.getSGLocation(), visibility_meters, (aip.getSGLocation())->get_absolute_view_pos( scenery_center ) );
+       globals->get_tile_mgr()->update( _aip.getSGLocation(), visibility_meters, (_aip.getSGLocation())->get_absolute_view_pos( scenery_center ) );
        // save results of update in SGLocation for fdm...
        
        //if ( globals->get_scenery()->get_cur_elev() > -9990 ) {
@@ -1312,10 +1532,10 @@ void FGAILocalTraffic::DoGroundElev() {
        //}
        
        // The need for this here means that at least 2 consecutive passes are needed :-(
-       aip.getSGLocation()->set_tile_center( globals->get_scenery()->get_next_center() );
+       _aip.getSGLocation()->set_tile_center( globals->get_scenery()->get_next_center() );
        
        //cout << "Transform Elev is " << globals->get_scenery()->get_cur_elev() << '\n';
-       aip.getSGLocation()->set_cur_elev_m(globals->get_scenery()->get_cur_elev());
+       _aip.getSGLocation()->set_cur_elev_m(globals->get_scenery()->get_cur_elev());
        //return(globals->get_scenery()->get_cur_elev());
 }
 
index d6f21c73592fdd8446b5ec0a4fad9ed7c285645d..690ebd2e7cf4c429f43427e9d5fc9ed4cfdc019e 100644 (file)
@@ -45,7 +45,8 @@ enum TaxiState {
 enum OperatingState {
        IN_PATTERN,
        TAXIING,
-       PARKED
+       PARKED,
+       EN_ROUTE
 };
 
 struct StartOfDescent {
@@ -58,11 +59,12 @@ class FGAILocalTraffic : public FGAIPlane {
        
 public:
        
+       // At the moment we expect the expanded short form callsign - eventually we will just want the reg + type.
        FGAILocalTraffic();
        ~FGAILocalTraffic();
        
        // Initialise
-       bool Init(string ICAO, OperatingState initialState = PARKED, PatternLeg initialLeg = DOWNWIND);
+       bool Init(const string& callsign, string ICAO, OperatingState initialState = PARKED, PatternLeg initialLeg = DOWNWIND);
        
        // Run the internal calculations
        void Update(double dt);
@@ -94,33 +96,48 @@ protected:
        // Attempt to enter the traffic pattern in a reasonably intelligent manner
        void EnterTrafficPattern(double dt);
        
-       // Do what is necessary to land and parkup at home airport
-       void ReturnToBase(double dt);
+       // Set up the internal state to be consistent for a downwind entry.
+       void DownwindEntry();
        
-private:
-       FGATCMgr* ATC;  
-       // This is purely for synactic convienience to avoid writing globals->get_ATC_mgr()-> all through the code!
-
-       // High-level stuff
-       OperatingState operatingState;
-       int circuitsToFly;      //Number of circuits still to do in this session NOT INCLUDING THE CURRENT ONE
-       bool touchAndGo;        //True if circuits should be flown touch and go, false for full stop
+       // Ditto for straight-in
+       void StraightInEntry(bool des = false);
        
-       // Its possible that this might be moved out to the ground/airport class at some point.
-       FGATCAlignedProjection ortho;   // Orthogonal mapping of the local area with the threshold at the origin
-       // and the runway aligned with the y axis.
+       // Do what is necessary to land and parkup at home airport
+       void ReturnToBase(double dt);
        
        // Airport/runway/pattern details
        string airportID;       // The ICAO code of the airport that we're operating around
        double aptElev;         // Airport elevation
        FGGround* ground;       // A pointer to the ground control.
        FGTower* tower; // A pointer to the tower control.
+       bool _controlled;       // Set true if we find tower control working for the airport, false otherwise.
        RunwayDetails rwy;
        double patternDirection;        // 1 for right, -1 for left (This is double because we multiply/divide turn rates
        // with it to get RH/LH turns - DON'T convert it to int under ANY circumstances!!
        double glideAngle;              // Assumed to be visual glidepath angle for FGAILocalTraffic - can be found at www.airnav.com
        // Its conceivable that patternDirection and glidePath could be moved into the RunwayDetails structure.
        
+       // Its possible that this might be moved out to the ground/airport class at some point.
+       FGATCAlignedProjection ortho;   // Orthogonal mapping of the local area with the threshold at the origin
+       // and the runway aligned with the y axis.
+       
+       void GetAirportDetails(string id);
+       
+       void GetRwyDetails(string id);
+       
+       double responseCounter;         // timer in seconds to allow response to requests to be a little while after them
+       // Will almost certainly get moved to FGAIPlane.        
+       
+private:
+       FGATCMgr* ATC;  
+       // This is purely for synactic convienience to avoid writing globals->get_ATC_mgr()-> all through the code!
+
+       // High-level stuff
+       OperatingState operatingState;
+       int circuitsToFly;      //Number of circuits still to do in this session NOT INCLUDING THE CURRENT ONE
+       bool touchAndGo;        //True if circuits should be flown touch and go, false for full stop
+       bool transmitted;       // Set true when a position report for the current leg has been transmitted.
+       
        // Performance characteristics of the plane in knots and ft/min - some of this might get moved out into FGAIPlane
        double Vr;
        double best_rate_of_climb_speed;
@@ -175,14 +192,19 @@ private:
        bool reportReadyForDeparture;   // set true when ATC has requested that the plane report when ready for departure
        bool clearedToLineUp;
        bool clearedToTakeOff;
+       bool _clearedToLand;    // also implies cleared for the option.
        bool liningUp;  // Set true when the turn onto the runway heading is commenced when taxiing out
        bool goAround;  // Set true if need to go-around
        bool goAroundCalled;    // Set true during go-around only after we have called our go-around on the radio
        bool contactTower;      // we have been told to contact tower
        bool contactGround;     // we have been told to contact ground
        bool changeFreq;        // true when we need to change frequency
+       bool _taxiToGA;         // Temporary mega-hack indicating we are to taxi to the GA parking and disconnect from tower control.
        atc_type changeFreqType;        // the service we need to change to
-       double responseCounter;         // timer in seconds to allow response to requests to be a little while after them
+       bool freeTaxi;  // False if the airport has a facilities file with a logical taxi network defined, true if we need to calculate our own taxiing points.
+       
+       // Hack for getting close to the runway when atan can go pear-shaped
+       double _savedSlope;
 
        void FlyTrafficPattern(double dt);
 
@@ -201,8 +223,6 @@ private:
        void GetNextTaxiNode();
        
        void DoGroundElev();
-       
-       void GetRwyDetails();
 };
 
 #endif  // _FG_AILocalTraffic_HXX
index d44a845114339721d29772b4f2e48a984933ccfa..7c9e60350241357f74c33c6ab6af56742372814e 100644 (file)
@@ -23,6 +23,7 @@
 
 #include <Main/fg_props.hxx>
 #include <Main/globals.hxx>
+#include <simgear/math/sg_random.h>
 
 #include <list>
 
 #  include <dirent.h>          // for directory reading
 #endif
 
+#ifdef FG_WEATHERCM
+# include <WeatherCM/FGLocalWeatherDatabase.h>
+#else
+# include <Environment/environment_mgr.hxx>
+# include <Environment/environment.hxx>
+#endif
+
 #include "AIMgr.hxx"
 #include "AILocalTraffic.hxx"
+#include "AIGAVFRTraffic.hxx"
 #include "ATCutils.hxx"
+#include "commlist.hxx"
 
 SG_USING_STD(list);
 SG_USING_STD(cout);
@@ -43,12 +53,18 @@ SG_USING_STD(cout);
 FGAIMgr::FGAIMgr() {
        ATC = globals->get_ATC_mgr();
        initDone = false;
+       ai_callsigns_used["CFGFS"] = 1; // so we don't inadvertently use this
+       // TODO - use the proper user callsign when it becomes user settable.
+       removalList.clear();
+       activated.clear();
 }
 
 FGAIMgr::~FGAIMgr() {
 }
 
 void FGAIMgr::init() {
+       //cout << "AIMgr::init called..." << endl;
+       
        // Pointers to user's position
        lon_node = fgGetNode("/position/longitude-deg", true);
        lat_node = fgGetNode("/position/latitude-deg", true);
@@ -58,12 +74,26 @@ void FGAIMgr::init() {
        lat = lat_node->getDoubleValue();
        elev = elev_node->getDoubleValue();
        
+       // Load up models at the start to avoid pausing later
+       // Hack alert - Hardwired paths!!
+       string planepath = "Aircraft/c172/Models/c172-dpm.ac";
+       _defaultModel = sgLoad3DModel( globals->get_fg_root(),
+                                         planepath.c_str(),
+                                         globals->get_props(),
+                                         globals->get_sim_time_sec() );
+                                                                         
+       planepath = "Aircraft/pa28-161/Models/pa28-161.ac";
+       _piperModel = sgLoad3DModel( globals->get_fg_root(),
+                                         planepath.c_str(),
+                                         globals->get_props(),
+                                         globals->get_sim_time_sec() );
+                                                                         
        // go through the $FG_ROOT/ATC directory and find all *.taxi files
        SGPath path(globals->get_fg_root());
        path.append("ATC/");
        string dir = path.dir();
-    string ext;
-    string file, f_ident;
+       string ext;
+       string file, f_ident;
        int pos;
        
        // WARNING - I (DCL) haven't tested this on MSVC - this is simply cribbed from TerraGear
@@ -88,12 +118,12 @@ void FGAIMgr::init() {
                                if(dclFindAirportID(f_ident, &a)) {
                                        SGBucket sgb(a.longitude, a.latitude);
                                        int idx = sgb.gen_index();
-                                       if(airports.find(idx) != airports.end()) {
-                                               airports[idx]->push_back(f_ident);
+                                       if(facilities.find(idx) != facilities.end()) {
+                                               facilities[idx]->push_back(f_ident);
                                        } else {
                                                aptID_list_type* apts = new aptID_list_type;
                                                apts->push_back(f_ident);
-                                               airports[idx] = apts;
+                                               facilities[idx] = apts;
                                        }
                                        SG_LOG(SG_ATC, SG_BULK, "Mapping " << f_ident << " to bucket " << idx); 
                                }
@@ -119,12 +149,12 @@ void FGAIMgr::init() {
                                if(dclFindAirportID(f_ident, &a)) {
                                        SGBucket sgb(a.longitude, a.latitude);
                                        int idx = sgb.gen_index();
-                                       if(airports.find(idx) != airports.end()) {
-                                               airports[idx]->push_back(f_ident);
+                                       if(facilities.find(idx) != facilities.end()) {
+                                               facilities[idx]->push_back(f_ident);
                                        } else {
-                                               aptID_list_type* apts = new aptID_list_type;
+                                               ID_list_type* apts = new ID_list_type;
                                                apts->push_back(f_ident);
-                                               airports[idx] = apts;
+                                               facilities[idx] = apts;
                                        }
                                        SG_LOG(SG_ATC, SG_BULK, "Mapping " << f_ident << " to bucket " << idx);
                                }
@@ -135,9 +165,27 @@ void FGAIMgr::init() {
 #endif
        
        // See if are in range at startup and activate if necessary
-       SearchByPos(10.0);
+       SearchByPos(15.0);
        
        initDone = true;
+       
+       //cout << "AIMgr::init done..." << endl;
+       
+       /*
+       // TESTING
+       FGATCAlignedProjection ortho;
+       ortho.Init(dclGetAirportPos("KEMT"), 205.0);    // Guess of rwy19 heading
+       //Point3D ip = ortho.ConvertFromLocal(Point3D(6000, 1000, 1000));       // 90 deg entry
+       //Point3D ip = ortho.ConvertFromLocal(Point3D(-7000, 3000, 1000));      // 45 deg entry
+       Point3D ip = ortho.ConvertFromLocal(Point3D(1000, -7000, 1000));        // straight-in
+       ATC->AIRegisterAirport("KEMT");
+       FGAIGAVFRTraffic* p = new FGAIGAVFRTraffic();
+       p->SetModel(_defaultModel);
+       p->Init(ip, "KEMT", GenerateShortForm(GenerateUniqueCallsign()));
+       ai_list.push_back(p);
+       traffic[ident].push_back(p);
+       activated["KEMT"] = 1;
+       */      
 }
 
 void FGAIMgr::bind() {
@@ -152,6 +200,11 @@ void FGAIMgr::update(double dt) {
                SG_LOG(SG_ATC, SG_WARN, "Warning - AIMgr::update(...) called before AIMgr::init()");
        }
        
+       //cout << activated.size() << '\n';
+       
+       Point3D userPos = Point3D(lon_node->getDoubleValue(), lat_node->getDoubleValue(), elev_node->getDoubleValue());
+       
+       // TODO - make these class variables!!
        static int i = 0;
        static int j = 0;
 
@@ -163,25 +216,106 @@ void FGAIMgr::update(double dt) {
        }
        
        if(j == 215) {
-               SearchByPos(15.0);
+               SearchByPos(25.0);
                j = 0;
+       } else if(j == 200) {
+               // Go through the list of activated airports and remove those out of range
+               //cout << "The following airports have been activated by the AI system:\n";
+               ai_activated_map_iterator apt_itr = activated.begin();
+               while(apt_itr != activated.end()) {
+                       //cout << "FIRST IS " << (*apt_itr).first << '\n';
+                       if(dclGetHorizontalSeparation(userPos, dclGetAirportPos((*apt_itr).first)) > (35.0 * 1600.0)) {
+                               // Then get rid of it and make sure the iterator is left pointing to the next one!
+                               string s = (*apt_itr).first;
+                               if(traffic.find(s) != traffic.end()) {
+                                       //cout << "s = " << s << ", traffic[s].size() = " << traffic[s].size() << '\n';
+                                       if(traffic[s].size()) {
+                                               apt_itr++;
+                                       } else {
+                                               //cout << "Erasing " << (*apt_itr).first << " and traffic" << '\n';
+                                               activated.erase(apt_itr++);
+                                               traffic.erase(s);
+                                       }
+                               } else {
+                                               //cout << "Erasing " << (*apt_itr).first << ' ' << (*apt_itr).second << '\n';
+                                               activated.erase(apt_itr++);
+                               }
+                       } else {
+                               apt_itr++;
+                       }
+               }
+       } else if(j == 180) {
+               // Go through the list of activated airports and do the random airplane generation
+               ai_traffic_map_iterator it = traffic.begin();
+               while(it != traffic.end()) {
+                       string s = (*it).first;
+                       //cout << "s = " << s << " size = " << (*it).second.size() << '\n';
+                       // Only generate extra traffic if within a certain distance of the user,
+                       // TODO - maybe take users's tuned freq into account as well.
+                       double d = dclGetHorizontalSeparation(userPos, dclGetAirportPos(s)); 
+                       if(d < (15.0 * 1600.0)) {
+                               double cd = 0.0;
+                               bool gen = false;
+                               //cout << "Size of list is " << (*it).second.size() << " at " << s << '\n';
+                               if((*it).second.size()) {
+                                       FGAIEntity* e = *((*it).second.rbegin());
+                                       cd = dclGetHorizontalSeparation(e->GetPos(), dclGetAirportPos(s));
+                                       if(cd < (d < 5000 ? 10000 : d + 5000)) {
+                                               gen = true;
+                                       }
+                               } else {
+                                       gen = true;
+                                       cd = 0.0;
+                               }
+                               if(gen) {
+                                       //cout << "Generating extra traffic at airport " << s << ", at least " << cd << " meters out\n";
+                                       //GenerateSimpleAirportTraffic(s, cd);
+                                       GenerateSimpleAirportTraffic(s, cd + 2000.0);   // The random seems a bit wierd - traffic could get far too bunched without the +2000.
+                               }
+                       }
+                       ++it;
+               }
        }
        
        ++j;
        
+       //cout << "Size of AI list is " << ai_list.size() << '\n';
+       
        // TODO - need to add a check of if any activated airports have gone out of range
        
+       string rs;      // plane to be removed, if one.
+       if(removalList.size()) {
+               rs = *(removalList.begin());
+               removalList.pop_front();
+       } else {
+               rs = "";
+       }
+       
        // Traverse the list of active planes and run all their update methods
        // TODO - spread the load - not all planes should need updating every frame.
        // Note that this will require dt to be calculated for each plane though
        // since they rely on it to calculate distance travelled.
        ai_list_itr = ai_list.begin();
        while(ai_list_itr != ai_list.end()) {
-               (*ai_list_itr)->Update(dt);
-               ++ai_list_itr;
+               FGAIEntity *e = *ai_list_itr;
+               if(rs.size() && e->GetCallsign() == rs) {
+                       //cout << "Removing " << rs << " from ai_list\n";
+                       ai_list_itr = ai_list.erase(ai_list_itr);
+                       delete e;
+                       // This is a hack - we should deref this plane from the airport count!
+               } else {
+                       e->Update(dt);
+                       ++ai_list_itr;
+               }
        }
+
+       //cout << "Size of AI list is " << ai_list.size() << '\n';
 }
 
+void FGAIMgr::ScheduleRemoval(string s) {
+       //cout << "Scheduling removal of plane " << s << " from AIMgr\n";
+       removalList.push_back(s);
+}
 
 // Activate AI traffic at an airport
 void FGAIMgr::ActivateAirport(string ident) {
@@ -189,21 +323,197 @@ void FGAIMgr::ActivateAirport(string ident) {
        // TODO - need to start the traffic more randomly
        FGAILocalTraffic* local_traffic = new FGAILocalTraffic;
        //local_traffic->Init(ident, IN_PATTERN, TAKEOFF_ROLL);
-       local_traffic->Init(ident);
+       local_traffic->Init(GenerateShortForm(GenerateUniqueCallsign()), ident);
        local_traffic->FlyCircuits(1, true);    // Fly 2 circuits with touch & go in between
        ai_list.push_back(local_traffic);
+       traffic[ident].push_back(local_traffic);
+       //cout << "******** ACTIVATING AIRPORT, ident = " << ident << '\n';
+       activated[ident] = 1;
+}
+
+// Hack - Generate AI traffic at an airport with no facilities file
+void FGAIMgr::GenerateSimpleAirportTraffic(string ident, double min_dist) {
+       // Ugly hack - don't let VFR Cessnas operate at a hardwired list of major airports
+       // This will go eventually once airport .xml files specify the traffic profile
+       if(ident == "KSFO" || ident == "KDFW" || ident == "EGLL" || ident == "KORD" || ident == "KJFK" 
+                          || ident == "KMSP" || ident == "KLAX" || ident == "KBOS" || ident == "KEDW"
+                                          || ident == "KSEA" || ident == "EHAM") {
+               return;
+       }
+       
+       /*
+       // TODO - check for military airports - this should be in the current data.
+       // UGGH - there's no point at the moment - everything is labelled civil in basic.dat!
+       FGAirport a;
+       if(dclFindAirportID(ident, &a)) {
+               cout << "CODE IS " << a.code << '\n';
+       } else {
+               // UG - can't find the airport!
+               return;
+       }
+       */
+       
+       Point3D aptpos = dclGetAirportPos(ident);       // TODO - check for elev of -9999
+       //cout << "ident = " << ident << ", elev = " << aptpos.elev() << '\n';
+       
+       // Operate from airports at 3000ft and below only to avoid the default cloud layers and since we don't degrade AI performance with altitude.
+       if(aptpos.elev() > 3000) {
+               //cout << "High alt airports not yet supported - returning\n";
+               return;
+       }
+       
+       // Rough hack for plane type - make 70% of the planes cessnas, the rest pipers.
+       bool cessna = true;
+       
+       // Get the time and only operate VFR in the (approximate) daytime.
+       //SGTime *t = globals->get_time_params();
+       string time_str = fgGetString("sim/time/gmt-string");
+       int loc_time = atoi((time_str.substr(0,3)).c_str());
+       //cout << "gmt_time = " << loc_time << '\n';
+       loc_time += (int)((aptpos.lon() / 360.0) * 24.0);
+       while(loc_time < 0) loc_time += 24;
+       while(loc_time > 24) loc_time -= 24;
+       //cout << "loc_time = " << loc_time << '\n';
+       if(loc_time < 7 || loc_time > 19) return;
+       
+       // Check that the visibility is OK for IFR operation.
+       double visibility;
+       #ifdef FG_WEATHERCM
+       //sgVec3 position = { aptpos.lat(), aptpos.lon(), aptpos.elev() };
+       //FGPhysicalProperty stationweather = WeatherDatabase->get(position);
+       #else
+       FGEnvironment stationweather =
+            ((FGEnvironmentMgr *)globals->get_subsystem("environment"))
+              ->getEnvironment(aptpos.lat(), aptpos.lon(), aptpos.elev());     // TODO - check whether this should take ft or m for elev.
+       #endif
+       #ifdef FG_WEATHERCM
+       visibility = fgGetDouble("/environment/visibility-m");
+       #else
+       visibility = stationweather.get_visibility_m();
+       #endif
+       // Technically we can do VFR down to 1 mile (1600m) but that's pretty murky!
+       //cout << "vis = " << visibility << '\n';
+       if(visibility < 3000) return;
+       
+       ATC->AIRegisterAirport(ident);
+       
+       // Next - get the distance from user to the airport.
+       Point3D userpos = Point3D(lon_node->getDoubleValue(), lat_node->getDoubleValue(), elev_node->getDoubleValue());
+       double d = dclGetHorizontalSeparation(userpos, aptpos); // in meters
+       
+       int lev = fgGetInt("/sim/ai-traffic/level");
+       if(lev < 1 || lev > 3) lev = 2;
+       if(visibility < 6000) lev = 1;
+       //cout << "level = " << lev << '\n';
+       
+       // Next - generate any local / circuit traffic
+
+       /*
+       // --------------------------- THIS BLOCK IS JUST FOR TESTING - COMMENT OUT BEFORE RELEASE ---------------
+       // Finally - generate VFR approaching traffic
+       //if(d > 2000) {
+       if(ident == "KPOC") {
+               double ad = 2000.0;
+               double avd = 3000.0;    // average spacing of arriving traffic in meters - relate to airport business and AI density setting one day!
+               //while(ad < (d < 10000 ? 12000 : d + 2000)) {
+               for(int i=0; i<8; ++i) {
+                       double dd = sg_random() * avd;
+                       // put a minimum spacing in for now since I don't think tower will cope otherwise!
+                       if(dd < 1500) dd = 1500; 
+                       //ad += dd;
+                       ad += dd;
+                       double dir = int(sg_random() * 36);
+                       if(dir == 36) dir--;
+                       dir *= 10;
+                       //dir = 180;
+                       if(sg_random() < 0.3) cessna = false;
+                       else cessna = true;
+                       string s = GenerateShortForm(GenerateUniqueCallsign(), (cessna ? "Cessna-" : "Piper-"));
+                       FGAIGAVFRTraffic* t = new FGAIGAVFRTraffic();
+                       t->SetModel(cessna ? _defaultModel : _piperModel);
+                       //cout << "Generating VFR traffic " << s << " inbound to " << ident << " " << ad << " meters out from " << dir << " degrees\n";
+                       Point3D tpos = dclUpdatePosition(aptpos, dir, 6.0, ad);
+                       if(tpos.elev() > (aptpos.elev() + 3000.0)) tpos.setelev(aptpos.elev() + 3000.0);
+                       t->Init(tpos, ident, s);
+                       ai_list.push_back(t);
+               }
+       }
        activated[ident] = 1;
-}      
+       return;
+       //---------------------------------------------------------------------------------------------------
+       */
+       
+       double ad;   // Minimum distance out of first arriving plane in meters.
+       double mind; // Minimum spacing of traffic in meters
+       double avd;  // average spacing of arriving traffic in meters - relate to airport business and AI density setting one day!
+       // Finally - generate VFR approaching traffic
+       //if(d > 2000) {
+       if(1) {
+               if(lev == 3) {
+                       ad = 5000.0;
+                       mind = 2000.0;
+                       avd = 6000.0;
+               } else if(lev == 2) {
+                       ad = 8000.0;
+                       mind = 4000.0;
+                       avd = 10000.0;
+               } else {
+                       ad = 9000.0;    // Start the first aircraft at least 9K out for now.
+                       mind = 6000.0;
+                       avd = 15000.0;
+               }
+               /*
+               // Check if there is already arriving traffic at this airport
+               cout << "BING A " << ident << '\n';
+               if(traffic.find(ident) != traffic.end()) {
+                       cout << "BING B " << ident << '\n';
+                       ai_list_type lst = traffic[ident];
+                       cout << "BING C " << ident << '\n';
+                       if(lst.size()) {
+                               cout << "BING D " << ident << '\n';
+                               double cd = dclGetHorizontalSeparation(aptpos, (*lst.rbegin())->GetPos());
+                               cout << "ident = " << ident << ", cd = " << cd << '\n';
+                               if(cd > ad) ad = cd;
+                       }
+               }
+               */
+               if(min_dist != 0) ad = min_dist;
+               //cout << "ident = " << ident << ", ad = " << ad << '\n';
+               while(ad < (d < 5000 ? 15000 : d + 10000)) {
+                       double dd = mind + (sg_random() * (avd - mind));
+                       ad += dd;
+                       double dir = int(sg_random() * 36);
+                       if(dir == 36) dir--;
+                       dir *= 10;
+                       if(sg_random() < 0.3) cessna = false;
+                       else cessna = true;
+                       string s = GenerateShortForm(GenerateUniqueCallsign(), (cessna ? "Cessna-" : "Piper-"));
+                       FGAIGAVFRTraffic* t = new FGAIGAVFRTraffic();
+                       t->SetModel(cessna ? _defaultModel : _piperModel);
+                       //cout << "Generating VFR traffic " << s << " inbound to " << ident << " " << ad << " meters out from " << dir << " degrees\n";
+                       Point3D tpos = dclUpdatePosition(aptpos, dir, 6.0, ad);
+                       if(tpos.elev() > (aptpos.elev() + 3000.0)) tpos.setelev(aptpos.elev() + 3000.0);        // FEET yuk :-(
+                       t->Init(tpos, ident, s);
+                       ai_list.push_back(t);
+                       traffic[ident].push_back(t);
+               }
+       }       
+}
 
+/*
+// Generate a VFR arrival at airport apt, at least distance d (meters) out.
+void FGAIMgr::GenerateVFRArrival(string apt, double d) {
+}
+*/
 
 // Search for valid airports in the vicinity of the user and activate them if necessary
-void FGAIMgr::SearchByPos(double range)
-{
+void FGAIMgr::SearchByPos(double range) {
        //cout << "In SearchByPos(...)" << endl;
        
        // get bucket number for plane position
        lon = lon_node->getDoubleValue();
        lat = lat_node->getDoubleValue();
+       elev = elev_node->getDoubleValue() * SG_FEET_TO_METER;
        SGBucket buck(lon, lat);
 
        // get neigboring buckets
@@ -212,6 +522,7 @@ void FGAIMgr::SearchByPos(double range)
        int by = (int)( range*SG_NM_TO_METER / buck.get_height_m() / 2 );
        //cout << "by = " << by << endl;
        
+       // Search for airports with facitities files --------------------------
        // loop over bucket range 
        for ( int i=-bx; i<=bx; i++) {
                //cout << "i loop\n";
@@ -220,10 +531,10 @@ void FGAIMgr::SearchByPos(double range)
                        buck = sgBucketOffset(lon, lat, i, j);
                        long int bucket = buck.gen_index();
                        //cout << "bucket is " << bucket << endl;
-                       if(airports.find(bucket) != airports.end()) {
-                               aptID_list_type* apts = airports[bucket];
-                               aptID_list_iterator current = apts->begin();
-                               aptID_list_iterator last = apts->end();
+                       if(facilities.find(bucket) != facilities.end()) {
+                               ID_list_type* apts = facilities[bucket];
+                               ID_list_iterator current = apts->begin();
+                               ID_list_iterator last = apts->end();
                                
                                //cout << "Size of apts is " << apts->size() << endl;
                                
@@ -239,7 +550,10 @@ void FGAIMgr::SearchByPos(double range)
                                                //if(dclFindAirportID(*current, &a)) {
                                                        //      // We can do something here based on distance from the user if we wish.
                                                //}
+                                               //string s = *current;
+                                               //cout << "s = " << s << '\n';
                                                ActivateAirport(*current);
+                                               //ActivateSimpleAirport(*current);      // TODO - put this back to ActivateAirport when that code is done.
                                                //cout << "Activation done" << endl;
                                        } else {
                                                //cout << *current << " already activated" << endl;
@@ -248,4 +562,86 @@ void FGAIMgr::SearchByPos(double range)
                        }
                }
        }
+       //-------------------------------------------------------------
+       
+       // Search for any towered airports in the vicinity ------------
+       comm_list_type towered;
+       comm_list_iterator twd_itr;
+       
+       int num_twd = current_commlist->FindByPos(lon, lat, elev, range, &towered, TOWER);
+       if (num_twd != 0) {
+               double closest = 1000000;
+               string s = "";
+               for(twd_itr = towered.begin(); twd_itr != towered.end(); twd_itr++) {
+                       // Only activate the closest airport not already activated each time.
+                       if(activated.find(twd_itr->ident) == activated.end()) {
+                               double sep = dclGetHorizontalSeparation(Point3D(lon, lat, elev), dclGetAirportPos(twd_itr->ident));
+                               if(sep < closest) {
+                                       closest = sep;
+                                       s = twd_itr->ident;
+                               }
+                               
+                       }
+               }
+               if(s.size()) {
+                       // TODO - find out why empty strings come through here when all in-range airports done.
+                       GenerateSimpleAirportTraffic(s);
+                       //cout << "**************ACTIVATING SIMPLE AIRPORT, ident = " << s << '\n';
+                       activated[s] = 1;
+               }
+       }
+}
+
+string FGAIMgr::GenerateCallsign() {
+       // For now we'll just generate US callsigns until we can regionally identify airports.
+       string s = "N";
+       // Add 3 to 5 numbers and make up to 5 with letters.
+       //sg_srandom_time();
+       double d = sg_random();
+       int n = int(d * 3);
+       if(n == 3) --n;
+       //cout << "First n, n = " << n << '\n';
+       int j = 3 + n;
+       //cout << "j = " << j << '\n';
+       for(int i=0; i<j; ++i) { 
+               int n = int(sg_random() * 10);
+               if(n == 10) --n;
+               s += (char)('0' + n);
+       }
+       for(int i=j; i<5; ++i) {
+               int n = int(sg_random() * 26);
+               if(n == 26) --n;
+               //cout << "Alpha, n = " << n << '\n';
+               s += (char)('A' + n);
+       }
+       //cout << "s = " << s << '\n';
+       return(s);
+}
+
+string FGAIMgr::GenerateUniqueCallsign() {
+       while(1) {
+               string s = GenerateCallsign();
+               if(!ai_callsigns_used[s]) {
+                       ai_callsigns_used[s] = 1;
+                       return(s);
+               }
+       }
+}
+
+// This will be moved somewhere else eventually!!!!
+string FGAIMgr::GenerateShortForm(string callsign, string plane_str, bool local) {
+       //cout << callsign << '\n';
+       string s;
+       if(local) s = "Trainer-";
+       else s = plane_str;
+       for(int i=3; i>0; --i) {
+               char c = callsign[callsign.size() - i];
+               //cout << c << '\n';
+               string tmp = "";
+               tmp += c;
+               if(isalpha(c)) s += GetPhoneticIdent(c);
+               else s += ConvertNumToSpokenDigits(tmp);
+               if(i > 1) s += '-';
+       }
+       return(s);
 }
index 2c67eb020fb3bd946cfe0d687b9d6fa2e22d37e7..b15d3efdc9206175abe060d5a21b134ca7ee2a1b 100644 (file)
@@ -54,19 +54,32 @@ private:
     // at any point in or at the end of the list.
     // Hence any new access must explicitly first check for atc_list.end() before dereferencing.
        
-       // A list of airport ID's
-       typedef list < string > aptID_list_type;
-       typedef aptID_list_type::iterator aptID_list_iterator;
+       // A list of airport or airplane ID's
+       typedef list < string > ID_list_type;
+       typedef ID_list_type::iterator ID_list_iterator;
+
+       // Temporary storage of ID of planes scheduled for removeal
+       ID_list_type removalList;
        
        // A map of airport-IDs that have taxiway network files against bucket number
-       typedef map < int, aptID_list_type* > ai_apt_map_type;
+       typedef map < int, ID_list_type* > ai_apt_map_type;
        typedef ai_apt_map_type::iterator ai_apt_map_iterator;
-       ai_apt_map_type airports;
+       ai_apt_map_type facilities;
        
        // A map of airport ID's that we've activated AI traffic at
        typedef map < string, int > ai_activated_map_type;
        typedef ai_activated_map_type::iterator ai_activated_map_iterator;
        ai_activated_map_type activated;
+       
+       // AI traffic lists mapped by airport
+       typedef map < string, ai_list_type > ai_traffic_map_type;
+       typedef ai_traffic_map_type::iterator ai_traffic_map_iterator;
+       ai_traffic_map_type traffic;
+       
+       // A map of callsigns that we have used (eg CFGFS or N0546D - the code will generate Cessna-four-six-delta from this later)
+       typedef map < string, int > ai_callsigns_map_type;
+       typedef ai_callsigns_map_type::iterator ai_callsigns_map_iterator;
+       ai_callsigns_map_type ai_callsigns_used;
 
     // Position of the Users Aircraft
     double lon;
@@ -89,8 +102,15 @@ public:
     void unbind();
 
     void update(double dt);
+       
+       // Signal that it is OK to remove a plane of callsign s
+       // (To be called by the plane itself).
+       void ScheduleRemoval(string s);
 
 private:
+       
+       ssgBranch* _defaultModel;  // Cessna 172!
+       ssgBranch* _piperModel;    // pa28-161
 
        bool initDone;  // Hack - guard against update getting called before init
 
@@ -98,10 +118,19 @@ private:
     //void RemoveFromList(const char* id, atc_type tp);
        
        // Activate AI traffic at an airport
-       void ActivateAirport(string id);
+       void ActivateAirport(string ident);
+       
+       // Hack - Generate AI traffic at an airport with no facilities file, with the first plane being at least min_dist out.
+       void GenerateSimpleAirportTraffic(string ident, double min_dist = 0.0);
        
        // Search for valid airports in the vicinity of the user and activate them if necessary
        void SearchByPos(double range);
+       
+       string GenerateCallsign();
+       
+       string GenerateUniqueCallsign();
+       
+       string GenerateShortForm(string callsign, string plane_str = "Cessna-", bool local = false);
 
 };
 
index 3594e5e70f82a02d98d946807afff684f9404057..23ebeda2ebbe265feb9e49a8e5909cd0f687eb4c 100644 (file)
@@ -53,6 +53,7 @@ void FGAIPlane::Update(double dt) {
        if(_pending) {
                if(tuned_station) {
                        if(tuned_station->GetFreqClear()) {
+                               //cout << "TUNED STATION FREQ CLEAR\n";
                                tuned_station->SetFreqInUse();
                                _pending = false;
                                _transmit = true;
@@ -69,6 +70,7 @@ void FGAIPlane::Update(double dt) {
                        }
                } else {
                        // Not tuned to ATC - Just go ahead and transmit
+                       //cout << "NOT TUNED TO ATC\n";
                        _pending = false;
                        _transmit = true;
                        _transmitting = false;
@@ -115,16 +117,16 @@ void FGAIPlane::Update(double dt) {
 
 void FGAIPlane::Bank(double angle) {
        // This *should* bank us smoothly to any angle
-       if(fabs(roll - angle) > 0.6) {
-               roll -= ((roll - angle)/fabs(roll - angle));  
+       if(fabs(_roll - angle) > 0.6) {
+               _roll -= ((_roll - angle)/fabs(_roll - angle));  
        }
 }
 
 // Duplication of Bank(0.0) really - should I cut this?
 void FGAIPlane::LevelWings(void) {
        // bring the plane back to level smoothly (this should work to come out of either bank)
-       if(fabs(roll) > 0.6) {
-               roll -= (roll/fabs(roll));
+       if(fabs(_roll) > 0.6) {
+               _roll -= (_roll/fabs(_roll));
        }
 }
 
index 7f00d89a5297ac07968540cb6bf6234a56f45ddc..e6da8ded4d7124211506b3e4c5055bc357a68888 100644 (file)
@@ -83,6 +83,9 @@ public:
        
        // Return what type of landing we're doing on this circuit
        virtual LandingType GetLandingOption();
+       
+       // Return the callsign
+       inline string GetCallsign() {return plane.callsign;}
 
 protected:
        PlaneRec plane;
index 7adb85c4f7d97b00d45f5cd005858ae6d9ba14f4..085d84166b7523e8fb14c99575a91cdc43e9b351 100644 (file)
@@ -34,8 +34,10 @@ FGATC::FGATC() {
        receiving = false;
        respond = false;
        runResponseCounter = false;
+       _runReleaseCounter = false;
        responseID = "";
        responseReqd = false;
+       _type = INVALID;
 }
 
 FGATC::~FGATC() {
@@ -53,6 +55,15 @@ void FGATC::Update(double dt) {
                        responseCounter += dt;
                }
        }
+       
+       if(_runReleaseCounter) {
+               if(_releaseCounter >= _releaseTime) {
+                       freqClear = true;
+                       _runReleaseCounter = false;
+               } else {
+                       _releaseCounter += dt;
+               }
+       }
 }
 
 void FGATC::ReceiveUserCallback(int code) {
@@ -71,12 +82,15 @@ void FGATC::SetResponseReqd(string rid) {
 }
 
 void FGATC::NotifyTransmissionFinished(string rid) {
+       //cout << "Transmission finished, callsign = " << rid << '\n';
        receiving = false;
        responseID = rid;
        if(responseReqd) {
                runResponseCounter = true;
                responseCounter = 0.0;
-               responseTime = 1.8;             // TODO - randomize this slightly.
+               responseTime = 1.2;     // TODO - randomize this slightly, and allow it to be dependent on the transmission and how busy the ATC is.
+               respond = false;        // TODO - this ignores the fact that more than one plane could call this before response
+                                                       // Shouldn't happen with AI only, but user could confuse things??
        } else {
                freqClear = true;
        }
@@ -95,10 +109,6 @@ void FGATC::SetDisplay() {
 void FGATC::SetNoDisplay() {
 }
 
-atc_type FGATC::GetType() {
-       return INVALID;
-}
-
 void FGATC::SetData(ATCData* d) {
        lon = d->lon;
        lat = d->lat;
index adc5f2d8ef49cde3240aeffe6d7de656080791c1..0a06761a0c36968f211c87ee00af9109673b772f 100644 (file)
@@ -148,7 +148,7 @@ public:
        // The user will just have to wait for a gap in dialog as in real life.
        
        // Return the type of ATC station that the class represents
-       virtual atc_type GetType();
+       inline atc_type GetType() { return _type; }
        
        // Set the core ATC data
        void SetData(ATCData* d);
@@ -192,6 +192,7 @@ protected:
        int range;
        string ident;           // Code of the airport its at.
        string name;            // Name transmitted in the broadcast.
+       atc_type _type;
        
        // Rendering related stuff
        bool voice;                     // Flag - true if we are using voice
@@ -209,6 +210,9 @@ protected:
        string responseID;      // ID of the plane to respond to
        bool respond;   // Flag to indicate now is the time to respond - ie set following the count down of the response timer.
        // Derived classes only need monitor this flag, and use the response ID, as long as they call FGATC::Update(...)
+       bool _runReleaseCounter;        // A timer for releasing the frequency after giving the message enough time to display
+       double _releaseTime;
+       double _releaseCounter;
 };
 
 inline istream&
index c83b514ede2da6ad0056e7cfce1a634be49a9bd6..d3548985e3b19f6e697d038ef40635689ce70b39 100644 (file)
@@ -260,7 +260,7 @@ void FGATCDialog::add_entry(string station, string transmission, string menutext
 
 void FGATCDialog::remove_entry( const string &station, const string &trans, atc_type type ) {
   atcmentry_vec_type* p = &((available_dialog[type])[station]);
-  atcmentry_vec_iterator current  = p->begin();  
+  atcmentry_vec_iterator current = p->begin();  
   while(current != p->end()) {
     if(current->transmission == trans) current = p->erase(current);
        else ++current;
@@ -269,7 +269,7 @@ void FGATCDialog::remove_entry( const string &station, const string &trans, atc_
 
 void FGATCDialog::remove_entry( const string &station, int code, atc_type type ) {
   atcmentry_vec_type* p = &((available_dialog[type])[station]);
-  atcmentry_vec_iterator current  = p->begin();
+  atcmentry_vec_iterator current = p->begin();
   while(current != p->end()) {
     if(current->callback_code == code) current = p->erase(current);
        else ++current;
@@ -279,7 +279,7 @@ void FGATCDialog::remove_entry( const string &station, int code, atc_type type )
 // query the database whether the transmission is already registered; 
 bool FGATCDialog::trans_reg( const string &station, const string &trans, atc_type type ) {
   atcmentry_vec_type* p = &((available_dialog[type])[station]);
-  atcmentry_vec_iterator current  = p->begin();
+  atcmentry_vec_iterator current = p->begin();
   for ( ; current != p->end() ; ++current ) {
     if ( current->transmission == trans ) return true;
   }
@@ -289,7 +289,7 @@ bool FGATCDialog::trans_reg( const string &station, const string &trans, atc_typ
 // query the database whether the transmission is already registered; 
 bool FGATCDialog::trans_reg( const string &station, int code, atc_type type ) {
   atcmentry_vec_type* p = &((available_dialog[type])[station]);
-  atcmentry_vec_iterator current  = p->begin();
+  atcmentry_vec_iterator current = p->begin();
   for ( ; current != p->end() ; ++current ) {
     if ( current->callback_code == code ) return true;
   }
@@ -397,14 +397,23 @@ void FGATCDialog::PopupCallback() {
                                break;
                        }
                } else if(atcptr->GetType() == TOWER) {
-                       ATCMenuEntry a = ((available_dialog[TOWER])[(string)atcptr->get_ident()])[atcDialogCommunicationOptions->getValue()];
-                       atcptr->SetFreqInUse();
-                       globals->get_ATC_display()->RegisterSingleMessage(atcptr->GenText(a.transmission, a.callback_code));
-                       _callbackPending = true;
-                       _callbackTimer = 0.0;
-                       _callbackWait = 5.0;
-                       _callbackPtr = atcptr;
-                       _callbackCode = a.callback_code;
+                       //cout << "TOWER " << endl;
+                       //cout << "ident is " << atcptr->get_ident() << endl;
+                       atcmentry_vec_type atcmlist = (available_dialog[TOWER])[(string)atcptr->get_ident()];
+                       if(atcmlist.size()) {
+                               //cout << "Doing callback...\n";
+                               ATCMenuEntry a = atcmlist[atcDialogCommunicationOptions->getValue()];
+                               atcptr->SetFreqInUse();
+                               globals->get_ATC_display()->RegisterSingleMessage(atcptr->GenText(a.transmission, a.callback_code));
+                               _callbackPending = true;
+                               _callbackTimer = 0.0;
+                               _callbackWait = 5.0;
+                               _callbackPtr = atcptr;
+                               _callbackCode = a.callback_code;
+                       } else {
+                               //cout << "No options available...\n";
+                       }
+                       //cout << "Donded" << endl;
                }
        }
 }
index 6d0fda8bedb8d0e66004c21849b0bec09865136a..78635152a2d6fb2fe623396fd07e41e6f6f66efa 100644 (file)
@@ -35,8 +35,8 @@
 FGATCDisplay::FGATCDisplay() {
        rep_msg = false;
        change_msg_flag = false;
-       dsp_offset1 = 0;
-       dsp_offset2 = 0;
+       dsp_offset1 = 0.0;
+       dsp_offset2 = 0.0;
 }
 
 
@@ -195,6 +195,7 @@ void FGATCDisplay::update(double dt) {
 }
 
 void FGATCDisplay::RegisterSingleMessage(string msg, double delay) {
+       //cout << msg << '\n';
        atcMessage m;
        m.msg = msg;
        m.repeating = false;
index cd1686d9d50f2617fa6a62c7eecc64514fa84119..d87ed5d9650b13a46f5885f633a1a933574a8ac5 100644 (file)
@@ -48,6 +48,7 @@ AirportATC::AirportATC() :
     ground_active(false),
     set_by_AI(false),
        numAI(0)
+       //airport_atc_map.clear();
 {
        for(int i=0; i<ATC_NUM_TYPES; ++i) {
                set_by_comm[0][i] = false;
@@ -58,9 +59,9 @@ AirportATC::AirportATC() :
 FGATCMgr::FGATCMgr() {
        comm_ident[0] = "";
        comm_ident[1] = "";
-       last_comm_ident[0] = "";
-       last_comm_ident[1] = "";
-       approach_ident = "";
+       //last_comm_ident[0] = "";
+       //last_comm_ident[1] = "";
+       //approach_ident = "";
        last_in_range = false;
        comm_type[0] = INVALID;
        comm_type[1] = INVALID;
@@ -83,6 +84,8 @@ void FGATCMgr::unbind() {
 }
 
 void FGATCMgr::init() {
+       //cout << "ATCMgr::init called..." << endl;
+       
        comm_node[0] = fgGetNode("/radios/comm[0]/frequencies/selected-mhz", true);
        comm_node[1] = fgGetNode("/radios/comm[1]/frequencies/selected-mhz", true);
        lon_node = fgGetNode("/position/longitude-deg", true);
@@ -135,6 +138,7 @@ void FGATCMgr::init() {
     current_atcdialog->Init();
 
        initDone = true;
+       //cout << "ATCmgr::init done!" << endl;
 }
 
 void FGATCMgr::update(double dt) {
@@ -149,7 +153,7 @@ void FGATCMgr::update(double dt) {
        //Traverse the list of active stations.
        //Only update one class per update step to avoid the whole ATC system having to calculate between frames.
        //Eventually we should only update every so many steps.
-       //cout << "In FGATCMgr::update - atc_list.size = " << atc_list.size() << '\n';
+       //cout << "In FGATCMgr::update - atc_list.size = " << atc_list.size() << endl;
        if(atc_list.size()) {
                if(atc_list_itr == atc_list.end()) {
                        atc_list_itr = atc_list.begin();
@@ -161,6 +165,14 @@ void FGATCMgr::update(double dt) {
                ++atc_list_itr;
        }
        
+       /*
+       cout << "ATC_LIST: " << atc_list.size() << ' ';
+       for(atc_list_iterator it = atc_list.begin(); it != atc_list.end(); it++) {
+               cout << (*it)->get_ident() << ' ';
+       }
+       cout << '\n';
+       */
+       
        // Search the tuned frequencies every now and then - this should be done with the event scheduler
        static int i = 0;       // Very ugly - but there should only ever be one instance of FGATCMgr.
        /*
@@ -197,6 +209,7 @@ unsigned short int FGATCMgr::GetFrequency(string ident, atc_type tp) {
 // Might need more sophistication in this in the future - eg registration by aircraft call-sign.
 bool FGATCMgr::AIRegisterAirport(string ident) {
        SG_LOG(SG_ATC, SG_BULK, "AI registered airport " << ident << " with the ATC system");
+       //cout << "AI registered airport " << ident << " with the ATC system" << '\n';
        if(airport_atc_map.find(ident) != airport_atc_map.end()) {
                airport_atc_map[ident]->set_by_AI = true;
                airport_atc_map[ident]->numAI++;
@@ -234,9 +247,19 @@ bool FGATCMgr::AIRegisterAirport(string ident) {
 // Channel is zero based
 bool FGATCMgr::CommRegisterAirport(string ident, int chan, atc_type tp) {
        SG_LOG(SG_ATC, SG_BULK, "Comm channel " << chan << " registered airport " << ident);
+       //cout << "Comm channel " << chan << " registered airport " << ident << '\n';
        if(airport_atc_map.find(ident) != airport_atc_map.end()) {
                //cout << "IN MAP - flagging set by comm..." << endl;
                airport_atc_map[ident]->set_by_comm[chan][tp] = true;
+               if(tp == ATIS) {
+                       airport_atc_map[ident]->atis_active = true;
+               } else if(tp == TOWER) {
+                       airport_atc_map[ident]->tower_active = true;
+               } else if(tp == GROUND) {
+                       airport_atc_map[ident]->ground_active = true;
+               } else if(tp == APPROACH) {
+                       //a->approach_active = true;
+               }       // TODO - there *must* be a better way to do this!!!
                return(true);
        } else {
                //cout << "NOT IN MAP - creating new..." << endl;
@@ -253,6 +276,15 @@ bool FGATCMgr::CommRegisterAirport(string ident, int chan, atc_type tp) {
                        a->tower_active = false;
                        a->ground_freq = GetFrequency(ident, GROUND);
                        a->ground_active = false;
+                       if(tp == ATIS) {
+                               a->atis_active = true;
+                       } else if(tp == TOWER) {
+                               a->tower_active = true;
+                       } else if(tp == GROUND) {
+                               a->ground_active = true;
+                       } else if(tp == APPROACH) {
+                               //a->approach_active = true;
+                       }       // TODO - there *must* be a better way to do this!!!
                        // TODO - some airports will have a tower/ground frequency but be inactive overnight.
                        a->set_by_AI = false;
                        a->numAI = 0;
@@ -267,8 +299,9 @@ bool FGATCMgr::CommRegisterAirport(string ident, int chan, atc_type tp) {
 
 // Remove from list only if not needed by the AI system or the other comm channel
 // Note that chan is zero based.
-void FGATCMgr::CommRemoveFromList(const char* id, atc_type tp, int chan) {
+void FGATCMgr::CommRemoveFromList(string id, atc_type tp, int chan) {
        SG_LOG(SG_ATC, SG_BULK, "CommRemoveFromList called for airport " << id << " " << tp << " by channel " << chan);
+       //cout << "CommRemoveFromList called for airport " << id << " " << tp << " by channel " << chan << '\n';
        if(airport_atc_map.find(id) != airport_atc_map.end()) {
                AirportATC* a = airport_atc_map[id];
                //cout << "In CommRemoveFromList, a->ground_freq = " << a->ground_freq << endl;
@@ -309,12 +342,16 @@ void FGATCMgr::CommRemoveFromList(const char* id, atc_type tp, int chan) {
                                a->set_by_comm[0][tp] = false;
                                // Remove only if not also set by the other comm channel
                                if(!a->set_by_comm[1][tp]) {
+                                       a->tower_active = false;
+                                       a->ground_active = false;
                                        RemoveFromList(id, tp);
                                }
                                break;
                        case 1:
                                a->set_by_comm[1][tp] = false;
                                if(!a->set_by_comm[0][tp]) {
+                                       a->tower_active = false;
+                                       a->ground_active = false;
                                        RemoveFromList(id, tp);
                                }
                                break;
@@ -326,24 +363,26 @@ void FGATCMgr::CommRemoveFromList(const char* id, atc_type tp, int chan) {
 
 // Remove from list - should only be called from above or similar
 // This function *will* remove it from the list regardless of who else might want it.
-void FGATCMgr::RemoveFromList(const char* id, atc_type tp) {
-       //cout << "Requested type = " << tp << '\n';
-       //cout << "id = " << id << '\n';
-       atc_list_itr = atc_list.begin();
-       while(atc_list_itr != atc_list.end()) {
-               //cout << "type = " << (*atc_list_itr)->GetType() << '\n';
-               //cout << "Ident = " << (*atc_list_itr)->get_ident() << '\n';
-               if( (!strcmp((*atc_list_itr)->get_ident(), id))
-                       && ((*atc_list_itr)->GetType() == tp) ) {
-                               //Before removing it stop it transmitting!!
-                               //cout << "OBLITERATING FROM LIST!!!\n";
-                               (*atc_list_itr)->SetNoDisplay();
-                               (*atc_list_itr)->Update(0.00833);
-                               delete (*atc_list_itr);
-                               atc_list_itr = atc_list.erase(atc_list_itr);
-                               break;
-                       }  // Note that that can upset where we are in the list but that doesn't really matter
-               ++atc_list_itr;
+void FGATCMgr::RemoveFromList(string id, atc_type tp) {
+       //cout << "FGATCMgr::RemoveFromList called..." << endl;
+       //cout << "Requested type = " << tp << endl;
+       //cout << "id = " << id << endl;
+       atc_list_iterator it = atc_list.begin();
+       while(it != atc_list.end()) {
+               //cout << "type = " << (*it)->GetType() << '\n';
+               //cout << "Ident = " << (*it)->get_ident() << '\n';
+               if( (!strcmp((*it)->get_ident(), id.c_str()))
+                       && ((*it)->GetType() == tp) ) {
+                       //Before removing it stop it transmitting!!
+                       //cout << "OBLITERATING FROM LIST!!!\n";
+                       (*it)->SetNoDisplay();
+                       (*it)->Update(0.00833);
+                       delete (*it);
+                       atc_list.erase(it);
+                       atc_list_itr = atc_list.begin();        // Reset the persistent itr incase we've left it off the end.
+                       break;
+               }
+               ++it;
        }
 }
 
@@ -351,16 +390,18 @@ void FGATCMgr::RemoveFromList(const char* id, atc_type tp) {
 // Find in list - return a currently active ATC pointer given ICAO code and type
 // Return NULL if the given service is not in the list
 // - *** THE CALLING FUNCTION MUST CHECK FOR THIS ***
-FGATC* FGATCMgr::FindInList(const char* id, atc_type tp) {
-       atc_list_itr = atc_list.begin();
-       while(atc_list_itr != atc_list.end()) {
-               if( (!strcmp((*atc_list_itr)->get_ident(), id))
-               && ((*atc_list_itr)->GetType() == tp) ) {
-                       return(*atc_list_itr);
-               }       // Note that that can upset where we are in the list but that shouldn't really matter
-               ++atc_list_itr;
+FGATC* FGATCMgr::FindInList(string id, atc_type tp) {
+       //cout << "Entering FindInList for " << id << ' ' << tp << endl;
+       atc_list_iterator it = atc_list.begin();
+       while(it != atc_list.end()) {
+               if( (!strcmp((*it)->get_ident(), id.c_str()))
+               && ((*it)->GetType() == tp) ) {
+                       return(*it);
+               }
+               ++it;
        }
        // If we get here it's not in the list
+       //cout << "Couldn't find it in the list though :-(" << endl;
        return(NULL);
 }
 
@@ -381,8 +422,10 @@ bool FGATCMgr::GetAirportATCDetails(string icao, AirportATC* a) {
 // - at the moment all these GetATC... functions exposed are just too complicated.
 FGATC* FGATCMgr::GetATCPointer(string icao, atc_type type) {
        if(airport_atc_map.find(icao) == airport_atc_map.end()) {
+               //cout << "Unable to find " << icao << ' ' << type << " in the airport_atc_map" << endl;
                return NULL;
        }
+       //cout << "Found " << icao << ' ' << type << endl;
        AirportATC *a = airport_atc_map[icao];
        //cout << "a->lon = " << a->lon << '\n';
        //cout << "a->elev = " << a->elev << '\n';
@@ -400,6 +443,7 @@ FGATC* FGATCMgr::GetATCPointer(string icao, atc_type type) {
                                atc_list.push_back(t);
                                a->tower_active = true;
                                airport_atc_map[icao] = a;
+                               //cout << "Initing tower in GetATCPointer()\n";
                                t->Init();
                                return(t);
                        } else {
@@ -441,6 +485,7 @@ FGATC* FGATCMgr::GetATCPointer(string icao, atc_type type) {
        }
        
        SG_LOG(SG_ATC, SG_ALERT, "ERROR IN FGATCMgr - reached end of GetATCPointer");
+       //cout << "ERROR IN FGATCMgr - reached end of GetATCPointer" << endl;
        
        return(NULL);
 }
@@ -503,7 +548,7 @@ void FGATCMgr::FreqSearch(int channel) {
                        }
                }
                // At this point we can assume that we need to add the service.
-               comm_ident[chan] = (data.ident).c_str();
+               comm_ident[chan] = data.ident;
                comm_type[chan] = data.type;
                comm_x[chan] = (double)data.x;
                comm_y[chan] = (double)data.y;
@@ -520,6 +565,7 @@ void FGATCMgr::FreqSearch(int channel) {
                        if(app != NULL) {
                                // The station is already in the ATC list
                                //cout << "In list - flagging SetDisplay..." << endl;
+                               comm_atc_ptr[chan] = app;
                                app->SetDisplay();
                        } else {
                                // Generate the station and put in the ATC list
@@ -532,19 +578,24 @@ void FGATCMgr::FreqSearch(int channel) {
                                atc_list.push_back(a);
                        }
                } else if (comm_type[chan] == TOWER) {
+                       //cout << "TOWER TOWER TOWER\n";
                        CommRegisterAirport(comm_ident[chan], chan, TOWER);
                        //cout << "Done (TOWER)" << endl;
                        FGATC* app = FindInList(comm_ident[chan], TOWER);
                        if(app != NULL) {
                                // The station is already in the ATC list
-                                SG_LOG(SG_GENERAL, SG_DEBUG, comm_ident[chan] << " is in list - flagging SetDisplay...");
+                               SG_LOG(SG_GENERAL, SG_DEBUG, comm_ident[chan] << " is in list - flagging SetDisplay...");
+                               //cout << comm_ident[chan] << " is in list - flagging SetDisplay...\n";
+                               comm_atc_ptr[chan] = app;
                                app->SetDisplay();
                        } else {
                                // Generate the station and put in the ATC list
-                                SG_LOG(SG_GENERAL, SG_DEBUG, comm_ident[chan] << " is not in list - generating...");
+                               SG_LOG(SG_GENERAL, SG_DEBUG, comm_ident[chan] << " is not in list - generating...");
+                               //cout << comm_ident[chan] << " is not in list - generating...\n";
                                FGTower* t = new FGTower;
                                t->SetData(&data);
                                comm_atc_ptr[chan] = t;
+                               //cout << "Initing tower in FreqSearch()\n";
                                t->Init();
                                t->SetDisplay();
                                atc_list.push_back(t);
@@ -555,6 +606,7 @@ void FGATCMgr::FreqSearch(int channel) {
                        FGATC* app = FindInList(comm_ident[chan], GROUND);
                        if(app != NULL) {
                                // The station is already in the ATC list
+                               comm_atc_ptr[chan] = app;
                                app->SetDisplay();
                        } else {
                                // Generate the station and put in the ATC list
@@ -638,19 +690,21 @@ void FGATCMgr::AreaSearch() {
        
        // remove planes which are out of range
        // TODO - I'm not entirely sure that this belongs here.
-       atc_list_itr = atc_list.begin();
-       while(atc_list_itr != atc_list.end()) {
-               if((*atc_list_itr)->GetType() == APPROACH ) {
-                       int np = (*atc_list_itr)->RemovePlane();
+       atc_list_iterator it = atc_list.begin();
+       while(it != atc_list.end()) {
+               if((*it)->GetType() == APPROACH ) {
+                       int np = (*it)->RemovePlane();
                        // if approach has no planes left remove it from ATC list
                        if ( np == 0) {
-                               (*atc_list_itr)->SetNoDisplay();
-                               (*atc_list_itr)->Update(0.00833);
-                               delete (*atc_list_itr);
-                               atc_list_itr = atc_list.erase(atc_list_itr);
+                               //cout << "REMOVING AN APPROACH STATION WITH NO PLANES..." << endl;
+                               (*it)->SetNoDisplay();
+                               (*it)->Update(0.00833);
+                               delete (*it);
+                               atc_list.erase(it);
+                               atc_list_itr = atc_list.begin();        // Reset the persistent itr incase we've left it off the end.
                                break;     // the other stations will be checked next time
                        }
                }
-               ++atc_list_itr;
+               ++it;
        }
 }
index 6741a35b7733afb5f0a3f2e36a68b9e7e32a3e25..07513874930e100dd3afb5daad2cfca87537cd1e 100644 (file)
@@ -128,10 +128,9 @@ private:
 
     double comm_range[2], comm_effective_range[2];
     bool comm_valid[2]; 
-    const char* comm_ident[2];
-    const char* last_comm_ident[2];
-
-    const char* approach_ident;
+    string comm_ident[2];
+    //string last_comm_ident[2];
+    //string approach_ident;
     bool last_in_range;
 
     //FGATIS atis;
@@ -195,17 +194,17 @@ private:
 
     // Remove a class from the atc_list and delete it from memory
        // *if* no other comm channel or AI plane is using it.
-    void CommRemoveFromList(const char* id, atc_type tp, int chan);
+    void CommRemoveFromList(string id, atc_type tp, int chan);
 
     // Remove a class from the atc_list and delete it from memory
        // Should be called from the above - not directly!!
-    void RemoveFromList(const char* id, atc_type tp);
+    void RemoveFromList(string id, atc_type tp);
 
     // Return a pointer to a class in the list given ICAO code and type
        // (external interface to this is through GetATCPointer) 
        // Return NULL if the given service is not in the list
        // - *** THE CALLING FUNCTION MUST CHECK FOR THIS ***
-    FGATC* FindInList(const char* id, atc_type tp);
+    FGATC* FindInList(string id, atc_type tp);
 
     // Search the specified channel for stations on the same frequency and in range.
     void FreqSearch(int channel);
index 3bd30e10a9278f77a839e456285ba6d27d7d9d0e..f5decf766499407b1041ee06e470162307b3b401 100644 (file)
@@ -114,38 +114,71 @@ string ConvertRwyNumToSpokenString(string s) {
 // Return the phonetic letter of a letter represented as an integer 1->26
 string GetPhoneticIdent(int i) {
        // TODO - Check i is between 1 and 26 and wrap if necessary
-       switch(i) {
-       case 1 : return("alpha");
-       case 2 : return("bravo");
-       case 3 : return("charlie");
-       case 4 : return("delta");
-       case 5 : return("echo");
-       case 6 : return("foxtrot");
-       case 7 : return("golf");
-       case 8 : return("hotel");
-       case 9 : return("india");
-       case 10 : return("juliet");
-       case 11 : return("kilo");
-       case 12 : return("lima");
-       case 13 : return("mike");
-       case 14 : return("november");
-       case 15 : return("oscar");
-       case 16 : return("papa");
-       case 17 : return("quebec");
-       case 18 : return("romeo");
-       case 19 : return("sierra");
-       case 20 : return("tango");
-       case 21 : return("uniform");
-       case 22 : return("victor");
-       case 23 : return("whiskey");
-       case 24 : return("x-ray");
-       case 25 : return("yankee");
-       case 26 : return("zulu");
+       return(GetPhoneticIdent(char('a' + (i-1))));
+}
+
+// Return the phonetic letter of a character in the range a-z or A-Z.
+// Currently always returns prefixed by lowercase.
+string GetPhoneticIdent(char c) {
+       c = tolower(c);
+       // TODO - Check c is between a and z and wrap if necessary
+       switch(c) {
+       case 'a' : return("alpha");
+       case 'b' : return("bravo");
+       case 'c' : return("charlie");
+       case 'd' : return("delta");
+       case 'e' : return("echo");
+       case 'f' : return("foxtrot");
+       case 'g' : return("golf");
+       case 'h' : return("hotel");
+       case 'i' : return("india");
+       case 'j' : return("juliet");
+       case 'k' : return("kilo");
+       case 'l' : return("lima");
+       case 'm' : return("mike");
+       case 'n' : return("november");
+       case 'o' : return("oscar");
+       case 'p' : return("papa");
+       case 'q' : return("quebec");
+       case 'r' : return("romeo");
+       case 's' : return("sierra");
+       case 't' : return("tango");
+       case 'u' : return("uniform");
+       case 'v' : return("victor");
+       case 'w' : return("whiskey");
+       case 'x' : return("x-ray");
+       case 'y' : return("yankee");
+       case 'z' : return("zulu");
        }
        // We shouldn't get here
        return("Error");
 }
 
+// Get the compass direction associated with a heading in degrees
+// Currently returns 8 direction resolution (N, NE, E etc...)
+// Might be modified in future to return 4, 8 or 16 resolution but defaulting to 8. 
+string GetCompassDirection(double h) {
+       while(h < 0.0) h += 360.0;
+       while(h > 360.0) h -= 360.0;
+       if(h < 22.5 || h > 337.5) {
+               return("North");
+       } else if(h < 67.5) {
+               return("North-East");
+       } else if(h < 112.5) {
+               return("East");
+       } else if(h < 157.5) {
+               return("South-East");
+       } else if(h < 202.5) {
+               return("South");
+       } else if(h < 247.5) {
+               return("South-West");
+       } else if(h < 292.5) {
+               return("West");
+       } else {
+               return("North-West");
+       }
+}
+
 //================================================================================================================
 
 // Given two positions (lat & lon in degrees), get the HORIZONTAL separation (in meters)
@@ -305,16 +338,31 @@ double dclGetAirportElev( const string& id ) {
     FGAirport a;
     // double lon, lat;
 
-    SG_LOG( SG_GENERAL, SG_INFO,
+    SG_LOG( SG_ATC, SG_INFO,
             "Finding elevation for airport: " << id );
 
     if ( dclFindAirportID( id, &a ) ) {
-        return a.elevation;
+        return a.elevation * SG_FEET_TO_METER;
     } else {
         return -9999.0;
     }
 }
 
+// get airport position
+Point3D dclGetAirportPos( const string& id ) {
+    FGAirport a;
+    // double lon, lat;
+
+    SG_LOG( SG_ATC, SG_INFO,
+            "Finding position for airport: " << id );
+
+    if ( dclFindAirportID( id, &a ) ) {
+        return Point3D(a.longitude, a.latitude, a.elevation);
+    } else {
+        return Point3D(0.0, 0.0, -9999.0);
+    }
+}      
+
 // Runway stuff
 // Given a Point3D (lon/lat/elev) and an FGRunway struct, determine if the point lies on the runway
 bool OnRunway(Point3D pt, const FGRunway& rwy) {
index 7dfa1569d90b098727fb21b405612d6beff6f4dd..448099a672680686c55920845eb10aba06209594 100644 (file)
@@ -56,6 +56,14 @@ string ConvertRwyNumToSpokenString(string s);
 // Return the phonetic letter of a letter represented as an integer 1->26
 string GetPhoneticIdent(int i);
 
+// Return the phonetic letter of a character in the range a-z or A-Z.
+// Currently always returns prefixed by lowercase.
+string GetPhoneticIdent(char c);
+
+// Get the compass direction associated with a heading in degrees
+// Currently returns 8 direction resolution (N, NE, E etc...)
+// Might be modified in future to return 4, 8 or 16 resolution but defaulting to 8. 
+string GetCompassDirection(double h);
 
 /*******************************
 *
@@ -97,9 +105,12 @@ double GetAngleDiff_deg( const double &a1, const double &a2);
 // find basic airport location info from airport database
 bool dclFindAirportID( const string& id, FGAirport *a );
 
-// get airport elevation
+// get airport elevation IN METERS
 double dclGetAirportElev( const string& id );
 
+// get airport position (elev portion in FEET)
+Point3D dclGetAirportPos( const string& id );
+
 /****************
 *
 *   Runways
index d442cf8be572185ed9d0e9b33fc4722ea6e510d4..3cca375130e441e1cc91f9826252b200f6413ad5 100644 (file)
@@ -17,6 +17,7 @@ libATC_a_SOURCES = \
        AIEntity.hxx AIEntity.cxx \
        AIPlane.hxx AIPlane.cxx \
        AILocalTraffic.hxx AILocalTraffic.cxx \
+       AIGAVFRTraffic.hxx AIGAVFRTraffic.cxx \
        transmission.hxx transmission.cxx transmissionlist.hxx transmissionlist.cxx
 
 INCLUDES = -I$(top_srcdir) -I$(top_srcdir)/src
index 09b639c965da2e37c3c36b7f4b1ae8222dbef1c2..6d7a5a869ceeb90c81611964ae405eec65b336e3 100644 (file)
@@ -42,6 +42,8 @@
 FGApproach::FGApproach(){
   comm1_node = fgGetNode("/radios/comm[0]/frequencies/selected-mhz", true);
   comm2_node = fgGetNode("/radios/comm[1]/frequencies/selected-mhz", true);
+  
+  _type = APPROACH;
 
   num_planes = 0;
   lon_node   = fgGetNode("/position/longitude-deg", true);
index 405de1a05426fa0e8947bc69f1e65c27c657b80c..fb2d319244dd79599603042db4d52d3dca22ffdb 100644 (file)
@@ -169,7 +169,6 @@ public:
   inline double get_bucket() const { return bucket; }
   inline int get_pnum() const { return num_planes; }
   inline string get_trans_ident() { return trans_ident; }
-  inline atc_type GetType() { return APPROACH; }
   
 private:
 
index 4c56b23a44234ca177e7bb5189c10fe89675907f..35bb25a2eec98c01872d4b82016f726416033803 100644 (file)
@@ -64,6 +64,7 @@ FGATIS::FGATIS() :
 {
        vPtr = globals->get_ATC_mgr()->GetVoicePointer(ATIS);
        voiceOK = (vPtr == NULL ? false : true);
+       _type = ATIS;
 }
 
 FGATIS::~FGATIS() {
index b46d1b47d88c72a31632ded520f35e64167722de..0d22b3efa229b59762e8a04e6daf901bac42f662 100644 (file)
@@ -86,7 +86,6 @@ class FGATIS : public FGATC {
        //Indicate that this instance should not be outputting to the ATC display
        inline void SetNoDisplay(void) {display = false;}
        
-       inline atc_type GetType() { return ATIS; }
        //inline void set_type(const atc_type tp) {type = tp;}
        inline string get_trans_ident() { return trans_ident; }
        inline void set_refname(string r) { refname = r; } 
index 5f0072f5e03ae8f741f796546f7a5344b5c20c93..4685600354def2c440dbf55c2ee11ce2dd2f13b4 100644 (file)
@@ -53,6 +53,7 @@ a_path::a_path() {
 
 FGGround::FGGround() {
        ATCmgr = globals->get_ATC_mgr();
+       _type = GROUND;
        display = false;
        networkLoadOK = false;
        ground_traffic.erase(ground_traffic.begin(), ground_traffic.end());
@@ -454,6 +455,11 @@ Gate* FGGround::GetGateNode() {
 }
 
 
+node* FGGround::GetHoldShortNode(string rwyID) {
+       return(NULL);   // TODO - either implement me or remove me!!!
+}
+
+
 // WARNING - This is hardwired to my prototype logical network format
 // and will almost certainly change when Bernie's stuff comes on-line.
 // Returns NULL if it can't find a valid node.
@@ -495,7 +501,7 @@ ground_network_path_type FGGround::GetPath(node* A, node* B) {
 ground_network_path_type FGGround::GetPath(node* A, string rwyID) {
        node* b = GetThresholdNode(rwyID);
        if(b == NULL) {
-               SG_LOG(SG_ATC, SG_ALERT, "ERROR - unable to find path to runway theshold in ground.cxx\n");
+               SG_LOG(SG_ATC, SG_ALERT, "ERROR - unable to find path to runway theshold in ground.cxx for airport " << ident << '\n');
                ground_network_path_type emptyPath;
                emptyPath.erase(emptyPath.begin(), emptyPath.end());
                return(emptyPath);
index 797c95e6f03be17bb1571216270ad1f5effef293..1a8a94cd2b184fd1eb1a6df6e47311efd51d0e51 100644 (file)
@@ -232,7 +232,6 @@ public:
     void Update(double dt);
        
        inline string get_trans_ident() { return trans_ident; }
-       inline atc_type GetType() { return GROUND; }
     inline void SetDisplay() {display = true;}
     inline void SetNoDisplay() {display = false;}
 
@@ -258,6 +257,9 @@ public:
        // Return a pointer to an unused gate
        Gate* GetGateNode();
        
+       // Return a pointer to a hold short node
+       node* GetHoldShortNode(string rwyID);
+       
        // Runway stuff - this might change in the future.
        // Get a list of exits from a given runway
        // It is up to the calling function to check for non-zero size of returned array before use
index a817d7ff0429efff8bb3de0a174c30c16095daa5..ea90860eba95a5762ee7ea1b2cc519cdd1149ccc 100644 (file)
@@ -46,6 +46,7 @@ SG_USING_STD(cout);
 // TowerPlaneRec
 
 TowerPlaneRec::TowerPlaneRec() :
+       planePtr(NULL),
        clearedToLand(false),
        clearedToLineUp(false),
        clearedToTakeOff(false),
@@ -65,12 +66,13 @@ TowerPlaneRec::TowerPlaneRec() :
        opType(TTT_UNKNOWN),
        leg(LEG_UNKNOWN),
        landingType(AIP_LT_UNKNOWN),
-       isUser(false) 
+       isUser(false)
 {
        plane.callsign = "UNKNOWN";
 }
 
 TowerPlaneRec::TowerPlaneRec(PlaneRec p) :
+       planePtr(NULL),
        clearedToLand(false),
        clearedToLineUp(false),
        clearedToTakeOff(false),
@@ -96,6 +98,7 @@ TowerPlaneRec::TowerPlaneRec(PlaneRec p) :
 }
 
 TowerPlaneRec::TowerPlaneRec(Point3D pt) :
+       planePtr(NULL),
        clearedToLand(false),
        clearedToLineUp(false),
        clearedToTakeOff(false),
@@ -122,6 +125,7 @@ TowerPlaneRec::TowerPlaneRec(Point3D pt) :
 }
 
 TowerPlaneRec::TowerPlaneRec(PlaneRec p, Point3D pt) :
+       planePtr(NULL),
        clearedToLand(false),
        clearedToLineUp(false),
        clearedToTakeOff(false),
@@ -153,23 +157,39 @@ TowerPlaneRec::TowerPlaneRec(PlaneRec p, Point3D pt) :
 /*******************************************
                TODO List
                           
-Currently user is assumed to have taken off again when leaving the runway - check speed/elev for taxiing-in.
-
-Tell AI plane to contact ground when taxiing in.
+Currently user is assumed to have taken off again when leaving the runway - check speed/elev for taxiing-in. (MAJOR)
 
-Use track instead of heading to determine what leg of the circuit the user is flying.
+Use track instead of heading to determine what leg of the circuit the user is flying. (MINOR)
 
-Use altitude as well as position to try to determine if the user has left the circuit.
+Use altitude as well as position to try to determine if the user has left the circuit. (MEDIUM - other issues as well).
 
 Currently HoldShortReported code assumes there will be only one plane holding for the runway at once and 
-will break when planes start queueing.
+will break when planes start queueing. (CRITICAL)
+
+Report-Runway-Vacated is left as only user ATC option following a go-around. (MAJOR)
+
+Report Go-Around should be added to user options following reporting final or downwind. (MEDIUM).
+
+Report-Downwind is not added as ATC option when user takes off to fly a circuit. (MAJOR)
+
+eta of USER can be calculated very wrongly in circuit if flying straight out and turn4 etc are with +ve ortho y. 
+This can then screw up circuit ordering for other planes (MEDIUM)
+
+USER leaving circuit needs to be more robustly considered when intentions unknown
+Currently only considered during climbout and breaks when user turns (MEDIUM).
 
-Implement ReportRunwayVacated
+GetPos() of the AI planes is called erratically - either too much or not enough. (MINOR)
+
+GO-AROUND is instructed very late at < 12s to landing - possibly make more dependent on chance of rwy clearing before landing (FEATURE)
+
+Need to make clear when TowerPlaneRecs do or don't get deleted in RemoveFromCircuitList etc. (MINOR until I misuse it - then CRITICAL!)
 *******************************************/
 
 FGTower::FGTower() {
        ATCmgr = globals->get_ATC_mgr();
        
+       _type = TOWER;
+       
        // Init the property nodes - TODO - need to make sure we're getting surface winds.
        wind_from_hdg = fgGetNode("/environment/wind-from-heading-deg", true);
        wind_speed_knots = fgGetNode("/environment/wind-speed-kt", true);
@@ -178,11 +198,14 @@ FGTower::FGTower() {
        update_count_max = 15;
        
        holdListItr = holdList.begin();
+       appList.clear();
        appListItr = appList.begin();
        depListItr = depList.begin();
        rwyListItr = rwyList.begin();
        circuitListItr = circuitList.begin();
        trafficListItr = trafficList.begin();
+       vacatedList.clear();
+       vacatedListItr = vacatedList.begin();
        
        freqClear = true;
        
@@ -192,6 +215,8 @@ FGTower::FGTower() {
        nominal_downwind_leg_pos = 1000.0;
        nominal_base_leg_pos = -1000.0;
        // TODO - set nominal crosswind leg pos based on minimum distance from takeoff end of rwy.
+       
+       _departureControlled = false;
 }
 
 FGTower::~FGTower() {
@@ -201,6 +226,7 @@ FGTower::~FGTower() {
 }
 
 void FGTower::Init() {
+       //cout << "Initialising tower " << ident << '\n';
     display = false;
        
        // Pointers to user's position
@@ -257,14 +283,18 @@ void FGTower::Init() {
                }
        }
        
+       // TODO - attempt to get a departure control pointer to see if we need to hand off departing traffic to departure.
+       
        // Get the airport elevation
-       aptElev = dclGetAirportElev(ident.c_str()) * SG_FEET_TO_METER;
+       aptElev = dclGetAirportElev(ident.c_str());
        
+       // TODO - this function only assumes one active rwy.
        DoRwyDetails();
        
-       // FIXME - this currently assumes use of the active rwy by the user.
-       rwyOccupied = OnAnyRunway(Point3D(user_lon_node->getDoubleValue(), user_lat_node->getDoubleValue(), 0.0));
+       // TODO - this currently assumes only one active runway.
+       rwyOccupied = OnActiveRunway(Point3D(user_lon_node->getDoubleValue(), user_lat_node->getDoubleValue(), 0.0));
        if(rwyOccupied) {
+               //cout << "User found on active runway\n";
                // Assume the user is started at the threshold ready to take-off
                TowerPlaneRec* t = new TowerPlaneRec;
                t->plane.callsign = fgGetString("/sim/user/callsign");
@@ -278,9 +308,12 @@ void FGTower::Init() {
                rwyList.push_back(t);
                departed = false;
        } else {
+               //cout << "User not on active runway\n";
                // For now assume that this means the user is not at the airport and is in the air.
                // TODO FIXME - this will break when user starts on apron, at hold short, etc.
-               current_atcdialog->add_entry(ident, "@AP Tower @CS @MI miles @CD of the airport for full stop with the ATIS", "Contact tower for VFR arrival (full stop)", TOWER, (int)USER_REQUEST_VFR_ARRIVAL_FULL_STOP);
+               if(!OnAnyRunway(Point3D(user_lon_node->getDoubleValue(), user_lat_node->getDoubleValue(), 0.0))) {
+                       current_atcdialog->add_entry(ident, "@AP Tower @CS @MI miles @CD of the airport for full stop with the ATIS", "Contact tower for VFR arrival (full stop)", TOWER, (int)USER_REQUEST_VFR_ARRIVAL_FULL_STOP);
+               }
        }
 }
 
@@ -357,6 +390,10 @@ void FGTower::Update(double dt) {
                CheckApproachList(dt);
        }
        
+       if(update_count == 8) {
+               CheckDepartureList(dt);
+       }
+       
        // TODO - do one plane from the departure list and set departed = false when out of consideration
        
        //doCommunication();
@@ -399,7 +436,7 @@ void FGTower::Update(double dt) {
 
 void FGTower::ReceiveUserCallback(int code) {
        if(code == (int)USER_REQUEST_VFR_DEPARTURE) {
-               cout << "User requested departure\n";
+               //cout << "User requested departure\n";
        } else if(code == (int)USER_REQUEST_VFR_ARRIVAL) {
                VFRArrivalContact("USER");
        } else if(code == (int)USER_REQUEST_VFR_ARRIVAL_FULL_STOP) {
@@ -417,11 +454,12 @@ void FGTower::ReceiveUserCallback(int code) {
 }
 
 void FGTower::Respond() {
-       cout << "Entering Respond, responseID = " << responseID << endl;
+       //cout << "\nEntering Respond, responseID = " << responseID << endl;
        TowerPlaneRec* t = FindPlane(responseID);
        if(t) {
                // This will grow!!!
                if(t->vfrArrivalReported && !t->vfrArrivalAcknowledged) {
+                       //cout << "Tower " << ident << " is responding to VFR arrival reported...\n";
                        // Testing - hardwire straight in for now
                        string trns = t->plane.callsign;
                        trns += " ";
@@ -433,24 +471,37 @@ void FGTower::Respond() {
                        Point3D op = ortho.ConvertToLocal(t->pos);
                        if(op.y() < -1000) {
                                trns += " Report three mile straight-in runway ";
-                               current_atcdialog->add_entry(ident, "@AP Tower @CS @MI mile final Runway @RW", "Report Final", TOWER, (int)USER_REPORT_3_MILE_FINAL);
+                               t->opType = STRAIGHT_IN;
+                               if(t->isUser) {
+                                       current_atcdialog->add_entry(ident, "@AP Tower @CS @MI mile final Runway @RW", "Report Final", TOWER, (int)USER_REPORT_3_MILE_FINAL);
+                               } else {
+                                       t->planePtr->RegisterTransmission(14);
+                               }
                        } else {
                                // For now we'll just request reporting downwind.
                                // TODO - In real life something like 'report 2 miles southwest right downwind rwy 19R' might be used
                                // but I'm not sure how to handle all permutations of which direction to tell to report from yet.
                                trns += " Report ";
-                               trns += (rwy.patternDirection ? "right " : "left ");
+                               //cout << "Responding, rwy.patterDirection is " << rwy.patternDirection << '\n';
+                               trns += ((rwy.patternDirection == 1) ? "right " : "left ");
                                trns += "downwind runway ";
-                               current_atcdialog->add_entry(ident, "@AP Tower @CS Downwind @RW", "Report Downwind", TOWER, (int)USER_REPORT_DOWNWIND);
+                               t->opType = CIRCUIT;
+                               // leave it in the app list until it gets into pattern though.
+                               if(t->isUser) {
+                                       current_atcdialog->add_entry(ident, "@AP Tower @CS Downwind @RW", "Report Downwind", TOWER, (int)USER_REPORT_DOWNWIND);
+                               } else {
+                                       t->planePtr->RegisterTransmission(15);
+                               }
                        }
                        trns += ConvertRwyNumToSpokenString(activeRwy);
                        if(display) {
                                globals->get_ATC_display()->RegisterSingleMessage(trns, 0);
                        } else {
-                               cout << "Not displaying, trns was " << trns << '\n';
+                               //cout << "Not displaying, trns was " << trns << '\n';
                        }
                        t->vfrArrivalAcknowledged = true;
                } else if(t->downwindReported) {
+                       //cout << "Tower " << ident << " is responding to downwind reported...\n";
                        t->downwindReported = false;
                        int i = 1;
                        for(tower_plane_rec_list_iterator twrItr = circuitList.begin(); twrItr != circuitList.end(); twrItr++) {
@@ -461,9 +512,10 @@ void FGTower::Respond() {
                        trns += " Number ";
                        trns += ConvertNumToSpokenDigits(i);
                        trns += " ";
-                       if(i == 1) {
-                               trns += "Cleared to land";
+                       if((i == 1) && (!rwyList.size()) && (t->nextOnRwy)) {
+                               trns += "Cleared to land";      // TODO - clear for the option if appropriate
                                t->clearedToLand = true;
+                               if(!t->isUser) t->planePtr->RegisterTransmission(7);
                        }
                        if(display) {
                                globals->get_ATC_display()->RegisterSingleMessage(trns);
@@ -471,8 +523,10 @@ void FGTower::Respond() {
                        if(t->isUser) {
                                if(t->opType == TTT_UNKNOWN) t->opType = CIRCUIT;
                                current_atcdialog->add_entry(ident, "@CS Clear of the runway", "Report runway vacated", TOWER, USER_REPORT_RWY_VACATED);
+                               // TODO - add report going around as an option
                        }
                } else if(t->holdShortReported) {
+                       //cout << "Tower " << ident << " is reponding to holdShortReported...\n";
                        if(t->nextOnRwy) {
                                if(rwyOccupied) {       // TODO - ought to add a sanity check that it isn't this plane only on the runway (even though it shouldn't be!!)
                                        // Do nothing for now - consider acknowloging hold short eventually
@@ -503,11 +557,12 @@ void FGTower::Respond() {
                        }
                        t->holdShortReported = false;
                } else if(t->finalReported && !(t->finalAcknowledged)) {
+                       //cout << "Tower " << ident << " is responding to finalReported...\n";
                        bool disp = true;
                        string trns = t->plane.callsign;
-                       cout << (t->nextOnRwy ? "Next on rwy " : "Not next!! ");
-                       cout << (rwyOccupied ? "RWY OCCUPIED!!\n" : "Rwy not occupied\n");
-                       if(t->nextOnRwy && !rwyOccupied) {
+                       //cout << (t->nextOnRwy ? "Next on rwy " : "Not next!! ");
+                       //cout << (rwyOccupied ? "RWY OCCUPIED!!\n" : "Rwy not occupied\n");
+                       if(t->nextOnRwy && !rwyOccupied && !(t->instructedToGoAround)) {
                                if(t->landingType == FULL_STOP) {
                                        trns += " cleared to land ";
                                } else {
@@ -515,7 +570,13 @@ void FGTower::Respond() {
                                }
                                // TODO - add winds
                                t->clearedToLand = true;
-                               if(t->isUser) current_atcdialog->add_entry(ident, "@CS Clear of the runway", "Report runway vacated", TOWER, USER_REPORT_RWY_VACATED);
+                               // Maybe remove report downwind from menu here as well incase user didn't bother to?
+                               if(t->isUser) {
+                                       current_atcdialog->add_entry(ident, "@CS Clear of the runway", "Report runway vacated", TOWER, USER_REPORT_RWY_VACATED);
+                                       // TODO - add report going around as an option.
+                               } else {
+                                       t->planePtr->RegisterTransmission(7);
+                               }
                        } else if(t->eta < 20) {
                                // Do nothing - we'll be telling it to go around in less than 10 seconds if the
                                // runway doesn't clear so no point in calling "continue approach".
@@ -529,27 +590,38 @@ void FGTower::Respond() {
                        }
                        t->finalAcknowledged = true;
                } else if(t->rwyVacatedReported && !(t->rwyVacatedAcknowledged)) {
-                       string trns = t->plane.callsign;
-                       if(separateGround) {
-                               trns += " Contact ground on ";
-                               double f = globals->get_ATC_mgr()->GetFrequency(ident, GROUND) / 100.0; 
-                               char buf[10];
-                               sprintf(buf, "%.2f", f);
-                               trns += buf;
-                               trns += " Good Day";
-                       } else {
-                               // Cop-out!!
-                               trns += " cleared for taxi to the GA parking";
-                       }
-                       if(display) {
-                               globals->get_ATC_display()->RegisterSingleMessage(trns);
-                       }
+                       ProcessRunwayVacatedReport(t);
                        t->rwyVacatedAcknowledged = true;
-                       // Maybe we should check that the plane really *has* vacated the runway!
                }
        }
-       freqClear = true;       // FIXME - set this to come true after enough time to render the message
-       //cout << "Done Respond" << endl;
+       //freqClear = true;     // FIXME - set this to come true after enough time to render the message
+       _releaseCounter = 0.0;
+       _releaseTime = 5.5;
+       _runReleaseCounter = true;
+       //cout << "Done Respond\n" << endl;
+}
+
+void FGTower::ProcessRunwayVacatedReport(TowerPlaneRec* t) {
+       //cout << "Processing rwy vacated...\n";
+       string trns = t->plane.callsign;
+       if(separateGround) {
+               trns += " Contact ground on ";
+               double f = globals->get_ATC_mgr()->GetFrequency(ident, GROUND) / 100.0; 
+               char buf[10];
+               sprintf(buf, "%.2f", f);
+               trns += buf;
+               trns += " Good Day";
+               if(!t->isUser) t->planePtr->RegisterTransmission(5);
+       } else {
+               // Cop-out!!
+               trns += " cleared for taxi to the GA parking";
+               if(!t->isUser) t->planePtr->RegisterTransmission(6);    // TODO - this is a mega-hack!!
+       }
+       //cout << "trns = " << trns << '\n';
+       if(display) {
+               globals->get_ATC_display()->RegisterSingleMessage(trns);
+       }
+       // Maybe we should check that the plane really *has* vacated the runway!
 }
 
 // Currently this assumes we *are* next on the runway and doesn't check for planes about to land - 
@@ -659,6 +731,10 @@ void FGTower::ClearHoldingPlane(TowerPlaneRec* t) {
        //cout << "Done ClearHoldingPlane " << endl;
 }
 
+
+// ***************************************************************************************
+// ********** Functions to periodically check what the various traffic is doing **********
+
 // Do one plane from the hold list
 void FGTower::CheckHoldList(double dt) {
        //cout << "Entering CheckHoldList..." << endl;
@@ -710,10 +786,12 @@ void FGTower::CheckCircuitList(double dt) {
                        circuitListItr = circuitList.begin();
                }
                TowerPlaneRec* t = *circuitListItr;
+               //cout << ident <<  ' ' << circuitList.size() << ' ' << t->plane.callsign << " " << t->leg << " eta " << t->eta << '\n';
                if(t->isUser) {
                        t->pos.setlon(user_lon_node->getDoubleValue());
                        t->pos.setlat(user_lat_node->getDoubleValue());
                        t->pos.setelev(user_elev_node->getDoubleValue());
+                       //cout << ident <<  ' ' << circuitList.size() << ' ' << t->plane.callsign << " " << t->leg << " eta " << t->eta << '\n';
                } else {
                        t->pos = t->planePtr->GetPos();         // We should probably only set the pos's on one walk through the traffic list in the update function, to save a few CPU should we end up duplicating this.
                        t->landingType = t->planePtr->GetLandingOption();
@@ -738,11 +816,13 @@ void FGTower::CheckCircuitList(double dt) {
                                        //cout << "Climbout\n";
                                        // If it's the user we may be unsure of his/her intentions.
                                        // (Hopefully the AI planes won't try confusing the sim!!!)
+                                       //cout << "tortho.y = " << tortho.y() << '\n';
                                        if(t->opType == TTT_UNKNOWN) {
                                                if(tortho.y() > 5000) {
                                                        // 5 km out from threshold - assume it's a departure
                                                        t->opType = OUTBOUND;   // TODO - could check if the user has climbed significantly above circuit altitude as well.
                                                        // Since we are unknown operation we should be in depList already.
+                                                       //cout << ident << " Removing user from circuitList (TTT_UNKNOWN)\n";
                                                        circuitList.erase(circuitListItr);
                                                        RemoveFromTrafficList(t->plane.callsign);
                                                        circuitListItr = circuitList.begin();
@@ -752,6 +832,7 @@ void FGTower::CheckCircuitList(double dt) {
                                                        // 10 km out - assume the user has abandoned the circuit!!
                                                        t->opType = OUTBOUND;
                                                        depList.push_back(t);
+                                                       //cout << ident << " removing user from circuitList (CIRCUIT)\n";
                                                        circuitList.erase(circuitListItr);
                                                        circuitListItr = circuitList.begin();
                                                }
@@ -807,70 +888,79 @@ void FGTower::CheckCircuitList(double dt) {
                // TODO - at the moment we're constraining plane 2 based on plane 1 - this won't (or might not) work for 3 planes in the circuit!!
                if(circuitListItr == circuitList.begin()) {
                        switch(t->leg) {
-                       case FINAL:
+                               case FINAL:
                                // Base leg must be at least as far out as the plane is - actually possibly not necessary for separation, but we'll use that for now.
                                base_leg_pos = tortho.y();
                                //cout << "base_leg_pos = " << base_leg_pos << '\n';
                                break;
-                       case TURN4:
+                               case TURN4:
                                // Fall through to base
-                       case BASE:
+                               case BASE:
                                base_leg_pos = tortho.y();
                                //cout << "base_leg_pos = " << base_leg_pos << '\n';
                                break;
-                       case TURN3:
+                               case TURN3:
                                // Fall through to downwind
-                       case DOWNWIND:
+                               case DOWNWIND:
                                // Only have the downwind leg pos as turn-to-base constraint if more negative than we already have.
                                base_leg_pos = (tortho.y() < base_leg_pos ? tortho.y() : base_leg_pos);
                                //cout << "base_leg_pos = " << base_leg_pos;
                                downwind_leg_pos = tortho.x();          // Assume that a following plane can simply be constrained by the immediately in front downwind plane
                                //cout << " downwind_leg_pos = " << downwind_leg_pos << '\n';
                                break;
-                       case TURN2:
+                               case TURN2:
                                // Fall through to crosswind
-                       case CROSSWIND:
+                               case CROSSWIND:
                                crosswind_leg_pos = tortho.y();
                                //cout << "crosswind_leg_pos = " << crosswind_leg_pos << '\n';
                                t->instructedToGoAround = false;
                                break;
-                       case TURN1:
+                               case TURN1:
                                // Fall through to climbout
-                       case CLIMBOUT:
+                               case CLIMBOUT:
                                // Only use current by constraint as largest
                                crosswind_leg_pos = (tortho.y() > crosswind_leg_pos ? tortho.y() : crosswind_leg_pos);
                                //cout << "crosswind_leg_pos = " << crosswind_leg_pos << '\n';
                                break;
-                       case TAKEOFF_ROLL:
+                               case TAKEOFF_ROLL:
                                break;
-                       case LEG_UNKNOWN:
+                               case LEG_UNKNOWN:
                                break;
-                       case LANDING_ROLL:
+                               case LANDING_ROLL:
                                break;
-                       default:
+                               default:
                                break;
                        }
                }
                
-               if(t->leg == FINAL) {
-                       if(t->landingType == FULL_STOP) t->opType = INBOUND;
-                       if(t->eta < 12 && rwyList.size() && !(t->instructedToGoAround)) {
-                               // TODO - need to make this more sophisticated 
-                               // eg. is the plane accelerating down the runway taking off [OK],
-                               // or stationary near the start [V. BAD!!].
-                               // For now this should stop the AI plane landing on top of the user.
-                               string trns = t->plane.callsign;
-                               trns += " GO AROUND TRAFFIC ON RUNWAY I REPEAT GO AROUND";
-                               if(display) {
-                                       globals->get_ATC_display()->RegisterSingleMessage(trns, 0);
-                               }
-                               t->instructedToGoAround = true;
-                               if(t->planePtr) {
-                                       cout << "Registering Go-around transmission with AI plane\n";
-                                       t->planePtr->RegisterTransmission(13);
+               if(t->leg == FINAL && !(t->instructedToGoAround)) {
+                       //cout << "YES FINAL, t->eta = " << t->eta << ", rwyList.size() = " << rwyList.size() << '\n';
+                       if(t->landingType == FULL_STOP) {
+                               t->opType = INBOUND;
+                               //cout << "\n******** SWITCHING TO INBOUND AT POINT AAA *********\n\n";
+                               if(t->eta < 12 && rwyList.size()) {
+                                       // TODO - need to make this more sophisticated 
+                                       // eg. is the plane accelerating down the runway taking off [OK],
+                                       // or stationary near the start [V. BAD!!].
+                                       // For now this should stop the AI plane landing on top of the user.
+                                       string trns = t->plane.callsign;
+                                       trns += " GO AROUND TRAFFIC ON RUNWAY I REPEAT GO AROUND";
+                                       if(display) {
+                                               globals->get_ATC_display()->RegisterSingleMessage(trns, 0);
+                                       }
+                                       t->instructedToGoAround = true;
+                                       t->clearedToLand = false;
+                                       // Assume it complies!!!
+                                       t->opType = CIRCUIT;
+                                       t->leg = CLIMBOUT;
+                                       if(t->planePtr) {
+                                               //cout << "Registering Go-around transmission with AI plane\n";
+                                               t->planePtr->RegisterTransmission(13);
+                                       }
                                }
                        }
                } else if(t->leg == LANDING_ROLL) {
+                       //cout << t->plane.callsign << " has landed - adding to rwyList\n";
                        rwyList.push_front(t);
                        // TODO - if(!clearedToLand) shout something!!
                        t->clearedToLand = false;
@@ -878,6 +968,7 @@ void FGTower::CheckCircuitList(double dt) {
                        if(t->isUser) {
                                t->opType = TTT_UNKNOWN;
                        }       // TODO - allow the user to specify opType via ATC menu
+                       //cout << ident << " Removing " << t->plane.callsign << " from circuitList..." << endl;
                        circuitListItr = circuitList.erase(circuitListItr);
                        if(circuitListItr == circuitList.end() ) {
                                circuitListItr = circuitList.begin();
@@ -909,16 +1000,24 @@ void FGTower::CheckRunwayList(double dt) {
                        }
                        bool on_rwy = OnActiveRunway(t->pos);
                        if(!on_rwy) {
+                               // TODO - for all of these we need to check what the user is *actually* doing!
                                if((t->opType == INBOUND) || (t->opType == STRAIGHT_IN)) {
-                                       rwyList.pop_front();
-                                       delete t;
-                                       // TODO - tell it to taxi / contact ground / don't delete it etc!
+                                       //cout << "Tower " << ident << " is removing plane " << t->plane.callsign << " from rwy list (vacated)\n";
+                                       //cout << "Size of rwylist was " << rwyList.size() << '\n';
+                                       //cout << "Size of vacatedList was " << vacatedList.size() << '\n';
+                                       RemoveFromRwyList(t->plane.callsign);
+                                       vacatedList.push_back(t);
+                                       //cout << "Size of rwylist is " << rwyList.size() << '\n';
+                                       //cout << "Size of vacatedList is " << vacatedList.size() << '\n';
+                                       // At the moment we wait until Runway Vacated is reported by the plane before telling to contact ground etc.
+                                       // It's possible we could be a bit more proactive about this.
                                } else if(t->opType == OUTBOUND) {
                                        depList.push_back(t);
                                        rwyList.pop_front();
                                        departed = true;
                                        timeSinceLastDeparture = 0.0;
                                } else if(t->opType == CIRCUIT) {
+                                       //cout << ident << " adding " << t->plane.callsign << " to circuitList" << endl;
                                        circuitList.push_back(t);
                                        AddToTrafficList(t);
                                        rwyList.pop_front();
@@ -926,6 +1025,7 @@ void FGTower::CheckRunwayList(double dt) {
                                        timeSinceLastDeparture = 0.0;
                                } else if(t->opType == TTT_UNKNOWN) {
                                        depList.push_back(t);
+                                       //cout << ident << " adding " << t->plane.callsign << " to circuitList" << endl;
                                        circuitList.push_back(t);
                                        AddToTrafficList(t);
                                        rwyList.pop_front();
@@ -942,25 +1042,139 @@ void FGTower::CheckRunwayList(double dt) {
 
 // Do one plane from the approach list
 void FGTower::CheckApproachList(double dt) {
+       //cout << "CheckApproachList called for " << ident << endl;
+       //cout << "AppList.size is " << appList.size() << endl;
        if(appList.size()) {
                if(appListItr == appList.end()) {
                        appListItr = appList.begin();
                }
                TowerPlaneRec* t = *appListItr;
                //cout << "t = " << t << endl;
+               //cout << "Checking " << t->plane.callsign << endl;
                if(t->isUser) {
                        t->pos.setlon(user_lon_node->getDoubleValue());
                        t->pos.setlat(user_lat_node->getDoubleValue());
                        t->pos.setelev(user_elev_node->getDoubleValue());
                } else {
                        // TODO - set/update the position if it's an AI plane
+               }
+               //cout << "eta is " << t->eta << ", rwy is " << (rwyList.size() ? "occupied " : "clear ") << '\n';
+               if(t->eta < 12 && rwyList.size() && !(t->instructedToGoAround)) {
+                       // TODO - need to make this more sophisticated 
+                       // eg. is the plane accelerating down the runway taking off [OK],
+                       // or stationary near the start [V. BAD!!].
+                       // For now this should stop the AI plane landing on top of the user.
+                       string trns = t->plane.callsign;
+                       trns += " GO AROUND TRAFFIC ON RUNWAY I REPEAT GO AROUND";
+                       if(display) {
+                               globals->get_ATC_display()->RegisterSingleMessage(trns, 0);
+                       }
+                       t->instructedToGoAround = true;
+                       t->clearedToLand = false;
+                       t->nextOnRwy = false;   // But note this is recalculated so don't rely on it
+                       // Assume it complies!!!
+                       t->opType = CIRCUIT;
+                       t->leg = CLIMBOUT;
+                       if(!t->isUser) {
+                               if(t->planePtr) {
+                                       //cout << "Registering Go-around transmission with AI plane\n";
+                                       t->planePtr->RegisterTransmission(13);
+                               }
+                       } else {
+                               // TODO - add Go-around ack to comm options,
+                               // remove report rwy vacated. (possibly).
+                       }
                }                               
-               if(t->nextOnRwy && !(t->clearedToLand)) {
+               if(t->nextOnRwy && !(t->clearedToLand) && !(t->instructedToGoAround)) {
                        // check distance away and whether runway occupied
                        // and schedule transmission if necessary
-               }                               
+               }
+               
+               // Check for landing...
+               bool landed = false;
+               if(!t->isUser) {
+                       if(t->planePtr) {
+                               if(t->planePtr->GetLeg() == LANDING_ROLL) {
+                                       landed = true;
+                               }
+                       } else {
+                               SG_LOG(SG_ATC, SG_ALERT, "WARNING - not user and null planePtr in CheckApproachList!");
+                       }
+               } else {        // user
+                       if(OnActiveRunway(t->pos)) {
+                               landed = true;
+                       }
+               }
+               
+               if(landed) {
+                       // Duplicated in CheckCircuitList - must be able to rationalise this somehow!
+                       //cout << "A " << t->plane.callsign << " has landed, adding to rwyList...\n";
+                       rwyList.push_front(t);
+                       // TODO - if(!clearedToLand) shout something!!
+                       t->clearedToLand = false;
+                       RemoveFromTrafficList(t->plane.callsign);
+                       //if(t->isUser) {
+                               //      t->opType = TTT_UNKNOWN;
+                       //}     // TODO - allow the user to specify opType via ATC menu
+                       appListItr = appList.erase(appListItr);
+                       if(appListItr == appList.end() ) {
+                               appListItr = appList.begin();
+                       }
+               }
+               
                ++appListItr;
        }
+       //cout << "Done" << endl;
+}
+
+// Do one plane from the departure list
+void FGTower::CheckDepartureList(double dt) {
+       if(depList.size()) {
+               if(depListItr == depList.end()) {
+                       depListItr = depList.begin();
+               }
+               TowerPlaneRec* t = *depListItr;
+               //cout << "Dep list, checking " << t->plane.callsign;
+               
+               double distout; // meters
+               if(t->isUser) distout = dclGetHorizontalSeparation(Point3D(lon, lat, elev), Point3D(user_lon_node->getDoubleValue(), user_lat_node->getDoubleValue(), user_elev_node->getDoubleValue()));
+               else distout = dclGetHorizontalSeparation(Point3D(lon, lat, elev), t->planePtr->GetPos());
+               //cout << " distout = " << distout << '\n';
+               if(distout > 10000) {
+                       string trns = t->plane.callsign;
+                       trns += " You are now clear of my airspace, good day";
+                       if(display) {
+                               globals->get_ATC_display()->RegisterSingleMessage(trns, 0);
+                       }
+                       if(t->isUser) {
+                               // Change the communication options
+                               RemoveAllUserDialogOptions();
+                               current_atcdialog->add_entry(ident, "@AP Tower @CS @MI miles @CD of the airport for full stop with the ATIS", "Contact tower for VFR arrival (full stop)", TOWER, (int)USER_REQUEST_VFR_ARRIVAL_FULL_STOP);
+                       } else {
+                               // Send a clear-of-airspace signal
+                               // TODO - implement this once we actually have departing AI traffic (currently all circuits or arrivals).
+                       }
+                       RemovePlane(t->plane.callsign);
+               } else {
+                       ++depListItr;
+               }
+       }
+}
+
+// ********** End periodic check functions ***********************************************
+// ***************************************************************************************
+
+
+// Remove all dialog options for this tower.
+void FGTower::RemoveAllUserDialogOptions() {
+       current_atcdialog->remove_entry(ident, USER_REQUEST_VFR_DEPARTURE, TOWER);
+       current_atcdialog->remove_entry(ident, USER_REQUEST_VFR_ARRIVAL, TOWER);
+       current_atcdialog->remove_entry(ident, USER_REQUEST_VFR_ARRIVAL_FULL_STOP, TOWER);
+       current_atcdialog->remove_entry(ident, USER_REQUEST_VFR_ARRIVAL_TOUCH_AND_GO, TOWER);
+       current_atcdialog->remove_entry(ident, USER_REPORT_3_MILE_FINAL, TOWER);
+       current_atcdialog->remove_entry(ident, USER_REPORT_DOWNWIND, TOWER);
+       current_atcdialog->remove_entry(ident, USER_REPORT_RWY_VACATED, TOWER);
+       current_atcdialog->remove_entry(ident, USER_REPORT_GOING_AROUND, TOWER);        
 }
 
 // Returns true if positions of crosswind/downwind/base leg turns should be constrained by previous traffic
@@ -1011,6 +1225,7 @@ void FGTower::DoRwyDetails() {
        FGRunway runway;
        bool rwyGood = globals->get_runways()->search(ident, int(hdg), &runway);
        if(rwyGood) {
+               //cout << "RUNWAY GOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOD\n";
                activeRwy = runway.rwy_no;
                rwy.rwyID = runway.rwy_no;
                SG_LOG(SG_ATC, SG_INFO, "Active runway for airport " << ident << " is " << activeRwy);
@@ -1051,6 +1266,7 @@ void FGTower::DoRwyDetails() {
                if(rwy.rwyID.size() == 3) {
                        rwy.patternDirection = (rwy.rwyID.substr(2,1) == "R" ? 1 : -1);
                }
+               //cout << "Doing details, rwy.patterDirection is " << rwy.patternDirection << '\n';
        } else {
                SG_LOG(SG_ATC, SG_ALERT, "Help  - can't get good runway in FGTower!!");
                activeRwy = "NN";
@@ -1113,6 +1329,7 @@ bool FGTower::RemoveFromTrafficList(string id) {
                TowerPlaneRec* tpr = *twrItr;
                if(tpr->plane.callsign == id) {
                        trafficList.erase(twrItr);
+                       trafficListItr = trafficList.begin();
                        return(true);
                }
        }       
@@ -1121,6 +1338,37 @@ bool FGTower::RemoveFromTrafficList(string id) {
 }
 
 
+// Returns true if successful
+bool FGTower::RemoveFromAppList(string id) {
+       tower_plane_rec_list_iterator twrItr;
+       for(twrItr = appList.begin(); twrItr != appList.end(); twrItr++) {
+               TowerPlaneRec* tpr = *twrItr;
+               if(tpr->plane.callsign == id) {
+                       appList.erase(twrItr);
+                       appListItr = appList.begin();
+                       return(true);
+               }
+       }       
+       //SG_LOG(SG_ATC, SG_WARN, "Warning - unable to remove aircraft " << id << " from appList in FGTower");
+       return(false);
+}
+
+// Returns true if successful
+bool FGTower::RemoveFromRwyList(string id) {
+       tower_plane_rec_list_iterator twrItr;
+       for(twrItr = rwyList.begin(); twrItr != rwyList.end(); twrItr++) {
+               TowerPlaneRec* tpr = *twrItr;
+               if(tpr->plane.callsign == id) {
+                       rwyList.erase(twrItr);
+                       rwyListItr = rwyList.begin();
+                       return(true);
+               }
+       }       
+       //SG_LOG(SG_ATC, SG_WARN, "Warning - unable to remove aircraft " << id << " from rwyList in FGTower");
+       return(false);
+}
+
+
 // Add a tower plane rec with ETA to the traffic list in the correct position ETA-wise
 // and set nextOnRwy if so.
 // Returns true if this could cause a threshold ETA conflict with other traffic, false otherwise.
@@ -1198,30 +1446,51 @@ bool FGTower::AddToTrafficList(TowerPlaneRec* t, bool holding) {
 
 // Add a tower plane rec with ETA to the circuit list in the correct position ETA-wise
 // Returns true if this might cause a separation conflict (based on ETA) with other traffic, false otherwise.
+// Safe to add a plane that is already in - planes with the same callsign are not added.
 bool FGTower::AddToCircuitList(TowerPlaneRec* t) {
+       if(!t) {
+               //cout << "**********************************************\n";
+               //cout << "AddToCircuitList called with NULL pointer!!!!!\n";
+               //cout << "**********************************************\n";
+               return false;
+       }
        //cout << "ADD: " << circuitList.size();
-       //cout << "AddToCircuitList called, currently size = " << circuitList.size() << endl;
+       //cout << ident << " AddToCircuitList called for " << t->plane.callsign << ", currently size = " << circuitList.size() << endl;
        double separation_time = 60.0;  // seconds - this is currently a guess for light plane separation, and includes a few seconds for a holding plane to taxi onto the rwy.
        bool conflict = false;
        tower_plane_rec_list_iterator twrItr;
+       // First check if the plane is already in the list
+       //cout << "A" << endl;
+       //cout << "Checking whether " << t->plane.callsign << " is already in circuit list..." << endl;
+       //cout << "B" << endl;
        for(twrItr = circuitList.begin(); twrItr != circuitList.end(); twrItr++) {
-                       TowerPlaneRec* tpr = *twrItr;
-                               
-                               if(t->eta < tpr->eta) {
-                                       // Ugg - this one's tricky.
-                                       // It depends on what the two planes are doing and whether there's a conflict what we do.
-                                       if(tpr->eta - t->eta > separation_time) {       // No probs, plane 2 can squeeze in before plane 1 with no apparent conflict
-                                               circuitList.insert(twrItr, t);
-                                       } else {        // Ooops - this ones tricky - we have a potential conflict!
-                                               conflict = true;
-                                               // HACK - just add anyway for now and flag conflict.
-                                               circuitList.insert(twrItr, t);
-                                       }
-                                       //cout << "\tC\t" << circuitList.size() << '\n';
-                                       return(conflict);
-                               }
+               if((*twrItr)->plane.callsign == t->plane.callsign) {
+                       //cout << "In list - returning...\n";
+                       return false;
+               }
+       }
+       //cout << "Not in list - adding..." << endl;
+       
+       for(twrItr = circuitList.begin(); twrItr != circuitList.end(); twrItr++) {
+               TowerPlaneRec* tpr = *twrItr;
+               //cout << tpr->plane.callsign << " eta is " << tpr->eta << '\n';
+               //cout << "New eta is " << t->eta << '\n';              
+               if(t->eta < tpr->eta) {
+                       // Ugg - this one's tricky.
+                       // It depends on what the two planes are doing and whether there's a conflict what we do.
+                       if(tpr->eta - t->eta > separation_time) {       // No probs, plane 2 can squeeze in before plane 1 with no apparent conflict
+                               circuitList.insert(twrItr, t);
+                       } else {        // Ooops - this ones tricky - we have a potential conflict!
+                               conflict = true;
+                               // HACK - just add anyway for now and flag conflict.
+                               circuitList.insert(twrItr, t);
+                       }
+                       //cout << "\tC\t" << circuitList.size() << '\n';
+                       return(conflict);
+               }
        }
        // If we get here we must be at the end of the list, or maybe the list is empty.
+       //cout << ident << " adding " << t->plane.callsign << " to circuitList" << endl;
        circuitList.push_back(t);       // TODO - check the separation with the preceding plane for the conflict flag.
        //cout << "\tE\t" << circuitList.size() << endl;
        return(conflict);
@@ -1250,13 +1519,14 @@ void FGTower::CalcETA(TowerPlaneRec* tpr, bool printout) {
        Point3D op = ortho.ConvertToLocal(tpr->pos);
        //if(printout) {
        //if(!tpr->isUser) cout << "Orthopos is " << op.x() << ", " << op.y() << ' ';
-       //      cout << "opType is " << tpr->opType << '\n';
+       //cout << "opType is " << tpr->opType << '\n';
        //}
        double dist_out_m = op.y();
-       double dist_across_m = fabs(op.x());    // FIXME = the fabs is a hack to cope with the fact that we don't know the circuit direction yet
+       double dist_across_m = fabs(op.x());    // The fabs is a hack to cope with the fact that we don't know the circuit direction yet
        //cout << "Doing ETA calc for " << tpr->plane.callsign << '\n';
        
-       if(tpr->opType == STRAIGHT_IN) {
+       if(tpr->opType == STRAIGHT_IN || tpr->opType == INBOUND) {
+               //cout << "CASE 1\n";
                double dist_to_go_m = sqrt((dist_out_m * dist_out_m) + (dist_across_m * dist_across_m));
                if(dist_to_go_m < 1000) {
                        tpr->eta = dist_to_go_m / final_ias;
@@ -1264,13 +1534,15 @@ void FGTower::CalcETA(TowerPlaneRec* tpr, bool printout) {
                        tpr->eta = (1000.0 / final_ias) + ((dist_to_go_m - 1000.0) / app_ias);
                }
        } else if(tpr->opType == CIRCUIT || tpr->opType == TTT_UNKNOWN) {       // Hack alert - UNKNOWN has sort of been added here as a temporary hack.
+               //cout << "CASE 2\n";
                // It's complicated - depends on if base leg is delayed or not
                //if(printout) {
-               //      cout << "Leg = " << tpr->leg << '\n';
+               //cout << "Leg = " << tpr->leg << '\n';
                //}
                if(tpr->leg == LANDING_ROLL) {
                        tpr->eta = 0;
                } else if((tpr->leg == FINAL) || (tpr->leg == TURN4)) {
+                       //cout << "dist_out_m = " << dist_out_m << '\n';
                        tpr->eta = fabs(dist_out_m) / final_ias;
                } else if((tpr->leg == BASE) || (tpr->leg == TURN3)) {
                        tpr->eta = (fabs(dist_out_m) / final_ias) + (dist_across_m / circuit_ias);
@@ -1289,7 +1561,7 @@ void FGTower::CalcETA(TowerPlaneRec* tpr, bool printout) {
                        if(!GetDownwindConstraint(current_dist_across_m)) {
                                current_dist_across_m = nominal_dist_across_m;
                        }
-                       double nominal_cross_dist_out_m = 2000; // Bit of a guess - AI plane turns to crosswind at 600ft agl.
+                       double nominal_cross_dist_out_m = 2000; // Bit of a guess - AI plane turns to crosswind at 700ft agl.
                        tpr->eta = fabs(current_base_dist_out_m) / final_ias;   // final
                        //cout << "a = " << tpr->eta << '\n';
                        if((tpr->leg == DOWNWIND) || (tpr->leg == TURN2)) {
@@ -1298,24 +1570,35 @@ void FGTower::CalcETA(TowerPlaneRec* tpr, bool printout) {
                                tpr->eta += fabs(current_base_dist_out_m - dist_out_m) / circuit_ias;
                                //cout << "c = " << tpr->eta << '\n';
                        } else if((tpr->leg == CROSSWIND) || (tpr->leg == TURN1)) {
+                               //cout << "CROSSWIND calc: ";
+                               //cout << tpr->eta << ' ';
                                if(dist_across_m > nominal_dist_across_m) {
                                        tpr->eta += dist_across_m / circuit_ias;
+                                       //cout << "a ";
                                } else {
                                        tpr->eta += nominal_dist_across_m / circuit_ias;
+                                       //cout << "b ";
                                }
+                               //cout << tpr->eta << ' ';
                                // should we use the dist across of the previous plane if there is previous still on downwind?
                                //if(printout) cout << "bb = " << tpr->eta << '\n';
                                if(dist_out_m > nominal_cross_dist_out_m) {
                                        tpr->eta += fabs(current_base_dist_out_m - dist_out_m) / circuit_ias;
+                                       //cout << "c ";
                                } else {
                                        tpr->eta += fabs(current_base_dist_out_m - nominal_cross_dist_out_m) / circuit_ias;
+                                       //cout << "d ";
                                }
+                               //cout << tpr->eta << ' ';
                                //if(printout) cout << "cc = " << tpr->eta << '\n';
                                if(nominal_dist_across_m > dist_across_m) {
                                        tpr->eta += (nominal_dist_across_m - dist_across_m) / circuit_ias;
+                                       //cout << "e ";
                                } else {
                                        // Nothing to add
+                                       //cout << "f ";
                                }
+                               //cout << tpr->eta << '\n';
                                //if(printout) cout << "dd = " << tpr->eta << '\n';
                        } else {
                                // We've only just started - why not use a generic estimate?
@@ -1346,18 +1629,23 @@ double FGTower::CalcDistOutMiles(TowerPlaneRec* tpr) {
 }
 
 
-// Iterate through all the lists and call CalcETA for all the planes.
+// Iterate through all the lists, update the position of, and call CalcETA for all the planes.
 void FGTower::doThresholdETACalc() {
        //cout << "Entering doThresholdETACalc..." << endl;
        tower_plane_rec_list_iterator twrItr;
        // Do the approach list first
        for(twrItr = appList.begin(); twrItr != appList.end(); twrItr++) {
                TowerPlaneRec* tpr = *twrItr;
+               if(!(tpr->isUser)) tpr->pos = tpr->planePtr->GetPos();
+               //cout << "APP: ";
                CalcETA(tpr);
        }       
        // Then the circuit list
+       //cout << "Circuit list size is " << circuitList.size() << '\n';
        for(twrItr = circuitList.begin(); twrItr != circuitList.end(); twrItr++) {
                TowerPlaneRec* tpr = *twrItr;
+               if(!(tpr->isUser)) tpr->pos = tpr->planePtr->GetPos();
+               //cout << "CIRC: ";
                CalcETA(tpr);
        }
        //cout << "Done doThresholdETCCalc" << endl;
@@ -1480,7 +1768,9 @@ void FGTower::RegisterAIPlane(PlaneRec plane, FGAIPlane* ai, tower_traffic_type
        CalcETA(t);
        
        if(op == CIRCUIT && lg != LEG_UNKNOWN) {
+               cout << "AAAAAAAAAAAAAAAaa" << endl;
                AddToCircuitList(t);
+               cout << "BBBBBBBBBBBBBBbbb" << endl;
        } else {
                // FLAG A WARNING
        }
@@ -1488,12 +1778,16 @@ void FGTower::RegisterAIPlane(PlaneRec plane, FGAIPlane* ai, tower_traffic_type
        doThresholdUseOrder();
 }
 
+void FGTower::DeregisterAIPlane(string id) {
+       RemovePlane(id);
+}
+
 // Contact tower for VFR approach
 // eg "Cessna Charlie Foxtrot Golf Foxtrot Sierra eight miles South of the airport for full stop with Bravo"
 // This function probably only called via user interaction - AI planes will have an overloaded function taking a planerec.
 // opt defaults to AIP_LT_UNKNOWN
 void FGTower::VFRArrivalContact(string ID, LandingType opt) {
-       //cout << "Request Landing Clearance called...\n";
+       //cout << "USER Request Landing Clearance called for ID " << ID << '\n';
        
        // For now we'll assume that the user is a light plane and can get him/her to join the circuit if necessary.
 
@@ -1542,11 +1836,38 @@ void FGTower::VFRArrivalContact(string ID, LandingType opt) {
        current_atcdialog->remove_entry(ident, USER_REQUEST_VFR_ARRIVAL_TOUCH_AND_GO, TOWER);
 }
 
+// landingType defaults to AIP_LT_UNKNOWN
+void FGTower::VFRArrivalContact(PlaneRec plane, FGAIPlane* requestee, LandingType lt) {
+       //cout << "VFRArrivalContact called for plane " << plane.callsign << " at " << ident << '\n';
+       // Possible hack - assume this plane is new for now - TODO - should check really
+       TowerPlaneRec* t = new TowerPlaneRec;
+       t->plane = plane;
+       t->planePtr = requestee;
+       t->landingType = lt;
+       t->pos = requestee->GetPos();
+       
+       //cout << "Hold Short reported by " << plane.callsign << '\n';
+       SG_LOG(SG_ATC, SG_BULK, "VFR arrival contact made by " << plane.callsign);
+       //cout << "VFR arrival contact made by " << plane.callsign << '\n';
+
+       // HACK - to get up and running I'm going to assume a staight-in final for now.
+       t->opType = STRAIGHT_IN;
+       
+       t->vfrArrivalReported = true;
+       responseReqd = true;
+       
+       //cout << "Before adding, appList.size = " << appList.size() << " at " << ident << '\n';
+       appList.push_back(t);   // Not necessarily permanent
+       //cout << "After adding, appList.size = " << appList.size() << " at " << ident << '\n';
+       AddToTrafficList(t);
+}
+
 void FGTower::RequestDepartureClearance(string ID) {
        //cout << "Request Departure Clearance called...\n";
 }
        
 void FGTower::ReportFinal(string ID) {
+       //cout << "Report Final Called at tower " << ident << " by plane " << ID << '\n';
        if(ID == "USER") {
                ID = fgGetString("/sim/user/callsign");
                current_atcdialog->remove_entry(ident, USER_REPORT_3_MILE_FINAL, TOWER);
@@ -1557,7 +1878,12 @@ void FGTower::ReportFinal(string ID) {
                t->finalAcknowledged = false;
                if(!(t->clearedToLand)) {
                        responseReqd = true;
-               } // possibly respond with wind even if already cleared to land?
+               } else {
+                       // possibly respond with wind even if already cleared to land?
+                       t->finalReported = false;
+                       t->finalAcknowledged = true;
+                       // HACK!! - prevents next reporting being misinterpreted as this one.
+               }
        } else {
                SG_LOG(SG_ATC, SG_WARN, "WARNING: Unable to find plane " << ID << " in FGTower::ReportFinal(...)");
        }
@@ -1586,38 +1912,115 @@ void FGTower::ReportLongFinal(string ID) {
 //void FGTower::ReportGoingAround(string ID);
 
 void FGTower::ReportRunwayVacated(string ID) {
-       //cout << "Report Runway Vacated Called...\n";
+       //cout << "Report Runway Vacated Called at tower " << ident << " by plane " << ID << '\n';
        if(ID == "USER") {
                ID = fgGetString("/sim/user/callsign");
                current_atcdialog->remove_entry(ident, USER_REPORT_RWY_VACATED, TOWER);
        }
        TowerPlaneRec* t = FindPlane(ID);
        if(t) {
+               //cout << "Found it...\n";
                t->rwyVacatedReported = true;
                responseReqd = true;
        } else {
                SG_LOG(SG_ATC, SG_WARN, "WARNING: Unable to find plane " << ID << " in FGTower::ReportRunwayVacated(...)");
+               SG_LOG(SG_ATC, SG_ALERT, "A WARNING: Unable to find plane " << ID << " in FGTower::ReportRunwayVacated(...)");
+               //cout << "WARNING: Unable to find plane " << ID << " in FGTower::ReportRunwayVacated(...)\n";
        }
 }
 
 TowerPlaneRec* FGTower::FindPlane(string ID) {
+       //cout << "FindPlane called for " << ID << "...\n";
        tower_plane_rec_list_iterator twrItr;
        // Do the approach list first
        for(twrItr = appList.begin(); twrItr != appList.end(); twrItr++) {
+               //cout << "appList callsign is " << (*twrItr)->plane.callsign << '\n';
                if((*twrItr)->plane.callsign == ID) return(*twrItr);
        }       
        // Then the circuit list
        for(twrItr = circuitList.begin(); twrItr != circuitList.end(); twrItr++) {
+               //cout << "circuitList callsign is " << (*twrItr)->plane.callsign << '\n';
                if((*twrItr)->plane.callsign == ID) return(*twrItr);
        }
-       // And finally the hold list
+       // Then the runway list
+       //cout << "rwyList.size() is " << rwyList.size() << '\n';
+       for(twrItr = rwyList.begin(); twrItr != rwyList.end(); twrItr++) {
+               //cout << "rwyList callsign is " << (*twrItr)->plane.callsign << '\n';
+               if((*twrItr)->plane.callsign == ID) return(*twrItr);
+       }
+       // The hold list
        for(twrItr = holdList.begin(); twrItr != holdList.end(); twrItr++) {
                if((*twrItr)->plane.callsign == ID) return(*twrItr);
        }
+       // And finally the vacated list
+       for(twrItr = vacatedList.begin(); twrItr != vacatedList.end(); twrItr++) {
+               //cout << "vacatedList callsign is " << (*twrItr)->plane.callsign << '\n';
+               if((*twrItr)->plane.callsign == ID) return(*twrItr);
+       }
        SG_LOG(SG_ATC, SG_WARN, "Unable to find " << ID << " in FGTower::FindPlane(...)");
+       //exit(-1);
        return(NULL);
 }
 
+void FGTower::RemovePlane(string ID) {
+       //cout << ident << " RemovePlane called for " << ID << '\n';
+       // We have to be careful here - we want to erase the plane from all lists it is in,
+       // but we can only delete it once, AT THE END.
+       TowerPlaneRec* t = NULL;
+       tower_plane_rec_list_iterator twrItr;
+       for(twrItr = appList.begin(); twrItr != appList.end(); twrItr++) {
+               if((*twrItr)->plane.callsign == ID) {
+                       t = *twrItr;
+                       twrItr = appList.erase(twrItr);
+                       appListItr = appList.begin();
+               }
+       }
+       for(twrItr = depList.begin(); twrItr != depList.end(); twrItr++) {
+               if((*twrItr)->plane.callsign == ID) {
+                       t = *twrItr;
+                       twrItr = depList.erase(twrItr);
+                       depListItr = depList.begin();
+               }
+       }
+       for(twrItr = circuitList.begin(); twrItr != circuitList.end(); twrItr++) {
+               if((*twrItr)->plane.callsign == ID) {
+                       t = *twrItr;
+                       twrItr = circuitList.erase(twrItr);
+                       circuitListItr = circuitList.begin();
+               }
+       }
+       for(twrItr = holdList.begin(); twrItr != holdList.end(); twrItr++) {
+               if((*twrItr)->plane.callsign == ID) {
+                       t = *twrItr;
+                       twrItr = holdList.erase(twrItr);
+                       holdListItr = holdList.begin();
+               }
+       }
+       for(twrItr = rwyList.begin(); twrItr != rwyList.end(); twrItr++) {
+               if((*twrItr)->plane.callsign == ID) {
+                       t = *twrItr;
+                       twrItr = rwyList.erase(twrItr);
+                       rwyListItr = rwyList.begin();
+               }
+       }
+       for(twrItr = vacatedList.begin(); twrItr != vacatedList.end(); twrItr++) {
+               if((*twrItr)->plane.callsign == ID) {
+                       t = *twrItr;
+                       twrItr = vacatedList.erase(twrItr);
+                       vacatedListItr = vacatedList.begin();
+               }
+       }
+       for(twrItr = trafficList.begin(); twrItr != trafficList.end(); twrItr++) {
+               if((*twrItr)->plane.callsign == ID) {
+                       t = *twrItr;
+                       twrItr = trafficList.erase(twrItr);
+                       trafficListItr = trafficList.begin();
+               }
+       }
+       // And finally, delete the record if we found it.
+       if(t) delete t;
+}
+
 void FGTower::ReportDownwind(string ID) {
        //cout << "ReportDownwind(...) called\n";
        if(ID == "USER") {
@@ -1628,6 +2031,16 @@ void FGTower::ReportDownwind(string ID) {
        if(t) {
                t->downwindReported = true;
                responseReqd = true;
+               // If the plane is in the app list, remove it and put it in the circuit list instead.
+               // Ideally we might want to do this at the 2 mile report prior to 45 deg entry, but at
+               // the moment that would b&gg?r up the constraint position calculations.
+               RemoveFromAppList(ID);
+               t->leg = DOWNWIND;
+               t->pos = t->planePtr->GetPos();
+               CalcETA(t);
+               //cout << "DDDDDDDDDDDDDDDDdddddddd" << endl;
+               AddToCircuitList(t);
+               //cout << "EEEEEEEEEEEEEEEEEEEEeeeeee" << endl;
        } else {
                SG_LOG(SG_ATC, SG_WARN, "WARNING: Unable to find plane " << ID << " in FGTower::ReportDownwind(...)");
        }
@@ -1730,7 +2143,7 @@ string FGTower::GenText(const string& m, int c) {
                                else if ( strcmp ( tag, "@MI" ) == 0 ) {
                                        char buf[10];
                                        //sprintf( buf, "%3.1f", tpars.miles );
-                                       int dist_miles = dclGetHorizontalSeparation(Point3D(lon, lat, elev), Point3D(user_lon_node->getDoubleValue(), user_lat_node->getDoubleValue(), user_elev_node->getDoubleValue())) / 1600;
+                                       int dist_miles = (int)dclGetHorizontalSeparation(Point3D(lon, lat, elev), Point3D(user_lon_node->getDoubleValue(), user_lat_node->getDoubleValue(), user_elev_node->getDoubleValue())) / 1600;
                                        sprintf(buf, "%i", dist_miles);
                                        strcat( &dum[0], &buf[0] );
                                }
index f06faeb56455c0eeab74e4844c4ae750bea3647b..78e43baa34204144d0e0b63ee0c598f6dcbd59b2 100644 (file)
@@ -60,7 +60,8 @@ enum tower_callback_type {
        USER_REQUEST_VFR_ARRIVAL_TOUCH_AND_GO = 4,
        USER_REPORT_3_MILE_FINAL = 5,
        USER_REPORT_DOWNWIND = 6,
-       USER_REPORT_RWY_VACATED = 7
+       USER_REPORT_RWY_VACATED = 7,
+       USER_REPORT_GOING_AROUND = 8
 };
 
 // TODO - need some differentiation of IFR and VFR traffic in order to give the former priority.
@@ -135,6 +136,8 @@ public:
        // eg "Cessna Charlie Foxtrot Golf Foxtrot Sierra eight miles South of the airport for full stop with Bravo"
        // This function probably only called via user interaction - AI planes will have an overloaded function taking a planerec.
        void VFRArrivalContact(string ID, LandingType opt = AIP_LT_UNKNOWN);
+       // For the AI planes...
+       void VFRArrivalContact(PlaneRec plane, FGAIPlane* requestee, LandingType lt = AIP_LT_UNKNOWN);
        
        void RequestDepartureClearance(string ID);      
        void ReportFinal(string ID);
@@ -154,6 +157,9 @@ public:
        // CAUTION - currently it is assumed that this plane's callsign is unique - it is up to AIMgr to generate unique callsigns.
        void RegisterAIPlane(PlaneRec plane, FGAIPlane* ai, tower_traffic_type op, PatternLeg lg = LEG_UNKNOWN);
        
+       // Deregister and remove an AI plane.
+       void DeregisterAIPlane(string id);
+       
        // Public interface to the active runway - this will get more complex 
        // in the future and consider multi-runway use, airplane weight etc.
        inline string GetActiveRunway() { return activeRwy; }
@@ -165,7 +171,6 @@ public:
        inline void SetNoDisplay() { display = false; }
        
        inline string get_trans_ident() { return trans_ident; }
-       inline atc_type GetType() { return TOWER; }
        
        inline FGGround* GetGroundPtr() { return ground; }
        
@@ -184,13 +189,17 @@ private:
        // Respond to a transmission
        void Respond();
        
+       void ProcessRunwayVacatedReport(TowerPlaneRec* t);
+       
+       // Remove all options from the user dialog choice
+       void RemoveAllUserDialogOptions();
+       
+       // Periodic checks on the various traffic.
        void CheckHoldList(double dt);
-
        void CheckCircuitList(double dt);
-       
        void CheckRunwayList(double dt);
-
        void CheckApproachList(double dt);
+       void CheckDepartureList(double dt);
        
        // Currently this assumes we *are* next on the runway and doesn't check for planes about to land - 
        // this should be done prior to calling this function.
@@ -199,6 +208,9 @@ private:
        // Find a pointer to plane of callsign ID within the internal data structures
        TowerPlaneRec* FindPlane(string ID);
        
+       // Remove and delete all instances of a plane with a given ID
+       void RemovePlane(string ID);
+       
        // Figure out if a given position lies on the active runway
        // Might have to change when we consider more than one active rwy.
        bool OnActiveRunway(Point3D pt);
@@ -292,8 +304,14 @@ private:
        tower_plane_rec_list_type trafficList;  // TODO - needs to be expandable to more than one rwy
        tower_plane_rec_list_iterator trafficListItr;
        
+       // List of planes that have vacated the runway inbound but not yet handed off to ground
+       tower_plane_rec_list_type vacatedList;
+       tower_plane_rec_list_iterator vacatedListItr;
+       
        // Returns true if successful
        bool RemoveFromTrafficList(string id);
+       bool RemoveFromAppList(string id);
+       bool RemoveFromRwyList(string id);
        
        // Return the ETA of plane no. list_pos (1-based) in the traffic list.
        // i.e. list_pos = 1 implies next to use runway.
@@ -311,6 +329,9 @@ private:
        bool separateGround;    // true if ground control is separate
        FGGround* ground;       // The ground control associated with this airport.
        
+       bool _departureControlled;      // true if we need to hand off departing traffic to departure control
+       //FGDeparture* _departure;      // The relevant departure control (once we've actually written it!)
+       
        // for failure modeling
        string trans_ident;             // transmitted ident
        bool tower_failed;              // tower failed?