]> git.mxchange.org Git - flightgear.git/blob - src/ATCDCL/tower.cxx
A generic, configurable autobrake system. Not fully features yet, but works quite...
[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(SGGeod::fromDegM(lon, lat, elev), SGGeod::fromDegM(user_lon_node->getDoubleValue(), user_lat_node->getDoubleValue(), user_elev_node->getDoubleValue()));
1413                 else distout = dclGetHorizontalSeparation(SGGeod::fromDegM(lon, lat, elev), t->planePtr->getPos());
1414                 //cout << " distout = " << distout << '\n';
1415                 if(t->isUser && !(t->clearedToTakeOff)) {       // HACK - we use clearedToTakeOff to check if ATC already contacted with plane (and cleared take-off) or not
1416                         if(!OnAnyRunway(SGGeod::fromDegM(user_lon_node->getDoubleValue(), user_lat_node->getDoubleValue(), 0.0), false)) {
1417                                 current_atcdialog->remove_entry(ident, USER_REQUEST_TAKE_OFF, TOWER);
1418                                 t->clearedToTakeOff = true;     // FIXME
1419                         }
1420                 }
1421                 if(distout > 10000) {
1422                         string trns = t->plane.callsign;
1423                         trns += " You are now clear of my airspace, good day";
1424                         pending_transmission = trns;
1425                         Transmit();
1426                         if(t->isUser) {
1427                                 // Change the communication options
1428                                 RemoveAllUserDialogOptions();
1429                                 //cout << "ADD A\n";
1430                                 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);
1431                         } else {
1432                                 // Send a clear-of-airspace signal
1433                                 // TODO - implement this once we actually have departing AI traffic (currently all circuits or arrivals).
1434                         }
1435                         RemovePlane(t->plane.callsign);
1436                 } else {
1437                         ++depListItr;
1438                 }
1439         }
1440 }
1441
1442 // ********** End periodic check functions ***********************************************
1443 // ***************************************************************************************
1444
1445
1446 // Remove all dialog options for this tower.
1447 void FGTower::RemoveAllUserDialogOptions() {
1448         current_atcdialog->remove_entry(ident, USER_REQUEST_VFR_DEPARTURE, TOWER);
1449         current_atcdialog->remove_entry(ident, USER_REQUEST_VFR_ARRIVAL, TOWER);
1450         current_atcdialog->remove_entry(ident, USER_REQUEST_VFR_ARRIVAL_FULL_STOP, TOWER);
1451         current_atcdialog->remove_entry(ident, USER_REQUEST_VFR_ARRIVAL_TOUCH_AND_GO, TOWER);
1452         current_atcdialog->remove_entry(ident, USER_REPORT_3_MILE_FINAL, TOWER);
1453         current_atcdialog->remove_entry(ident, USER_REPORT_DOWNWIND, TOWER);
1454         current_atcdialog->remove_entry(ident, USER_REPORT_RWY_VACATED, TOWER);
1455         current_atcdialog->remove_entry(ident, USER_REPORT_GOING_AROUND, TOWER);        
1456         current_atcdialog->remove_entry(ident, USER_REQUEST_TAKE_OFF, TOWER);
1457 }
1458
1459 // Returns true if positions of crosswind/downwind/base leg turns should be constrained by previous traffic
1460 // plus the constraint position as a rwy orientated orthopos (meters)
1461 bool FGTower::GetCrosswindConstraint(double& cpos) {
1462         if(crosswind_leg_pos != 0.0) {
1463                 cpos = crosswind_leg_pos;
1464                 return(true);
1465         } else {
1466                 cpos = 0.0;
1467                 return(false);
1468         }
1469 }
1470 bool FGTower::GetDownwindConstraint(double& dpos) {
1471         if(fabs(downwind_leg_pos) > nominal_downwind_leg_pos) {
1472                 dpos = downwind_leg_pos;
1473                 return(true);
1474         } else {
1475                 dpos = 0.0;
1476                 return(false);
1477         }
1478 }
1479 bool FGTower::GetBaseConstraint(double& bpos) {
1480         if(base_leg_pos < nominal_base_leg_pos) {
1481                 bpos = base_leg_pos;
1482                 return(true);
1483         } else {
1484                 bpos = nominal_base_leg_pos;
1485                 return(false);
1486         }
1487 }
1488
1489
1490 // Figure out which runways are active.
1491 // For now we'll just be simple and do one active runway - eventually this will get much more complex
1492 // This is a private function - public interface to the results of this is through GetActiveRunway
1493 void FGTower::DoRwyDetails() {
1494         //cout << "GetRwyDetails called" << endl;
1495         
1496         // Based on the airport-id and wind get the active runway
1497         
1498   const FGAirport* apt = fgFindAirportID(ident);
1499   assert(apt);
1500         FGRunway* runway = apt->getActiveRunwayForUsage();
1501
1502   activeRwy = runway->ident();
1503   rwy.rwyID = runway->ident();
1504   SG_LOG(SG_ATC, SG_INFO, "In FGGround, active runway for airport " << ident << " is " << activeRwy);
1505   
1506   // Get the threshold position
1507   double other_way = runway->headingDeg() - 180.0;
1508   while(other_way <= 0.0) {
1509     other_way += 360.0;
1510   }
1511     // move to the +l end/center of the runway
1512   //cout << "Runway center is at " << runway._lon << ", " << runway._lat << '\n';
1513   double tshlon = 0.0, tshlat = 0.0, tshr = 0.0;
1514   double tolon = 0.0, tolat = 0.0, tor = 0.0;
1515   rwy.length = runway->lengthM();
1516   geo_direct_wgs_84 ( aptElev, runway->latitude(), runway->longitude(), other_way, 
1517                       rwy.length / 2.0 - 25.0, &tshlat, &tshlon, &tshr );
1518   geo_direct_wgs_84 ( aptElev, runway->latitude(), runway->longitude(), runway->headingDeg(), 
1519                       rwy.length / 2.0 - 25.0, &tolat, &tolon, &tor );
1520   
1521   // Note - 25 meters in from the runway end is a bit of a hack to put the plane ahead of the user.
1522   // now copy what we need out of runway into rwy
1523   rwy.threshold_pos = SGGeod::fromDegM(tshlon, tshlat, aptElev);
1524   SGGeod takeoff_end = SGGeod::fromDegM(tolon, tolat, aptElev);
1525   //cout << "Threshold position = " << tshlon << ", " << tshlat << ", " << aptElev << '\n';
1526   //cout << "Takeoff position = " << tolon << ", " << tolat << ", " << aptElev << '\n';
1527   rwy.hdg = runway->headingDeg();
1528   // Set the projection for the local area based on this active runway
1529   ortho.Init(rwy.threshold_pos, rwy.hdg);       
1530   rwy.end1ortho = ortho.ConvertToLocal(rwy.threshold_pos);      // should come out as zero
1531   rwy.end2ortho = ortho.ConvertToLocal(takeoff_end);
1532   
1533   // Set the pattern direction
1534   // TODO - we'll check for a facilities file with this in eventually - for now assume left traffic except
1535   // for certain circumstances (RH parallel rwy).
1536   rwy.patternDirection = -1;            // Left
1537   if(rwy.rwyID.size() == 3) {
1538     rwy.patternDirection = (rwy.rwyID.substr(2,1) == "R" ? 1 : -1);
1539   }
1540   //cout << "Doing details, rwy.patterDirection is " << rwy.patternDirection << '\n';
1541 }
1542
1543
1544 // Figure out if a given position lies on the active runway
1545 // Might have to change when we consider more than one active rwy.
1546 bool FGTower::OnActiveRunway(const SGGeod& pt) {
1547         // TODO - check that the centre calculation below isn't confused by displaced thesholds etc.
1548         SGVec3d xyc((rwy.end1ortho.x() + rwy.end2ortho.x())/2.0, (rwy.end1ortho.y() + rwy.end2ortho.y())/2.0, 0.0);
1549         SGVec3d xyp = ortho.ConvertToLocal(pt);
1550         
1551         //cout << "Length offset = " << fabs(xyp.y() - xyc.y()) << '\n';
1552         //cout << "Width offset = " << fabs(xyp.x() - xyc.x()) << '\n';
1553
1554         double rlen = rwy.length/2.0 + 5.0;
1555         double rwidth = rwy.width/2.0;
1556         double ldiff = fabs(xyp.y() - xyc.y());
1557         double wdiff = fabs(xyp.x() - xyc.x());
1558
1559         return((ldiff < rlen) && (wdiff < rwidth));
1560 }
1561
1562 // Figure out if a given position lies on any runway or not
1563 // Only call this at startup - reading the runways database is expensive and needs to be fixed!
1564 bool FGTower::OnAnyRunway(const SGGeod& pt, bool onGround) {
1565         ATCData ad;
1566         double dist = current_commlist->FindClosest(lon, lat, elev, ad, TOWER, 7.0);
1567         if(dist < 0.0) {
1568                 return(false);
1569         }
1570         
1571         // Based on the airport-id, go through all the runways and check for a point in them
1572
1573   const FGAirport* apt = fgFindAirportID(ad.ident);
1574   assert(apt);
1575   
1576   for (unsigned int i=0; i<apt->numRunways(); ++i) {
1577     if (OnRunway(pt, apt->getRunwayByIndex(i))) {
1578       return true;
1579     }
1580   }
1581
1582   // if onGround is true, we only match real runways, so we're done
1583   if (onGround) return false;
1584
1585   // try taxiways as well
1586   for (unsigned int i=0; i<apt->numTaxiways(); ++i) {
1587     if (OnRunway(pt, apt->getTaxiwayByIndex(i))) {
1588       return true;
1589     }
1590   }
1591   
1592         return false;
1593 }
1594
1595
1596 // Returns true if successful
1597 bool FGTower::RemoveFromTrafficList(const string& id) {
1598         tower_plane_rec_list_iterator twrItr;
1599         for(twrItr = trafficList.begin(); twrItr != trafficList.end(); twrItr++) {
1600                 TowerPlaneRec* tpr = *twrItr;
1601                 if(tpr->plane.callsign == id) {
1602                         trafficList.erase(twrItr);
1603                         trafficListItr = trafficList.begin();
1604                         return(true);
1605                 }
1606         }       
1607         SG_LOG(SG_ATC, SG_WARN, "Warning - unable to remove aircraft " << id << " from trafficList in FGTower");
1608         return(false);
1609 }
1610
1611
1612 // Returns true if successful
1613 bool FGTower::RemoveFromAppList(const string& id) {
1614         tower_plane_rec_list_iterator twrItr;
1615         for(twrItr = appList.begin(); twrItr != appList.end(); twrItr++) {
1616                 TowerPlaneRec* tpr = *twrItr;
1617                 if(tpr->plane.callsign == id) {
1618                         appList.erase(twrItr);
1619                         appListItr = appList.begin();
1620                         return(true);
1621                 }
1622         }       
1623         //SG_LOG(SG_ATC, SG_WARN, "Warning - unable to remove aircraft " << id << " from appList in FGTower");
1624         return(false);
1625 }
1626
1627 // Returns true if successful
1628 bool FGTower::RemoveFromRwyList(const string& id) {
1629         tower_plane_rec_list_iterator twrItr;
1630         for(twrItr = rwyList.begin(); twrItr != rwyList.end(); twrItr++) {
1631                 TowerPlaneRec* tpr = *twrItr;
1632                 if(tpr->plane.callsign == id) {
1633                         rwyList.erase(twrItr);
1634                         rwyListItr = rwyList.begin();
1635                         return(true);
1636                 }
1637         }       
1638         //SG_LOG(SG_ATC, SG_WARN, "Warning - unable to remove aircraft " << id << " from rwyList in FGTower");
1639         return(false);
1640 }
1641
1642
1643 // Add a tower plane rec with ETA to the traffic list in the correct position ETA-wise
1644 // and set nextOnRwy if so.
1645 // Returns true if this could cause a threshold ETA conflict with other traffic, false otherwise.
1646 // For planes holding they are put in the first position with time to go, and the return value is
1647 // true if in the first position (nextOnRwy) and false otherwise.
1648 // See the comments in FGTower::doThresholdUseOrder for notes on the ordering
1649 bool FGTower::AddToTrafficList(TowerPlaneRec* t, bool holding) {
1650         //cout << "ADD: " << trafficList.size();
1651         //cout << "AddToTrafficList called, currently size = " << trafficList.size() << ", holding = " << holding << endl;
1652         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.
1653         double departure_sep_time = 60.0;       // Separation time behind departing airplanes.  Comments above also apply.
1654         bool conflict = false;
1655         double lastETA = 0.0;
1656         bool firstTime = true;
1657         // FIXME - make this more robust for different plane types eg. light following heavy.
1658         tower_plane_rec_list_iterator twrItr;
1659         //twrItr = trafficList.begin();
1660         //while(1) {
1661         for(twrItr = trafficList.begin(); twrItr != trafficList.end(); twrItr++) {
1662                 //if(twrItr == trafficList.end()) {
1663                 //      cout << "  END  ";
1664                 //      trafficList.push_back(t);
1665                 //      return(holding ? firstTime : conflict);
1666                 //} else {
1667                         TowerPlaneRec* tpr = *twrItr;
1668                         if(holding) {
1669                                 //cout << (tpr->isUser ? "USER!\n" : "NOT user\n");
1670                                 //cout << "tpr->eta - lastETA = " << tpr->eta - lastETA << '\n';
1671                                 double dep_allowance = (timeSinceLastDeparture < departure_sep_time ? departure_sep_time - timeSinceLastDeparture : 0.0); 
1672                                 double slot_time = (firstTime ? separation_time + dep_allowance : separation_time + departure_sep_time);
1673                                 // separation_time + departure_sep_time in the above accounts for the fact that the arrival could be touch and go,
1674                                 // and if not needs time to clear the rwy anyway.
1675                                 if(tpr->eta  - lastETA > slot_time) {
1676                                         t->nextOnRwy = firstTime;
1677                                         trafficList.insert(twrItr, t);
1678                                         //cout << "\tH\t" << trafficList.size() << '\n';
1679                                         return(firstTime);
1680                                 }
1681                                 firstTime = false;
1682                         } else {
1683                                 if(t->eta < tpr->eta) {
1684                                         // Ugg - this one's tricky.
1685                                         // It depends on what the two planes are doing and whether there's a conflict what we do.
1686                                         if(tpr->eta - t->eta > separation_time) {       // No probs, plane 2 can squeeze in before plane 1 with no apparent conflict
1687                                                 if(tpr->nextOnRwy) {
1688                                                         tpr->nextOnRwy = false;
1689                                                         t->nextOnRwy = true;
1690                                                 }
1691                                                 trafficList.insert(twrItr, t);
1692                                         } else {        // Ooops - this ones tricky - we have a potential conflict!
1693                                                 conflict = true;
1694                                                 // HACK - just add anyway for now and flag conflict - TODO - FIX THIS using CIRCUIT/STRAIGHT_IN and VFR/IFR precedence rules.
1695                                                 if(tpr->nextOnRwy) {
1696                                                         tpr->nextOnRwy = false;
1697                                                         t->nextOnRwy = true;
1698                                                 }
1699                                                 trafficList.insert(twrItr, t);
1700                                         }
1701                                         //cout << "\tC\t" << trafficList.size() << '\n';
1702                                         return(conflict);
1703                                 }
1704                         }
1705                 //}
1706                 //++twrItr;
1707         }
1708         // If we get here we must be at the end of the list, or maybe the list is empty.
1709         if(!trafficList.size()) {
1710                 t->nextOnRwy = true;
1711                 // conflict and firstTime should be false and true respectively in this case anyway.
1712         } else {
1713                 t->nextOnRwy = false;
1714         }
1715         trafficList.push_back(t);
1716         //cout << "\tE\t" << trafficList.size() << endl;
1717         return(holding ? firstTime : conflict);
1718 }
1719
1720 // Add a tower plane rec with ETA to the circuit list in the correct position ETA-wise
1721 // Returns true if this might cause a separation conflict (based on ETA) with other traffic, false otherwise.
1722 // Safe to add a plane that is already in - planes with the same callsign are not added.
1723 bool FGTower::AddToCircuitList(TowerPlaneRec* t) {
1724         if(!t) {
1725                 //cout << "**********************************************\n";
1726                 //cout << "AddToCircuitList called with NULL pointer!!!!!\n";
1727                 //cout << "**********************************************\n";
1728                 return false;
1729         }
1730         //cout << "ADD: " << circuitList.size();
1731         //cout << ident << " AddToCircuitList called for " << t->plane.callsign << ", currently size = " << circuitList.size() << endl;
1732         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.
1733         bool conflict = false;
1734         tower_plane_rec_list_iterator twrItr;
1735         // First check if the plane is already in the list
1736         //cout << "A" << endl;
1737         //cout << "Checking whether " << t->plane.callsign << " is already in circuit list..." << endl;
1738         //cout << "B" << endl;
1739         for(twrItr = circuitList.begin(); twrItr != circuitList.end(); twrItr++) {
1740                 if((*twrItr)->plane.callsign == t->plane.callsign) {
1741                         //cout << "In list - returning...\n";
1742                         return false;
1743                 }
1744         }
1745         //cout << "Not in list - adding..." << endl;
1746         
1747         for(twrItr = circuitList.begin(); twrItr != circuitList.end(); twrItr++) {
1748                 TowerPlaneRec* tpr = *twrItr;
1749                 //cout << tpr->plane.callsign << " eta is " << tpr->eta << '\n';
1750                 //cout << "New eta is " << t->eta << '\n';              
1751                 if(t->eta < tpr->eta) {
1752                         // Ugg - this one's tricky.
1753                         // It depends on what the two planes are doing and whether there's a conflict what we do.
1754                         if(tpr->eta - t->eta > separation_time) {       // No probs, plane 2 can squeeze in before plane 1 with no apparent conflict
1755                                 circuitList.insert(twrItr, t);
1756                                 circuitListItr = circuitList.begin();
1757                         } else {        // Ooops - this ones tricky - we have a potential conflict!
1758                                 conflict = true;
1759                                 // HACK - just add anyway for now and flag conflict.
1760                                 circuitList.insert(twrItr, t);
1761                                 circuitListItr = circuitList.begin();
1762                         }
1763                         //cout << "\tC\t" << circuitList.size() << '\n';
1764                         return(conflict);
1765                 }
1766         }
1767         // If we get here we must be at the end of the list, or maybe the list is empty.
1768         //cout << ident << " adding " << t->plane.callsign << " to circuitList" << endl;
1769         circuitList.push_back(t);       // TODO - check the separation with the preceding plane for the conflict flag.
1770         circuitListItr = circuitList.begin();
1771         //cout << "\tE\t" << circuitList.size() << endl;
1772         return(conflict);
1773 }
1774
1775 // Add to vacated list only if not already present
1776 void FGTower::AddToVacatedList(TowerPlaneRec* t) {
1777         tower_plane_rec_list_iterator twrItr;
1778         bool found = false;
1779         for(twrItr = vacatedList.begin(); twrItr != vacatedList.end(); twrItr++) {
1780                 if((*twrItr)->plane.callsign == t->plane.callsign) {
1781                         found = true;
1782                 }
1783         }
1784         if(found) return;
1785         vacatedList.push_back(t);
1786 }
1787
1788 void FGTower::AddToHoldingList(TowerPlaneRec* t) {
1789         tower_plane_rec_list_iterator it, end = holdList.end();
1790         for (it = holdList.begin(); it != end; ++it) {
1791                 if ((*it)->plane.callsign == t->plane.callsign)
1792                         return;
1793         
1794                 holdList.push_back(t);
1795         }
1796 }
1797
1798 // Calculate the eta of a plane to the threshold.
1799 // For ground traffic this is the fastest they can get there.
1800 // For air traffic this is the middle approximation.
1801 void FGTower::CalcETA(TowerPlaneRec* tpr, bool printout) {
1802         // For now we'll be very crude and hardwire expected speeds to C172-like values
1803         // The speeds below are specified in knots IAS and then converted to m/s
1804         double app_ias = 100.0 * 0.514444;                      // Speed during straight-in approach
1805         double circuit_ias = 80.0 * 0.514444;           // Speed around circuit
1806         double final_ias = 70.0 * 0.514444;             // Speed during final approach
1807         
1808         //if(printout) {
1809         //cout << "In CalcETA, airplane ident = " << tpr->plane.callsign << '\n';
1810         //cout << (tpr->isUser ? "USER\n" : "AI\n");
1811         //cout << flush;
1812         //}
1813         
1814         // Sign convention - dist_out is -ve for approaching planes and +ve for departing planes
1815         // dist_across is +ve in the pattern direction - ie a plane correctly on downwind will have a +ve dist_across
1816         
1817         SGVec3d op = ortho.ConvertToLocal(tpr->pos);
1818         //if(printout) {
1819         //if(!tpr->isUser) cout << "Orthopos is " << op.x() << ", " << op.y() << ' ';
1820         //cout << "opType is " << tpr->opType << '\n';
1821         //}
1822         double dist_out_m = op.y();
1823         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
1824         //cout << "Doing ETA calc for " << tpr->plane.callsign << '\n';
1825         
1826         if(tpr->opType == STRAIGHT_IN || tpr->opType == INBOUND) {
1827                 //cout << "CASE 1\n";
1828                 double dist_to_go_m = sqrt((dist_out_m * dist_out_m) + (dist_across_m * dist_across_m));
1829                 if(dist_to_go_m < 1000) {
1830                         tpr->eta = dist_to_go_m / final_ias;
1831                 } else {
1832                         tpr->eta = (1000.0 / final_ias) + ((dist_to_go_m - 1000.0) / app_ias);
1833                 }
1834         } else if(tpr->opType == CIRCUIT || tpr->opType == TTT_UNKNOWN) {       // Hack alert - UNKNOWN has sort of been added here as a temporary hack.
1835                 //cout << "CASE 2\n";
1836                 // It's complicated - depends on if base leg is delayed or not
1837                 //if(printout) {
1838                 //cout << "Leg = " << tpr->leg << '\n';
1839                 //}
1840                 if(tpr->leg == LANDING_ROLL) {
1841                         tpr->eta = 0;
1842                 } else if((tpr->leg == FINAL) || (tpr->leg == TURN4)) {
1843                         //cout << "dist_out_m = " << dist_out_m << '\n';
1844                         tpr->eta = fabs(dist_out_m) / final_ias;
1845                 } else if((tpr->leg == BASE) || (tpr->leg == TURN3)) {
1846                         tpr->eta = (fabs(dist_out_m) / final_ias) + (dist_across_m / circuit_ias);
1847                 } else {
1848                         // Need to calculate where base leg is likely to be
1849                         // FIXME - for now I'll hardwire it to 1000m which is what AILocalTraffic uses!!!
1850                         // 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
1851                         double nominal_base_dist_out_m = -1000;
1852                         double current_base_dist_out_m;
1853                         if(!GetBaseConstraint(current_base_dist_out_m)) {
1854                                 current_base_dist_out_m = nominal_base_dist_out_m;
1855                         }
1856                         //cout << "current_base_dist_out_m = " << current_base_dist_out_m << '\n';
1857                         double nominal_dist_across_m = 1000;    // Hardwired value from AILocalTraffic
1858                         double current_dist_across_m;
1859                         if(!GetDownwindConstraint(current_dist_across_m)) {
1860                                 current_dist_across_m = nominal_dist_across_m;
1861                         }
1862                         double nominal_cross_dist_out_m = 2000; // Bit of a guess - AI plane turns to crosswind at 700ft agl.
1863                         tpr->eta = fabs(current_base_dist_out_m) / final_ias;   // final
1864                         //cout << "a = " << tpr->eta << '\n';
1865                         if((tpr->leg == DOWNWIND) || (tpr->leg == TURN2)) {
1866                                 tpr->eta += dist_across_m / circuit_ias;
1867                                 //cout << "b = " << tpr->eta << '\n';
1868                                 tpr->eta += fabs(current_base_dist_out_m - dist_out_m) / circuit_ias;
1869                                 //cout << "c = " << tpr->eta << '\n';
1870                         } else if((tpr->leg == CROSSWIND) || (tpr->leg == TURN1)) {
1871                                 //cout << "CROSSWIND calc: ";
1872                                 //cout << tpr->eta << ' ';
1873                                 if(dist_across_m > nominal_dist_across_m) {
1874                                         tpr->eta += dist_across_m / circuit_ias;
1875                                         //cout << "a ";
1876                                 } else {
1877                                         tpr->eta += nominal_dist_across_m / circuit_ias;
1878                                         //cout << "b ";
1879                                 }
1880                                 //cout << tpr->eta << ' ';
1881                                 // should we use the dist across of the previous plane if there is previous still on downwind?
1882                                 //if(printout) cout << "bb = " << tpr->eta << '\n';
1883                                 if(dist_out_m > nominal_cross_dist_out_m) {
1884                                         tpr->eta += fabs(current_base_dist_out_m - dist_out_m) / circuit_ias;
1885                                         //cout << "c ";
1886                                 } else {
1887                                         tpr->eta += fabs(current_base_dist_out_m - nominal_cross_dist_out_m) / circuit_ias;
1888                                         //cout << "d ";
1889                                 }
1890                                 //cout << tpr->eta << ' ';
1891                                 //if(printout) cout << "cc = " << tpr->eta << '\n';
1892                                 if(nominal_dist_across_m > dist_across_m) {
1893                                         tpr->eta += (nominal_dist_across_m - dist_across_m) / circuit_ias;
1894                                         //cout << "e ";
1895                                 } else {
1896                                         // Nothing to add
1897                                         //cout << "f ";
1898                                 }
1899                                 //cout << tpr->eta << '\n';
1900                                 //if(printout) cout << "dd = " << tpr->eta << '\n';
1901                         } else {
1902                                 // We've only just started - why not use a generic estimate?
1903                                 tpr->eta = 240.0;
1904                         }
1905                 }
1906                 //if(printout) {
1907                 //      cout << "ETA = " << tpr->eta << '\n';
1908                 //}
1909                 //if(!tpr->isUser) cout << tpr->plane.callsign << '\t' << tpr->eta << '\n';
1910         } else {
1911                 tpr->eta = 99999;
1912         }       
1913 }
1914
1915
1916 // Calculate the distance of a plane to the threshold in meters
1917 // TODO - Modify to calculate flying distance of a plane in the circuit
1918 double FGTower::CalcDistOutM(TowerPlaneRec* tpr) {
1919         return(dclGetHorizontalSeparation(rwy.threshold_pos, tpr->pos));
1920 }
1921
1922
1923 // Calculate the distance of a plane to the threshold in miles
1924 // TODO - Modify to calculate flying distance of a plane in the circuit
1925 double FGTower::CalcDistOutMiles(TowerPlaneRec* tpr) {
1926         return(CalcDistOutM(tpr) / 1600.0);             // FIXME - use a proper constant if possible.
1927 }
1928
1929
1930 // Iterate through all the lists, update the position of, and call CalcETA for all the planes.
1931 void FGTower::doThresholdETACalc() {
1932         //cout << "Entering doThresholdETACalc..." << endl;
1933         tower_plane_rec_list_iterator twrItr;
1934         // Do the approach list first
1935         for(twrItr = appList.begin(); twrItr != appList.end(); twrItr++) {
1936                 TowerPlaneRec* tpr = *twrItr;
1937                 if(!(tpr->isUser)) tpr->pos = tpr->planePtr->getPos();
1938                 //cout << "APP: ";
1939                 CalcETA(tpr);
1940         }       
1941         // Then the circuit list
1942         //cout << "Circuit list size is " << circuitList.size() << '\n';
1943         for(twrItr = circuitList.begin(); twrItr != circuitList.end(); twrItr++) {
1944                 TowerPlaneRec* tpr = *twrItr;
1945                 if(!(tpr->isUser)) tpr->pos = tpr->planePtr->getPos();
1946                 //cout << "CIRC: ";
1947                 CalcETA(tpr);
1948         }
1949         //cout << "Done doThresholdETCCalc" << endl;
1950 }
1951                 
1952
1953 // Check that the planes in traffic list are correctly ordered,
1954 // that the nearest (timewise) is flagged next on rwy, and return
1955 // true if any threshold use conflicts are detected, false otherwise.
1956 bool FGTower::doThresholdUseOrder() {
1957         //cout << "Entering doThresholdUseOrder..." << endl;
1958         bool conflict = false;
1959         
1960         // Wipe out traffic list, go through circuit, app and hold list, and reorder them in traffic list.
1961         // Here's the rather simplistic assumptions we're using:
1962         // Currently all planes are assumed to be GA light singles with corresponding speeds and separation times.
1963         // In order of priority for runway use:
1964         // STRAIGHT_IN > CIRCUIT > HOLDING_FOR_DEPARTURE
1965         // No modification of planes speeds occurs - conflicts are resolved by delaying turn for base,
1966         // and holding planes until a space.
1967         // When calculating if a holding plane can use the runway, time clearance from last departure
1968         // as well as time clearance to next arrival must be considered.
1969         
1970         trafficList.clear();
1971         
1972         tower_plane_rec_list_iterator twrItr;
1973         // Do the approach list first
1974         //if(ident == "KRHV") cout << "A" << flush;
1975         for(twrItr = appList.begin(); twrItr != appList.end(); twrItr++) {
1976                 TowerPlaneRec* tpr = *twrItr;
1977                 //if(ident == "KRHV") cout << tpr->plane.callsign << '\n';
1978                 conflict = AddToTrafficList(tpr);
1979         }       
1980         // Then the circuit list
1981         //if(ident == "KRHV") cout << "C" << flush;
1982         for(twrItr = circuitList.begin(); twrItr != circuitList.end(); twrItr++) {
1983                 TowerPlaneRec* tpr = *twrItr;
1984                 //if(ident == "KRHV") cout << tpr->plane.callsign << '\n';
1985                 conflict = AddToTrafficList(tpr);
1986         }
1987         // And finally the hold list
1988         //cout << "H" << endl;
1989         for(twrItr = holdList.begin(); twrItr != holdList.end(); twrItr++) {
1990                 TowerPlaneRec* tpr = *twrItr;
1991                 AddToTrafficList(tpr, true);
1992         }
1993         
1994         
1995         if(0) {
1996         //if(ident == "KRHV") {
1997                 cout << "T\n";
1998                 for(twrItr = trafficList.begin(); twrItr != trafficList.end(); twrItr++) {
1999                         TowerPlaneRec* tpr = *twrItr;
2000                         cout << tpr->plane.callsign << '\t' << tpr->eta << '\t';
2001                 }
2002                 cout << endl;
2003         }
2004         
2005         //cout << "Done doThresholdUseOrder" << endl;
2006         return(conflict);
2007 }
2008
2009
2010 // Return the ETA of plane no. list_pos (1-based) in the traffic list.
2011 // i.e. list_pos = 1 implies next to use runway.
2012 double FGTower::GetTrafficETA(unsigned int list_pos, bool printout) {
2013         if(trafficList.size() < list_pos) {
2014                 return(99999);
2015         }
2016
2017         tower_plane_rec_list_iterator twrItr;
2018         twrItr = trafficList.begin();
2019         for(unsigned int i = 1; i < list_pos; i++, twrItr++);
2020         TowerPlaneRec* tpr = *twrItr;
2021         CalcETA(tpr, printout);
2022         //cout << "ETA returned = " << tpr->eta << '\n';
2023         return(tpr->eta);
2024 }
2025         
2026
2027 void FGTower::ContactAtHoldShort(const PlaneRec& plane, FGAIPlane* requestee, tower_traffic_type operation) {
2028         // HACK - assume that anything contacting at hold short is new for now - FIXME LATER
2029         TowerPlaneRec* t = new TowerPlaneRec;
2030         t->plane = plane;
2031         t->planePtr = requestee;
2032         t->holdShortReported = true;
2033         t->clearedToLineUp = false;
2034         t->clearedToTakeOff = false;
2035         t->opType = operation;
2036         t->pos = requestee->getPos();
2037         
2038         //cout << "Hold Short reported by " << plane.callsign << '\n';
2039         SG_LOG(SG_ATC, SG_BULK, "Hold Short reported by " << plane.callsign);
2040
2041 /*      
2042         bool next = AddToTrafficList(t, true);
2043         if(next) {
2044                 double teta = GetTrafficETA(2);
2045                 if(teta < 150.0) {
2046                         t->clearanceCounter = 7.0;      // This reduces the delay before response to 3 secs if an immediate takeoff is reqd
2047                         //cout << "Reducing response time to request due imminent traffic\n";
2048                 }
2049         } else {
2050         }
2051 */
2052         // TODO - possibly add the reduced interval to clearance when immediate back in under the new scheme
2053
2054         holdList.push_back(t);
2055         
2056         responseReqd = true;
2057 }
2058
2059 // Register the presence of an AI plane at a point where contact would already have been made in real life
2060 // CAUTION - currently it is assumed that this plane's callsign is unique - it is up to AIMgr to generate unique callsigns.
2061 void FGTower::RegisterAIPlane(const PlaneRec& plane, FGAIPlane* ai, const tower_traffic_type& op, const PatternLeg& lg) {
2062         // At the moment this is only going to be tested with inserting an AI plane on downwind
2063         TowerPlaneRec* t = new TowerPlaneRec;
2064         t->plane = plane;
2065         t->planePtr = ai;
2066         t->opType = op;
2067         t->leg = lg;
2068         t->pos = ai->getPos();
2069         
2070         CalcETA(t);
2071         
2072         if(op == CIRCUIT && lg != LEG_UNKNOWN) {
2073                 AddToCircuitList(t);
2074         } else {
2075                 // FLAG A WARNING
2076         }
2077         
2078         doThresholdUseOrder();
2079 }
2080
2081 void FGTower::DeregisterAIPlane(const string& id) {
2082         RemovePlane(id);
2083 }
2084
2085 // Contact tower for VFR approach
2086 // eg "Cessna Charlie Foxtrot Golf Foxtrot Sierra eight miles South of the airport for full stop with Bravo"
2087 // This function probably only called via user interaction - AI planes will have an overloaded function taking a planerec.
2088 // opt defaults to AIP_LT_UNKNOWN
2089 void FGTower::VFRArrivalContact(const string& ID, const LandingType& opt) {
2090         //cout << "USER Request Landing Clearance called for ID " << ID << '\n';
2091         
2092         // For now we'll assume that the user is a light plane and can get him/her to join the circuit if necessary.
2093
2094         TowerPlaneRec* t;       
2095         string usercall = fgGetString("/sim/user/callsign");
2096         if(ID == "USER" || ID == usercall) {
2097                 t = FindPlane(usercall);
2098                 if(!t) {
2099                         //cout << "NOT t\n";
2100                         t = new TowerPlaneRec;
2101                         t->isUser = true;
2102                         t->pos.setLongitudeDeg(user_lon_node->getDoubleValue());
2103                         t->pos.setLatitudeDeg(user_lat_node->getDoubleValue());
2104                         t->pos.setElevationM(user_elev_node->getDoubleValue());
2105                 } else {
2106                         //cout << "IS t\n";
2107                         // Oops - the plane is already registered with this tower - maybe we took off and flew a giant circuit without
2108                         // quite getting out of tower airspace - just ignore for now and treat as new arrival.
2109                         // TODO - Maybe should remove from departure and circuit list if in there though!!
2110                 }
2111         } else {
2112                 // Oops - something has gone wrong - put out a warning
2113                 cout << "WARNING - FGTower::VFRContact(string ID, LandingType lt) called with ID " << ID << " which does not appear to be the user.\n";
2114                 return;
2115         }
2116                 
2117         
2118         // TODO
2119         // Calculate where the plane is in relation to the active runway and it's circuit
2120         // and set the op-type as appropriate.
2121         
2122         // HACK - to get up and running I'm going to assume that the user contacts tower on a staight-in final for now.
2123         t->opType = STRAIGHT_IN;
2124         
2125         t->plane.type = GA_SINGLE;      // FIXME - Another assumption!
2126         t->plane.callsign = usercall;
2127         
2128         t->vfrArrivalReported = true;
2129         responseReqd = true;
2130         
2131         appList.push_back(t);   // Not necessarily permanent
2132         appListItr = appList.begin();
2133         AddToTrafficList(t);
2134         
2135         current_atcdialog->remove_entry(ident, USER_REQUEST_VFR_ARRIVAL, TOWER);
2136         current_atcdialog->remove_entry(ident, USER_REQUEST_VFR_ARRIVAL_FULL_STOP, TOWER);
2137         current_atcdialog->remove_entry(ident, USER_REQUEST_VFR_ARRIVAL_TOUCH_AND_GO, TOWER);
2138 }
2139
2140 // landingType defaults to AIP_LT_UNKNOWN
2141 void FGTower::VFRArrivalContact(const PlaneRec& plane, FGAIPlane* requestee, const LandingType& lt) {
2142         //cout << "VFRArrivalContact called for plane " << plane.callsign << " at " << ident << '\n';
2143         // Possible hack - assume this plane is new for now - TODO - should check really
2144         TowerPlaneRec* t = new TowerPlaneRec;
2145         t->plane = plane;
2146         t->planePtr = requestee;
2147         t->landingType = lt;
2148         t->pos = requestee->getPos();
2149         
2150         //cout << "Hold Short reported by " << plane.callsign << '\n';
2151         SG_LOG(SG_ATC, SG_BULK, "VFR arrival contact made by " << plane.callsign);
2152         //cout << "VFR arrival contact made by " << plane.callsign << '\n';
2153
2154         // HACK - to get up and running I'm going to assume a staight-in final for now.
2155         t->opType = STRAIGHT_IN;
2156         
2157         t->vfrArrivalReported = true;
2158         responseReqd = true;
2159         
2160         //cout << "Before adding, appList.size = " << appList.size() << " at " << ident << '\n';
2161         appList.push_back(t);   // Not necessarily permanent
2162         appListItr = appList.begin();
2163         //cout << "After adding, appList.size = " << appList.size() << " at " << ident << '\n';
2164         AddToTrafficList(t);
2165 }
2166
2167 void FGTower::RequestDepartureClearance(const string& ID) {
2168         //cout << "Request Departure Clearance called...\n";
2169 }
2170
2171 void FGTower::RequestTakeOffClearance(const string& ID) {
2172         string uid=ID;
2173         if(ID == "USER") {
2174                 uid = fgGetString("/sim/user/callsign");
2175                 current_atcdialog->remove_entry(ident, USER_REQUEST_TAKE_OFF, TOWER);
2176         }
2177         TowerPlaneRec* t = FindPlane(uid);
2178         if(t) {
2179                 if(!(t->clearedToTakeOff)) {
2180                         departed = false;
2181                         t->lineUpReported=true;
2182                         responseReqd = true;
2183                 }
2184         }
2185         else {
2186                 SG_LOG(SG_ATC, SG_WARN, "WARNING: Unable to find plane " << ID << " in FGTower::RequestTakeOffClearance(...)");
2187         }
2188 }
2189         
2190 void FGTower::ReportFinal(const string& ID) {
2191         //cout << "Report Final Called at tower " << ident << " by plane " << ID << '\n';
2192         string uid=ID;
2193         if(ID == "USER") {
2194                 uid = fgGetString("/sim/user/callsign");
2195                 current_atcdialog->remove_entry(ident, USER_REPORT_3_MILE_FINAL, TOWER);
2196         }
2197         TowerPlaneRec* t = FindPlane(uid);
2198         if(t) {
2199                 t->finalReported = true;
2200                 t->finalAcknowledged = false;
2201                 if(!(t->clearedToLand)) {
2202                         responseReqd = true;
2203                 } else {
2204                         // possibly respond with wind even if already cleared to land?
2205                         t->finalReported = false;
2206                         t->finalAcknowledged = true;
2207                         // HACK!! - prevents next reporting being misinterpreted as this one.
2208                 }
2209         } else {
2210                 SG_LOG(SG_ATC, SG_WARN, "WARNING: Unable to find plane " << ID << " in FGTower::ReportFinal(...)");
2211         }
2212 }
2213
2214 void FGTower::ReportLongFinal(const string& ID) {
2215         string uid=ID;
2216         if(ID == "USER") {
2217                 uid = fgGetString("/sim/user/callsign");
2218                 current_atcdialog->remove_entry(ident, USER_REPORT_3_MILE_FINAL, TOWER);
2219         }
2220         TowerPlaneRec* t = FindPlane(uid);
2221         if(t) {
2222                 t->longFinalReported = true;
2223                 t->longFinalAcknowledged = false;
2224                 if(!(t->clearedToLand)) {
2225                         responseReqd = true;
2226                 } // possibly respond with wind even if already cleared to land?
2227         } else {
2228                 SG_LOG(SG_ATC, SG_WARN, "WARNING: Unable to find plane " << ID << " in FGTower::ReportLongFinal(...)");
2229         }
2230 }
2231
2232 //void FGTower::ReportOuterMarker(string ID);
2233 //void FGTower::ReportMiddleMarker(string ID);
2234 //void FGTower::ReportInnerMarker(string ID);
2235
2236 void FGTower::ReportRunwayVacated(const string& ID) {
2237         //cout << "Report Runway Vacated Called at tower " << ident << " by plane " << ID << '\n';
2238         string uid=ID;
2239         if(ID == "USER") {
2240                 uid = fgGetString("/sim/user/callsign");
2241                 current_atcdialog->remove_entry(ident, USER_REPORT_RWY_VACATED, TOWER);
2242         }
2243         TowerPlaneRec* t = FindPlane(uid);
2244         if(t) {
2245                 //cout << "Found it...\n";
2246                 t->rwyVacatedReported = true;
2247                 responseReqd = true;
2248         } else {
2249                 SG_LOG(SG_ATC, SG_WARN, "WARNING: Unable to find plane " << ID << " in FGTower::ReportRunwayVacated(...)");
2250                 SG_LOG(SG_ATC, SG_ALERT, "A WARNING: Unable to find plane " << ID << " in FGTower::ReportRunwayVacated(...)");
2251                 //cout << "WARNING: Unable to find plane " << ID << " in FGTower::ReportRunwayVacated(...)\n";
2252         }
2253 }
2254
2255 TowerPlaneRec* FGTower::FindPlane(const string& ID) {
2256         //cout << "FindPlane called for " << ID << "...\n";
2257         tower_plane_rec_list_iterator twrItr;
2258         // Do the approach list first
2259         for(twrItr = appList.begin(); twrItr != appList.end(); twrItr++) {
2260                 //cout << "appList callsign is " << (*twrItr)->plane.callsign << '\n';
2261                 if((*twrItr)->plane.callsign == ID) return(*twrItr);
2262         }       
2263         // Then the circuit list
2264         for(twrItr = circuitList.begin(); twrItr != circuitList.end(); twrItr++) {
2265                 //cout << "circuitList callsign is " << (*twrItr)->plane.callsign << '\n';
2266                 if((*twrItr)->plane.callsign == ID) return(*twrItr);
2267         }
2268         // Then the runway list
2269         //cout << "rwyList.size() is " << rwyList.size() << '\n';
2270         for(twrItr = rwyList.begin(); twrItr != rwyList.end(); twrItr++) {
2271                 //cout << "rwyList callsign is " << (*twrItr)->plane.callsign << '\n';
2272                 if((*twrItr)->plane.callsign == ID) return(*twrItr);
2273         }
2274         // The hold list
2275         for(twrItr = holdList.begin(); twrItr != holdList.end(); twrItr++) {
2276                 if((*twrItr)->plane.callsign == ID) return(*twrItr);
2277         }
2278         // And finally the vacated list
2279         for(twrItr = vacatedList.begin(); twrItr != vacatedList.end(); twrItr++) {
2280                 //cout << "vacatedList callsign is " << (*twrItr)->plane.callsign << '\n';
2281                 if((*twrItr)->plane.callsign == ID) return(*twrItr);
2282         }
2283         SG_LOG(SG_ATC, SG_WARN, "Unable to find " << ID << " in FGTower::FindPlane(...)");
2284         //exit(-1);
2285         return(NULL);
2286 }
2287
2288 void FGTower::RemovePlane(const string& ID) {
2289         //cout << ident << " RemovePlane called for " << ID << '\n';
2290         // We have to be careful here - we want to erase the plane from all lists it is in,
2291         // but we can only delete it once, AT THE END.
2292         TowerPlaneRec* t = NULL;
2293         tower_plane_rec_list_iterator twrItr;
2294         for(twrItr = appList.begin(); twrItr != appList.end();) {
2295                 if((*twrItr)->plane.callsign == ID) {
2296                         t = *twrItr;
2297                         twrItr = appList.erase(twrItr);
2298                         appListItr = appList.begin();
2299                         // HACK: aircraft are sometimes more than once in a list, so we need to
2300                         // remove them all before we can delete the TowerPlaneRec class
2301                         //break;
2302                 } else
2303                         ++twrItr;
2304         }
2305         for(twrItr = depList.begin(); twrItr != depList.end();) {
2306                 if((*twrItr)->plane.callsign == ID) {
2307                         t = *twrItr;
2308                         twrItr = depList.erase(twrItr);
2309                         depListItr = depList.begin();
2310                 } else
2311                         ++twrItr;
2312         }
2313         for(twrItr = circuitList.begin(); twrItr != circuitList.end();) {
2314                 if((*twrItr)->plane.callsign == ID) {
2315                         t = *twrItr;
2316                         twrItr = circuitList.erase(twrItr);
2317                         circuitListItr = circuitList.begin();
2318                 } else
2319                         ++twrItr;
2320         }
2321         for(twrItr = holdList.begin(); twrItr != holdList.end();) {
2322                 if((*twrItr)->plane.callsign == ID) {
2323                         t = *twrItr;
2324                         twrItr = holdList.erase(twrItr);
2325                         holdListItr = holdList.begin();
2326                 } else
2327                         ++twrItr;
2328         }
2329         for(twrItr = rwyList.begin(); twrItr != rwyList.end();) {
2330                 if((*twrItr)->plane.callsign == ID) {
2331                         t = *twrItr;
2332                         twrItr = rwyList.erase(twrItr);
2333                         rwyListItr = rwyList.begin();
2334                 } else
2335                         ++twrItr;
2336         }
2337         for(twrItr = vacatedList.begin(); twrItr != vacatedList.end();) {
2338                 if((*twrItr)->plane.callsign == ID) {
2339                         t = *twrItr;
2340                         twrItr = vacatedList.erase(twrItr);
2341                         vacatedListItr = vacatedList.begin();
2342                 } else
2343                         ++twrItr;
2344         }
2345         for(twrItr = trafficList.begin(); twrItr != trafficList.end();) {
2346                 if((*twrItr)->plane.callsign == ID) {
2347                         t = *twrItr;
2348                         twrItr = trafficList.erase(twrItr);
2349                         trafficListItr = trafficList.begin();
2350                 } else
2351                         ++twrItr;
2352         }
2353         // And finally, delete the record.
2354         delete t;
2355 }
2356
2357 void FGTower::ReportDownwind(const string& ID) {
2358         //cout << "ReportDownwind(...) called\n";
2359         string uid=ID;
2360         if(ID == "USER") {
2361                 uid = fgGetString("/sim/user/callsign");
2362                 current_atcdialog->remove_entry(ident, USER_REPORT_DOWNWIND, TOWER);
2363         }
2364         TowerPlaneRec* t = FindPlane(uid);
2365         if(t) {
2366                 t->downwindReported = true;
2367                 responseReqd = true;
2368                 // If the plane is in the app list, remove it and put it in the circuit list instead.
2369                 // Ideally we might want to do this at the 2 mile report prior to 45 deg entry, but at
2370                 // the moment that would b&gg?r up the constraint position calculations.
2371                 RemoveFromAppList(ID);
2372                 t->leg = DOWNWIND;
2373                 if(t->isUser) {
2374                         t->pos.setLongitudeDeg(user_lon_node->getDoubleValue());
2375                         t->pos.setLatitudeDeg(user_lat_node->getDoubleValue());
2376                         t->pos.setElevationM(user_elev_node->getDoubleValue());
2377                 } else {
2378                         // ASSERT(t->planePtr != NULL);
2379                         t->pos = t->planePtr->getPos();
2380                 }
2381                 CalcETA(t);
2382                 AddToCircuitList(t);
2383         } else {
2384                 SG_LOG(SG_ATC, SG_WARN, "WARNING: Unable to find plane " << ID << " in FGTower::ReportDownwind(...)");
2385         }
2386 }
2387
2388 void FGTower::ReportGoingAround(const string& ID) {
2389         string uid=ID;
2390         if(ID == "USER") {
2391                 uid = fgGetString("/sim/user/callsign");
2392                 RemoveAllUserDialogOptions();   // TODO - it would be much more efficient if ATCDialog simply had a clear() function!!!
2393                 current_atcdialog->add_entry(ident, "@AP Tower, @CS Downwind @RW", "Report Downwind", TOWER, (int)USER_REPORT_DOWNWIND);
2394         }
2395         TowerPlaneRec* t = FindPlane(uid);
2396         if(t) {
2397                 //t->goAroundReported = true;  // No need to set this until we start responding to it.
2398                 responseReqd = false;   // might change in the future but for now we'll not distract them during the go-around.
2399                 // If the plane is in the app list, remove it and put it in the circuit list instead.
2400                 RemoveFromAppList(ID);
2401                 t->leg = CLIMBOUT;
2402                 if(t->isUser) {
2403                         t->pos.setLongitudeDeg(user_lon_node->getDoubleValue());
2404                         t->pos.setLatitudeDeg(user_lat_node->getDoubleValue());
2405                         t->pos.setElevationM(user_elev_node->getDoubleValue());
2406                 } else {
2407                         // ASSERT(t->planePtr != NULL);
2408                         t->pos = t->planePtr->getPos();
2409                 }
2410                 CalcETA(t);
2411                 AddToCircuitList(t);
2412         } else {
2413                 SG_LOG(SG_ATC, SG_WARN, "WARNING: Unable to find plane " << ID << " in FGTower::ReportDownwind(...)");
2414         }
2415 }
2416
2417 string FGTower::GenText(const string& m, int c) {
2418         const int cmax = 300;
2419         //string message;
2420         char tag[4];
2421         char crej = '@';
2422         char mes[cmax];
2423         char dum[cmax];
2424         //char buf[10];
2425         char *pos;
2426         int len;
2427         //FGTransmission t;
2428         string usercall = fgGetString("/sim/user/callsign");
2429         TowerPlaneRec* t = FindPlane(responseID);
2430         
2431         //transmission_list_type     tmissions = transmissionlist_station[station];
2432         //transmission_list_iterator current   = tmissions.begin();
2433         //transmission_list_iterator last      = tmissions.end();
2434         
2435         //for ( ; current != last ; ++current ) {
2436         //      if ( current->get_code().c1 == code.c1 &&  
2437         //              current->get_code().c2 == code.c2 &&
2438         //          current->get_code().c3 == code.c3 ) {
2439                         
2440                         //if ( ttext ) message = current->get_transtext();
2441                         //else message = current->get_menutext();
2442                         strcpy( &mes[0], m.c_str() ); 
2443                         
2444                         // Replace all the '@' parameters with the actual text.
2445                         int check = 0;  // If mes gets overflowed the while loop can go infinite
2446                         double gp = fgGetFloat("/gear/gear/position-norm");
2447                         while ( strchr(&mes[0], crej) != NULL  ) {      // ie. loop until no more occurances of crej ('@') found
2448                                 pos = strchr( &mes[0], crej );
2449                                 memmove(&tag[0], pos, 3);
2450                                 tag[3] = '\0';
2451                                 int i;
2452                                 len = 0;
2453                                 for ( i=0; i<cmax; i++ ) {
2454                                         if ( mes[i] == crej ) {
2455                                                 len = i; 
2456                                                 break;
2457                                         }
2458                                 }
2459                                 strncpy( &dum[0], &mes[0], len );
2460                                 dum[len] = '\0';
2461                                 
2462                                 if ( strcmp ( tag, "@ST" ) == 0 )
2463                                         //strcat( &dum[0], tpars.station.c_str() );
2464                                         strcat(&dum[0], ident.c_str());
2465                                 else if ( strcmp ( tag, "@AP" ) == 0 )
2466                                         //strcat( &dum[0], tpars.airport.c_str() );
2467                                         strcat(&dum[0], name.c_str());
2468                                 else if ( strcmp ( tag, "@CS" ) == 0 ) 
2469                                         //strcat( &dum[0], tpars.callsign.c_str() );
2470                                         strcat(&dum[0], usercall.c_str());
2471                                 else if ( strcmp ( tag, "@TD" ) == 0 ) {
2472                                         /*
2473                                         if ( tpars.tdir == 1 ) {
2474                                                 char buf[] = "left";
2475                                                 strcat( &dum[0], &buf[0] );
2476                                         }
2477                                         else {
2478                                                 char buf[] = "right";
2479                                                 strcat( &dum[0], &buf[0] );
2480                                         }
2481                                         */
2482                                 }
2483                                 else if ( strcmp ( tag, "@HE" ) == 0 ) {
2484                                         /*
2485                                         char buf[10];
2486                                         sprintf( buf, "%i", (int)(tpars.heading) );
2487                                         strcat( &dum[0], &buf[0] );
2488                                         */
2489                                 }
2490                                 else if ( strcmp ( tag, "@AT" ) == 0 ) {        // ATIS ID
2491                                         /*
2492                                         char buf[10];
2493                                         sprintf( buf, "%i", (int)(tpars.heading) );
2494                                         strcat( &dum[0], &buf[0] );
2495                                         */
2496                                         double f = globals->get_ATC_mgr()->GetFrequency(ident, ATIS) / 100.0;
2497                                         if(f) {
2498                                                 string atis_id;
2499                                                 atis_id = ", information " + GetATISID();
2500                                                 strcat( &dum[0], atis_id.c_str() );
2501                                         }
2502                                 }
2503                                 else if ( strcmp ( tag, "@VD" ) == 0 ) {
2504                                         /*
2505                                         if ( tpars.VDir == 1 ) {
2506                                                 char buf[] = "Descend and maintain";
2507                                                 strcat( &dum[0], &buf[0] );
2508                                         }
2509                                         else if ( tpars.VDir == 2 ) {
2510                                                 char buf[] = "Maintain";
2511                                                 strcat( &dum[0], &buf[0] );
2512                                         }
2513                                         else if ( tpars.VDir == 3 ) {
2514                                                 char buf[] = "Climb and maintain";
2515                                                 strcat( &dum[0], &buf[0] );
2516                                         } 
2517                                         */
2518                                 }
2519                                 else if ( strcmp ( tag, "@AL" ) == 0 ) {
2520                                         /*
2521                                         char buf[10];
2522                                         sprintf( buf, "%i", (int)(tpars.alt) );
2523                                         strcat( &dum[0], &buf[0] );
2524                                         */
2525                                 }
2526                                 else if ( strcmp ( tag, "@TO" ) == 0 ) {      // Requesting take-off or departure clearance
2527                                         string tmp;
2528                                         if (rwyOccupied) {
2529                                                 tmp = "Ready for take-off";
2530                                         } else {
2531                                                 if (OnAnyRunway(SGGeod::fromDegM(user_lon_node->getDoubleValue(),
2532                                                                 user_lat_node->getDoubleValue(), 0.0),true)) {
2533                                                         tmp = "Request take-off clearance";
2534                                                 } else {
2535                                                         tmp = "Request departure clearance";
2536                                                 }
2537                                         }
2538                                         strcat(&dum[0], tmp.c_str());
2539                                 }
2540                                 else if ( strcmp ( tag, "@MI" ) == 0 ) {
2541                                         char buf[10];
2542                                         //sprintf( buf, "%3.1f", tpars.miles );
2543                                         int dist_miles = (int)dclGetHorizontalSeparation(SGGeod::fromDegM(lon, lat, elev), SGGeod::fromDegM(user_lon_node->getDoubleValue(), user_lat_node->getDoubleValue(), user_elev_node->getDoubleValue())) / 1600;
2544                                         sprintf(buf, "%i", dist_miles);
2545                                         strcat( &dum[0], &buf[0] );
2546                                 }
2547                                 else if ( strcmp ( tag, "@FR" ) == 0 ) {
2548                                         /*
2549                                         char buf[10];
2550                                         sprintf( buf, "%6.2f", tpars.freq );
2551                                         strcat( &dum[0], &buf[0] );
2552                                         */
2553                                 }
2554                                 else if ( strcmp ( tag, "@RW" ) == 0 ) {
2555                                         strcat(&dum[0], ConvertRwyNumToSpokenString(activeRwy).c_str());
2556                                 }
2557                                 else if ( strcmp ( tag, "@GR" ) == 0 ) {        // Gear position (on final)
2558                                         if(t->gearWasUp && gp > 0.99) {
2559                                                 strcat(&dum[0], ", gear down, ready to land.");
2560                                         }
2561                                 }
2562                                 else if(strcmp(tag, "@CD") == 0) {      // @CD = compass direction
2563                                         double h = GetHeadingFromTo(SGGeod::fromDegM(lon, lat, elev), SGGeod::fromDegM(user_lon_node->getDoubleValue(), user_lat_node->getDoubleValue(), user_elev_node->getDoubleValue()));
2564                                         while(h < 0.0) h += 360.0;
2565                                         while(h > 360.0) h -= 360.0;
2566                                         if(h < 22.5 || h > 337.5) {
2567                                                 strcat(&dum[0], "North");
2568                                         } else if(h < 67.5) {
2569                                                 strcat(&dum[0], "North-East");
2570                                         } else if(h < 112.5) {
2571                                                 strcat(&dum[0], "East");
2572                                         } else if(h < 157.5) {
2573                                                 strcat(&dum[0], "South-East");
2574                                         } else if(h < 202.5) {
2575                                                 strcat(&dum[0], "South");
2576                                         } else if(h < 247.5) {
2577                                                 strcat(&dum[0], "South-West");
2578                                         } else if(h < 292.5) {
2579                                                 strcat(&dum[0], "West");
2580                                         } else {
2581                                                 strcat(&dum[0], "North-West");
2582                                         }
2583                                 } else {
2584                                         cout << "Tag " << tag << " not found" << endl;
2585                                         break;
2586                                 }
2587                                 strcat( &dum[0], &mes[len+3] );
2588                                 strcpy( &mes[0], &dum[0] );
2589                                 
2590                                 ++check;
2591                                 if(check > 10) {
2592                                         SG_LOG(SG_ATC, SG_WARN, "WARNING: Possibly endless loop terminated in FGTransmissionlist::gen_text(...)"); 
2593                                         break;
2594                                 }
2595                         }
2596                         
2597                         //cout << mes  << endl;  
2598                         //break;
2599                 //}
2600         //}
2601         return mes[0] ? mes : "No transmission found";
2602 }
2603
2604 string FGTower::GetWeather() {
2605         std::ostringstream msg;
2606
2607         // wind
2608         double hdg = wind_from_hdg->getDoubleValue();
2609         double speed = wind_speed_knots->getDoubleValue();
2610         if (speed < 1)
2611                 msg << "wind calm";
2612         else
2613                 msg << "wind " << int(hdg) << " degrees at " << int(speed) << " knots";
2614
2615         // visibility
2616         double visibility = fgGetDouble("/environment/visibility-m");
2617         if (visibility < 10000)
2618                 msg << ", visibility " << int(visibility / 1609) << " miles";
2619
2620         // pressure / altimeter
2621         double pressure = fgGetDouble("/environment/pressure-sea-level-inhg");
2622         msg << ", QFE " << fixed << setprecision(2) << pressure << ".";
2623
2624         return msg.str();
2625 }
2626
2627 string FGTower::GetATISID() {
2628         int hours = fgGetInt("/sim/time/utc/hour");
2629         int phonetic_id = current_commlist->GetCallSign(ident, hours, 0);
2630         return GetPhoneticIdent(phonetic_id);
2631 }
2632
2633 ostream& operator << (ostream& os, tower_traffic_type ttt) {
2634         switch(ttt) {
2635         case(CIRCUIT):      return(os << "CIRCUIT");
2636         case(INBOUND):      return(os << "INBOUND");
2637         case(OUTBOUND):     return(os << "OUTBOUND");
2638         case(TTT_UNKNOWN):  return(os << "UNKNOWN");
2639         case(STRAIGHT_IN):  return(os << "STRAIGHT_IN");
2640         }
2641         return(os << "ERROR - Unknown switch in tower_traffic_type operator << ");
2642 }
2643