]> git.mxchange.org Git - flightgear.git/blobdiff - src/ATC/tower.cxx
Fix the nmea and garmin output to a) fake a GSA sentence, b) fix a y2k bug
[flightgear.git] / src / ATC / tower.cxx
index e33e62f6089bfc9d25d453270d8b968e904c1aa6..a817d7ff0429efff8bb3de0a174c30c16095daa5 100644 (file)
 // along with this program; if not, write to the Free Software
 // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif
+
+#ifdef HAVE_STRINGS_H
+#  include <strings.h>   // bcopy()
+#else
+#  include <string.h>    // MSVC doesn't have strings.h
+#endif
+
 #include <Main/globals.hxx>
 #include <Airports/runways.hxx>
 #include <simgear/math/sg_geodesy.hxx>
@@ -27,6 +37,7 @@
 #include "ATCdisplay.hxx"
 #include "ATCmgr.hxx"
 #include "ATCutils.hxx"
+#include "ATCDialog.hxx"
 #include "commlist.hxx"
 #include "AILocalTraffic.hxx"
 
@@ -35,82 +46,102 @@ SG_USING_STD(cout);
 // TowerPlaneRec
 
 TowerPlaneRec::TowerPlaneRec() :
-clearedToLand(false),
-clearedToLineUp(false),
-clearedToTakeOff(false),
-holdShortReported(false),
-downwindReported(false),
-longFinalReported(false),
-longFinalAcknowledged(false),
-finalReported(false),
-finalAcknowledged(false),
-onRwy(false),
-nextOnRwy(false),
-opType(TTT_UNKNOWN),
-leg(LEG_UNKNOWN),
-landingType(AIP_LT_UNKNOWN),
-isUser(false)
+       clearedToLand(false),
+       clearedToLineUp(false),
+       clearedToTakeOff(false),
+       holdShortReported(false),
+       downwindReported(false),
+       longFinalReported(false),
+       longFinalAcknowledged(false),
+       finalReported(false),
+       finalAcknowledged(false),
+       rwyVacatedReported(false),
+       rwyVacatedAcknowledged(false),
+       instructedToGoAround(false),
+       onRwy(false),
+       nextOnRwy(false),
+       vfrArrivalReported(false),
+       vfrArrivalAcknowledged(false),
+       opType(TTT_UNKNOWN),
+       leg(LEG_UNKNOWN),
+       landingType(AIP_LT_UNKNOWN),
+       isUser(false) 
 {
        plane.callsign = "UNKNOWN";
 }
 
 TowerPlaneRec::TowerPlaneRec(PlaneRec p) :
-clearedToLand(false),
-clearedToLineUp(false),
-clearedToTakeOff(false),
-holdShortReported(false),
-downwindReported(false),
-longFinalReported(false),
-longFinalAcknowledged(false),
-finalReported(false),
-finalAcknowledged(false),
-onRwy(false),
-nextOnRwy(false),
-opType(TTT_UNKNOWN),
-leg(LEG_UNKNOWN),
-landingType(AIP_LT_UNKNOWN),
-isUser(false)
+       clearedToLand(false),
+       clearedToLineUp(false),
+       clearedToTakeOff(false),
+       holdShortReported(false),
+       downwindReported(false),
+       longFinalReported(false),
+       longFinalAcknowledged(false),
+       finalReported(false),
+       finalAcknowledged(false),
+       rwyVacatedReported(false),
+       rwyVacatedAcknowledged(false),
+       instructedToGoAround(false),
+       onRwy(false),
+       nextOnRwy(false),
+       vfrArrivalReported(false),
+       vfrArrivalAcknowledged(false),
+       opType(TTT_UNKNOWN),
+       leg(LEG_UNKNOWN),
+       landingType(AIP_LT_UNKNOWN),
+       isUser(false)
 {
        plane = p;
 }
 
 TowerPlaneRec::TowerPlaneRec(Point3D pt) :
-clearedToLand(false),
-clearedToLineUp(false),
-clearedToTakeOff(false),
-holdShortReported(false),
-downwindReported(false),
-longFinalReported(false),
-longFinalAcknowledged(false),
-finalReported(false),
-finalAcknowledged(false),
-onRwy(false),
-nextOnRwy(false),
-opType(TTT_UNKNOWN),
-leg(LEG_UNKNOWN),
-landingType(AIP_LT_UNKNOWN),
-isUser(false)
+       clearedToLand(false),
+       clearedToLineUp(false),
+       clearedToTakeOff(false),
+       holdShortReported(false),
+       downwindReported(false),
+       longFinalReported(false),
+       longFinalAcknowledged(false),
+       finalReported(false),
+       finalAcknowledged(false),
+       rwyVacatedReported(false),
+       rwyVacatedAcknowledged(false),
+       instructedToGoAround(false),
+       onRwy(false),
+       nextOnRwy(false),
+       vfrArrivalReported(false),
+       vfrArrivalAcknowledged(false),
+       opType(TTT_UNKNOWN),
+       leg(LEG_UNKNOWN),
+       landingType(AIP_LT_UNKNOWN),
+       isUser(false)
 {
        plane.callsign = "UNKNOWN";
        pos = pt;
 }
 
 TowerPlaneRec::TowerPlaneRec(PlaneRec p, Point3D pt) :
