]> git.mxchange.org Git - flightgear.git/blob - src/ATC/tower.cxx
Add Manuel Bessler and Stephen Lowry's remote joystick code and add David Magginson...
[flightgear.git] / src / ATC / tower.cxx
1 // FGTower - a class to provide tower control at towered airports.
2 //
3 // Written by David Luff, started March 2002.
4 //
5 // Copyright (C) 2002  David C. Luff - david.luff@nottingham.ac.uk
6 //
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License as
9 // published by the Free Software Foundation; either version 2 of the
10 // License, or (at your option) any later version.
11 //
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 // General Public License for more details.
16 //
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software
19 // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20
21 #include <strings.h>            // bcopy()
22
23 #include <Main/globals.hxx>
24 #include <Airports/runways.hxx>
25 #include <simgear/math/sg_geodesy.hxx>
26 #include <simgear/debug/logstream.hxx>
27
28 #include "tower.hxx"
29 #include "ATCdisplay.hxx"
30 #include "ATCmgr.hxx"
31 #include "ATCutils.hxx"
32 #include "ATCDialog.hxx"
33 #include "commlist.hxx"
34 #include "AILocalTraffic.hxx"
35
36 SG_USING_STD(cout);
37
38 // TowerPlaneRec
39
40 TowerPlaneRec::TowerPlaneRec() :
41         clearedToLand(false),
42         clearedToLineUp(false),
43         clearedToTakeOff(false),
44         holdShortReported(false),
45         downwindReported(false),
46         longFinalReported(false),
47         longFinalAcknowledged(false),
48         finalReported(false),
49         finalAcknowledged(false),
50         rwyVacatedReported(false),
51         rwyVacatedAcknowledged(false),
52         instructedToGoAround(false),
53         onRwy(false),
54         nextOnRwy(false),
55         vfrArrivalReported(false),
56         vfrArrivalAcknowledged(false),
57         opType(TTT_UNKNOWN),
58         leg(LEG_UNKNOWN),
59         landingType(AIP_LT_UNKNOWN),
60         isUser(false) 
61 {
62         plane.callsign = "UNKNOWN";
63 }
64
65 TowerPlaneRec::TowerPlaneRec(PlaneRec p) :
66         clearedToLand(false),
67         clearedToLineUp(false),
68         clearedToTakeOff(false),
69         holdShortReported(false),
70         downwindReported(false),
71         longFinalReported(false),
72         longFinalAcknowledged(false),
73         finalReported(false),
74         finalAcknowledged(false),
75         rwyVacatedReported(false),
76         rwyVacatedAcknowledged(false),
77         instructedToGoAround(false),
78         onRwy(false),
79         nextOnRwy(false),
80         vfrArrivalReported(false),
81         vfrArrivalAcknowledged(false),
82         opType(TTT_UNKNOWN),
83         leg(LEG_UNKNOWN),
84         landingType(AIP_LT_UNKNOWN),
85         isUser(false)
86 {
87         plane = p;
88 }
89
90 TowerPlaneRec::TowerPlaneRec(Point3D pt) :
91         clearedToLand(false),
92         clearedToLineUp(false),
93         clearedToTakeOff(false),
94         holdShortReported(false),
95         downwindReported(false),
96         longFinalReported(false),
97         longFinalAcknowledged(false),
98         finalReported(false),
99         finalAcknowledged(false),
100         rwyVacatedReported(false),
101         rwyVacatedAcknowledged(false),
102         instructedToGoAround(false),
103         onRwy(false),
104         nextOnRwy(false),
105         vfrArrivalReported(false),
106         vfrArrivalAcknowledged(false),
107         opType(TTT_UNKNOWN),
108         leg(LEG_UNKNOWN),
109         landingType(AIP_LT_UNKNOWN),
110         isUser(false)
111 {
112         plane.callsign = "UNKNOWN";
113         pos = pt;
114 }
115
116 TowerPlaneRec::TowerPlaneRec(PlaneRec p, Point3D pt) :
117         clearedToLand(false),
118         clearedToLineUp(false),
119         clearedToTakeOff(false),
120         holdShortReported(false),
121         downwindReported(false),
122         longFinalReported(false),
123         longFinalAcknowledged(false),
124         finalReported(false),
125         finalAcknowledged(false),
126         rwyVacatedReported(false),
127         rwyVacatedAcknowledged(false),
128         instructedToGoAround(false),
129         onRwy(false),
130         nextOnRwy(false),
131         vfrArrivalReported(false),
132         vfrArrivalAcknowledged(false),
133         opType(TTT_UNKNOWN),
134         leg(LEG_UNKNOWN),
135         landingType(AIP_LT_UNKNOWN),
136         isUser(false)
137 {
138         plane = p;
139         pos = pt;
140 }
141
142
143 // FGTower
144
145 /*******************************************
146                TODO List
147                            
148 Currently user is assumed to have taken off again when leaving the runway - check speed/elev for taxiing-in.
149
150 Tell AI plane to contact ground when taxiing in.
151
152 Use track instead of heading to determine what leg of the circuit the user is flying.
153
154 Use altitude as well as position to try to determine if the user has left the circuit.
155
156 Currently HoldShortReported code assumes there will be only one plane holding for the runway at once and 
157 will break when planes start queueing.
158
159 Implement ReportRunwayVacated
160 *******************************************/
161
162 FGTower::FGTower() {
163         ATCmgr = globals->get_ATC_mgr();
164         
165         // Init the property nodes - TODO - need to make sure we're getting surface winds.
166         wind_from_hdg = fgGetNode("/environment/wind-from-heading-deg", true);
167         wind_speed_knots = fgGetNode("/environment/wind-speed-kt", true);
168         
169         update_count = 0;
170         update_count_max = 15;
171         
172         holdListItr = holdList.begin();
173         appListItr = appList.begin();
174         depListItr = depList.begin();
175         rwyListItr = rwyList.begin();
176         circuitListItr = circuitList.begin();
177         trafficListItr = trafficList.begin();
178         
179         freqClear = true;
180         
181         timeSinceLastDeparture = 9999;
182         departed = false;
183         
184         nominal_downwind_leg_pos = 1000.0;
185         nominal_base_leg_pos = -1000.0;
186         // TODO - set nominal crosswind leg pos based on minimum distance from takeoff end of rwy.
187 }
188
189 FGTower::~FGTower() {
190         if(!separateGround) {
191                 delete ground;
192         }
193 }
194
195 void FGTower::Init() {
196     display = false;
197         
198         // Pointers to user's position
199         user_lon_node = fgGetNode("/position/longitude-deg", true);
200         user_lat_node = fgGetNode("/position/latitude-deg", true);
201         user_elev_node = fgGetNode("/position/altitude-ft", true);
202         user_hdg_node = fgGetNode("/orientation/heading-deg", true);
203         
204         // Need some way to initialise rwyOccupied flag correctly if the user is on the runway and to know its the user.
205         // I'll punt the startup issue for now though!!!
206         rwyOccupied = false;
207         
208         // Setup the ground control at this airport
209         AirportATC a;
210         //cout << "Tower ident = " << ident << '\n';
211         if(ATCmgr->GetAirportATCDetails(ident, &a)) {
212                 if(a.ground_freq) {             // Ground control
213                         ground = (FGGround*)ATCmgr->GetATCPointer(ident, GROUND);
214                         separateGround = true;
215                         if(ground == NULL) {
216                                 // Something has gone wrong :-(
217                                 SG_LOG(SG_ATC, SG_WARN, "ERROR - ground has frequency but can't get ground pointer :-(");
218                                 ground = new FGGround(ident);
219                                 separateGround = false;
220                                 ground->Init();
221                                 if(display) {
222                                         ground->SetDisplay();
223                                 } else {
224                                         ground->SetNoDisplay();
225                                 }
226                         }
227                 } else {
228                         // Initialise ground anyway to do the shortest path stuff!
229                         // Note that we're now responsible for updating and deleting this - NOT the ATCMgr.
230                         ground = new FGGround(ident);
231                         separateGround = false;
232                         ground->Init();
233                         if(display) {
234                                 ground->SetDisplay();
235                         } else {
236                                 ground->SetNoDisplay();
237                         }
238                 }
239         } else {
240                 SG_LOG(SG_ATC, SG_ALERT, "Unable to find airport details for " << ident << " in FGTower::Init()");
241                 // Initialise ground anyway to avoid segfault later
242                 ground = new FGGround(ident);
243                 separateGround = false;
244                 ground->Init();
245                 if(display) {
246                         ground->SetDisplay();
247                 } else {
248                         ground->SetNoDisplay();
249                 }
250         }
251         
252         // Get the airport elevation
253         aptElev = dclGetAirportElev(ident.c_str()) * SG_FEET_TO_METER;
254         
255         DoRwyDetails();
256         
257         // FIXME - this currently assumes use of the active rwy by the user.
258         rwyOccupied = OnAnyRunway(Point3D(user_lon_node->getDoubleValue(), user_lat_node->getDoubleValue(), 0.0));
259         if(rwyOccupied) {
260                 // Assume the user is started at the threshold ready to take-off
261                 TowerPlaneRec* t = new TowerPlaneRec;
262                 t->plane.callsign = fgGetString("/sim/user/callsign");
263                 t->plane.type = GA_SINGLE;      // FIXME - hardwired!!
264                 t->opType = TTT_UNKNOWN;        // We don't know if the user wants to do circuits or a departure...
265                 t->landingType = AIP_LT_UNKNOWN;
266                 t->leg = TAKEOFF_ROLL;
267                 t->isUser = true;
268                 t->planePtr = NULL;
269                 t->clearedToTakeOff = true;
270                 rwyList.push_back(t);
271                 departed = false;
272         } else {
273                 // For now assume that this means the user is not at the airport and is in the air.
274                 // TODO FIXME - this will break when user starts on apron, at hold short, etc.
275                 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);
276         }
277 }
278
279 void FGTower::Update(double dt) {
280         //cout << "T" << endl;
281         // Each time step, what do we need to do?
282         // We need to go through the list of outstanding requests and acknowedgements
283         // and process at least one of them.
284         // We need to go through the list of planes under our control and check if
285         // any need to be addressed.
286         // We need to check for planes not under our control coming within our 
287         // control area and address if necessary.
288         
289         // TODO - a lot of the below probably doesn't need to be called every frame and should be staggered.
290         
291         // Sort the arriving planes
292         
293         /*
294         if(ident == "KEMT") {
295                 cout << update_count << "\ttL: " << trafficList.size() << "  cL: " << circuitList.size() << "  hL: " << holdList.size() << "  aL: " << appList.size() << '\n';
296         }
297         */
298         //if(ident == "EGNX") cout << display << '\n';
299         
300         if(departed != false) {
301                 timeSinceLastDeparture += dt;
302                 //if(ident == "KEMT") 
303                 //      cout << "  dt = " << dt << "  timeSinceLastDeparture = " << timeSinceLastDeparture << '\n';
304         }
305         
306         //cout << ident << " respond = " << respond << " responseReqd = " << responseReqd << '\n'; 
307         if(respond) {
308                 if(!responseReqd) SG_LOG(SG_ATC, SG_ALERT, "ERROR - respond is true and responseReqd is false in FGTower::Update(...)");
309                 Respond();
310                 respond = false;
311                 responseReqd = false;
312         }
313         
314         // Calculate the eta of each plane to the threshold.
315         // For ground traffic this is the fastest they can get there.
316         // For air traffic this is the middle approximation.
317         if(update_count == 1) {
318                 doThresholdETACalc();
319         }
320         
321         // Order the list of traffic as per expected threshold use and flag any conflicts
322         if(update_count == 2) {
323                 //bool conflicts = doThresholdUseOrder();
324                 doThresholdUseOrder();
325         }
326         
327         // sortConficts() !!!
328         
329         if(update_count == 4) {
330                 CheckHoldList(dt);
331         }
332         
333         // Uggh - HACK - why have we got rwyOccupied - wouldn't simply testing rwyList.size() do?
334         if(rwyList.size()) {
335                 rwyOccupied = true;
336         } else {
337                 rwyOccupied = false;
338         }
339         
340         if(update_count == 5 && rwyOccupied) {
341                 CheckRunwayList(dt);
342         }
343                 
344         if(update_count == 6) {
345                 CheckCircuitList(dt);
346         }
347         
348         if(update_count == 7) {
349                 CheckApproachList(dt);
350         }
351         
352         // TODO - do one plane from the departure list and set departed = false when out of consideration
353         
354         //doCommunication();
355         
356         if(!separateGround) {
357                 // The display stuff might have to get more clever than this when not separate 
358                 // since the tower and ground might try communicating simultaneously even though
359                 // they're mean't to be the same contoller/frequency!!
360                 if(display) {
361                         ground->SetDisplay();
362                 } else {
363                         ground->SetNoDisplay();
364                 }
365                 ground->Update(dt);
366         }
367         
368         ++update_count;
369         // How big should ii get - ie how long should the update cycle interval stretch?
370         if(update_count >= update_count_max) {
371                 update_count = 0;
372         }
373         
374         // Call the base class update for the response time handling.
375         FGATC::Update(dt);
376
377         if(ident == "KEMT") {   
378                 // For AI debugging convienience - may be removed
379                 Point3D user_pos;
380                 user_pos.setlon(user_lon_node->getDoubleValue());
381                 user_pos.setlat(user_lat_node->getDoubleValue());
382                 user_pos.setelev(user_elev_node->getDoubleValue());
383                 Point3D user_ortho_pos = ortho.ConvertToLocal(user_pos);
384                 fgSetDouble("/AI/user/ortho-x", user_ortho_pos.x());
385                 fgSetDouble("/AI/user/ortho-y", user_ortho_pos.y());
386                 fgSetDouble("/AI/user/elev", user_elev_node->getDoubleValue());
387         }
388         
389         //cout << "Done T" << endl;
390 }
391
392 void FGTower::ReceiveUserCallback(int code) {
393         if(code == (int)USER_REQUEST_VFR_DEPARTURE) {
394                 cout << "User requested departure\n";
395         } else if(code == (int)USER_REQUEST_VFR_ARRIVAL) {
396                 VFRArrivalContact("USER");
397         } else if(code == (int)USER_REQUEST_VFR_ARRIVAL_FULL_STOP) {
398                 VFRArrivalContact("USER", FULL_STOP);
399         } else if(code == (int)USER_REQUEST_VFR_ARRIVAL_TOUCH_AND_GO) {
400                 VFRArrivalContact("USER", TOUCH_AND_GO);
401         } else if(code == (int)USER_REPORT_DOWNWIND) {
402                 ReportDownwind("USER");
403         } else if(code == (int)USER_REPORT_3_MILE_FINAL) {
404                 // For now we'll just call report final instead of report long final to avoid having to alter the response code
405                 ReportFinal("USER");
406         } else if(code == (int)USER_REPORT_RWY_VACATED) {
407                 ReportRunwayVacated("USER");
408         }
409 }
410
411 void FGTower::Respond() {
412         cout << "Entering Respond, responseID = " << responseID << endl;
413         TowerPlaneRec* t = FindPlane(responseID);
414         if(t) {
415                 // This will grow!!!
416                 if(t->vfrArrivalReported && !t->vfrArrivalAcknowledged) {
417                         // Testing - hardwire straight in for now
418                         string trns = t->plane.callsign;
419                         trns += " ";
420                         trns += name;
421                         trns += " Tower";
422                         // Should we clear staight in or for downwind entry?
423                         // For now we'll clear straight in if greater than 1km from a line drawn through the threshold perpendicular to the rwy.
424                         // Later on we might check the actual heading and direct some of those to enter on downwind or base.
425                         Point3D op = ortho.ConvertToLocal(t->pos);
426                         if(op.y() < -1000) {
427                                 trns += " Report three mile straight-in runway ";
428                                 current_atcdialog->add_entry(ident, "@AP Tower @CS @MI mile final Runway @RW", "Report Final", TOWER, (int)USER_REPORT_3_MILE_FINAL);
429                         } else {
430                                 // For now we'll just request reporting downwind.
431                                 // TODO - In real life something like 'report 2 miles southwest right downwind rwy 19R' might be used
432                                 // but I'm not sure how to handle all permutations of which direction to tell to report from yet.
433                                 trns += " Report ";
434                                 trns += (rwy.patternDirection ? "right " : "left ");
435                                 trns += "downwind runway ";
436                                 current_atcdialog->add_entry(ident, "@AP Tower @CS Downwind @RW", "Report Downwind", TOWER, (int)USER_REPORT_DOWNWIND);
437                         }
438                         trns += ConvertRwyNumToSpokenString(activeRwy);
439                         if(display) {
440                                 globals->get_ATC_display()->RegisterSingleMessage(trns, 0);
441                         } else {
442                                 cout << "Not displaying, trns was " << trns << '\n';
443                         }
444                         t->vfrArrivalAcknowledged = true;
445                 } else if(t->downwindReported) {
446                         t->downwindReported = false;
447                         int i = 1;
448                         for(tower_plane_rec_list_iterator twrItr = circuitList.begin(); twrItr != circuitList.end(); twrItr++) {
449                                 if((*twrItr)->plane.callsign == responseID) break;
450                                 ++i;
451                         }
452                         string trns = t->plane.callsign;
453                         trns += " Number ";
454                         trns += ConvertNumToSpokenDigits(i);
455                         trns += " ";
456                         if(i == 1) {
457                                 trns += "Cleared to land";
458                                 t->clearedToLand = true;
459                         }
460                         if(display) {
461                                 globals->get_ATC_display()->RegisterSingleMessage(trns);
462                         }
463                         if(t->isUser) {
464                                 if(t->opType == TTT_UNKNOWN) t->opType = CIRCUIT;
465                                 current_atcdialog->add_entry(ident, "@CS Clear of the runway", "Report runway vacated", TOWER, USER_REPORT_RWY_VACATED);
466                         }
467                 } else if(t->holdShortReported) {
468                         if(t->nextOnRwy) {
469                                 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!!)
470                                         // Do nothing for now - consider acknowloging hold short eventually
471                                 } else {
472                                         ClearHoldingPlane(t);
473                                         t->leg = TAKEOFF_ROLL;
474                                         rwyList.push_back(t);
475                                         rwyOccupied = true;
476                                         // WARNING - WE ARE ASSUMING ONLY ONE PLANE REPORTING HOLD AT A TIME BELOW
477                                         // FIXME TODO - FIX THIS!!!
478                                         if(holdList.size()) {
479                                                 if(holdListItr == holdList.end()) {
480                                                         holdListItr = holdList.begin();
481                                                 }
482                                                 holdList.erase(holdListItr);
483                                                 holdListItr = holdList.begin();
484                                         }
485                                 }
486                         } else {
487                                 // Tell him to hold and what position he is.
488                                 // Not currently sure under which circumstances we do or don't bother transmitting this.
489                                 string trns = t->plane.callsign;
490                                 trns += " hold position";
491                                 if(display) {
492                                         globals->get_ATC_display()->RegisterSingleMessage(trns, 0);
493                                 }
494                                 // TODO - add some idea of what traffic is blocking him.
495                         }
496                         t->holdShortReported = false;
497                 } else if(t->finalReported && !(t->finalAcknowledged)) {
498                         bool disp = true;
499                         string trns = t->plane.callsign;
500                         cout << (t->nextOnRwy ? "Next on rwy " : "Not next!! ");
501                         cout << (rwyOccupied ? "RWY OCCUPIED!!\n" : "Rwy not occupied\n");
502                         if(t->nextOnRwy && !rwyOccupied) {
503                                 if(t->landingType == FULL_STOP) {
504                                         trns += " cleared to land ";
505                                 } else {
506                                         trns += " cleared for the option ";
507                                 }
508                                 // TODO - add winds
509                                 t->clearedToLand = true;
510                                 if(t->isUser) current_atcdialog->add_entry(ident, "@CS Clear of the runway", "Report runway vacated", TOWER, USER_REPORT_RWY_VACATED);
511                         } else if(t->eta < 20) {
512                                 // Do nothing - we'll be telling it to go around in less than 10 seconds if the
513                                 // runway doesn't clear so no point in calling "continue approach".
514                                 disp = false;
515                         } else {
516                                 trns += " continue approach";
517                                 t->clearedToLand = false;
518                         }
519                         if(display && disp) {
520                                 globals->get_ATC_display()->RegisterSingleMessage(trns);
521                         }
522                         t->finalAcknowledged = true;
523                 } else if(t->rwyVacatedReported && !(t->rwyVacatedAcknowledged)) {
524                         string trns = t->plane.callsign;
525                         if(separateGround) {
526                                 trns += " Contact ground on ";
527                                 double f = globals->get_ATC_mgr()->GetFrequency(ident, GROUND) / 100.0; 
528                                 char buf[10];
529                                 sprintf(buf, "%.2f", f);
530                                 trns += buf;
531                                 trns += " Good Day";
532                         } else {
533                                 // Cop-out!!
534                                 trns += " cleared for taxi to the GA parking";
535                         }
536                         if(display) {
537                                 globals->get_ATC_display()->RegisterSingleMessage(trns);
538                         }
539                         t->rwyVacatedAcknowledged = true;
540                         // Maybe we should check that the plane really *has* vacated the runway!
541                 }
542         }
543         freqClear = true;       // FIXME - set this to come true after enough time to render the message
544         //cout << "Done Respond" << endl;
545 }
546
547 // Currently this assumes we *are* next on the runway and doesn't check for planes about to land - 
548 // this should be done prior to calling this function.
549 void FGTower::ClearHoldingPlane(TowerPlaneRec* t) {
550         //cout << "Entering ClearHoldingPlane..." << endl;
551         // Lets Roll !!!!
552         string trns = t->plane.callsign;
553         //if(departed plane < some threshold in time away) {
554         if(0) {         // FIXME
555         //if(timeSinceLastDeparture <= 60.0 && departed == true) {
556                 trns += " line up";
557                 t->clearedToLineUp = true;
558                 t->planePtr->RegisterTransmission(3);   // cleared to line-up
559         //} else if(arriving plane < some threshold away) {
560         } else if(GetTrafficETA(2) < 150.0 && (timeSinceLastDeparture > 60.0 || departed == false)) {   // Hack - hardwired time
561                 trns += " cleared immediate take-off";
562                 if(trafficList.size()) {
563                         tower_plane_rec_list_iterator trfcItr = trafficList.begin();
564                         trfcItr++;      // At the moment the holding plane should be first in trafficList.
565                         // Note though that this will break if holding planes aren't put in trafficList in the future.
566                         TowerPlaneRec* trfc = *trfcItr;
567                         trns += "... traffic is";
568                         switch(trfc->plane.type) {
569                         case UNKNOWN:
570                                 break;
571                         case GA_SINGLE:
572                                 trns += " a Cessna";    // TODO - add ability to specify actual plane type somewhere
573                                 break;
574                         case GA_HP_SINGLE:
575                                 trns += " a Piper";
576                                 break;
577                         case GA_TWIN:
578                                 trns += " a King-air";
579                                 break;
580                         case GA_JET:
581                                 trns += " a Learjet";
582                                 break;
583                         case MEDIUM:
584                                 trns += " a Regional";
585                                 break;
586                         case HEAVY:
587                                 trns += " a Heavy";
588                                 break;
589                         case MIL_JET:
590                                 trns += " Military";
591                                 break;
592                         }
593                         //if(trfc->opType == STRAIGHT_IN || trfc->opType == TTT_UNKNOWN) {
594                         if(trfc->opType == STRAIGHT_IN) {
595                                 double miles_out = CalcDistOutMiles(trfc);
596                                 if(miles_out < 2) {
597                                         trns += " on final";
598                                 } else {
599                                         trns += " on ";
600                                         trns += ConvertNumToSpokenDigits((int)miles_out);
601                                         trns += " mile final";
602                                 }
603                         } else if(trfc->opType == CIRCUIT) {
604                                 //cout << "Getting leg of " << trfc->plane.callsign << '\n';
605                                 switch(trfc->leg) {
606                                 case FINAL:
607                                         trns += " on final";
608                                         break;
609                                 case TURN4:
610                                         trns += " turning final";
611                                         break;
612                                 case BASE:
613                                         trns += " on base";
614                                         break;
615                                 case TURN3:
616                                         trns += " turning base";
617                                         break;
618                                 case DOWNWIND:
619                                         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.
620                                         break;
621                                 // And to eliminate compiler warnings...
622                                 case TAKEOFF_ROLL: break;
623                                 case CLIMBOUT:     break;
624                                 case TURN1:        break;
625                                 case CROSSWIND:    break;
626                                 case TURN2:        break;
627                                 case LANDING_ROLL: break;
628                                 case LEG_UNKNOWN:  break;
629                                 }
630                         }
631                 } else {
632                         // By definition there should be some arriving traffic if we're cleared for immediate takeoff
633                         SG_LOG(SG_ATC, SG_WARN, "Warning: Departing traffic cleared for *immediate* take-off despite no arriving traffic in FGTower");
634                 }
635                 t->clearedToTakeOff = true;
636                 t->planePtr->RegisterTransmission(4);   // cleared to take-off - TODO differentiate between immediate and normal take-off
637                 departed = false;
638                 timeSinceLastDeparture = 0.0;
639         } else {
640         //} else if(timeSinceLastDeparture > 60.0 || departed == false) {       // Hack - test for timeSinceLastDeparture should be in lineup block eventually
641                 trns += " cleared for take-off";
642                 // TODO - add traffic is... ?
643                 t->clearedToTakeOff = true;
644                 t->planePtr->RegisterTransmission(4);   // cleared to take-off
645                 departed = false;
646                 timeSinceLastDeparture = 0.0;
647         }
648         if(display) {
649                 globals->get_ATC_display()->RegisterSingleMessage(trns, 0);
650         }
651         //cout << "Done ClearHoldingPlane " << endl;
652 }
653
654 // Do one plane from the hold list
655 void FGTower::CheckHoldList(double dt) {
656         //cout << "Entering CheckHoldList..." << endl;
657         if(holdList.size()) {
658                 //cout << "*holdListItr = " << *holdListItr << endl;
659                 if(holdListItr == holdList.end()) {
660                         holdListItr = holdList.begin();
661                 }
662                 //cout << "*holdListItr = " << *holdListItr << endl;
663                 //Process(*holdListItr);
664                 TowerPlaneRec* t = *holdListItr;
665                 //cout << "t = " << t << endl;
666                 if(t->holdShortReported) {
667                         // NO-OP - leave it to the response handler.
668                 } else {        // not responding to report, but still need to clear if clear
669                         if(t->nextOnRwy) {
670                                 //cout << "departed = " << departed << '\n';
671                                 //cout << "timeSinceLastDeparture = " << timeSinceLastDeparture << '\n';
672                                 if(rwyOccupied) {
673                                         // Do nothing
674                                 } else if(timeSinceLastDeparture <= 60.0 && departed == true) {
675                                         // Do nothing - this is a bit of a hack - should maybe do line up be ready here
676                                 } else {
677                                         ClearHoldingPlane(t);
678                                         t->leg = TAKEOFF_ROLL;
679                                         rwyList.push_back(t);
680                                         rwyOccupied = true;
681                                         holdList.erase(holdListItr);
682                                         holdListItr = holdList.begin();
683                                 }
684                         }
685                         // TODO - rationalise the considerable code duplication above!
686                 }
687                 ++holdListItr;
688         }
689         //cout << "Done CheckHoldList" << endl;
690 }
691
692 // do the ciruit list
693 void FGTower::CheckCircuitList(double dt) {
694         //cout << "Entering CheckCircuitList..." << endl;
695         // Clear the constraints - we recalculate here.
696         base_leg_pos = 0.0;
697         downwind_leg_pos = 0.0;
698         crosswind_leg_pos = 0.0;
699         
700         if(circuitList.size()) {        // Do one plane from the circuit
701                 if(circuitListItr == circuitList.end()) {
702                         circuitListItr = circuitList.begin();
703                 }
704                 TowerPlaneRec* t = *circuitListItr;
705                 if(t->isUser) {
706                         t->pos.setlon(user_lon_node->getDoubleValue());
707                         t->pos.setlat(user_lat_node->getDoubleValue());
708                         t->pos.setelev(user_elev_node->getDoubleValue());
709                 } else {
710                         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.
711                         t->landingType = t->planePtr->GetLandingOption();
712                         //cout << "AI plane landing option is " << t->landingType << '\n';
713                 }
714                 Point3D tortho = ortho.ConvertToLocal(t->pos);
715                 if(t->isUser) {
716                         // Need to figure out which leg he's on
717                         //cout << "rwy.hdg = " << rwy.hdg << " user hdg = " << user_hdg_node->getDoubleValue();
718                         double ho = GetAngleDiff_deg(user_hdg_node->getDoubleValue(), rwy.hdg);
719                         //cout << " ho = " << ho << " abs(ho = " << abs(ho) << '\n';
720                         // TODO FIXME - get the wind and convert this to track, or otherwise use track somehow!!!
721                         // If it's gusty might need to filter the value, although we are leaving 30 degrees each way leeway!
722                         if(abs(ho) < 30) {
723                                 // could be either takeoff, climbout or landing - check orthopos.y
724                                 //cout << "tortho.y = " << tortho.y() << '\n';
725                                 if((tortho.y() < 0) || (t->leg == TURN4) || (t->leg == FINAL)) {
726                                         t->leg = FINAL;
727                                         //cout << "Final\n";
728                                 } else {
729                                         t->leg = CLIMBOUT;      // TODO - check elev wrt. apt elev to differentiate takeoff roll and climbout
730                                         //cout << "Climbout\n";
731                                         // If it's the user we may be unsure of his/her intentions.
732                                         // (Hopefully the AI planes won't try confusing the sim!!!)
733                                         if(t->opType == TTT_UNKNOWN) {
734                                                 if(tortho.y() > 5000) {
735                                                         // 5 km out from threshold - assume it's a departure
736                                                         t->opType = OUTBOUND;   // TODO - could check if the user has climbed significantly above circuit altitude as well.
737                                                         // Since we are unknown operation we should be in depList already.
738                                                         circuitList.erase(circuitListItr);
739                                                         RemoveFromTrafficList(t->plane.callsign);
740                                                         circuitListItr = circuitList.begin();
741                                                 }
742                                         } else if(t->opType == CIRCUIT) {
743                                                 if(tortho.y() > 10000) {
744                                                         // 10 km out - assume the user has abandoned the circuit!!
745                                                         t->opType = OUTBOUND;
746                                                         depList.push_back(t);
747                                                         circuitList.erase(circuitListItr);
748                                                         circuitListItr = circuitList.begin();
749                                                 }
750                                         }
751                                 }
752                         } else if(abs(ho) < 60) {
753                                 // turn1 or turn 4
754                                 // TODO - either fix or doublecheck this hack by looking at heading and pattern direction
755                                 if((t->leg == CLIMBOUT) || (t->leg == TURN1)) {
756                                         t->leg = TURN1;
757                                         //cout << "Turn1\n";
758                                 } else {
759                                         t->leg = TURN4;
760                                         //cout << "Turn4\n";
761                                 }
762                         } else if(abs(ho) < 120) {
763                                 // crosswind or base
764                                 // TODO - either fix or doublecheck this hack by looking at heading and pattern direction
765                                 if((t->leg == TURN1) || (t->leg == CROSSWIND)) {
766                                         t->leg = CROSSWIND;
767                                         //cout << "Crosswind\n";
768                                 } else {
769                                         t->leg = BASE;
770                                         //cout << "Base\n";
771                                 }
772                         } else if(abs(ho) < 150) {
773                                 // turn2 or turn 3
774                                 // TODO - either fix or doublecheck this hack by looking at heading and pattern direction
775                                 if((t->leg == CROSSWIND) || (t->leg == TURN2)) {
776                                         t->leg = TURN2;
777                                         //cout << "Turn2\n";
778                                 } else {
779                                         t->leg = TURN3;
780                                         // Probably safe now to assume the user is flying a circuit
781                                         t->opType = CIRCUIT;
782                                         //cout << "Turn3\n";
783                                 }
784                         } else {
785                                 // downwind
786                                 t->leg = DOWNWIND;
787                                 //cout << "Downwind\n";
788                         }
789                         if(t->leg == FINAL) {
790                                 if(OnActiveRunway(t->pos)) {
791                                         t->leg = LANDING_ROLL;
792                                 }
793                         }
794                 } else {
795                         t->leg = t->planePtr->GetLeg();
796                 }
797                 
798                 // Set the constraints IF this is the first plane in the circuit
799                 // 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!!
800                 if(circuitListItr == circuitList.begin()) {
801                         switch(t->leg) {
802                         case FINAL:
803                                 // 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.
804                                 base_leg_pos = tortho.y();
805                                 //cout << "base_leg_pos = " << base_leg_pos << '\n';
806                                 break;
807                         case TURN4:
808                                 // Fall through to base
809                         case BASE:
810                                 base_leg_pos = tortho.y();
811                                 //cout << "base_leg_pos = " << base_leg_pos << '\n';
812                                 break;
813                         case TURN3:
814                                 // Fall through to downwind
815                         case DOWNWIND:
816                                 // Only have the downwind leg pos as turn-to-base constraint if more negative than we already have.
817                                 base_leg_pos = (tortho.y() < base_leg_pos ? tortho.y() : base_leg_pos);
818                                 //cout << "base_leg_pos = " << base_leg_pos;
819                                 downwind_leg_pos = tortho.x();          // Assume that a following plane can simply be constrained by the immediately in front downwind plane
820                                 //cout << " downwind_leg_pos = " << downwind_leg_pos << '\n';
821                                 break;
822                         case TURN2:
823                                 // Fall through to crosswind
824                         case CROSSWIND:
825                                 crosswind_leg_pos = tortho.y();
826                                 //cout << "crosswind_leg_pos = " << crosswind_leg_pos << '\n';
827                                 t->instructedToGoAround = false;
828                                 break;
829                         case TURN1:
830                                 // Fall through to climbout
831                         case CLIMBOUT:
832                                 // Only use current by constraint as largest
833                                 crosswind_leg_pos = (tortho.y() > crosswind_leg_pos ? tortho.y() : crosswind_leg_pos);
834                                 //cout << "crosswind_leg_pos = " << crosswind_leg_pos << '\n';
835                                 break;
836                         case TAKEOFF_ROLL:
837                                 break;
838                         case LEG_UNKNOWN:
839                                 break;
840                         case LANDING_ROLL:
841                                 break;
842                         default:
843                                 break;
844                         }
845                 }
846                 
847                 if(t->leg == FINAL) {
848                         if(t->landingType == FULL_STOP) t->opType = INBOUND;
849                         if(t->eta < 12 && rwyList.size() && !(t->instructedToGoAround)) {
850                                 // TODO - need to make this more sophisticated 
851                                 // eg. is the plane accelerating down the runway taking off [OK],
852                                 // or stationary near the start [V. BAD!!].
853                                 // For now this should stop the AI plane landing on top of the user.
854                                 string trns = t->plane.callsign;
855                                 trns += " GO AROUND TRAFFIC ON RUNWAY I REPEAT GO AROUND";
856                                 if(display) {
857                                         globals->get_ATC_display()->RegisterSingleMessage(trns, 0);
858                                 }
859                                 t->instructedToGoAround = true;
860                                 if(t->planePtr) {
861                                         cout << "Registering Go-around transmission with AI plane\n";
862                                         t->planePtr->RegisterTransmission(13);
863                                 }
864                         }
865                 } else if(t->leg == LANDING_ROLL) {
866                         rwyList.push_front(t);
867                         // TODO - if(!clearedToLand) shout something!!
868                         t->clearedToLand = false;
869                         RemoveFromTrafficList(t->plane.callsign);
870                         if(t->isUser) {
871                                 t->opType = TTT_UNKNOWN;
872                         }       // TODO - allow the user to specify opType via ATC menu
873                         circuitListItr = circuitList.erase(circuitListItr);
874                         if(circuitListItr == circuitList.end() ) {
875                                 circuitListItr = circuitList.begin();
876                         }
877                 }
878                 ++circuitListItr;
879         }
880         //cout << "Done CheckCircuitList" << endl;
881 }
882
883 // 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!!
884 // FIXME - at the moment it looks like we're only doing the first plane from the rwy list.
885 // (However, at the moment there should only be one airplane on the rwy at once, until we
886 // start allowing planes to line up whilst previous arrival clears the rwy.)
887 void FGTower::CheckRunwayList(double dt) {
888         //cout << "Entering CheckRunwayList..." << endl;
889         if(rwyOccupied) {
890                 if(!rwyList.size()) {
891                         rwyOccupied = false;
892                 } else {
893                         rwyListItr = rwyList.begin();
894                         TowerPlaneRec* t = *rwyListItr;
895                         if(t->isUser) {
896                                 t->pos.setlon(user_lon_node->getDoubleValue());
897                                 t->pos.setlat(user_lat_node->getDoubleValue());
898                                 t->pos.setelev(user_elev_node->getDoubleValue());
899                         } else {
900                                 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.
901                         }
902                         bool on_rwy = OnActiveRunway(t->pos);
903                         if(!on_rwy) {
904                                 if((t->opType == INBOUND) || (t->opType == STRAIGHT_IN)) {
905                                         rwyList.pop_front();
906                                         delete t;
907                                         // TODO - tell it to taxi / contact ground / don't delete it etc!
908                                 } else if(t->opType == OUTBOUND) {
909                                         depList.push_back(t);
910                                         rwyList.pop_front();
911                                         departed = true;
912                                         timeSinceLastDeparture = 0.0;
913                                 } else if(t->opType == CIRCUIT) {
914                                         circuitList.push_back(t);
915                                         AddToTrafficList(t);
916                                         rwyList.pop_front();
917                                         departed = true;
918                                         timeSinceLastDeparture = 0.0;
919                                 } else if(t->opType == TTT_UNKNOWN) {
920                                         depList.push_back(t);
921                                         circuitList.push_back(t);
922                                         AddToTrafficList(t);
923                                         rwyList.pop_front();
924                                         departed = true;
925                                         timeSinceLastDeparture = 0.0;   // TODO - we need to take into account that the user might taxi-in when flagged opType UNKNOWN - check speed/altitude etc to make decision as to what user is up to.
926                                 } else {
927                                         // HELP - we shouldn't ever get here!!!
928                                 }
929                         }
930                 }
931         }
932         //cout << "Done CheckRunwayList" << endl;
933 }
934
935 // Do one plane from the approach list
936 void FGTower::CheckApproachList(double dt) {
937         if(appList.size()) {
938                 if(appListItr == appList.end()) {
939                         appListItr = appList.begin();
940                 }
941                 TowerPlaneRec* t = *appListItr;
942                 //cout << "t = " << t << endl;
943                 if(t->isUser) {
944                         t->pos.setlon(user_lon_node->getDoubleValue());
945                         t->pos.setlat(user_lat_node->getDoubleValue());
946                         t->pos.setelev(user_elev_node->getDoubleValue());
947                 } else {
948                         // TODO - set/update the position if it's an AI plane
949                 }                               
950                 if(t->nextOnRwy && !(t->clearedToLand)) {
951                         // check distance away and whether runway occupied
952                         // and schedule transmission if necessary
953                 }                               
954                 ++appListItr;
955         }
956 }
957
958 // Returns true if positions of crosswind/downwind/base leg turns should be constrained by previous traffic
959 // plus the constraint position as a rwy orientated orthopos (meters)
960 bool FGTower::GetCrosswindConstraint(double& cpos) {
961         if(crosswind_leg_pos != 0.0) {
962                 cpos = crosswind_leg_pos;
963                 return(true);
964         } else {
965                 cpos = 0.0;
966                 return(false);
967         }
968 }
969 bool FGTower::GetDownwindConstraint(double& dpos) {
970         if(fabs(downwind_leg_pos) > nominal_downwind_leg_pos) {
971                 dpos = downwind_leg_pos;
972                 return(true);
973         } else {
974                 dpos = 0.0;
975                 return(false);
976         }
977 }
978 bool FGTower::GetBaseConstraint(double& bpos) {
979         if(base_leg_pos < nominal_base_leg_pos) {
980                 bpos = base_leg_pos;
981                 return(true);
982         } else {
983                 bpos = nominal_base_leg_pos;
984                 return(false);
985         }
986 }
987
988
989 // Figure out which runways are active.
990 // For now we'll just be simple and do one active runway - eventually this will get much more complex
991 // This is a private function - public interface to the results of this is through GetActiveRunway
992 void FGTower::DoRwyDetails() {
993         //cout << "GetRwyDetails called" << endl;
994         
995         // Based on the airport-id and wind get the active runway
996         
997         //wind
998         double hdg = wind_from_hdg->getDoubleValue();
999         double speed = wind_speed_knots->getDoubleValue();
1000         hdg = (speed == 0.0 ? 270.0 : hdg);
1001         //cout << "Heading = " << hdg << '\n';
1002         
1003         FGRunway runway;
1004         bool rwyGood = globals->get_runways()->search(ident, int(hdg), &runway);
1005         if(rwyGood) {
1006                 activeRwy = runway.rwy_no;
1007                 rwy.rwyID = runway.rwy_no;
1008                 SG_LOG(SG_ATC, SG_INFO, "Active runway for airport " << ident << " is " << activeRwy);
1009                 
1010                 // Get the threshold position
1011                 double other_way = runway.heading - 180.0;
1012                 while(other_way <= 0.0) {
1013                         other_way += 360.0;
1014                 }
1015         // move to the +l end/center of the runway
1016                 //cout << "Runway center is at " << runway.lon << ", " << runway.lat << '\n';
1017         Point3D origin = Point3D(runway.lon, runway.lat, aptElev);
1018                 Point3D ref = origin;
1019         double tshlon, tshlat, tshr;
1020                 double tolon, tolat, tor;
1021                 rwy.length = runway.length * SG_FEET_TO_METER;
1022                 rwy.width = runway.width * SG_FEET_TO_METER;
1023         geo_direct_wgs_84 ( aptElev, ref.lat(), ref.lon(), other_way, 
1024                                 rwy.length / 2.0 - 25.0, &tshlat, &tshlon, &tshr );
1025         geo_direct_wgs_84 ( aptElev, ref.lat(), ref.lon(), runway.heading, 
1026                                 rwy.length / 2.0 - 25.0, &tolat, &tolon, &tor );
1027                 // Note - 25 meters in from the runway end is a bit of a hack to put the plane ahead of the user.
1028                 // now copy what we need out of runway into rwy
1029         rwy.threshold_pos = Point3D(tshlon, tshlat, aptElev);
1030                 Point3D takeoff_end = Point3D(tolon, tolat, aptElev);
1031                 //cout << "Threshold position = " << tshlon << ", " << tshlat << ", " << aptElev << '\n';
1032                 //cout << "Takeoff position = " << tolon << ", " << tolat << ", " << aptElev << '\n';
1033                 rwy.hdg = runway.heading;
1034                 // Set the projection for the local area based on this active runway
1035                 ortho.Init(rwy.threshold_pos, rwy.hdg); 
1036                 rwy.end1ortho = ortho.ConvertToLocal(rwy.threshold_pos);        // should come out as zero
1037                 rwy.end2ortho = ortho.ConvertToLocal(takeoff_end);
1038                 
1039                 // Set the pattern direction
1040                 // TODO - we'll check for a facilities file with this in eventually - for now assume left traffic except
1041                 // for certain circumstances (RH parallel rwy).
1042                 rwy.patternDirection = -1;              // Left
1043                 if(rwy.rwyID.size() == 3) {
1044                         rwy.patternDirection = (rwy.rwyID.substr(2,1) == "R" ? 1 : -1);
1045                 }
1046         } else {
1047                 SG_LOG(SG_ATC, SG_ALERT, "Help  - can't get good runway in FGTower!!");
1048                 activeRwy = "NN";
1049         }
1050 }
1051
1052
1053 // Figure out if a given position lies on the active runway
1054 // Might have to change when we consider more than one active rwy.
1055 bool FGTower::OnActiveRunway(Point3D pt) {
1056         // TODO - check that the centre calculation below isn't confused by displaced thesholds etc.
1057         Point3D xyc((rwy.end1ortho.x() + rwy.end2ortho.x())/2.0, (rwy.end1ortho.y() + rwy.end2ortho.y())/2.0, 0.0);
1058         Point3D xyp = ortho.ConvertToLocal(pt);
1059         
1060         //cout << "Length offset = " << fabs(xyp.y() - xyc.y()) << '\n';
1061         //cout << "Width offset = " << fabs(xyp.x() - xyc.x()) << '\n';
1062
1063         double rlen = rwy.length/2.0 + 5.0;
1064         double rwidth = rwy.width/2.0;
1065         double ldiff = fabs(xyp.y() - xyc.y());
1066         double wdiff = fabs(xyp.x() - xyc.x());
1067
1068         return((ldiff < rlen) && (wdiff < rwidth));
1069 }       
1070
1071
1072 // Figure out if a given position lies on any runway or not
1073 // Only call this at startup - reading the runways database is expensive and needs to be fixed!
1074 bool FGTower::OnAnyRunway(Point3D pt) {
1075         ATCData ad;
1076         double dist = current_commlist->FindClosest(lon, lat, elev, ad, TOWER, 10.0);
1077         if(dist < 0.0) {
1078                 return(false);
1079         }
1080         // Based on the airport-id, go through all the runways and check for a point in them
1081         
1082         // TODO - do we actually need to search for the airport - surely we already know our ident and
1083         // can just search runways of our airport???
1084         //cout << "Airport ident is " << ad.ident << '\n';
1085         FGRunway runway;
1086         bool rwyGood = globals->get_runways()->search(ad.ident, &runway);
1087         if(!rwyGood) {
1088                 SG_LOG(SG_ATC, SG_WARN, "Unable to find any runways for airport ID " << ad.ident << " in FGTower");
1089         }
1090         bool on = false;
1091         while(runway.id == ad.ident) {          
1092                 on = OnRunway(pt, runway);
1093                 //cout << "Runway " << runway.rwy_no << ": On = " << (on ? "true\n" : "false\n");
1094                 if(on) return(true);
1095                 globals->get_runways()->next(&runway);          
1096         }
1097         return(on);
1098 }
1099
1100
1101 // Returns true if successful
1102 bool FGTower::RemoveFromTrafficList(string id) {
1103         tower_plane_rec_list_iterator twrItr;
1104         for(twrItr = trafficList.begin(); twrItr != trafficList.end(); twrItr++) {
1105                 TowerPlaneRec* tpr = *twrItr;
1106                 if(tpr->plane.callsign == id) {
1107                         trafficList.erase(twrItr);
1108                         return(true);
1109                 }
1110         }       
1111         SG_LOG(SG_ATC, SG_WARN, "Warning - unable to remove aircraft " << id << " from trafficList in FGTower");
1112         return(false);
1113 }
1114
1115
1116 // Add a tower plane rec with ETA to the traffic list in the correct position ETA-wise
1117 // and set nextOnRwy if so.
1118 // Returns true if this could cause a threshold ETA conflict with other traffic, false otherwise.
1119 // For planes holding they are put in the first position with time to go, and the return value is
1120 // true if in the first position (nextOnRwy) and false otherwise.
1121 // See the comments in FGTower::doThresholdUseOrder for notes on the ordering
1122 bool FGTower::AddToTrafficList(TowerPlaneRec* t, bool holding) {
1123         //cout << "ADD: " << trafficList.size();
1124         //cout << "AddToTrafficList called, currently size = " << trafficList.size() << ", holding = " << holding << endl;
1125         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.
1126         double departure_sep_time = 60.0;       // Separation time behind departing airplanes.  Comments above also apply.
1127         bool conflict = false;
1128         double lastETA = 0.0;
1129         bool firstTime = true;
1130         // FIXME - make this more robust for different plane types eg. light following heavy.
1131         tower_plane_rec_list_iterator twrItr;
1132         //twrItr = trafficList.begin();
1133         //while(1) {
1134         for(twrItr = trafficList.begin(); twrItr != trafficList.end(); twrItr++) {
1135                 //if(twrItr == trafficList.end()) {
1136                 //      cout << "  END  ";
1137                 //      trafficList.push_back(t);
1138                 //      return(holding ? firstTime : conflict);
1139                 //} else {
1140                         TowerPlaneRec* tpr = *twrItr;
1141                         if(holding) {
1142                                 //cout << (tpr->isUser ? "USER!\n" : "NOT user\n");
1143                                 //cout << "tpr->eta - lastETA = " << tpr->eta - lastETA << '\n';
1144                                 double dep_allowance = (timeSinceLastDeparture < departure_sep_time ? departure_sep_time - timeSinceLastDeparture : 0.0); 
1145                                 double slot_time = (firstTime ? separation_time + dep_allowance : separation_time + departure_sep_time);
1146                                 // separation_time + departure_sep_time in the above accounts for the fact that the arrival could be touch and go,
1147                                 // and if not needs time to clear the rwy anyway.
1148                                 if(tpr->eta  - lastETA > slot_time) {
1149                                         t->nextOnRwy = firstTime;
1150                                         trafficList.insert(twrItr, t);
1151                                         //cout << "\tH\t" << trafficList.size() << '\n';
1152                                         return(firstTime);
1153                                 }
1154                                 firstTime = false;
1155                         } else {
1156                                 if(t->eta < tpr->eta) {
1157                                         // Ugg - this one's tricky.
1158                                         // It depends on what the two planes are doing and whether there's a conflict what we do.
1159                                         if(tpr->eta - t->eta > separation_time) {       // No probs, plane 2 can squeeze in before plane 1 with no apparent conflict
1160                                                 if(tpr->nextOnRwy) {
1161                                                         tpr->nextOnRwy = false;
1162                                                         t->nextOnRwy = true;
1163                                                 }
1164                                                 trafficList.insert(twrItr, t);
1165                                         } else {        // Ooops - this ones tricky - we have a potential conflict!
1166                                                 conflict = true;
1167                                                 // HACK - just add anyway for now and flag conflict - TODO - FIX THIS using CIRCUIT/STRAIGHT_IN and VFR/IFR precedence rules.
1168                                                 if(tpr->nextOnRwy) {
1169                                                         tpr->nextOnRwy = false;
1170                                                         t->nextOnRwy = true;
1171                                                 }
1172                                                 trafficList.insert(twrItr, t);
1173                                         }
1174                                         //cout << "\tC\t" << trafficList.size() << '\n';
1175                                         return(conflict);
1176                                 }
1177                         }
1178                 //}
1179                 //++twrItr;
1180         }
1181         // If we get here we must be at the end of the list, or maybe the list is empty.
1182         if(!trafficList.size()) {
1183                 t->nextOnRwy = true;
1184                 // conflict and firstTime should be false and true respectively in this case anyway.
1185         }
1186         trafficList.push_back(t);
1187         //cout << "\tE\t" << trafficList.size() << endl;
1188         return(holding ? firstTime : conflict);
1189 }
1190
1191 // Add a tower plane rec with ETA to the circuit list in the correct position ETA-wise
1192 // Returns true if this might cause a separation conflict (based on ETA) with other traffic, false otherwise.
1193 bool FGTower::AddToCircuitList(TowerPlaneRec* t) {
1194         //cout << "ADD: " << circuitList.size();
1195         //cout << "AddToCircuitList called, currently size = " << circuitList.size() << endl;
1196         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.
1197         bool conflict = false;
1198         tower_plane_rec_list_iterator twrItr;
1199         for(twrItr = circuitList.begin(); twrItr != circuitList.end(); twrItr++) {
1200                         TowerPlaneRec* tpr = *twrItr;
1201                                 
1202                                 if(t->eta < tpr->eta) {
1203                                         // Ugg - this one's tricky.
1204                                         // It depends on what the two planes are doing and whether there's a conflict what we do.
1205                                         if(tpr->eta - t->eta > separation_time) {       // No probs, plane 2 can squeeze in before plane 1 with no apparent conflict
1206                                                 circuitList.insert(twrItr, t);
1207                                         } else {        // Ooops - this ones tricky - we have a potential conflict!
1208                                                 conflict = true;
1209                                                 // HACK - just add anyway for now and flag conflict.
1210                                                 circuitList.insert(twrItr, t);
1211                                         }
1212                                         //cout << "\tC\t" << circuitList.size() << '\n';
1213                                         return(conflict);
1214                                 }
1215         }
1216         // If we get here we must be at the end of the list, or maybe the list is empty.
1217         circuitList.push_back(t);       // TODO - check the separation with the preceding plane for the conflict flag.
1218         //cout << "\tE\t" << circuitList.size() << endl;
1219         return(conflict);
1220 }
1221
1222
1223 // Calculate the eta of a plane to the threshold.
1224 // For ground traffic this is the fastest they can get there.
1225 // For air traffic this is the middle approximation.
1226 void FGTower::CalcETA(TowerPlaneRec* tpr, bool printout) {
1227         // For now we'll be very crude and hardwire expected speeds to C172-like values
1228         // The speeds below are specified in knots IAS and then converted to m/s
1229         double app_ias = 100.0 * 0.514444;                      // Speed during straight-in approach
1230         double circuit_ias = 80.0 * 0.514444;           // Speed around circuit
1231         double final_ias = 70.0 * 0.514444;             // Speed during final approach
1232         
1233         //if(printout) {
1234         //cout << "In CalcETA, airplane ident = " << tpr->plane.callsign << '\n';
1235         //cout << (tpr->isUser ? "USER\n" : "AI\n");
1236         //cout << flush;
1237         //}
1238         
1239         // Sign convention - dist_out is -ve for approaching planes and +ve for departing planes
1240         // dist_across is +ve in the pattern direction - ie a plane correctly on downwind will have a +ve dist_across
1241         
1242         Point3D op = ortho.ConvertToLocal(tpr->pos);
1243         //if(printout) {
1244         //if(!tpr->isUser) cout << "Orthopos is " << op.x() << ", " << op.y() << ' ';
1245         //      cout << "opType is " << tpr->opType << '\n';
1246         //}
1247         double dist_out_m = op.y();
1248         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
1249         //cout << "Doing ETA calc for " << tpr->plane.callsign << '\n';
1250         
1251         if(tpr->opType == STRAIGHT_IN) {
1252                 double dist_to_go_m = sqrt((dist_out_m * dist_out_m) + (dist_across_m * dist_across_m));
1253                 if(dist_to_go_m < 1000) {
1254                         tpr->eta = dist_to_go_m / final_ias;
1255                 } else {
1256                         tpr->eta = (1000.0 / final_ias) + ((dist_to_go_m - 1000.0) / app_ias);
1257                 }
1258         } else if(tpr->opType == CIRCUIT || tpr->opType == TTT_UNKNOWN) {       // Hack alert - UNKNOWN has sort of been added here as a temporary hack.
1259                 // It's complicated - depends on if base leg is delayed or not
1260                 //if(printout) {
1261                 //      cout << "Leg = " << tpr->leg << '\n';
1262                 //}
1263                 if(tpr->leg == LANDING_ROLL) {
1264                         tpr->eta = 0;
1265                 } else if((tpr->leg == FINAL) || (tpr->leg == TURN4)) {
1266                         tpr->eta = fabs(dist_out_m) / final_ias;
1267                 } else if((tpr->leg == BASE) || (tpr->leg == TURN3)) {
1268                         tpr->eta = (fabs(dist_out_m) / final_ias) + (dist_across_m / circuit_ias);
1269                 } else {
1270                         // Need to calculate where base leg is likely to be
1271                         // FIXME - for now I'll hardwire it to 1000m which is what AILocalTraffic uses!!!
1272                         // TODO - as a matter of design - AILocalTraffic should get the nominal no-traffic base turn distance from Tower, since in real life the published pattern might differ from airport to airport
1273                         double nominal_base_dist_out_m = -1000;
1274                         double current_base_dist_out_m;
1275                         if(!GetBaseConstraint(current_base_dist_out_m)) {
1276                                 current_base_dist_out_m = nominal_base_dist_out_m;
1277                         }
1278                         //cout << "current_base_dist_out_m = " << current_base_dist_out_m << '\n';
1279                         double nominal_dist_across_m = 1000;    // Hardwired value from AILocalTraffic
1280                         double current_dist_across_m;
1281                         if(!GetDownwindConstraint(current_dist_across_m)) {
1282                                 current_dist_across_m = nominal_dist_across_m;
1283                         }
1284                         double nominal_cross_dist_out_m = 2000; // Bit of a guess - AI plane turns to crosswind at 600ft agl.
1285                         tpr->eta = fabs(current_base_dist_out_m) / final_ias;   // final
1286                         //cout << "a = " << tpr->eta << '\n';
1287                         if((tpr->leg == DOWNWIND) || (tpr->leg == TURN2)) {
1288                                 tpr->eta += dist_across_m / circuit_ias;
1289                                 //cout << "b = " << tpr->eta << '\n';
1290                                 tpr->eta += fabs(current_base_dist_out_m - dist_out_m) / circuit_ias;
1291                                 //cout << "c = " << tpr->eta << '\n';
1292                         } else if((tpr->leg == CROSSWIND) || (tpr->leg == TURN1)) {
1293                                 if(dist_across_m > nominal_dist_across_m) {
1294                                         tpr->eta += dist_across_m / circuit_ias;
1295                                 } else {
1296                                         tpr->eta += nominal_dist_across_m / circuit_ias;
1297                                 }
1298                                 // should we use the dist across of the previous plane if there is previous still on downwind?
1299                                 //if(printout) cout << "bb = " << tpr->eta << '\n';
1300                                 if(dist_out_m > nominal_cross_dist_out_m) {
1301                                         tpr->eta += fabs(current_base_dist_out_m - dist_out_m) / circuit_ias;
1302                                 } else {
1303                                         tpr->eta += fabs(current_base_dist_out_m - nominal_cross_dist_out_m) / circuit_ias;
1304                                 }
1305                                 //if(printout) cout << "cc = " << tpr->eta << '\n';
1306                                 if(nominal_dist_across_m > dist_across_m) {
1307                                         tpr->eta += (nominal_dist_across_m - dist_across_m) / circuit_ias;
1308                                 } else {
1309                                         // Nothing to add
1310                                 }
1311                                 //if(printout) cout << "dd = " << tpr->eta << '\n';
1312                         } else {
1313                                 // We've only just started - why not use a generic estimate?
1314                                 tpr->eta = 240.0;
1315                         }
1316                 }
1317                 //if(printout) {
1318                 //      cout << "ETA = " << tpr->eta << '\n';
1319                 //}
1320                 //if(!tpr->isUser) cout << tpr->plane.callsign << '\t' << tpr->eta << '\n';
1321         } else {
1322                 tpr->eta = 99999;
1323         }       
1324 }
1325
1326
1327 // Calculate the distance of a plane to the threshold in meters
1328 // TODO - Modify to calculate flying distance of a plane in the circuit
1329 double FGTower::CalcDistOutM(TowerPlaneRec* tpr) {
1330         return(dclGetHorizontalSeparation(rwy.threshold_pos, tpr->pos));
1331 }
1332
1333
1334 // Calculate the distance of a plane to the threshold in miles
1335 // TODO - Modify to calculate flying distance of a plane in the circuit
1336 double FGTower::CalcDistOutMiles(TowerPlaneRec* tpr) {
1337         return(CalcDistOutM(tpr) / 1600.0);             // FIXME - use a proper constant if possible.
1338 }
1339
1340
1341 // Iterate through all the lists and call CalcETA for all the planes.
1342 void FGTower::doThresholdETACalc() {
1343         //cout << "Entering doThresholdETACalc..." << endl;
1344         tower_plane_rec_list_iterator twrItr;
1345         // Do the approach list first
1346         for(twrItr = appList.begin(); twrItr != appList.end(); twrItr++) {
1347                 TowerPlaneRec* tpr = *twrItr;
1348                 CalcETA(tpr);
1349         }       
1350         // Then the circuit list
1351         for(twrItr = circuitList.begin(); twrItr != circuitList.end(); twrItr++) {
1352                 TowerPlaneRec* tpr = *twrItr;
1353                 CalcETA(tpr);
1354         }
1355         //cout << "Done doThresholdETCCalc" << endl;
1356 }
1357                 
1358
1359 // Check that the planes in traffic list are correctly ordered,
1360 // that the nearest (timewise) is flagged next on rwy, and return
1361 // true if any threshold use conflicts are detected, false otherwise.
1362 bool FGTower::doThresholdUseOrder() {
1363         //cout << "Entering doThresholdUseOrder..." << endl;
1364         bool conflict = false;
1365         
1366         // Wipe out traffic list, go through circuit, app and hold list, and reorder them in traffic list.
1367         // Here's the rather simplistic assumptions we're using:
1368         // Currently all planes are assumed to be GA light singles with corresponding speeds and separation times.
1369         // In order of priority for runway use:
1370         // STRAIGHT_IN > CIRCUIT > HOLDING_FOR_DEPARTURE
1371         // No modification of planes speeds occurs - conflicts are resolved by delaying turn for base,
1372         // and holding planes until a space.
1373         // When calculating if a holding plane can use the runway, time clearance from last departure
1374         // as well as time clearance to next arrival must be considered.
1375         
1376         trafficList.clear();
1377         
1378         tower_plane_rec_list_iterator twrItr;
1379         // Do the approach list first
1380         //cout << "A" << flush;
1381         for(twrItr = appList.begin(); twrItr != appList.end(); twrItr++) {
1382                 TowerPlaneRec* tpr = *twrItr;
1383                 conflict = AddToTrafficList(tpr);
1384         }       
1385         // Then the circuit list
1386         //cout << "C" << flush;
1387         for(twrItr = circuitList.begin(); twrItr != circuitList.end(); twrItr++) {
1388                 TowerPlaneRec* tpr = *twrItr;
1389                 conflict = AddToTrafficList(tpr);
1390         }
1391         // And finally the hold list
1392         //cout << "H" << endl;
1393         for(twrItr = holdList.begin(); twrItr != holdList.end(); twrItr++) {
1394                 TowerPlaneRec* tpr = *twrItr;
1395                 AddToTrafficList(tpr, true);
1396         }
1397         
1398         if(0) {
1399         //if(ident == "KEMT") {
1400                 for(twrItr = trafficList.begin(); twrItr != trafficList.end(); twrItr++) {
1401                         TowerPlaneRec* tpr = *twrItr;
1402                         cout << tpr->plane.callsign << '\t' << tpr->eta << '\t';
1403                 }
1404                 cout << endl;
1405         }
1406         
1407         //cout << "Done doThresholdUseOrder" << endl;
1408         return(conflict);
1409 }
1410
1411
1412 // Return the ETA of plane no. list_pos (1-based) in the traffic list.
1413 // i.e. list_pos = 1 implies next to use runway.
1414 double FGTower::GetTrafficETA(unsigned int list_pos, bool printout) {
1415         if(trafficList.size() < list_pos) {
1416                 return(99999);
1417         }
1418
1419         tower_plane_rec_list_iterator twrItr;
1420         twrItr = trafficList.begin();
1421         for(unsigned int i = 1; i < list_pos; i++, twrItr++);
1422         TowerPlaneRec* tpr = *twrItr;
1423         CalcETA(tpr, printout);
1424         //cout << "ETA returned = " << tpr->eta << '\n';
1425         return(tpr->eta);
1426 }
1427         
1428
1429 void FGTower::ContactAtHoldShort(PlaneRec plane, FGAIPlane* requestee, tower_traffic_type operation) {
1430         // HACK - assume that anything contacting at hold short is new for now - FIXME LATER
1431         TowerPlaneRec* t = new TowerPlaneRec;
1432         t->plane = plane;
1433         t->planePtr = requestee;
1434         t->holdShortReported = true;
1435         t->clearedToLineUp = false;
1436         t->clearedToTakeOff = false;
1437         t->opType = operation;
1438         t->pos = requestee->GetPos();
1439         
1440         //cout << "Hold Short reported by " << plane.callsign << '\n';
1441         SG_LOG(SG_ATC, SG_BULK, "Hold Short reported by " << plane.callsign);
1442
1443 /*      
1444         bool next = AddToTrafficList(t, true);
1445         if(next) {
1446                 double teta = GetTrafficETA(2);
1447                 if(teta < 150.0) {
1448                         t->clearanceCounter = 7.0;      // This reduces the delay before response to 3 secs if an immediate takeoff is reqd
1449                         //cout << "Reducing response time to request due imminent traffic\n";
1450                 }
1451         } else {
1452         }
1453 */
1454         // TODO - possibly add the reduced interval to clearance when immediate back in under the new scheme
1455
1456         holdList.push_back(t);
1457         
1458         responseReqd = true;
1459 }
1460
1461 // Register the presence of an AI plane at a point where contact would already have been made in real life
1462 // CAUTION - currently it is assumed that this plane's callsign is unique - it is up to AIMgr to generate unique callsigns.
1463 void FGTower::RegisterAIPlane(PlaneRec plane, FGAIPlane* ai, tower_traffic_type op, PatternLeg lg) {
1464         // At the moment this is only going to be tested with inserting an AI plane on downwind
1465         TowerPlaneRec* t = new TowerPlaneRec;
1466         t->plane = plane;
1467         t->planePtr = ai;
1468         t->opType = op;
1469         t->leg = lg;
1470         t->pos = ai->GetPos();
1471         
1472         CalcETA(t);
1473         
1474         if(op == CIRCUIT && lg != LEG_UNKNOWN) {
1475                 AddToCircuitList(t);
1476         } else {
1477                 // FLAG A WARNING
1478         }
1479         
1480         doThresholdUseOrder();
1481 }
1482
1483 // Contact tower for VFR approach
1484 // eg "Cessna Charlie Foxtrot Golf Foxtrot Sierra eight miles South of the airport for full stop with Bravo"
1485 // This function probably only called via user interaction - AI planes will have an overloaded function taking a planerec.
1486 // opt defaults to AIP_LT_UNKNOWN
1487 void FGTower::VFRArrivalContact(string ID, LandingType opt) {
1488         //cout << "Request Landing Clearance called...\n";
1489         
1490         // For now we'll assume that the user is a light plane and can get him/her to join the circuit if necessary.
1491
1492         TowerPlaneRec* t;       
1493         string usercall = fgGetString("/sim/user/callsign");
1494         if(ID == "USER" || ID == usercall) {
1495                 t = FindPlane(usercall);
1496                 if(!t) {
1497                         //cout << "NOT t\n";
1498                         t = new TowerPlaneRec;
1499                         t->isUser = true;
1500                         t->pos.setlon(user_lon_node->getDoubleValue());
1501                         t->pos.setlat(user_lat_node->getDoubleValue());
1502                         t->pos.setelev(user_elev_node->getDoubleValue());
1503                 } else {
1504                         //cout << "IS t\n";
1505                         // Oops - the plane is already registered with this tower - maybe we took off and flew a giant circuit without
1506                         // quite getting out of tower airspace - just ignore for now and treat as new arrival.
1507                         // TODO - Maybe should remove from departure and circuit list if in there though!!
1508                 }
1509         } else {
1510                 // Oops - something has gone wrong - put out a warning
1511                 cout << "WARNING - FGTower::VFRContact(string ID, LandingType lt) called with ID " << ID << " which does not appear to be the user.\n";
1512                 return;
1513         }
1514                 
1515         
1516         // TODO
1517         // Calculate where the plane is in relation to the active runway and it's circuit
1518         // and set the op-type as appropriate.
1519         
1520         // HACK - to get up and running I'm going to assume that the user contacts tower on a staight-in final for now.
1521         t->opType = STRAIGHT_IN;
1522         
1523         t->plane.type = GA_SINGLE;      // FIXME - Another assumption!
1524         t->plane.callsign = usercall;
1525         
1526         t->vfrArrivalReported = true;
1527         responseReqd = true;
1528         
1529         appList.push_back(t);   // Not necessarily permanent
1530         AddToTrafficList(t);
1531         
1532         current_atcdialog->remove_entry(ident, USER_REQUEST_VFR_ARRIVAL, TOWER);
1533         current_atcdialog->remove_entry(ident, USER_REQUEST_VFR_ARRIVAL_FULL_STOP, TOWER);
1534         current_atcdialog->remove_entry(ident, USER_REQUEST_VFR_ARRIVAL_TOUCH_AND_GO, TOWER);
1535 }
1536
1537 void FGTower::RequestDepartureClearance(string ID) {
1538         //cout << "Request Departure Clearance called...\n";
1539 }
1540         
1541 void FGTower::ReportFinal(string ID) {
1542         if(ID == "USER") {
1543                 ID = fgGetString("/sim/user/callsign");
1544                 current_atcdialog->remove_entry(ident, USER_REPORT_3_MILE_FINAL, TOWER);
1545         }
1546         TowerPlaneRec* t = FindPlane(ID);
1547         if(t) {
1548                 t->finalReported = true;
1549                 t->finalAcknowledged = false;
1550                 if(!(t->clearedToLand)) {
1551                         responseReqd = true;
1552                 } // possibly respond with wind even if already cleared to land?
1553         } else {
1554                 SG_LOG(SG_ATC, SG_WARN, "WARNING: Unable to find plane " << ID << " in FGTower::ReportFinal(...)");
1555         }
1556 }
1557
1558 void FGTower::ReportLongFinal(string ID) {
1559         if(ID == "USER") {
1560                 ID = fgGetString("/sim/user/callsign");
1561                 current_atcdialog->remove_entry(ident, USER_REPORT_3_MILE_FINAL, TOWER);
1562         }
1563         TowerPlaneRec* t = FindPlane(ID);
1564         if(t) {
1565                 t->longFinalReported = true;
1566                 t->longFinalAcknowledged = false;
1567                 if(!(t->clearedToLand)) {
1568                         responseReqd = true;
1569                 } // possibly respond with wind even if already cleared to land?
1570         } else {
1571                 SG_LOG(SG_ATC, SG_WARN, "WARNING: Unable to find plane " << ID << " in FGTower::ReportLongFinal(...)");
1572         }
1573 }
1574
1575 //void FGTower::ReportOuterMarker(string ID);
1576 //void FGTower::ReportMiddleMarker(string ID);
1577 //void FGTower::ReportInnerMarker(string ID);
1578 //void FGTower::ReportGoingAround(string ID);
1579
1580 void FGTower::ReportRunwayVacated(string ID) {
1581         //cout << "Report Runway Vacated Called...\n";
1582         if(ID == "USER") {
1583                 ID = fgGetString("/sim/user/callsign");
1584                 current_atcdialog->remove_entry(ident, USER_REPORT_RWY_VACATED, TOWER);
1585         }
1586         TowerPlaneRec* t = FindPlane(ID);
1587         if(t) {
1588                 t->rwyVacatedReported = true;
1589                 responseReqd = true;
1590         } else {
1591                 SG_LOG(SG_ATC, SG_WARN, "WARNING: Unable to find plane " << ID << " in FGTower::ReportRunwayVacated(...)");
1592         }
1593 }
1594
1595 TowerPlaneRec* FGTower::FindPlane(string ID) {
1596         tower_plane_rec_list_iterator twrItr;
1597         // Do the approach list first
1598         for(twrItr = appList.begin(); twrItr != appList.end(); twrItr++) {
1599                 if((*twrItr)->plane.callsign == ID) return(*twrItr);
1600         }       
1601         // Then the circuit list
1602         for(twrItr = circuitList.begin(); twrItr != circuitList.end(); twrItr++) {
1603                 if((*twrItr)->plane.callsign == ID) return(*twrItr);
1604         }
1605         // And finally the hold list
1606         for(twrItr = holdList.begin(); twrItr != holdList.end(); twrItr++) {
1607                 if((*twrItr)->plane.callsign == ID) return(*twrItr);
1608         }
1609         SG_LOG(SG_ATC, SG_WARN, "Unable to find " << ID << " in FGTower::FindPlane(...)");
1610         return(NULL);
1611 }
1612
1613 void FGTower::ReportDownwind(string ID) {
1614         //cout << "ReportDownwind(...) called\n";
1615         if(ID == "USER") {
1616                 ID = fgGetString("/sim/user/callsign");
1617                 current_atcdialog->remove_entry(ident, USER_REPORT_DOWNWIND, TOWER);
1618         }
1619         TowerPlaneRec* t = FindPlane(ID);
1620         if(t) {
1621                 t->downwindReported = true;
1622                 responseReqd = true;
1623         } else {
1624                 SG_LOG(SG_ATC, SG_WARN, "WARNING: Unable to find plane " << ID << " in FGTower::ReportDownwind(...)");
1625         }
1626 }
1627
1628 string FGTower::GenText(const string& m, int c) {
1629         const int cmax = 300;
1630         //string message;
1631         char tag[4];
1632         char crej = '@';
1633         char mes[cmax];
1634         char dum[cmax];
1635         //char buf[10];
1636         char *pos;
1637         int len;
1638         //FGTransmission t;
1639         string usercall = fgGetString("/sim/user/callsign");
1640         
1641         //transmission_list_type     tmissions = transmissionlist_station[station];
1642         //transmission_list_iterator current   = tmissions.begin();
1643         //transmission_list_iterator last      = tmissions.end();
1644         
1645         //for ( ; current != last ; ++current ) {
1646         //      if ( current->get_code().c1 == code.c1 &&  
1647         //              current->get_code().c2 == code.c2 &&
1648         //          current->get_code().c3 == code.c3 ) {
1649                         
1650                         //if ( ttext ) message = current->get_transtext();
1651                         //else message = current->get_menutext();
1652                         strcpy( &mes[0], m.c_str() ); 
1653                         
1654                         // Replace all the '@' parameters with the actual text.
1655                         int check = 0;  // If mes gets overflowed the while loop can go infinite
1656                         while ( strchr(&mes[0], crej) != NULL  ) {      // ie. loop until no more occurances of crej ('@') found
1657                                 pos = strchr( &mes[0], crej );
1658                                 bcopy(pos, &tag[0], 3);
1659                                 tag[3] = '\0';
1660                                 int i;
1661                                 len = 0;
1662                                 for ( i=0; i<cmax; i++ ) {
1663                                         if ( mes[i] == crej ) {
1664                                                 len = i; 
1665                                                 break;
1666                                         }
1667                                 }
1668                                 strncpy( &dum[0], &mes[0], len );
1669                                 dum[len] = '\0';
1670                                 
1671                                 if ( strcmp ( tag, "@ST" ) == 0 )
1672                                         //strcat( &dum[0], tpars.station.c_str() );
1673                                         strcat(&dum[0], ident.c_str());
1674                                 else if ( strcmp ( tag, "@AP" ) == 0 )
1675                                         //strcat( &dum[0], tpars.airport.c_str() );
1676                                         strcat(&dum[0], name.c_str());
1677                                 else if ( strcmp ( tag, "@CS" ) == 0 ) 
1678                                         //strcat( &dum[0], tpars.callsign.c_str() );
1679                                         strcat(&dum[0], usercall.c_str());
1680                                 else if ( strcmp ( tag, "@TD" ) == 0 ) {
1681                                         /*
1682                                         if ( tpars.tdir == 1 ) {
1683                                                 char buf[] = "left";
1684                                                 strcat( &dum[0], &buf[0] );
1685                                         }
1686                                         else {
1687                                                 char buf[] = "right";
1688                                                 strcat( &dum[0], &buf[0] );
1689                                         }
1690                                         */
1691                                 }
1692                                 else if ( strcmp ( tag, "@HE" ) == 0 ) {
1693                                         /*
1694                                         char buf[10];
1695                                         sprintf( buf, "%i", (int)(tpars.heading) );
1696                                         strcat( &dum[0], &buf[0] );
1697                                         */
1698                                 }
1699                                 else if ( strcmp ( tag, "@VD" ) == 0 ) {
1700                                         /*
1701                                         if ( tpars.VDir == 1 ) {
1702                                                 char buf[] = "Descend and maintain";
1703                                                 strcat( &dum[0], &buf[0] );
1704                                         }
1705                                         else if ( tpars.VDir == 2 ) {
1706                                                 char buf[] = "Maintain";
1707                                                 strcat( &dum[0], &buf[0] );
1708                                         }
1709                                         else if ( tpars.VDir == 3 ) {
1710                                                 char buf[] = "Climb and maintain";
1711                                                 strcat( &dum[0], &buf[0] );
1712                                         } 
1713                                         */
1714                                 }
1715                                 else if ( strcmp ( tag, "@AL" ) == 0 ) {
1716                                         /*
1717                                         char buf[10];
1718                                         sprintf( buf, "%i", (int)(tpars.alt) );
1719                                         strcat( &dum[0], &buf[0] );
1720                                         */
1721                                 }
1722                                 else if ( strcmp ( tag, "@MI" ) == 0 ) {
1723                                         char buf[10];
1724                                         //sprintf( buf, "%3.1f", tpars.miles );
1725                                         int dist_miles = dclGetHorizontalSeparation(Point3D(lon, lat, elev), Point3D(user_lon_node->getDoubleValue(), user_lat_node->getDoubleValue(), user_elev_node->getDoubleValue())) / 1600;
1726                                         sprintf(buf, "%i", dist_miles);
1727                                         strcat( &dum[0], &buf[0] );
1728                                 }
1729                                 else if ( strcmp ( tag, "@FR" ) == 0 ) {
1730                                         /*
1731                                         char buf[10];
1732                                         sprintf( buf, "%6.2f", tpars.freq );
1733                                         strcat( &dum[0], &buf[0] );
1734                                         */
1735                                 }
1736                                 else if ( strcmp ( tag, "@RW" ) == 0 ) {
1737                                         strcat(&dum[0], ConvertRwyNumToSpokenString(activeRwy).c_str());
1738                                 } else if(strcmp(tag, "@CD") == 0) {    // @CD = compass direction
1739                                         double h = GetHeadingFromTo(Point3D(lon, lat, elev), Point3D(user_lon_node->getDoubleValue(), user_lat_node->getDoubleValue(), user_elev_node->getDoubleValue()));
1740                                         while(h < 0.0) h += 360.0;
1741                                         while(h > 360.0) h -= 360.0;
1742                                         if(h < 22.5 || h > 337.5) {
1743                                                 strcat(&dum[0], "North");
1744                                         } else if(h < 67.5) {
1745                                                 strcat(&dum[0], "North-East");
1746                                         } else if(h < 112.5) {
1747                                                 strcat(&dum[0], "East");
1748                                         } else if(h < 157.5) {
1749                                                 strcat(&dum[0], "South-East");
1750                                         } else if(h < 202.5) {
1751                                                 strcat(&dum[0], "South");
1752                                         } else if(h < 247.5) {
1753                                                 strcat(&dum[0], "South-West");
1754                                         } else if(h < 292.5) {
1755                                                 strcat(&dum[0], "West");
1756                                         } else {
1757                                                 strcat(&dum[0], "North-West");
1758                                         }
1759                                 } else {
1760                                         cout << "Tag " << tag << " not found" << endl;
1761                                         break;
1762                                 }
1763                                 strcat( &dum[0], &mes[len+3] );
1764                                 strcpy( &mes[0], &dum[0] );
1765                                 
1766                                 ++check;
1767                                 if(check > 10) {
1768                                         SG_LOG(SG_ATC, SG_WARN, "WARNING: Possibly endless loop terminated in FGTransmissionlist::gen_text(...)"); 
1769                                         break;
1770                                 }
1771                         }
1772                         
1773                         //cout << mes  << endl;  
1774                         //break;
1775                 //}
1776         //}
1777         if ( mes != "" ) return mes;
1778         else return "No transmission found";
1779 }
1780
1781 ostream& operator << (ostream& os, tower_traffic_type ttt) {
1782         switch(ttt) {
1783         case(CIRCUIT):      return(os << "CIRCUIT");
1784         case(INBOUND):      return(os << "INBOUND");
1785         case(OUTBOUND):     return(os << "OUTBOUND");
1786         case(TTT_UNKNOWN):  return(os << "UNKNOWN");
1787         case(STRAIGHT_IN):  return(os << "STRAIGHT_IN");
1788         }
1789         return(os << "ERROR - Unknown switch in tower_traffic_type operator << ");
1790 }
1791