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