-clearedToLand(false),
-clearedToLineUp(false),
-clearedToTakeOff(false),
-holdShortReported(false),
-downwindReported(false),
-longFinalReported(false),
-longFinalAcknowledged(false),
-finalReported(false),
-finalAcknowledged(false),
-onRwy(false),
-nextOnRwy(false),
-opType(TTT_UNKNOWN),
-leg(LEG_UNKNOWN),
-landingType(AIP_LT_UNKNOWN),
-isUser(false)
+       clearedToLand(false),
+       clearedToLineUp(false),
+       clearedToTakeOff(false),
+       holdShortReported(false),
+       downwindReported(false),
+       longFinalReported(false),
+       longFinalAcknowledged(false),
+       finalReported(false),
+       finalAcknowledged(false),
+       rwyVacatedReported(false),
+       rwyVacatedAcknowledged(false),
+       instructedToGoAround(false),
+       onRwy(false),
+       nextOnRwy(false),
+       vfrArrivalReported(false),
+       vfrArrivalAcknowledged(false),
+       opType(TTT_UNKNOWN),
+       leg(LEG_UNKNOWN),
+       landingType(AIP_LT_UNKNOWN),
+       isUser(false)
 {
        plane = p;
        pos = pt;
@@ -119,6 +150,23 @@ isUser(false)
 
 // FGTower
 
+/*******************************************
+               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.
+
+Use track instead of heading to determine what leg of the circuit the user is flying.
+
+Use altitude as well as position to try to determine if the user has left the circuit.
+
+Currently HoldShortReported code assumes there will be only one plane holding for the runway at once and 
+will break when planes start queueing.
+
+Implement ReportRunwayVacated
+*******************************************/
+
 FGTower::FGTower() {
        ATCmgr = globals->get_ATC_mgr();
        
@@ -140,6 +188,10 @@ FGTower::FGTower() {
        
        timeSinceLastDeparture = 9999;
        departed = false;
+       
+       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.
 }
 
 FGTower::~FGTower() {
@@ -215,8 +267,8 @@ void FGTower::Init() {
        if(rwyOccupied) {
                // Assume the user is started at the threshold ready to take-off
                TowerPlaneRec* t = new TowerPlaneRec;
-               t->plane.callsign = "Charlie Foxtrot Sierra";   // C-FGFS !!! - fixme - this is a bit hardwired
-               t->plane.type = GA_SINGLE;
+               t->plane.callsign = fgGetString("/sim/user/callsign");
+               t->plane.type = GA_SINGLE;      // FIXME - hardwired!!
                t->opType = TTT_UNKNOWN;        // We don't know if the user wants to do circuits or a departure...
                t->landingType = AIP_LT_UNKNOWN;
                t->leg = TAKEOFF_ROLL;
@@ -225,11 +277,15 @@ void FGTower::Init() {
                t->clearedToTakeOff = true;
                rwyList.push_back(t);
                departed = false;
+       } else {
+               // 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);
        }
 }
 
 void FGTower::Update(double dt) {
-       //cout << "T" << flush;
+       //cout << "T" << endl;
        // Each time step, what do we need to do?
        // We need to go through the list of outstanding requests and acknowedgements
        // and process at least one of them.
@@ -247,6 +303,7 @@ void FGTower::Update(double dt) {
                cout << update_count << "\ttL: " << trafficList.size() << "  cL: " << circuitList.size() << "  hL: " << holdList.size() << "  aL: " << appList.size() << '\n';
        }
        */
+       //if(ident == "EGNX") cout << display << '\n';
        
        if(departed != false) {
                timeSinceLastDeparture += dt;
@@ -254,6 +311,7 @@ void FGTower::Update(double dt) {
                //      cout << "  dt = " << dt << "  timeSinceLastDeparture = " << timeSinceLastDeparture << '\n';
        }
        
+       //cout << ident << " respond = " << respond << " responseReqd = " << responseReqd << '\n'; 
        if(respond) {
                if(!responseReqd) SG_LOG(SG_ATC, SG_ALERT, "ERROR - respond is true and responseReqd is false in FGTower::Update(...)");
                Respond();
@@ -335,38 +393,275 @@ void FGTower::Update(double dt) {
                fgSetDouble("/AI/user/ortho-y", user_ortho_pos.y());
                fgSetDouble("/AI/user/elev", user_elev_node->getDoubleValue());
        }
+       
+       //cout << "Done T" << endl;
+}
+
+void FGTower::ReceiveUserCallback(int code) {
+       if(code == (int)USER_REQUEST_VFR_DEPARTURE) {
+               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) {
+               VFRArrivalContact("USER", FULL_STOP);
+       } else if(code == (int)USER_REQUEST_VFR_ARRIVAL_TOUCH_AND_GO) {
+               VFRArrivalContact("USER", TOUCH_AND_GO);
+       } else if(code == (int)USER_REPORT_DOWNWIND) {
+               ReportDownwind("USER");
+       } else if(code == (int)USER_REPORT_3_MILE_FINAL) {
+               // For now we'll just call report final instead of report long final to avoid having to alter the response code
+               ReportFinal("USER");
+       } else if(code == (int)USER_REPORT_RWY_VACATED) {
+               ReportRunwayVacated("USER");
+       }
 }
 
 void FGTower::Respond() {
-       //cout << "Respond() called\n";
+       cout << "Entering Respond, responseID = " << responseID << endl;
        TowerPlaneRec* t = FindPlane(responseID);
        if(t) {
                // This will grow!!!
-               if(t->downwindReported) {
+               if(t->vfrArrivalReported && !t->vfrArrivalAcknowledged) {
+                       // Testing - hardwire straight in for now
+                       string trns = t->plane.callsign;
+                       trns += " ";
+                       trns += name;
+                       trns += " Tower";
+                       // Should we clear staight in or for downwind entry?
+                       // For now we'll clear straight in if greater than 1km from a line drawn through the threshold perpendicular to the rwy.
+                       // Later on we might check the actual heading and direct some of those to enter on downwind or base.
+                       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);
+                       } 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 ");
+                               trns += "downwind runway ";
+                               current_atcdialog->add_entry(ident, "@AP Tower @CS Downwind @RW", "Report Downwind", TOWER, (int)USER_REPORT_DOWNWIND);
+                       }
+                       trns += ConvertRwyNumToSpokenString(activeRwy);
+                       if(display) {
+                               globals->get_ATC_display()->RegisterSingleMessage(trns, 0);
+                       } else {
+                               cout << "Not displaying, trns was " << trns << '\n';
+                       }
+                       t->vfrArrivalAcknowledged = true;
+               } else if(t->downwindReported) {
                        t->downwindReported = false;
                        int i = 1;
                        for(tower_plane_rec_list_iterator twrItr = circuitList.begin(); twrItr != circuitList.end(); twrItr++) {
                                if((*twrItr)->plane.callsign == responseID) break;
                                ++i;
-                       }                       
-                       string trns = "Number ";
+                       }
+                       string trns = t->plane.callsign;
+                       trns += " Number ";
                        trns += ConvertNumToSpokenDigits(i);
                        trns += " ";
-                       trns += t->plane.callsign;
+                       if(i == 1) {
+                               trns += "Cleared to land";
+                               t->clearedToLand = true;
+                       }
                        if(display) {
-                               globals->get_ATC_display()->RegisterSingleMessage(trns, 0);
+                               globals->get_ATC_display()->RegisterSingleMessage(trns);
                        }
-                       freqClear = true;       // FIXME - set this to come true after enough time to render the message
-                       if(t->isUser && t->opType == TTT_UNKNOWN) {
-                               t->opType = CIRCUIT;
+                       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);
+                       }
+               } else if(t->holdShortReported) {
+                       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
+                               } else {
+                                       ClearHoldingPlane(t);
+                                       t->leg = TAKEOFF_ROLL;
+                                       rwyList.push_back(t);
+                                       rwyOccupied = true;
+                                       // WARNING - WE ARE ASSUMING ONLY ONE PLANE REPORTING HOLD AT A TIME BELOW
+                                       // FIXME TODO - FIX THIS!!!
+                                       if(holdList.size()) {
+                                               if(holdListItr == holdList.end()) {
+                                                       holdListItr = holdList.begin();
+                                               }
+                                               holdList.erase(holdListItr);
+                                               holdListItr = holdList.begin();
+                                       }
+                               }
+                       } else {
+                               // Tell him to hold and what position he is.
+                               // Not currently sure under which circumstances we do or don't bother transmitting this.
+                               string trns = t->plane.callsign;
+                               trns += " hold position";
+                               if(display) {
+                                       globals->get_ATC_display()->RegisterSingleMessage(trns, 0);
+                               }
+                               // TODO - add some idea of what traffic is blocking him.
+                       }
+                       t->holdShortReported = false;
+               } else if(t->finalReported && !(t->finalAcknowledged)) {
+                       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) {
+                               if(t->landingType == FULL_STOP) {
+                                       trns += " cleared to land ";
+                               } else {
+                                       trns += " cleared for the option ";
+                               }
+                               // 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);
+                       } 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".
+                               disp = false;
+                       } else {
+                               trns += " continue approach";
+                               t->clearedToLand = false;
+                       }
+                       if(display && disp) {
+                               globals->get_ATC_display()->RegisterSingleMessage(trns);
                        }
+                       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);
+                       }
+                       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;
+}
+
+// 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.
+void FGTower::ClearHoldingPlane(TowerPlaneRec* t) {
+       //cout << "Entering ClearHoldingPlane..." << endl;
+       // Lets Roll !!!!
+       string trns = t->plane.callsign;
+       //if(departed plane < some threshold in time away) {
+       if(0) {         // FIXME
+       //if(timeSinceLastDeparture <= 60.0 && departed == true) {
+               trns += " line up";
+               t->clearedToLineUp = true;
+               t->planePtr->RegisterTransmission(3);   // cleared to line-up
+       //} else if(arriving plane < some threshold away) {
+       } else if(GetTrafficETA(2) < 150.0 && (timeSinceLastDeparture > 60.0 || departed == false)) {   // Hack - hardwired time
+               trns += " cleared immediate take-off";
+               if(trafficList.size()) {
+                       tower_plane_rec_list_iterator trfcItr = trafficList.begin();
+                       trfcItr++;      // At the moment the holding plane should be first in trafficList.
+                       // Note though that this will break if holding planes aren't put in trafficList in the future.
+                       TowerPlaneRec* trfc = *trfcItr;
+                       trns += "... traffic is";
+                       switch(trfc->plane.type) {
+                       case UNKNOWN:
+                               break;
+                       case GA_SINGLE:
+                               trns += " a Cessna";    // TODO - add ability to specify actual plane type somewhere
+                               break;
+                       case GA_HP_SINGLE:
+                               trns += " a Piper";
+                               break;
+                       case GA_TWIN:
+                               trns += " a King-air";
+                               break;
+                       case GA_JET:
+                               trns += " a Learjet";
+                               break;
+                       case MEDIUM:
+                               trns += " a Regional";
+                               break;
+                       case HEAVY:
+                               trns += " a Heavy";
+                               break;
+                       case MIL_JET:
+                               trns += " Military";
+                               break;
+                       }
+                       //if(trfc->opType == STRAIGHT_IN || trfc->opType == TTT_UNKNOWN) {
+                       if(trfc->opType == STRAIGHT_IN) {
+                               double miles_out = CalcDistOutMiles(trfc);
+                               if(miles_out < 2) {
+                                       trns += " on final";
+                               } else {
+                                       trns += " on ";
+                                       trns += ConvertNumToSpokenDigits((int)miles_out);
+                                       trns += " mile final";
+                               }
+                       } else if(trfc->opType == CIRCUIT) {
+                               //cout << "Getting leg of " << trfc->plane.callsign << '\n';
+                               switch(trfc->leg) {
+                               case FINAL:
+                                       trns += " on final";
+                                       break;
+                               case TURN4:
+                                       trns += " turning final";
+                                       break;
+                               case BASE:
+                                       trns += " on base";
+                                       break;
+                               case TURN3:
+                                       trns += " turning base";
+                                       break;
+                               case DOWNWIND:
+                                       trns += " in circuit";  // At the moment the user plane is generally flagged as unknown opType when downwind incase its a downwind departure which means we won't get here.
+                                       break;
+                               // And to eliminate compiler warnings...
+                               case TAKEOFF_ROLL: break;
+                               case CLIMBOUT:     break;
+                               case TURN1:        break;
+                               case CROSSWIND:    break;
+                               case TURN2:        break;
+                               case LANDING_ROLL: break;
+                               case LEG_UNKNOWN:  break;
+                               }
+                       }
+               } else {
+                       // By definition there should be some arriving traffic if we're cleared for immediate takeoff
+                       SG_LOG(SG_ATC, SG_WARN, "Warning: Departing traffic cleared for *immediate* take-off despite no arriving traffic in FGTower");
+               }
+               t->clearedToTakeOff = true;
+               t->planePtr->RegisterTransmission(4);   // cleared to take-off - TODO differentiate between immediate and normal take-off
+               departed = false;
+               timeSinceLastDeparture = 0.0;
+       } else {
+       //} else if(timeSinceLastDeparture > 60.0 || departed == false) {       // Hack - test for timeSinceLastDeparture should be in lineup block eventually
+               trns += " cleared for take-off";
+               // TODO - add traffic is... ?
+               t->clearedToTakeOff = true;
+               t->planePtr->RegisterTransmission(4);   // cleared to take-off
+               departed = false;
+               timeSinceLastDeparture = 0.0;
+       }
+       if(display) {
+               globals->get_ATC_display()->RegisterSingleMessage(trns, 0);
+       }
+       //cout << "Done ClearHoldingPlane " << endl;
 }
 
 // Do one plane from the hold list
 void FGTower::CheckHoldList(double dt) {
-       //cout << "ug\n";
+       //cout << "Entering CheckHoldList..." << endl;
        if(holdList.size()) {
                //cout << "*holdListItr = " << *holdListItr << endl;
                if(holdListItr == holdList.end()) {
@@ -377,197 +672,34 @@ void FGTower::CheckHoldList(double dt) {
                TowerPlaneRec* t = *holdListItr;
                //cout << "t = " << t << endl;
                if(t->holdShortReported) {
-                       //cout << "ding\n";
-                       double responseTime = 10.0;             // seconds - this should get more sophisticated at some point
-                       if(t->clearanceCounter > responseTime) {
-                               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
-                                       } else {
-                                               // Lets Roll !!!!
-                                               string trns = t->plane.callsign;
-                                               //if(departed plane < some threshold in time away) {
-                                               if(0) {         // FIXME
-                                                       trns += " line up";
-                                                       t->clearedToLineUp = true;
-                                                       t->planePtr->RegisterTransmission(3);   // cleared to line-up
-                                                       t->leg = TAKEOFF_ROLL;
-                                               //} else if(arriving plane < some threshold away) {
-                                               } else if(GetTrafficETA(2) < 150.0) {
-                                                       trns += " cleared immediate take-off";
-                                                       if(trafficList.size()) {
-                                                               tower_plane_rec_list_iterator trfcItr = trafficList.begin();
-                                                               trfcItr++;      // At the moment the holding plane should be first in trafficList.
-                                                               // Note though that this will break if holding planes aren't put in trafficList in the future.
-                                                               TowerPlaneRec* trfc = *trfcItr;
-                                                               trns += "... traffic is";
-                                                               switch(trfc->plane.type) {
-                                                               case UNKNOWN:
-                                                                       break;
-                                                               case GA_SINGLE:
-                                                                       trns += " a Cessna";    // TODO - add ability to specify actual plane type somewhere
-                                                                       break;
-                                                               case GA_HP_SINGLE:
-                                                                       trns += " a Piper";
-                                                                       break;
-                                                               case GA_TWIN:
-                                                                       trns += " a King-air";
-                                                                       break;
-                                                               case GA_JET:
-                                                                       trns += " a Learjet";
-                                                                       break;
-                                                               case MEDIUM:
-                                                                       trns += " a Regional";
-                                                                       break;
-                                                               case HEAVY:
-                                                                       trns += " a Heavy";
-                                                                       break;
-                                                               case MIL_JET:
-                                                                       trns += " Military";
-                                                                       break;
-                                                               }
-                                                               //if(trfc->opType == STRAIGHT_IN || trfc->opType == TTT_UNKNOWN) {
-                                                               if(trfc->opType == STRAIGHT_IN) {
-                                                                       double miles_out = CalcDistOutMiles(trfc);
-                                                                       if(miles_out < 2) {
-                                                                               trns += " on final";
-                                                                       } else {
-                                                                               trns += " on ";
-                                                                               trns += ConvertNumToSpokenDigits((int)miles_out);
-                                                                               trns += " mile final";
-                                                                       }
-                                                               } else if(trfc->opType == CIRCUIT) {
-                                                                       cout << "Getting leg of " << trfc->plane.callsign << '\n';
-                                                                       switch(trfc->leg) {
-                                                                       case FINAL:
-                                                                               trns += " on final";
-                                                                               break;
-                                                                       case TURN4:
-                                                                               trns += " turning final";
-                                                                               break;
-                                                                       case BASE:
-                                                                               trns += " on base";
-                                                                               break;
-                                                                       case TURN3:
-                                                                               trns += " turning base";
-                                                                               break;
-                                                                       case DOWNWIND:
-                                                                               trns += " in circuit";  // At the moment the user plane is generally flagged as unknown opType when downwind incase its a downwind departure which means we won't get here.
-                                                                               break;
-                                                                       // And to eliminate compiler warnings...
-                                                                       case TAKEOFF_ROLL: break;
-                                                                       case CLIMBOUT:     break;
-                                                                       case TURN1:        break;
-                                                                       case CROSSWIND:    break;
-                                                                       case TURN2:        break;
-                                                                       case LANDING_ROLL: break;
-                                                                       case LEG_UNKNOWN:  break;
-                                                                       }
-                                                               }
-                                                       } else {
-                                                               // By definition there should be some arriving traffic if we're cleared for immediate takeoff
-                                                               SG_LOG(SG_ATC, SG_WARN, "Warning: Departing traffic cleared for *immediate* take-off despite no arriving traffic in FGTower");
-                                                       }
-                                                       t->clearedToTakeOff = true;
-                                                       t->planePtr->RegisterTransmission(4);   // cleared to take-off - TODO differentiate between immediate and normal take-off
-                                                       t->leg = TAKEOFF_ROLL;
-                                                       departed = false;
-                                                       timeSinceLastDeparture = 0.0;
-                                               } else {
-                                                       trns += " cleared for take-off";
-                                                       // TODO - add traffic is... ?
-                                                       t->clearedToTakeOff = true;
-                                                       t->planePtr->RegisterTransmission(4);   // cleared to take-off
-                                                       t->leg = TAKEOFF_ROLL;
-                                                       departed = false;
-                                                       timeSinceLastDeparture = 0.0;
-                                               }
-                                               if(display) {
-                                                       globals->get_ATC_display()->RegisterSingleMessage(trns, 0);
-                                               }
-                                               t->holdShortReported = false;
-                                               t->clearanceCounter = 0;
-                                               rwyList.push_back(t);
-                                               rwyOccupied = true;
-                                               holdList.erase(holdListItr);
-                                               holdListItr = holdList.begin();
-                                       }
-                               } else {
-                                       // Tell him to hold and what position he is.
-                                       // Not currently sure under which circumstances we do or don't bother transmitting this.
-                                       string trns = t->plane.callsign;
-                                       trns += " hold position";
-                                       if(display) {
-                                               globals->get_ATC_display()->RegisterSingleMessage(trns, 0);
-                                       }
-                                       t->holdShortReported = false;
-                                       t->clearanceCounter = 0;
-                                       // TODO - add some idea of what traffic is blocking him.
-                               }
-                       } else {
-                               t->clearanceCounter += (dt * holdList.size() * update_count_max);
-                       }
+                       // NO-OP - leave it to the response handler.
                } else {        // not responding to report, but still need to clear if clear
-                       //cout << "dong\n";
                        if(t->nextOnRwy) {
                                //cout << "departed = " << departed << '\n';
                                //cout << "timeSinceLastDeparture = " << timeSinceLastDeparture << '\n';
                                if(rwyOccupied) {
-                                       // Do nothing for now - consider acknowloging hold short eventually
+                                       // Do nothing
                                } else if(timeSinceLastDeparture <= 60.0 && departed == true) {
                                        // Do nothing - this is a bit of a hack - should maybe do line up be ready here
                                } else {
-                                       // Lets Roll !!!!
-                                       string trns = t->plane.callsign;
-                                       //cout << "******************* squaggle\n";
-                                       //cout << "departed = " << departed << '\n';
-                                       //cout << "timeSinceLastDeparture = " << timeSinceLastDeparture << '\n';
-                                       //if(departed plane < some threshold in time away) {
-                                               if(0) {         // FIXME
-                                                       //cout << "A\n";
-                                                       trns += " line up";
-                                                       t->clearedToLineUp = true;
-                                                       t->planePtr->RegisterTransmission(3);   // cleared to line-up
-                                                       t->leg = TAKEOFF_ROLL;
-                                       //} else if(arriving plane < some threshold away) {
-                                               } else if(GetTrafficETA(2) < 150.0 && (timeSinceLastDeparture > 60.0 || departed == false)) {   // Hack - hardwired time
-                                                       //cout << "B\n";
-                                                       trns += " cleared immediate take-off";
-                                                       // TODO - add traffic is... ?
-                                                       t->clearedToTakeOff = true;
-                                                       t->planePtr->RegisterTransmission(4);   // cleared to take-off - TODO differentiate between immediate and normal take-off
-                                                       t->leg = TAKEOFF_ROLL;
-                                                       departed = false;
-                                                       timeSinceLastDeparture = 0.0;
-                                               } else if(timeSinceLastDeparture > 60.0 || departed == false) { // Hack - test for timeSinceLastDeparture should be in lineup block eventually
-                                                       //cout << "C\n";
-                                                       trns += " cleared for take-off";
-                                                       // TODO - add traffic is... ?
-                                                       t->clearedToTakeOff = true;
-                                                       t->planePtr->RegisterTransmission(4);   // cleared to take-off
-                                                       t->leg = TAKEOFF_ROLL;
-                                                       departed = false;
-                                                       timeSinceLastDeparture = 0.0;
-                                               } else {
-                                                       //cout << "D\n";
-                                               }
-                                               if(display) {
-                                                       globals->get_ATC_display()->RegisterSingleMessage(trns, 0);
-                                               }
-                                               rwyList.push_back(t);
-                                               rwyOccupied = true;
-                                               holdList.erase(holdListItr);
-                                               holdListItr = holdList.begin();
+                                       ClearHoldingPlane(t);
+                                       t->leg = TAKEOFF_ROLL;
+                                       rwyList.push_back(t);
+                                       rwyOccupied = true;
+                                       holdList.erase(holdListItr);
+                                       holdListItr = holdList.begin();
                                }
                        }
                        // TODO - rationalise the considerable code duplication above!
                }
                ++holdListItr;
        }
+       //cout << "Done CheckHoldList" << endl;
 }
 
 // do the ciruit list
 void FGTower::CheckCircuitList(double dt) {
+       //cout << "Entering CheckCircuitList..." << endl;
        // Clear the constraints - we recalculate here.
        base_leg_pos = 0.0;
        downwind_leg_pos = 0.0;
@@ -700,6 +832,7 @@ void FGTower::CheckCircuitList(double dt) {
                        case CROSSWIND:
                                crosswind_leg_pos = tortho.y();
                                //cout << "crosswind_leg_pos = " << crosswind_leg_pos << '\n';
+                               t->instructedToGoAround = false;
                                break;
                        case TURN1:
                                // Fall through to climbout
@@ -721,8 +854,26 @@ void FGTower::CheckCircuitList(double dt) {
                
                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);
+                               }
+                       }
                } else if(t->leg == LANDING_ROLL) {
                        rwyList.push_front(t);
+                       // TODO - if(!clearedToLand) shout something!!
+                       t->clearedToLand = false;
                        RemoveFromTrafficList(t->plane.callsign);
                        if(t->isUser) {
                                t->opType = TTT_UNKNOWN;
@@ -734,6 +885,7 @@ void FGTower::CheckCircuitList(double dt) {
                }
                ++circuitListItr;
        }
+       //cout << "Done CheckCircuitList" << endl;
 }
 
 // Do the runway list - we'll do the whole runway list since it's important and there'll never be many planes on the rwy at once!!
