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