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