1 // FGTower - a class to provide tower control at towered airports.
3 // Written by David Luff, started March 2002.
5 // Copyright (C) 2002 David C. Luff - david.luff@nottingham.ac.uk
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License as
9 // published by the Free Software Foundation; either version 2 of the
10 // License, or (at your option) any later version.
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 // General Public License for more details.
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software
19 // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
26 # include <strings.h> // bcopy()
28 # include <string.h> // MSVC doesn't have strings.h
31 #include <Main/globals.hxx>
32 #include <Airports/runways.hxx>
33 #include <simgear/math/sg_geodesy.hxx>
34 #include <simgear/debug/logstream.hxx>
37 #include "ATCdisplay.hxx"
39 #include "ATCutils.hxx"
40 #include "ATCDialog.hxx"
41 #include "commlist.hxx"
42 #include "AILocalTraffic.hxx"
48 TowerPlaneRec::TowerPlaneRec() :
51 clearedToLineUp(false),
52 clearedToTakeOff(false),
53 holdShortReported(false),
54 downwindReported(false),
55 longFinalReported(false),
56 longFinalAcknowledged(false),
58 finalAcknowledged(false),
59 rwyVacatedReported(false),
60 rwyVacatedAcknowledged(false),
61 instructedToGoAround(false),
64 vfrArrivalReported(false),
65 vfrArrivalAcknowledged(false),
68 landingType(AIP_LT_UNKNOWN),
71 plane.callsign = "UNKNOWN";
74 TowerPlaneRec::TowerPlaneRec(PlaneRec p) :
77 clearedToLineUp(false),
78 clearedToTakeOff(false),
79 holdShortReported(false),
80 downwindReported(false),
81 longFinalReported(false),
82 longFinalAcknowledged(false),
84 finalAcknowledged(false),
85 rwyVacatedReported(false),
86 rwyVacatedAcknowledged(false),
87 instructedToGoAround(false),
90 vfrArrivalReported(false),
91 vfrArrivalAcknowledged(false),
94 landingType(AIP_LT_UNKNOWN),
100 TowerPlaneRec::TowerPlaneRec(Point3D pt) :
102 clearedToLand(false),
103 clearedToLineUp(false),
104 clearedToTakeOff(false),
105 holdShortReported(false),
106 downwindReported(false),
107 longFinalReported(false),
108 longFinalAcknowledged(false),
109 finalReported(false),
110 finalAcknowledged(false),
111 rwyVacatedReported(false),
112 rwyVacatedAcknowledged(false),
113 instructedToGoAround(false),
116 vfrArrivalReported(false),
117 vfrArrivalAcknowledged(false),
120 landingType(AIP_LT_UNKNOWN),
123 plane.callsign = "UNKNOWN";
127 TowerPlaneRec::TowerPlaneRec(PlaneRec p, Point3D pt) :
129 clearedToLand(false),
130 clearedToLineUp(false),
131 clearedToTakeOff(false),
132 holdShortReported(false),
133 downwindReported(false),
134 longFinalReported(false),
135 longFinalAcknowledged(false),
136 finalReported(false),
137 finalAcknowledged(false),
138 rwyVacatedReported(false),
139 rwyVacatedAcknowledged(false),
140 instructedToGoAround(false),
143 vfrArrivalReported(false),
144 vfrArrivalAcknowledged(false),
147 landingType(AIP_LT_UNKNOWN),
157 /*******************************************
160 Currently user is assumed to have taken off again when leaving the runway - check speed/elev for taxiing-in. (MAJOR)
162 Use track instead of heading to determine what leg of the circuit the user is flying. (MINOR)
164 Use altitude as well as position to try to determine if the user has left the circuit. (MEDIUM - other issues as well).
166 Currently HoldShortReported code assumes there will be only one plane holding for the runway at once and
167 will break when planes start queueing. (CRITICAL)
169 Report-Runway-Vacated is left as only user ATC option following a go-around. (MAJOR)
171 Report Go-Around should be added to user options following reporting final or downwind. (MEDIUM).
173 Report-Downwind is not added as ATC option when user takes off to fly a circuit. (MAJOR)
175 eta of USER can be calculated very wrongly in circuit if flying straight out and turn4 etc are with +ve ortho y.
176 This can then screw up circuit ordering for other planes (MEDIUM)
178 USER leaving circuit needs to be more robustly considered when intentions unknown
179 Currently only considered during climbout and breaks when user turns (MEDIUM).
181 GetPos() of the AI planes is called erratically - either too much or not enough. (MINOR)
183 GO-AROUND is instructed very late at < 12s to landing - possibly make more dependent on chance of rwy clearing before landing (FEATURE)
185 Need to make clear when TowerPlaneRecs do or don't get deleted in RemoveFromCircuitList etc. (MINOR until I misuse it - then CRITICAL!)
186 *******************************************/
189 ATCmgr = globals->get_ATC_mgr();
193 // Init the property nodes - TODO - need to make sure we're getting surface winds.
194 wind_from_hdg = fgGetNode("/environment/wind-from-heading-deg", true);
195 wind_speed_knots = fgGetNode("/environment/wind-speed-kt", true);
198 update_count_max = 15;
200 holdListItr = holdList.begin();
202 appListItr = appList.begin();
203 depListItr = depList.begin();
204 rwyListItr = rwyList.begin();
205 circuitListItr = circuitList.begin();
206 trafficListItr = trafficList.begin();
208 vacatedListItr = vacatedList.begin();
212 timeSinceLastDeparture = 9999;
215 nominal_downwind_leg_pos = 1000.0;
216 nominal_base_leg_pos = -1000.0;
217 // TODO - set nominal crosswind leg pos based on minimum distance from takeoff end of rwy.
219 _departureControlled = false;
222 FGTower::~FGTower() {
223 if(!separateGround) {
228 void FGTower::Init() {
229 //cout << "Initialising tower " << ident << '\n';
232 // Pointers to user's position
233 user_lon_node = fgGetNode("/position/longitude-deg", true);
234 user_lat_node = fgGetNode("/position/latitude-deg", true);
235 user_elev_node = fgGetNode("/position/altitude-ft", true);
236 user_hdg_node = fgGetNode("/orientation/heading-deg", true);
238 // Need some way to initialise rwyOccupied flag correctly if the user is on the runway and to know its the user.
239 // I'll punt the startup issue for now though!!!
242 // Setup the ground control at this airport
244 //cout << "Tower ident = " << ident << '\n';
245 if(ATCmgr->GetAirportATCDetails(ident, &a)) {
246 if(a.ground_freq) { // Ground control
247 ground = (FGGround*)ATCmgr->GetATCPointer(ident, GROUND);
248 separateGround = true;
250 // Something has gone wrong :-(
251 SG_LOG(SG_ATC, SG_WARN, "ERROR - ground has frequency but can't get ground pointer :-(");
252 ground = new FGGround(ident);
253 separateGround = false;
256 ground->SetDisplay();
258 ground->SetNoDisplay();
262 // Initialise ground anyway to do the shortest path stuff!
263 // Note that we're now responsible for updating and deleting this - NOT the ATCMgr.
264 ground = new FGGround(ident);
265 separateGround = false;
268 ground->SetDisplay();
270 ground->SetNoDisplay();
274 SG_LOG(SG_ATC, SG_ALERT, "Unable to find airport details for " << ident << " in FGTower::Init()");
275 // Initialise ground anyway to avoid segfault later
276 ground = new FGGround(ident);
277 separateGround = false;
280 ground->SetDisplay();
282 ground->SetNoDisplay();
286 // TODO - attempt to get a departure control pointer to see if we need to hand off departing traffic to departure.
288 // Get the airport elevation
289 aptElev = dclGetAirportElev(ident.c_str());
291 // TODO - this function only assumes one active rwy.
294 // TODO - this currently assumes only one active runway.
295 rwyOccupied = OnActiveRunway(Point3D(user_lon_node->getDoubleValue(), user_lat_node->getDoubleValue(), 0.0));
297 //cout << "User found on active runway\n";
298 // Assume the user is started at the threshold ready to take-off
299 TowerPlaneRec* t = new TowerPlaneRec;
300 t->plane.callsign = fgGetString("/sim/user/callsign");
301 t->plane.type = GA_SINGLE; // FIXME - hardwired!!
302 t->opType = TTT_UNKNOWN; // We don't know if the user wants to do circuits or a departure...
303 t->landingType = AIP_LT_UNKNOWN;
304 t->leg = TAKEOFF_ROLL;
307 t->clearedToTakeOff = true;
308 rwyList.push_back(t);
311 //cout << "User not on active runway\n";
312 // For now assume that this means the user is not at the airport and is in the air.
313 // TODO FIXME - this will break when user starts on apron, at hold short, etc.
314 if(!OnAnyRunway(Point3D(user_lon_node->getDoubleValue(), user_lat_node->getDoubleValue(), 0.0))) {
315 current_atcdialog->add_entry(ident, "@AP Tower @CS @MI miles @CD of the airport for full stop with the ATIS", "Contact tower for VFR arrival (full stop)", TOWER, (int)USER_REQUEST_VFR_ARRIVAL_FULL_STOP);
320 void FGTower::Update(double dt) {
321 //cout << "T" << endl;
322 // Each time step, what do we need to do?
323 // We need to go through the list of outstanding requests and acknowedgements
324 // and process at least one of them.
325 // We need to go through the list of planes under our control and check if
326 // any need to be addressed.
327 // We need to check for planes not under our control coming within our
328 // control area and address if necessary.
330 // TODO - a lot of the below probably doesn't need to be called every frame and should be staggered.
332 // Sort the arriving planes
335 if(ident == "KEMT") {
336 cout << update_count << "\ttL: " << trafficList.size() << " cL: " << circuitList.size() << " hL: " << holdList.size() << " aL: " << appList.size() << '\n';
339 //if(ident == "EGNX") cout << display << '\n';
341 if(departed != false) {
342 timeSinceLastDeparture += dt;
343 //if(ident == "KEMT")
344 // cout << " dt = " << dt << " timeSinceLastDeparture = " << timeSinceLastDeparture << '\n';
347 //cout << ident << " respond = " << respond << " responseReqd = " << responseReqd << '\n';
349 if(!responseReqd) SG_LOG(SG_ATC, SG_ALERT, "ERROR - respond is true and responseReqd is false in FGTower::Update(...)");
352 responseReqd = false;
355 // Calculate the eta of each plane to the threshold.
356 // For ground traffic this is the fastest they can get there.
357 // For air traffic this is the middle approximation.
358 if(update_count == 1) {
359 doThresholdETACalc();
362 // Order the list of traffic as per expected threshold use and flag any conflicts
363 if(update_count == 2) {
364 //bool conflicts = doThresholdUseOrder();
365 doThresholdUseOrder();
368 // sortConficts() !!!
370 if(update_count == 4) {
374 // Uggh - HACK - why have we got rwyOccupied - wouldn't simply testing rwyList.size() do?
381 if(update_count == 5 && rwyOccupied) {
385 if(update_count == 6) {
386 CheckCircuitList(dt);
389 if(update_count == 7) {
390 CheckApproachList(dt);
393 if(update_count == 8) {
394 CheckDepartureList(dt);
397 // TODO - do one plane from the departure list and set departed = false when out of consideration
401 if(!separateGround) {
402 // The display stuff might have to get more clever than this when not separate
403 // since the tower and ground might try communicating simultaneously even though
404 // they're mean't to be the same contoller/frequency!!
406 ground->SetDisplay();
408 ground->SetNoDisplay();
414 // How big should ii get - ie how long should the update cycle interval stretch?
415 if(update_count >= update_count_max) {
419 // Call the base class update for the response time handling.
422 if(ident == "KEMT") {
423 // For AI debugging convienience - may be removed
425 user_pos.setlon(user_lon_node->getDoubleValue());
426 user_pos.setlat(user_lat_node->getDoubleValue());
427 user_pos.setelev(user_elev_node->getDoubleValue());
428 Point3D user_ortho_pos = ortho.ConvertToLocal(user_pos);
429 fgSetDouble("/AI/user/ortho-x", user_ortho_pos.x());
430 fgSetDouble("/AI/user/ortho-y", user_ortho_pos.y());
431 fgSetDouble("/AI/user/elev", user_elev_node->getDoubleValue());
434 //cout << "Done T" << endl;
437 void FGTower::ReceiveUserCallback(int code) {
438 if(code == (int)USER_REQUEST_VFR_DEPARTURE) {
439 //cout << "User requested departure\n";
440 } else if(code == (int)USER_REQUEST_VFR_ARRIVAL) {
441 VFRArrivalContact("USER");
442 } else if(code == (int)USER_REQUEST_VFR_ARRIVAL_FULL_STOP) {
443 VFRArrivalContact("USER", FULL_STOP);
444 } else if(code == (int)USER_REQUEST_VFR_ARRIVAL_TOUCH_AND_GO) {
445 VFRArrivalContact("USER", TOUCH_AND_GO);
446 } else if(code == (int)USER_REPORT_DOWNWIND) {
447 ReportDownwind("USER");
448 } else if(code == (int)USER_REPORT_3_MILE_FINAL) {
449 // For now we'll just call report final instead of report long final to avoid having to alter the response code
451 } else if(code == (int)USER_REPORT_RWY_VACATED) {
452 ReportRunwayVacated("USER");
456 void FGTower::Respond() {
457 //cout << "\nEntering Respond, responseID = " << responseID << endl;
458 TowerPlaneRec* t = FindPlane(responseID);
461 if(t->vfrArrivalReported && !t->vfrArrivalAcknowledged) {
462 //cout << "Tower " << ident << " is responding to VFR arrival reported...\n";
463 // Testing - hardwire straight in for now
464 string trns = t->plane.callsign;
468 // Should we clear staight in or for downwind entry?
469 // For now we'll clear straight in if greater than 1km from a line drawn through the threshold perpendicular to the rwy.
470 // Later on we might check the actual heading and direct some of those to enter on downwind or base.
471 Point3D op = ortho.ConvertToLocal(t->pos);
473 trns += " Report three mile straight-in runway ";
474 t->opType = STRAIGHT_IN;
476 current_atcdialog->add_entry(ident, "@AP Tower @CS @MI mile final Runway @RW", "Report Final", TOWER, (int)USER_REPORT_3_MILE_FINAL);
478 t->planePtr->RegisterTransmission(14);
481 // For now we'll just request reporting downwind.
482 // TODO - In real life something like 'report 2 miles southwest right downwind rwy 19R' might be used
483 // but I'm not sure how to handle all permutations of which direction to tell to report from yet.
485 //cout << "Responding, rwy.patterDirection is " << rwy.patternDirection << '\n';
486 trns += ((rwy.patternDirection == 1) ? "right " : "left ");
487 trns += "downwind runway ";
489 // leave it in the app list until it gets into pattern though.
491 current_atcdialog->add_entry(ident, "@AP Tower @CS Downwind @RW", "Report Downwind", TOWER, (int)USER_REPORT_DOWNWIND);
493 t->planePtr->RegisterTransmission(15);
496 trns += ConvertRwyNumToSpokenString(activeRwy);
498 globals->get_ATC_display()->RegisterSingleMessage(trns, 0);
500 //cout << "Not displaying, trns was " << trns << '\n';
502 t->vfrArrivalAcknowledged = true;
503 } else if(t->downwindReported) {
504 //cout << "Tower " << ident << " is responding to downwind reported...\n";
505 t->downwindReported = false;
507 for(tower_plane_rec_list_iterator twrItr = circuitList.begin(); twrItr != circuitList.end(); twrItr++) {
508 if((*twrItr)->plane.callsign == responseID) break;
511 string trns = t->plane.callsign;
513 trns += ConvertNumToSpokenDigits(i);
515 if((i == 1) && (!rwyList.size()) && (t->nextOnRwy)) {
516 trns += "Cleared to land"; // TODO - clear for the option if appropriate
517 t->clearedToLand = true;
518 if(!t->isUser) t->planePtr->RegisterTransmission(7);
521 globals->get_ATC_display()->RegisterSingleMessage(trns);
524 if(t->opType == TTT_UNKNOWN) t->opType = CIRCUIT;
525 current_atcdialog->add_entry(ident, "@CS Clear of the runway", "Report runway vacated", TOWER, USER_REPORT_RWY_VACATED);
526 // TODO - add report going around as an option
528 } else if(t->holdShortReported) {
529 //cout << "Tower " << ident << " is reponding to holdShortReported...\n";
531 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!!)
532 // Do nothing for now - consider acknowloging hold short eventually
534 ClearHoldingPlane(t);
535 t->leg = TAKEOFF_ROLL;
536 rwyList.push_back(t);
538 // WARNING - WE ARE ASSUMING ONLY ONE PLANE REPORTING HOLD AT A TIME BELOW
539 // FIXME TODO - FIX THIS!!!
540 if(holdList.size()) {
541 if(holdListItr == holdList.end()) {
542 holdListItr = holdList.begin();
544 holdList.erase(holdListItr);
545 holdListItr = holdList.begin();
549 // Tell him to hold and what position he is.
550 // Not currently sure under which circumstances we do or don't bother transmitting this.
551 string trns = t->plane.callsign;
552 trns += " hold position";
554 globals->get_ATC_display()->RegisterSingleMessage(trns, 0);
556 // TODO - add some idea of what traffic is blocking him.
558 t->holdShortReported = false;
559 } else if(t->finalReported && !(t->finalAcknowledged)) {
560 //cout << "Tower " << ident << " is responding to finalReported...\n";
562 string trns = t->plane.callsign;
563 //cout << (t->nextOnRwy ? "Next on rwy " : "Not next!! ");
564 //cout << (rwyOccupied ? "RWY OCCUPIED!!\n" : "Rwy not occupied\n");
565 if(t->nextOnRwy && !rwyOccupied && !(t->instructedToGoAround)) {
566 if(t->landingType == FULL_STOP) {
567 trns += " cleared to land ";
569 trns += " cleared for the option ";
572 t->clearedToLand = true;
573 // Maybe remove report downwind from menu here as well incase user didn't bother to?
575 current_atcdialog->add_entry(ident, "@CS Clear of the runway", "Report runway vacated", TOWER, USER_REPORT_RWY_VACATED);
576 // TODO - add report going around as an option.
578 t->planePtr->RegisterTransmission(7);
580 } else if(t->eta < 20) {
581 // Do nothing - we'll be telling it to go around in less than 10 seconds if the
582 // runway doesn't clear so no point in calling "continue approach".
585 trns += " continue approach";
586 t->clearedToLand = false;
588 if(display && disp) {
589 globals->get_ATC_display()->RegisterSingleMessage(trns);
591 t->finalAcknowledged = true;
592 } else if(t->rwyVacatedReported && !(t->rwyVacatedAcknowledged)) {
593 ProcessRunwayVacatedReport(t);
594 t->rwyVacatedAcknowledged = true;
597 //freqClear = true; // FIXME - set this to come true after enough time to render the message
598 _releaseCounter = 0.0;
600 _runReleaseCounter = true;
601 //cout << "Done Respond\n" << endl;
604 void FGTower::ProcessRunwayVacatedReport(TowerPlaneRec* t) {
605 //cout << "Processing rwy vacated...\n";
606 string trns = t->plane.callsign;
608 trns += " Contact ground on ";
609 double f = globals->get_ATC_mgr()->GetFrequency(ident, GROUND) / 100.0;
611 sprintf(buf, "%.2f", f);
614 if(!t->isUser) t->planePtr->RegisterTransmission(5);
617 trns += " cleared for taxi to the GA parking";
618 if(!t->isUser) t->planePtr->RegisterTransmission(6); // TODO - this is a mega-hack!!
620 //cout << "trns = " << trns << '\n';
622 globals->get_ATC_display()->RegisterSingleMessage(trns);
624 // Maybe we should check that the plane really *has* vacated the runway!
627 // Currently this assumes we *are* next on the runway and doesn't check for planes about to land -
628 // this should be done prior to calling this function.
629 void FGTower::ClearHoldingPlane(TowerPlaneRec* t) {
630 //cout << "Entering ClearHoldingPlane..." << endl;
632 string trns = t->plane.callsign;
633 //if(departed plane < some threshold in time away) {
635 //if(timeSinceLastDeparture <= 60.0 && departed == true) {
637 t->clearedToLineUp = true;
638 t->planePtr->RegisterTransmission(3); // cleared to line-up
639 //} else if(arriving plane < some threshold away) {
640 } else if(GetTrafficETA(2) < 150.0 && (timeSinceLastDeparture > 60.0 || departed == false)) { // Hack - hardwired time
641 trns += " cleared immediate take-off";
642 if(trafficList.size()) {
643 tower_plane_rec_list_iterator trfcItr = trafficList.begin();
644 trfcItr++; // At the moment the holding plane should be first in trafficList.
645 // Note though that this will break if holding planes aren't put in trafficList in the future.
646 TowerPlaneRec* trfc = *trfcItr;
647 trns += "... traffic is";
648 switch(trfc->plane.type) {
652 trns += " a Cessna"; // TODO - add ability to specify actual plane type somewhere
658 trns += " a King-air";
661 trns += " a Learjet";
664 trns += " a Regional";
673 //if(trfc->opType == STRAIGHT_IN || trfc->opType == TTT_UNKNOWN) {
674 if(trfc->opType == STRAIGHT_IN) {
675 double miles_out = CalcDistOutMiles(trfc);
680 trns += ConvertNumToSpokenDigits((int)miles_out);
681 trns += " mile final";
683 } else if(trfc->opType == CIRCUIT) {
684 //cout << "Getting leg of " << trfc->plane.callsign << '\n';
690 trns += " turning final";
696 trns += " turning base";
699 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.
701 // And to eliminate compiler warnings...
702 case TAKEOFF_ROLL: break;
703 case CLIMBOUT: break;
705 case CROSSWIND: break;
707 case LANDING_ROLL: break;
708 case LEG_UNKNOWN: break;
712 // By definition there should be some arriving traffic if we're cleared for immediate takeoff
713 SG_LOG(SG_ATC, SG_WARN, "Warning: Departing traffic cleared for *immediate* take-off despite no arriving traffic in FGTower");
715 t->clearedToTakeOff = true;
716 t->planePtr->RegisterTransmission(4); // cleared to take-off - TODO differentiate between immediate and normal take-off
718 timeSinceLastDeparture = 0.0;
720 //} else if(timeSinceLastDeparture > 60.0 || departed == false) { // Hack - test for timeSinceLastDeparture should be in lineup block eventually
721 trns += " cleared for take-off";
722 // TODO - add traffic is... ?
723 t->clearedToTakeOff = true;
724 t->planePtr->RegisterTransmission(4); // cleared to take-off
726 timeSinceLastDeparture = 0.0;
729 globals->get_ATC_display()->RegisterSingleMessage(trns, 0);
731 //cout << "Done ClearHoldingPlane " << endl;
735 // ***************************************************************************************
736 // ********** Functions to periodically check what the various traffic is doing **********
738 // Do one plane from the hold list
739 void FGTower::CheckHoldList(double dt) {
740 //cout << "Entering CheckHoldList..." << endl;
741 if(holdList.size()) {
742 //cout << "*holdListItr = " << *holdListItr << endl;
743 if(holdListItr == holdList.end()) {
744 holdListItr = holdList.begin();
746 //cout << "*holdListItr = " << *holdListItr << endl;
747 //Process(*holdListItr);
748 TowerPlaneRec* t = *holdListItr;
749 //cout << "t = " << t << endl;
750 if(t->holdShortReported) {
751 // NO-OP - leave it to the response handler.
752 } else { // not responding to report, but still need to clear if clear
754 //cout << "departed = " << departed << '\n';
755 //cout << "timeSinceLastDeparture = " << timeSinceLastDeparture << '\n';
758 } else if(timeSinceLastDeparture <= 60.0 && departed == true) {
759 // Do nothing - this is a bit of a hack - should maybe do line up be ready here
761 ClearHoldingPlane(t);
762 t->leg = TAKEOFF_ROLL;
763 rwyList.push_back(t);
765 holdList.erase(holdListItr);
766 holdListItr = holdList.begin();
769 // TODO - rationalise the considerable code duplication above!
773 //cout << "Done CheckHoldList" << endl;
776 // do the ciruit list
777 void FGTower::CheckCircuitList(double dt) {
778 //cout << "Entering CheckCircuitList..." << endl;
779 // Clear the constraints - we recalculate here.
781 downwind_leg_pos = 0.0;
782 crosswind_leg_pos = 0.0;
784 if(circuitList.size()) { // Do one plane from the circuit
785 if(circuitListItr == circuitList.end()) {
786 circuitListItr = circuitList.begin();
788 TowerPlaneRec* t = *circuitListItr;
789 //cout << ident << ' ' << circuitList.size() << ' ' << t->plane.callsign << " " << t->leg << " eta " << t->eta << '\n';
791 t->pos.setlon(user_lon_node->getDoubleValue());
792 t->pos.setlat(user_lat_node->getDoubleValue());
793 t->pos.setelev(user_elev_node->getDoubleValue());
794 //cout << ident << ' ' << circuitList.size() << ' ' << t->plane.callsign << " " << t->leg << " eta " << t->eta << '\n';
796 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.
797 t->landingType = t->planePtr->GetLandingOption();
798 //cout << "AI plane landing option is " << t->landingType << '\n';
800 Point3D tortho = ortho.ConvertToLocal(t->pos);
802 // Need to figure out which leg he's on
803 //cout << "rwy.hdg = " << rwy.hdg << " user hdg = " << user_hdg_node->getDoubleValue();
804 double ho = GetAngleDiff_deg(user_hdg_node->getDoubleValue(), rwy.hdg);
805 //cout << " ho = " << ho << " abs(ho = " << abs(ho) << '\n';
806 // TODO FIXME - get the wind and convert this to track, or otherwise use track somehow!!!
807 // If it's gusty might need to filter the value, although we are leaving 30 degrees each way leeway!
809 // could be either takeoff, climbout or landing - check orthopos.y
810 //cout << "tortho.y = " << tortho.y() << '\n';
811 if((tortho.y() < 0) || (t->leg == TURN4) || (t->leg == FINAL)) {
815 t->leg = CLIMBOUT; // TODO - check elev wrt. apt elev to differentiate takeoff roll and climbout
816 //cout << "Climbout\n";
817 // If it's the user we may be unsure of his/her intentions.
818 // (Hopefully the AI planes won't try confusing the sim!!!)
819 //cout << "tortho.y = " << tortho.y() << '\n';
820 if(t->opType == TTT_UNKNOWN) {
821 if(tortho.y() > 5000) {
822 // 5 km out from threshold - assume it's a departure
823 t->opType = OUTBOUND; // TODO - could check if the user has climbed significantly above circuit altitude as well.
824 // Since we are unknown operation we should be in depList already.
825 //cout << ident << " Removing user from circuitList (TTT_UNKNOWN)\n";
826 circuitList.erase(circuitListItr);
827 RemoveFromTrafficList(t->plane.callsign);
828 circuitListItr = circuitList.begin();
830 } else if(t->opType == CIRCUIT) {
831 if(tortho.y() > 10000) {
832 // 10 km out - assume the user has abandoned the circuit!!
833 t->opType = OUTBOUND;
834 depList.push_back(t);
835 //cout << ident << " removing user from circuitList (CIRCUIT)\n";
836 circuitList.erase(circuitListItr);
837 circuitListItr = circuitList.begin();
841 } else if(abs(ho) < 60) {
843 // TODO - either fix or doublecheck this hack by looking at heading and pattern direction
844 if((t->leg == CLIMBOUT) || (t->leg == TURN1)) {
851 } else if(abs(ho) < 120) {
853 // TODO - either fix or doublecheck this hack by looking at heading and pattern direction
854 if((t->leg == TURN1) || (t->leg == CROSSWIND)) {
856 //cout << "Crosswind\n";
861 } else if(abs(ho) < 150) {
863 // TODO - either fix or doublecheck this hack by looking at heading and pattern direction
864 if((t->leg == CROSSWIND) || (t->leg == TURN2)) {
869 // Probably safe now to assume the user is flying a circuit
876 //cout << "Downwind\n";
878 if(t->leg == FINAL) {
879 if(OnActiveRunway(t->pos)) {
880 t->leg = LANDING_ROLL;
884 t->leg = t->planePtr->GetLeg();
887 // Set the constraints IF this is the first plane in the circuit
888 // 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!!
889 if(circuitListItr == circuitList.begin()) {
892 // 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.
893 base_leg_pos = tortho.y();
894 //cout << "base_leg_pos = " << base_leg_pos << '\n';
897 // Fall through to base
899 base_leg_pos = tortho.y();
900 //cout << "base_leg_pos = " << base_leg_pos << '\n';
903 // Fall through to downwind
905 // Only have the downwind leg pos as turn-to-base constraint if more negative than we already have.
906 base_leg_pos = (tortho.y() < base_leg_pos ? tortho.y() : base_leg_pos);
907 //cout << "base_leg_pos = " << base_leg_pos;
908 downwind_leg_pos = tortho.x(); // Assume that a following plane can simply be constrained by the immediately in front downwind plane
909 //cout << " downwind_leg_pos = " << downwind_leg_pos << '\n';
912 // Fall through to crosswind
914 crosswind_leg_pos = tortho.y();
915 //cout << "crosswind_leg_pos = " << crosswind_leg_pos << '\n';
916 t->instructedToGoAround = false;
919 // Fall through to climbout
921 // Only use current by constraint as largest
922 crosswind_leg_pos = (tortho.y() > crosswind_leg_pos ? tortho.y() : crosswind_leg_pos);
923 //cout << "crosswind_leg_pos = " << crosswind_leg_pos << '\n';
936 if(t->leg == FINAL && !(t->instructedToGoAround)) {
937 //cout << "YES FINAL, t->eta = " << t->eta << ", rwyList.size() = " << rwyList.size() << '\n';
938 if(t->landingType == FULL_STOP) {
940 //cout << "\n******** SWITCHING TO INBOUND AT POINT AAA *********\n\n";
941 if(t->eta < 12 && rwyList.size()) {
942 // TODO - need to make this more sophisticated
943 // eg. is the plane accelerating down the runway taking off [OK],
944 // or stationary near the start [V. BAD!!].
945 // For now this should stop the AI plane landing on top of the user.
946 string trns = t->plane.callsign;
947 trns += " GO AROUND TRAFFIC ON RUNWAY I REPEAT GO AROUND";
949 globals->get_ATC_display()->RegisterSingleMessage(trns, 0);
951 t->instructedToGoAround = true;
952 t->clearedToLand = false;
953 // Assume it complies!!!
957 //cout << "Registering Go-around transmission with AI plane\n";
958 t->planePtr->RegisterTransmission(13);
962 } else if(t->leg == LANDING_ROLL) {
963 //cout << t->plane.callsign << " has landed - adding to rwyList\n";
964 rwyList.push_front(t);
965 // TODO - if(!clearedToLand) shout something!!
966 t->clearedToLand = false;
967 RemoveFromTrafficList(t->plane.callsign);
969 t->opType = TTT_UNKNOWN;
970 } // TODO - allow the user to specify opType via ATC menu
971 //cout << ident << " Removing " << t->plane.callsign << " from circuitList..." << endl;
972 circuitListItr = circuitList.erase(circuitListItr);
973 if(circuitListItr == circuitList.end() ) {
974 circuitListItr = circuitList.begin();
979 //cout << "Done CheckCircuitList" << endl;
982 // 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!!
983 // FIXME - at the moment it looks like we're only doing the first plane from the rwy list.
984 // (However, at the moment there should only be one airplane on the rwy at once, until we
985 // start allowing planes to line up whilst previous arrival clears the rwy.)
986 void FGTower::CheckRunwayList(double dt) {
987 //cout << "Entering CheckRunwayList..." << endl;
989 if(!rwyList.size()) {
992 rwyListItr = rwyList.begin();
993 TowerPlaneRec* t = *rwyListItr;
995 t->pos.setlon(user_lon_node->getDoubleValue());
996 t->pos.setlat(user_lat_node->getDoubleValue());
997 t->pos.setelev(user_elev_node->getDoubleValue());
999 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.
1001 bool on_rwy = OnActiveRunway(t->pos);
1003 // TODO - for all of these we need to check what the user is *actually* doing!
1004 if((t->opType == INBOUND) || (t->opType == STRAIGHT_IN)) {
1005 //cout << "Tower " << ident << " is removing plane " << t->plane.callsign << " from rwy list (vacated)\n";
1006 //cout << "Size of rwylist was " << rwyList.size() << '\n';
1007 //cout << "Size of vacatedList was " << vacatedList.size() << '\n';
1008 RemoveFromRwyList(t->plane.callsign);
1009 vacatedList.push_back(t);
1010 //cout << "Size of rwylist is " << rwyList.size() << '\n';
1011 //cout << "Size of vacatedList is " << vacatedList.size() << '\n';
1012 // At the moment we wait until Runway Vacated is reported by the plane before telling to contact ground etc.
1013 // It's possible we could be a bit more proactive about this.
1014 } else if(t->opType == OUTBOUND) {
1015 depList.push_back(t);
1016 rwyList.pop_front();
1018 timeSinceLastDeparture = 0.0;
1019 } else if(t->opType == CIRCUIT) {
1020 //cout << ident << " adding " << t->plane.callsign << " to circuitList" << endl;
1021 circuitList.push_back(t);
1022 AddToTrafficList(t);
1023 rwyList.pop_front();
1025 timeSinceLastDeparture = 0.0;
1026 } else if(t->opType == TTT_UNKNOWN) {
1027 depList.push_back(t);
1028 //cout << ident << " adding " << t->plane.callsign << " to circuitList" << endl;
1029 circuitList.push_back(t);
1030 AddToTrafficList(t);
1031 rwyList.pop_front();
1033 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.
1035 // HELP - we shouldn't ever get here!!!
1040 //cout << "Done CheckRunwayList" << endl;
1043 // Do one plane from the approach list
1044 void FGTower::CheckApproachList(double dt) {
1045 //cout << "CheckApproachList called for " << ident << endl;
1046 //cout << "AppList.size is " << appList.size() << endl;
1047 if(appList.size()) {
1048 if(appListItr == appList.end()) {
1049 appListItr = appList.begin();
1051 TowerPlaneRec* t = *appListItr;
1052 //cout << "t = " << t << endl;
1053 //cout << "Checking " << t->plane.callsign << endl;
1055 t->pos.setlon(user_lon_node->getDoubleValue());
1056 t->pos.setlat(user_lat_node->getDoubleValue());
1057 t->pos.setelev(user_elev_node->getDoubleValue());
1059 // TODO - set/update the position if it's an AI plane
1061 //cout << "eta is " << t->eta << ", rwy is " << (rwyList.size() ? "occupied " : "clear ") << '\n';
1062 if(t->eta < 12 && rwyList.size() && !(t->instructedToGoAround)) {
1063 // TODO - need to make this more sophisticated
1064 // eg. is the plane accelerating down the runway taking off [OK],
1065 // or stationary near the start [V. BAD!!].
1066 // For now this should stop the AI plane landing on top of the user.
1067 string trns = t->plane.callsign;
1068 trns += " GO AROUND TRAFFIC ON RUNWAY I REPEAT GO AROUND";
1070 globals->get_ATC_display()->RegisterSingleMessage(trns, 0);
1072 t->instructedToGoAround = true;
1073 t->clearedToLand = false;
1074 t->nextOnRwy = false; // But note this is recalculated so don't rely on it
1075 // Assume it complies!!!
1076 t->opType = CIRCUIT;
1080 //cout << "Registering Go-around transmission with AI plane\n";
1081 t->planePtr->RegisterTransmission(13);
1084 // TODO - add Go-around ack to comm options,
1085 // remove report rwy vacated. (possibly).
1088 if(t->nextOnRwy && !(t->clearedToLand) && !(t->instructedToGoAround)) {
1089 // check distance away and whether runway occupied
1090 // and schedule transmission if necessary
1093 // Check for landing...
1094 bool landed = false;
1097 if(t->planePtr->GetLeg() == LANDING_ROLL) {
1101 SG_LOG(SG_ATC, SG_ALERT, "WARNING - not user and null planePtr in CheckApproachList!");
1104 if(OnActiveRunway(t->pos)) {
1110 // Duplicated in CheckCircuitList - must be able to rationalise this somehow!
1111 //cout << "A " << t->plane.callsign << " has landed, adding to rwyList...\n";
1112 rwyList.push_front(t);
1113 // TODO - if(!clearedToLand) shout something!!
1114 t->clearedToLand = false;
1115 RemoveFromTrafficList(t->plane.callsign);
1117 // t->opType = TTT_UNKNOWN;
1118 //} // TODO - allow the user to specify opType via ATC menu
1119 appListItr = appList.erase(appListItr);
1120 if(appListItr == appList.end() ) {
1121 appListItr = appList.begin();
1127 //cout << "Done" << endl;
1130 // Do one plane from the departure list
1131 void FGTower::CheckDepartureList(double dt) {
1132 if(depList.size()) {
1133 if(depListItr == depList.end()) {
1134 depListItr = depList.begin();
1136 TowerPlaneRec* t = *depListItr;
1137 //cout << "Dep list, checking " << t->plane.callsign;
1139 double distout; // meters
1140 if(t->isUser) distout = dclGetHorizontalSeparation(Point3D(lon, lat, elev), Point3D(user_lon_node->getDoubleValue(), user_lat_node->getDoubleValue(), user_elev_node->getDoubleValue()));
1141 else distout = dclGetHorizontalSeparation(Point3D(lon, lat, elev), t->planePtr->GetPos());
1142 //cout << " distout = " << distout << '\n';
1143 if(distout > 10000) {
1144 string trns = t->plane.callsign;
1145 trns += " You are now clear of my airspace, good day";
1147 globals->get_ATC_display()->RegisterSingleMessage(trns, 0);
1150 // Change the communication options
1151 RemoveAllUserDialogOptions();
1152 current_atcdialog->add_entry(ident, "@AP Tower @CS @MI miles @CD of the airport for full stop with the ATIS", "Contact tower for VFR arrival (full stop)", TOWER, (int)USER_REQUEST_VFR_ARRIVAL_FULL_STOP);
1154 // Send a clear-of-airspace signal
1155 // TODO - implement this once we actually have departing AI traffic (currently all circuits or arrivals).
1157 RemovePlane(t->plane.callsign);
1164 // ********** End periodic check functions ***********************************************
1165 // ***************************************************************************************
1168 // Remove all dialog options for this tower.
1169 void FGTower::RemoveAllUserDialogOptions() {
1170 current_atcdialog->remove_entry(ident, USER_REQUEST_VFR_DEPARTURE, TOWER);
1171 current_atcdialog->remove_entry(ident, USER_REQUEST_VFR_ARRIVAL, TOWER);
1172 current_atcdialog->remove_entry(ident, USER_REQUEST_VFR_ARRIVAL_FULL_STOP, TOWER);
1173 current_atcdialog->remove_entry(ident, USER_REQUEST_VFR_ARRIVAL_TOUCH_AND_GO, TOWER);
1174 current_atcdialog->remove_entry(ident, USER_REPORT_3_MILE_FINAL, TOWER);
1175 current_atcdialog->remove_entry(ident, USER_REPORT_DOWNWIND, TOWER);
1176 current_atcdialog->remove_entry(ident, USER_REPORT_RWY_VACATED, TOWER);
1177 current_atcdialog->remove_entry(ident, USER_REPORT_GOING_AROUND, TOWER);
1180 // Returns true if positions of crosswind/downwind/base leg turns should be constrained by previous traffic
1181 // plus the constraint position as a rwy orientated orthopos (meters)
1182 bool FGTower::GetCrosswindConstraint(double& cpos) {
1183 if(crosswind_leg_pos != 0.0) {
1184 cpos = crosswind_leg_pos;
1191 bool FGTower::GetDownwindConstraint(double& dpos) {
1192 if(fabs(downwind_leg_pos) > nominal_downwind_leg_pos) {
1193 dpos = downwind_leg_pos;
1200 bool FGTower::GetBaseConstraint(double& bpos) {
1201 if(base_leg_pos < nominal_base_leg_pos) {
1202 bpos = base_leg_pos;
1205 bpos = nominal_base_leg_pos;
1211 // Figure out which runways are active.
1212 // For now we'll just be simple and do one active runway - eventually this will get much more complex
1213 // This is a private function - public interface to the results of this is through GetActiveRunway
1214 void FGTower::DoRwyDetails() {
1215 //cout << "GetRwyDetails called" << endl;
1217 // Based on the airport-id and wind get the active runway
1220 double hdg = wind_from_hdg->getDoubleValue();
1221 double speed = wind_speed_knots->getDoubleValue();
1222 hdg = (speed == 0.0 ? 270.0 : hdg);
1223 //cout << "Heading = " << hdg << '\n';
1226 bool rwyGood = globals->get_runways()->search(ident, int(hdg), &runway);
1228 //cout << "RUNWAY GOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOD\n";
1229 activeRwy = runway.rwy_no;
1230 rwy.rwyID = runway.rwy_no;
1231 SG_LOG(SG_ATC, SG_INFO, "Active runway for airport " << ident << " is " << activeRwy);
1233 // Get the threshold position
1234 double other_way = runway.heading - 180.0;
1235 while(other_way <= 0.0) {
1238 // move to the +l end/center of the runway
1239 //cout << "Runway center is at " << runway.lon << ", " << runway.lat << '\n';
1240 Point3D origin = Point3D(runway.lon, runway.lat, aptElev);
1241 Point3D ref = origin;
1242 double tshlon, tshlat, tshr;
1243 double tolon, tolat, tor;
1244 rwy.length = runway.length * SG_FEET_TO_METER;
1245 rwy.width = runway.width * SG_FEET_TO_METER;
1246 geo_direct_wgs_84 ( aptElev, ref.lat(), ref.lon(), other_way,
1247 rwy.length / 2.0 - 25.0, &tshlat, &tshlon, &tshr );
1248 geo_direct_wgs_84 ( aptElev, ref.lat(), ref.lon(), runway.heading,
1249 rwy.length / 2.0 - 25.0, &tolat, &tolon, &tor );
1250 // Note - 25 meters in from the runway end is a bit of a hack to put the plane ahead of the user.
1251 // now copy what we need out of runway into rwy
1252 rwy.threshold_pos = Point3D(tshlon, tshlat, aptElev);
1253 Point3D takeoff_end = Point3D(tolon, tolat, aptElev);
1254 //cout << "Threshold position = " << tshlon << ", " << tshlat << ", " << aptElev << '\n';
1255 //cout << "Takeoff position = " << tolon << ", " << tolat << ", " << aptElev << '\n';
1256 rwy.hdg = runway.heading;
1257 // Set the projection for the local area based on this active runway
1258 ortho.Init(rwy.threshold_pos, rwy.hdg);
1259 rwy.end1ortho = ortho.ConvertToLocal(rwy.threshold_pos); // should come out as zero
1260 rwy.end2ortho = ortho.ConvertToLocal(takeoff_end);
1262 // Set the pattern direction
1263 // TODO - we'll check for a facilities file with this in eventually - for now assume left traffic except
1264 // for certain circumstances (RH parallel rwy).
1265 rwy.patternDirection = -1; // Left
1266 if(rwy.rwyID.size() == 3) {
1267 rwy.patternDirection = (rwy.rwyID.substr(2,1) == "R" ? 1 : -1);
1269 //cout << "Doing details, rwy.patterDirection is " << rwy.patternDirection << '\n';
1271 SG_LOG(SG_ATC, SG_ALERT, "Help - can't get good runway in FGTower!!");
1277 // Figure out if a given position lies on the active runway
1278 // Might have to change when we consider more than one active rwy.
1279 bool FGTower::OnActiveRunway(Point3D pt) {
1280 // TODO - check that the centre calculation below isn't confused by displaced thesholds etc.
1281 Point3D xyc((rwy.end1ortho.x() + rwy.end2ortho.x())/2.0, (rwy.end1ortho.y() + rwy.end2ortho.y())/2.0, 0.0);
1282 Point3D xyp = ortho.ConvertToLocal(pt);
1284 //cout << "Length offset = " << fabs(xyp.y() - xyc.y()) << '\n';
1285 //cout << "Width offset = " << fabs(xyp.x() - xyc.x()) << '\n';
1287 double rlen = rwy.length/2.0 + 5.0;
1288 double rwidth = rwy.width/2.0;
1289 double ldiff = fabs(xyp.y() - xyc.y());
1290 double wdiff = fabs(xyp.x() - xyc.x());
1292 return((ldiff < rlen) && (wdiff < rwidth));
1296 // Figure out if a given position lies on any runway or not
1297 // Only call this at startup - reading the runways database is expensive and needs to be fixed!
1298 bool FGTower::OnAnyRunway(Point3D pt) {
1300 double dist = current_commlist->FindClosest(lon, lat, elev, ad, TOWER, 10.0);
1304 // Based on the airport-id, go through all the runways and check for a point in them
1306 // TODO - do we actually need to search for the airport - surely we already know our ident and
1307 // can just search runways of our airport???
1308 //cout << "Airport ident is " << ad.ident << '\n';
1310 bool rwyGood = globals->get_runways()->search(ad.ident, &runway);
1312 SG_LOG(SG_ATC, SG_WARN, "Unable to find any runways for airport ID " << ad.ident << " in FGTower");
1315 while(runway.id == ad.ident) {
1316 on = OnRunway(pt, runway);
1317 //cout << "Runway " << runway.rwy_no << ": On = " << (on ? "true\n" : "false\n");
1318 if(on) return(true);
1319 globals->get_runways()->next(&runway);
1325 // Returns true if successful
1326 bool FGTower::RemoveFromTrafficList(string id) {
1327 tower_plane_rec_list_iterator twrItr;
1328 for(twrItr = trafficList.begin(); twrItr != trafficList.end(); twrItr++) {
1329 TowerPlaneRec* tpr = *twrItr;
1330 if(tpr->plane.callsign == id) {
1331 trafficList.erase(twrItr);
1332 trafficListItr = trafficList.begin();
1336 SG_LOG(SG_ATC, SG_WARN, "Warning - unable to remove aircraft " << id << " from trafficList in FGTower");
1341 // Returns true if successful
1342 bool FGTower::RemoveFromAppList(string id) {
1343 tower_plane_rec_list_iterator twrItr;
1344 for(twrItr = appList.begin(); twrItr != appList.end(); twrItr++) {
1345 TowerPlaneRec* tpr = *twrItr;
1346 if(tpr->plane.callsign == id) {
1347 appList.erase(twrItr);
1348 appListItr = appList.begin();
1352 //SG_LOG(SG_ATC, SG_WARN, "Warning - unable to remove aircraft " << id << " from appList in FGTower");
1356 // Returns true if successful
1357 bool FGTower::RemoveFromRwyList(string id) {
1358 tower_plane_rec_list_iterator twrItr;
1359 for(twrItr = rwyList.begin(); twrItr != rwyList.end(); twrItr++) {
1360 TowerPlaneRec* tpr = *twrItr;
1361 if(tpr->plane.callsign == id) {
1362 rwyList.erase(twrItr);
1363 rwyListItr = rwyList.begin();
1367 //SG_LOG(SG_ATC, SG_WARN, "Warning - unable to remove aircraft " << id << " from rwyList in FGTower");
1372 // Add a tower plane rec with ETA to the traffic list in the correct position ETA-wise
1373 // and set nextOnRwy if so.
1374 // Returns true if this could cause a threshold ETA conflict with other traffic, false otherwise.
1375 // For planes holding they are put in the first position with time to go, and the return value is
1376 // true if in the first position (nextOnRwy) and false otherwise.
1377 // See the comments in FGTower::doThresholdUseOrder for notes on the ordering
1378 bool FGTower::AddToTrafficList(TowerPlaneRec* t, bool holding) {
1379 //cout << "ADD: " << trafficList.size();
1380 //cout << "AddToTrafficList called, currently size = " << trafficList.size() << ", holding = " << holding << endl;
1381 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.
1382 double departure_sep_time = 60.0; // Separation time behind departing airplanes. Comments above also apply.
1383 bool conflict = false;
1384 double lastETA = 0.0;
1385 bool firstTime = true;
1386 // FIXME - make this more robust for different plane types eg. light following heavy.
1387 tower_plane_rec_list_iterator twrItr;
1388 //twrItr = trafficList.begin();
1390 for(twrItr = trafficList.begin(); twrItr != trafficList.end(); twrItr++) {
1391 //if(twrItr == trafficList.end()) {
1393 // trafficList.push_back(t);
1394 // return(holding ? firstTime : conflict);
1396 TowerPlaneRec* tpr = *twrItr;
1398 //cout << (tpr->isUser ? "USER!\n" : "NOT user\n");
1399 //cout << "tpr->eta - lastETA = " << tpr->eta - lastETA << '\n';
1400 double dep_allowance = (timeSinceLastDeparture < departure_sep_time ? departure_sep_time - timeSinceLastDeparture : 0.0);
1401 double slot_time = (firstTime ? separation_time + dep_allowance : separation_time + departure_sep_time);
1402 // separation_time + departure_sep_time in the above accounts for the fact that the arrival could be touch and go,
1403 // and if not needs time to clear the rwy anyway.
1404 if(tpr->eta - lastETA > slot_time) {
1405 t->nextOnRwy = firstTime;
1406 trafficList.insert(twrItr, t);
1407 //cout << "\tH\t" << trafficList.size() << '\n';
1412 if(t->eta < tpr->eta) {
1413 // Ugg - this one's tricky.
1414 // It depends on what the two planes are doing and whether there's a conflict what we do.
1415 if(tpr->eta - t->eta > separation_time) { // No probs, plane 2 can squeeze in before plane 1 with no apparent conflict
1416 if(tpr->nextOnRwy) {
1417 tpr->nextOnRwy = false;
1418 t->nextOnRwy = true;
1420 trafficList.insert(twrItr, t);
1421 } else { // Ooops - this ones tricky - we have a potential conflict!
1423 // HACK - just add anyway for now and flag conflict - TODO - FIX THIS using CIRCUIT/STRAIGHT_IN and VFR/IFR precedence rules.
1424 if(tpr->nextOnRwy) {
1425 tpr->nextOnRwy = false;
1426 t->nextOnRwy = true;
1428 trafficList.insert(twrItr, t);
1430 //cout << "\tC\t" << trafficList.size() << '\n';
1437 // If we get here we must be at the end of the list, or maybe the list is empty.
1438 if(!trafficList.size()) {
1439 t->nextOnRwy = true;
1440 // conflict and firstTime should be false and true respectively in this case anyway.
1442 trafficList.push_back(t);
1443 //cout << "\tE\t" << trafficList.size() << endl;
1444 return(holding ? firstTime : conflict);
1447 // Add a tower plane rec with ETA to the circuit list in the correct position ETA-wise
1448 // Returns true if this might cause a separation conflict (based on ETA) with other traffic, false otherwise.
1449 // Safe to add a plane that is already in - planes with the same callsign are not added.
1450 bool FGTower::AddToCircuitList(TowerPlaneRec* t) {
1452 //cout << "**********************************************\n";
1453 //cout << "AddToCircuitList called with NULL pointer!!!!!\n";
1454 //cout << "**********************************************\n";
1457 //cout << "ADD: " << circuitList.size();
1458 //cout << ident << " AddToCircuitList called for " << t->plane.callsign << ", currently size = " << circuitList.size() << endl;
1459 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.
1460 bool conflict = false;
1461 tower_plane_rec_list_iterator twrItr;
1462 // First check if the plane is already in the list
1463 //cout << "A" << endl;
1464 //cout << "Checking whether " << t->plane.callsign << " is already in circuit list..." << endl;
1465 //cout << "B" << endl;
1466 for(twrItr = circuitList.begin(); twrItr != circuitList.end(); twrItr++) {
1467 if((*twrItr)->plane.callsign == t->plane.callsign) {
1468 //cout << "In list - returning...\n";
1472 //cout << "Not in list - adding..." << endl;
1474 for(twrItr = circuitList.begin(); twrItr != circuitList.end(); twrItr++) {
1475 TowerPlaneRec* tpr = *twrItr;
1476 //cout << tpr->plane.callsign << " eta is " << tpr->eta << '\n';
1477 //cout << "New eta is " << t->eta << '\n';
1478 if(t->eta < tpr->eta) {
1479 // Ugg - this one's tricky.
1480 // It depends on what the two planes are doing and whether there's a conflict what we do.
1481 if(tpr->eta - t->eta > separation_time) { // No probs, plane 2 can squeeze in before plane 1 with no apparent conflict
1482 circuitList.insert(twrItr, t);
1483 } else { // Ooops - this ones tricky - we have a potential conflict!
1485 // HACK - just add anyway for now and flag conflict.
1486 circuitList.insert(twrItr, t);
1488 //cout << "\tC\t" << circuitList.size() << '\n';
1492 // If we get here we must be at the end of the list, or maybe the list is empty.
1493 //cout << ident << " adding " << t->plane.callsign << " to circuitList" << endl;
1494 circuitList.push_back(t); // TODO - check the separation with the preceding plane for the conflict flag.
1495 //cout << "\tE\t" << circuitList.size() << endl;
1500 // Calculate the eta of a plane to the threshold.
1501 // For ground traffic this is the fastest they can get there.
1502 // For air traffic this is the middle approximation.
1503 void FGTower::CalcETA(TowerPlaneRec* tpr, bool printout) {
1504 // For now we'll be very crude and hardwire expected speeds to C172-like values
1505 // The speeds below are specified in knots IAS and then converted to m/s
1506 double app_ias = 100.0 * 0.514444; // Speed during straight-in approach
1507 double circuit_ias = 80.0 * 0.514444; // Speed around circuit
1508 double final_ias = 70.0 * 0.514444; // Speed during final approach
1511 //cout << "In CalcETA, airplane ident = " << tpr->plane.callsign << '\n';
1512 //cout << (tpr->isUser ? "USER\n" : "AI\n");
1516 // Sign convention - dist_out is -ve for approaching planes and +ve for departing planes
1517 // dist_across is +ve in the pattern direction - ie a plane correctly on downwind will have a +ve dist_across
1519 Point3D op = ortho.ConvertToLocal(tpr->pos);
1521 //if(!tpr->isUser) cout << "Orthopos is " << op.x() << ", " << op.y() << ' ';
1522 //cout << "opType is " << tpr->opType << '\n';
1524 double dist_out_m = op.y();
1525 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
1526 //cout << "Doing ETA calc for " << tpr->plane.callsign << '\n';
1528 if(tpr->opType == STRAIGHT_IN || tpr->opType == INBOUND) {
1529 //cout << "CASE 1\n";
1530 double dist_to_go_m = sqrt((dist_out_m * dist_out_m) + (dist_across_m * dist_across_m));
1531 if(dist_to_go_m < 1000) {
1532 tpr->eta = dist_to_go_m / final_ias;
1534 tpr->eta = (1000.0 / final_ias) + ((dist_to_go_m - 1000.0) / app_ias);
1536 } else if(tpr->opType == CIRCUIT || tpr->opType == TTT_UNKNOWN) { // Hack alert - UNKNOWN has sort of been added here as a temporary hack.
1537 //cout << "CASE 2\n";
1538 // It's complicated - depends on if base leg is delayed or not
1540 //cout << "Leg = " << tpr->leg << '\n';
1542 if(tpr->leg == LANDING_ROLL) {
1544 } else if((tpr->leg == FINAL) || (tpr->leg == TURN4)) {
1545 //cout << "dist_out_m = " << dist_out_m << '\n';
1546 tpr->eta = fabs(dist_out_m) / final_ias;
1547 } else if((tpr->leg == BASE) || (tpr->leg == TURN3)) {
1548 tpr->eta = (fabs(dist_out_m) / final_ias) + (dist_across_m / circuit_ias);
1550 // Need to calculate where base leg is likely to be
1551 // FIXME - for now I'll hardwire it to 1000m which is what AILocalTraffic uses!!!
1552 // 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
1553 double nominal_base_dist_out_m = -1000;
1554 double current_base_dist_out_m;
1555 if(!GetBaseConstraint(current_base_dist_out_m)) {
1556 current_base_dist_out_m = nominal_base_dist_out_m;
1558 //cout << "current_base_dist_out_m = " << current_base_dist_out_m << '\n';
1559 double nominal_dist_across_m = 1000; // Hardwired value from AILocalTraffic
1560 double current_dist_across_m;
1561 if(!GetDownwindConstraint(current_dist_across_m)) {
1562 current_dist_across_m = nominal_dist_across_m;
1564 double nominal_cross_dist_out_m = 2000; // Bit of a guess - AI plane turns to crosswind at 700ft agl.
1565 tpr->eta = fabs(current_base_dist_out_m) / final_ias; // final
1566 //cout << "a = " << tpr->eta << '\n';
1567 if((tpr->leg == DOWNWIND) || (tpr->leg == TURN2)) {
1568 tpr->eta += dist_across_m / circuit_ias;
1569 //cout << "b = " << tpr->eta << '\n';
1570 tpr->eta += fabs(current_base_dist_out_m - dist_out_m) / circuit_ias;
1571 //cout << "c = " << tpr->eta << '\n';
1572 } else if((tpr->leg == CROSSWIND) || (tpr->leg == TURN1)) {
1573 //cout << "CROSSWIND calc: ";
1574 //cout << tpr->eta << ' ';
1575 if(dist_across_m > nominal_dist_across_m) {
1576 tpr->eta += dist_across_m / circuit_ias;
1579 tpr->eta += nominal_dist_across_m / circuit_ias;
1582 //cout << tpr->eta << ' ';
1583 // should we use the dist across of the previous plane if there is previous still on downwind?
1584 //if(printout) cout << "bb = " << tpr->eta << '\n';
1585 if(dist_out_m > nominal_cross_dist_out_m) {
1586 tpr->eta += fabs(current_base_dist_out_m - dist_out_m) / circuit_ias;
1589 tpr->eta += fabs(current_base_dist_out_m - nominal_cross_dist_out_m) / circuit_ias;
1592 //cout << tpr->eta << ' ';
1593 //if(printout) cout << "cc = " << tpr->eta << '\n';
1594 if(nominal_dist_across_m > dist_across_m) {
1595 tpr->eta += (nominal_dist_across_m - dist_across_m) / circuit_ias;
1601 //cout << tpr->eta << '\n';
1602 //if(printout) cout << "dd = " << tpr->eta << '\n';
1604 // We've only just started - why not use a generic estimate?
1609 // cout << "ETA = " << tpr->eta << '\n';
1611 //if(!tpr->isUser) cout << tpr->plane.callsign << '\t' << tpr->eta << '\n';
1618 // Calculate the distance of a plane to the threshold in meters
1619 // TODO - Modify to calculate flying distance of a plane in the circuit
1620 double FGTower::CalcDistOutM(TowerPlaneRec* tpr) {
1621 return(dclGetHorizontalSeparation(rwy.threshold_pos, tpr->pos));
1625 // Calculate the distance of a plane to the threshold in miles
1626 // TODO - Modify to calculate flying distance of a plane in the circuit
1627 double FGTower::CalcDistOutMiles(TowerPlaneRec* tpr) {
1628 return(CalcDistOutM(tpr) / 1600.0); // FIXME - use a proper constant if possible.
1632 // Iterate through all the lists, update the position of, and call CalcETA for all the planes.
1633 void FGTower::doThresholdETACalc() {
1634 //cout << "Entering doThresholdETACalc..." << endl;
1635 tower_plane_rec_list_iterator twrItr;
1636 // Do the approach list first
1637 for(twrItr = appList.begin(); twrItr != appList.end(); twrItr++) {
1638 TowerPlaneRec* tpr = *twrItr;
1639 if(!(tpr->isUser)) tpr->pos = tpr->planePtr->GetPos();
1643 // Then the circuit list
1644 //cout << "Circuit list size is " << circuitList.size() << '\n';
1645 for(twrItr = circuitList.begin(); twrItr != circuitList.end(); twrItr++) {
1646 TowerPlaneRec* tpr = *twrItr;
1647 if(!(tpr->isUser)) tpr->pos = tpr->planePtr->GetPos();
1651 //cout << "Done doThresholdETCCalc" << endl;
1655 // Check that the planes in traffic list are correctly ordered,
1656 // that the nearest (timewise) is flagged next on rwy, and return
1657 // true if any threshold use conflicts are detected, false otherwise.
1658 bool FGTower::doThresholdUseOrder() {
1659 //cout << "Entering doThresholdUseOrder..." << endl;
1660 bool conflict = false;
1662 // Wipe out traffic list, go through circuit, app and hold list, and reorder them in traffic list.
1663 // Here's the rather simplistic assumptions we're using:
1664 // Currently all planes are assumed to be GA light singles with corresponding speeds and separation times.
1665 // In order of priority for runway use:
1666 // STRAIGHT_IN > CIRCUIT > HOLDING_FOR_DEPARTURE
1667 // No modification of planes speeds occurs - conflicts are resolved by delaying turn for base,
1668 // and holding planes until a space.
1669 // When calculating if a holding plane can use the runway, time clearance from last departure
1670 // as well as time clearance to next arrival must be considered.
1672 trafficList.clear();
1674 tower_plane_rec_list_iterator twrItr;
1675 // Do the approach list first
1676 //cout << "A" << flush;
1677 for(twrItr = appList.begin(); twrItr != appList.end(); twrItr++) {
1678 TowerPlaneRec* tpr = *twrItr;
1679 conflict = AddToTrafficList(tpr);
1681 // Then the circuit list
1682 //cout << "C" << flush;
1683 for(twrItr = circuitList.begin(); twrItr != circuitList.end(); twrItr++) {
1684 TowerPlaneRec* tpr = *twrItr;
1685 conflict = AddToTrafficList(tpr);
1687 // And finally the hold list
1688 //cout << "H" << endl;
1689 for(twrItr = holdList.begin(); twrItr != holdList.end(); twrItr++) {
1690 TowerPlaneRec* tpr = *twrItr;
1691 AddToTrafficList(tpr, true);
1695 //if(ident == "KEMT") {
1696 for(twrItr = trafficList.begin(); twrItr != trafficList.end(); twrItr++) {
1697 TowerPlaneRec* tpr = *twrItr;
1698 cout << tpr->plane.callsign << '\t' << tpr->eta << '\t';
1703 //cout << "Done doThresholdUseOrder" << endl;
1708 // Return the ETA of plane no. list_pos (1-based) in the traffic list.
1709 // i.e. list_pos = 1 implies next to use runway.
1710 double FGTower::GetTrafficETA(unsigned int list_pos, bool printout) {
1711 if(trafficList.size() < list_pos) {
1715 tower_plane_rec_list_iterator twrItr;
1716 twrItr = trafficList.begin();
1717 for(unsigned int i = 1; i < list_pos; i++, twrItr++);
1718 TowerPlaneRec* tpr = *twrItr;
1719 CalcETA(tpr, printout);
1720 //cout << "ETA returned = " << tpr->eta << '\n';
1725 void FGTower::ContactAtHoldShort(PlaneRec plane, FGAIPlane* requestee, tower_traffic_type operation) {
1726 // HACK - assume that anything contacting at hold short is new for now - FIXME LATER
1727 TowerPlaneRec* t = new TowerPlaneRec;
1729 t->planePtr = requestee;
1730 t->holdShortReported = true;
1731 t->clearedToLineUp = false;
1732 t->clearedToTakeOff = false;
1733 t->opType = operation;
1734 t->pos = requestee->GetPos();
1736 //cout << "Hold Short reported by " << plane.callsign << '\n';
1737 SG_LOG(SG_ATC, SG_BULK, "Hold Short reported by " << plane.callsign);
1740 bool next = AddToTrafficList(t, true);
1742 double teta = GetTrafficETA(2);
1744 t->clearanceCounter = 7.0; // This reduces the delay before response to 3 secs if an immediate takeoff is reqd
1745 //cout << "Reducing response time to request due imminent traffic\n";
1750 // TODO - possibly add the reduced interval to clearance when immediate back in under the new scheme
1752 holdList.push_back(t);
1754 responseReqd = true;
1757 // Register the presence of an AI plane at a point where contact would already have been made in real life
1758 // CAUTION - currently it is assumed that this plane's callsign is unique - it is up to AIMgr to generate unique callsigns.
1759 void FGTower::RegisterAIPlane(PlaneRec plane, FGAIPlane* ai, tower_traffic_type op, PatternLeg lg) {
1760 // At the moment this is only going to be tested with inserting an AI plane on downwind
1761 TowerPlaneRec* t = new TowerPlaneRec;
1766 t->pos = ai->GetPos();
1770 if(op == CIRCUIT && lg != LEG_UNKNOWN) {
1771 cout << "AAAAAAAAAAAAAAAaa" << endl;
1772 AddToCircuitList(t);
1773 cout << "BBBBBBBBBBBBBBbbb" << endl;
1778 doThresholdUseOrder();
1781 void FGTower::DeregisterAIPlane(string id) {
1785 // Contact tower for VFR approach
1786 // eg "Cessna Charlie Foxtrot Golf Foxtrot Sierra eight miles South of the airport for full stop with Bravo"
1787 // This function probably only called via user interaction - AI planes will have an overloaded function taking a planerec.
1788 // opt defaults to AIP_LT_UNKNOWN
1789 void FGTower::VFRArrivalContact(string ID, LandingType opt) {
1790 //cout << "USER Request Landing Clearance called for ID " << ID << '\n';
1792 // For now we'll assume that the user is a light plane and can get him/her to join the circuit if necessary.
1795 string usercall = fgGetString("/sim/user/callsign");
1796 if(ID == "USER" || ID == usercall) {
1797 t = FindPlane(usercall);
1799 //cout << "NOT t\n";
1800 t = new TowerPlaneRec;
1802 t->pos.setlon(user_lon_node->getDoubleValue());
1803 t->pos.setlat(user_lat_node->getDoubleValue());
1804 t->pos.setelev(user_elev_node->getDoubleValue());
1807 // Oops - the plane is already registered with this tower - maybe we took off and flew a giant circuit without
1808 // quite getting out of tower airspace - just ignore for now and treat as new arrival.
1809 // TODO - Maybe should remove from departure and circuit list if in there though!!
1812 // Oops - something has gone wrong - put out a warning
1813 cout << "WARNING - FGTower::VFRContact(string ID, LandingType lt) called with ID " << ID << " which does not appear to be the user.\n";
1819 // Calculate where the plane is in relation to the active runway and it's circuit
1820 // and set the op-type as appropriate.
1822 // HACK - to get up and running I'm going to assume that the user contacts tower on a staight-in final for now.
1823 t->opType = STRAIGHT_IN;
1825 t->plane.type = GA_SINGLE; // FIXME - Another assumption!
1826 t->plane.callsign = usercall;
1828 t->vfrArrivalReported = true;
1829 responseReqd = true;
1831 appList.push_back(t); // Not necessarily permanent
1832 AddToTrafficList(t);
1834 current_atcdialog->remove_entry(ident, USER_REQUEST_VFR_ARRIVAL, TOWER);
1835 current_atcdialog->remove_entry(ident, USER_REQUEST_VFR_ARRIVAL_FULL_STOP, TOWER);
1836 current_atcdialog->remove_entry(ident, USER_REQUEST_VFR_ARRIVAL_TOUCH_AND_GO, TOWER);
1839 // landingType defaults to AIP_LT_UNKNOWN
1840 void FGTower::VFRArrivalContact(PlaneRec plane, FGAIPlane* requestee, LandingType lt) {
1841 //cout << "VFRArrivalContact called for plane " << plane.callsign << " at " << ident << '\n';
1842 // Possible hack - assume this plane is new for now - TODO - should check really
1843 TowerPlaneRec* t = new TowerPlaneRec;
1845 t->planePtr = requestee;
1846 t->landingType = lt;
1847 t->pos = requestee->GetPos();
1849 //cout << "Hold Short reported by " << plane.callsign << '\n';
1850 SG_LOG(SG_ATC, SG_BULK, "VFR arrival contact made by " << plane.callsign);
1851 //cout << "VFR arrival contact made by " << plane.callsign << '\n';
1853 // HACK - to get up and running I'm going to assume a staight-in final for now.
1854 t->opType = STRAIGHT_IN;
1856 t->vfrArrivalReported = true;
1857 responseReqd = true;
1859 //cout << "Before adding, appList.size = " << appList.size() << " at " << ident << '\n';
1860 appList.push_back(t); // Not necessarily permanent
1861 //cout << "After adding, appList.size = " << appList.size() << " at " << ident << '\n';
1862 AddToTrafficList(t);
1865 void FGTower::RequestDepartureClearance(string ID) {
1866 //cout << "Request Departure Clearance called...\n";
1869 void FGTower::ReportFinal(string ID) {
1870 //cout << "Report Final Called at tower " << ident << " by plane " << ID << '\n';
1872 ID = fgGetString("/sim/user/callsign");
1873 current_atcdialog->remove_entry(ident, USER_REPORT_3_MILE_FINAL, TOWER);
1875 TowerPlaneRec* t = FindPlane(ID);
1877 t->finalReported = true;
1878 t->finalAcknowledged = false;
1879 if(!(t->clearedToLand)) {
1880 responseReqd = true;
1882 // possibly respond with wind even if already cleared to land?
1883 t->finalReported = false;
1884 t->finalAcknowledged = true;
1885 // HACK!! - prevents next reporting being misinterpreted as this one.
1888 SG_LOG(SG_ATC, SG_WARN, "WARNING: Unable to find plane " << ID << " in FGTower::ReportFinal(...)");
1892 void FGTower::ReportLongFinal(string ID) {
1894 ID = fgGetString("/sim/user/callsign");
1895 current_atcdialog->remove_entry(ident, USER_REPORT_3_MILE_FINAL, TOWER);
1897 TowerPlaneRec* t = FindPlane(ID);
1899 t->longFinalReported = true;
1900 t->longFinalAcknowledged = false;
1901 if(!(t->clearedToLand)) {
1902 responseReqd = true;
1903 } // possibly respond with wind even if already cleared to land?
1905 SG_LOG(SG_ATC, SG_WARN, "WARNING: Unable to find plane " << ID << " in FGTower::ReportLongFinal(...)");
1909 //void FGTower::ReportOuterMarker(string ID);
1910 //void FGTower::ReportMiddleMarker(string ID);
1911 //void FGTower::ReportInnerMarker(string ID);
1912 //void FGTower::ReportGoingAround(string ID);
1914 void FGTower::ReportRunwayVacated(string ID) {
1915 //cout << "Report Runway Vacated Called at tower " << ident << " by plane " << ID << '\n';
1917 ID = fgGetString("/sim/user/callsign");
1918 current_atcdialog->remove_entry(ident, USER_REPORT_RWY_VACATED, TOWER);
1920 TowerPlaneRec* t = FindPlane(ID);
1922 //cout << "Found it...\n";
1923 t->rwyVacatedReported = true;
1924 responseReqd = true;
1926 SG_LOG(SG_ATC, SG_WARN, "WARNING: Unable to find plane " << ID << " in FGTower::ReportRunwayVacated(...)");
1927 SG_LOG(SG_ATC, SG_ALERT, "A WARNING: Unable to find plane " << ID << " in FGTower::ReportRunwayVacated(...)");
1928 //cout << "WARNING: Unable to find plane " << ID << " in FGTower::ReportRunwayVacated(...)\n";
1932 TowerPlaneRec* FGTower::FindPlane(string ID) {
1933 //cout << "FindPlane called for " << ID << "...\n";
1934 tower_plane_rec_list_iterator twrItr;
1935 // Do the approach list first
1936 for(twrItr = appList.begin(); twrItr != appList.end(); twrItr++) {
1937 //cout << "appList callsign is " << (*twrItr)->plane.callsign << '\n';
1938 if((*twrItr)->plane.callsign == ID) return(*twrItr);
1940 // Then the circuit list
1941 for(twrItr = circuitList.begin(); twrItr != circuitList.end(); twrItr++) {
1942 //cout << "circuitList callsign is " << (*twrItr)->plane.callsign << '\n';
1943 if((*twrItr)->plane.callsign == ID) return(*twrItr);
1945 // Then the runway list
1946 //cout << "rwyList.size() is " << rwyList.size() << '\n';
1947 for(twrItr = rwyList.begin(); twrItr != rwyList.end(); twrItr++) {
1948 //cout << "rwyList callsign is " << (*twrItr)->plane.callsign << '\n';
1949 if((*twrItr)->plane.callsign == ID) return(*twrItr);
1952 for(twrItr = holdList.begin(); twrItr != holdList.end(); twrItr++) {
1953 if((*twrItr)->plane.callsign == ID) return(*twrItr);
1955 // And finally the vacated list
1956 for(twrItr = vacatedList.begin(); twrItr != vacatedList.end(); twrItr++) {
1957 //cout << "vacatedList callsign is " << (*twrItr)->plane.callsign << '\n';
1958 if((*twrItr)->plane.callsign == ID) return(*twrItr);
1960 SG_LOG(SG_ATC, SG_WARN, "Unable to find " << ID << " in FGTower::FindPlane(...)");
1965 void FGTower::RemovePlane(string ID) {
1966 //cout << ident << " RemovePlane called for " << ID << '\n';
1967 // We have to be careful here - we want to erase the plane from all lists it is in,
1968 // but we can only delete it once, AT THE END.
1969 TowerPlaneRec* t = NULL;
1970 tower_plane_rec_list_iterator twrItr;
1971 for(twrItr = appList.begin(); twrItr != appList.end(); twrItr++) {
1972 if((*twrItr)->plane.callsign == ID) {
1974 twrItr = appList.erase(twrItr);
1975 appListItr = appList.begin();
1978 for(twrItr = depList.begin(); twrItr != depList.end(); twrItr++) {
1979 if((*twrItr)->plane.callsign == ID) {
1981 twrItr = depList.erase(twrItr);
1982 depListItr = depList.begin();
1985 for(twrItr = circuitList.begin(); twrItr != circuitList.end(); twrItr++) {
1986 if((*twrItr)->plane.callsign == ID) {
1988 twrItr = circuitList.erase(twrItr);
1989 circuitListItr = circuitList.begin();
1992 for(twrItr = holdList.begin(); twrItr != holdList.end(); twrItr++) {
1993 if((*twrItr)->plane.callsign == ID) {
1995 twrItr = holdList.erase(twrItr);
1996 holdListItr = holdList.begin();
1999 for(twrItr = rwyList.begin(); twrItr != rwyList.end(); twrItr++) {
2000 if((*twrItr)->plane.callsign == ID) {
2002 twrItr = rwyList.erase(twrItr);
2003 rwyListItr = rwyList.begin();
2006 for(twrItr = vacatedList.begin(); twrItr != vacatedList.end(); twrItr++) {
2007 if((*twrItr)->plane.callsign == ID) {
2009 twrItr = vacatedList.erase(twrItr);
2010 vacatedListItr = vacatedList.begin();
2013 for(twrItr = trafficList.begin(); twrItr != trafficList.end(); twrItr++) {
2014 if((*twrItr)->plane.callsign == ID) {
2016 twrItr = trafficList.erase(twrItr);
2017 trafficListItr = trafficList.begin();
2020 // And finally, delete the record if we found it.
2024 void FGTower::ReportDownwind(string ID) {
2025 //cout << "ReportDownwind(...) called\n";
2027 ID = fgGetString("/sim/user/callsign");
2028 current_atcdialog->remove_entry(ident, USER_REPORT_DOWNWIND, TOWER);
2030 TowerPlaneRec* t = FindPlane(ID);
2032 t->downwindReported = true;
2033 responseReqd = true;
2034 // If the plane is in the app list, remove it and put it in the circuit list instead.
2035 // Ideally we might want to do this at the 2 mile report prior to 45 deg entry, but at
2036 // the moment that would b&gg?r up the constraint position calculations.
2037 RemoveFromAppList(ID);
2039 t->pos = t->planePtr->GetPos();
2041 //cout << "DDDDDDDDDDDDDDDDdddddddd" << endl;
2042 AddToCircuitList(t);
2043 //cout << "EEEEEEEEEEEEEEEEEEEEeeeeee" << endl;
2045 SG_LOG(SG_ATC, SG_WARN, "WARNING: Unable to find plane " << ID << " in FGTower::ReportDownwind(...)");
2049 string FGTower::GenText(const string& m, int c) {
2050 const int cmax = 300;
2060 string usercall = fgGetString("/sim/user/callsign");
2062 //transmission_list_type tmissions = transmissionlist_station[station];
2063 //transmission_list_iterator current = tmissions.begin();
2064 //transmission_list_iterator last = tmissions.end();
2066 //for ( ; current != last ; ++current ) {
2067 // if ( current->get_code().c1 == code.c1 &&
2068 // current->get_code().c2 == code.c2 &&
2069 // current->get_code().c3 == code.c3 ) {
2071 //if ( ttext ) message = current->get_transtext();
2072 //else message = current->get_menutext();
2073 strcpy( &mes[0], m.c_str() );
2075 // Replace all the '@' parameters with the actual text.
2076 int check = 0; // If mes gets overflowed the while loop can go infinite
2077 while ( strchr(&mes[0], crej) != NULL ) { // ie. loop until no more occurances of crej ('@') found
2078 pos = strchr( &mes[0], crej );
2079 bcopy(pos, &tag[0], 3);
2083 for ( i=0; i<cmax; i++ ) {
2084 if ( mes[i] == crej ) {
2089 strncpy( &dum[0], &mes[0], len );
2092 if ( strcmp ( tag, "@ST" ) == 0 )
2093 //strcat( &dum[0], tpars.station.c_str() );
2094 strcat(&dum[0], ident.c_str());
2095 else if ( strcmp ( tag, "@AP" ) == 0 )
2096 //strcat( &dum[0], tpars.airport.c_str() );
2097 strcat(&dum[0], name.c_str());
2098 else if ( strcmp ( tag, "@CS" ) == 0 )
2099 //strcat( &dum[0], tpars.callsign.c_str() );
2100 strcat(&dum[0], usercall.c_str());
2101 else if ( strcmp ( tag, "@TD" ) == 0 ) {
2103 if ( tpars.tdir == 1 ) {
2104 char buf[] = "left";
2105 strcat( &dum[0], &buf[0] );
2108 char buf[] = "right";
2109 strcat( &dum[0], &buf[0] );
2113 else if ( strcmp ( tag, "@HE" ) == 0 ) {
2116 sprintf( buf, "%i", (int)(tpars.heading) );
2117 strcat( &dum[0], &buf[0] );
2120 else if ( strcmp ( tag, "@VD" ) == 0 ) {
2122 if ( tpars.VDir == 1 ) {
2123 char buf[] = "Descend and maintain";
2124 strcat( &dum[0], &buf[0] );
2126 else if ( tpars.VDir == 2 ) {
2127 char buf[] = "Maintain";
2128 strcat( &dum[0], &buf[0] );
2130 else if ( tpars.VDir == 3 ) {
2131 char buf[] = "Climb and maintain";
2132 strcat( &dum[0], &buf[0] );
2136 else if ( strcmp ( tag, "@AL" ) == 0 ) {
2139 sprintf( buf, "%i", (int)(tpars.alt) );
2140 strcat( &dum[0], &buf[0] );
2143 else if ( strcmp ( tag, "@MI" ) == 0 ) {
2145 //sprintf( buf, "%3.1f", tpars.miles );
2146 int dist_miles = (int)dclGetHorizontalSeparation(Point3D(lon, lat, elev), Point3D(user_lon_node->getDoubleValue(), user_lat_node->getDoubleValue(), user_elev_node->getDoubleValue())) / 1600;
2147 sprintf(buf, "%i", dist_miles);
2148 strcat( &dum[0], &buf[0] );
2150 else if ( strcmp ( tag, "@FR" ) == 0 ) {
2153 sprintf( buf, "%6.2f", tpars.freq );
2154 strcat( &dum[0], &buf[0] );
2157 else if ( strcmp ( tag, "@RW" ) == 0 ) {
2158 strcat(&dum[0], ConvertRwyNumToSpokenString(activeRwy).c_str());
2159 } else if(strcmp(tag, "@CD") == 0) { // @CD = compass direction
2160 double h = GetHeadingFromTo(Point3D(lon, lat, elev), Point3D(user_lon_node->getDoubleValue(), user_lat_node->getDoubleValue(), user_elev_node->getDoubleValue()));
2161 while(h < 0.0) h += 360.0;
2162 while(h > 360.0) h -= 360.0;
2163 if(h < 22.5 || h > 337.5) {
2164 strcat(&dum[0], "North");
2165 } else if(h < 67.5) {
2166 strcat(&dum[0], "North-East");
2167 } else if(h < 112.5) {
2168 strcat(&dum[0], "East");
2169 } else if(h < 157.5) {
2170 strcat(&dum[0], "South-East");
2171 } else if(h < 202.5) {
2172 strcat(&dum[0], "South");
2173 } else if(h < 247.5) {
2174 strcat(&dum[0], "South-West");
2175 } else if(h < 292.5) {
2176 strcat(&dum[0], "West");
2178 strcat(&dum[0], "North-West");
2181 cout << "Tag " << tag << " not found" << endl;
2184 strcat( &dum[0], &mes[len+3] );
2185 strcpy( &mes[0], &dum[0] );
2189 SG_LOG(SG_ATC, SG_WARN, "WARNING: Possibly endless loop terminated in FGTransmissionlist::gen_text(...)");
2194 //cout << mes << endl;
2198 if ( mes != "" ) return mes;
2199 else return "No transmission found";
2202 ostream& operator << (ostream& os, tower_traffic_type ttt) {
2204 case(CIRCUIT): return(os << "CIRCUIT");
2205 case(INBOUND): return(os << "INBOUND");
2206 case(OUTBOUND): return(os << "OUTBOUND");
2207 case(TTT_UNKNOWN): return(os << "UNKNOWN");
2208 case(STRAIGHT_IN): return(os << "STRAIGHT_IN");
2210 return(os << "ERROR - Unknown switch in tower_traffic_type operator << ");