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