@@ -741,6 +893,7 @@ void FGTower::CheckCircuitList(double dt) {
 // (However, at the moment there should only be one airplane on the rwy at once, until we
 // start allowing planes to line up whilst previous arrival clears the rwy.)
 void FGTower::CheckRunwayList(double dt) {
+       //cout << "Entering CheckRunwayList..." << endl;
        if(rwyOccupied) {
                if(!rwyList.size()) {
                        rwyOccupied = false;
@@ -784,6 +937,7 @@ void FGTower::CheckRunwayList(double dt) {
                        }
                }
        }
+       //cout << "Done CheckRunwayList" << endl;
 }
 
 // Do one plane from the approach list
@@ -821,7 +975,7 @@ bool FGTower::GetCrosswindConstraint(double& cpos) {
        }
 }
 bool FGTower::GetDownwindConstraint(double& dpos) {
-       if(downwind_leg_pos != 0.0) {
+       if(fabs(downwind_leg_pos) > nominal_downwind_leg_pos) {
                dpos = downwind_leg_pos;
                return(true);
        } else {
@@ -830,11 +984,11 @@ bool FGTower::GetDownwindConstraint(double& dpos) {
        }
 }
 bool FGTower::GetBaseConstraint(double& bpos) {
-       if(base_leg_pos != 0.0) {
+       if(base_leg_pos < nominal_base_leg_pos) {
                bpos = base_leg_pos;
                return(true);
        } else {
-               bpos = 0.0;
+               bpos = nominal_base_leg_pos;
                return(false);
        }
 }
@@ -889,6 +1043,14 @@ void FGTower::DoRwyDetails() {
                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);
+               
+               // Set the pattern direction
+               // TODO - we'll check for a facilities file with this in eventually - for now assume left traffic except
+               // for certain circumstances (RH parallel rwy).
+               rwy.patternDirection = -1;              // Left
+               if(rwy.rwyID.size() == 3) {
+                       rwy.patternDirection = (rwy.rwyID.substr(2,1) == "R" ? 1 : -1);
+               }
        } else {
                SG_LOG(SG_ATC, SG_ALERT, "Help  - can't get good runway in FGTower!!");
                activeRwy = "NN";
@@ -967,7 +1129,7 @@ bool FGTower::RemoveFromTrafficList(string id) {
 // See the comments in FGTower::doThresholdUseOrder for notes on the ordering
 bool FGTower::AddToTrafficList(TowerPlaneRec* t, bool holding) {
        //cout << "ADD: " << trafficList.size();
-       //cout << "AddToTrafficList called, currently size = " << trafficList.size() << ", holding = " << holding << '\n';
+       //cout << "AddToTrafficList called, currently size = " << trafficList.size() << ", holding = " << holding << endl;
        double separation_time = 90.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.
        double departure_sep_time = 60.0;       // Separation time behind departing airplanes.  Comments above also apply.
        bool conflict = false;
@@ -1030,10 +1192,41 @@ bool FGTower::AddToTrafficList(TowerPlaneRec* t, bool holding) {
                // conflict and firstTime should be false and true respectively in this case anyway.
        }
        trafficList.push_back(t);
-       //cout << "\tE\t" << trafficList.size() << '\n';
+       //cout << "\tE\t" << trafficList.size() << endl;
        return(holding ? firstTime : conflict);
 }
 
+// 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.
+bool FGTower::AddToCircuitList(TowerPlaneRec* t) {
+       //cout << "ADD: " << circuitList.size();
+       //cout << "AddToCircuitList called, 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;
+       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 we get here we must be at the end of the list, or maybe the list is empty.
+       circuitList.push_back(t);       // TODO - check the separation with the preceding plane for the conflict flag.
+       //cout << "\tE\t" << circuitList.size() << endl;
+       return(conflict);
+}
+
 
 // Calculate the eta of a plane to the threshold.
 // For ground traffic this is the fastest they can get there.
@@ -1046,8 +1239,9 @@ void FGTower::CalcETA(TowerPlaneRec* tpr, bool printout) {
        double final_ias = 70.0 * 0.514444;             // Speed during final approach
        
        //if(printout) {
-       //      cout << "In CalcETA, airplane ident = " << tpr->plane.callsign << '\n';
-       //      cout << (tpr->isUser ? "USER\n" : "AI\n");
+       //cout << "In CalcETA, airplane ident = " << tpr->plane.callsign << '\n';
+       //cout << (tpr->isUser ? "USER\n" : "AI\n");
+       //cout << flush;
        //}
        
        // Sign convention - dist_out is -ve for approaching planes and +ve for departing planes
@@ -1055,7 +1249,7 @@ void FGTower::CalcETA(TowerPlaneRec* tpr, bool printout) {
        
        Point3D op = ortho.ConvertToLocal(tpr->pos);
        //if(printout) {
-       //      cout << "Orthopos is " << op.x() << ", " << op.y() << '\n';
+       //if(!tpr->isUser) cout << "Orthopos is " << op.x() << ", " << op.y() << ' ';
        //      cout << "opType is " << tpr->opType << '\n';
        //}
        double dist_out_m = op.y();
@@ -1089,6 +1283,7 @@ void FGTower::CalcETA(TowerPlaneRec* tpr, bool printout) {
                        if(!GetBaseConstraint(current_base_dist_out_m)) {
                                current_base_dist_out_m = nominal_base_dist_out_m;
                        }
+                       //cout << "current_base_dist_out_m = " << current_base_dist_out_m << '\n';
                        double nominal_dist_across_m = 1000;    // Hardwired value from AILocalTraffic
                        double current_dist_across_m;
                        if(!GetDownwindConstraint(current_dist_across_m)) {
@@ -1096,12 +1291,12 @@ void FGTower::CalcETA(TowerPlaneRec* tpr, bool printout) {
                        }
                        double nominal_cross_dist_out_m = 2000; // Bit of a guess - AI plane turns to crosswind at 600ft agl.
                        tpr->eta = fabs(current_base_dist_out_m) / final_ias;   // final
-                       //if(printout) cout << "a = " << tpr->eta << '\n';
+                       //cout << "a = " << tpr->eta << '\n';
                        if((tpr->leg == DOWNWIND) || (tpr->leg == TURN2)) {
                                tpr->eta += dist_across_m / circuit_ias;
-                               //if(printout) cout << "b = " << tpr->eta << '\n';
+                               //cout << "b = " << tpr->eta << '\n';
                                tpr->eta += fabs(current_base_dist_out_m - dist_out_m) / circuit_ias;
-                               //if(printout) cout << "c = " << tpr->eta << '\n';
+                               //cout << "c = " << tpr->eta << '\n';
                        } else if((tpr->leg == CROSSWIND) || (tpr->leg == TURN1)) {
                                if(dist_across_m > nominal_dist_across_m) {
                                        tpr->eta += dist_across_m / circuit_ias;
@@ -1130,6 +1325,7 @@ void FGTower::CalcETA(TowerPlaneRec* tpr, bool printout) {
                //if(printout) {
                //      cout << "ETA = " << tpr->eta << '\n';
                //}
+               //if(!tpr->isUser) cout << tpr->plane.callsign << '\t' << tpr->eta << '\n';
        } else {
                tpr->eta = 99999;
        }       
@@ -1152,6 +1348,7 @@ double FGTower::CalcDistOutMiles(TowerPlaneRec* tpr) {
 
 // Iterate through all the lists 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++) {
@@ -1163,6 +1360,7 @@ void FGTower::doThresholdETACalc() {
                TowerPlaneRec* tpr = *twrItr;
                CalcETA(tpr);
        }
+       //cout << "Done doThresholdETCCalc" << endl;
 }
                
 
@@ -1170,6 +1368,7 @@ void FGTower::doThresholdETACalc() {
 // that the nearest (timewise) is flagged next on rwy, and return
 // true if any threshold use conflicts are detected, false otherwise.
 bool FGTower::doThresholdUseOrder() {
+       //cout << "Entering doThresholdUseOrder..." << endl;
        bool conflict = false;
        
        // Wipe out traffic list, go through circuit, app and hold list, and reorder them in traffic list.
@@ -1186,16 +1385,19 @@ bool FGTower::doThresholdUseOrder() {
        
        tower_plane_rec_list_iterator twrItr;
        // Do the approach list first
+       //cout << "A" << flush;
        for(twrItr = appList.begin(); twrItr != appList.end(); twrItr++) {
                TowerPlaneRec* tpr = *twrItr;
                conflict = AddToTrafficList(tpr);
        }       
        // Then the circuit list
+       //cout << "C" << flush;
        for(twrItr = circuitList.begin(); twrItr != circuitList.end(); twrItr++) {
                TowerPlaneRec* tpr = *twrItr;
                conflict = AddToTrafficList(tpr);
        }
        // And finally the hold list
+       //cout << "H" << endl;
        for(twrItr = holdList.begin(); twrItr != holdList.end(); twrItr++) {
                TowerPlaneRec* tpr = *twrItr;
                AddToTrafficList(tpr, true);
@@ -1207,16 +1409,13 @@ bool FGTower::doThresholdUseOrder() {
                        TowerPlaneRec* tpr = *twrItr;
                        cout << tpr->plane.callsign << '\t' << tpr->eta << '\t';
                }
-               cout << '\n';
+               cout << endl;
        }
        
+       //cout << "Done doThresholdUseOrder" << endl;
        return(conflict);
 }
 
-/*
-void FGTower::doCommunication() {
-}
-*/
 
 // Return the ETA of plane no. list_pos (1-based) in the traffic list.
 // i.e. list_pos = 1 implies next to use runway.
@@ -1241,69 +1440,164 @@ void FGTower::ContactAtHoldShort(PlaneRec plane, FGAIPlane* requestee, tower_tra
        t->plane = plane;
        t->planePtr = requestee;
        t->holdShortReported = true;
-       t->clearanceCounter = 0;
        t->clearedToLineUp = false;
        t->clearedToTakeOff = false;
        t->opType = operation;
+       t->pos = requestee->GetPos();
        
        //cout << "Hold Short reported by " << plane.callsign << '\n';
-       
+       SG_LOG(SG_ATC, SG_BULK, "Hold Short reported by " << plane.callsign);
+
+/*     
        bool next = AddToTrafficList(t, true);
-       
        if(next) {
-               //cout << "Next to take off - respond with clearance\n";
                double teta = GetTrafficETA(2);
                if(teta < 150.0) {
                        t->clearanceCounter = 7.0;      // This reduces the delay before response to 3 secs if an immediate takeoff is reqd
                        //cout << "Reducing response time to request due imminent traffic\n";
                }
        } else {
-               //cout << "Not next to take off - respond with hold\n";
        }
+*/
+       // TODO - possibly add the reduced interval to clearance when immediate back in under the new scheme
+
+       holdList.push_back(t);
        
-       //cout << "t = " << t << '\n';
+       responseReqd = true;
+}
+
+// Register the presence of an AI plane at a point where contact would already have been made in real life
+// CAUTION - currently it is assumed that this plane's callsign is unique - it is up to AIMgr to generate unique callsigns.
+void FGTower::RegisterAIPlane(PlaneRec plane, FGAIPlane* ai, tower_traffic_type op, PatternLeg lg) {
+       // At the moment this is only going to be tested with inserting an AI plane on downwind
+       TowerPlaneRec* t = new TowerPlaneRec;
+       t->plane = plane;
+       t->planePtr = ai;
+       t->opType = op;
+       t->leg = lg;
+       t->pos = ai->GetPos();
        
-       holdList.push_back(t);
+       CalcETA(t);
+       
+       if(op == CIRCUIT && lg != LEG_UNKNOWN) {
+               AddToCircuitList(t);
+       } else {
+               // FLAG A WARNING
+       }
+       
+       doThresholdUseOrder();
 }
 
-void FGTower::RequestLandingClearance(string 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";
        
-       // Assume this comes from the user - have another function taking a pointer to the AIplane for the AI traffic.
-       // For now we'll also assume that the user is a light plane and can get him/her to join the circuit if necessary.
-       
-       TowerPlaneRec* t = new TowerPlaneRec;
-       t->isUser = true;
-       t->clearedToLand = false;
-       t->pos.setlon(user_lon_node->getDoubleValue());
-       t->pos.setlat(user_lat_node->getDoubleValue());
-       t->pos.setelev(user_elev_node->getDoubleValue());
+       // For now we'll assume that the user is a light plane and can get him/her to join the circuit if necessary.
+
+       TowerPlaneRec* t;       
+       string usercall = fgGetString("/sim/user/callsign");
+       if(ID == "USER" || ID == usercall) {
+               t = FindPlane(usercall);
+               if(!t) {
+                       //cout << "NOT t\n";
+                       t = new TowerPlaneRec;
+                       t->isUser = true;
+                       t->pos.setlon(user_lon_node->getDoubleValue());
+                       t->pos.setlat(user_lat_node->getDoubleValue());
+                       t->pos.setelev(user_elev_node->getDoubleValue());
+               } else {
+                       //cout << "IS t\n";
+                       // Oops - the plane is already registered with this tower - maybe we took off and flew a giant circuit without
+                       // quite getting out of tower airspace - just ignore for now and treat as new arrival.
+                       // TODO - Maybe should remove from departure and circuit list if in there though!!
+               }
+       } else {
+               // Oops - something has gone wrong - put out a warning
+               cout << "WARNING - FGTower::VFRContact(string ID, LandingType lt) called with ID " << ID << " which does not appear to be the user.\n";
+               return;
+       }
+               
        
        // TODO
-       // Calculate where the user is in relation to the active runway and it's circuit
+       // Calculate where the plane is in relation to the active runway and it's circuit
        // and set the op-type as appropriate.
        
        // HACK - to get up and running I'm going to assume that the user contacts tower on a staight-in final for now.
        t->opType = STRAIGHT_IN;
        
        t->plane.type = GA_SINGLE;      // FIXME - Another assumption!
-       t->plane.callsign = ID;
+       t->plane.callsign = usercall;
+       
+       t->vfrArrivalReported = true;
+       responseReqd = true;
        
        appList.push_back(t);   // Not necessarily permanent
        AddToTrafficList(t);
+       
+       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);
 }
 
 void FGTower::RequestDepartureClearance(string ID) {
        //cout << "Request Departure Clearance called...\n";
-}      
-//void FGTower::ReportFinal(string ID);
-//void FGTower::ReportLongFinal(string ID);
+}
+       
+void FGTower::ReportFinal(string ID) {
+       if(ID == "USER") {
+               ID = fgGetString("/sim/user/callsign");
+               current_atcdialog->remove_entry(ident, USER_REPORT_3_MILE_FINAL, TOWER);
+       }
+       TowerPlaneRec* t = FindPlane(ID);
+       if(t) {
+               t->finalReported = true;
+               t->finalAcknowledged = false;
+               if(!(t->clearedToLand)) {
+                       responseReqd = true;
+               } // possibly respond with wind even if already cleared to land?
+       } else {
+               SG_LOG(SG_ATC, SG_WARN, "WARNING: Unable to find plane " << ID << " in FGTower::ReportFinal(...)");
+       }
+}
+
+void FGTower::ReportLongFinal(string ID) {
+       if(ID == "USER") {
+               ID = fgGetString("/sim/user/callsign");
+               current_atcdialog->remove_entry(ident, USER_REPORT_3_MILE_FINAL, TOWER);
+       }
+       TowerPlaneRec* t = FindPlane(ID);
+       if(t) {
+               t->longFinalReported = true;
+               t->longFinalAcknowledged = false;
+               if(!(t->clearedToLand)) {
+                       responseReqd = true;
+               } // possibly respond with wind even if already cleared to land?
+       } else {
+               SG_LOG(SG_ATC, SG_WARN, "WARNING: Unable to find plane " << ID << " in FGTower::ReportLongFinal(...)");
+       }
+}
+
 //void FGTower::ReportOuterMarker(string ID);
 //void FGTower::ReportMiddleMarker(string ID);
 //void FGTower::ReportInnerMarker(string ID);
 //void FGTower::ReportGoingAround(string ID);
+
 void FGTower::ReportRunwayVacated(string ID) {
        //cout << "Report Runway Vacated Called...\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) {
+               t->rwyVacatedReported = true;
+               responseReqd = true;
+       } else {
+               SG_LOG(SG_ATC, SG_WARN, "WARNING: Unable to find plane " << ID << " in FGTower::ReportRunwayVacated(...)");
+       }
 }
 
 TowerPlaneRec* FGTower::FindPlane(string ID) {
@@ -1326,7 +1620,10 @@ TowerPlaneRec* FGTower::FindPlane(string ID) {
 
 void FGTower::ReportDownwind(string ID) {
        //cout << "ReportDownwind(...) called\n";
-       // Tell the plane reporting what number she is in the circuit
+       if(ID == "USER") {
+               ID = fgGetString("/sim/user/callsign");
+               current_atcdialog->remove_entry(ident, USER_REPORT_DOWNWIND, TOWER);
+       }
        TowerPlaneRec* t = FindPlane(ID);
        if(t) {
                t->downwindReported = true;
@@ -1336,6 +1633,159 @@ void FGTower::ReportDownwind(string ID) {
        }
 }
 
+string FGTower::GenText(const string& m, int c) {
+       const int cmax = 300;
+       //string message;
+       char tag[4];
+       char crej = '@';
+       char mes[cmax];
+       char dum[cmax];
+       //char buf[10];
+       char *pos;
+       int len;
+       //FGTransmission t;
+       string usercall = fgGetString("/sim/user/callsign");
+       
+       //transmission_list_type     tmissions = transmissionlist_station[station];
+       //transmission_list_iterator current   = tmissions.begin();
+       //transmission_list_iterator last      = tmissions.end();
+       
+       //for ( ; current != last ; ++current ) {
+       //      if ( current->get_code().c1 == code.c1 &&  
+       //              current->get_code().c2 == code.c2 &&
+       //          current->get_code().c3 == code.c3 ) {
+                       
+                       //if ( ttext ) message = current->get_transtext();
+                       //else message = current->get_menutext();
+                       strcpy( &mes[0], m.c_str() ); 
+                       
+                       // Replace all the '@' parameters with the actual text.
+                       int check = 0;  // If mes gets overflowed the while loop can go infinite
+                       while ( strchr(&mes[0], crej) != NULL  ) {      // ie. loop until no more occurances of crej ('@') found
+                               pos = strchr( &mes[0], crej );
+                               bcopy(pos, &tag[0], 3);
+                               tag[3] = '\0';
+                               int i;
+                               len = 0;
+                               for ( i=0; i<cmax; i++ ) {
+                                       if ( mes[i] == crej ) {
+                                               len = i; 
+                                               break;
+                                       }
+                               }
+                               strncpy( &dum[0], &mes[0], len );
+                               dum[len] = '\0';
+                               
+                               if ( strcmp ( tag, "@ST" ) == 0 )
+                                       //strcat( &dum[0], tpars.station.c_str() );
+                                       strcat(&dum[0], ident.c_str());
+                               else if ( strcmp ( tag, "@AP" ) == 0 )
+                                       //strcat( &dum[0], tpars.airport.c_str() );
+                                       strcat(&dum[0], name.c_str());
+                               else if ( strcmp ( tag, "@CS" ) == 0 ) 
+                                       //strcat( &dum[0], tpars.callsign.c_str() );
+                                       strcat(&dum[0], usercall.c_str());
+                               else if ( strcmp ( tag, "@TD" ) == 0 ) {
+                                       /*
+                                       if ( tpars.tdir == 1 ) {
+                                               char buf[] = "left";
+                                               strcat( &dum[0], &buf[0] );
+                                       }
+                                       else {
+                                               char buf[] = "right";
+                                               strcat( &dum[0], &buf[0] );
+                                       }
+                                       */
+                               }
+                               else if ( strcmp ( tag, "@HE" ) == 0 ) {
+                                       /*
+                                       char buf[10];
+                                       sprintf( buf, "%i", (int)(tpars.heading) );
+                                       strcat( &dum[0], &buf[0] );
+                                       */
+                               }
+                               else if ( strcmp ( tag, "@VD" ) == 0 ) {
+                                       /*
+                                       if ( tpars.VDir == 1 ) {
+                                               char buf[] = "Descend and maintain";
+                                               strcat( &dum[0], &buf[0] );
+                                       }
+                                       else if ( tpars.VDir == 2 ) {
+                                               char buf[] = "Maintain";
+                                               strcat( &dum[0], &buf[0] );
+                                       }
+                                       else if ( tpars.VDir == 3 ) {
+                                               char buf[] = "Climb and maintain";
+                                               strcat( &dum[0], &buf[0] );
+                                       } 
+                                       */
+                               }
+                               else if ( strcmp ( tag, "@AL" ) == 0 ) {
+                                       /*
+                                       char buf[10];
+                                       sprintf( buf, "%i", (int)(tpars.alt) );
+                                       strcat( &dum[0], &buf[0] );
+                                       */
+                               }
+                               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;
+                                       sprintf(buf, "%i", dist_miles);
+                                       strcat( &dum[0], &buf[0] );
+                               }
+                               else if ( strcmp ( tag, "@FR" ) == 0 ) {
+                                       /*
+                                       char buf[10];
+                                       sprintf( buf, "%6.2f", tpars.freq );
+                                       strcat( &dum[0], &buf[0] );
+                                       */
+                               }
+                               else if ( strcmp ( tag, "@RW" ) == 0 ) {
+                                       strcat(&dum[0], ConvertRwyNumToSpokenString(activeRwy).c_str());
+                               } else if(strcmp(tag, "@CD") == 0) {    // @CD = compass direction
+                                       double h = GetHeadingFromTo(Point3D(lon, lat, elev), Point3D(user_lon_node->getDoubleValue(), user_lat_node->getDoubleValue(), user_elev_node->getDoubleValue()));
+                                       while(h < 0.0) h += 360.0;
+                                       while(h > 360.0) h -= 360.0;
+                                       if(h < 22.5 || h > 337.5) {
+                                               strcat(&dum[0], "North");
+                                       } else if(h < 67.5) {
+                                               strcat(&dum[0], "North-East");
+                                       } else if(h < 112.5) {
+                                               strcat(&dum[0], "East");
+                                       } else if(h < 157.5) {
+                                               strcat(&dum[0], "South-East");
+                                       } else if(h < 202.5) {
+                                               strcat(&dum[0], "South");
+                                       } else if(h < 247.5) {
+                                               strcat(&dum[0], "South-West");
+                                       } else if(h < 292.5) {
+                                               strcat(&dum[0], "West");
+                                       } else {
+                                               strcat(&dum[0], "North-West");
+                                       }
+                               } else {
+                                       cout << "Tag " << tag << " not found" << endl;
+                                       break;
+                               }
+                               strcat( &dum[0], &mes[len+3] );
+                               strcpy( &mes[0], &dum[0] );
+                               
+                               ++check;
+                               if(check > 10) {
+                                       SG_LOG(SG_ATC, SG_WARN, "WARNING: Possibly endless loop terminated in FGTransmissionlist::gen_text(...)"); 
+                                       break;
+                               }
+                       }
+                       
+                       //cout << mes  << endl;  
+                       //break;
+               //}
+       //}
+       if ( mes != "" ) return mes;
+       else return "No transmission found";
+}
+
 ostream& operator << (ostream& os, tower_traffic_type ttt) {
        switch(ttt) {
        case(CIRCUIT):      return(os << "CIRCUIT");