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