From: daveluff Date: Fri, 23 Jan 2004 17:18:24 +0000 (+0000) Subject: Lots of changes to the ATC/AI system for initial revision of random AI GA VFR traffic X-Git-Url: https://git.mxchange.org/?a=commitdiff_plain;h=a739fad66484506ebb71fc802bf8f1a0c2ab4c6c;p=flightgear.git Lots of changes to the ATC/AI system for initial revision of random AI GA VFR traffic --- diff --git a/src/ATC/AIEntity.cxx b/src/ATC/AIEntity.cxx index 2b333cfdb..d87b86dbf 100644 --- a/src/ATC/AIEntity.cxx +++ b/src/ATC/AIEntity.cxx @@ -40,19 +40,39 @@ #include "AIEntity.hxx" +FGAIEntity::FGAIEntity() { +} + FGAIEntity::~FGAIEntity() { + //cout << "FGAIEntity dtor called..." << endl; + _model->deRef(); // Ought to check valid? + //cout << "Removing model from scene graph..." << endl; + globals->get_scenery()->get_scene_graph()->removeKid(_aip.getSceneGraph()); + //cout << "Done!" << endl; +} + +void FGAIEntity::SetModel(ssgBranch* model) { + _model = model; + _model->ref(); + _aip.init(_model); + _aip.setVisible(false); + globals->get_scenery()->get_scene_graph()->addKid(_aip.getSceneGraph()); } void FGAIEntity::Update(double dt) { } +string FGAIEntity::GetCallsign() { + return(""); +} + void FGAIEntity::RegisterTransmission(int code) { } // Run the internal calculations //void FGAIEntity::Update() { void FGAIEntity::Transform() { - aip.setPosition(pos.lon(), pos.lat(), pos.elev() * SG_METER_TO_FEET); - aip.setOrientation(roll, pitch, hdg); - aip.update( globals->get_scenery()->get_center() ); + _aip.setPosition(_pos.lon(), _pos.lat(), _pos.elev() * SG_METER_TO_FEET); + _aip.setOrientation(_roll, _pitch, _hdg); + _aip.update( globals->get_scenery()->get_center() ); } diff --git a/src/ATC/AIEntity.hxx b/src/ATC/AIEntity.hxx index 436eb186d..d3d60ac2d 100644 --- a/src/ATC/AIEntity.hxx +++ b/src/ATC/AIEntity.hxx @@ -41,7 +41,11 @@ class FGAIEntity { public: + FGAIEntity(); virtual ~FGAIEntity(); + + // Set the 3D model to use (Must be called) + void SetModel(ssgBranch* model); // Run the internal calculations virtual void Update(double dt); @@ -50,17 +54,20 @@ public: // FIXME int code is a hack - eventually this will receive Alexander's coded messages. virtual void RegisterTransmission(int code); - inline Point3D GetPos() { return(pos); } - + inline Point3D GetPos() { return(_pos); } + + virtual string GetCallsign(); + protected: - Point3D pos; // WGS84 lat & lon in degrees, elev above sea-level in meters - double hdg; //True heading in degrees - double roll; //degrees - double pitch; //degrees + Point3D _pos; // WGS84 lat & lon in degrees, elev above sea-level in meters + double _hdg; //True heading in degrees + double _roll; //degrees + double _pitch; //degrees - char* model_path; //Path to the 3D model - SGModelPlacement aip; + char* _model_path; //Path to the 3D model + ssgBranch* _model; // Pointer to the model + SGModelPlacement _aip; void Transform(); }; diff --git a/src/ATC/AILocalTraffic.cxx b/src/ATC/AILocalTraffic.cxx index e427bb382..76a294f60 100644 --- a/src/ATC/AILocalTraffic.cxx +++ b/src/ATC/AILocalTraffic.cxx @@ -51,17 +51,28 @@ SG_USING_STD(string); #include "ATCmgr.hxx" #include "AILocalTraffic.hxx" #include "ATCutils.hxx" +#include "AIMgr.hxx" FGAILocalTraffic::FGAILocalTraffic() { + /*ssgBranch *model = sgLoad3DModel( globals->get_fg_root(), + planepath.c_str(), + globals->get_props(), + globals->get_sim_time_sec() ); + *//* + _model = model; + _model->ref(); + _aip.init(_model); + */ + //SetModel(model); + ATC = globals->get_ATC_mgr(); - // TODO - unhardwire this - possibly let the AI manager set the callsign - plane.callsign = "Trainer-two-five-charlie"; + // TODO - unhardwire this plane.type = GA_SINGLE; - roll = 0.0; - pitch = 0.0; - hdg = 270.0; + _roll = 0.0; + _pitch = 0.0; + _hdg = 270.0; //Hardwire initialisation for now - a lot of this should be read in from config eventually Vr = 70.0; @@ -90,23 +101,66 @@ FGAILocalTraffic::FGAILocalTraffic() { holdingShort = false; clearedToLineUp = false; clearedToTakeOff = false; + _clearedToLand = false; reportReadyForDeparture = false; contactTower = false; contactGround = false; + _taxiToGA = false; descending = false; targetDescentRate = 0.0; goAround = false; goAroundCalled = false; + + transmitted = false; + + freeTaxi = false; + _savedSlope = 0.0; + + _controlled = false; } FGAILocalTraffic::~FGAILocalTraffic() { + //_model->deRef(); } +void FGAILocalTraffic::GetAirportDetails(string id) { + AirportATC a; + if(ATC->GetAirportATCDetails(airportID, &a)) { + if(a.tower_freq) { // Has a tower - TODO - check the opening hours!!! + tower = (FGTower*)ATC->GetATCPointer(airportID, TOWER); // Maybe need some error checking here + if(tower == NULL) { + // Something has gone wrong - abort or carry on with un-towered operation? + SG_LOG(SG_ATC, SG_ALERT, "ERROR - can't get a tower pointer from tower control for " << airportID << " in FGAILocalTraffic::GetAirportDetails() :-("); + _controlled = false; + } else { + _controlled = true; + } + if(tower) { + ground = tower->GetGroundPtr(); + if(ground == NULL) { + // Something has gone wrong :-( + SG_LOG(SG_ATC, SG_ALERT, "ERROR - can't get a ground pointer from tower control in FGAILocalTraffic::GetAirportDetails() :-("); + } + } + } else { + _controlled = false; + // TODO - Check CTAF, unicom etc + } + } else { + SG_LOG(SG_ATC, SG_ALERT, "Unable to find airport details in for " << airportID << " in FGAILocalTraffic::GetAirportDetails() :-("); + _controlled = false; + } + // Get the airport elevation + aptElev = dclGetAirportElev(airportID.c_str()); + //cout << "Airport elev in AILocalTraffic = " << aptElev << '\n'; + // WARNING - we use this elev for the whole airport - some assumptions in the code + // might fall down with very slopey airports. +} // Get details of the active runway // It is assumed that by the time this is called the tower control and airport code will have been set up. -void FGAILocalTraffic::GetRwyDetails() { +void FGAILocalTraffic::GetRwyDetails(string id) { //cout << "GetRwyDetails called" << endl; rwy.rwyID = tower->GetActiveRunway(); @@ -114,12 +168,9 @@ void FGAILocalTraffic::GetRwyDetails() { // Now we need to get the threshold position and rwy heading FGRunway runway; - bool rwyGood = globals->get_runways()->search(airportID, rwy.rwyID, - &runway); + bool rwyGood = globals->get_runways()->search(id, rwy.rwyID, &runway); if(rwyGood) { - // Get the threshold position - hdg = runway.heading; // TODO - check - is this our heading we are setting here, and if so should we be? - //cout << "hdg reset to " << hdg << '\n'; + double hdg = runway.heading; double other_way = hdg - 180.0; while(other_way <= 0.0) { other_way += 360.0; @@ -132,6 +183,7 @@ void FGAILocalTraffic::GetRwyDetails() { double tshlon, tshlat, tshr; double tolon, tolat, tor; rwy.length = runway.length * SG_FEET_TO_METER; + rwy.width = runway.width * SG_FEET_TO_METER; geo_direct_wgs_84 ( aptElev, ref.lat(), ref.lon(), other_way, rwy.length / 2.0 - 25.0, &tshlat, &tshlon, &tshr ); geo_direct_wgs_84 ( aptElev, ref.lat(), ref.lon(), hdg, @@ -144,6 +196,7 @@ void FGAILocalTraffic::GetRwyDetails() { //cout << "Takeoff position = " << tolon << ", " << tolat << ", " << aptElev << '\n'; rwy.hdg = hdg; // Set the projection for the local area + //cout << "Initing ortho for airport " << id << '\n'; ortho.Init(rwy.threshold_pos, rwy.hdg); rwy.end1ortho = ortho.ConvertToLocal(rwy.threshold_pos); // should come out as zero rwy.end2ortho = ortho.ConvertToLocal(takeoff_end); @@ -164,56 +217,38 @@ To a certain extent it's FGAIMgr that has to worry about this, but we need to pr sufficient initialisation functionality within the plane classes to allow the manager to initially position them where and how required. */ -bool FGAILocalTraffic::Init(string ICAO, OperatingState initialState, PatternLeg initialLeg) { +bool FGAILocalTraffic::Init(const string& callsign, string ICAO, OperatingState initialState, PatternLeg initialLeg) { //cout << "FGAILocalTraffic.Init(...) called" << endl; - // Hack alert - Hardwired path!! - string planepath = "Aircraft/c172/Models/c172-dpm.ac"; - ssgBranch *model = sgLoad3DModel( globals->get_fg_root(), - planepath.c_str(), - globals->get_props(), - globals->get_sim_time_sec() ); - aip.init( model ); - aip.setVisible(false); // This will be set to true once a valid ground elevation has been determined - globals->get_scenery()->get_scene_graph()->addKid(aip.getSceneGraph()); - - // Find the tower frequency - this is dependent on the ATC system being initialised before the AI system airportID = ICAO; - AirportATC a; - if(ATC->GetAirportATCDetails(airportID, &a)) { - if(a.tower_freq) { // Has a tower - tower = (FGTower*)ATC->GetATCPointer(airportID, TOWER); // Maybe need some error checking here - if(tower == NULL) { - // Something has gone wrong - abort or carry on with un-towered operation? - return(false); - } - freq = (double)tower->get_freq() / 100.0; - ground = tower->GetGroundPtr(); - if(ground == NULL) { - // Something has gone wrong :-( - SG_LOG(SG_ATC, SG_ALERT, "ERROR - can't get a ground pointer from tower control in FGAILocalTraffic::Init() :-("); - return(false); - } else if((initialState == PARKED) || (initialState == TAXIING)) { - freq = (double)ground->get_freq() / 100.0; - } - //cout << "AILocalTraffic freq is " << freq << '\n'; - } else { - // TODO - Check CTAF, unicom etc - } - } else { - //cout << "Unable to find airport details in FGAILocalTraffic::Init()\n"; + + plane.callsign = callsign; + + if(initialState == EN_ROUTE) return(true); + + // Get the ATC pointers and airport elev + GetAirportDetails(airportID); + + // Get the active runway details (and copy them into rwy) + GetRwyDetails(airportID); + //cout << "Runway is " << rwy.rwyID << '\n'; + + // FIXME TODO - pattern direction is still hardwired + patternDirection = -1; // Left + // At the bare minimum we ought to make sure it goes the right way at dual parallel rwy airports! + if(rwy.rwyID.size() == 3) { + patternDirection = (rwy.rwyID.substr(2,1) == "R" ? 1 : -1); } - // Get the active runway details (and copy them into rwy) - GetRwyDetails(); - - // Get the airport elevation - aptElev = dclGetAirportElev(airportID.c_str()) * SG_FEET_TO_METER; - //cout << "Airport elev in AILocalTraffic = " << aptElev << '\n'; - // WARNING - we use this elev for the whole airport - some assumptions in the code - // might fall down with very slopey airports. + // TODO - this assumes a controlled airport - make sure we revert to CTAF etc if uncontrolled or after-hours. + if((initialState == PARKED) || (initialState == TAXIING)) { + freq = (double)ground->get_freq() / 100.0; + } else { + freq = (double)tower->get_freq() / 100.0; + } //cout << "In Init(), initialState = " << initialState << endl; operatingState = initialState; + Point3D orthopos; switch(operatingState) { case PARKED: tuned_station = ground; @@ -224,13 +259,13 @@ bool FGAILocalTraffic::Init(string ICAO, OperatingState initialState, PatternLeg SG_LOG(SG_ATC, SG_ALERT, "No gate found by FGAILocalTraffic whilst attempting Init at " << airportID << '\n'); return(false); } - pitch = 0.0; - roll = 0.0; + _pitch = 0.0; + _roll = 0.0; vel = 0.0; slope = 0.0; - pos = ourGate->pos; - pos.setelev(aptElev); - hdg = ourGate->heading; + _pos = ourGate->pos; + _pos.setelev(aptElev); + _hdg = ourGate->heading; // Now we've set the position we can do the ground elev elevInitGood = false; @@ -240,9 +275,36 @@ bool FGAILocalTraffic::Init(string ICAO, OperatingState initialState, PatternLeg Transform(); break; case TAXIING: - tuned_station = ground; + //tuned_station = ground; // FIXME - implement this case properly - return(false); // remove this line when fixed! + // For now we'll assume that the plane should start at the hold short in this case + // and that we're working without ground network elements. Ie. an airport with no facility file. + tuned_station = tower; + freeTaxi = true; + // Set a position and orientation in an approximate place for hold short. + //cout << "rwy.width = " << rwy.width << '\n'; + orthopos = Point3D((rwy.width / 2.0 + 10.0) * -1.0, 0.0, 0.0); + // TODO - set the x pos to be +ve if a RH parallel rwy. + _pos = ortho.ConvertFromLocal(orthopos); + _pos.setelev(aptElev); + _hdg = rwy.hdg + 90.0; + // TODO - reset the heading if RH rwy. + _pitch = 0.0; + _roll = 0.0; + vel = 0.0; + slope = 0.0; + elevInitGood = false; + inAir = false; + DoGroundElev(); + Transform(); + + responseCounter = 0.0; + contactTower = false; + changeFreq = true; + holdingShort = true; + clearedToLineUp = false; + changeFreqType = TOWER; + break; case IN_PATTERN: // For now we'll always start the in_pattern case on the threshold ready to take-off @@ -257,34 +319,28 @@ bool FGAILocalTraffic::Init(string ICAO, OperatingState initialState, PatternLeg circuitsToFly = 0; // ie just fly this circuit and then stop touchAndGo = false; - // FIXME TODO - pattern direction is still hardwired - patternDirection = -1; // Left - // At the bare minimum we ought to make sure it goes the right way at dual parallel rwy airports! - if(rwy.rwyID.size() == 3) { - patternDirection = (rwy.rwyID.substr(2,1) == "R" ? 1 : -1); - } if(initialLeg == DOWNWIND) { - pos = ortho.ConvertFromLocal(Point3D(1000*patternDirection, 800, 0.0)); - pos.setelev(rwy.threshold_pos.elev() + 1000 * SG_FEET_TO_METER); - hdg = rwy.hdg + 180.0; + _pos = ortho.ConvertFromLocal(Point3D(1000*patternDirection, 800, 0.0)); + _pos.setelev(rwy.threshold_pos.elev() + 1000 * SG_FEET_TO_METER); + _hdg = rwy.hdg + 180.0; leg = DOWNWIND; elevInitGood = false; inAir = true; track = rwy.hdg - (180 * patternDirection); //should tend to bring track back into the 0->360 range slope = 0.0; - pitch = 0.0; - roll = 0.0; + _pitch = 0.0; + _roll = 0.0; IAS = 90.0; descending = false; - aip.setVisible(true); + _aip.setVisible(true); tower->RegisterAIPlane(plane, this, CIRCUIT, DOWNWIND); } else { // Default to initial position on threshold for now - pos.setlat(rwy.threshold_pos.lat()); - pos.setlon(rwy.threshold_pos.lon()); - pos.setelev(rwy.threshold_pos.elev()); - hdg = rwy.hdg; + _pos.setlat(rwy.threshold_pos.lat()); + _pos.setlon(rwy.threshold_pos.lon()); + _pos.setelev(rwy.threshold_pos.elev()); + _hdg = rwy.hdg; // Now we've set the position we can do the ground elev // This might not always be necessary if we implement in-air start @@ -292,8 +348,8 @@ bool FGAILocalTraffic::Init(string ICAO, OperatingState initialState, PatternLeg inAir = false; DoGroundElev(); - pitch = 0.0; - roll = 0.0; + _pitch = 0.0; + _roll = 0.0; leg = TAKEOFF_ROLL; vel = 0.0; slope = 0.0; @@ -303,6 +359,9 @@ bool FGAILocalTraffic::Init(string ICAO, OperatingState initialState, PatternLeg Transform(); break; + case EN_ROUTE: + // This implies we're being init'd by AIGAVFRTraffic - simple return now + return(true); default: SG_LOG(SG_ATC, SG_ALERT, "Attempt to set unknown operating state in FGAILocalTraffic.Init(...)\n"); return(false); @@ -313,6 +372,41 @@ bool FGAILocalTraffic::Init(string ICAO, OperatingState initialState, PatternLeg } +// Set up downwind state - this is designed to be called from derived classes who are already tuned to tower +void FGAILocalTraffic::DownwindEntry() { + circuitsToFly = 0; // ie just fly this circuit and then stop + touchAndGo = false; + operatingState = IN_PATTERN; + leg = DOWNWIND; + elevInitGood = false; + inAir = true; + track = rwy.hdg - (180 * patternDirection); //should tend to bring track back into the 0->360 range + slope = 0.0; + _pitch = 0.0; + _roll = 0.0; + IAS = 90.0; + descending = false; +} + +void FGAILocalTraffic::StraightInEntry(bool des) { + //cout << "************ STRAIGHT-IN ********************\n"; + circuitsToFly = 0; // ie just fly this circuit and then stop + touchAndGo = false; + operatingState = IN_PATTERN; + leg = FINAL; + elevInitGood = false; + inAir = true; + track = rwy.hdg; + transmitted = true; // TODO - fix this hack. + // TODO - set up the next 5 properly for a descent! + slope = -5.5; + _pitch = 0.0; + _roll = 0.0; + IAS = 90.0; + descending = des; +} + + // Return what type of landing we're doing on this circuit LandingType FGAILocalTraffic::GetLandingOption() { //cout << "circuitsToFly = " << circuitsToFly << '\n'; @@ -334,19 +428,23 @@ void FGAILocalTraffic::FlyCircuits(int numCircuits, bool tag) { return; break; case TAXIING: - // TODO - For now we'll punt this and do nothing + // HACK - assume that we're taxiing out for now + circuitsToFly += numCircuits; + touchAndGo = tag; break; case PARKED: circuitsToFly = numCircuits; // Note that one too many circuits gets flown because we only test and decrement circuitsToFly after landing // thus flying one too many circuits. TODO - Need to sort this out better! touchAndGo = tag; break; + case EN_ROUTE: + break; } } // Run the internal calculations void FGAILocalTraffic::Update(double dt) { - //cout << "A" << flush; + //cout << "U" << flush; //double responseTime = 10.0; // seconds - this should get more sophisticated at some point responseCounter += dt; if((contactTower) && (responseCounter >= 8.0)) { @@ -366,6 +464,35 @@ void FGAILocalTraffic::Update(double dt) { changeFreqType = TOWER; } + if((contactGround) && (responseCounter >= 8.0)) { + // Acknowledge request before changing frequency so it gets rendered if the user is on the same freq + string trns = "Ground "; + double f = globals->get_ATC_mgr()->GetFrequency(airportID, GROUND) / 100.0; + char buf[10]; + sprintf(buf, "%.2f", f); + trns += buf; + trns += " "; + trns += "Good Day"; + pending_transmission = trns; + ConditionalTransmit(5.0); + responseCounter = 0.0; + contactGround = false; + changeFreq = true; + changeFreqType = GROUND; + } + + if((_taxiToGA) && (responseCounter >= 8.0)) { + // Acknowledge request before changing frequency so it gets rendered if the user is on the same freq + string trns = "GA Parking, Thank you and Good Day"; + //double f = globals->get_ATC_mgr()->GetFrequency(airportID, GROUND) / 100.0; + pending_transmission = trns; + ConditionalTransmit(5.0); + tower->DeregisterAIPlane(plane.callsign); + _taxiToGA = false; + // HACK - check if we are at a simple airport or not first + globals->get_AI_mgr()->ScheduleRemoval(plane.callsign); + } + if((changeFreq) && (responseCounter > 8.0)) { switch(changeFreqType) { case TOWER: @@ -373,7 +500,6 @@ void FGAILocalTraffic::Update(double dt) { freq = (double)tower->get_freq() / 100.0; //Transmit("DING!"); // Contact the tower, even if only virtually - changeFreq = false; pending_transmission = plane.callsign; pending_transmission += " at hold short for runway "; pending_transmission += ConvertRwyNumToSpokenString(rwy.rwyID); @@ -387,8 +513,12 @@ void FGAILocalTraffic::Update(double dt) { Transmit(2); break; case GROUND: + tower->DeregisterAIPlane(plane.callsign); tuned_station = ground; freq = (double)ground->get_freq() / 100.0; + // HACK - check if we are at a simple airport or not first + // TODO FIXME TODO FIXME !!!!!!! + if(airportID != "KEMT") globals->get_AI_mgr()->ScheduleRemoval(plane.callsign); break; // And to avoid compiler warnings... case APPROACH: break; @@ -397,9 +527,10 @@ void FGAILocalTraffic::Update(double dt) { case DEPARTURE: break; case INVALID: break; } + changeFreq = false; } - //cout << "." << flush; + //cout << "," << flush; switch(operatingState) { case IN_PATTERN: @@ -407,11 +538,11 @@ void FGAILocalTraffic::Update(double dt) { if(!inAir) { DoGroundElev(); if(!elevInitGood) { - if(aip.getSGLocation()->get_cur_elev_m() > -9990.0) { - pos.setelev(aip.getSGLocation()->get_cur_elev_m() + wheelOffset); + if(_aip.getSGLocation()->get_cur_elev_m() > -9990.0) { + _pos.setelev(_aip.getSGLocation()->get_cur_elev_m() + wheelOffset); //cout << "TAKEOFF_ROLL, POS = " << pos.lon() << ", " << pos.lat() << ", " << pos.elev() << '\n'; //Transform(); - aip.setVisible(true); + _aip.setVisible(true); //cout << "Making plane visible!\n"; elevInitGood = true; } @@ -425,17 +556,17 @@ void FGAILocalTraffic::Update(double dt) { //cout << "*" << flush; if(!elevInitGood) { //DoGroundElev(); - if(aip.getSGLocation()->get_cur_elev_m() > -9990.0) { - pos.setelev(aip.getSGLocation()->get_cur_elev_m() + wheelOffset); + if(_aip.getSGLocation()->get_cur_elev_m() > -9990.0) { + _pos.setelev(_aip.getSGLocation()->get_cur_elev_m() + wheelOffset); //Transform(); - aip.setVisible(true); + _aip.setVisible(true); //Transform(); //cout << "Making plane visible!\n"; elevInitGood = true; } } DoGroundElev(); - //cout << "," << flush; + //cout << "~" << flush; if(!((holdingShort) && (!clearedToLineUp))) { //cout << "|" << flush; Taxi(dt); @@ -445,7 +576,18 @@ void FGAILocalTraffic::Update(double dt) { // possible assumption that we're at the hold short here - may not always hold // TODO - sort out the case where we're cleared to line-up first and then cleared to take-off on the rwy. taxiState = TD_LINING_UP; + //cout << "A" << endl; path = ground->GetPath(holdShortNode, rwy.rwyID); + //cout << "B" << endl; + if(!path.size()) { // Assume no facility file so we'll just taxi to a point on the runway near the threshold + //cout << "C" << endl; + node* np = new node; + np->struct_type = NODE; + np->pos = ortho.ConvertFromLocal(Point3D(0.0, 10.0, 0.0)); + path.push_back(np); + } else { + //cout << "D" << endl; + } /* cout << "path returned was:" << endl; for(unsigned int i=0; iget_cur_elev_m() > -9990.0) { - pos.setelev(aip.getSGLocation()->get_cur_elev_m() + wheelOffset); + if(_aip.getSGLocation()->get_cur_elev_m() > -9990.0) { + _pos.setelev(_aip.getSGLocation()->get_cur_elev_m() + wheelOffset); //Transform(); - aip.setVisible(true); + _aip.setVisible(true); //Transform(); //cout << "Making plane visible!\n"; elevInitGood = true; @@ -488,7 +630,7 @@ void FGAILocalTraffic::Update(double dt) { if((taxiRequestPending) && (taxiRequestCleared)) { //cout << "&" << flush; // Get the active runway details (in case they've changed since init) - GetRwyDetails(); + GetRwyDetails(airportID); // Get the takeoff node for the active runway, get a path to it and start taxiing path = ground->GetPathToHoldShort(ourGate, rwy.rwyID); @@ -550,10 +692,10 @@ void FGAILocalTraffic::Update(double dt) { } //cout << "I " << flush; - // Convienience output for AI debugging user the property logger - fgSetDouble("/AI/Local1/ortho-x", (ortho.ConvertToLocal(pos)).x()); - fgSetDouble("/AI/Local1/ortho-y", (ortho.ConvertToLocal(pos)).y()); - fgSetDouble("/AI/Local1/elev", pos.elev() * SG_METER_TO_FEET); + // Convienience output for AI debugging using the property logger + //fgSetDouble("/AI/Local1/ortho-x", (ortho.ConvertToLocal(_pos)).x()); + //fgSetDouble("/AI/Local1/ortho-y", (ortho.ConvertToLocal(_pos)).y()); + //fgSetDouble("/AI/Local1/elev", _pos.elev() * SG_METER_TO_FEET); // And finally, call parent for transmission rendering FGAIPlane::Update(dt); @@ -580,9 +722,25 @@ void FGAILocalTraffic::RegisterTransmission(int code) { clearedToTakeOff = true; SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " cleared to take-off..."); break; + case 5: // contact ground + responseCounter = 0; + contactGround = true; + SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " told to contact ground..."); + break; + // case 6 is a temporary mega-hack for controlled airports without separate ground control + case 6: // taxi to the GA parking + responseCounter = 0; + _taxiToGA = true; + SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " told to taxi to the GA parking..."); + break; + case 7: // Cleared to land (also implies cleared for the option + _clearedToLand = true; + SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " cleared to land..."); + break; case 13: // Go around! responseCounter = 0; goAround = true; + _clearedToLand = false; SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " told to go-around!!"); break; default: @@ -597,8 +755,6 @@ void FGAILocalTraffic::FlyTrafficPattern(double dt) { // Need to differentiate between in-air (IAS governed) and on-ground (vel governed) // Take-off is an interesting case - we are on the ground but takeoff speed is IAS governed. - static bool transmitted = false; // FIXME - this is a hack - // WIND // Wind has two effects - a mechanical one in that IAS translates to a different vel, and the hdg != track, // but also a piloting effect, in that the AI must be able to descend at a different rate in order to hit the threshold. @@ -609,9 +765,9 @@ void FGAILocalTraffic::FlyTrafficPattern(double dt) { double turn_time = 60.0; // seconds - TODO - check this guess double turn_circumference; double turn_radius; - Point3D orthopos = ortho.ConvertToLocal(pos); // ortho position of the plane + Point3D orthopos = ortho.ConvertToLocal(_pos); // ortho position of the plane //cout << "runway elev = " << rwy.threshold_pos.elev() << ' ' << rwy.threshold_pos.elev() * SG_METER_TO_FEET << '\n'; - //cout << "elev = " << pos.elev() << ' ' << pos.elev() * SG_METER_TO_FEET << '\n'; + //cout << "elev = " << _pos.elev() << ' ' << _pos.elev() * SG_METER_TO_FEET << '\n'; // HACK FOR TESTING - REMOVE //cout << "Calling ExitRunway..." << endl; @@ -633,13 +789,13 @@ void FGAILocalTraffic::FlyTrafficPattern(double dt) { double dveldt = 5.0; vel += dveldt * dt; } - if(aip.getSGLocation()->get_cur_elev_m() > -9990.0) { - pos.setelev(aip.getSGLocation()->get_cur_elev_m() + wheelOffset); + if(_aip.getSGLocation()->get_cur_elev_m() > -9990.0) { + _pos.setelev(_aip.getSGLocation()->get_cur_elev_m() + wheelOffset); } - IAS = vel + (cos((hdg - wind_from) * DCL_DEGREES_TO_RADIANS) * wind_speed); + IAS = vel + (cos((_hdg - wind_from) * DCL_DEGREES_TO_RADIANS) * wind_speed); if(IAS >= 70) { leg = CLIMBOUT; - pitch = 10.0; + _pitch = 10.0; IAS = best_rate_of_climb_speed; //slope = 7.0; slope = 6.0; // Reduced it slightly since it's climbing a lot steeper than I can in the JSBSim C172. @@ -652,24 +808,24 @@ void FGAILocalTraffic::FlyTrafficPattern(double dt) { // (decided in FGTower and accessed through GetCrosswindConstraint(...)). // According to AIM, traffic should climb to within 300ft of pattern altitude before commencing crosswind turn. // TODO - At hot 'n high airports this may be 500ft AGL though - need to make this a variable. - if((pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET > 700) { + if((_pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET > 700) { double cc = 0.0; if(tower->GetCrosswindConstraint(cc)) { if(orthopos.y() > cc) { - cout << "Turning to crosswind, distance from threshold = " << orthopos.y() << '\n'; + //cout << "Turning to crosswind, distance from threshold = " << orthopos.y() << '\n'; leg = TURN1; } } else if(orthopos.y() > 1500.0) { // Added this constraint as a hack to prevent turning too early when going around. // TODO - We should be doing it as a distance from takeoff end, not theshold end though. - cout << "Turning to crosswind, distance from threshold = " << orthopos.y() << '\n'; + //cout << "Turning to crosswind, distance from threshold = " << orthopos.y() << '\n'; leg = TURN1; } } // Need to check for levelling off in case we can't turn crosswind as soon // as we would like due to other traffic. - if((pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET > 1000) { + if((_pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET > 1000) { slope = 0.0; - pitch = 0.0; + _pitch = 0.0; IAS = 80.0; // FIXME - use smooth transistion to new speed and attitude. } if(goAround && !goAroundCalled) { @@ -692,21 +848,21 @@ void FGAILocalTraffic::FlyTrafficPattern(double dt) { goAround = false; LevelWings(); track = rwy.hdg + (90.0 * patternDirection); - if((pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET > 1000) { + if((_pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET > 1000) { slope = 0.0; - pitch = 0.0; + _pitch = 0.0; IAS = 80.0; // FIXME - use smooth transistion to new speed } // turn 1000m out for now, taking other traffic into accout - if(fabs(orthopos.x()) > 980) { + if(fabs(orthopos.x()) > 900) { double dd = 0.0; if(tower->GetDownwindConstraint(dd)) { if(fabs(orthopos.x()) > fabs(dd)) { - cout << "Turning to downwind, distance from centerline = " << fabs(orthopos.x()) << '\n'; + //cout << "Turning to downwind, distance from centerline = " << fabs(orthopos.x()) << '\n'; leg = TURN2; } } else { - cout << "Turning to downwind, distance from centerline = " << fabs(orthopos.x()) << '\n'; + //cout << "Turning to downwind, distance from centerline = " << fabs(orthopos.x()) << '\n'; leg = TURN2; } } @@ -715,9 +871,9 @@ void FGAILocalTraffic::FlyTrafficPattern(double dt) { track += (360.0 / turn_time) * dt * patternDirection; Bank(25.0 * patternDirection); // just in case we didn't make height on crosswind - if((pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET > 1000) { + if((_pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET > 1000) { slope = 0.0; - pitch = 0.0; + _pitch = 0.0; IAS = 80.0; // FIXME - use smooth transistion to new speed } if((track < (rwy.hdg - 179.0)) || (track > (rwy.hdg + 179.0))) { @@ -730,9 +886,14 @@ void FGAILocalTraffic::FlyTrafficPattern(double dt) { LevelWings(); track = rwy.hdg - (180 * patternDirection); //should tend to bring track back into the 0->360 range // just in case we didn't make height on crosswind - if((pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET > 1000) { + if(((_pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET > 995) && ((_pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET < 1015)) { slope = 0.0; - pitch = 0.0; + _pitch = 0.0; + IAS = 90.0; // FIXME - use smooth transistion to new speed + } + if((_pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET >= 1015) { + slope = -1.0; + _pitch = -1.0; IAS = 90.0; // FIXME - use smooth transistion to new speed } if((orthopos.y() < 0) && (!transmitted)) { @@ -740,6 +901,7 @@ void FGAILocalTraffic::FlyTrafficPattern(double dt) { transmitted = true; } if((orthopos.y() < -100) && (!descending)) { + //cout << "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDdddd\n"; // Maybe we should think about when to start descending. // For now we're assuming that we aim to follow the same glidepath regardless of wind. double d1; @@ -752,7 +914,7 @@ void FGAILocalTraffic::FlyTrafficPattern(double dt) { } if(descending) { slope = -5.5; // FIXME - calculate to descent at 500fpm and hit the desired point on the runway (taking wind into account as well!!) - pitch = -3.0; + _pitch = -3.0; IAS = 85.0; } @@ -766,13 +928,13 @@ void FGAILocalTraffic::FlyTrafficPattern(double dt) { double bb = 0.0; if(tower->GetBaseConstraint(bb)) { if(fabs(orthopos.y()) > fabs(bb)) { - cout << "Turning to base, distance from threshold = " << fabs(orthopos.y()) << '\n'; + //cout << "Turning to base, distance from threshold = " << fabs(orthopos.y()) << '\n'; leg = TURN3; transmitted = false; IAS = 80.0; } } else { - cout << "Turning to base, distance from threshold = " << fabs(orthopos.y()) << '\n'; + //cout << "Turning to base, distance from threshold = " << fabs(orthopos.y()) << '\n'; leg = TURN3; transmitted = false; IAS = 80.0; @@ -789,7 +951,8 @@ void FGAILocalTraffic::FlyTrafficPattern(double dt) { case BASE: LevelWings(); if(!transmitted) { - TransmitPatternPositionReport(); + // Base report should only be transmitted at uncontrolled airport - not towered. + if(!_controlled) TransmitPatternPositionReport(); transmitted = true; } @@ -805,7 +968,7 @@ void FGAILocalTraffic::FlyTrafficPattern(double dt) { } if(descending) { slope = -5.5; // FIXME - calculate to descent at 500fpm and hit the threshold (taking wind into account as well!!) - pitch = -4.0; + _pitch = -4.0; IAS = 70.0; } @@ -833,16 +996,17 @@ void FGAILocalTraffic::FlyTrafficPattern(double dt) { case FINAL: if(goAround && responseCounter > 2.0) { leg = CLIMBOUT; - pitch = 8.0; + _pitch = 8.0; IAS = best_rate_of_climb_speed; slope = 5.0; // A bit less steep than the initial climbout. inAir = true; goAroundCalled = false; + descending = false; break; } LevelWings(); if(!transmitted) { - TransmitPatternPositionReport(); + if((!_controlled) || (!_clearedToLand)) TransmitPatternPositionReport(); transmitted = true; } if(!descending) { @@ -855,33 +1019,70 @@ void FGAILocalTraffic::FlyTrafficPattern(double dt) { } if(descending) { - slope = -5.5; // FIXME - calculate to descent at 500fpm and hit the threshold (taking wind into account as well!!) - pitch = -4.0; - IAS = 70.0; + if(orthopos.y() < -50.0) { + double thesh_offset = 30.0; + slope = atan((_pos.elev() - dclGetAirportElev(airportID)) / (orthopos.y() - thesh_offset)) * DCL_RADIANS_TO_DEGREES; + //cout << "slope = " << slope << ", elev = " << _pos.elev() << ", apt_elev = " << dclGetAirportElev(airportID) << ", op.y = " << orthopos.y() << '\n'; + if(slope < -10.0) slope = -10.0; + _savedSlope = slope; + _pitch = -4.0; + IAS = 70.0; + } else { + if(_pos.elev() < (rwy.threshold_pos.elev()+10.0+wheelOffset)) { + if(_aip.getSGLocation()->get_cur_elev_m() > -9990.0) { + if(_pos.elev() < (_aip.getSGLocation()->get_cur_elev_m() + wheelOffset + 1.0)) { + slope = -2.0; + _pitch = 1.0; + IAS = 55.0; + } else if(_pos.elev() < (_aip.getSGLocation()->get_cur_elev_m() + wheelOffset + 5.0)) { + slope = -4.0; + _pitch = -2.0; + IAS = 60.0; + } else { + slope = _savedSlope; + _pitch = -3.0; + IAS = 65.0; + } + } else { + // Elev not determined + slope = _savedSlope; + _pitch = -3.0; + IAS = 65.0; + } + } else { + slope = _savedSlope; + _pitch = -3.0; + IAS = 65.0; + } + } } // Try and track the extended centreline track = rwy.hdg - (0.2 * orthopos.x()); //cout << "orthopos.x() = " << orthopos.x() << " hdg = " << hdg << '\n'; - if(pos.elev() < (rwy.threshold_pos.elev()+20.0+wheelOffset)) { + if(_pos.elev() < (rwy.threshold_pos.elev()+20.0+wheelOffset)) { DoGroundElev(); // Need to call it here expicitly on final since it's only called // for us in update(...) when the inAir flag is false. } - if(pos.elev() < (rwy.threshold_pos.elev()+10.0+wheelOffset)) { - if(aip.getSGLocation()->get_cur_elev_m() > -9990.0) { - if((aip.getSGLocation()->get_cur_elev_m() + wheelOffset) > pos.elev()) { + if(_pos.elev() < (rwy.threshold_pos.elev()+10.0+wheelOffset)) { + //slope = -1.0; + //_pitch = 1.0; + if(_aip.getSGLocation()->get_cur_elev_m() > -9990.0) { + if((_aip.getSGLocation()->get_cur_elev_m() + wheelOffset) > _pos.elev()) { slope = 0.0; - pitch = 0.0; + _pitch = 0.0; leg = LANDING_ROLL; inAir = false; } } // else need a fallback position based on arpt elev in case ground elev determination fails? - } + } else { + // TODO + } break; case LANDING_ROLL: //inAir = false; descending = false; - if(aip.getSGLocation()->get_cur_elev_m() > -9990.0) { - pos.setelev(aip.getSGLocation()->get_cur_elev_m() + wheelOffset); + if(_aip.getSGLocation()->get_cur_elev_m() > -9990.0) { + _pos.setelev(_aip.getSGLocation()->get_cur_elev_m() + wheelOffset); } track = rwy.hdg; dveldt = -5.0; @@ -912,7 +1113,7 @@ void FGAILocalTraffic::FlyTrafficPattern(double dt) { // Does it really matter? // Apply wind to ground-relative velocity if in the air - vel = IAS - (cos((hdg - wind_from) * DCL_DEGREES_TO_RADIANS) * wind_speed); + vel = IAS - (cos((_hdg - wind_from) * DCL_DEGREES_TO_RADIANS) * wind_speed); //crab = f(track, wind, vel); // The vector we need to fly is our desired vector minus the wind vector // TODO - we probably ought to use plib's built in vector types and operations for this @@ -946,9 +1147,11 @@ void FGAILocalTraffic::FlyTrafficPattern(double dt) { crab = 0.0; } - hdg = track + crab; + //cout << "X " << orthopos.x() << " Y " << orthopos.y() << " SLOPE " << slope << " elev " << _pos.elev() * SG_METER_TO_FEET << '\n'; + + _hdg = track + crab; dist = vel * 0.514444 * dt; - pos = dclUpdatePosition(pos, track, slope, dist); + _pos = dclUpdatePosition(_pos, track, slope, dist); } // Pattern direction is true for right, false for left @@ -1025,11 +1228,13 @@ void FGAILocalTraffic::TransmitPatternPositionReport(void) { } trns += ConvertRwyNumToSpokenString(rwy.rwyID); + trns += " "; + // And add the airport name again trns += tower->get_name(); - pending_transmission = trns; // FIXME - make up pending_transmission natively - ConditionalTransmit(90.0, code); // Assume a report of this leg will be invalid if we can't transmit within a minute and a half. + pending_transmission = trns; + ConditionalTransmit(60.0, code); // Assume a report of this leg will be invalid if we can't transmit within a minute. } // Callback handler @@ -1037,6 +1242,7 @@ void FGAILocalTraffic::TransmitPatternPositionReport(void) { void FGAILocalTraffic::ProcessCallback(int code) { // 1 - Request Departure from ground // 2 - Report at hold short + // 3 - Report runway vacated // 10 - report crosswind // 11 - report downwind // 12 - report base @@ -1045,6 +1251,8 @@ void FGAILocalTraffic::ProcessCallback(int code) { ground->RequestDeparture(plane, this); } else if(code == 2) { tower->ContactAtHoldShort(plane, this, CIRCUIT); + } else if(code == 3) { + tower->ReportRunwayVacated(plane.callsign); } else if(code == 11) { tower->ReportDownwind(plane.callsign); } else if(code == 13) { @@ -1055,6 +1263,9 @@ void FGAILocalTraffic::ProcessCallback(int code) { void FGAILocalTraffic::ExitRunway(Point3D orthopos) { //cout << "In ExitRunway" << endl; //cout << "Runway ID is " << rwy.ID << endl; + + _clearedToLand = false; + node_array_type exitNodes = ground->GetExits(rwy.rwyID); //I suppose we ought to have some fallback for rwy with no defined exits? /* cout << "Node ID's of exits are "; @@ -1072,7 +1283,7 @@ void FGAILocalTraffic::ExitRunway(Point3D orthopos) { node* rwyExit = *(exitNodes.begin()); //int gateID; //This might want to be more persistant at some point while(nItr != exitNodes.end()) { - d = ortho.ConvertToLocal((*nItr)->pos).y() - ortho.ConvertToLocal(pos).y(); //FIXME - consider making orthopos a class variable + d = ortho.ConvertToLocal((*nItr)->pos).y() - ortho.ConvertToLocal(_pos).y(); //FIXME - consider making orthopos a class variable if(d > 0.0) { if(d < dist) { dist = d; @@ -1091,7 +1302,8 @@ void FGAILocalTraffic::ExitRunway(Point3D orthopos) { // Implies no available gates - what shall we do? // For now just vanish the plane - possibly we can make this more elegant in the future SG_LOG(SG_ATC, SG_ALERT, "No gate found by FGAILocalTraffic whilst landing at " << airportID << '\n'); - aip.setVisible(false); + //_aip.setVisible(false); + //cout << "Setting visible false\n"; operatingState = PARKED; return; } @@ -1113,9 +1325,16 @@ void FGAILocalTraffic::ExitRunway(Point3D orthopos) { StartTaxi(); } else { // Something must have gone wrong with the ground network file - or there is only a rwy here and no exits defined - SG_LOG(SG_ATC, SG_ALERT, "No exits found by FGAILocalTraffic from runway " << rwy.rwyID << " at " << airportID << '\n'); + SG_LOG(SG_ATC, SG_INFO, "No exits found by FGAILocalTraffic from runway " << rwy.rwyID << " at " << airportID << '\n'); + //cout << "No exits found by " << plane.callsign << " from runway " << rwy.rwyID << " at " << airportID << '\n'; // What shall we do - just remove the plane from sight? - aip.setVisible(false); + _aip.setVisible(false); + //cout << "Setting visible false\n"; + //tower->ReportRunwayVacated(plane.callsign); + string trns = "Clear of the runway "; + trns += plane.callsign; + pending_transmission = trns; + Transmit(3); operatingState = PARKED; } } @@ -1159,6 +1378,7 @@ void FGAILocalTraffic::GetNextTaxiNode() { void FGAILocalTraffic::StartTaxi() { //cout << "StartTaxi called" << endl; operatingState = TAXIING; + taxiPathPos = 0; //Set the desired heading @@ -1166,7 +1386,7 @@ void FGAILocalTraffic::StartTaxi() { //Eventually we may need to consider the fact that we might start on a curved arc and //not be able to head directly for the first node. GetNextTaxiNode(); // sets the class variable nextTaxiNode to the next taxi node! - desiredTaxiHeading = GetHeadingFromTo(pos, nextTaxiNode->pos); + desiredTaxiHeading = GetHeadingFromTo(_pos, nextTaxiNode->pos); //cout << "First taxi heading is " << desiredTaxiHeading << endl; } @@ -1211,7 +1431,7 @@ void FGAILocalTraffic::Taxi(double dt) { // Look out for the finish!! //Point3D orthopos = ortho.ConvertToLocal(pos); // ortho position of the plane - desiredTaxiHeading = GetHeadingFromTo(pos, nextTaxiNode->pos); + desiredTaxiHeading = GetHeadingFromTo(_pos, nextTaxiNode->pos); bool lastNode = (taxiPathPos == path.size() ? true : false); if(lastNode) { @@ -1221,7 +1441,7 @@ void FGAILocalTraffic::Taxi(double dt) { // HACK ALERT! - for now we will taxi at constant speed for straights and turns // Remember that hdg is always equal to track when taxiing so we don't have to consider them both - double dist_to_go = dclGetHorizontalSeparation(pos, nextTaxiNode->pos); // we may be able to do this more cheaply using orthopos + double dist_to_go = dclGetHorizontalSeparation(_pos, nextTaxiNode->pos); // we may be able to do this more cheaply using orthopos //cout << "dist_to_go = " << dist_to_go << endl; if((nextTaxiNode->type == GATE) && (dist_to_go <= 0.1)) { // This might be more robust to outward paths starting with a gate if we check for either @@ -1233,18 +1453,18 @@ void FGAILocalTraffic::Taxi(double dt) { // ((s.dt)/(PI.r)) x 180 degrees // or alternatively (s.dt)/r radians //cout << "hdg = " << hdg << " desired taxi heading = " << desiredTaxiHeading << '\n'; - hdg = TaxiTurnTowardsHeading(hdg, desiredTaxiHeading, nominalTaxiSpeed, taxiTurnRadius, dt); + _hdg = TaxiTurnTowardsHeading(_hdg, desiredTaxiHeading, nominalTaxiSpeed, taxiTurnRadius, dt); double vel = nominalTaxiSpeed; //cout << "vel = " << vel << endl; double dist = vel * 0.514444 * dt; //cout << "dist = " << dist << endl; - double track = hdg; + double track = _hdg; //cout << "track = " << track << endl; double slope = 0.0; - pos = dclUpdatePosition(pos, track, slope, dist); + _pos = dclUpdatePosition(_pos, track, slope, dist); //cout << "Updated position...\n"; - if(aip.getSGLocation()->get_cur_elev_m() > -9990) { - pos.setelev(aip.getSGLocation()->get_cur_elev_m() + wheelOffset); + if(_aip.getSGLocation()->get_cur_elev_m() > -9990) { + _pos.setelev(_aip.getSGLocation()->get_cur_elev_m() + wheelOffset); } // else don't change the elev until we get a valid ground elev again! } else if(lastNode) { if(taxiState == TD_LINING_UP) { @@ -1252,20 +1472,20 @@ void FGAILocalTraffic::Taxi(double dt) { liningUp = true; } if(liningUp) { - hdg = TaxiTurnTowardsHeading(hdg, rwy.hdg, nominalTaxiSpeed, taxiTurnRadius, dt); + _hdg = TaxiTurnTowardsHeading(_hdg, rwy.hdg, nominalTaxiSpeed, taxiTurnRadius, dt); double vel = nominalTaxiSpeed; //cout << "vel = " << vel << endl; double dist = vel * 0.514444 * dt; //cout << "dist = " << dist << endl; - double track = hdg; + double track = _hdg; //cout << "track = " << track << endl; double slope = 0.0; - pos = dclUpdatePosition(pos, track, slope, dist); + _pos = dclUpdatePosition(_pos, track, slope, dist); //cout << "Updated position...\n"; - if(aip.getSGLocation()->get_cur_elev_m() > -9990) { - pos.setelev(aip.getSGLocation()->get_cur_elev_m() + wheelOffset); + if(_aip.getSGLocation()->get_cur_elev_m() > -9990) { + _pos.setelev(_aip.getSGLocation()->get_cur_elev_m() + wheelOffset); } // else don't change the elev until we get a valid ground elev again! - if(fabs(hdg - rwy.hdg) <= 1.0) { + if(fabs(_hdg - rwy.hdg) <= 1.0) { operatingState = IN_PATTERN; leg = TAKEOFF_ROLL; inAir = false; @@ -1301,9 +1521,9 @@ void FGAILocalTraffic::DoGroundElev() { double visibility_meters = fgGetDouble("/environment/visibility-m"); //globals->get_tile_mgr()->prep_ssg_nodes( acmodel_location, - globals->get_tile_mgr()->prep_ssg_nodes( aip.getSGLocation(), visibility_meters ); + globals->get_tile_mgr()->prep_ssg_nodes( _aip.getSGLocation(), visibility_meters ); Point3D scenery_center = globals->get_scenery()->get_center(); - globals->get_tile_mgr()->update( aip.getSGLocation(), visibility_meters, (aip.getSGLocation())->get_absolute_view_pos( scenery_center ) ); + globals->get_tile_mgr()->update( _aip.getSGLocation(), visibility_meters, (_aip.getSGLocation())->get_absolute_view_pos( scenery_center ) ); // save results of update in SGLocation for fdm... //if ( globals->get_scenery()->get_cur_elev() > -9990 ) { @@ -1312,10 +1532,10 @@ void FGAILocalTraffic::DoGroundElev() { //} // The need for this here means that at least 2 consecutive passes are needed :-( - aip.getSGLocation()->set_tile_center( globals->get_scenery()->get_next_center() ); + _aip.getSGLocation()->set_tile_center( globals->get_scenery()->get_next_center() ); //cout << "Transform Elev is " << globals->get_scenery()->get_cur_elev() << '\n'; - aip.getSGLocation()->set_cur_elev_m(globals->get_scenery()->get_cur_elev()); + _aip.getSGLocation()->set_cur_elev_m(globals->get_scenery()->get_cur_elev()); //return(globals->get_scenery()->get_cur_elev()); } diff --git a/src/ATC/AILocalTraffic.hxx b/src/ATC/AILocalTraffic.hxx index d6f21c735..690ebd2e7 100644 --- a/src/ATC/AILocalTraffic.hxx +++ b/src/ATC/AILocalTraffic.hxx @@ -45,7 +45,8 @@ enum TaxiState { enum OperatingState { IN_PATTERN, TAXIING, - PARKED + PARKED, + EN_ROUTE }; struct StartOfDescent { @@ -58,11 +59,12 @@ class FGAILocalTraffic : public FGAIPlane { public: + // At the moment we expect the expanded short form callsign - eventually we will just want the reg + type. FGAILocalTraffic(); ~FGAILocalTraffic(); // Initialise - bool Init(string ICAO, OperatingState initialState = PARKED, PatternLeg initialLeg = DOWNWIND); + bool Init(const string& callsign, string ICAO, OperatingState initialState = PARKED, PatternLeg initialLeg = DOWNWIND); // Run the internal calculations void Update(double dt); @@ -94,33 +96,48 @@ protected: // Attempt to enter the traffic pattern in a reasonably intelligent manner void EnterTrafficPattern(double dt); - // Do what is necessary to land and parkup at home airport - void ReturnToBase(double dt); + // Set up the internal state to be consistent for a downwind entry. + void DownwindEntry(); -private: - FGATCMgr* ATC; - // This is purely for synactic convienience to avoid writing globals->get_ATC_mgr()-> all through the code! - - // High-level stuff - OperatingState operatingState; - int circuitsToFly; //Number of circuits still to do in this session NOT INCLUDING THE CURRENT ONE - bool touchAndGo; //True if circuits should be flown touch and go, false for full stop + // Ditto for straight-in + void StraightInEntry(bool des = false); - // Its possible that this might be moved out to the ground/airport class at some point. - FGATCAlignedProjection ortho; // Orthogonal mapping of the local area with the threshold at the origin - // and the runway aligned with the y axis. + // Do what is necessary to land and parkup at home airport + void ReturnToBase(double dt); // Airport/runway/pattern details string airportID; // The ICAO code of the airport that we're operating around double aptElev; // Airport elevation FGGround* ground; // A pointer to the ground control. FGTower* tower; // A pointer to the tower control. + bool _controlled; // Set true if we find tower control working for the airport, false otherwise. RunwayDetails rwy; double patternDirection; // 1 for right, -1 for left (This is double because we multiply/divide turn rates // with it to get RH/LH turns - DON'T convert it to int under ANY circumstances!! double glideAngle; // Assumed to be visual glidepath angle for FGAILocalTraffic - can be found at www.airnav.com // Its conceivable that patternDirection and glidePath could be moved into the RunwayDetails structure. + // Its possible that this might be moved out to the ground/airport class at some point. + FGATCAlignedProjection ortho; // Orthogonal mapping of the local area with the threshold at the origin + // and the runway aligned with the y axis. + + void GetAirportDetails(string id); + + void GetRwyDetails(string id); + + double responseCounter; // timer in seconds to allow response to requests to be a little while after them + // Will almost certainly get moved to FGAIPlane. + +private: + FGATCMgr* ATC; + // This is purely for synactic convienience to avoid writing globals->get_ATC_mgr()-> all through the code! + + // High-level stuff + OperatingState operatingState; + int circuitsToFly; //Number of circuits still to do in this session NOT INCLUDING THE CURRENT ONE + bool touchAndGo; //True if circuits should be flown touch and go, false for full stop + bool transmitted; // Set true when a position report for the current leg has been transmitted. + // Performance characteristics of the plane in knots and ft/min - some of this might get moved out into FGAIPlane double Vr; double best_rate_of_climb_speed; @@ -175,14 +192,19 @@ private: bool reportReadyForDeparture; // set true when ATC has requested that the plane report when ready for departure bool clearedToLineUp; bool clearedToTakeOff; + bool _clearedToLand; // also implies cleared for the option. bool liningUp; // Set true when the turn onto the runway heading is commenced when taxiing out bool goAround; // Set true if need to go-around bool goAroundCalled; // Set true during go-around only after we have called our go-around on the radio bool contactTower; // we have been told to contact tower bool contactGround; // we have been told to contact ground bool changeFreq; // true when we need to change frequency + bool _taxiToGA; // Temporary mega-hack indicating we are to taxi to the GA parking and disconnect from tower control. atc_type changeFreqType; // the service we need to change to - double responseCounter; // timer in seconds to allow response to requests to be a little while after them + bool freeTaxi; // False if the airport has a facilities file with a logical taxi network defined, true if we need to calculate our own taxiing points. + + // Hack for getting close to the runway when atan can go pear-shaped + double _savedSlope; void FlyTrafficPattern(double dt); @@ -201,8 +223,6 @@ private: void GetNextTaxiNode(); void DoGroundElev(); - - void GetRwyDetails(); }; #endif // _FG_AILocalTraffic_HXX diff --git a/src/ATC/AIMgr.cxx b/src/ATC/AIMgr.cxx index d44a84511..7c9e60350 100644 --- a/src/ATC/AIMgr.cxx +++ b/src/ATC/AIMgr.cxx @@ -23,6 +23,7 @@ #include
#include
+#include #include @@ -33,9 +34,18 @@ # include // for directory reading #endif +#ifdef FG_WEATHERCM +# include +#else +# include +# include +#endif + #include "AIMgr.hxx" #include "AILocalTraffic.hxx" +#include "AIGAVFRTraffic.hxx" #include "ATCutils.hxx" +#include "commlist.hxx" SG_USING_STD(list); SG_USING_STD(cout); @@ -43,12 +53,18 @@ SG_USING_STD(cout); FGAIMgr::FGAIMgr() { ATC = globals->get_ATC_mgr(); initDone = false; + ai_callsigns_used["CFGFS"] = 1; // so we don't inadvertently use this + // TODO - use the proper user callsign when it becomes user settable. + removalList.clear(); + activated.clear(); } FGAIMgr::~FGAIMgr() { } void FGAIMgr::init() { + //cout << "AIMgr::init called..." << endl; + // Pointers to user's position lon_node = fgGetNode("/position/longitude-deg", true); lat_node = fgGetNode("/position/latitude-deg", true); @@ -58,12 +74,26 @@ void FGAIMgr::init() { lat = lat_node->getDoubleValue(); elev = elev_node->getDoubleValue(); + // Load up models at the start to avoid pausing later + // Hack alert - Hardwired paths!! + string planepath = "Aircraft/c172/Models/c172-dpm.ac"; + _defaultModel = sgLoad3DModel( globals->get_fg_root(), + planepath.c_str(), + globals->get_props(), + globals->get_sim_time_sec() ); + + planepath = "Aircraft/pa28-161/Models/pa28-161.ac"; + _piperModel = sgLoad3DModel( globals->get_fg_root(), + planepath.c_str(), + globals->get_props(), + globals->get_sim_time_sec() ); + // go through the $FG_ROOT/ATC directory and find all *.taxi files SGPath path(globals->get_fg_root()); path.append("ATC/"); string dir = path.dir(); - string ext; - string file, f_ident; + string ext; + string file, f_ident; int pos; // WARNING - I (DCL) haven't tested this on MSVC - this is simply cribbed from TerraGear @@ -88,12 +118,12 @@ void FGAIMgr::init() { if(dclFindAirportID(f_ident, &a)) { SGBucket sgb(a.longitude, a.latitude); int idx = sgb.gen_index(); - if(airports.find(idx) != airports.end()) { - airports[idx]->push_back(f_ident); + if(facilities.find(idx) != facilities.end()) { + facilities[idx]->push_back(f_ident); } else { aptID_list_type* apts = new aptID_list_type; apts->push_back(f_ident); - airports[idx] = apts; + facilities[idx] = apts; } SG_LOG(SG_ATC, SG_BULK, "Mapping " << f_ident << " to bucket " << idx); } @@ -119,12 +149,12 @@ void FGAIMgr::init() { if(dclFindAirportID(f_ident, &a)) { SGBucket sgb(a.longitude, a.latitude); int idx = sgb.gen_index(); - if(airports.find(idx) != airports.end()) { - airports[idx]->push_back(f_ident); + if(facilities.find(idx) != facilities.end()) { + facilities[idx]->push_back(f_ident); } else { - aptID_list_type* apts = new aptID_list_type; + ID_list_type* apts = new ID_list_type; apts->push_back(f_ident); - airports[idx] = apts; + facilities[idx] = apts; } SG_LOG(SG_ATC, SG_BULK, "Mapping " << f_ident << " to bucket " << idx); } @@ -135,9 +165,27 @@ void FGAIMgr::init() { #endif // See if are in range at startup and activate if necessary - SearchByPos(10.0); + SearchByPos(15.0); initDone = true; + + //cout << "AIMgr::init done..." << endl; + + /* + // TESTING + FGATCAlignedProjection ortho; + ortho.Init(dclGetAirportPos("KEMT"), 205.0); // Guess of rwy19 heading + //Point3D ip = ortho.ConvertFromLocal(Point3D(6000, 1000, 1000)); // 90 deg entry + //Point3D ip = ortho.ConvertFromLocal(Point3D(-7000, 3000, 1000)); // 45 deg entry + Point3D ip = ortho.ConvertFromLocal(Point3D(1000, -7000, 1000)); // straight-in + ATC->AIRegisterAirport("KEMT"); + FGAIGAVFRTraffic* p = new FGAIGAVFRTraffic(); + p->SetModel(_defaultModel); + p->Init(ip, "KEMT", GenerateShortForm(GenerateUniqueCallsign())); + ai_list.push_back(p); + traffic[ident].push_back(p); + activated["KEMT"] = 1; + */ } void FGAIMgr::bind() { @@ -152,6 +200,11 @@ void FGAIMgr::update(double dt) { SG_LOG(SG_ATC, SG_WARN, "Warning - AIMgr::update(...) called before AIMgr::init()"); } + //cout << activated.size() << '\n'; + + Point3D userPos = Point3D(lon_node->getDoubleValue(), lat_node->getDoubleValue(), elev_node->getDoubleValue()); + + // TODO - make these class variables!! static int i = 0; static int j = 0; @@ -163,25 +216,106 @@ void FGAIMgr::update(double dt) { } if(j == 215) { - SearchByPos(15.0); + SearchByPos(25.0); j = 0; + } else if(j == 200) { + // Go through the list of activated airports and remove those out of range + //cout << "The following airports have been activated by the AI system:\n"; + ai_activated_map_iterator apt_itr = activated.begin(); + while(apt_itr != activated.end()) { + //cout << "FIRST IS " << (*apt_itr).first << '\n'; + if(dclGetHorizontalSeparation(userPos, dclGetAirportPos((*apt_itr).first)) > (35.0 * 1600.0)) { + // Then get rid of it and make sure the iterator is left pointing to the next one! + string s = (*apt_itr).first; + if(traffic.find(s) != traffic.end()) { + //cout << "s = " << s << ", traffic[s].size() = " << traffic[s].size() << '\n'; + if(traffic[s].size()) { + apt_itr++; + } else { + //cout << "Erasing " << (*apt_itr).first << " and traffic" << '\n'; + activated.erase(apt_itr++); + traffic.erase(s); + } + } else { + //cout << "Erasing " << (*apt_itr).first << ' ' << (*apt_itr).second << '\n'; + activated.erase(apt_itr++); + } + } else { + apt_itr++; + } + } + } else if(j == 180) { + // Go through the list of activated airports and do the random airplane generation + ai_traffic_map_iterator it = traffic.begin(); + while(it != traffic.end()) { + string s = (*it).first; + //cout << "s = " << s << " size = " << (*it).second.size() << '\n'; + // Only generate extra traffic if within a certain distance of the user, + // TODO - maybe take users's tuned freq into account as well. + double d = dclGetHorizontalSeparation(userPos, dclGetAirportPos(s)); + if(d < (15.0 * 1600.0)) { + double cd = 0.0; + bool gen = false; + //cout << "Size of list is " << (*it).second.size() << " at " << s << '\n'; + if((*it).second.size()) { + FGAIEntity* e = *((*it).second.rbegin()); + cd = dclGetHorizontalSeparation(e->GetPos(), dclGetAirportPos(s)); + if(cd < (d < 5000 ? 10000 : d + 5000)) { + gen = true; + } + } else { + gen = true; + cd = 0.0; + } + if(gen) { + //cout << "Generating extra traffic at airport " << s << ", at least " << cd << " meters out\n"; + //GenerateSimpleAirportTraffic(s, cd); + GenerateSimpleAirportTraffic(s, cd + 2000.0); // The random seems a bit wierd - traffic could get far too bunched without the +2000. + } + } + ++it; + } } ++j; + //cout << "Size of AI list is " << ai_list.size() << '\n'; + // TODO - need to add a check of if any activated airports have gone out of range + string rs; // plane to be removed, if one. + if(removalList.size()) { + rs = *(removalList.begin()); + removalList.pop_front(); + } else { + rs = ""; + } + // Traverse the list of active planes and run all their update methods // TODO - spread the load - not all planes should need updating every frame. // Note that this will require dt to be calculated for each plane though // since they rely on it to calculate distance travelled. ai_list_itr = ai_list.begin(); while(ai_list_itr != ai_list.end()) { - (*ai_list_itr)->Update(dt); - ++ai_list_itr; + FGAIEntity *e = *ai_list_itr; + if(rs.size() && e->GetCallsign() == rs) { + //cout << "Removing " << rs << " from ai_list\n"; + ai_list_itr = ai_list.erase(ai_list_itr); + delete e; + // This is a hack - we should deref this plane from the airport count! + } else { + e->Update(dt); + ++ai_list_itr; + } } + + //cout << "Size of AI list is " << ai_list.size() << '\n'; } +void FGAIMgr::ScheduleRemoval(string s) { + //cout << "Scheduling removal of plane " << s << " from AIMgr\n"; + removalList.push_back(s); +} // Activate AI traffic at an airport void FGAIMgr::ActivateAirport(string ident) { @@ -189,21 +323,197 @@ void FGAIMgr::ActivateAirport(string ident) { // TODO - need to start the traffic more randomly FGAILocalTraffic* local_traffic = new FGAILocalTraffic; //local_traffic->Init(ident, IN_PATTERN, TAKEOFF_ROLL); - local_traffic->Init(ident); + local_traffic->Init(GenerateShortForm(GenerateUniqueCallsign()), ident); local_traffic->FlyCircuits(1, true); // Fly 2 circuits with touch & go in between ai_list.push_back(local_traffic); + traffic[ident].push_back(local_traffic); + //cout << "******** ACTIVATING AIRPORT, ident = " << ident << '\n'; + activated[ident] = 1; +} + +// Hack - Generate AI traffic at an airport with no facilities file +void FGAIMgr::GenerateSimpleAirportTraffic(string ident, double min_dist) { + // Ugly hack - don't let VFR Cessnas operate at a hardwired list of major airports + // This will go eventually once airport .xml files specify the traffic profile + if(ident == "KSFO" || ident == "KDFW" || ident == "EGLL" || ident == "KORD" || ident == "KJFK" + || ident == "KMSP" || ident == "KLAX" || ident == "KBOS" || ident == "KEDW" + || ident == "KSEA" || ident == "EHAM") { + return; + } + + /* + // TODO - check for military airports - this should be in the current data. + // UGGH - there's no point at the moment - everything is labelled civil in basic.dat! + FGAirport a; + if(dclFindAirportID(ident, &a)) { + cout << "CODE IS " << a.code << '\n'; + } else { + // UG - can't find the airport! + return; + } + */ + + Point3D aptpos = dclGetAirportPos(ident); // TODO - check for elev of -9999 + //cout << "ident = " << ident << ", elev = " << aptpos.elev() << '\n'; + + // Operate from airports at 3000ft and below only to avoid the default cloud layers and since we don't degrade AI performance with altitude. + if(aptpos.elev() > 3000) { + //cout << "High alt airports not yet supported - returning\n"; + return; + } + + // Rough hack for plane type - make 70% of the planes cessnas, the rest pipers. + bool cessna = true; + + // Get the time and only operate VFR in the (approximate) daytime. + //SGTime *t = globals->get_time_params(); + string time_str = fgGetString("sim/time/gmt-string"); + int loc_time = atoi((time_str.substr(0,3)).c_str()); + //cout << "gmt_time = " << loc_time << '\n'; + loc_time += (int)((aptpos.lon() / 360.0) * 24.0); + while(loc_time < 0) loc_time += 24; + while(loc_time > 24) loc_time -= 24; + //cout << "loc_time = " << loc_time << '\n'; + if(loc_time < 7 || loc_time > 19) return; + + // Check that the visibility is OK for IFR operation. + double visibility; + #ifdef FG_WEATHERCM + //sgVec3 position = { aptpos.lat(), aptpos.lon(), aptpos.elev() }; + //FGPhysicalProperty stationweather = WeatherDatabase->get(position); + #else + FGEnvironment stationweather = + ((FGEnvironmentMgr *)globals->get_subsystem("environment")) + ->getEnvironment(aptpos.lat(), aptpos.lon(), aptpos.elev()); // TODO - check whether this should take ft or m for elev. + #endif + #ifdef FG_WEATHERCM + visibility = fgGetDouble("/environment/visibility-m"); + #else + visibility = stationweather.get_visibility_m(); + #endif + // Technically we can do VFR down to 1 mile (1600m) but that's pretty murky! + //cout << "vis = " << visibility << '\n'; + if(visibility < 3000) return; + + ATC->AIRegisterAirport(ident); + + // Next - get the distance from user to the airport. + Point3D userpos = Point3D(lon_node->getDoubleValue(), lat_node->getDoubleValue(), elev_node->getDoubleValue()); + double d = dclGetHorizontalSeparation(userpos, aptpos); // in meters + + int lev = fgGetInt("/sim/ai-traffic/level"); + if(lev < 1 || lev > 3) lev = 2; + if(visibility < 6000) lev = 1; + //cout << "level = " << lev << '\n'; + + // Next - generate any local / circuit traffic + + /* + // --------------------------- THIS BLOCK IS JUST FOR TESTING - COMMENT OUT BEFORE RELEASE --------------- + // Finally - generate VFR approaching traffic + //if(d > 2000) { + if(ident == "KPOC") { + double ad = 2000.0; + double avd = 3000.0; // average spacing of arriving traffic in meters - relate to airport business and AI density setting one day! + //while(ad < (d < 10000 ? 12000 : d + 2000)) { + for(int i=0; i<8; ++i) { + double dd = sg_random() * avd; + // put a minimum spacing in for now since I don't think tower will cope otherwise! + if(dd < 1500) dd = 1500; + //ad += dd; + ad += dd; + double dir = int(sg_random() * 36); + if(dir == 36) dir--; + dir *= 10; + //dir = 180; + if(sg_random() < 0.3) cessna = false; + else cessna = true; + string s = GenerateShortForm(GenerateUniqueCallsign(), (cessna ? "Cessna-" : "Piper-")); + FGAIGAVFRTraffic* t = new FGAIGAVFRTraffic(); + t->SetModel(cessna ? _defaultModel : _piperModel); + //cout << "Generating VFR traffic " << s << " inbound to " << ident << " " << ad << " meters out from " << dir << " degrees\n"; + Point3D tpos = dclUpdatePosition(aptpos, dir, 6.0, ad); + if(tpos.elev() > (aptpos.elev() + 3000.0)) tpos.setelev(aptpos.elev() + 3000.0); + t->Init(tpos, ident, s); + ai_list.push_back(t); + } + } activated[ident] = 1; -} + return; + //--------------------------------------------------------------------------------------------------- + */ + + double ad; // Minimum distance out of first arriving plane in meters. + double mind; // Minimum spacing of traffic in meters + double avd; // average spacing of arriving traffic in meters - relate to airport business and AI density setting one day! + // Finally - generate VFR approaching traffic + //if(d > 2000) { + if(1) { + if(lev == 3) { + ad = 5000.0; + mind = 2000.0; + avd = 6000.0; + } else if(lev == 2) { + ad = 8000.0; + mind = 4000.0; + avd = 10000.0; + } else { + ad = 9000.0; // Start the first aircraft at least 9K out for now. + mind = 6000.0; + avd = 15000.0; + } + /* + // Check if there is already arriving traffic at this airport + cout << "BING A " << ident << '\n'; + if(traffic.find(ident) != traffic.end()) { + cout << "BING B " << ident << '\n'; + ai_list_type lst = traffic[ident]; + cout << "BING C " << ident << '\n'; + if(lst.size()) { + cout << "BING D " << ident << '\n'; + double cd = dclGetHorizontalSeparation(aptpos, (*lst.rbegin())->GetPos()); + cout << "ident = " << ident << ", cd = " << cd << '\n'; + if(cd > ad) ad = cd; + } + } + */ + if(min_dist != 0) ad = min_dist; + //cout << "ident = " << ident << ", ad = " << ad << '\n'; + while(ad < (d < 5000 ? 15000 : d + 10000)) { + double dd = mind + (sg_random() * (avd - mind)); + ad += dd; + double dir = int(sg_random() * 36); + if(dir == 36) dir--; + dir *= 10; + if(sg_random() < 0.3) cessna = false; + else cessna = true; + string s = GenerateShortForm(GenerateUniqueCallsign(), (cessna ? "Cessna-" : "Piper-")); + FGAIGAVFRTraffic* t = new FGAIGAVFRTraffic(); + t->SetModel(cessna ? _defaultModel : _piperModel); + //cout << "Generating VFR traffic " << s << " inbound to " << ident << " " << ad << " meters out from " << dir << " degrees\n"; + Point3D tpos = dclUpdatePosition(aptpos, dir, 6.0, ad); + if(tpos.elev() > (aptpos.elev() + 3000.0)) tpos.setelev(aptpos.elev() + 3000.0); // FEET yuk :-( + t->Init(tpos, ident, s); + ai_list.push_back(t); + traffic[ident].push_back(t); + } + } +} +/* +// Generate a VFR arrival at airport apt, at least distance d (meters) out. +void FGAIMgr::GenerateVFRArrival(string apt, double d) { +} +*/ // Search for valid airports in the vicinity of the user and activate them if necessary -void FGAIMgr::SearchByPos(double range) -{ +void FGAIMgr::SearchByPos(double range) { //cout << "In SearchByPos(...)" << endl; // get bucket number for plane position lon = lon_node->getDoubleValue(); lat = lat_node->getDoubleValue(); + elev = elev_node->getDoubleValue() * SG_FEET_TO_METER; SGBucket buck(lon, lat); // get neigboring buckets @@ -212,6 +522,7 @@ void FGAIMgr::SearchByPos(double range) int by = (int)( range*SG_NM_TO_METER / buck.get_height_m() / 2 ); //cout << "by = " << by << endl; + // Search for airports with facitities files -------------------------- // loop over bucket range for ( int i=-bx; i<=bx; i++) { //cout << "i loop\n"; @@ -220,10 +531,10 @@ void FGAIMgr::SearchByPos(double range) buck = sgBucketOffset(lon, lat, i, j); long int bucket = buck.gen_index(); //cout << "bucket is " << bucket << endl; - if(airports.find(bucket) != airports.end()) { - aptID_list_type* apts = airports[bucket]; - aptID_list_iterator current = apts->begin(); - aptID_list_iterator last = apts->end(); + if(facilities.find(bucket) != facilities.end()) { + ID_list_type* apts = facilities[bucket]; + ID_list_iterator current = apts->begin(); + ID_list_iterator last = apts->end(); //cout << "Size of apts is " << apts->size() << endl; @@ -239,7 +550,10 @@ void FGAIMgr::SearchByPos(double range) //if(dclFindAirportID(*current, &a)) { // // We can do something here based on distance from the user if we wish. //} + //string s = *current; + //cout << "s = " << s << '\n'; ActivateAirport(*current); + //ActivateSimpleAirport(*current); // TODO - put this back to ActivateAirport when that code is done. //cout << "Activation done" << endl; } else { //cout << *current << " already activated" << endl; @@ -248,4 +562,86 @@ void FGAIMgr::SearchByPos(double range) } } } + //------------------------------------------------------------- + + // Search for any towered airports in the vicinity ------------ + comm_list_type towered; + comm_list_iterator twd_itr; + + int num_twd = current_commlist->FindByPos(lon, lat, elev, range, &towered, TOWER); + if (num_twd != 0) { + double closest = 1000000; + string s = ""; + for(twd_itr = towered.begin(); twd_itr != towered.end(); twd_itr++) { + // Only activate the closest airport not already activated each time. + if(activated.find(twd_itr->ident) == activated.end()) { + double sep = dclGetHorizontalSeparation(Point3D(lon, lat, elev), dclGetAirportPos(twd_itr->ident)); + if(sep < closest) { + closest = sep; + s = twd_itr->ident; + } + + } + } + if(s.size()) { + // TODO - find out why empty strings come through here when all in-range airports done. + GenerateSimpleAirportTraffic(s); + //cout << "**************ACTIVATING SIMPLE AIRPORT, ident = " << s << '\n'; + activated[s] = 1; + } + } +} + +string FGAIMgr::GenerateCallsign() { + // For now we'll just generate US callsigns until we can regionally identify airports. + string s = "N"; + // Add 3 to 5 numbers and make up to 5 with letters. + //sg_srandom_time(); + double d = sg_random(); + int n = int(d * 3); + if(n == 3) --n; + //cout << "First n, n = " << n << '\n'; + int j = 3 + n; + //cout << "j = " << j << '\n'; + for(int i=0; i0; --i) { + char c = callsign[callsign.size() - i]; + //cout << c << '\n'; + string tmp = ""; + tmp += c; + if(isalpha(c)) s += GetPhoneticIdent(c); + else s += ConvertNumToSpokenDigits(tmp); + if(i > 1) s += '-'; + } + return(s); } diff --git a/src/ATC/AIMgr.hxx b/src/ATC/AIMgr.hxx index 2c67eb020..b15d3efdc 100644 --- a/src/ATC/AIMgr.hxx +++ b/src/ATC/AIMgr.hxx @@ -54,19 +54,32 @@ private: // at any point in or at the end of the list. // Hence any new access must explicitly first check for atc_list.end() before dereferencing. - // A list of airport ID's - typedef list < string > aptID_list_type; - typedef aptID_list_type::iterator aptID_list_iterator; + // A list of airport or airplane ID's + typedef list < string > ID_list_type; + typedef ID_list_type::iterator ID_list_iterator; + + // Temporary storage of ID of planes scheduled for removeal + ID_list_type removalList; // A map of airport-IDs that have taxiway network files against bucket number - typedef map < int, aptID_list_type* > ai_apt_map_type; + typedef map < int, ID_list_type* > ai_apt_map_type; typedef ai_apt_map_type::iterator ai_apt_map_iterator; - ai_apt_map_type airports; + ai_apt_map_type facilities; // A map of airport ID's that we've activated AI traffic at typedef map < string, int > ai_activated_map_type; typedef ai_activated_map_type::iterator ai_activated_map_iterator; ai_activated_map_type activated; + + // AI traffic lists mapped by airport + typedef map < string, ai_list_type > ai_traffic_map_type; + typedef ai_traffic_map_type::iterator ai_traffic_map_iterator; + ai_traffic_map_type traffic; + + // A map of callsigns that we have used (eg CFGFS or N0546D - the code will generate Cessna-four-six-delta from this later) + typedef map < string, int > ai_callsigns_map_type; + typedef ai_callsigns_map_type::iterator ai_callsigns_map_iterator; + ai_callsigns_map_type ai_callsigns_used; // Position of the Users Aircraft double lon; @@ -89,8 +102,15 @@ public: void unbind(); void update(double dt); + + // Signal that it is OK to remove a plane of callsign s + // (To be called by the plane itself). + void ScheduleRemoval(string s); private: + + ssgBranch* _defaultModel; // Cessna 172! + ssgBranch* _piperModel; // pa28-161 bool initDone; // Hack - guard against update getting called before init @@ -98,10 +118,19 @@ private: //void RemoveFromList(const char* id, atc_type tp); // Activate AI traffic at an airport - void ActivateAirport(string id); + void ActivateAirport(string ident); + + // Hack - Generate AI traffic at an airport with no facilities file, with the first plane being at least min_dist out. + void GenerateSimpleAirportTraffic(string ident, double min_dist = 0.0); // Search for valid airports in the vicinity of the user and activate them if necessary void SearchByPos(double range); + + string GenerateCallsign(); + + string GenerateUniqueCallsign(); + + string GenerateShortForm(string callsign, string plane_str = "Cessna-", bool local = false); }; diff --git a/src/ATC/AIPlane.cxx b/src/ATC/AIPlane.cxx index 3594e5e70..23ebeda2e 100644 --- a/src/ATC/AIPlane.cxx +++ b/src/ATC/AIPlane.cxx @@ -53,6 +53,7 @@ void FGAIPlane::Update(double dt) { if(_pending) { if(tuned_station) { if(tuned_station->GetFreqClear()) { + //cout << "TUNED STATION FREQ CLEAR\n"; tuned_station->SetFreqInUse(); _pending = false; _transmit = true; @@ -69,6 +70,7 @@ void FGAIPlane::Update(double dt) { } } else { // Not tuned to ATC - Just go ahead and transmit + //cout << "NOT TUNED TO ATC\n"; _pending = false; _transmit = true; _transmitting = false; @@ -115,16 +117,16 @@ void FGAIPlane::Update(double dt) { void FGAIPlane::Bank(double angle) { // This *should* bank us smoothly to any angle - if(fabs(roll - angle) > 0.6) { - roll -= ((roll - angle)/fabs(roll - angle)); + if(fabs(_roll - angle) > 0.6) { + _roll -= ((_roll - angle)/fabs(_roll - angle)); } } // Duplication of Bank(0.0) really - should I cut this? void FGAIPlane::LevelWings(void) { // bring the plane back to level smoothly (this should work to come out of either bank) - if(fabs(roll) > 0.6) { - roll -= (roll/fabs(roll)); + if(fabs(_roll) > 0.6) { + _roll -= (_roll/fabs(_roll)); } } diff --git a/src/ATC/AIPlane.hxx b/src/ATC/AIPlane.hxx index 7f00d89a5..e6da8ded4 100644 --- a/src/ATC/AIPlane.hxx +++ b/src/ATC/AIPlane.hxx @@ -83,6 +83,9 @@ public: // Return what type of landing we're doing on this circuit virtual LandingType GetLandingOption(); + + // Return the callsign + inline string GetCallsign() {return plane.callsign;} protected: PlaneRec plane; diff --git a/src/ATC/ATC.cxx b/src/ATC/ATC.cxx index 7adb85c4f..085d84166 100644 --- a/src/ATC/ATC.cxx +++ b/src/ATC/ATC.cxx @@ -34,8 +34,10 @@ FGATC::FGATC() { receiving = false; respond = false; runResponseCounter = false; + _runReleaseCounter = false; responseID = ""; responseReqd = false; + _type = INVALID; } FGATC::~FGATC() { @@ -53,6 +55,15 @@ void FGATC::Update(double dt) { responseCounter += dt; } } + + if(_runReleaseCounter) { + if(_releaseCounter >= _releaseTime) { + freqClear = true; + _runReleaseCounter = false; + } else { + _releaseCounter += dt; + } + } } void FGATC::ReceiveUserCallback(int code) { @@ -71,12 +82,15 @@ void FGATC::SetResponseReqd(string rid) { } void FGATC::NotifyTransmissionFinished(string rid) { + //cout << "Transmission finished, callsign = " << rid << '\n'; receiving = false; responseID = rid; if(responseReqd) { runResponseCounter = true; responseCounter = 0.0; - responseTime = 1.8; // TODO - randomize this slightly. + responseTime = 1.2; // TODO - randomize this slightly, and allow it to be dependent on the transmission and how busy the ATC is. + respond = false; // TODO - this ignores the fact that more than one plane could call this before response + // Shouldn't happen with AI only, but user could confuse things?? } else { freqClear = true; } @@ -95,10 +109,6 @@ void FGATC::SetDisplay() { void FGATC::SetNoDisplay() { } -atc_type FGATC::GetType() { - return INVALID; -} - void FGATC::SetData(ATCData* d) { lon = d->lon; lat = d->lat; diff --git a/src/ATC/ATC.hxx b/src/ATC/ATC.hxx index adc5f2d8e..0a06761a0 100644 --- a/src/ATC/ATC.hxx +++ b/src/ATC/ATC.hxx @@ -148,7 +148,7 @@ public: // The user will just have to wait for a gap in dialog as in real life. // Return the type of ATC station that the class represents - virtual atc_type GetType(); + inline atc_type GetType() { return _type; } // Set the core ATC data void SetData(ATCData* d); @@ -192,6 +192,7 @@ protected: int range; string ident; // Code of the airport its at. string name; // Name transmitted in the broadcast. + atc_type _type; // Rendering related stuff bool voice; // Flag - true if we are using voice @@ -209,6 +210,9 @@ protected: string responseID; // ID of the plane to respond to bool respond; // Flag to indicate now is the time to respond - ie set following the count down of the response timer. // Derived classes only need monitor this flag, and use the response ID, as long as they call FGATC::Update(...) + bool _runReleaseCounter; // A timer for releasing the frequency after giving the message enough time to display + double _releaseTime; + double _releaseCounter; }; inline istream& diff --git a/src/ATC/ATCDialog.cxx b/src/ATC/ATCDialog.cxx index c83b514ed..d3548985e 100644 --- a/src/ATC/ATCDialog.cxx +++ b/src/ATC/ATCDialog.cxx @@ -260,7 +260,7 @@ void FGATCDialog::add_entry(string station, string transmission, string menutext void FGATCDialog::remove_entry( const string &station, const string &trans, atc_type type ) { atcmentry_vec_type* p = &((available_dialog[type])[station]); - atcmentry_vec_iterator current = p->begin(); + atcmentry_vec_iterator current = p->begin(); while(current != p->end()) { if(current->transmission == trans) current = p->erase(current); else ++current; @@ -269,7 +269,7 @@ void FGATCDialog::remove_entry( const string &station, const string &trans, atc_ void FGATCDialog::remove_entry( const string &station, int code, atc_type type ) { atcmentry_vec_type* p = &((available_dialog[type])[station]); - atcmentry_vec_iterator current = p->begin(); + atcmentry_vec_iterator current = p->begin(); while(current != p->end()) { if(current->callback_code == code) current = p->erase(current); else ++current; @@ -279,7 +279,7 @@ void FGATCDialog::remove_entry( const string &station, int code, atc_type type ) // query the database whether the transmission is already registered; bool FGATCDialog::trans_reg( const string &station, const string &trans, atc_type type ) { atcmentry_vec_type* p = &((available_dialog[type])[station]); - atcmentry_vec_iterator current = p->begin(); + atcmentry_vec_iterator current = p->begin(); for ( ; current != p->end() ; ++current ) { if ( current->transmission == trans ) return true; } @@ -289,7 +289,7 @@ bool FGATCDialog::trans_reg( const string &station, const string &trans, atc_typ // query the database whether the transmission is already registered; bool FGATCDialog::trans_reg( const string &station, int code, atc_type type ) { atcmentry_vec_type* p = &((available_dialog[type])[station]); - atcmentry_vec_iterator current = p->begin(); + atcmentry_vec_iterator current = p->begin(); for ( ; current != p->end() ; ++current ) { if ( current->callback_code == code ) return true; } @@ -397,14 +397,23 @@ void FGATCDialog::PopupCallback() { break; } } else if(atcptr->GetType() == TOWER) { - ATCMenuEntry a = ((available_dialog[TOWER])[(string)atcptr->get_ident()])[atcDialogCommunicationOptions->getValue()]; - atcptr->SetFreqInUse(); - globals->get_ATC_display()->RegisterSingleMessage(atcptr->GenText(a.transmission, a.callback_code)); - _callbackPending = true; - _callbackTimer = 0.0; - _callbackWait = 5.0; - _callbackPtr = atcptr; - _callbackCode = a.callback_code; + //cout << "TOWER " << endl; + //cout << "ident is " << atcptr->get_ident() << endl; + atcmentry_vec_type atcmlist = (available_dialog[TOWER])[(string)atcptr->get_ident()]; + if(atcmlist.size()) { + //cout << "Doing callback...\n"; + ATCMenuEntry a = atcmlist[atcDialogCommunicationOptions->getValue()]; + atcptr->SetFreqInUse(); + globals->get_ATC_display()->RegisterSingleMessage(atcptr->GenText(a.transmission, a.callback_code)); + _callbackPending = true; + _callbackTimer = 0.0; + _callbackWait = 5.0; + _callbackPtr = atcptr; + _callbackCode = a.callback_code; + } else { + //cout << "No options available...\n"; + } + //cout << "Donded" << endl; } } } diff --git a/src/ATC/ATCdisplay.cxx b/src/ATC/ATCdisplay.cxx index 6d0fda8be..78635152a 100644 --- a/src/ATC/ATCdisplay.cxx +++ b/src/ATC/ATCdisplay.cxx @@ -35,8 +35,8 @@ FGATCDisplay::FGATCDisplay() { rep_msg = false; change_msg_flag = false; - dsp_offset1 = 0; - dsp_offset2 = 0; + dsp_offset1 = 0.0; + dsp_offset2 = 0.0; } @@ -195,6 +195,7 @@ void FGATCDisplay::update(double dt) { } void FGATCDisplay::RegisterSingleMessage(string msg, double delay) { + //cout << msg << '\n'; atcMessage m; m.msg = msg; m.repeating = false; diff --git a/src/ATC/ATCmgr.cxx b/src/ATC/ATCmgr.cxx index cd1686d9d..d87ed5d96 100644 --- a/src/ATC/ATCmgr.cxx +++ b/src/ATC/ATCmgr.cxx @@ -48,6 +48,7 @@ AirportATC::AirportATC() : ground_active(false), set_by_AI(false), numAI(0) + //airport_atc_map.clear(); { for(int i=0; iInit(); initDone = true; + //cout << "ATCmgr::init done!" << endl; } void FGATCMgr::update(double dt) { @@ -149,7 +153,7 @@ void FGATCMgr::update(double dt) { //Traverse the list of active stations. //Only update one class per update step to avoid the whole ATC system having to calculate between frames. //Eventually we should only update every so many steps. - //cout << "In FGATCMgr::update - atc_list.size = " << atc_list.size() << '\n'; + //cout << "In FGATCMgr::update - atc_list.size = " << atc_list.size() << endl; if(atc_list.size()) { if(atc_list_itr == atc_list.end()) { atc_list_itr = atc_list.begin(); @@ -161,6 +165,14 @@ void FGATCMgr::update(double dt) { ++atc_list_itr; } + /* + cout << "ATC_LIST: " << atc_list.size() << ' '; + for(atc_list_iterator it = atc_list.begin(); it != atc_list.end(); it++) { + cout << (*it)->get_ident() << ' '; + } + cout << '\n'; + */ + // Search the tuned frequencies every now and then - this should be done with the event scheduler static int i = 0; // Very ugly - but there should only ever be one instance of FGATCMgr. /* @@ -197,6 +209,7 @@ unsigned short int FGATCMgr::GetFrequency(string ident, atc_type tp) { // Might need more sophistication in this in the future - eg registration by aircraft call-sign. bool FGATCMgr::AIRegisterAirport(string ident) { SG_LOG(SG_ATC, SG_BULK, "AI registered airport " << ident << " with the ATC system"); + //cout << "AI registered airport " << ident << " with the ATC system" << '\n'; if(airport_atc_map.find(ident) != airport_atc_map.end()) { airport_atc_map[ident]->set_by_AI = true; airport_atc_map[ident]->numAI++; @@ -234,9 +247,19 @@ bool FGATCMgr::AIRegisterAirport(string ident) { // Channel is zero based bool FGATCMgr::CommRegisterAirport(string ident, int chan, atc_type tp) { SG_LOG(SG_ATC, SG_BULK, "Comm channel " << chan << " registered airport " << ident); + //cout << "Comm channel " << chan << " registered airport " << ident << '\n'; if(airport_atc_map.find(ident) != airport_atc_map.end()) { //cout << "IN MAP - flagging set by comm..." << endl; airport_atc_map[ident]->set_by_comm[chan][tp] = true; + if(tp == ATIS) { + airport_atc_map[ident]->atis_active = true; + } else if(tp == TOWER) { + airport_atc_map[ident]->tower_active = true; + } else if(tp == GROUND) { + airport_atc_map[ident]->ground_active = true; + } else if(tp == APPROACH) { + //a->approach_active = true; + } // TODO - there *must* be a better way to do this!!! return(true); } else { //cout << "NOT IN MAP - creating new..." << endl; @@ -253,6 +276,15 @@ bool FGATCMgr::CommRegisterAirport(string ident, int chan, atc_type tp) { a->tower_active = false; a->ground_freq = GetFrequency(ident, GROUND); a->ground_active = false; + if(tp == ATIS) { + a->atis_active = true; + } else if(tp == TOWER) { + a->tower_active = true; + } else if(tp == GROUND) { + a->ground_active = true; + } else if(tp == APPROACH) { + //a->approach_active = true; + } // TODO - there *must* be a better way to do this!!! // TODO - some airports will have a tower/ground frequency but be inactive overnight. a->set_by_AI = false; a->numAI = 0; @@ -267,8 +299,9 @@ bool FGATCMgr::CommRegisterAirport(string ident, int chan, atc_type tp) { // Remove from list only if not needed by the AI system or the other comm channel // Note that chan is zero based. -void FGATCMgr::CommRemoveFromList(const char* id, atc_type tp, int chan) { +void FGATCMgr::CommRemoveFromList(string id, atc_type tp, int chan) { SG_LOG(SG_ATC, SG_BULK, "CommRemoveFromList called for airport " << id << " " << tp << " by channel " << chan); + //cout << "CommRemoveFromList called for airport " << id << " " << tp << " by channel " << chan << '\n'; if(airport_atc_map.find(id) != airport_atc_map.end()) { AirportATC* a = airport_atc_map[id]; //cout << "In CommRemoveFromList, a->ground_freq = " << a->ground_freq << endl; @@ -309,12 +342,16 @@ void FGATCMgr::CommRemoveFromList(const char* id, atc_type tp, int chan) { a->set_by_comm[0][tp] = false; // Remove only if not also set by the other comm channel if(!a->set_by_comm[1][tp]) { + a->tower_active = false; + a->ground_active = false; RemoveFromList(id, tp); } break; case 1: a->set_by_comm[1][tp] = false; if(!a->set_by_comm[0][tp]) { + a->tower_active = false; + a->ground_active = false; RemoveFromList(id, tp); } break; @@ -326,24 +363,26 @@ void FGATCMgr::CommRemoveFromList(const char* id, atc_type tp, int chan) { // Remove from list - should only be called from above or similar // This function *will* remove it from the list regardless of who else might want it. -void FGATCMgr::RemoveFromList(const char* id, atc_type tp) { - //cout << "Requested type = " << tp << '\n'; - //cout << "id = " << id << '\n'; - atc_list_itr = atc_list.begin(); - while(atc_list_itr != atc_list.end()) { - //cout << "type = " << (*atc_list_itr)->GetType() << '\n'; - //cout << "Ident = " << (*atc_list_itr)->get_ident() << '\n'; - if( (!strcmp((*atc_list_itr)->get_ident(), id)) - && ((*atc_list_itr)->GetType() == tp) ) { - //Before removing it stop it transmitting!! - //cout << "OBLITERATING FROM LIST!!!\n"; - (*atc_list_itr)->SetNoDisplay(); - (*atc_list_itr)->Update(0.00833); - delete (*atc_list_itr); - atc_list_itr = atc_list.erase(atc_list_itr); - break; - } // Note that that can upset where we are in the list but that doesn't really matter - ++atc_list_itr; +void FGATCMgr::RemoveFromList(string id, atc_type tp) { + //cout << "FGATCMgr::RemoveFromList called..." << endl; + //cout << "Requested type = " << tp << endl; + //cout << "id = " << id << endl; + atc_list_iterator it = atc_list.begin(); + while(it != atc_list.end()) { + //cout << "type = " << (*it)->GetType() << '\n'; + //cout << "Ident = " << (*it)->get_ident() << '\n'; + if( (!strcmp((*it)->get_ident(), id.c_str())) + && ((*it)->GetType() == tp) ) { + //Before removing it stop it transmitting!! + //cout << "OBLITERATING FROM LIST!!!\n"; + (*it)->SetNoDisplay(); + (*it)->Update(0.00833); + delete (*it); + atc_list.erase(it); + atc_list_itr = atc_list.begin(); // Reset the persistent itr incase we've left it off the end. + break; + } + ++it; } } @@ -351,16 +390,18 @@ void FGATCMgr::RemoveFromList(const char* id, atc_type tp) { // Find in list - return a currently active ATC pointer given ICAO code and type // Return NULL if the given service is not in the list // - *** THE CALLING FUNCTION MUST CHECK FOR THIS *** -FGATC* FGATCMgr::FindInList(const char* id, atc_type tp) { - atc_list_itr = atc_list.begin(); - while(atc_list_itr != atc_list.end()) { - if( (!strcmp((*atc_list_itr)->get_ident(), id)) - && ((*atc_list_itr)->GetType() == tp) ) { - return(*atc_list_itr); - } // Note that that can upset where we are in the list but that shouldn't really matter - ++atc_list_itr; +FGATC* FGATCMgr::FindInList(string id, atc_type tp) { + //cout << "Entering FindInList for " << id << ' ' << tp << endl; + atc_list_iterator it = atc_list.begin(); + while(it != atc_list.end()) { + if( (!strcmp((*it)->get_ident(), id.c_str())) + && ((*it)->GetType() == tp) ) { + return(*it); + } + ++it; } // If we get here it's not in the list + //cout << "Couldn't find it in the list though :-(" << endl; return(NULL); } @@ -381,8 +422,10 @@ bool FGATCMgr::GetAirportATCDetails(string icao, AirportATC* a) { // - at the moment all these GetATC... functions exposed are just too complicated. FGATC* FGATCMgr::GetATCPointer(string icao, atc_type type) { if(airport_atc_map.find(icao) == airport_atc_map.end()) { + //cout << "Unable to find " << icao << ' ' << type << " in the airport_atc_map" << endl; return NULL; } + //cout << "Found " << icao << ' ' << type << endl; AirportATC *a = airport_atc_map[icao]; //cout << "a->lon = " << a->lon << '\n'; //cout << "a->elev = " << a->elev << '\n'; @@ -400,6 +443,7 @@ FGATC* FGATCMgr::GetATCPointer(string icao, atc_type type) { atc_list.push_back(t); a->tower_active = true; airport_atc_map[icao] = a; + //cout << "Initing tower in GetATCPointer()\n"; t->Init(); return(t); } else { @@ -441,6 +485,7 @@ FGATC* FGATCMgr::GetATCPointer(string icao, atc_type type) { } SG_LOG(SG_ATC, SG_ALERT, "ERROR IN FGATCMgr - reached end of GetATCPointer"); + //cout << "ERROR IN FGATCMgr - reached end of GetATCPointer" << endl; return(NULL); } @@ -503,7 +548,7 @@ void FGATCMgr::FreqSearch(int channel) { } } // At this point we can assume that we need to add the service. - comm_ident[chan] = (data.ident).c_str(); + comm_ident[chan] = data.ident; comm_type[chan] = data.type; comm_x[chan] = (double)data.x; comm_y[chan] = (double)data.y; @@ -520,6 +565,7 @@ void FGATCMgr::FreqSearch(int channel) { if(app != NULL) { // The station is already in the ATC list //cout << "In list - flagging SetDisplay..." << endl; + comm_atc_ptr[chan] = app; app->SetDisplay(); } else { // Generate the station and put in the ATC list @@ -532,19 +578,24 @@ void FGATCMgr::FreqSearch(int channel) { atc_list.push_back(a); } } else if (comm_type[chan] == TOWER) { + //cout << "TOWER TOWER TOWER\n"; CommRegisterAirport(comm_ident[chan], chan, TOWER); //cout << "Done (TOWER)" << endl; FGATC* app = FindInList(comm_ident[chan], TOWER); if(app != NULL) { // The station is already in the ATC list - SG_LOG(SG_GENERAL, SG_DEBUG, comm_ident[chan] << " is in list - flagging SetDisplay..."); + SG_LOG(SG_GENERAL, SG_DEBUG, comm_ident[chan] << " is in list - flagging SetDisplay..."); + //cout << comm_ident[chan] << " is in list - flagging SetDisplay...\n"; + comm_atc_ptr[chan] = app; app->SetDisplay(); } else { // Generate the station and put in the ATC list - SG_LOG(SG_GENERAL, SG_DEBUG, comm_ident[chan] << " is not in list - generating..."); + SG_LOG(SG_GENERAL, SG_DEBUG, comm_ident[chan] << " is not in list - generating..."); + //cout << comm_ident[chan] << " is not in list - generating...\n"; FGTower* t = new FGTower; t->SetData(&data); comm_atc_ptr[chan] = t; + //cout << "Initing tower in FreqSearch()\n"; t->Init(); t->SetDisplay(); atc_list.push_back(t); @@ -555,6 +606,7 @@ void FGATCMgr::FreqSearch(int channel) { FGATC* app = FindInList(comm_ident[chan], GROUND); if(app != NULL) { // The station is already in the ATC list + comm_atc_ptr[chan] = app; app->SetDisplay(); } else { // Generate the station and put in the ATC list @@ -638,19 +690,21 @@ void FGATCMgr::AreaSearch() { // remove planes which are out of range // TODO - I'm not entirely sure that this belongs here. - atc_list_itr = atc_list.begin(); - while(atc_list_itr != atc_list.end()) { - if((*atc_list_itr)->GetType() == APPROACH ) { - int np = (*atc_list_itr)->RemovePlane(); + atc_list_iterator it = atc_list.begin(); + while(it != atc_list.end()) { + if((*it)->GetType() == APPROACH ) { + int np = (*it)->RemovePlane(); // if approach has no planes left remove it from ATC list if ( np == 0) { - (*atc_list_itr)->SetNoDisplay(); - (*atc_list_itr)->Update(0.00833); - delete (*atc_list_itr); - atc_list_itr = atc_list.erase(atc_list_itr); + //cout << "REMOVING AN APPROACH STATION WITH NO PLANES..." << endl; + (*it)->SetNoDisplay(); + (*it)->Update(0.00833); + delete (*it); + atc_list.erase(it); + atc_list_itr = atc_list.begin(); // Reset the persistent itr incase we've left it off the end. break; // the other stations will be checked next time } } - ++atc_list_itr; + ++it; } } diff --git a/src/ATC/ATCmgr.hxx b/src/ATC/ATCmgr.hxx index 6741a35b7..075138749 100644 --- a/src/ATC/ATCmgr.hxx +++ b/src/ATC/ATCmgr.hxx @@ -128,10 +128,9 @@ private: double comm_range[2], comm_effective_range[2]; bool comm_valid[2]; - const char* comm_ident[2]; - const char* last_comm_ident[2]; - - const char* approach_ident; + string comm_ident[2]; + //string last_comm_ident[2]; + //string approach_ident; bool last_in_range; //FGATIS atis; @@ -195,17 +194,17 @@ private: // Remove a class from the atc_list and delete it from memory // *if* no other comm channel or AI plane is using it. - void CommRemoveFromList(const char* id, atc_type tp, int chan); + void CommRemoveFromList(string id, atc_type tp, int chan); // Remove a class from the atc_list and delete it from memory // Should be called from the above - not directly!! - void RemoveFromList(const char* id, atc_type tp); + void RemoveFromList(string id, atc_type tp); // Return a pointer to a class in the list given ICAO code and type // (external interface to this is through GetATCPointer) // Return NULL if the given service is not in the list // - *** THE CALLING FUNCTION MUST CHECK FOR THIS *** - FGATC* FindInList(const char* id, atc_type tp); + FGATC* FindInList(string id, atc_type tp); // Search the specified channel for stations on the same frequency and in range. void FreqSearch(int channel); diff --git a/src/ATC/ATCutils.cxx b/src/ATC/ATCutils.cxx index 3bd30e10a..f5decf766 100644 --- a/src/ATC/ATCutils.cxx +++ b/src/ATC/ATCutils.cxx @@ -114,38 +114,71 @@ string ConvertRwyNumToSpokenString(string s) { // Return the phonetic letter of a letter represented as an integer 1->26 string GetPhoneticIdent(int i) { // TODO - Check i is between 1 and 26 and wrap if necessary - switch(i) { - case 1 : return("alpha"); - case 2 : return("bravo"); - case 3 : return("charlie"); - case 4 : return("delta"); - case 5 : return("echo"); - case 6 : return("foxtrot"); - case 7 : return("golf"); - case 8 : return("hotel"); - case 9 : return("india"); - case 10 : return("juliet"); - case 11 : return("kilo"); - case 12 : return("lima"); - case 13 : return("mike"); - case 14 : return("november"); - case 15 : return("oscar"); - case 16 : return("papa"); - case 17 : return("quebec"); - case 18 : return("romeo"); - case 19 : return("sierra"); - case 20 : return("tango"); - case 21 : return("uniform"); - case 22 : return("victor"); - case 23 : return("whiskey"); - case 24 : return("x-ray"); - case 25 : return("yankee"); - case 26 : return("zulu"); + return(GetPhoneticIdent(char('a' + (i-1)))); +} + +// Return the phonetic letter of a character in the range a-z or A-Z. +// Currently always returns prefixed by lowercase. +string GetPhoneticIdent(char c) { + c = tolower(c); + // TODO - Check c is between a and z and wrap if necessary + switch(c) { + case 'a' : return("alpha"); + case 'b' : return("bravo"); + case 'c' : return("charlie"); + case 'd' : return("delta"); + case 'e' : return("echo"); + case 'f' : return("foxtrot"); + case 'g' : return("golf"); + case 'h' : return("hotel"); + case 'i' : return("india"); + case 'j' : return("juliet"); + case 'k' : return("kilo"); + case 'l' : return("lima"); + case 'm' : return("mike"); + case 'n' : return("november"); + case 'o' : return("oscar"); + case 'p' : return("papa"); + case 'q' : return("quebec"); + case 'r' : return("romeo"); + case 's' : return("sierra"); + case 't' : return("tango"); + case 'u' : return("uniform"); + case 'v' : return("victor"); + case 'w' : return("whiskey"); + case 'x' : return("x-ray"); + case 'y' : return("yankee"); + case 'z' : return("zulu"); } // We shouldn't get here return("Error"); } +// Get the compass direction associated with a heading in degrees +// Currently returns 8 direction resolution (N, NE, E etc...) +// Might be modified in future to return 4, 8 or 16 resolution but defaulting to 8. +string GetCompassDirection(double h) { + while(h < 0.0) h += 360.0; + while(h > 360.0) h -= 360.0; + if(h < 22.5 || h > 337.5) { + return("North"); + } else if(h < 67.5) { + return("North-East"); + } else if(h < 112.5) { + return("East"); + } else if(h < 157.5) { + return("South-East"); + } else if(h < 202.5) { + return("South"); + } else if(h < 247.5) { + return("South-West"); + } else if(h < 292.5) { + return("West"); + } else { + return("North-West"); + } +} + //================================================================================================================ // Given two positions (lat & lon in degrees), get the HORIZONTAL separation (in meters) @@ -305,16 +338,31 @@ double dclGetAirportElev( const string& id ) { FGAirport a; // double lon, lat; - SG_LOG( SG_GENERAL, SG_INFO, + SG_LOG( SG_ATC, SG_INFO, "Finding elevation for airport: " << id ); if ( dclFindAirportID( id, &a ) ) { - return a.elevation; + return a.elevation * SG_FEET_TO_METER; } else { return -9999.0; } } +// get airport position +Point3D dclGetAirportPos( const string& id ) { + FGAirport a; + // double lon, lat; + + SG_LOG( SG_ATC, SG_INFO, + "Finding position for airport: " << id ); + + if ( dclFindAirportID( id, &a ) ) { + return Point3D(a.longitude, a.latitude, a.elevation); + } else { + return Point3D(0.0, 0.0, -9999.0); + } +} + // Runway stuff // Given a Point3D (lon/lat/elev) and an FGRunway struct, determine if the point lies on the runway bool OnRunway(Point3D pt, const FGRunway& rwy) { diff --git a/src/ATC/ATCutils.hxx b/src/ATC/ATCutils.hxx index 7dfa1569d..448099a67 100644 --- a/src/ATC/ATCutils.hxx +++ b/src/ATC/ATCutils.hxx @@ -56,6 +56,14 @@ string ConvertRwyNumToSpokenString(string s); // Return the phonetic letter of a letter represented as an integer 1->26 string GetPhoneticIdent(int i); +// Return the phonetic letter of a character in the range a-z or A-Z. +// Currently always returns prefixed by lowercase. +string GetPhoneticIdent(char c); + +// Get the compass direction associated with a heading in degrees +// Currently returns 8 direction resolution (N, NE, E etc...) +// Might be modified in future to return 4, 8 or 16 resolution but defaulting to 8. +string GetCompassDirection(double h); /******************************* * @@ -97,9 +105,12 @@ double GetAngleDiff_deg( const double &a1, const double &a2); // find basic airport location info from airport database bool dclFindAirportID( const string& id, FGAirport *a ); -// get airport elevation +// get airport elevation IN METERS double dclGetAirportElev( const string& id ); +// get airport position (elev portion in FEET) +Point3D dclGetAirportPos( const string& id ); + /**************** * * Runways diff --git a/src/ATC/Makefile.am b/src/ATC/Makefile.am index d442cf8be..3cca37513 100644 --- a/src/ATC/Makefile.am +++ b/src/ATC/Makefile.am @@ -17,6 +17,7 @@ libATC_a_SOURCES = \ AIEntity.hxx AIEntity.cxx \ AIPlane.hxx AIPlane.cxx \ AILocalTraffic.hxx AILocalTraffic.cxx \ + AIGAVFRTraffic.hxx AIGAVFRTraffic.cxx \ transmission.hxx transmission.cxx transmissionlist.hxx transmissionlist.cxx INCLUDES = -I$(top_srcdir) -I$(top_srcdir)/src diff --git a/src/ATC/approach.cxx b/src/ATC/approach.cxx index 09b639c96..6d7a5a869 100644 --- a/src/ATC/approach.cxx +++ b/src/ATC/approach.cxx @@ -42,6 +42,8 @@ FGApproach::FGApproach(){ comm1_node = fgGetNode("/radios/comm[0]/frequencies/selected-mhz", true); comm2_node = fgGetNode("/radios/comm[1]/frequencies/selected-mhz", true); + + _type = APPROACH; num_planes = 0; lon_node = fgGetNode("/position/longitude-deg", true); diff --git a/src/ATC/approach.hxx b/src/ATC/approach.hxx index 405de1a05..fb2d31924 100644 --- a/src/ATC/approach.hxx +++ b/src/ATC/approach.hxx @@ -169,7 +169,6 @@ public: inline double get_bucket() const { return bucket; } inline int get_pnum() const { return num_planes; } inline string get_trans_ident() { return trans_ident; } - inline atc_type GetType() { return APPROACH; } private: diff --git a/src/ATC/atis.cxx b/src/ATC/atis.cxx index 4c56b23a4..35bb25a2e 100644 --- a/src/ATC/atis.cxx +++ b/src/ATC/atis.cxx @@ -64,6 +64,7 @@ FGATIS::FGATIS() : { vPtr = globals->get_ATC_mgr()->GetVoicePointer(ATIS); voiceOK = (vPtr == NULL ? false : true); + _type = ATIS; } FGATIS::~FGATIS() { diff --git a/src/ATC/atis.hxx b/src/ATC/atis.hxx index b46d1b47d..0d22b3efa 100644 --- a/src/ATC/atis.hxx +++ b/src/ATC/atis.hxx @@ -86,7 +86,6 @@ class FGATIS : public FGATC { //Indicate that this instance should not be outputting to the ATC display inline void SetNoDisplay(void) {display = false;} - inline atc_type GetType() { return ATIS; } //inline void set_type(const atc_type tp) {type = tp;} inline string get_trans_ident() { return trans_ident; } inline void set_refname(string r) { refname = r; } diff --git a/src/ATC/ground.cxx b/src/ATC/ground.cxx index 5f0072f5e..468560035 100644 --- a/src/ATC/ground.cxx +++ b/src/ATC/ground.cxx @@ -53,6 +53,7 @@ a_path::a_path() { FGGround::FGGround() { ATCmgr = globals->get_ATC_mgr(); + _type = GROUND; display = false; networkLoadOK = false; ground_traffic.erase(ground_traffic.begin(), ground_traffic.end()); @@ -454,6 +455,11 @@ Gate* FGGround::GetGateNode() { } +node* FGGround::GetHoldShortNode(string rwyID) { + return(NULL); // TODO - either implement me or remove me!!! +} + + // WARNING - This is hardwired to my prototype logical network format // and will almost certainly change when Bernie's stuff comes on-line. // Returns NULL if it can't find a valid node. @@ -495,7 +501,7 @@ ground_network_path_type FGGround::GetPath(node* A, node* B) { ground_network_path_type FGGround::GetPath(node* A, string rwyID) { node* b = GetThresholdNode(rwyID); if(b == NULL) { - SG_LOG(SG_ATC, SG_ALERT, "ERROR - unable to find path to runway theshold in ground.cxx\n"); + SG_LOG(SG_ATC, SG_ALERT, "ERROR - unable to find path to runway theshold in ground.cxx for airport " << ident << '\n'); ground_network_path_type emptyPath; emptyPath.erase(emptyPath.begin(), emptyPath.end()); return(emptyPath); diff --git a/src/ATC/ground.hxx b/src/ATC/ground.hxx index 797c95e6f..1a8a94cd2 100644 --- a/src/ATC/ground.hxx +++ b/src/ATC/ground.hxx @@ -232,7 +232,6 @@ public: void Update(double dt); inline string get_trans_ident() { return trans_ident; } - inline atc_type GetType() { return GROUND; } inline void SetDisplay() {display = true;} inline void SetNoDisplay() {display = false;} @@ -258,6 +257,9 @@ public: // Return a pointer to an unused gate Gate* GetGateNode(); + // Return a pointer to a hold short node + node* GetHoldShortNode(string rwyID); + // Runway stuff - this might change in the future. // Get a list of exits from a given runway // It is up to the calling function to check for non-zero size of returned array before use diff --git a/src/ATC/tower.cxx b/src/ATC/tower.cxx index a817d7ff0..ea90860eb 100644 --- a/src/ATC/tower.cxx +++ b/src/ATC/tower.cxx @@ -46,6 +46,7 @@ SG_USING_STD(cout); // TowerPlaneRec TowerPlaneRec::TowerPlaneRec() : + planePtr(NULL), clearedToLand(false), clearedToLineUp(false), clearedToTakeOff(false), @@ -65,12 +66,13 @@ TowerPlaneRec::TowerPlaneRec() : opType(TTT_UNKNOWN), leg(LEG_UNKNOWN), landingType(AIP_LT_UNKNOWN), - isUser(false) + isUser(false) { plane.callsign = "UNKNOWN"; } TowerPlaneRec::TowerPlaneRec(PlaneRec p) : + planePtr(NULL), clearedToLand(false), clearedToLineUp(false), clearedToTakeOff(false), @@ -96,6 +98,7 @@ TowerPlaneRec::TowerPlaneRec(PlaneRec p) : } TowerPlaneRec::TowerPlaneRec(Point3D pt) : + planePtr(NULL), clearedToLand(false), clearedToLineUp(false), clearedToTakeOff(false), @@ -122,6 +125,7 @@ TowerPlaneRec::TowerPlaneRec(Point3D pt) : } TowerPlaneRec::TowerPlaneRec(PlaneRec p, Point3D pt) : + planePtr(NULL), clearedToLand(false), clearedToLineUp(false), clearedToTakeOff(false), @@ -153,23 +157,39 @@ TowerPlaneRec::TowerPlaneRec(PlaneRec p, Point3D pt) : /******************************************* TODO List -Currently user is assumed to have taken off again when leaving the runway - check speed/elev for taxiing-in. - -Tell AI plane to contact ground when taxiing in. +Currently user is assumed to have taken off again when leaving the runway - check speed/elev for taxiing-in. (MAJOR) -Use track instead of heading to determine what leg of the circuit the user is flying. +Use track instead of heading to determine what leg of the circuit the user is flying. (MINOR) -Use altitude as well as position to try to determine if the user has left the circuit. +Use altitude as well as position to try to determine if the user has left the circuit. (MEDIUM - other issues as well). Currently HoldShortReported code assumes there will be only one plane holding for the runway at once and -will break when planes start queueing. +will break when planes start queueing. (CRITICAL) + +Report-Runway-Vacated is left as only user ATC option following a go-around. (MAJOR) + +Report Go-Around should be added to user options following reporting final or downwind. (MEDIUM). + +Report-Downwind is not added as ATC option when user takes off to fly a circuit. (MAJOR) + +eta of USER can be calculated very wrongly in circuit if flying straight out and turn4 etc are with +ve ortho y. +This can then screw up circuit ordering for other planes (MEDIUM) + +USER leaving circuit needs to be more robustly considered when intentions unknown +Currently only considered during climbout and breaks when user turns (MEDIUM). -Implement ReportRunwayVacated +GetPos() of the AI planes is called erratically - either too much or not enough. (MINOR) + +GO-AROUND is instructed very late at < 12s to landing - possibly make more dependent on chance of rwy clearing before landing (FEATURE) + +Need to make clear when TowerPlaneRecs do or don't get deleted in RemoveFromCircuitList etc. (MINOR until I misuse it - then CRITICAL!) *******************************************/ FGTower::FGTower() { ATCmgr = globals->get_ATC_mgr(); + _type = TOWER; + // Init the property nodes - TODO - need to make sure we're getting surface winds. wind_from_hdg = fgGetNode("/environment/wind-from-heading-deg", true); wind_speed_knots = fgGetNode("/environment/wind-speed-kt", true); @@ -178,11 +198,14 @@ FGTower::FGTower() { update_count_max = 15; holdListItr = holdList.begin(); + appList.clear(); appListItr = appList.begin(); depListItr = depList.begin(); rwyListItr = rwyList.begin(); circuitListItr = circuitList.begin(); trafficListItr = trafficList.begin(); + vacatedList.clear(); + vacatedListItr = vacatedList.begin(); freqClear = true; @@ -192,6 +215,8 @@ FGTower::FGTower() { nominal_downwind_leg_pos = 1000.0; nominal_base_leg_pos = -1000.0; // TODO - set nominal crosswind leg pos based on minimum distance from takeoff end of rwy. + + _departureControlled = false; } FGTower::~FGTower() { @@ -201,6 +226,7 @@ FGTower::~FGTower() { } void FGTower::Init() { + //cout << "Initialising tower " << ident << '\n'; display = false; // Pointers to user's position @@ -257,14 +283,18 @@ void FGTower::Init() { } } + // TODO - attempt to get a departure control pointer to see if we need to hand off departing traffic to departure. + // Get the airport elevation - aptElev = dclGetAirportElev(ident.c_str()) * SG_FEET_TO_METER; + aptElev = dclGetAirportElev(ident.c_str()); + // TODO - this function only assumes one active rwy. DoRwyDetails(); - // FIXME - this currently assumes use of the active rwy by the user. - rwyOccupied = OnAnyRunway(Point3D(user_lon_node->getDoubleValue(), user_lat_node->getDoubleValue(), 0.0)); + // TODO - this currently assumes only one active runway. + rwyOccupied = OnActiveRunway(Point3D(user_lon_node->getDoubleValue(), user_lat_node->getDoubleValue(), 0.0)); if(rwyOccupied) { + //cout << "User found on active runway\n"; // Assume the user is started at the threshold ready to take-off TowerPlaneRec* t = new TowerPlaneRec; t->plane.callsign = fgGetString("/sim/user/callsign"); @@ -278,9 +308,12 @@ void FGTower::Init() { rwyList.push_back(t); departed = false; } else { + //cout << "User not on active runway\n"; // For now assume that this means the user is not at the airport and is in the air. // TODO FIXME - this will break when user starts on apron, at hold short, etc. - 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); + if(!OnAnyRunway(Point3D(user_lon_node->getDoubleValue(), user_lat_node->getDoubleValue(), 0.0))) { + 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); + } } } @@ -357,6 +390,10 @@ void FGTower::Update(double dt) { CheckApproachList(dt); } + if(update_count == 8) { + CheckDepartureList(dt); + } + // TODO - do one plane from the departure list and set departed = false when out of consideration //doCommunication(); @@ -399,7 +436,7 @@ void FGTower::Update(double dt) { void FGTower::ReceiveUserCallback(int code) { if(code == (int)USER_REQUEST_VFR_DEPARTURE) { - cout << "User requested departure\n"; + //cout << "User requested departure\n"; } else if(code == (int)USER_REQUEST_VFR_ARRIVAL) { VFRArrivalContact("USER"); } else if(code == (int)USER_REQUEST_VFR_ARRIVAL_FULL_STOP) { @@ -417,11 +454,12 @@ void FGTower::ReceiveUserCallback(int code) { } void FGTower::Respond() { - cout << "Entering Respond, responseID = " << responseID << endl; + //cout << "\nEntering Respond, responseID = " << responseID << endl; TowerPlaneRec* t = FindPlane(responseID); if(t) { // This will grow!!! if(t->vfrArrivalReported && !t->vfrArrivalAcknowledged) { + //cout << "Tower " << ident << " is responding to VFR arrival reported...\n"; // Testing - hardwire straight in for now string trns = t->plane.callsign; trns += " "; @@ -433,24 +471,37 @@ void FGTower::Respond() { Point3D op = ortho.ConvertToLocal(t->pos); if(op.y() < -1000) { trns += " Report three mile straight-in runway "; - current_atcdialog->add_entry(ident, "@AP Tower @CS @MI mile final Runway @RW", "Report Final", TOWER, (int)USER_REPORT_3_MILE_FINAL); + t->opType = STRAIGHT_IN; + if(t->isUser) { + current_atcdialog->add_entry(ident, "@AP Tower @CS @MI mile final Runway @RW", "Report Final", TOWER, (int)USER_REPORT_3_MILE_FINAL); + } else { + t->planePtr->RegisterTransmission(14); + } } else { // For now we'll just request reporting downwind. // TODO - In real life something like 'report 2 miles southwest right downwind rwy 19R' might be used // but I'm not sure how to handle all permutations of which direction to tell to report from yet. trns += " Report "; - trns += (rwy.patternDirection ? "right " : "left "); + //cout << "Responding, rwy.patterDirection is " << rwy.patternDirection << '\n'; + trns += ((rwy.patternDirection == 1) ? "right " : "left "); trns += "downwind runway "; - current_atcdialog->add_entry(ident, "@AP Tower @CS Downwind @RW", "Report Downwind", TOWER, (int)USER_REPORT_DOWNWIND); + t->opType = CIRCUIT; + // leave it in the app list until it gets into pattern though. + if(t->isUser) { + current_atcdialog->add_entry(ident, "@AP Tower @CS Downwind @RW", "Report Downwind", TOWER, (int)USER_REPORT_DOWNWIND); + } else { + t->planePtr->RegisterTransmission(15); + } } trns += ConvertRwyNumToSpokenString(activeRwy); if(display) { globals->get_ATC_display()->RegisterSingleMessage(trns, 0); } else { - cout << "Not displaying, trns was " << trns << '\n'; + //cout << "Not displaying, trns was " << trns << '\n'; } t->vfrArrivalAcknowledged = true; } else if(t->downwindReported) { + //cout << "Tower " << ident << " is responding to downwind reported...\n"; t->downwindReported = false; int i = 1; for(tower_plane_rec_list_iterator twrItr = circuitList.begin(); twrItr != circuitList.end(); twrItr++) { @@ -461,9 +512,10 @@ void FGTower::Respond() { trns += " Number "; trns += ConvertNumToSpokenDigits(i); trns += " "; - if(i == 1) { - trns += "Cleared to land"; + if((i == 1) && (!rwyList.size()) && (t->nextOnRwy)) { + trns += "Cleared to land"; // TODO - clear for the option if appropriate t->clearedToLand = true; + if(!t->isUser) t->planePtr->RegisterTransmission(7); } if(display) { globals->get_ATC_display()->RegisterSingleMessage(trns); @@ -471,8 +523,10 @@ void FGTower::Respond() { if(t->isUser) { if(t->opType == TTT_UNKNOWN) t->opType = CIRCUIT; current_atcdialog->add_entry(ident, "@CS Clear of the runway", "Report runway vacated", TOWER, USER_REPORT_RWY_VACATED); + // TODO - add report going around as an option } } else if(t->holdShortReported) { + //cout << "Tower " << ident << " is reponding to holdShortReported...\n"; if(t->nextOnRwy) { 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!!) // Do nothing for now - consider acknowloging hold short eventually @@ -503,11 +557,12 @@ void FGTower::Respond() { } t->holdShortReported = false; } else if(t->finalReported && !(t->finalAcknowledged)) { + //cout << "Tower " << ident << " is responding to finalReported...\n"; bool disp = true; string trns = t->plane.callsign; - cout << (t->nextOnRwy ? "Next on rwy " : "Not next!! "); - cout << (rwyOccupied ? "RWY OCCUPIED!!\n" : "Rwy not occupied\n"); - if(t->nextOnRwy && !rwyOccupied) { + //cout << (t->nextOnRwy ? "Next on rwy " : "Not next!! "); + //cout << (rwyOccupied ? "RWY OCCUPIED!!\n" : "Rwy not occupied\n"); + if(t->nextOnRwy && !rwyOccupied && !(t->instructedToGoAround)) { if(t->landingType == FULL_STOP) { trns += " cleared to land "; } else { @@ -515,7 +570,13 @@ void FGTower::Respond() { } // TODO - add winds t->clearedToLand = true; - if(t->isUser) current_atcdialog->add_entry(ident, "@CS Clear of the runway", "Report runway vacated", TOWER, USER_REPORT_RWY_VACATED); + // Maybe remove report downwind from menu here as well incase user didn't bother to? + if(t->isUser) { + current_atcdialog->add_entry(ident, "@CS Clear of the runway", "Report runway vacated", TOWER, USER_REPORT_RWY_VACATED); + // TODO - add report going around as an option. + } else { + t->planePtr->RegisterTransmission(7); + } } else if(t->eta < 20) { // Do nothing - we'll be telling it to go around in less than 10 seconds if the // runway doesn't clear so no point in calling "continue approach". @@ -529,27 +590,38 @@ void FGTower::Respond() { } t->finalAcknowledged = true; } else if(t->rwyVacatedReported && !(t->rwyVacatedAcknowledged)) { - string trns = t->plane.callsign; - if(separateGround) { - trns += " Contact ground on "; - double f = globals->get_ATC_mgr()->GetFrequency(ident, GROUND) / 100.0; - char buf[10]; - sprintf(buf, "%.2f", f); - trns += buf; - trns += " Good Day"; - } else { - // Cop-out!! - trns += " cleared for taxi to the GA parking"; - } - if(display) { - globals->get_ATC_display()->RegisterSingleMessage(trns); - } + ProcessRunwayVacatedReport(t); t->rwyVacatedAcknowledged = true; - // Maybe we should check that the plane really *has* vacated the runway! } } - freqClear = true; // FIXME - set this to come true after enough time to render the message - //cout << "Done Respond" << endl; + //freqClear = true; // FIXME - set this to come true after enough time to render the message + _releaseCounter = 0.0; + _releaseTime = 5.5; + _runReleaseCounter = true; + //cout << "Done Respond\n" << endl; +} + +void FGTower::ProcessRunwayVacatedReport(TowerPlaneRec* t) { + //cout << "Processing rwy vacated...\n"; + string trns = t->plane.callsign; + if(separateGround) { + trns += " Contact ground on "; + double f = globals->get_ATC_mgr()->GetFrequency(ident, GROUND) / 100.0; + char buf[10]; + sprintf(buf, "%.2f", f); + trns += buf; + trns += " Good Day"; + if(!t->isUser) t->planePtr->RegisterTransmission(5); + } else { + // Cop-out!! + trns += " cleared for taxi to the GA parking"; + if(!t->isUser) t->planePtr->RegisterTransmission(6); // TODO - this is a mega-hack!! + } + //cout << "trns = " << trns << '\n'; + if(display) { + globals->get_ATC_display()->RegisterSingleMessage(trns); + } + // Maybe we should check that the plane really *has* vacated the runway! } // Currently this assumes we *are* next on the runway and doesn't check for planes about to land - @@ -659,6 +731,10 @@ void FGTower::ClearHoldingPlane(TowerPlaneRec* t) { //cout << "Done ClearHoldingPlane " << endl; } + +// *************************************************************************************** +// ********** Functions to periodically check what the various traffic is doing ********** + // Do one plane from the hold list void FGTower::CheckHoldList(double dt) { //cout << "Entering CheckHoldList..." << endl; @@ -710,10 +786,12 @@ void FGTower::CheckCircuitList(double dt) { circuitListItr = circuitList.begin(); } TowerPlaneRec* t = *circuitListItr; + //cout << ident << ' ' << circuitList.size() << ' ' << t->plane.callsign << " " << t->leg << " eta " << t->eta << '\n'; if(t->isUser) { t->pos.setlon(user_lon_node->getDoubleValue()); t->pos.setlat(user_lat_node->getDoubleValue()); t->pos.setelev(user_elev_node->getDoubleValue()); + //cout << ident << ' ' << circuitList.size() << ' ' << t->plane.callsign << " " << t->leg << " eta " << t->eta << '\n'; } else { 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. t->landingType = t->planePtr->GetLandingOption(); @@ -738,11 +816,13 @@ void FGTower::CheckCircuitList(double dt) { //cout << "Climbout\n"; // If it's the user we may be unsure of his/her intentions. // (Hopefully the AI planes won't try confusing the sim!!!) + //cout << "tortho.y = " << tortho.y() << '\n'; if(t->opType == TTT_UNKNOWN) { if(tortho.y() > 5000) { // 5 km out from threshold - assume it's a departure t->opType = OUTBOUND; // TODO - could check if the user has climbed significantly above circuit altitude as well. // Since we are unknown operation we should be in depList already. + //cout << ident << " Removing user from circuitList (TTT_UNKNOWN)\n"; circuitList.erase(circuitListItr); RemoveFromTrafficList(t->plane.callsign); circuitListItr = circuitList.begin(); @@ -752,6 +832,7 @@ void FGTower::CheckCircuitList(double dt) { // 10 km out - assume the user has abandoned the circuit!! t->opType = OUTBOUND; depList.push_back(t); + //cout << ident << " removing user from circuitList (CIRCUIT)\n"; circuitList.erase(circuitListItr); circuitListItr = circuitList.begin(); } @@ -807,70 +888,79 @@ void FGTower::CheckCircuitList(double dt) { // 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!! if(circuitListItr == circuitList.begin()) { switch(t->leg) { - case FINAL: + case FINAL: // 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. base_leg_pos = tortho.y(); //cout << "base_leg_pos = " << base_leg_pos << '\n'; break; - case TURN4: + case TURN4: // Fall through to base - case BASE: + case BASE: base_leg_pos = tortho.y(); //cout << "base_leg_pos = " << base_leg_pos << '\n'; break; - case TURN3: + case TURN3: // Fall through to downwind - case DOWNWIND: + case DOWNWIND: // Only have the downwind leg pos as turn-to-base constraint if more negative than we already have. base_leg_pos = (tortho.y() < base_leg_pos ? tortho.y() : base_leg_pos); //cout << "base_leg_pos = " << base_leg_pos; downwind_leg_pos = tortho.x(); // Assume that a following plane can simply be constrained by the immediately in front downwind plane //cout << " downwind_leg_pos = " << downwind_leg_pos << '\n'; break; - case TURN2: + case TURN2: // Fall through to crosswind - case CROSSWIND: + case CROSSWIND: crosswind_leg_pos = tortho.y(); //cout << "crosswind_leg_pos = " << crosswind_leg_pos << '\n'; t->instructedToGoAround = false; break; - case TURN1: + case TURN1: // Fall through to climbout - case CLIMBOUT: + case CLIMBOUT: // Only use current by constraint as largest crosswind_leg_pos = (tortho.y() > crosswind_leg_pos ? tortho.y() : crosswind_leg_pos); //cout << "crosswind_leg_pos = " << crosswind_leg_pos << '\n'; break; - case TAKEOFF_ROLL: + case TAKEOFF_ROLL: break; - case LEG_UNKNOWN: + case LEG_UNKNOWN: break; - case LANDING_ROLL: + case LANDING_ROLL: break; - default: + default: break; } } - if(t->leg == FINAL) { - if(t->landingType == FULL_STOP) t->opType = INBOUND; - if(t->eta < 12 && rwyList.size() && !(t->instructedToGoAround)) { - // TODO - need to make this more sophisticated - // eg. is the plane accelerating down the runway taking off [OK], - // or stationary near the start [V. BAD!!]. - // For now this should stop the AI plane landing on top of the user. - string trns = t->plane.callsign; - trns += " GO AROUND TRAFFIC ON RUNWAY I REPEAT GO AROUND"; - if(display) { - globals->get_ATC_display()->RegisterSingleMessage(trns, 0); - } - t->instructedToGoAround = true; - if(t->planePtr) { - cout << "Registering Go-around transmission with AI plane\n"; - t->planePtr->RegisterTransmission(13); + if(t->leg == FINAL && !(t->instructedToGoAround)) { + //cout << "YES FINAL, t->eta = " << t->eta << ", rwyList.size() = " << rwyList.size() << '\n'; + if(t->landingType == FULL_STOP) { + t->opType = INBOUND; + //cout << "\n******** SWITCHING TO INBOUND AT POINT AAA *********\n\n"; + if(t->eta < 12 && rwyList.size()) { + // TODO - need to make this more sophisticated + // eg. is the plane accelerating down the runway taking off [OK], + // or stationary near the start [V. BAD!!]. + // For now this should stop the AI plane landing on top of the user. + string trns = t->plane.callsign; + trns += " GO AROUND TRAFFIC ON RUNWAY I REPEAT GO AROUND"; + if(display) { + globals->get_ATC_display()->RegisterSingleMessage(trns, 0); + } + t->instructedToGoAround = true; + t->clearedToLand = false; + // Assume it complies!!! + t->opType = CIRCUIT; + t->leg = CLIMBOUT; + if(t->planePtr) { + //cout << "Registering Go-around transmission with AI plane\n"; + t->planePtr->RegisterTransmission(13); + } } } } else if(t->leg == LANDING_ROLL) { + //cout << t->plane.callsign << " has landed - adding to rwyList\n"; rwyList.push_front(t); // TODO - if(!clearedToLand) shout something!! t->clearedToLand = false; @@ -878,6 +968,7 @@ void FGTower::CheckCircuitList(double dt) { if(t->isUser) { t->opType = TTT_UNKNOWN; } // TODO - allow the user to specify opType via ATC menu + //cout << ident << " Removing " << t->plane.callsign << " from circuitList..." << endl; circuitListItr = circuitList.erase(circuitListItr); if(circuitListItr == circuitList.end() ) { circuitListItr = circuitList.begin(); @@ -909,16 +1000,24 @@ void FGTower::CheckRunwayList(double dt) { } bool on_rwy = OnActiveRunway(t->pos); if(!on_rwy) { + // TODO - for all of these we need to check what the user is *actually* doing! if((t->opType == INBOUND) || (t->opType == STRAIGHT_IN)) { - rwyList.pop_front(); - delete t; - // TODO - tell it to taxi / contact ground / don't delete it etc! + //cout << "Tower " << ident << " is removing plane " << t->plane.callsign << " from rwy list (vacated)\n"; + //cout << "Size of rwylist was " << rwyList.size() << '\n'; + //cout << "Size of vacatedList was " << vacatedList.size() << '\n'; + RemoveFromRwyList(t->plane.callsign); + vacatedList.push_back(t); + //cout << "Size of rwylist is " << rwyList.size() << '\n'; + //cout << "Size of vacatedList is " << vacatedList.size() << '\n'; + // At the moment we wait until Runway Vacated is reported by the plane before telling to contact ground etc. + // It's possible we could be a bit more proactive about this. } else if(t->opType == OUTBOUND) { depList.push_back(t); rwyList.pop_front(); departed = true; timeSinceLastDeparture = 0.0; } else if(t->opType == CIRCUIT) { + //cout << ident << " adding " << t->plane.callsign << " to circuitList" << endl; circuitList.push_back(t); AddToTrafficList(t); rwyList.pop_front(); @@ -926,6 +1025,7 @@ void FGTower::CheckRunwayList(double dt) { timeSinceLastDeparture = 0.0; } else if(t->opType == TTT_UNKNOWN) { depList.push_back(t); + //cout << ident << " adding " << t->plane.callsign << " to circuitList" << endl; circuitList.push_back(t); AddToTrafficList(t); rwyList.pop_front(); @@ -942,25 +1042,139 @@ void FGTower::CheckRunwayList(double dt) { // Do one plane from the approach list void FGTower::CheckApproachList(double dt) { + //cout << "CheckApproachList called for " << ident << endl; + //cout << "AppList.size is " << appList.size() << endl; if(appList.size()) { if(appListItr == appList.end()) { appListItr = appList.begin(); } TowerPlaneRec* t = *appListItr; //cout << "t = " << t << endl; + //cout << "Checking " << t->plane.callsign << endl; if(t->isUser) { t->pos.setlon(user_lon_node->getDoubleValue()); t->pos.setlat(user_lat_node->getDoubleValue()); t->pos.setelev(user_elev_node->getDoubleValue()); } else { // TODO - set/update the position if it's an AI plane + } + //cout << "eta is " << t->eta << ", rwy is " << (rwyList.size() ? "occupied " : "clear ") << '\n'; + if(t->eta < 12 && rwyList.size() && !(t->instructedToGoAround)) { + // TODO - need to make this more sophisticated + // eg. is the plane accelerating down the runway taking off [OK], + // or stationary near the start [V. BAD!!]. + // For now this should stop the AI plane landing on top of the user. + string trns = t->plane.callsign; + trns += " GO AROUND TRAFFIC ON RUNWAY I REPEAT GO AROUND"; + if(display) { + globals->get_ATC_display()->RegisterSingleMessage(trns, 0); + } + t->instructedToGoAround = true; + t->clearedToLand = false; + t->nextOnRwy = false; // But note this is recalculated so don't rely on it + // Assume it complies!!! + t->opType = CIRCUIT; + t->leg = CLIMBOUT; + if(!t->isUser) { + if(t->planePtr) { + //cout << "Registering Go-around transmission with AI plane\n"; + t->planePtr->RegisterTransmission(13); + } + } else { + // TODO - add Go-around ack to comm options, + // remove report rwy vacated. (possibly). + } } - if(t->nextOnRwy && !(t->clearedToLand)) { + if(t->nextOnRwy && !(t->clearedToLand) && !(t->instructedToGoAround)) { // check distance away and whether runway occupied // and schedule transmission if necessary - } + } + + // Check for landing... + bool landed = false; + if(!t->isUser) { + if(t->planePtr) { + if(t->planePtr->GetLeg() == LANDING_ROLL) { + landed = true; + } + } else { + SG_LOG(SG_ATC, SG_ALERT, "WARNING - not user and null planePtr in CheckApproachList!"); + } + } else { // user + if(OnActiveRunway(t->pos)) { + landed = true; + } + } + + if(landed) { + // Duplicated in CheckCircuitList - must be able to rationalise this somehow! + //cout << "A " << t->plane.callsign << " has landed, adding to rwyList...\n"; + rwyList.push_front(t); + // TODO - if(!clearedToLand) shout something!! + t->clearedToLand = false; + RemoveFromTrafficList(t->plane.callsign); + //if(t->isUser) { + // t->opType = TTT_UNKNOWN; + //} // TODO - allow the user to specify opType via ATC menu + appListItr = appList.erase(appListItr); + if(appListItr == appList.end() ) { + appListItr = appList.begin(); + } + } + ++appListItr; } + //cout << "Done" << endl; +} + +// Do one plane from the departure list +void FGTower::CheckDepartureList(double dt) { + if(depList.size()) { + if(depListItr == depList.end()) { + depListItr = depList.begin(); + } + TowerPlaneRec* t = *depListItr; + //cout << "Dep list, checking " << t->plane.callsign; + + double distout; // meters + if(t->isUser) distout = dclGetHorizontalSeparation(Point3D(lon, lat, elev), Point3D(user_lon_node->getDoubleValue(), user_lat_node->getDoubleValue(), user_elev_node->getDoubleValue())); + else distout = dclGetHorizontalSeparation(Point3D(lon, lat, elev), t->planePtr->GetPos()); + //cout << " distout = " << distout << '\n'; + if(distout > 10000) { + string trns = t->plane.callsign; + trns += " You are now clear of my airspace, good day"; + if(display) { + globals->get_ATC_display()->RegisterSingleMessage(trns, 0); + } + if(t->isUser) { + // Change the communication options + RemoveAllUserDialogOptions(); + 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); + } else { + // Send a clear-of-airspace signal + // TODO - implement this once we actually have departing AI traffic (currently all circuits or arrivals). + } + RemovePlane(t->plane.callsign); + } else { + ++depListItr; + } + } +} + +// ********** End periodic check functions *********************************************** +// *************************************************************************************** + + +// Remove all dialog options for this tower. +void FGTower::RemoveAllUserDialogOptions() { + current_atcdialog->remove_entry(ident, USER_REQUEST_VFR_DEPARTURE, TOWER); + current_atcdialog->remove_entry(ident, USER_REQUEST_VFR_ARRIVAL, TOWER); + current_atcdialog->remove_entry(ident, USER_REQUEST_VFR_ARRIVAL_FULL_STOP, TOWER); + current_atcdialog->remove_entry(ident, USER_REQUEST_VFR_ARRIVAL_TOUCH_AND_GO, TOWER); + current_atcdialog->remove_entry(ident, USER_REPORT_3_MILE_FINAL, TOWER); + current_atcdialog->remove_entry(ident, USER_REPORT_DOWNWIND, TOWER); + current_atcdialog->remove_entry(ident, USER_REPORT_RWY_VACATED, TOWER); + current_atcdialog->remove_entry(ident, USER_REPORT_GOING_AROUND, TOWER); } // Returns true if positions of crosswind/downwind/base leg turns should be constrained by previous traffic @@ -1011,6 +1225,7 @@ void FGTower::DoRwyDetails() { FGRunway runway; bool rwyGood = globals->get_runways()->search(ident, int(hdg), &runway); if(rwyGood) { + //cout << "RUNWAY GOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOD\n"; activeRwy = runway.rwy_no; rwy.rwyID = runway.rwy_no; SG_LOG(SG_ATC, SG_INFO, "Active runway for airport " << ident << " is " << activeRwy); @@ -1051,6 +1266,7 @@ void FGTower::DoRwyDetails() { if(rwy.rwyID.size() == 3) { rwy.patternDirection = (rwy.rwyID.substr(2,1) == "R" ? 1 : -1); } + //cout << "Doing details, rwy.patterDirection is " << rwy.patternDirection << '\n'; } else { SG_LOG(SG_ATC, SG_ALERT, "Help - can't get good runway in FGTower!!"); activeRwy = "NN"; @@ -1113,6 +1329,7 @@ bool FGTower::RemoveFromTrafficList(string id) { TowerPlaneRec* tpr = *twrItr; if(tpr->plane.callsign == id) { trafficList.erase(twrItr); + trafficListItr = trafficList.begin(); return(true); } } @@ -1121,6 +1338,37 @@ bool FGTower::RemoveFromTrafficList(string id) { } +// Returns true if successful +bool FGTower::RemoveFromAppList(string id) { + tower_plane_rec_list_iterator twrItr; + for(twrItr = appList.begin(); twrItr != appList.end(); twrItr++) { + TowerPlaneRec* tpr = *twrItr; + if(tpr->plane.callsign == id) { + appList.erase(twrItr); + appListItr = appList.begin(); + return(true); + } + } + //SG_LOG(SG_ATC, SG_WARN, "Warning - unable to remove aircraft " << id << " from appList in FGTower"); + return(false); +} + +// Returns true if successful +bool FGTower::RemoveFromRwyList(string id) { + tower_plane_rec_list_iterator twrItr; + for(twrItr = rwyList.begin(); twrItr != rwyList.end(); twrItr++) { + TowerPlaneRec* tpr = *twrItr; + if(tpr->plane.callsign == id) { + rwyList.erase(twrItr); + rwyListItr = rwyList.begin(); + return(true); + } + } + //SG_LOG(SG_ATC, SG_WARN, "Warning - unable to remove aircraft " << id << " from rwyList in FGTower"); + return(false); +} + + // Add a tower plane rec with ETA to the traffic list in the correct position ETA-wise // and set nextOnRwy if so. // Returns true if this could cause a threshold ETA conflict with other traffic, false otherwise. @@ -1198,30 +1446,51 @@ bool FGTower::AddToTrafficList(TowerPlaneRec* t, bool holding) { // Add a tower plane rec with ETA to the circuit list in the correct position ETA-wise // Returns true if this might cause a separation conflict (based on ETA) with other traffic, false otherwise. +// Safe to add a plane that is already in - planes with the same callsign are not added. bool FGTower::AddToCircuitList(TowerPlaneRec* t) { + if(!t) { + //cout << "**********************************************\n"; + //cout << "AddToCircuitList called with NULL pointer!!!!!\n"; + //cout << "**********************************************\n"; + return false; + } //cout << "ADD: " << circuitList.size(); - //cout << "AddToCircuitList called, currently size = " << circuitList.size() << endl; + //cout << ident << " AddToCircuitList called for " << t->plane.callsign << ", currently size = " << circuitList.size() << endl; 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. bool conflict = false; tower_plane_rec_list_iterator twrItr; + // First check if the plane is already in the list + //cout << "A" << endl; + //cout << "Checking whether " << t->plane.callsign << " is already in circuit list..." << endl; + //cout << "B" << endl; for(twrItr = circuitList.begin(); twrItr != circuitList.end(); twrItr++) { - TowerPlaneRec* tpr = *twrItr; - - if(t->eta < tpr->eta) { - // Ugg - this one's tricky. - // It depends on what the two planes are doing and whether there's a conflict what we do. - if(tpr->eta - t->eta > separation_time) { // No probs, plane 2 can squeeze in before plane 1 with no apparent conflict - circuitList.insert(twrItr, t); - } else { // Ooops - this ones tricky - we have a potential conflict! - conflict = true; - // HACK - just add anyway for now and flag conflict. - circuitList.insert(twrItr, t); - } - //cout << "\tC\t" << circuitList.size() << '\n'; - return(conflict); - } + if((*twrItr)->plane.callsign == t->plane.callsign) { + //cout << "In list - returning...\n"; + return false; + } + } + //cout << "Not in list - adding..." << endl; + + for(twrItr = circuitList.begin(); twrItr != circuitList.end(); twrItr++) { + TowerPlaneRec* tpr = *twrItr; + //cout << tpr->plane.callsign << " eta is " << tpr->eta << '\n'; + //cout << "New eta is " << t->eta << '\n'; + if(t->eta < tpr->eta) { + // Ugg - this one's tricky. + // It depends on what the two planes are doing and whether there's a conflict what we do. + if(tpr->eta - t->eta > separation_time) { // No probs, plane 2 can squeeze in before plane 1 with no apparent conflict + circuitList.insert(twrItr, t); + } else { // Ooops - this ones tricky - we have a potential conflict! + conflict = true; + // HACK - just add anyway for now and flag conflict. + circuitList.insert(twrItr, t); + } + //cout << "\tC\t" << circuitList.size() << '\n'; + return(conflict); + } } // If we get here we must be at the end of the list, or maybe the list is empty. + //cout << ident << " adding " << t->plane.callsign << " to circuitList" << endl; circuitList.push_back(t); // TODO - check the separation with the preceding plane for the conflict flag. //cout << "\tE\t" << circuitList.size() << endl; return(conflict); @@ -1250,13 +1519,14 @@ void FGTower::CalcETA(TowerPlaneRec* tpr, bool printout) { Point3D op = ortho.ConvertToLocal(tpr->pos); //if(printout) { //if(!tpr->isUser) cout << "Orthopos is " << op.x() << ", " << op.y() << ' '; - // cout << "opType is " << tpr->opType << '\n'; + //cout << "opType is " << tpr->opType << '\n'; //} double dist_out_m = op.y(); - double dist_across_m = fabs(op.x()); // FIXME = the fabs is a hack to cope with the fact that we don't know the circuit direction yet + 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 //cout << "Doing ETA calc for " << tpr->plane.callsign << '\n'; - if(tpr->opType == STRAIGHT_IN) { + if(tpr->opType == STRAIGHT_IN || tpr->opType == INBOUND) { + //cout << "CASE 1\n"; double dist_to_go_m = sqrt((dist_out_m * dist_out_m) + (dist_across_m * dist_across_m)); if(dist_to_go_m < 1000) { tpr->eta = dist_to_go_m / final_ias; @@ -1264,13 +1534,15 @@ void FGTower::CalcETA(TowerPlaneRec* tpr, bool printout) { tpr->eta = (1000.0 / final_ias) + ((dist_to_go_m - 1000.0) / app_ias); } } else if(tpr->opType == CIRCUIT || tpr->opType == TTT_UNKNOWN) { // Hack alert - UNKNOWN has sort of been added here as a temporary hack. + //cout << "CASE 2\n"; // It's complicated - depends on if base leg is delayed or not //if(printout) { - // cout << "Leg = " << tpr->leg << '\n'; + //cout << "Leg = " << tpr->leg << '\n'; //} if(tpr->leg == LANDING_ROLL) { tpr->eta = 0; } else if((tpr->leg == FINAL) || (tpr->leg == TURN4)) { + //cout << "dist_out_m = " << dist_out_m << '\n'; tpr->eta = fabs(dist_out_m) / final_ias; } else if((tpr->leg == BASE) || (tpr->leg == TURN3)) { tpr->eta = (fabs(dist_out_m) / final_ias) + (dist_across_m / circuit_ias); @@ -1289,7 +1561,7 @@ void FGTower::CalcETA(TowerPlaneRec* tpr, bool printout) { if(!GetDownwindConstraint(current_dist_across_m)) { current_dist_across_m = nominal_dist_across_m; } - double nominal_cross_dist_out_m = 2000; // Bit of a guess - AI plane turns to crosswind at 600ft agl. + double nominal_cross_dist_out_m = 2000; // Bit of a guess - AI plane turns to crosswind at 700ft agl. tpr->eta = fabs(current_base_dist_out_m) / final_ias; // final //cout << "a = " << tpr->eta << '\n'; if((tpr->leg == DOWNWIND) || (tpr->leg == TURN2)) { @@ -1298,24 +1570,35 @@ void FGTower::CalcETA(TowerPlaneRec* tpr, bool printout) { tpr->eta += fabs(current_base_dist_out_m - dist_out_m) / circuit_ias; //cout << "c = " << tpr->eta << '\n'; } else if((tpr->leg == CROSSWIND) || (tpr->leg == TURN1)) { + //cout << "CROSSWIND calc: "; + //cout << tpr->eta << ' '; if(dist_across_m > nominal_dist_across_m) { tpr->eta += dist_across_m / circuit_ias; + //cout << "a "; } else { tpr->eta += nominal_dist_across_m / circuit_ias; + //cout << "b "; } + //cout << tpr->eta << ' '; // should we use the dist across of the previous plane if there is previous still on downwind? //if(printout) cout << "bb = " << tpr->eta << '\n'; if(dist_out_m > nominal_cross_dist_out_m) { tpr->eta += fabs(current_base_dist_out_m - dist_out_m) / circuit_ias; + //cout << "c "; } else { tpr->eta += fabs(current_base_dist_out_m - nominal_cross_dist_out_m) / circuit_ias; + //cout << "d "; } + //cout << tpr->eta << ' '; //if(printout) cout << "cc = " << tpr->eta << '\n'; if(nominal_dist_across_m > dist_across_m) { tpr->eta += (nominal_dist_across_m - dist_across_m) / circuit_ias; + //cout << "e "; } else { // Nothing to add + //cout << "f "; } + //cout << tpr->eta << '\n'; //if(printout) cout << "dd = " << tpr->eta << '\n'; } else { // We've only just started - why not use a generic estimate? @@ -1346,18 +1629,23 @@ double FGTower::CalcDistOutMiles(TowerPlaneRec* tpr) { } -// Iterate through all the lists and call CalcETA for all the planes. +// Iterate through all the lists, update the position of, and call CalcETA for all the planes. void FGTower::doThresholdETACalc() { //cout << "Entering doThresholdETACalc..." << endl; tower_plane_rec_list_iterator twrItr; // Do the approach list first for(twrItr = appList.begin(); twrItr != appList.end(); twrItr++) { TowerPlaneRec* tpr = *twrItr; + if(!(tpr->isUser)) tpr->pos = tpr->planePtr->GetPos(); + //cout << "APP: "; CalcETA(tpr); } // Then the circuit list + //cout << "Circuit list size is " << circuitList.size() << '\n'; for(twrItr = circuitList.begin(); twrItr != circuitList.end(); twrItr++) { TowerPlaneRec* tpr = *twrItr; + if(!(tpr->isUser)) tpr->pos = tpr->planePtr->GetPos(); + //cout << "CIRC: "; CalcETA(tpr); } //cout << "Done doThresholdETCCalc" << endl; @@ -1480,7 +1768,9 @@ void FGTower::RegisterAIPlane(PlaneRec plane, FGAIPlane* ai, tower_traffic_type CalcETA(t); if(op == CIRCUIT && lg != LEG_UNKNOWN) { + cout << "AAAAAAAAAAAAAAAaa" << endl; AddToCircuitList(t); + cout << "BBBBBBBBBBBBBBbbb" << endl; } else { // FLAG A WARNING } @@ -1488,12 +1778,16 @@ void FGTower::RegisterAIPlane(PlaneRec plane, FGAIPlane* ai, tower_traffic_type doThresholdUseOrder(); } +void FGTower::DeregisterAIPlane(string id) { + RemovePlane(id); +} + // Contact tower for VFR approach // eg "Cessna Charlie Foxtrot Golf Foxtrot Sierra eight miles South of the airport for full stop with Bravo" // This function probably only called via user interaction - AI planes will have an overloaded function taking a planerec. // opt defaults to AIP_LT_UNKNOWN void FGTower::VFRArrivalContact(string ID, LandingType opt) { - //cout << "Request Landing Clearance called...\n"; + //cout << "USER Request Landing Clearance called for ID " << ID << '\n'; // For now we'll assume that the user is a light plane and can get him/her to join the circuit if necessary. @@ -1542,11 +1836,38 @@ void FGTower::VFRArrivalContact(string ID, LandingType opt) { current_atcdialog->remove_entry(ident, USER_REQUEST_VFR_ARRIVAL_TOUCH_AND_GO, TOWER); } +// landingType defaults to AIP_LT_UNKNOWN +void FGTower::VFRArrivalContact(PlaneRec plane, FGAIPlane* requestee, LandingType lt) { + //cout << "VFRArrivalContact called for plane " << plane.callsign << " at " << ident << '\n'; + // Possible hack - assume this plane is new for now - TODO - should check really + TowerPlaneRec* t = new TowerPlaneRec; + t->plane = plane; + t->planePtr = requestee; + t->landingType = lt; + t->pos = requestee->GetPos(); + + //cout << "Hold Short reported by " << plane.callsign << '\n'; + SG_LOG(SG_ATC, SG_BULK, "VFR arrival contact made by " << plane.callsign); + //cout << "VFR arrival contact made by " << plane.callsign << '\n'; + + // HACK - to get up and running I'm going to assume a staight-in final for now. + t->opType = STRAIGHT_IN; + + t->vfrArrivalReported = true; + responseReqd = true; + + //cout << "Before adding, appList.size = " << appList.size() << " at " << ident << '\n'; + appList.push_back(t); // Not necessarily permanent + //cout << "After adding, appList.size = " << appList.size() << " at " << ident << '\n'; + AddToTrafficList(t); +} + void FGTower::RequestDepartureClearance(string ID) { //cout << "Request Departure Clearance called...\n"; } void FGTower::ReportFinal(string ID) { + //cout << "Report Final Called at tower " << ident << " by plane " << ID << '\n'; if(ID == "USER") { ID = fgGetString("/sim/user/callsign"); current_atcdialog->remove_entry(ident, USER_REPORT_3_MILE_FINAL, TOWER); @@ -1557,7 +1878,12 @@ void FGTower::ReportFinal(string ID) { t->finalAcknowledged = false; if(!(t->clearedToLand)) { responseReqd = true; - } // possibly respond with wind even if already cleared to land? + } else { + // possibly respond with wind even if already cleared to land? + t->finalReported = false; + t->finalAcknowledged = true; + // HACK!! - prevents next reporting being misinterpreted as this one. + } } else { SG_LOG(SG_ATC, SG_WARN, "WARNING: Unable to find plane " << ID << " in FGTower::ReportFinal(...)"); } @@ -1586,38 +1912,115 @@ void FGTower::ReportLongFinal(string ID) { //void FGTower::ReportGoingAround(string ID); void FGTower::ReportRunwayVacated(string ID) { - //cout << "Report Runway Vacated Called...\n"; + //cout << "Report Runway Vacated Called at tower " << ident << " by plane " << ID << '\n'; if(ID == "USER") { ID = fgGetString("/sim/user/callsign"); current_atcdialog->remove_entry(ident, USER_REPORT_RWY_VACATED, TOWER); } TowerPlaneRec* t = FindPlane(ID); if(t) { + //cout << "Found it...\n"; t->rwyVacatedReported = true; responseReqd = true; } else { SG_LOG(SG_ATC, SG_WARN, "WARNING: Unable to find plane " << ID << " in FGTower::ReportRunwayVacated(...)"); + SG_LOG(SG_ATC, SG_ALERT, "A WARNING: Unable to find plane " << ID << " in FGTower::ReportRunwayVacated(...)"); + //cout << "WARNING: Unable to find plane " << ID << " in FGTower::ReportRunwayVacated(...)\n"; } } TowerPlaneRec* FGTower::FindPlane(string ID) { + //cout << "FindPlane called for " << ID << "...\n"; tower_plane_rec_list_iterator twrItr; // Do the approach list first for(twrItr = appList.begin(); twrItr != appList.end(); twrItr++) { + //cout << "appList callsign is " << (*twrItr)->plane.callsign << '\n'; if((*twrItr)->plane.callsign == ID) return(*twrItr); } // Then the circuit list for(twrItr = circuitList.begin(); twrItr != circuitList.end(); twrItr++) { + //cout << "circuitList callsign is " << (*twrItr)->plane.callsign << '\n'; if((*twrItr)->plane.callsign == ID) return(*twrItr); } - // And finally the hold list + // Then the runway list + //cout << "rwyList.size() is " << rwyList.size() << '\n'; + for(twrItr = rwyList.begin(); twrItr != rwyList.end(); twrItr++) { + //cout << "rwyList callsign is " << (*twrItr)->plane.callsign << '\n'; + if((*twrItr)->plane.callsign == ID) return(*twrItr); + } + // The hold list for(twrItr = holdList.begin(); twrItr != holdList.end(); twrItr++) { if((*twrItr)->plane.callsign == ID) return(*twrItr); } + // And finally the vacated list + for(twrItr = vacatedList.begin(); twrItr != vacatedList.end(); twrItr++) { + //cout << "vacatedList callsign is " << (*twrItr)->plane.callsign << '\n'; + if((*twrItr)->plane.callsign == ID) return(*twrItr); + } SG_LOG(SG_ATC, SG_WARN, "Unable to find " << ID << " in FGTower::FindPlane(...)"); + //exit(-1); return(NULL); } +void FGTower::RemovePlane(string ID) { + //cout << ident << " RemovePlane called for " << ID << '\n'; + // We have to be careful here - we want to erase the plane from all lists it is in, + // but we can only delete it once, AT THE END. + TowerPlaneRec* t = NULL; + tower_plane_rec_list_iterator twrItr; + for(twrItr = appList.begin(); twrItr != appList.end(); twrItr++) { + if((*twrItr)->plane.callsign == ID) { + t = *twrItr; + twrItr = appList.erase(twrItr); + appListItr = appList.begin(); + } + } + for(twrItr = depList.begin(); twrItr != depList.end(); twrItr++) { + if((*twrItr)->plane.callsign == ID) { + t = *twrItr; + twrItr = depList.erase(twrItr); + depListItr = depList.begin(); + } + } + for(twrItr = circuitList.begin(); twrItr != circuitList.end(); twrItr++) { + if((*twrItr)->plane.callsign == ID) { + t = *twrItr; + twrItr = circuitList.erase(twrItr); + circuitListItr = circuitList.begin(); + } + } + for(twrItr = holdList.begin(); twrItr != holdList.end(); twrItr++) { + if((*twrItr)->plane.callsign == ID) { + t = *twrItr; + twrItr = holdList.erase(twrItr); + holdListItr = holdList.begin(); + } + } + for(twrItr = rwyList.begin(); twrItr != rwyList.end(); twrItr++) { + if((*twrItr)->plane.callsign == ID) { + t = *twrItr; + twrItr = rwyList.erase(twrItr); + rwyListItr = rwyList.begin(); + } + } + for(twrItr = vacatedList.begin(); twrItr != vacatedList.end(); twrItr++) { + if((*twrItr)->plane.callsign == ID) { + t = *twrItr; + twrItr = vacatedList.erase(twrItr); + vacatedListItr = vacatedList.begin(); + } + } + for(twrItr = trafficList.begin(); twrItr != trafficList.end(); twrItr++) { + if((*twrItr)->plane.callsign == ID) { + t = *twrItr; + twrItr = trafficList.erase(twrItr); + trafficListItr = trafficList.begin(); + } + } + // And finally, delete the record if we found it. + if(t) delete t; +} + void FGTower::ReportDownwind(string ID) { //cout << "ReportDownwind(...) called\n"; if(ID == "USER") { @@ -1628,6 +2031,16 @@ void FGTower::ReportDownwind(string ID) { if(t) { t->downwindReported = true; responseReqd = true; + // If the plane is in the app list, remove it and put it in the circuit list instead. + // Ideally we might want to do this at the 2 mile report prior to 45 deg entry, but at + // the moment that would b&gg?r up the constraint position calculations. + RemoveFromAppList(ID); + t->leg = DOWNWIND; + t->pos = t->planePtr->GetPos(); + CalcETA(t); + //cout << "DDDDDDDDDDDDDDDDdddddddd" << endl; + AddToCircuitList(t); + //cout << "EEEEEEEEEEEEEEEEEEEEeeeeee" << endl; } else { SG_LOG(SG_ATC, SG_WARN, "WARNING: Unable to find plane " << ID << " in FGTower::ReportDownwind(...)"); } @@ -1730,7 +2143,7 @@ string FGTower::GenText(const string& m, int c) { else if ( strcmp ( tag, "@MI" ) == 0 ) { char buf[10]; //sprintf( buf, "%3.1f", tpars.miles ); - int dist_miles = dclGetHorizontalSeparation(Point3D(lon, lat, elev), Point3D(user_lon_node->getDoubleValue(), user_lat_node->getDoubleValue(), user_elev_node->getDoubleValue())) / 1600; + int dist_miles = (int)dclGetHorizontalSeparation(Point3D(lon, lat, elev), Point3D(user_lon_node->getDoubleValue(), user_lat_node->getDoubleValue(), user_elev_node->getDoubleValue())) / 1600; sprintf(buf, "%i", dist_miles); strcat( &dum[0], &buf[0] ); } diff --git a/src/ATC/tower.hxx b/src/ATC/tower.hxx index f06faeb56..78e43baa3 100644 --- a/src/ATC/tower.hxx +++ b/src/ATC/tower.hxx @@ -60,7 +60,8 @@ enum tower_callback_type { USER_REQUEST_VFR_ARRIVAL_TOUCH_AND_GO = 4, USER_REPORT_3_MILE_FINAL = 5, USER_REPORT_DOWNWIND = 6, - USER_REPORT_RWY_VACATED = 7 + USER_REPORT_RWY_VACATED = 7, + USER_REPORT_GOING_AROUND = 8 }; // TODO - need some differentiation of IFR and VFR traffic in order to give the former priority. @@ -135,6 +136,8 @@ public: // eg "Cessna Charlie Foxtrot Golf Foxtrot Sierra eight miles South of the airport for full stop with Bravo" // This function probably only called via user interaction - AI planes will have an overloaded function taking a planerec. void VFRArrivalContact(string ID, LandingType opt = AIP_LT_UNKNOWN); + // For the AI planes... + void VFRArrivalContact(PlaneRec plane, FGAIPlane* requestee, LandingType lt = AIP_LT_UNKNOWN); void RequestDepartureClearance(string ID); void ReportFinal(string ID); @@ -154,6 +157,9 @@ public: // CAUTION - currently it is assumed that this plane's callsign is unique - it is up to AIMgr to generate unique callsigns. void RegisterAIPlane(PlaneRec plane, FGAIPlane* ai, tower_traffic_type op, PatternLeg lg = LEG_UNKNOWN); + // Deregister and remove an AI plane. + void DeregisterAIPlane(string id); + // Public interface to the active runway - this will get more complex // in the future and consider multi-runway use, airplane weight etc. inline string GetActiveRunway() { return activeRwy; } @@ -165,7 +171,6 @@ public: inline void SetNoDisplay() { display = false; } inline string get_trans_ident() { return trans_ident; } - inline atc_type GetType() { return TOWER; } inline FGGround* GetGroundPtr() { return ground; } @@ -184,13 +189,17 @@ private: // Respond to a transmission void Respond(); + void ProcessRunwayVacatedReport(TowerPlaneRec* t); + + // Remove all options from the user dialog choice + void RemoveAllUserDialogOptions(); + + // Periodic checks on the various traffic. void CheckHoldList(double dt); - void CheckCircuitList(double dt); - void CheckRunwayList(double dt); - void CheckApproachList(double dt); + void CheckDepartureList(double dt); // Currently this assumes we *are* next on the runway and doesn't check for planes about to land - // this should be done prior to calling this function. @@ -199,6 +208,9 @@ private: // Find a pointer to plane of callsign ID within the internal data structures TowerPlaneRec* FindPlane(string ID); + // Remove and delete all instances of a plane with a given ID + void RemovePlane(string ID); + // Figure out if a given position lies on the active runway // Might have to change when we consider more than one active rwy. bool OnActiveRunway(Point3D pt); @@ -292,8 +304,14 @@ private: tower_plane_rec_list_type trafficList; // TODO - needs to be expandable to more than one rwy tower_plane_rec_list_iterator trafficListItr; + // List of planes that have vacated the runway inbound but not yet handed off to ground + tower_plane_rec_list_type vacatedList; + tower_plane_rec_list_iterator vacatedListItr; + // Returns true if successful bool RemoveFromTrafficList(string id); + bool RemoveFromAppList(string id); + bool RemoveFromRwyList(string id); // Return the ETA of plane no. list_pos (1-based) in the traffic list. // i.e. list_pos = 1 implies next to use runway. @@ -311,6 +329,9 @@ private: bool separateGround; // true if ground control is separate FGGround* ground; // The ground control associated with this airport. + bool _departureControlled; // true if we need to hand off departing traffic to departure control + //FGDeparture* _departure; // The relevant departure control (once we've actually written it!) + // for failure modeling string trans_ident; // transmitted ident bool tower_failed; // tower failed?