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