]> git.mxchange.org Git - flightgear.git/blob - src/ATCDCL/tower.cxx
Avoid a crash with out-of-sync airport runway usage and runway definitions.
[flightgear.git] / src / ATCDCL / 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 // Copyright (C) 2008  Daniyar Atadjanov (ground clearance, gear check, weather, etc.)
7 //
8 // This program is free software; you can redistribute it and/or
9 // modify it under the terms of the GNU General Public License as
10 // published by the Free Software Foundation; either version 2 of the
11 // License, or (at your option) any later version.
12 //
13 // This program is distributed in the hope that it will be useful, but
14 // WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16 // General Public License for more details.
17 //
18 // You should have received a copy of the GNU General Public License
19 // along with this program; if not, write to the Free Software
20 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
21
22 #ifdef HAVE_CONFIG_H
23 #  include <config.h>
24 #endif
25
26 #ifdef HAVE_STRINGS_H
27 #  include <strings.h>   // bcopy()
28 #else
29 #  include <string.h>    // MSVC doesn't have strings.h
30 #endif
31
32 #include <sstream>
33 #include <iomanip>
34 #include <iostream>
35
36 #include <simgear/debug/logstream.hxx>
37 #include <simgear/math/sg_geodesy.hxx>
38 #include <simgear/math/sg_random.h>
39 #include <simgear/misc/sg_path.hxx>
40
41 #include <Main/globals.hxx>
42 #include <Airports/runways.hxx>
43
44 #include "tower.hxx"
45 #include "ATCmgr.hxx"
46 #include "ATCutils.hxx"
47 #include "ATCDialog.hxx"
48 #include "commlist.hxx"
49 #include "AILocalTraffic.hxx"
50
51
52 using std::cout;
53
54 // TowerPlaneRec
55
56 TowerPlaneRec::TowerPlaneRec() :
57         planePtr(NULL),
58         eta(0),
59         dist_out(0),
60         clearedToLand(false),
61         clearedToLineUp(false),
62         clearedToTakeOff(false),
63         holdShortReported(false),
64         lineUpReported(false),
65         downwindReported(false),
66         longFinalReported(false),
67         longFinalAcknowledged(false),
68         finalReported(false),
69         finalAcknowledged(false),
70         rwyVacatedReported(false),
71         rwyVacatedAcknowledged(false),
72         goAroundReported(false),
73         instructedToGoAround(false),
74         onRwy(false),
75         nextOnRwy(false),
76         gearWasUp(false),
77         gearUpReported(false),
78         vfrArrivalReported(false),
79         vfrArrivalAcknowledged(false),
80         opType(TTT_UNKNOWN),
81         leg(LEG_UNKNOWN),
82         landingType(AIP_LT_UNKNOWN),
83         isUser(false)
84 {
85         plane.callsign = "UNKNOWN";
86 }
87
88 TowerPlaneRec::TowerPlaneRec(const PlaneRec& p) :
89         planePtr(NULL),
90         eta(0),
91         dist_out(0),
92         clearedToLand(false),
93         clearedToLineUp(false),
94         clearedToTakeOff(false),
95         holdShortReported(false),
96         lineUpReported(false),
97         downwindReported(false),
98         longFinalReported(false),
99         longFinalAcknowledged(false),
100         finalReported(false),
101         finalAcknowledged(false),
102         rwyVacatedReported(false),
103         rwyVacatedAcknowledged(false),
104         goAroundReported(false),
105         instructedToGoAround(false),
106         onRwy(false),
107         nextOnRwy(false),
108         gearWasUp(false),
109         gearUpReported(false),
110         vfrArrivalReported(false),
111         vfrArrivalAcknowledged(false),
112         opType(TTT_UNKNOWN),
113         leg(LEG_UNKNOWN),
114         landingType(AIP_LT_UNKNOWN),
115         isUser(false)
116 {
117         plane = p;
118 }
119
120 TowerPlaneRec::TowerPlaneRec(const SGGeod& pt) :
121         planePtr(NULL),
122         eta(0),
123         dist_out(0),
124         clearedToLand(false),
125         clearedToLineUp(false),
126         clearedToTakeOff(false),
127         holdShortReported(false),
128         lineUpReported(false),
129         downwindReported(false),
130         longFinalReported(false),
131         longFinalAcknowledged(false),
132         finalReported(false),
133         finalAcknowledged(false),
134         rwyVacatedReported(false),
135         rwyVacatedAcknowledged(false),
136         goAroundReported(false),
137         instructedToGoAround(false),
138         onRwy(false),
139         nextOnRwy(false),
140         gearWasUp(false),
141         gearUpReported(false),
142         vfrArrivalReported(false),
143         vfrArrivalAcknowledged(false),
144         opType(TTT_UNKNOWN),
145         leg(LEG_UNKNOWN),
146         landingType(AIP_LT_UNKNOWN),
147         isUser(false)
148 {
149         plane.callsign = "UNKNOWN";
150         pos = pt;
151 }
152
153 TowerPlaneRec::TowerPlaneRec(const PlaneRec& p, const SGGeod& pt) :
154         planePtr(NULL),
155         eta(0),
156         dist_out(0),
157         clearedToLand(false),
158         clearedToLineUp(false),
159         clearedToTakeOff(false),
160         holdShortReported(false),
161         lineUpReported(false),
162         downwindReported(false),
163         longFinalReported(false),
164         longFinalAcknowledged(false),
165         finalReported(false),
166         finalAcknowledged(false),
167         rwyVacatedReported(false),
168         rwyVacatedAcknowledged(false),
169         goAroundReported(false),
170         instructedToGoAround(false),
171         onRwy(false),
172         nextOnRwy(false),
173         gearWasUp(false),
174         gearUpReported(false),
175         vfrArrivalReported(false),
176         vfrArrivalAcknowledged(false),
177         opType(TTT_UNKNOWN),
178         leg(LEG_UNKNOWN),
179         landingType(AIP_LT_UNKNOWN),
180         isUser(false)
181 {
182         plane = p;
183         pos = pt;
184 }
185
186
187 // FGTower
188
189 /*******************************************
190                TODO List
191                            
192 Currently user is assumed to have taken off again when leaving the runway - check speed/elev for taxiing-in. (MAJOR)
193
194 Use track instead of heading to determine what leg of the circuit the user is flying. (MINOR)
195
196 Use altitude as well as position to try to determine if the user has left the circuit. (MEDIUM - other issues as well).
197
198 Currently HoldShortReported code assumes there will be only one plane holding for the runway at once and 
199 will break when planes start queueing. (CRITICAL)
200
201 Report-Runway-Vacated is left as only user ATC option following a go-around. (MAJOR)
202
203 Report-Downwind is not added as ATC option when user takes off to fly a circuit. (MAJOR)
204
205 eta of USER can be calculated very wrongly in circuit if flying straight out and turn4 etc are with +ve ortho y. 
206 This can then screw up circuit ordering for other planes (MEDIUM)
207
208 USER leaving circuit needs to be more robustly considered when intentions unknown
209 Currently only considered during climbout and breaks when user turns (MEDIUM).
210
211 GetPos() of the AI planes is called erratically - either too much or not enough. (MINOR)
212
213 GO-AROUND is instructed very late at < 12s to landing - possibly make more dependent on chance of rwy clearing before landing (FEATURE)
214
215 Need to make clear when TowerPlaneRecs do or don't get deleted in RemoveFromCircuitList etc. (MINOR until I misuse it - then CRITICAL!)
216
217 FGTower::RemoveAllUserDialogOptions() really ought to be replaced by an ATCDialog::clear_entries() function. (MINOR - efficiency).
218
219 At the moment planes in the lists are not guaranteed to always have a sensible ETA - it should be set as part of AddList functions, and lists should only be accessed this way. (FAIRLY MAJOR). 
220 *******************************************/
221
222 FGTower::FGTower() :
223         separateGround(true),
224         ground(0)
225 {
226         ATCmgr = globals->get_ATC_mgr();
227         
228         _type = TOWER;
229         
230         // Init the property nodes - TODO - need to make sure we're getting surface winds.
231         wind_from_hdg = fgGetNode("/environment/wind-from-heading-deg", true);
232         wind_speed_knots = fgGetNode("/environment/wind-speed-kt", true);
233         
234         update_count = 0;
235         update_count_max = 15;
236         
237         holdListItr = holdList.begin();
238         appListItr = appList.begin();
239         depListItr = depList.begin();
240         rwyListItr = rwyList.begin();
241         circuitListItr = circuitList.begin();
242         trafficListItr = trafficList.begin();
243         vacatedListItr = vacatedList.begin();
244         
245         freqClear = true;
246         
247         timeSinceLastDeparture = 9999;
248         departed = false;
249         
250         nominal_downwind_leg_pos = 1000.0;
251         nominal_base_leg_pos = -1000.0;
252         // TODO - set nominal crosswind leg pos based on minimum distance from takeoff end of rwy.
253         
254         _departureControlled = false;
255 }
256
257 FGTower::~FGTower() {
258         if(!separateGround) {
259                 delete ground;
260         }
261 }
262
263 void FGTower::Init() {
264         //cout << "Initialising tower " << ident << '\n';
265         
266         // Pointers to user's position
267         user_lon_node = fgGetNode("/position/longitude-deg", true);
268         user_lat_node = fgGetNode("/position/latitude-deg", true);
269         user_elev_node = fgGetNode("/position/altitude-ft", true);
270         user_hdg_node = fgGetNode("/orientation/heading-deg", true);
271         
272         // Need some way to initialise rwyOccupied flag correctly if the user is on the runway and to know its the user.
273         // I'll punt the startup issue for now though!!!
274         rwyOccupied = false;
275         
276         // Setup the ground control at this airport
277         AirportATC a;
278         //cout << "Tower ident = " << ident << '\n';
279         if(ATCmgr->GetAirportATCDetails(ident, &a)) {
280                 if(a.ground_freq) {             // Ground control
281                         ground = (FGGround*)ATCmgr->GetATCPointer(ident, GROUND);
282                         separateGround = true;
283                         if(ground == NULL) {
284                                 // Something has gone wrong :-(
285                                 SG_LOG(SG_ATC, SG_WARN, "ERROR - ground has frequency but can't get ground pointer :-(");
286                                 ground = new FGGround(ident);
287                                 separateGround = false;
288                                 ground->Init();
289                                 if(_display) {
290                                         ground->SetDisplay();
291                                 } else {
292                                         ground->SetNoDisplay();
293                                 }
294                         }
295                 } else {
296                         // Initialise ground anyway to do the shortest path stuff!
297                         // Note that we're now responsible for updating and deleting this - NOT the ATCMgr.
298                         ground = new FGGround(ident);
299                         separateGround = false;
300                         ground->Init();
301                         if(_display) {
302                                 ground->SetDisplay();
303                         } else {
304                                 ground->SetNoDisplay();
305                         }
306                 }
307         } else {
308                 SG_LOG(SG_ATC, SG_ALERT, "Unable to find airport details for " << ident << " in FGTower::Init()");
309                 // Initialise ground anyway to avoid segfault later
310                 ground = new FGGround(ident);
311                 separateGround = false;
312                 ground->Init();
313                 if(_display) {
314                         ground->SetDisplay();
315                 } else {
316                         ground->SetNoDisplay();
317                 }
318         }
319         
320         RemoveAllUserDialogOptions();
321         
322         // TODO - attempt to get a departure control pointer to see if we need to hand off departing traffic to departure.
323         
324         // Get the airport elevation
325         aptElev = fgGetAirportElev(ident.c_str());
326         
327         // TODO - this function only assumes one active rwy.
328         DoRwyDetails();
329         
330         // TODO - this currently assumes only one active runway.
331         rwyOccupied = OnActiveRunway(SGGeod::fromDegM(user_lon_node->getDoubleValue(), user_lat_node->getDoubleValue(), 0.0));
332         
333         if(!OnAnyRunway(SGGeod::fromDegM(user_lon_node->getDoubleValue(), user_lat_node->getDoubleValue(), 0.0), false)) {
334                 //cout << ident << "  ADD 0\n";
335                 current_atcdialog->add_entry(ident, "@AP Tower, @CS @MI miles @CD of the airport for full stop@AT",
336                                 "Contact tower for VFR arrival (full stop)", TOWER,
337                                 (int)USER_REQUEST_VFR_ARRIVAL_FULL_STOP);
338         } else {
339                 //cout << "User found on active runway\n";
340                 // Assume the user is started at the threshold ready to take-off
341                 TowerPlaneRec* t = new TowerPlaneRec;
342                 t->plane.callsign = fgGetString("/sim/user/callsign");
343                 t->plane.type = GA_SINGLE;      // FIXME - hardwired!!
344                 t->opType = TTT_UNKNOWN;        // We don't know if the user wants to do circuits or a departure...
345                 t->landingType = AIP_LT_UNKNOWN;
346                 t->leg = TAKEOFF_ROLL;
347                 t->isUser = true;
348                 t->planePtr = NULL;
349                 t->clearedToTakeOff = false;
350                 rwyList.push_back(t);
351                 rwyListItr = rwyList.begin();
352                 departed = false;
353                 current_atcdialog->add_entry(ident, "@CS @TO", "Request departure / take-off clearance",
354                                 TOWER, (int)USER_REQUEST_TAKE_OFF);
355         }
356 }
357
358 void FGTower::Update(double dt) {
359         //cout << "T" << endl;
360         // Each time step, what do we need to do?
361         // We need to go through the list of outstanding requests and acknowedgements
362         // and process at least one of them.
363         // We need to go through the list of planes under our control and check if
364         // any need to be addressed.
365         // We need to check for planes not under our control coming within our 
366         // control area and address if necessary.
367         
368         // TODO - a lot of the below probably doesn't need to be called every frame and should be staggered.
369         
370         // Sort the arriving planes
371         
372         /*
373         if(ident == "KEMT") {
374                 cout << update_count << "\ttL: " << trafficList.size() << "  cL: " << circuitList.size() << "  hL: " << holdList.size() << "  aL: " << appList.size() << '\n';
375         }
376         */
377         //if(ident == "EGNX") cout << display << '\n';
378         
379         if(departed != false) {
380                 timeSinceLastDeparture += dt;
381                 //if(ident == "KEMT") 
382                 //      cout << "  dt = " << dt << "  timeSinceLastDeparture = " << timeSinceLastDeparture << '\n';
383         }
384         
385         //cout << ident << " respond = " << respond << " responseReqd = " << responseReqd << '\n'; 
386         if(respond) {
387                 if(!responseReqd) SG_LOG(SG_ATC, SG_ALERT, "ERROR - respond is true and responseReqd is false in FGTower::Update(...)");
388                 Respond();
389                 respond = false;
390                 responseReqd = false;
391         }
392         
393         // Calculate the eta of each plane to the threshold.
394         // For ground traffic this is the fastest they can get there.
395         // For air traffic this is the middle approximation.
396         if(update_count == 1) {
397                 doThresholdETACalc();
398         }
399         
400         // Order the list of traffic as per expected threshold use and flag any conflicts
401         if(update_count == 2) {
402                 //bool conflicts = doThresholdUseOrder();
403                 doThresholdUseOrder();
404         }
405         
406         // sortConficts() !!!
407         
408         if(update_count == 4) {
409                 CheckHoldList(dt);
410         }
411         
412         // Uggh - HACK - why have we got rwyOccupied - wouldn't simply testing rwyList.size() do?
413         if(rwyList.size()) {
414                 rwyOccupied = true;
415         } else {
416                 rwyOccupied = false;
417         }
418         
419         if(update_count == 5 && rwyOccupied) {
420                 CheckRunwayList(dt);
421         }
422                 
423         if(update_count == 6) {
424                 CheckCircuitList(dt);
425         }
426         
427         if(update_count == 7) {
428                 CheckApproachList(dt);
429         }
430         
431         if(update_count == 8) {
432                 CheckDepartureList(dt);
433         }
434         
435         // TODO - do one plane from the departure list and set departed = false when out of consideration
436         
437         //doCommunication();
438         
439         if(!separateGround) {
440                 // The display stuff might have to get more clever than this when not separate 
441                 // since the tower and ground might try communicating simultaneously even though
442                 // they're mean't to be the same contoller/frequency!!
443                 // We could also get rid of this by overloading FGATC's Set(No)Display() functions.
444                 if(_display) {
445                         ground->SetDisplay();
446                 } else {
447                         ground->SetNoDisplay();
448                 }
449                 ground->Update(dt);
450         }
451         
452         ++update_count;
453         // How big should ii get - ie how long should the update cycle interval stretch?
454         if(update_count >= update_count_max) {
455                 update_count = 0;
456         }
457         
458         // Call the base class update for the response time handling.
459         FGATC::Update(dt);
460 }
461
462 void FGTower::ReceiveUserCallback(int code) {
463         if(code == (int)USER_REQUEST_VFR_DEPARTURE) {
464                 RequestDepartureClearance("USER");
465         } else if(code == (int)USER_REQUEST_VFR_ARRIVAL) {
466                 VFRArrivalContact("USER");
467         } else if(code == (int)USER_REQUEST_VFR_ARRIVAL_FULL_STOP) {
468                 VFRArrivalContact("USER", FULL_STOP);
469         } else if(code == (int)USER_REQUEST_VFR_ARRIVAL_TOUCH_AND_GO) {
470                 VFRArrivalContact("USER", TOUCH_AND_GO);
471         } else if(code == (int)USER_REPORT_DOWNWIND) {
472                 ReportDownwind("USER");
473         } else if(code == (int)USER_REPORT_3_MILE_FINAL) {
474                 // For now we'll just call report final instead of report long final to avoid having to alter the response code
475                 ReportFinal("USER");
476         } else if(code == (int)USER_REPORT_RWY_VACATED) {
477                 ReportRunwayVacated("USER");
478         } else if(code == (int)USER_REPORT_GOING_AROUND) {
479                 ReportGoingAround("USER");
480         } else if(code == (int)USER_REQUEST_TAKE_OFF) {
481                 RequestTakeOffClearance("USER");
482         }
483 }
484
485 // **************** RESPONSE FUNCTIONS ****************
486
487 void FGTower::Respond() {
488         //cout << "\nEntering Respond, responseID = " << responseID << endl;
489         TowerPlaneRec* t = FindPlane(responseID);
490         if(t) {
491                 // This will grow!!!
492                 if(t->vfrArrivalReported && !t->vfrArrivalAcknowledged) {
493                         //cout << "Tower " << ident << " is responding to VFR arrival reported...\n";
494                         // Testing - hardwire straight in for now
495                         string trns = t->plane.callsign;
496                         trns += " ";
497                         trns += name;
498                         trns += " Tower,";
499                         // Should we clear staight in or for downwind entry?
500                         // For now we'll clear straight in if greater than 1km from a line drawn through the threshold perpendicular to the rwy.
501                         // Later on we might check the actual heading and direct some of those to enter on downwind or base.
502                         SGVec3d op = ortho.ConvertToLocal(t->pos);
503                         float gp = fgGetFloat("/gear/gear/position-norm");
504                         if(gp < 1)
505                                 t->gearWasUp = true; // This will be needed on final to tell "Gear down, ready to land."
506                         if(op.y() < -1000) {
507                                 trns += " Report three mile straight-in runway ";
508                                 t->opType = STRAIGHT_IN;
509                                 if(t->isUser) {
510                                         current_atcdialog->add_entry(ident, "@CS @MI mile final runway @RW@GR", "Report Final", TOWER, (int)USER_REPORT_3_MILE_FINAL);
511                                 } else {
512                                         t->planePtr->RegisterTransmission(14);
513                                 }
514                         } else {
515                                 // For now we'll just request reporting downwind.
516                                 // TODO - In real life something like 'report 2 miles southwest right downwind rwy 19R' might be used
517                                 // but I'm not sure how to handle all permutations of which direction to tell to report from yet.
518                                 trns += " Report ";
519                                 //cout << "Responding, rwy.patterDirection is " << rwy.patternDirection << '\n';
520                                 trns += ((rwy.patternDirection == 1) ? "right " : "left ");
521                                 trns += "downwind runway ";
522                                 t->opType = CIRCUIT;
523                                 // leave it in the app list until it gets into pattern though.
524                                 if(t->isUser) {
525                                         current_atcdialog->add_entry(ident, "@AP Tower, @CS Downwind @RW", "Report Downwind", TOWER, (int)USER_REPORT_DOWNWIND);
526                                 } else {
527                                         t->planePtr->RegisterTransmission(15);
528                                 }
529                         }
530                         trns += ConvertRwyNumToSpokenString(activeRwy);
531                         if(_display) {
532                                 pending_transmission = trns;
533                                 Transmit();
534                         } else {
535                                 //cout << "Not displaying, trns was " << trns << '\n';
536                         }
537                         t->vfrArrivalAcknowledged = true;
538                 } else if(t->downwindReported) {
539                         //cout << "Tower " << ident << " is responding to downwind reported...\n";
540                         ProcessDownwindReport(t);
541                         t->downwindReported = false;
542                 } else if(t->lineUpReported) {
543                         string trns = t->plane.callsign;
544                         if(rwyOccupied) {
545                                 double f = globals->get_ATC_mgr()->GetFrequency(ident, ATIS) / 100.0;
546                                 string wtr;
547                                 if(!f) {
548                                         wtr = ", " + GetWeather();
549                                 }
550                                 trns += " Cleared for take-off" + wtr;
551                                 t->clearedToTakeOff = true;
552                         } else {
553                                 if(!OnAnyRunway(SGGeod::fromDegM(user_lon_node->getDoubleValue(), user_lat_node->getDoubleValue(), 0.0), true)) {
554                                         // TODO: Check if any AI Planes on final and tell something like: "After the landing CALLSIGN line up runway two eight right"
555                                         trns += " Line up runway " + ConvertRwyNumToSpokenString(activeRwy);
556                                         t->clearedToTakeOff = false;
557                                         current_atcdialog->add_entry(ident, "@CS @TO", "Report ready for take-off", TOWER, (int)USER_REQUEST_TAKE_OFF);
558
559                                 } else {
560                                         sg_srandom_time();
561                                         if((int(sg_random() * 10) + 1) != 3) {
562                                                 t->clearedToTakeOff = true;
563                                                 trns += " Cleared immediate take-off ";
564                                         } else {
565                                                 t->clearedToTakeOff = false;
566                                                 trns += " Negative, departure runway " + ConvertRwyNumToSpokenString(activeRwy);
567                                         }
568                                 }
569                         }
570                         if(_display) {
571                                 pending_transmission = trns;
572                                 Transmit();
573                         } else {
574                                 //cout << "Not displaying, trns was " << trns << '\n';
575                         }
576                         t->lineUpReported = false;
577                 } else if(t->holdShortReported) {
578                         //cout << "Tower " << ident << " is reponding to holdShortReported...\n";
579                         if(t->nextOnRwy) {
580                                 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!!)
581                                         // Do nothing for now - consider acknowloging hold short eventually
582                                 } else {
583                                         ClearHoldingPlane(t);
584                                         t->leg = TAKEOFF_ROLL;
585                                         rwyList.push_back(t);
586                                         rwyListItr = rwyList.begin();
587                                         rwyOccupied = true;
588                                         // WARNING - WE ARE ASSUMING ONLY ONE PLANE REPORTING HOLD AT A TIME BELOW
589                                         // FIXME TODO - FIX THIS!!!
590                                         if(!holdList.empty()) {
591                                                 if(holdListItr == holdList.end()) {
592                                                         holdListItr = holdList.begin();
593                                                 }
594                                                 holdList.erase(holdListItr);
595                                                 holdListItr = holdList.begin();
596                                         }
597                                 }
598                         } else {
599                                 // Tell him to hold and what position he is.
600                                 // Not currently sure under which circumstances we do or don't bother transmitting this.
601                                 string trns = t->plane.callsign;
602                                 trns += " hold position";
603                                 if(_display) {
604                                         pending_transmission = trns;
605                                         Transmit();
606                                 }
607                                 // TODO - add some idea of what traffic is blocking him.
608                         }
609                         t->holdShortReported = false;
610                 } else if(t->finalReported && !(t->finalAcknowledged)) {
611                         //cout << "Tower " << ident << " is responding to finalReported...\n";
612                         bool disp = true;
613                         string trns = t->plane.callsign;
614                         //cout << (t->nextOnRwy ? "Next on rwy " : "Not next!! ");
615                         //cout << (rwyOccupied ? "RWY OCCUPIED!!\n" : "Rwy not occupied\n");
616                         if(t->nextOnRwy && !rwyOccupied && !(t->instructedToGoAround)) {
617                                 if(t->landingType == FULL_STOP) {
618                                         trns += " cleared to land ";
619                                 } else {
620                                         double f = globals->get_ATC_mgr()->GetFrequency(ident, ATIS) / 100.0;
621                                         string wtr;
622                                         if(!f) {
623                                                 wtr = ", " + GetWeather();
624                                         } else {
625                                                 wtr = ", runway " + ConvertRwyNumToSpokenString(activeRwy);
626                                         }
627                                         trns += " cleared to land" + wtr;
628                                 }
629                                 // TODO - add winds
630                                 t->clearedToLand = true;
631                                 // Maybe remove report downwind from menu here as well incase user didn't bother to?
632                                 if(t->isUser) {
633                                         //cout << "ADD VACATED B\n";
634                                         // Put going around at the top (and hence default) since that'll be more desperate,
635                                         // or put rwy vacated at the top since that'll be more common?
636                                         current_atcdialog->add_entry(ident, "@CS Going Around", "Report going around", TOWER, USER_REPORT_GOING_AROUND);
637                                         current_atcdialog->add_entry(ident, "@CS Clear of the runway", "Report runway vacated", TOWER, USER_REPORT_RWY_VACATED);
638                                 } else {
639                                         t->planePtr->RegisterTransmission(7);
640                                 }
641                         } else if(t->eta < 20) {
642                                 // Do nothing - we'll be telling it to go around in less than 10 seconds if the
643                                 // runway doesn't clear so no point in calling "continue approach".
644                                 disp = false;
645                         } else {
646                                 trns += " continue approach";
647                                 trns += " and report ";
648                                 trns += ((rwy.patternDirection == 1) ? "right " : "left ");
649                                 trns += "downwind runway " + ConvertRwyNumToSpokenString(activeRwy);
650                                 t->opType = CIRCUIT;
651                                 if(t->isUser) {
652                                         current_atcdialog->add_entry(ident, "@AP Tower, @CS Downwind @RW", "Report Downwind", TOWER, (int)USER_REPORT_DOWNWIND);
653                                 } else {
654                                         t->planePtr->RegisterTransmission(15);
655                                 }
656                                 t->clearedToLand = false;
657                         }
658                         if(_display && disp) {
659                                 pending_transmission = trns;
660                                 Transmit();
661                         }
662                         t->finalAcknowledged = true;
663                 } else if(t->rwyVacatedReported && !(t->rwyVacatedAcknowledged)) {
664                         ProcessRunwayVacatedReport(t);
665                         t->rwyVacatedAcknowledged = true;
666                 }
667         }
668         //freqClear = true;     // FIXME - set this to come true after enough time to render the message
669         _releaseCounter = 0.0;
670         _releaseTime = 5.5;
671         _runReleaseCounter = true;
672         //cout << "Done Respond\n" << endl;
673 }
674
675 void FGTower::ProcessDownwindReport(TowerPlaneRec* t) {
676         int i = 1;
677         int a = 0;      // Count of preceding planes on approach
678         bool cf = false;        // conflicting traffic on final
679         bool cc = false;        // preceding traffic in circuit
680         TowerPlaneRec* tc = NULL;
681         for(tower_plane_rec_list_iterator twrItr = circuitList.begin(); twrItr != circuitList.end(); twrItr++) {
682                 if((*twrItr)->plane.callsign == responseID) break;
683                 tc = *twrItr;
684                 ++i;
685         }
686         if(i > 1) { cc = true; }
687         doThresholdETACalc();
688         TowerPlaneRec* tf = NULL;
689         for(tower_plane_rec_list_iterator twrItr = appList.begin(); twrItr != appList.end(); twrItr++) {
690                 if((*twrItr)->eta < (t->eta + 45) && strcmp((*twrItr)->plane.callsign.c_str(), t->plane.callsign.c_str()) != 0) { // don't let ATC ask you to follow yourself
691                         a++;
692                         tf = *twrItr;
693                         cf = true;
694                         // This should set the flagged plane to be the last conflicting one, and hence the one to follow.
695                         // It ignores the fact that we might have problems slotting into the approach traffic behind it - 
696                         // eventually we'll need some fancy algorithms for that!
697                 }
698         }
699         string trns = t->plane.callsign;
700         trns += " Number ";
701         trns += ConvertNumToSpokenDigits(i + a);
702         // This assumes that the number spoken is landing position, not circuit position, since some of the traffic might be on straight-in final.
703         trns += " ";
704         TowerPlaneRec* tt = NULL;
705         if((i == 1) && rwyList.empty() && (t->nextOnRwy) && (!cf)) {    // Unfortunately nextOnRwy currently doesn't handle circuit/straight-in ordering properly at present, hence the cf check below.
706                 trns += "Cleared to land";      // TODO - clear for the option if appropriate
707                 t->clearedToLand = true;
708                 if(!t->isUser) t->planePtr->RegisterTransmission(7);
709         } else if((i+a) > 1) {
710                 //First set tt to point to the correct preceding plane - final or circuit
711                 if(tc && tf) {
712                         tt = (tf->eta < tc->eta ? tf : tc);
713                 } else if(tc) {
714                         tt = tc;
715                 } else if(tf) {
716                         tt = tf;
717                 } else {
718                         // We should never get here!
719                         SG_LOG(SG_ATC, SG_ALERT, "ALERT - Logic error in FGTower::ProcessDownwindReport");
720                         return;
721                 }
722                 trns += "Follow the ";
723                 string s = tt->plane.callsign;
724                 int p = s.find('-');
725                 s = s.substr(0,p);
726                 trns += s;
727                 if((tt->opType) == CIRCUIT) {
728                         PatternLeg leg;
729                         if(tt->isUser) {
730                                 leg = tt->leg;
731                         } else {
732                                 leg = tt->planePtr->GetLeg();
733                         }
734                         if(leg == FINAL) {
735                                 trns += " on final";
736                         } else if(leg == TURN4) {
737                                 trns += " turning final";
738                         } else if(leg == BASE) {
739                                 trns += " on base";
740                         } else if(leg == TURN3) {
741                                 trns += " turning base";
742                         }
743                 } else {
744                         double miles_out = CalcDistOutMiles(tt);
745                         if(miles_out < 2) {
746                                 trns += " on short final";
747                         } else {
748                                 trns += " on ";
749                                 trns += ConvertNumToSpokenDigits((int)miles_out);
750                                 trns += " mile final";
751                         }
752                 }
753         }
754         if(_display) {
755                 pending_transmission = trns;
756                 Transmit();
757         }
758         if(t->isUser) {
759                 if(t->opType == TTT_UNKNOWN) t->opType = CIRCUIT;
760                 //cout << "ADD VACATED A\n";
761                 // Put going around at the top (and hence default) since that'll be more desperate,
762                 // or put rwy vacated at the top since that'll be more common?
763                 //cout << "ident = " << ident << ", adding go-around option\n";
764                 current_atcdialog->add_entry(ident, "@CS Going Around", "Report going around", TOWER, USER_REPORT_GOING_AROUND);
765                 current_atcdialog->add_entry(ident, "@CS Clear of the runway", "Report runway vacated", TOWER, USER_REPORT_RWY_VACATED);
766         }
767 }
768
769 void FGTower::ProcessRunwayVacatedReport(TowerPlaneRec* t) {
770         //cout << "Processing rwy vacated...\n";
771         if(t->isUser) current_atcdialog->remove_entry(ident, USER_REPORT_GOING_AROUND, TOWER);
772         string trns = t->plane.callsign;
773         if(separateGround) {
774                 trns += " Contact ground on ";
775                 double f = globals->get_ATC_mgr()->GetFrequency(ident, GROUND) / 100.0; 
776                 char buf[10];
777                 sprintf(buf, "%.2f", f);
778                 trns += buf;
779                 trns += " Good Day";
780                 if(!t->isUser) t->planePtr->RegisterTransmission(5);
781         } else {
782                 // Cop-out!!
783                 trns += " cleared for taxi to general aviation parking";
784                 if(!t->isUser) t->planePtr->RegisterTransmission(6);    // TODO - this is a mega-hack!!
785         }
786         //cout << "trns = " << trns << '\n';
787         if(_display) {
788                 pending_transmission = trns;
789                 Transmit();
790         }
791         RemoveFromRwyList(t->plane.callsign);
792         AddToVacatedList(t);
793         // Maybe we should check that the plane really *has* vacated the runway!
794 }
795
796 // *********** END RESPONSE FUNCTIONS *****************
797
798 // Currently this assumes we *are* next on the runway and doesn't check for planes about to land - 
799 // this should be done prior to calling this function.
800 void FGTower::ClearHoldingPlane(TowerPlaneRec* t) {
801         //cout << "Entering ClearHoldingPlane..." << endl;
802         // Lets Roll !!!!
803         string trns = t->plane.callsign;
804         //if(departed plane < some threshold in time away) {
805         if(0) {         // FIXME
806         //if(timeSinceLastDeparture <= 60.0 && departed == true) {
807                 trns += " line up runway " + ConvertRwyNumToSpokenString(activeRwy);
808                 t->clearedToLineUp = true;
809                 t->planePtr->RegisterTransmission(3);   // cleared to line-up
810         //} else if(arriving plane < some threshold away) {
811         } else if(GetTrafficETA(2) < 150.0 && (timeSinceLastDeparture > 60.0 || departed == false)) {   // Hack - hardwired time
812                 trns += " cleared immediate take-off";
813                 if(trafficList.size()) {
814                         tower_plane_rec_list_iterator trfcItr = trafficList.begin();
815                         trfcItr++;      // At the moment the holding plane should be first in trafficList.
816                         // Note though that this will break if holding planes aren't put in trafficList in the future.
817                         TowerPlaneRec* trfc = *trfcItr;
818                         trns += "... traffic is";
819                         switch(trfc->plane.type) {
820                         case UNKNOWN:
821                                 break;
822                         case GA_SINGLE:
823                                 trns += " a Cessna";    // TODO - add ability to specify actual plane type somewhere
824                                 break;
825                         case GA_HP_SINGLE:
826                                 trns += " a Piper";
827                                 break;
828                         case GA_TWIN:
829                                 trns += " a King-air";
830                                 break;
831                         case GA_JET:
832                                 trns += " a Learjet";
833                                 break;
834                         case MEDIUM:
835                                 trns += " a Regional";
836                                 break;
837                         case HEAVY:
838                                 trns += " a Heavy";
839                                 break;
840                         case MIL_JET:
841                                 trns += " Military";
842                                 break;
843                         }
844                         //if(trfc->opType == STRAIGHT_IN || trfc->opType == TTT_UNKNOWN) {
845                         if(trfc->opType == STRAIGHT_IN) {
846                                 double miles_out = CalcDistOutMiles(trfc);
847                                 if(miles_out < 2) {
848                                         trns += " on final";
849                                 } else {
850                                         trns += " on ";
851                                         trns += ConvertNumToSpokenDigits((int)miles_out);
852                                         trns += " mile final";
853                                 }
854                         } else if(trfc->opType == CIRCUIT) {
855                                 //cout << "Getting leg of " << trfc->plane.callsign << '\n';
856                                 switch(trfc->leg) {
857                                 case FINAL:
858                                         trns += " on final";
859                                         break;
860                                 case TURN4:
861                                         trns += " turning final";
862                                         break;
863                                 case BASE:
864                                         trns += " on base";
865                                         break;
866                                 case TURN3:
867                                         trns += " turning base";
868                                         break;
869                                 case DOWNWIND:
870                                         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.
871                                         break;
872                                 // And to eliminate compiler warnings...
873                                 case TAKEOFF_ROLL: break;
874                                 case CLIMBOUT:     break;
875                                 case TURN1:        break;
876                                 case CROSSWIND:    break;
877                                 case TURN2:        break;
878                                 case LANDING_ROLL: break;
879                                 case LEG_UNKNOWN:  break;
880                                 }
881                         }
882                 } else {
883                         // By definition there should be some arriving traffic if we're cleared for immediate takeoff
884                         SG_LOG(SG_ATC, SG_WARN, "Warning: Departing traffic cleared for *immediate* take-off despite no arriving traffic in FGTower");
885                 }
886                 t->clearedToTakeOff = true;
887                 t->planePtr->RegisterTransmission(4);   // cleared to take-off - TODO differentiate between immediate and normal take-off
888                 departed = false;
889                 timeSinceLastDeparture = 0.0;
890         } else {
891         //} else if(timeSinceLastDeparture > 60.0 || departed == false) {       // Hack - test for timeSinceLastDeparture should be in lineup block eventually
892                 trns += " cleared for take-off";
893                 // TODO - add traffic is... ?
894                 t->clearedToTakeOff = true;
895                 t->planePtr->RegisterTransmission(4);   // cleared to take-off
896                 departed = false;
897                 timeSinceLastDeparture = 0.0;
898         }
899         if(_display) {
900                 pending_transmission = trns;
901                 Transmit();
902         }
903         //cout << "Done ClearHoldingPlane " << endl;
904 }
905
906
907 // ***************************************************************************************
908 // ********** Functions to periodically check what the various traffic is doing **********
909
910 // Do one plane from the hold list
911 void FGTower::CheckHoldList(double dt) {
912         //cout << "Entering CheckHoldList..." << endl;
913         if(!holdList.empty()) {
914                 //cout << "*holdListItr = " << *holdListItr << endl;
915                 if(holdListItr == holdList.end()) {
916                         holdListItr = holdList.begin();
917                 }
918                 //cout << "*holdListItr = " << *holdListItr << endl;
919                 //Process(*holdListItr);
920                 TowerPlaneRec* t = *holdListItr;
921                 //cout << "t = " << t << endl;
922                 if(t->holdShortReported) {
923                         // NO-OP - leave it to the response handler.
924                 } else {        // not responding to report, but still need to clear if clear
925                         if(t->nextOnRwy) {
926                                 //cout << "departed = " << departed << '\n';
927                                 //cout << "timeSinceLastDeparture = " << timeSinceLastDeparture << '\n';
928                                 if(rwyOccupied) {
929                                         RemoveAllUserDialogOptions();
930                                         current_atcdialog->add_entry(ident, "@CS Ready for take-off", "Request take-off clearance", TOWER, (int)USER_REQUEST_TAKE_OFF);
931                                 } else if(timeSinceLastDeparture <= 60.0 && departed == true) {
932                                         // Do nothing - this is a bit of a hack - should maybe do line up be ready here
933                                 } else {
934                                         ClearHoldingPlane(t);
935                                         t->leg = TAKEOFF_ROLL;
936                                         rwyList.push_back(t);
937                                         rwyListItr = rwyList.begin();
938                                         rwyOccupied = true;
939                                         holdList.erase(holdListItr);
940                                         holdListItr = holdList.begin();
941                                         if (holdList.empty())
942                                           return;
943                                 }
944                         }
945                         // TODO - rationalise the considerable code duplication above!
946                 }
947                 ++holdListItr;
948         }
949         //cout << "Done CheckHoldList" << endl;
950 }
951
952 // do the ciruit list
953 void FGTower::CheckCircuitList(double dt) {
954         //cout << "Entering CheckCircuitList..." << endl;
955         // Clear the constraints - we recalculate here.
956         base_leg_pos = 0.0;
957         downwind_leg_pos = 0.0;
958         crosswind_leg_pos = 0.0;
959         
960         if(!circuitList.empty()) {      // Do one plane from the circuit
961                 if(circuitListItr == circuitList.end()) {
962                         circuitListItr = circuitList.begin();
963                 }
964                 TowerPlaneRec* t = *circuitListItr;
965                 //cout << ident <<  ' ' << circuitList.size() << ' ' << t->plane.callsign << " " << t->leg << " eta " << t->eta << '\n';
966                 if(t->isUser) {
967                         t->pos.setLongitudeDeg(user_lon_node->getDoubleValue());
968                         t->pos.setLatitudeDeg(user_lat_node->getDoubleValue());
969                         t->pos.setElevationM(user_elev_node->getDoubleValue());
970                         //cout << ident <<  ' ' << circuitList.size() << ' ' << t->plane.callsign << " " << t->leg << " eta " << t->eta << '\n';
971                 } else {
972                         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.
973                         t->landingType = t->planePtr->GetLandingOption();
974                         //cout << "AI plane landing option is " << t->landingType << '\n';
975                 }
976                 SGVec3d tortho = ortho.ConvertToLocal(t->pos);
977                 if(t->isUser) {
978                         // Need to figure out which leg he's on
979                         //cout << "rwy.hdg = " << rwy.hdg << " user hdg = " << user_hdg_node->getDoubleValue();
980                         double ho = GetAngleDiff_deg(user_hdg_node->getDoubleValue(), rwy.hdg);
981                         //cout << " ho = " << ho << " abs(ho = " << abs(ho) << '\n';
982                         // TODO FIXME - get the wind and convert this to track, or otherwise use track somehow!!!
983                         // If it's gusty might need to filter the value, although we are leaving 30 degrees each way leeway!
984                         if(fabs(ho) < 30) {
985                                 // could be either takeoff, climbout or landing - check orthopos.y
986                                 //cout << "tortho.y = " << tortho.y() << '\n';
987                                 if((tortho.y() < 0) || (t->leg == TURN4) || (t->leg == FINAL)) {
988                                         t->leg = FINAL;
989                                         //cout << "Final\n";
990                                 } else {
991                                         t->leg = CLIMBOUT;      // TODO - check elev wrt. apt elev to differentiate takeoff roll and climbout
992                                         //cout << "Climbout\n";
993                                         // If it's the user we may be unsure of his/her intentions.
994                                         // (Hopefully the AI planes won't try confusing the sim!!!)
995                                         //cout << "tortho.y = " << tortho.y() << '\n';
996                                         if(t->opType == TTT_UNKNOWN) {
997                                                 if(tortho.y() > 5000) {
998                                                         // 5 km out from threshold - assume it's a departure
999                                                         t->opType = OUTBOUND;   // TODO - could check if the user has climbed significantly above circuit altitude as well.
1000                                                         // Since we are unknown operation we should be in depList already.
1001                                                         //cout << ident << " Removing user from circuitList (TTT_UNKNOWN)\n";
1002                                                         circuitListItr = circuitList.erase(circuitListItr);
1003                                                         RemoveFromTrafficList(t->plane.callsign);
1004                                                         if (circuitList.empty())
1005                                                             return;
1006                                                 }
1007                                         } else if(t->opType == CIRCUIT) {
1008                                                 if(tortho.y() > 10000) {
1009                                                         // 10 km out - assume the user has abandoned the circuit!!
1010                                                         t->opType = OUTBOUND;
1011                                                         depList.push_back(t);
1012                                                         depListItr = depList.begin();
1013                                                         //cout << ident << " removing user from circuitList (CIRCUIT)\n";
1014                                                         circuitListItr = circuitList.erase(circuitListItr);
1015                                                         if (circuitList.empty())
1016                                                           return;
1017                                                 }
1018                                         }
1019                                 }
1020                         } else if(fabs(ho) < 60) {
1021                                 // turn1 or turn 4
1022                                 // TODO - either fix or doublecheck this hack by looking at heading and pattern direction
1023                                 if((t->leg == CLIMBOUT) || (t->leg == TURN1)) {
1024                                         t->leg = TURN1;
1025                                         //cout << "Turn1\n";
1026                                 } else {
1027                                         t->leg = TURN4;
1028                                         //cout << "Turn4\n";
1029                                 }
1030                         } else if(fabs(ho) < 120) {
1031                                 // crosswind or base
1032                                 // TODO - either fix or doublecheck this hack by looking at heading and pattern direction
1033                                 if((t->leg == TURN1) || (t->leg == CROSSWIND)) {
1034                                         t->leg = CROSSWIND;
1035                                         //cout << "Crosswind\n";
1036                                 } else {
1037                                         t->leg = BASE;
1038                                         //cout << "Base\n";
1039                                 }
1040                         } else if(fabs(ho) < 150) {
1041                                 // turn2 or turn 3
1042                                 // TODO - either fix or doublecheck this hack by looking at heading and pattern direction
1043                                 if((t->leg == CROSSWIND) || (t->leg == TURN2)) {
1044                                         t->leg = TURN2;
1045                                         //cout << "Turn2\n";
1046                                 } else {
1047                                         t->leg = TURN3;
1048                                         // Probably safe now to assume the user is flying a circuit
1049                                         t->opType = CIRCUIT;
1050                                         //cout << "Turn3\n";
1051                                 }
1052                         } else {
1053                                 // downwind
1054                                 t->leg = DOWNWIND;
1055                                 //cout << "Downwind\n";
1056                         }
1057                         if(t->leg == FINAL) {
1058                                 if(OnActiveRunway(t->pos)) {
1059                                         t->leg = LANDING_ROLL;
1060                                 }
1061                         }
1062                 } else {
1063                         t->leg = t->planePtr->GetLeg();
1064                 }
1065                 
1066                 // Set the constraints IF this is the first plane in the circuit
1067                 // 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!!
1068                 if(circuitListItr == circuitList.begin()) {
1069                         switch(t->leg) {
1070                                 case FINAL:
1071                                 // 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.
1072                                 base_leg_pos = tortho.y();
1073                                 //cout << "base_leg_pos = " << base_leg_pos << '\n';
1074                                 break;
1075                                 case TURN4:
1076                                 // Fall through to base
1077                                 case BASE:
1078                                 base_leg_pos = tortho.y();
1079                                 //cout << "base_leg_pos = " << base_leg_pos << '\n';
1080                                 break;
1081                                 case TURN3:
1082                                 // Fall through to downwind
1083                                 case DOWNWIND:
1084                                 // Only have the downwind leg pos as turn-to-base constraint if more negative than we already have.
1085                                 base_leg_pos = (tortho.y() < base_leg_pos ? tortho.y() : base_leg_pos);
1086                                 //cout << "base_leg_pos = " << base_leg_pos;
1087                                 downwind_leg_pos = tortho.x();          // Assume that a following plane can simply be constrained by the immediately in front downwind plane
1088                                 //cout << " downwind_leg_pos = " << downwind_leg_pos << '\n';
1089                                 break;
1090                                 case TURN2:
1091                                 // Fall through to crosswind
1092                                 case CROSSWIND:
1093                                 crosswind_leg_pos = tortho.y();
1094                                 //cout << "crosswind_leg_pos = " << crosswind_leg_pos << '\n';
1095                                 t->instructedToGoAround = false;
1096                                 break;
1097                                 case TURN1:
1098                                 // Fall through to climbout
1099                                 case CLIMBOUT:
1100                                 // Only use current by constraint as largest
1101                                 crosswind_leg_pos = (tortho.y() > crosswind_leg_pos ? tortho.y() : crosswind_leg_pos);
1102                                 //cout << "crosswind_leg_pos = " << crosswind_leg_pos << '\n';
1103                                 break;
1104                                 case TAKEOFF_ROLL:
1105                                 break;
1106                                 case LEG_UNKNOWN:
1107                                 break;
1108                                 case LANDING_ROLL:
1109                                 break;
1110                                 default:
1111                                 break;
1112                         }
1113                 }
1114                 
1115                 if(t->leg == FINAL && !(t->instructedToGoAround)) {
1116                         doThresholdETACalc();
1117                         doThresholdUseOrder();
1118                         /*
1119                         if(t->isUser) {
1120                                 cout << "Checking USER on final... ";
1121                                 cout << "eta " << t->eta;
1122                                 if(t->clearedToLand) cout << " cleared to land\n";
1123                         }
1124                         */
1125                         //cout << "YES FINAL, t->eta = " << t->eta << ", rwyList.size() = " << rwyList.size() << '\n';
1126                         if(t->landingType == FULL_STOP) {
1127                                 t->opType = INBOUND;
1128                                 //cout << "\n******** SWITCHING TO INBOUND AT POINT AAA *********\n\n";
1129                         }
1130                         if(t->eta < 12 && rwyList.size()) {
1131                                 // TODO - need to make this more sophisticated 
1132                                 // eg. is the plane accelerating down the runway taking off [OK],
1133                                 // or stationary near the start [V. BAD!!].
1134                                 // For now this should stop the AI plane landing on top of the user.
1135                                 tower_plane_rec_list_iterator twrItr;
1136                                 twrItr = rwyList.begin();
1137                                 TowerPlaneRec* tpr = *twrItr;
1138                                 if(strcmp(tpr->plane.callsign.c_str(), t->plane.callsign.c_str()) == 0
1139                                                 && rwyList.size() == 1) {
1140                                         // Fixing bug when ATC says that we must go around because of traffic on rwy
1141                                         // but that traffic is our plane! In future we can use this expression
1142                                         // for other ATC-messages like "On ground at 46, vacate left."
1143
1144                                 } else {
1145                                         string trns = t->plane.callsign;
1146                                         trns += " GO AROUND TRAFFIC ON RUNWAY I REPEAT GO AROUND";
1147                                         pending_transmission = trns;
1148                                         ImmediateTransmit();
1149                                         t->instructedToGoAround = true;
1150                                         t->clearedToLand = false;
1151                                         // Assume it complies!!!
1152                                         t->opType = CIRCUIT;
1153                                         t->leg = CLIMBOUT;
1154                                         if(t->planePtr) {
1155                                                 //cout << "Registering Go-around transmission with AI plane\n";
1156                                                 t->planePtr->RegisterTransmission(13);
1157                                         }
1158                                 }
1159                         } else if(!t->clearedToLand) {
1160                                 // The whip through the appList is a hack since currently t->nextOnRwy doesn't always work
1161                                 // TODO - fix this!
1162                                 bool cf = false;
1163                                 for(tower_plane_rec_list_iterator twrItr = appList.begin(); twrItr != appList.end(); twrItr++) {
1164                                         if((*twrItr)->eta < t->eta) {
1165                                                 cf = true;
1166                                         }
1167                                 }
1168                                 if(t->nextOnRwy && !cf) {
1169                                         if(!rwyList.size()) {
1170                                                 string trns = t->plane.callsign;
1171                                                 trns += " Cleared to land";
1172                                                 pending_transmission = trns;
1173                                                 Transmit();
1174                                                 //if(t->isUser) cout << "Transmitting cleared to Land!!!\n";
1175                                                 t->clearedToLand = true;
1176                                                 if(!t->isUser) {
1177                                                         t->planePtr->RegisterTransmission(7);
1178                                                 }
1179                                         }
1180                                 } else {
1181                                         //if(t->isUser) cout << "Not next\n";
1182                                 }
1183                         }
1184                 } else if(t->leg == LANDING_ROLL) {
1185                         //cout << t->plane.callsign << " has landed - adding to rwyList\n";
1186                         rwyList.push_front(t);
1187                         // TODO - if(!clearedToLand) shout something!!
1188                         t->clearedToLand = false;
1189                         RemoveFromTrafficList(t->plane.callsign);
1190                         if(t->isUser) {
1191                                 t->opType = TTT_UNKNOWN;
1192                         }       // TODO - allow the user to specify opType via ATC menu
1193                         //cout << ident << " Removing " << t->plane.callsign << " from circuitList..." << endl;
1194                         circuitListItr = circuitList.erase(circuitListItr);
1195                         if(circuitListItr == circuitList.end() ) {
1196                                 circuitListItr = circuitList.begin();
1197                                 // avoid increment of circuitListItr (would increment to second element, or crash if no element left)
1198                                 return;
1199                         }
1200                 }
1201                 ++circuitListItr;
1202         }
1203         //cout << "Done CheckCircuitList" << endl;
1204 }
1205
1206 // 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!!
1207 // FIXME - at the moment it looks like we're only doing the first plane from the rwy list.
1208 // (However, at the moment there should only be one airplane on the rwy at once, until we
1209 // start allowing planes to line up whilst previous arrival clears the rwy.)
1210 void FGTower::CheckRunwayList(double dt) {
1211         //cout << "Entering CheckRunwayList..." << endl;
1212         if(rwyOccupied) {
1213                 if(!rwyList.size()) {
1214                         rwyOccupied = false;
1215                 } else {
1216                         rwyListItr = rwyList.begin();
1217                         TowerPlaneRec* t = *rwyListItr;
1218                         if(t->isUser) {
1219                                 t->pos.setLongitudeDeg(user_lon_node->getDoubleValue());
1220                                 t->pos.setLatitudeDeg(user_lat_node->getDoubleValue());
1221                                 t->pos.setElevationM(user_elev_node->getDoubleValue());
1222                         } else {
1223                                 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.
1224                         }
1225                         bool on_rwy = OnActiveRunway(t->pos);
1226                         if(!on_rwy) {
1227                                 // TODO - for all of these we need to check what the user is *actually* doing!
1228                                 if((t->opType == INBOUND) || (t->opType == STRAIGHT_IN)) {
1229                                         //cout << "Tower " << ident << " is removing plane " << t->plane.callsign << " from rwy list (vacated)\n";
1230                                         //cout << "Size of rwylist was " << rwyList.size() << '\n';
1231                                         //cout << "Size of vacatedList was " << vacatedList.size() << '\n';
1232                                         RemoveFromRwyList(t->plane.callsign);
1233                                         AddToVacatedList(t);
1234                                         //cout << "Size of rwylist is " << rwyList.size() << '\n';
1235                                         //cout << "Size of vacatedList is " << vacatedList.size() << '\n';
1236                                         // At the moment we wait until Runway Vacated is reported by the plane before telling to contact ground etc.
1237                                         // It's possible we could be a bit more proactive about this.
1238                                 } else if(t->opType == OUTBOUND) {
1239                                         depList.push_back(t);
1240                                         depListItr = depList.begin();
1241                                         rwyList.pop_front();
1242                                         departed = true;
1243                                         timeSinceLastDeparture = 0.0;
1244                                 } else if(t->opType == CIRCUIT) {
1245                                         //cout << ident << " adding " << t->plane.callsign << " to circuitList" << endl;
1246                                         circuitList.push_back(t);
1247                                         circuitListItr = circuitList.begin();
1248                                         AddToTrafficList(t);
1249                                         rwyList.pop_front();
1250                                         departed = true;
1251                                         timeSinceLastDeparture = 0.0;
1252                                 } else if(t->opType == TTT_UNKNOWN) {
1253                                         depList.push_back(t);
1254                                         depListItr = depList.begin();
1255                                         //cout << ident << " adding " << t->plane.callsign << " to circuitList" << endl;
1256                                         circuitList.push_back(t);
1257                                         circuitListItr = circuitList.begin();
1258                                         AddToTrafficList(t);
1259                                         rwyList.pop_front();
1260                                         departed = true;
1261                                         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.
1262                                 } else {
1263                                         // HELP - we shouldn't ever get here!!!
1264                                 }
1265                         }
1266                 }
1267         }
1268         //cout << "Done CheckRunwayList" << endl;
1269 }
1270
1271 // Do one plane from the approach list
1272 void FGTower::CheckApproachList(double dt) {
1273         //cout << "CheckApproachList called for " << ident << endl;
1274         //cout << "AppList.size is " << appList.size() << endl;
1275         if(!appList.empty()) {
1276                 if(appListItr == appList.end()) {
1277                         appListItr = appList.begin();
1278                 }
1279                 TowerPlaneRec* t = *appListItr;
1280                 //cout << "t = " << t << endl;
1281                 //cout << "Checking " << t->plane.callsign << endl;
1282                 if(t->isUser) {
1283                         t->pos.setLongitudeDeg(user_lon_node->getDoubleValue());
1284                         t->pos.setLatitudeDeg(user_lat_node->getDoubleValue());
1285                         t->pos.setElevationM(user_elev_node->getDoubleValue());
1286                 } else {
1287                         // TODO - set/update the position if it's an AI plane
1288                 }
1289                 doThresholdETACalc();   // We need this here because planes in the lists are not guaranteed to *always* have the correct ETA
1290                 //cout << "eta is " << t->eta << ", rwy is " << (rwyList.size() ? "occupied " : "clear ") << '\n';
1291                 SGVec3d tortho = ortho.ConvertToLocal(t->pos);
1292                 if(t->eta < 12 && rwyList.size() && !(t->instructedToGoAround)) {
1293                         // TODO - need to make this more sophisticated 
1294                         // eg. is the plane accelerating down the runway taking off [OK],
1295                         // or stationary near the start [V. BAD!!].
1296                         // For now this should stop the AI plane landing on top of the user.
1297                         tower_plane_rec_list_iterator twrItr;
1298                         twrItr = rwyList.begin();
1299                         TowerPlaneRec* tpr = *twrItr;
1300                         if(strcmp ( tpr->plane.callsign.c_str(), t->plane.callsign.c_str() ) == 0 && rwyList.size() == 1) {
1301                                         // Fixing bug when ATC says that we must go around because of traffic on rwy
1302                                         // but that traffic is we! In future we can use this expression
1303                                         // for other ATC-messages like "On ground at 46, vacate left."
1304
1305                         } else {
1306                                 string trns = t->plane.callsign;
1307                                 trns += " GO AROUND TRAFFIC ON RUNWAY I REPEAT GO AROUND";
1308                                 pending_transmission = trns;
1309                                 ImmediateTransmit();
1310                                 t->instructedToGoAround = true;
1311                                 t->clearedToLand = false;
1312                                 t->nextOnRwy = false;   // But note this is recalculated so don't rely on it
1313                                 // Assume it complies!!!
1314                                 t->opType = CIRCUIT;
1315                                 t->leg = CLIMBOUT;
1316                                 if(!t->isUser) {
1317                                         if(t->planePtr) {
1318                                                 //cout << "Registering Go-around transmission with AI plane\n";
1319                                                 t->planePtr->RegisterTransmission(13);
1320                                         }
1321                                 } else {
1322                                         // TODO - add Go-around ack to comm options,
1323                                         // remove report rwy vacated. (possibly).
1324                                 }
1325                         }
1326                 } else if(t->isUser && t->eta < 90 && tortho.y() > -2500 && t->clearedToLand && t->gearUpReported == false) {
1327                         // Check if gear up or down
1328                         double gp = fgGetFloat("/gear/gear/position-norm");
1329                         if(gp < 1) {
1330                                 string trnsm = t->plane.callsign;
1331                                 sg_srandom_time();
1332                                 int rnd = int(sg_random() * 2) + 1;
1333                                 if(rnd == 2) {                          // Random message for more realistic ATC ;)
1334                                         trnsm += ", LANDING GEAR APPEARS UP!";
1335                                 } else {
1336                                         trnsm += ", Check wheels down and locked.";
1337                                 }
1338                                 pending_transmission = trnsm;
1339                                 ImmediateTransmit();
1340                                 t->gearUpReported = true;
1341                         }
1342                 } else if(t->eta < 90 && !t->clearedToLand) {
1343                         //doThresholdETACalc();
1344                         doThresholdUseOrder();
1345                         // The whip through the appList is a hack since currently t->nextOnRwy doesn't always work
1346                         // TODO - fix this!
1347                         bool cf = false;
1348                         for(tower_plane_rec_list_iterator twrItr = appList.begin(); twrItr != appList.end(); twrItr++) {
1349                                 if((*twrItr)->eta < t->eta) {
1350                                         cf = true;
1351                                 }
1352                         }
1353                         if(t->nextOnRwy && !cf) {
1354                                 if(!rwyList.size()) {
1355                                         string trns = t->plane.callsign;
1356                                         trns += " Cleared to land";
1357                                         pending_transmission = trns;
1358                                         Transmit();
1359                                         //if(t->isUser) cout << "Transmitting cleared to Land!!!\n";
1360                                         t->clearedToLand = true;
1361                                         if(!t->isUser) {
1362                                                 t->planePtr->RegisterTransmission(7);
1363                                         }
1364                                 }
1365                         } else {
1366                                 //if(t->isUser) cout << "Not next\n";
1367                         }
1368                 }
1369                 
1370                 // Check for landing...
1371                 bool landed = false;
1372                 if(!t->isUser) {
1373                         if(t->planePtr) {
1374                                 if(t->planePtr->GetLeg() == LANDING_ROLL) {
1375                                         landed = true;
1376                                 }
1377                         } else {
1378                                 SG_LOG(SG_ATC, SG_ALERT, "WARNING - not user and null planePtr in CheckApproachList!");
1379                         }
1380                 } else {        // user
1381                         if(OnActiveRunway(t->pos)) {
1382                                 landed = true;
1383                         }
1384                 }
1385                 
1386                 if(landed) {
1387                         // Duplicated in CheckCircuitList - must be able to rationalise this somehow!
1388                         //cout << "A " << t->plane.callsign << " has landed, adding to rwyList...\n";
1389                         rwyList.push_front(t);
1390                         // TODO - if(!clearedToLand) shout something!!
1391                         t->clearedToLand = false;
1392                         RemoveFromTrafficList(t->plane.callsign);
1393                         //if(t->isUser) {
1394                                 //      t->opType = TTT_UNKNOWN;
1395                         //}     // TODO - allow the user to specify opType via ATC menu
1396                         appListItr = appList.erase(appListItr);
1397                         if(appListItr == appList.end() ) {
1398                                 appListItr = appList.begin();
1399                         }
1400                         if (appList.empty())
1401                           return;
1402
1403                 }
1404                 
1405                 ++appListItr;
1406         }
1407         //cout << "Done" << endl;
1408 }
1409
1410 // Do one plane from the departure list
1411 void FGTower::CheckDepartureList(double dt) {
1412         if(!depList.empty()) {
1413                 if(depListItr == depList.end()) {
1414                         depListItr = depList.begin();
1415                 }
1416                 TowerPlaneRec* t = *depListItr;
1417                 //cout << "Dep list, checking " << t->plane.callsign;
1418                 
1419                 double distout; // meters
1420                 if(t->isUser) distout = dclGetHorizontalSeparation(_geod, 
1421       SGGeod::fromDegM(user_lon_node->getDoubleValue(), user_lat_node->getDoubleValue(), user_elev_node->getDoubleValue()));
1422                 else distout = dclGetHorizontalSeparation(_geod, t->planePtr->getPos());
1423                 //cout << " distout = " << distout << '\n';
1424                 if(t->isUser && !(t->clearedToTakeOff)) {       // HACK - we use clearedToTakeOff to check if ATC already contacted with plane (and cleared take-off) or not
1425                         if(!OnAnyRunway(SGGeod::fromDegM(user_lon_node->getDoubleValue(), user_lat_node->getDoubleValue(), 0.0), false)) {
1426                                 current_atcdialog->remove_entry(ident, USER_REQUEST_TAKE_OFF, TOWER);
1427                                 t->clearedToTakeOff = true;     // FIXME
1428                         }
1429                 }
1430                 if(distout > 10000) {
1431                         string trns = t->plane.callsign;
1432                         trns += " You are now clear of my airspace, good day";
1433                         pending_transmission = trns;
1434                         Transmit();
1435                         if(t->isUser) {
1436                                 // Change the communication options
1437                                 RemoveAllUserDialogOptions();
1438                                 //cout << "ADD A\n";
1439                                 current_atcdialog->add_entry(ident, "@AP Tower, @CS @MI miles @CD of the airport for full stop@AT", "Contact tower for VFR arrival (full stop)", TOWER, (int)USER_REQUEST_VFR_ARRIVAL_FULL_STOP);
1440                         } else {
1441                                 // Send a clear-of-airspace signal
1442                                 // TODO - implement this once we actually have departing AI traffic (currently all circuits or arrivals).
1443                         }
1444                         RemovePlane(t->plane.callsign);
1445                 } else {
1446                         ++depListItr;
1447                 }
1448         }
1449 }
1450
1451 // ********** End periodic check functions ***********************************************
1452 // ***************************************************************************************
1453
1454
1455 // Remove all dialog options for this tower.
1456 void FGTower::RemoveAllUserDialogOptions() {
1457         current_atcdialog->remove_entry(ident, USER_REQUEST_VFR_DEPARTURE, TOWER);
1458         current_atcdialog->remove_entry(ident, USER_REQUEST_VFR_ARRIVAL, TOWER);
1459         current_atcdialog->remove_entry(ident, USER_REQUEST_VFR_ARRIVAL_FULL_STOP, TOWER);
1460         current_atcdialog->remove_entry(ident, USER_REQUEST_VFR_ARRIVAL_TOUCH_AND_GO, TOWER);
1461         current_atcdialog->remove_entry(ident, USER_REPORT_3_MILE_FINAL, TOWER);
1462         current_atcdialog->remove_entry(ident, USER_REPORT_DOWNWIND, TOWER);
1463         current_atcdialog->remove_entry(ident, USER_REPORT_RWY_VACATED, TOWER);
1464         current_atcdialog->remove_entry(ident, USER_REPORT_GOING_AROUND, TOWER);        
1465         current_atcdialog->remove_entry(ident, USER_REQUEST_TAKE_OFF, TOWER);
1466 }
1467
1468 // Returns true if positions of crosswind/downwind/base leg turns should be constrained by previous traffic
1469 // plus the constraint position as a rwy orientated orthopos (meters)
1470 bool FGTower::GetCrosswindConstraint(double& cpos) {
1471         if(crosswind_leg_pos != 0.0) {
1472                 cpos = crosswind_leg_pos;
1473                 return(true);
1474         } else {
1475                 cpos = 0.0;
1476                 return(false);
1477         }
1478 }
1479 bool FGTower::GetDownwindConstraint(double& dpos) {
1480         if(fabs(downwind_leg_pos) > nominal_downwind_leg_pos) {
1481                 dpos = downwind_leg_pos;
1482                 return(true);
1483         } else {
1484                 dpos = 0.0;
1485                 return(false);
1486         }
1487 }
1488 bool FGTower::GetBaseConstraint(double& bpos) {
1489         if(base_leg_pos < nominal_base_leg_pos) {
1490                 bpos = base_leg_pos;
1491                 return(true);
1492         } else {
1493                 bpos = nominal_base_leg_pos;
1494                 return(false);
1495         }
1496 }
1497
1498
1499 // Figure out which runways are active.
1500 // For now we'll just be simple and do one active runway - eventually this will get much more complex
1501 // This is a private function - public interface to the results of this is through GetActiveRunway
1502 void FGTower::DoRwyDetails() {
1503         //cout << "GetRwyDetails called" << endl;
1504         
1505         // Based on the airport-id and wind get the active runway
1506         
1507   const FGAirport* apt = fgFindAirportID(ident);
1508   if (!apt) {
1509     SG_LOG(SG_ATC, SG_WARN, "FGTower::DoRwyDetails: unknown ICAO:" << ident);
1510     return;
1511   }
1512   
1513         FGRunway* runway = apt->getActiveRunwayForUsage();
1514
1515   activeRwy = runway->ident();
1516   rwy.rwyID = runway->ident();
1517   SG_LOG(SG_ATC, SG_INFO, "In FGGround, active runway for airport " << ident << " is " << activeRwy);
1518   
1519   // Get the threshold position
1520   double other_way = runway->headingDeg() - 180.0;
1521   while(other_way <= 0.0) {
1522     other_way += 360.0;
1523   }
1524     // move to the +l end/center of the runway
1525   //cout << "Runway center is at " << runway._lon << ", " << runway._lat << '\n';
1526   double tshlon = 0.0, tshlat = 0.0, tshr = 0.0;
1527   double tolon = 0.0, tolat = 0.0, tor = 0.0;
1528   rwy.length = runway->lengthM();
1529   geo_direct_wgs_84 ( aptElev, runway->latitude(), runway->longitude(), other_way, 
1530                       rwy.length / 2.0 - 25.0, &tshlat, &tshlon, &tshr );
1531   geo_direct_wgs_84 ( aptElev, runway->latitude(), runway->longitude(), runway->headingDeg(), 
1532                       rwy.length / 2.0 - 25.0, &tolat, &tolon, &tor );
1533   
1534   // Note - 25 meters in from the runway end is a bit of a hack to put the plane ahead of the user.
1535   // now copy what we need out of runway into rwy
1536   rwy.threshold_pos = SGGeod::fromDegM(tshlon, tshlat, aptElev);
1537   SGGeod takeoff_end = SGGeod::fromDegM(tolon, tolat, aptElev);
1538   //cout << "Threshold position = " << tshlon << ", " << tshlat << ", " << aptElev << '\n';
1539   //cout << "Takeoff position = " << tolon << ", " << tolat << ", " << aptElev << '\n';
1540   rwy.hdg = runway->headingDeg();
1541   // Set the projection for the local area based on this active runway
1542   ortho.Init(rwy.threshold_pos, rwy.hdg);       
1543   rwy.end1ortho = ortho.ConvertToLocal(rwy.threshold_pos);      // should come out as zero
1544   rwy.end2ortho = ortho.ConvertToLocal(takeoff_end);
1545   
1546   // Set the pattern direction
1547   // TODO - we'll check for a facilities file with this in eventually - for now assume left traffic except
1548   // for certain circumstances (RH parallel rwy).
1549   rwy.patternDirection = -1;            // Left
1550   if(rwy.rwyID.size() == 3) {
1551     rwy.patternDirection = (rwy.rwyID.substr(2,1) == "R" ? 1 : -1);
1552   }
1553   //cout << "Doing details, rwy.patterDirection is " << rwy.patternDirection << '\n';
1554 }
1555
1556
1557 // Figure out if a given position lies on the active runway
1558 // Might have to change when we consider more than one active rwy.
1559 bool FGTower::OnActiveRunway(const SGGeod& pt) {
1560         // TODO - check that the centre calculation below isn't confused by displaced thesholds etc.
1561         SGVec3d xyc((rwy.end1ortho.x() + rwy.end2ortho.x())/2.0, (rwy.end1ortho.y() + rwy.end2ortho.y())/2.0, 0.0);
1562         SGVec3d xyp = ortho.ConvertToLocal(pt);
1563         
1564         //cout << "Length offset = " << fabs(xyp.y() - xyc.y()) << '\n';
1565         //cout << "Width offset = " << fabs(xyp.x() - xyc.x()) << '\n';
1566
1567         double rlen = rwy.length/2.0 + 5.0;
1568         double rwidth = rwy.width/2.0;
1569         double ldiff = fabs(xyp.y() - xyc.y());
1570         double wdiff = fabs(xyp.x() - xyc.x());
1571
1572         return((ldiff < rlen) && (wdiff < rwidth));
1573 }
1574
1575 // Figure out if a given position lies on any runway or not
1576 // Only call this at startup - reading the runways database is expensive and needs to be fixed!
1577 bool FGTower::OnAnyRunway(const SGGeod& pt, bool onGround) {
1578         ATCData ad;
1579         double dist = current_commlist->FindClosest(_geod, ad, TOWER, 7.0);
1580         if(dist < 0.0) {
1581                 return(false);
1582         }
1583         
1584         // Based on the airport-id, go through all the runways and check for a point in them
1585
1586   const FGAirport* apt = fgFindAirportID(ad.ident);
1587   assert(apt);
1588   
1589   for (unsigned int i=0; i<apt->numRunways(); ++i) {
1590     if (OnRunway(pt, apt->getRunwayByIndex(i))) {
1591       return true;
1592     }
1593   }
1594
1595   // if onGround is true, we only match real runways, so we're done
1596   if (onGround) return false;
1597
1598   // try taxiways as well
1599   for (unsigned int i=0; i<apt->numTaxiways(); ++i) {
1600     if (OnRunway(pt, apt->getTaxiwayByIndex(i))) {
1601       return true;
1602     }
1603   }
1604   
1605         return false;
1606 }
1607
1608
1609 // Returns true if successful
1610 bool FGTower::RemoveFromTrafficList(const string& id) {
1611         tower_plane_rec_list_iterator twrItr;
1612         for(twrItr = trafficList.begin(); twrItr != trafficList.end(); twrItr++) {
1613                 TowerPlaneRec* tpr = *twrItr;
1614                 if(tpr->plane.callsign == id) {
1615                         trafficList.erase(twrItr);
1616                         trafficListItr = trafficList.begin();
1617                         return(true);
1618                 }
1619         }       
1620         SG_LOG(SG_ATC, SG_WARN, "Warning - unable to remove aircraft " << id << " from trafficList in FGTower");
1621         return(false);
1622 }
1623
1624
1625 // Returns true if successful
1626 bool FGTower::RemoveFromAppList(const string& id) {
1627         tower_plane_rec_list_iterator twrItr;
1628         for(twrItr = appList.begin(); twrItr != appList.end(); twrItr++) {
1629                 TowerPlaneRec* tpr = *twrItr;
1630                 if(tpr->plane.callsign == id) {
1631                         appList.erase(twrItr);
1632                         appListItr = appList.begin();
1633                         return(true);
1634                 }
1635         }       
1636         //SG_LOG(SG_ATC, SG_WARN, "Warning - unable to remove aircraft " << id << " from appList in FGTower");
1637         return(false);
1638 }
1639
1640 // Returns true if successful
1641 bool FGTower::RemoveFromRwyList(const string& id) {
1642         tower_plane_rec_list_iterator twrItr;
1643         for(twrItr = rwyList.begin(); twrItr != rwyList.end(); twrItr++) {
1644                 TowerPlaneRec* tpr = *twrItr;
1645                 if(tpr->plane.callsign == id) {
1646                         rwyList.erase(twrItr);
1647                         rwyListItr = rwyList.begin();
1648                         return(true);
1649                 }
1650         }       
1651         //SG_LOG(SG_ATC, SG_WARN, "Warning - unable to remove aircraft " << id << " from rwyList in FGTower");
1652         return(false);
1653 }
1654
1655
1656 // Add a tower plane rec with ETA to the traffic list in the correct position ETA-wise
1657 // and set nextOnRwy if so.
1658 // Returns true if this could cause a threshold ETA conflict with other traffic, false otherwise.
1659 // For planes holding they are put in the first position with time to go, and the return value is
1660 // true if in the first position (nextOnRwy) and false otherwise.
1661 // See the comments in FGTower::doThresholdUseOrder for notes on the ordering
1662 bool FGTower::AddToTrafficList(TowerPlaneRec* t, bool holding) {
1663         //cout << "ADD: " << trafficList.size();
1664         //cout << "AddToTrafficList called, currently size = " << trafficList.size() << ", holding = " << holding << endl;
1665         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.
1666         double departure_sep_time = 60.0;       // Separation time behind departing airplanes.  Comments above also apply.
1667         bool conflict = false;
1668         double lastETA = 0.0;
1669         bool firstTime = true;
1670         // FIXME - make this more robust for different plane types eg. light following heavy.
1671         tower_plane_rec_list_iterator twrItr;
1672         //twrItr = trafficList.begin();
1673         //while(1) {
1674         for(twrItr = trafficList.begin(); twrItr != trafficList.end(); twrItr++) {
1675                 //if(twrItr == trafficList.end()) {
1676                 //      cout << "  END  ";
1677                 //      trafficList.push_back(t);
1678                 //      return(holding ? firstTime : conflict);
1679                 //} else {
1680                         TowerPlaneRec* tpr = *twrItr;
1681                         if(holding) {
1682                                 //cout << (tpr->isUser ? "USER!\n" : "NOT user\n");
1683                                 //cout << "tpr->eta - lastETA = " << tpr->eta - lastETA << '\n';
1684                                 double dep_allowance = (timeSinceLastDeparture < departure_sep_time ? departure_sep_time - timeSinceLastDeparture : 0.0); 
1685                                 double slot_time = (firstTime ? separation_time + dep_allowance : separation_time + departure_sep_time);
1686                                 // separation_time + departure_sep_time in the above accounts for the fact that the arrival could be touch and go,
1687                                 // and if not needs time to clear the rwy anyway.
1688                                 if(tpr->eta  - lastETA > slot_time) {
1689                                         t->nextOnRwy = firstTime;
1690                                         trafficList.insert(twrItr, t);
1691                                         //cout << "\tH\t" << trafficList.size() << '\n';
1692                                         return(firstTime);
1693                                 }
1694                                 firstTime = false;
1695                         } else {
1696                                 if(t->eta < tpr->eta) {
1697                                         // Ugg - this one's tricky.
1698                                         // It depends on what the two planes are doing and whether there's a conflict what we do.
1699                                         if(tpr->eta - t->eta > separation_time) {       // No probs, plane 2 can squeeze in before plane 1 with no apparent conflict
1700                                                 if(tpr->nextOnRwy) {
1701                                                         tpr->nextOnRwy = false;
1702                                                         t->nextOnRwy = true;
1703                                                 }
1704                                                 trafficList.insert(twrItr, t);
1705                                         } else {        // Ooops - this ones tricky - we have a potential conflict!
1706                                                 conflict = true;
1707                                                 // HACK - just add anyway for now and flag conflict - TODO - FIX THIS using CIRCUIT/STRAIGHT_IN and VFR/IFR precedence rules.
1708                                                 if(tpr->nextOnRwy) {
1709                                                         tpr->nextOnRwy = false;
1710                                                         t->nextOnRwy = true;
1711                                                 }
1712                                                 trafficList.insert(twrItr, t);
1713                                         }
1714                                         //cout << "\tC\t" << trafficList.size() << '\n';
1715                                         return(conflict);
1716                                 }
1717                         }
1718                 //}
1719                 //++twrItr;
1720         }
1721         // If we get here we must be at the end of the list, or maybe the list is empty.
1722         if(!trafficList.size()) {
1723                 t->nextOnRwy = true;
1724                 // conflict and firstTime should be false and true respectively in this case anyway.
1725         } else {
1726                 t->nextOnRwy = false;
1727         }
1728         trafficList.push_back(t);
1729         //cout << "\tE\t" << trafficList.size() << endl;
1730         return(holding ? firstTime : conflict);
1731 }
1732
1733 // Add a tower plane rec with ETA to the circuit list in the correct position ETA-wise
1734 // Returns true if this might cause a separation conflict (based on ETA) with other traffic, false otherwise.
1735 // Safe to add a plane that is already in - planes with the same callsign are not added.
1736 bool FGTower::AddToCircuitList(TowerPlaneRec* t) {
1737         if(!t) {
1738                 //cout << "**********************************************\n";
1739                 //cout << "AddToCircuitList called with NULL pointer!!!!!\n";
1740                 //cout << "**********************************************\n";
1741                 return false;
1742         }
1743         //cout << "ADD: " << circuitList.size();
1744         //cout << ident << " AddToCircuitList called for " << t->plane.callsign << ", currently size = " << circuitList.size() << endl;
1745         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.
1746         bool conflict = false;
1747         tower_plane_rec_list_iterator twrItr;
1748         // First check if the plane is already in the list
1749         //cout << "A" << endl;
1750         //cout << "Checking whether " << t->plane.callsign << " is already in circuit list..." << endl;
1751         //cout << "B" << endl;
1752         for(twrItr = circuitList.begin(); twrItr != circuitList.end(); twrItr++) {
1753                 if((*twrItr)->plane.callsign == t->plane.callsign) {
1754                         //cout << "In list - returning...\n";
1755                         return false;
1756                 }
1757         }
1758         //cout << "Not in list - adding..." << endl;
1759         
1760         for(twrItr = circuitList.begin(); twrItr != circuitList.end(); twrItr++) {
1761                 TowerPlaneRec* tpr = *twrItr;
1762                 //cout << tpr->plane.callsign << " eta is " << tpr->eta << '\n';
1763                 //cout << "New eta is " << t->eta << '\n';              
1764                 if(t->eta < tpr->eta) {
1765                         // Ugg - this one's tricky.
1766                         // It depends on what the two planes are doing and whether there's a conflict what we do.
1767                         if(tpr->eta - t->eta > separation_time) {       // No probs, plane 2 can squeeze in before plane 1 with no apparent conflict
1768                                 circuitList.insert(twrItr, t);
1769                                 circuitListItr = circuitList.begin();
1770                         } else {        // Ooops - this ones tricky - we have a potential conflict!
1771                                 conflict = true;
1772                                 // HACK - just add anyway for now and flag conflict.
1773                                 circuitList.insert(twrItr, t);
1774                                 circuitListItr = circuitList.begin();
1775                         }
1776                         //cout << "\tC\t" << circuitList.size() << '\n';
1777                         return(conflict);
1778                 }
1779         }
1780         // If we get here we must be at the end of the list, or maybe the list is empty.
1781         //cout << ident << " adding " << t->plane.callsign << " to circuitList" << endl;
1782         circuitList.push_back(t);       // TODO - check the separation with the preceding plane for the conflict flag.
1783         circuitListItr = circuitList.begin();
1784         //cout << "\tE\t" << circuitList.size() << endl;
1785         return(conflict);
1786 }
1787
1788 // Add to vacated list only if not already present
1789 void FGTower::AddToVacatedList(TowerPlaneRec* t) {
1790         tower_plane_rec_list_iterator twrItr;
1791         bool found = false;
1792         for(twrItr = vacatedList.begin(); twrItr != vacatedList.end(); twrItr++) {
1793                 if((*twrItr)->plane.callsign == t->plane.callsign) {
1794                         found = true;
1795                 }
1796         }
1797         if(found) return;
1798         vacatedList.push_back(t);
1799 }
1800
1801 void FGTower::AddToHoldingList(TowerPlaneRec* t) {
1802         tower_plane_rec_list_iterator it, end = holdList.end();
1803         for (it = holdList.begin(); it != end; ++it) {
1804                 if ((*it)->plane.callsign == t->plane.callsign)
1805                         return;
1806         
1807                 holdList.push_back(t);
1808         }
1809 }
1810
1811 // Calculate the eta of a plane to the threshold.
1812 // For ground traffic this is the fastest they can get there.
1813 // For air traffic this is the middle approximation.
1814 void FGTower::CalcETA(TowerPlaneRec* tpr, bool printout) {
1815         // For now we'll be very crude and hardwire expected speeds to C172-like values
1816         // The speeds below are specified in knots IAS and then converted to m/s
1817         double app_ias = 100.0 * 0.514444;                      // Speed during straight-in approach
1818         double circuit_ias = 80.0 * 0.514444;           // Speed around circuit
1819         double final_ias = 70.0 * 0.514444;             // Speed during final approach
1820         
1821         //if(printout) {
1822         //cout << "In CalcETA, airplane ident = " << tpr->plane.callsign << '\n';
1823         //cout << (tpr->isUser ? "USER\n" : "AI\n");
1824         //cout << flush;
1825         //}
1826         
1827         // Sign convention - dist_out is -ve for approaching planes and +ve for departing planes
1828         // dist_across is +ve in the pattern direction - ie a plane correctly on downwind will have a +ve dist_across
1829         
1830         SGVec3d op = ortho.ConvertToLocal(tpr->pos);
1831         //if(printout) {
1832         //if(!tpr->isUser) cout << "Orthopos is " << op.x() << ", " << op.y() << ' ';
1833         //cout << "opType is " << tpr->opType << '\n';
1834         //}
1835         double dist_out_m = op.y();
1836         double dist_across_m = fabs(op.x());    // The fabs is a hack to cope with the fact that we don't know the circuit direction yet
1837         //cout << "Doing ETA calc for " << tpr->plane.callsign << '\n';
1838         
1839         if(tpr->opType == STRAIGHT_IN || tpr->opType == INBOUND) {
1840                 //cout << "CASE 1\n";
1841                 double dist_to_go_m = sqrt((dist_out_m * dist_out_m) + (dist_across_m * dist_across_m));
1842                 if(dist_to_go_m < 1000) {
1843                         tpr->eta = dist_to_go_m / final_ias;
1844                 } else {
1845                         tpr->eta = (1000.0 / final_ias) + ((dist_to_go_m - 1000.0) / app_ias);
1846                 }
1847         } else if(tpr->opType == CIRCUIT || tpr->opType == TTT_UNKNOWN) {       // Hack alert - UNKNOWN has sort of been added here as a temporary hack.
1848                 //cout << "CASE 2\n";
1849                 // It's complicated - depends on if base leg is delayed or not
1850                 //if(printout) {
1851                 //cout << "Leg = " << tpr->leg << '\n';
1852                 //}
1853                 if(tpr->leg == LANDING_ROLL) {
1854                         tpr->eta = 0;
1855                 } else if((tpr->leg == FINAL) || (tpr->leg == TURN4)) {
1856                         //cout << "dist_out_m = " << dist_out_m << '\n';
1857                         tpr->eta = fabs(dist_out_m) / final_ias;
1858                 } else if((tpr->leg == BASE) || (tpr->leg == TURN3)) {
1859                         tpr->eta = (fabs(dist_out_m) / final_ias) + (dist_across_m / circuit_ias);
1860                 } else {
1861                         // Need to calculate where base leg is likely to be
1862                         // FIXME - for now I'll hardwire it to 1000m which is what AILocalTraffic uses!!!
1863                         // 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
1864                         double nominal_base_dist_out_m = -1000;
1865                         double current_base_dist_out_m;
1866                         if(!GetBaseConstraint(current_base_dist_out_m)) {
1867                                 current_base_dist_out_m = nominal_base_dist_out_m;
1868                         }
1869                         //cout << "current_base_dist_out_m = " << current_base_dist_out_m << '\n';
1870                         double nominal_dist_across_m = 1000;    // Hardwired value from AILocalTraffic
1871                         double current_dist_across_m;
1872                         if(!GetDownwindConstraint(current_dist_across_m)) {
1873                                 current_dist_across_m = nominal_dist_across_m;
1874                         }
1875                         double nominal_cross_dist_out_m = 2000; // Bit of a guess - AI plane turns to crosswind at 700ft agl.
1876                         tpr->eta = fabs(current_base_dist_out_m) / final_ias;   // final
1877                         //cout << "a = " << tpr->eta << '\n';
1878                         if((tpr->leg == DOWNWIND) || (tpr->leg == TURN2)) {
1879                                 tpr->eta += dist_across_m / circuit_ias;
1880                                 //cout << "b = " << tpr->eta << '\n';
1881                                 tpr->eta += fabs(current_base_dist_out_m - dist_out_m) / circuit_ias;
1882                                 //cout << "c = " << tpr->eta << '\n';
1883                         } else if((tpr->leg == CROSSWIND) || (tpr->leg == TURN1)) {
1884                                 //cout << "CROSSWIND calc: ";
1885                                 //cout << tpr->eta << ' ';
1886                                 if(dist_across_m > nominal_dist_across_m) {
1887                                         tpr->eta += dist_across_m / circuit_ias;
1888                                         //cout << "a ";
1889                                 } else {
1890                                         tpr->eta += nominal_dist_across_m / circuit_ias;
1891                                         //cout << "b ";
1892                                 }
1893                                 //cout << tpr->eta << ' ';
1894                                 // should we use the dist across of the previous plane if there is previous still on downwind?
1895                                 //if(printout) cout << "bb = " << tpr->eta << '\n';
1896                                 if(dist_out_m > nominal_cross_dist_out_m) {
1897                                         tpr->eta += fabs(current_base_dist_out_m - dist_out_m) / circuit_ias;
1898                                         //cout << "c ";
1899                                 } else {
1900                                         tpr->eta += fabs(current_base_dist_out_m - nominal_cross_dist_out_m) / circuit_ias;
1901                                         //cout << "d ";
1902                                 }
1903                                 //cout << tpr->eta << ' ';
1904                                 //if(printout) cout << "cc = " << tpr->eta << '\n';
1905                                 if(nominal_dist_across_m > dist_across_m) {
1906                                         tpr->eta += (nominal_dist_across_m - dist_across_m) / circuit_ias;
1907                                         //cout << "e ";
1908                                 } else {
1909                                         // Nothing to add
1910                                         //cout << "f ";
1911                                 }
1912                                 //cout << tpr->eta << '\n';
1913                                 //if(printout) cout << "dd = " << tpr->eta << '\n';
1914                         } else {
1915                                 // We've only just started - why not use a generic estimate?
1916                                 tpr->eta = 240.0;
1917                         }
1918                 }
1919                 //if(printout) {
1920                 //      cout << "ETA = " << tpr->eta << '\n';
1921                 //}
1922                 //if(!tpr->isUser) cout << tpr->plane.callsign << '\t' << tpr->eta << '\n';
1923         } else {
1924                 tpr->eta = 99999;
1925         }       
1926 }
1927
1928
1929 // Calculate the distance of a plane to the threshold in meters
1930 // TODO - Modify to calculate flying distance of a plane in the circuit
1931 double FGTower::CalcDistOutM(TowerPlaneRec* tpr) {
1932         return(dclGetHorizontalSeparation(rwy.threshold_pos, tpr->pos));
1933 }
1934
1935
1936 // Calculate the distance of a plane to the threshold in miles
1937 // TODO - Modify to calculate flying distance of a plane in the circuit
1938 double FGTower::CalcDistOutMiles(TowerPlaneRec* tpr) {
1939         return(CalcDistOutM(tpr) / 1600.0);             // FIXME - use a proper constant if possible.
1940 }
1941
1942
1943 // Iterate through all the lists, update the position of, and call CalcETA for all the planes.
1944 void FGTower::doThresholdETACalc() {
1945         //cout << "Entering doThresholdETACalc..." << endl;
1946         tower_plane_rec_list_iterator twrItr;
1947         // Do the approach list first
1948         for(twrItr = appList.begin(); twrItr != appList.end(); twrItr++) {
1949                 TowerPlaneRec* tpr = *twrItr;
1950                 if(!(tpr->isUser)) tpr->pos = tpr->planePtr->getPos();
1951                 //cout << "APP: ";
1952                 CalcETA(tpr);
1953         }       
1954         // Then the circuit list
1955         //cout << "Circuit list size is " << circuitList.size() << '\n';
1956         for(twrItr = circuitList.begin(); twrItr != circuitList.end(); twrItr++) {
1957                 TowerPlaneRec* tpr = *twrItr;
1958                 if(!(tpr->isUser)) tpr->pos = tpr->planePtr->getPos();
1959                 //cout << "CIRC: ";
1960                 CalcETA(tpr);
1961         }
1962         //cout << "Done doThresholdETCCalc" << endl;
1963 }
1964                 
1965
1966 // Check that the planes in traffic list are correctly ordered,
1967 // that the nearest (timewise) is flagged next on rwy, and return
1968 // true if any threshold use conflicts are detected, false otherwise.
1969 bool FGTower::doThresholdUseOrder() {
1970         //cout << "Entering doThresholdUseOrder..." << endl;
1971         bool conflict = false;
1972         
1973         // Wipe out traffic list, go through circuit, app and hold list, and reorder them in traffic list.
1974         // Here's the rather simplistic assumptions we're using:
1975         // Currently all planes are assumed to be GA light singles with corresponding speeds and separation times.
1976         // In order of priority for runway use:
1977         // STRAIGHT_IN > CIRCUIT > HOLDING_FOR_DEPARTURE
1978         // No modification of planes speeds occurs - conflicts are resolved by delaying turn for base,
1979         // and holding planes until a space.
1980         // When calculating if a holding plane can use the runway, time clearance from last departure
1981         // as well as time clearance to next arrival must be considered.
1982         
1983         trafficList.clear();
1984         
1985         tower_plane_rec_list_iterator twrItr;
1986         // Do the approach list first
1987         //if(ident == "KRHV") cout << "A" << flush;
1988         for(twrItr = appList.begin(); twrItr != appList.end(); twrItr++) {
1989                 TowerPlaneRec* tpr = *twrItr;
1990                 //if(ident == "KRHV") cout << tpr->plane.callsign << '\n';
1991                 conflict = AddToTrafficList(tpr);
1992         }       
1993         // Then the circuit list
1994         //if(ident == "KRHV") cout << "C" << flush;
1995         for(twrItr = circuitList.begin(); twrItr != circuitList.end(); twrItr++) {
1996                 TowerPlaneRec* tpr = *twrItr;
1997                 //if(ident == "KRHV") cout << tpr->plane.callsign << '\n';
1998                 conflict = AddToTrafficList(tpr);
1999         }
2000         // And finally the hold list
2001         //cout << "H" << endl;
2002         for(twrItr = holdList.begin(); twrItr != holdList.end(); twrItr++) {
2003                 TowerPlaneRec* tpr = *twrItr;
2004                 AddToTrafficList(tpr, true);
2005         }
2006         
2007         
2008         if(0) {
2009         //if(ident == "KRHV") {
2010                 cout << "T\n";
2011                 for(twrItr = trafficList.begin(); twrItr != trafficList.end(); twrItr++) {
2012                         TowerPlaneRec* tpr = *twrItr;
2013                         cout << tpr->plane.callsign << '\t' << tpr->eta << '\t';
2014                 }
2015                 cout << endl;
2016         }
2017         
2018         //cout << "Done doThresholdUseOrder" << endl;
2019         return(conflict);
2020 }
2021
2022
2023 // Return the ETA of plane no. list_pos (1-based) in the traffic list.
2024 // i.e. list_pos = 1 implies next to use runway.
2025 double FGTower::GetTrafficETA(unsigned int list_pos, bool printout) {
2026         if(trafficList.size() < list_pos) {
2027                 return(99999);
2028         }
2029
2030         tower_plane_rec_list_iterator twrItr;
2031         twrItr = trafficList.begin();
2032         for(unsigned int i = 1; i < list_pos; i++, twrItr++);
2033         TowerPlaneRec* tpr = *twrItr;
2034         CalcETA(tpr, printout);
2035         //cout << "ETA returned = " << tpr->eta << '\n';
2036         return(tpr->eta);
2037 }
2038         
2039
2040 void FGTower::ContactAtHoldShort(const PlaneRec& plane, FGAIPlane* requestee, tower_traffic_type operation) {
2041         // HACK - assume that anything contacting at hold short is new for now - FIXME LATER
2042         TowerPlaneRec* t = new TowerPlaneRec;
2043         t->plane = plane;
2044         t->planePtr = requestee;
2045         t->holdShortReported = true;
2046         t->clearedToLineUp = false;
2047         t->clearedToTakeOff = false;
2048         t->opType = operation;
2049         t->pos = requestee->getPos();
2050         
2051         //cout << "Hold Short reported by " << plane.callsign << '\n';
2052         SG_LOG(SG_ATC, SG_BULK, "Hold Short reported by " << plane.callsign);
2053
2054 /*      
2055         bool next = AddToTrafficList(t, true);
2056         if(next) {
2057                 double teta = GetTrafficETA(2);
2058                 if(teta < 150.0) {
2059                         t->clearanceCounter = 7.0;      // This reduces the delay before response to 3 secs if an immediate takeoff is reqd
2060                         //cout << "Reducing response time to request due imminent traffic\n";
2061                 }
2062         } else {
2063         }
2064 */
2065         // TODO - possibly add the reduced interval to clearance when immediate back in under the new scheme
2066
2067         holdList.push_back(t);
2068         
2069         responseReqd = true;
2070 }
2071
2072 // Register the presence of an AI plane at a point where contact would already have been made in real life
2073 // CAUTION - currently it is assumed that this plane's callsign is unique - it is up to AIMgr to generate unique callsigns.
2074 void FGTower::RegisterAIPlane(const PlaneRec& plane, FGAIPlane* ai, const tower_traffic_type& op, const PatternLeg& lg) {
2075         // At the moment this is only going to be tested with inserting an AI plane on downwind
2076         TowerPlaneRec* t = new TowerPlaneRec;
2077         t->plane = plane;
2078         t->planePtr = ai;
2079         t->opType = op;
2080         t->leg = lg;
2081         t->pos = ai->getPos();
2082         
2083         CalcETA(t);
2084         
2085         if(op == CIRCUIT && lg != LEG_UNKNOWN) {
2086                 AddToCircuitList(t);
2087         } else {
2088                 // FLAG A WARNING
2089         }
2090         
2091         doThresholdUseOrder();
2092 }
2093
2094 void FGTower::DeregisterAIPlane(const string& id) {
2095         RemovePlane(id);
2096 }
2097
2098 // Contact tower for VFR approach
2099 // eg "Cessna Charlie Foxtrot Golf Foxtrot Sierra eight miles South of the airport for full stop with Bravo"
2100 // This function probably only called via user interaction - AI planes will have an overloaded function taking a planerec.
2101 // opt defaults to AIP_LT_UNKNOWN
2102 void FGTower::VFRArrivalContact(const string& ID, const LandingType& opt) {
2103         //cout << "USER Request Landing Clearance called for ID " << ID << '\n';
2104         
2105         // For now we'll assume that the user is a light plane and can get him/her to join the circuit if necessary.
2106
2107         TowerPlaneRec* t;       
2108         string usercall = fgGetString("/sim/user/callsign");
2109         if(ID == "USER" || ID == usercall) {
2110                 t = FindPlane(usercall);
2111                 if(!t) {
2112                         //cout << "NOT t\n";
2113                         t = new TowerPlaneRec;
2114                         t->isUser = true;
2115                         t->pos.setLongitudeDeg(user_lon_node->getDoubleValue());
2116                         t->pos.setLatitudeDeg(user_lat_node->getDoubleValue());
2117                         t->pos.setElevationM(user_elev_node->getDoubleValue());
2118                 } else {
2119                         //cout << "IS t\n";
2120                         // Oops - the plane is already registered with this tower - maybe we took off and flew a giant circuit without
2121                         // quite getting out of tower airspace - just ignore for now and treat as new arrival.
2122                         // TODO - Maybe should remove from departure and circuit list if in there though!!
2123                 }
2124         } else {
2125                 // Oops - something has gone wrong - put out a warning
2126                 cout << "WARNING - FGTower::VFRContact(string ID, LandingType lt) called with ID " << ID << " which does not appear to be the user.\n";
2127                 return;
2128         }
2129                 
2130         
2131         // TODO
2132         // Calculate where the plane is in relation to the active runway and it's circuit
2133         // and set the op-type as appropriate.
2134         
2135         // HACK - to get up and running I'm going to assume that the user contacts tower on a staight-in final for now.
2136         t->opType = STRAIGHT_IN;
2137         
2138         t->plane.type = GA_SINGLE;      // FIXME - Another assumption!
2139         t->plane.callsign = usercall;
2140         CalcETA(t);
2141         
2142         t->vfrArrivalReported = true;
2143         responseReqd = true;
2144         
2145         appList.push_back(t);   // Not necessarily permanent
2146         appListItr = appList.begin();
2147         AddToTrafficList(t);
2148         
2149         current_atcdialog->remove_entry(ident, USER_REQUEST_VFR_ARRIVAL, TOWER);
2150         current_atcdialog->remove_entry(ident, USER_REQUEST_VFR_ARRIVAL_FULL_STOP, TOWER);
2151         current_atcdialog->remove_entry(ident, USER_REQUEST_VFR_ARRIVAL_TOUCH_AND_GO, TOWER);
2152 }
2153
2154 // landingType defaults to AIP_LT_UNKNOWN
2155 void FGTower::VFRArrivalContact(const PlaneRec& plane, FGAIPlane* requestee, const LandingType& lt) {
2156         //cout << "VFRArrivalContact called for plane " << plane.callsign << " at " << ident << '\n';
2157         // Possible hack - assume this plane is new for now - TODO - should check really
2158         TowerPlaneRec* t = new TowerPlaneRec;
2159         t->plane = plane;
2160         t->planePtr = requestee;
2161         t->landingType = lt;
2162         t->pos = requestee->getPos();
2163         
2164         //cout << "Hold Short reported by " << plane.callsign << '\n';
2165         SG_LOG(SG_ATC, SG_BULK, "VFR arrival contact made by " << plane.callsign);
2166         //cout << "VFR arrival contact made by " << plane.callsign << '\n';
2167
2168         // HACK - to get up and running I'm going to assume a staight-in final for now.
2169         t->opType = STRAIGHT_IN;
2170         
2171         t->vfrArrivalReported = true;
2172         responseReqd = true;
2173         
2174         //cout << "Before adding, appList.size = " << appList.size() << " at " << ident << '\n';
2175         appList.push_back(t);   // Not necessarily permanent
2176         appListItr = appList.begin();
2177         //cout << "After adding, appList.size = " << appList.size() << " at " << ident << '\n';
2178         AddToTrafficList(t);
2179 }
2180
2181 void FGTower::RequestDepartureClearance(const string& ID) {
2182         //cout << "Request Departure Clearance called...\n";
2183 }
2184
2185 void FGTower::RequestTakeOffClearance(const string& ID) {
2186         string uid=ID;
2187         if(ID == "USER") {
2188                 uid = fgGetString("/sim/user/callsign");
2189                 current_atcdialog->remove_entry(ident, USER_REQUEST_TAKE_OFF, TOWER);
2190         }
2191         TowerPlaneRec* t = FindPlane(uid);
2192         if(t) {
2193                 if(!(t->clearedToTakeOff)) {
2194                         departed = false;
2195                         t->lineUpReported=true;
2196                         responseReqd = true;
2197                 }
2198         }
2199         else {
2200                 SG_LOG(SG_ATC, SG_WARN, "WARNING: Unable to find plane " << ID << " in FGTower::RequestTakeOffClearance(...)");
2201         }
2202 }
2203         
2204 void FGTower::ReportFinal(const string& ID) {
2205         //cout << "Report Final Called at tower " << ident << " by plane " << ID << '\n';
2206         string uid=ID;
2207         if(ID == "USER") {
2208                 uid = fgGetString("/sim/user/callsign");
2209                 current_atcdialog->remove_entry(ident, USER_REPORT_3_MILE_FINAL, TOWER);
2210         }
2211         TowerPlaneRec* t = FindPlane(uid);
2212         if(t) {
2213                 t->finalReported = true;
2214                 t->finalAcknowledged = false;
2215                 if(!(t->clearedToLand)) {
2216                         responseReqd = true;
2217                 } else {
2218                         // possibly respond with wind even if already cleared to land?
2219                         t->finalReported = false;
2220                         t->finalAcknowledged = true;
2221                         // HACK!! - prevents next reporting being misinterpreted as this one.
2222                 }
2223         } else {
2224                 SG_LOG(SG_ATC, SG_WARN, "WARNING: Unable to find plane " << ID << " in FGTower::ReportFinal(...)");
2225         }
2226 }
2227
2228 void FGTower::ReportLongFinal(const string& ID) {
2229         string uid=ID;
2230         if(ID == "USER") {
2231                 uid = fgGetString("/sim/user/callsign");
2232                 current_atcdialog->remove_entry(ident, USER_REPORT_3_MILE_FINAL, TOWER);
2233         }
2234         TowerPlaneRec* t = FindPlane(uid);
2235         if(t) {
2236                 t->longFinalReported = true;
2237                 t->longFinalAcknowledged = false;
2238                 if(!(t->clearedToLand)) {
2239                         responseReqd = true;
2240                 } // possibly respond with wind even if already cleared to land?
2241         } else {
2242                 SG_LOG(SG_ATC, SG_WARN, "WARNING: Unable to find plane " << ID << " in FGTower::ReportLongFinal(...)");
2243         }
2244 }
2245
2246 //void FGTower::ReportOuterMarker(string ID);
2247 //void FGTower::ReportMiddleMarker(string ID);
2248 //void FGTower::ReportInnerMarker(string ID);
2249
2250 void FGTower::ReportRunwayVacated(const string& ID) {
2251         //cout << "Report Runway Vacated Called at tower " << ident << " by plane " << ID << '\n';
2252         string uid=ID;
2253         if(ID == "USER") {
2254                 uid = fgGetString("/sim/user/callsign");
2255                 current_atcdialog->remove_entry(ident, USER_REPORT_RWY_VACATED, TOWER);
2256         }
2257         TowerPlaneRec* t = FindPlane(uid);
2258         if(t) {
2259                 //cout << "Found it...\n";
2260                 t->rwyVacatedReported = true;
2261                 responseReqd = true;
2262         } else {
2263                 SG_LOG(SG_ATC, SG_WARN, "WARNING: Unable to find plane " << ID << " in FGTower::ReportRunwayVacated(...)");
2264                 SG_LOG(SG_ATC, SG_ALERT, "A WARNING: Unable to find plane " << ID << " in FGTower::ReportRunwayVacated(...)");
2265                 //cout << "WARNING: Unable to find plane " << ID << " in FGTower::ReportRunwayVacated(...)\n";
2266         }
2267 }
2268
2269 TowerPlaneRec* FGTower::FindPlane(const string& ID) {
2270         //cout << "FindPlane called for " << ID << "...\n";
2271         tower_plane_rec_list_iterator twrItr;
2272         // Do the approach list first
2273         for(twrItr = appList.begin(); twrItr != appList.end(); twrItr++) {
2274                 //cout << "appList callsign is " << (*twrItr)->plane.callsign << '\n';
2275                 if((*twrItr)->plane.callsign == ID) return(*twrItr);
2276         }       
2277         // Then the circuit list
2278         for(twrItr = circuitList.begin(); twrItr != circuitList.end(); twrItr++) {
2279                 //cout << "circuitList callsign is " << (*twrItr)->plane.callsign << '\n';
2280                 if((*twrItr)->plane.callsign == ID) return(*twrItr);
2281         }
2282         // Then the runway list
2283         //cout << "rwyList.size() is " << rwyList.size() << '\n';
2284         for(twrItr = rwyList.begin(); twrItr != rwyList.end(); twrItr++) {
2285                 //cout << "rwyList callsign is " << (*twrItr)->plane.callsign << '\n';
2286                 if((*twrItr)->plane.callsign == ID) return(*twrItr);
2287         }
2288         // The hold list
2289         for(twrItr = holdList.begin(); twrItr != holdList.end(); twrItr++) {
2290                 if((*twrItr)->plane.callsign == ID) return(*twrItr);
2291         }
2292         // And finally the vacated list
2293         for(twrItr = vacatedList.begin(); twrItr != vacatedList.end(); twrItr++) {
2294                 //cout << "vacatedList callsign is " << (*twrItr)->plane.callsign << '\n';
2295                 if((*twrItr)->plane.callsign == ID) return(*twrItr);
2296         }
2297         SG_LOG(SG_ATC, SG_WARN, "Unable to find " << ID << " in FGTower::FindPlane(...)");
2298         //exit(-1);
2299         return(NULL);
2300 }
2301
2302 void FGTower::RemovePlane(const string& ID) {
2303         //cout << ident << " RemovePlane called for " << ID << '\n';
2304         // We have to be careful here - we want to erase the plane from all lists it is in,
2305         // but we can only delete it once, AT THE END.
2306         TowerPlaneRec* t = NULL;
2307         tower_plane_rec_list_iterator twrItr;
2308         for(twrItr = appList.begin(); twrItr != appList.end();) {
2309                 if((*twrItr)->plane.callsign == ID) {
2310                         t = *twrItr;
2311                         twrItr = appList.erase(twrItr);
2312                         appListItr = appList.begin();
2313                         // HACK: aircraft are sometimes more than once in a list, so we need to
2314                         // remove them all before we can delete the TowerPlaneRec class
2315                         //break;
2316                 } else
2317                         ++twrItr;
2318         }
2319         for(twrItr = depList.begin(); twrItr != depList.end();) {
2320                 if((*twrItr)->plane.callsign == ID) {
2321                         t = *twrItr;
2322                         twrItr = depList.erase(twrItr);
2323                         depListItr = depList.begin();
2324                 } else
2325                         ++twrItr;
2326         }
2327         for(twrItr = circuitList.begin(); twrItr != circuitList.end();) {
2328                 if((*twrItr)->plane.callsign == ID) {
2329                         t = *twrItr;
2330                         twrItr = circuitList.erase(twrItr);
2331                         circuitListItr = circuitList.begin();
2332                 } else
2333                         ++twrItr;
2334         }
2335         for(twrItr = holdList.begin(); twrItr != holdList.end();) {
2336                 if((*twrItr)->plane.callsign == ID) {
2337                         t = *twrItr;
2338                         twrItr = holdList.erase(twrItr);
2339                         holdListItr = holdList.begin();
2340                 } else
2341                         ++twrItr;
2342         }
2343         for(twrItr = rwyList.begin(); twrItr != rwyList.end();) {
2344                 if((*twrItr)->plane.callsign == ID) {
2345                         t = *twrItr;
2346                         twrItr = rwyList.erase(twrItr);
2347                         rwyListItr = rwyList.begin();
2348                 } else
2349                         ++twrItr;
2350         }
2351         for(twrItr = vacatedList.begin(); twrItr != vacatedList.end();) {
2352                 if((*twrItr)->plane.callsign == ID) {
2353                         t = *twrItr;
2354                         twrItr = vacatedList.erase(twrItr);
2355                         vacatedListItr = vacatedList.begin();
2356                 } else
2357                         ++twrItr;
2358         }
2359         for(twrItr = trafficList.begin(); twrItr != trafficList.end();) {
2360                 if((*twrItr)->plane.callsign == ID) {
2361                         t = *twrItr;
2362                         twrItr = trafficList.erase(twrItr);
2363                         trafficListItr = trafficList.begin();
2364                 } else
2365                         ++twrItr;
2366         }
2367         // And finally, delete the record.
2368         delete t;
2369 }
2370
2371 void FGTower::ReportDownwind(const string& ID) {
2372         //cout << "ReportDownwind(...) called\n";
2373         string uid=ID;
2374         if(ID == "USER") {
2375                 uid = fgGetString("/sim/user/callsign");
2376                 current_atcdialog->remove_entry(ident, USER_REPORT_DOWNWIND, TOWER);
2377         }
2378         TowerPlaneRec* t = FindPlane(uid);
2379         if(t) {
2380                 t->downwindReported = true;
2381                 responseReqd = true;
2382                 // If the plane is in the app list, remove it and put it in the circuit list instead.
2383                 // Ideally we might want to do this at the 2 mile report prior to 45 deg entry, but at
2384                 // the moment that would b&gg?r up the constraint position calculations.
2385                 RemoveFromAppList(ID);
2386                 t->leg = DOWNWIND;
2387                 if(t->isUser) {
2388                         t->pos.setLongitudeDeg(user_lon_node->getDoubleValue());
2389                         t->pos.setLatitudeDeg(user_lat_node->getDoubleValue());
2390                         t->pos.setElevationM(user_elev_node->getDoubleValue());
2391                 } else {
2392                         // ASSERT(t->planePtr != NULL);
2393                         t->pos = t->planePtr->getPos();
2394                 }
2395                 CalcETA(t);
2396                 AddToCircuitList(t);
2397         } else {
2398                 SG_LOG(SG_ATC, SG_WARN, "WARNING: Unable to find plane " << ID << " in FGTower::ReportDownwind(...)");
2399         }
2400 }
2401
2402 void FGTower::ReportGoingAround(const string& ID) {
2403         string uid=ID;
2404         if(ID == "USER") {
2405                 uid = fgGetString("/sim/user/callsign");
2406                 RemoveAllUserDialogOptions();   // TODO - it would be much more efficient if ATCDialog simply had a clear() function!!!
2407                 current_atcdialog->add_entry(ident, "@AP Tower, @CS Downwind @RW", "Report Downwind", TOWER, (int)USER_REPORT_DOWNWIND);
2408         }
2409         TowerPlaneRec* t = FindPlane(uid);
2410         if(t) {
2411                 //t->goAroundReported = true;  // No need to set this until we start responding to it.
2412                 responseReqd = false;   // might change in the future but for now we'll not distract them during the go-around.
2413                 // If the plane is in the app list, remove it and put it in the circuit list instead.
2414                 RemoveFromAppList(ID);
2415                 t->leg = CLIMBOUT;
2416                 if(t->isUser) {
2417                         t->pos.setLongitudeDeg(user_lon_node->getDoubleValue());
2418                         t->pos.setLatitudeDeg(user_lat_node->getDoubleValue());
2419                         t->pos.setElevationM(user_elev_node->getDoubleValue());
2420                 } else {
2421                         // ASSERT(t->planePtr != NULL);
2422                         t->pos = t->planePtr->getPos();
2423                 }
2424                 CalcETA(t);
2425                 AddToCircuitList(t);
2426         } else {
2427                 SG_LOG(SG_ATC, SG_WARN, "WARNING: Unable to find plane " << ID << " in FGTower::ReportDownwind(...)");
2428         }
2429 }
2430
2431 string FGTower::GenText(const string& m, int c) {
2432         const int cmax = 300;
2433         //string message;
2434         char tag[4];
2435         char crej = '@';
2436         char mes[cmax];
2437         char dum[cmax];
2438         //char buf[10];
2439         char *pos;
2440         int len;
2441         //FGTransmission t;
2442         string usercall = fgGetString("/sim/user/callsign");
2443         TowerPlaneRec* t = FindPlane(responseID);
2444         
2445         //transmission_list_type     tmissions = transmissionlist_station[station];
2446         //transmission_list_iterator current   = tmissions.begin();
2447         //transmission_list_iterator last      = tmissions.end();
2448         
2449         //for ( ; current != last ; ++current ) {
2450         //      if ( current->get_code().c1 == code.c1 &&  
2451         //              current->get_code().c2 == code.c2 &&
2452         //          current->get_code().c3 == code.c3 ) {
2453                         
2454                         //if ( ttext ) message = current->get_transtext();
2455                         //else message = current->get_menutext();
2456                         strcpy( &mes[0], m.c_str() ); 
2457                         
2458                         // Replace all the '@' parameters with the actual text.
2459                         int check = 0;  // If mes gets overflowed the while loop can go infinite
2460                         double gp = fgGetFloat("/gear/gear/position-norm");
2461                         while ( strchr(&mes[0], crej) != NULL  ) {      // ie. loop until no more occurances of crej ('@') found
2462                                 pos = strchr( &mes[0], crej );
2463                                 memmove(&tag[0], pos, 3);
2464                                 tag[3] = '\0';
2465                                 int i;
2466                                 len = 0;
2467                                 for ( i=0; i<cmax; i++ ) {
2468                                         if ( mes[i] == crej ) {
2469                                                 len = i; 
2470                                                 break;
2471                                         }
2472                                 }
2473                                 strncpy( &dum[0], &mes[0], len );
2474                                 dum[len] = '\0';
2475                                 
2476                                 if ( strcmp ( tag, "@ST" ) == 0 )
2477                                         //strcat( &dum[0], tpars.station.c_str() );
2478                                         strcat(&dum[0], ident.c_str());
2479                                 else if ( strcmp ( tag, "@AP" ) == 0 )
2480                                         //strcat( &dum[0], tpars.airport.c_str() );
2481                                         strcat(&dum[0], name.c_str());
2482                                 else if ( strcmp ( tag, "@CS" ) == 0 ) 
2483                                         //strcat( &dum[0], tpars.callsign.c_str() );
2484                                         strcat(&dum[0], usercall.c_str());
2485                                 else if ( strcmp ( tag, "@TD" ) == 0 ) {
2486                                         /*
2487                                         if ( tpars.tdir == 1 ) {
2488                                                 char buf[] = "left";
2489                                                 strcat( &dum[0], &buf[0] );
2490                                         }
2491                                         else {
2492                                                 char buf[] = "right";
2493                                                 strcat( &dum[0], &buf[0] );
2494                                         }
2495                                         */
2496                                 }
2497                                 else if ( strcmp ( tag, "@HE" ) == 0 ) {
2498                                         /*
2499                                         char buf[10];
2500                                         sprintf( buf, "%i", (int)(tpars.heading) );
2501                                         strcat( &dum[0], &buf[0] );
2502                                         */
2503                                 }
2504                                 else if ( strcmp ( tag, "@AT" ) == 0 ) {        // ATIS ID
2505                                         /*
2506                                         char buf[10];
2507                                         sprintf( buf, "%i", (int)(tpars.heading) );
2508                                         strcat( &dum[0], &buf[0] );
2509                                         */
2510                                         double f = globals->get_ATC_mgr()->GetFrequency(ident, ATIS) / 100.0;
2511                                         if(f) {
2512                                                 string atis_id;
2513                                                 atis_id = ", information " + GetATISID();
2514                                                 strcat( &dum[0], atis_id.c_str() );
2515                                         }
2516                                 }
2517                                 else if ( strcmp ( tag, "@VD" ) == 0 ) {
2518                                         /*
2519                                         if ( tpars.VDir == 1 ) {
2520                                                 char buf[] = "Descend and maintain";
2521                                                 strcat( &dum[0], &buf[0] );
2522                                         }
2523                                         else if ( tpars.VDir == 2 ) {
2524                                                 char buf[] = "Maintain";
2525                                                 strcat( &dum[0], &buf[0] );
2526                                         }
2527                                         else if ( tpars.VDir == 3 ) {
2528                                                 char buf[] = "Climb and maintain";
2529                                                 strcat( &dum[0], &buf[0] );
2530                                         } 
2531                                         */
2532                                 }
2533                                 else if ( strcmp ( tag, "@AL" ) == 0 ) {
2534                                         /*
2535                                         char buf[10];
2536                                         sprintf( buf, "%i", (int)(tpars.alt) );
2537                                         strcat( &dum[0], &buf[0] );
2538                                         */
2539                                 }
2540                                 else if ( strcmp ( tag, "@TO" ) == 0 ) {      // Requesting take-off or departure clearance
2541                                         string tmp;
2542                                         if (rwyOccupied) {
2543                                                 tmp = "Ready for take-off";
2544                                         } else {
2545                                                 if (OnAnyRunway(SGGeod::fromDegM(user_lon_node->getDoubleValue(),
2546                                                                 user_lat_node->getDoubleValue(), 0.0),true)) {
2547                                                         tmp = "Request take-off clearance";
2548                                                 } else {
2549                                                         tmp = "Request departure clearance";
2550                                                 }
2551                                         }
2552                                         strcat(&dum[0], tmp.c_str());
2553                                 }
2554                                 else if ( strcmp ( tag, "@MI" ) == 0 ) {
2555                                         char buf[10];
2556                                         //sprintf( buf, "%3.1f", tpars.miles );
2557                                         int dist_miles = (int)dclGetHorizontalSeparation(_geod, SGGeod::fromDegM(user_lon_node->getDoubleValue(), user_lat_node->getDoubleValue(), user_elev_node->getDoubleValue())) / 1600;
2558                                         sprintf(buf, "%i", dist_miles);
2559                                         strcat( &dum[0], &buf[0] );
2560                                 }
2561                                 else if ( strcmp ( tag, "@FR" ) == 0 ) {
2562                                         /*
2563                                         char buf[10];
2564                                         sprintf( buf, "%6.2f", tpars.freq );
2565                                         strcat( &dum[0], &buf[0] );
2566                                         */
2567                                 }
2568                                 else if ( strcmp ( tag, "@RW" ) == 0 ) {
2569                                         strcat(&dum[0], ConvertRwyNumToSpokenString(activeRwy).c_str());
2570                                 }
2571                                 else if ( strcmp ( tag, "@GR" ) == 0 ) {        // Gear position (on final)
2572                                         if(t->gearWasUp && gp > 0.99) {
2573                                                 strcat(&dum[0], ", gear down, ready to land.");
2574                                         }
2575                                 }
2576                                 else if(strcmp(tag, "@CD") == 0) {      // @CD = compass direction
2577                                         double h = GetHeadingFromTo(_geod, SGGeod::fromDegM(user_lon_node->getDoubleValue(), user_lat_node->getDoubleValue(), user_elev_node->getDoubleValue()));
2578                                         while(h < 0.0) h += 360.0;
2579                                         while(h > 360.0) h -= 360.0;
2580                                         if(h < 22.5 || h > 337.5) {
2581                                                 strcat(&dum[0], "North");
2582                                         } else if(h < 67.5) {
2583                                                 strcat(&dum[0], "North-East");
2584                                         } else if(h < 112.5) {
2585                                                 strcat(&dum[0], "East");
2586                                         } else if(h < 157.5) {
2587                                                 strcat(&dum[0], "South-East");
2588                                         } else if(h < 202.5) {
2589                                                 strcat(&dum[0], "South");
2590                                         } else if(h < 247.5) {
2591                                                 strcat(&dum[0], "South-West");
2592                                         } else if(h < 292.5) {
2593                                                 strcat(&dum[0], "West");
2594                                         } else {
2595                                                 strcat(&dum[0], "North-West");
2596                                         }
2597                                 } else {
2598                                         cout << "Tag " << tag << " not found" << endl;
2599                                         break;
2600                                 }
2601                                 strcat( &dum[0], &mes[len+3] );
2602                                 strcpy( &mes[0], &dum[0] );
2603                                 
2604                                 ++check;
2605                                 if(check > 10) {
2606                                         SG_LOG(SG_ATC, SG_WARN, "WARNING: Possibly endless loop terminated in FGTransmissionlist::gen_text(...)"); 
2607                                         break;
2608                                 }
2609                         }
2610                         
2611                         //cout << mes  << endl;  
2612                         //break;
2613                 //}
2614         //}
2615         return mes[0] ? mes : "No transmission found";
2616 }
2617
2618 string FGTower::GetWeather() {
2619         std::ostringstream msg;
2620
2621         // wind
2622         double hdg = wind_from_hdg->getDoubleValue();
2623         double speed = wind_speed_knots->getDoubleValue();
2624         if (speed < 1)
2625                 msg << "wind calm";
2626         else
2627                 msg << "wind " << int(hdg) << " degrees at " << int(speed) << " knots";
2628
2629         // visibility
2630         double visibility = fgGetDouble("/environment/visibility-m");
2631         if (visibility < 10000)
2632                 msg << ", visibility " << int(visibility / 1609) << " miles";
2633
2634         // pressure / altimeter
2635         double pressure = fgGetDouble("/environment/pressure-sea-level-inhg");
2636         msg << ", QFE " << fixed << setprecision(2) << pressure << ".";
2637
2638         return msg.str();
2639 }
2640
2641 string FGTower::GetATISID() {
2642         double tstamp = atof(fgGetString("sim/time/elapsed-sec"));
2643         const int minute(60);                   // in SI units
2644         int interval = ATIS ? 60*minute : 2*minute;     // AWOS updated frequently
2645         int sequence = current_commlist->GetAtisSequence(ident, 
2646                               tstamp, interval);
2647
2648         return GetPhoneticLetter(sequence);  // the sequence letter
2649 }
2650
2651 ostream& operator << (ostream& os, tower_traffic_type ttt) {
2652         switch(ttt) {
2653         case(CIRCUIT):      return(os << "CIRCUIT");
2654         case(INBOUND):      return(os << "INBOUND");
2655         case(OUTBOUND):     return(os << "OUTBOUND");
2656         case(TTT_UNKNOWN):  return(os << "UNKNOWN");
2657         case(STRAIGHT_IN):  return(os << "STRAIGHT_IN");
2658         }
2659         return(os << "ERROR - Unknown switch in tower_traffic_type operator << ");
2660 }
2661