1
0
Fork 0

Lots of changes to the ATC/AI system for initial revision of random AI GA VFR traffic

This commit is contained in:
daveluff 2004-01-23 17:18:24 +00:00
parent afb654a068
commit a739fad664
25 changed files with 1718 additions and 441 deletions

View file

@ -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() );
}

View file

@ -41,8 +41,12 @@ 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();
};

File diff suppressed because it is too large Load diff

View file

@ -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,9 +96,38 @@ protected:
// Attempt to enter the traffic pattern in a reasonably intelligent manner
void EnterTrafficPattern(double dt);
// Set up the internal state to be consistent for a downwind entry.
void DownwindEntry();
// Ditto for straight-in
void StraightInEntry(bool des = false);
// 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!
@ -105,21 +136,7 @@ private:
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
// 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.
// 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.
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.
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;
@ -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

View file

@ -23,6 +23,7 @@
#include <Main/fg_props.hxx>
#include <Main/globals.hxx>
#include <simgear/math/sg_random.h>
#include <list>
@ -33,9 +34,18 @@
# include <dirent.h> // for directory reading
#endif
#ifdef FG_WEATHERCM
# include <WeatherCM/FGLocalWeatherDatabase.h>
#else
# include <Environment/environment_mgr.hxx>
# include <Environment/environment.hxx>
#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,6 +74,20 @@ 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/");
@ -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);
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; i<j; ++i) {
int n = int(sg_random() * 10);
if(n == 10) --n;
s += (char)('0' + n);
}
for(int i=j; i<5; ++i) {
int n = int(sg_random() * 26);
if(n == 26) --n;
//cout << "Alpha, n = " << n << '\n';
s += (char)('A' + n);
}
//cout << "s = " << s << '\n';
return(s);
}
string FGAIMgr::GenerateUniqueCallsign() {
while(1) {
string s = GenerateCallsign();
if(!ai_callsigns_used[s]) {
ai_callsigns_used[s] = 1;
return(s);
}
}
}
// This will be moved somewhere else eventually!!!!
string FGAIMgr::GenerateShortForm(string callsign, string plane_str, bool local) {
//cout << callsign << '\n';
string s;
if(local) s = "Trainer-";
else s = plane_str;
for(int i=3; i>0; --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);
}

View file

@ -54,20 +54,33 @@ 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;
double lat;
@ -90,19 +103,35 @@ public:
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
// Remove a class from the ai_list and delete it from memory
//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);
};
#endif // _FG_AIMGR_HXX

View file

@ -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));
}
}

View file

@ -84,6 +84,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;

View file

@ -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;

View file

@ -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&

View file

@ -397,7 +397,12 @@ void FGATCDialog::PopupCallback() {
break;
}
} else if(atcptr->GetType() == TOWER) {
ATCMenuEntry a = ((available_dialog[TOWER])[(string)atcptr->get_ident()])[atcDialogCommunicationOptions->getValue()];
//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;
@ -405,6 +410,10 @@ void FGATCDialog::PopupCallback() {
_callbackWait = 5.0;
_callbackPtr = atcptr;
_callbackCode = a.callback_code;
} else {
//cout << "No options available...\n";
}
//cout << "Donded" << endl;
}
}
}

View file

@ -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;

View file

@ -48,6 +48,7 @@ AirportATC::AirportATC() :
ground_active(false),
set_by_AI(false),
numAI(0)
//airport_atc_map.clear();
{
for(int i=0; i<ATC_NUM_TYPES; ++i) {
set_by_comm[0][i] = false;
@ -58,9 +59,9 @@ AirportATC::AirportATC() :
FGATCMgr::FGATCMgr() {
comm_ident[0] = "";
comm_ident[1] = "";
last_comm_ident[0] = "";
last_comm_ident[1] = "";
approach_ident = "";
//last_comm_ident[0] = "";
//last_comm_ident[1] = "";
//approach_ident = "";
last_in_range = false;
comm_type[0] = INVALID;
comm_type[1] = INVALID;
@ -83,6 +84,8 @@ void FGATCMgr::unbind() {
}
void FGATCMgr::init() {
//cout << "ATCMgr::init called..." << endl;
comm_node[0] = fgGetNode("/radios/comm[0]/frequencies/selected-mhz", true);
comm_node[1] = fgGetNode("/radios/comm[1]/frequencies/selected-mhz", true);
lon_node = fgGetNode("/position/longitude-deg", true);
@ -135,6 +138,7 @@ void FGATCMgr::init() {
current_atcdialog->Init();
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) ) {
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";
(*atc_list_itr)->SetNoDisplay();
(*atc_list_itr)->Update(0.00833);
delete (*atc_list_itr);
atc_list_itr = atc_list.erase(atc_list_itr);
(*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;
} // Note that that can upset where we are in the list but that doesn't really matter
++atc_list_itr;
}
++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...");
//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...");
//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;
}
}

View file

@ -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);

View file

@ -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) {

View file

@ -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

View file

@ -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

View file

@ -43,6 +43,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);
lat_node = fgGetNode("/position/latitude-deg", true);

View file

@ -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:

View file

@ -64,6 +64,7 @@ FGATIS::FGATIS() :
{
vPtr = globals->get_ATC_mgr()->GetVoicePointer(ATIS);
voiceOK = (vPtr == NULL ? false : true);
_type = ATIS;
}
FGATIS::~FGATIS() {

View file

@ -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; }

View file

@ -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);

View file

@ -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

View file

@ -46,6 +46,7 @@ SG_USING_STD(cout);
// TowerPlaneRec
TowerPlaneRec::TowerPlaneRec() :
planePtr(NULL),
clearedToLand(false),
clearedToLineUp(false),
clearedToTakeOff(false),
@ -71,6 +72,7 @@ TowerPlaneRec::TowerPlaneRec() :
}
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.
Currently user is assumed to have taken off again when leaving the runway - check speed/elev for taxiing-in. (MAJOR)
Tell AI plane to contact ground when taxiing in.
Use track instead of heading to determine what leg of the circuit the user is flying. (MINOR)
Use track instead of heading to determine what leg of the circuit the user is flying.
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)
Implement ReportRunwayVacated
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).
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() {
}
}
// Get the airport elevation
aptElev = dclGetAirportElev(ident.c_str()) * SG_FEET_TO_METER;
// 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());
// 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,10 +308,13 @@ 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.
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);
}
}
}
void FGTower::Update(double dt) {
@ -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 ";
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 ";
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,6 +590,19 @@ void FGTower::Respond() {
}
t->finalAcknowledged = true;
} else if(t->rwyVacatedReported && !(t->rwyVacatedAcknowledged)) {
ProcessRunwayVacatedReport(t);
t->rwyVacatedAcknowledged = true;
}
}
//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 ";
@ -537,19 +611,17 @@ void FGTower::Respond() {
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);
}
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;
}
// 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();
}
@ -852,9 +933,12 @@ void FGTower::CheckCircuitList(double dt) {
}
}
if(t->leg == FINAL) {
if(t->landingType == FULL_STOP) t->opType = INBOUND;
if(t->eta < 12 && rwyList.size() && !(t->instructedToGoAround)) {
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!!].
@ -865,12 +949,18 @@ void FGTower::CheckCircuitList(double dt) {
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";
//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,12 +1042,15 @@ 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());
@ -955,12 +1058,123 @@ void FGTower::CheckApproachList(double dt) {
} else {
// TODO - set/update the position if it's an AI plane
}
if(t->nextOnRwy && !(t->clearedToLand)) {
//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) && !(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,15 +1446,35 @@ 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++) {
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.
@ -1222,6 +1490,7 @@ bool FGTower::AddToCircuitList(TowerPlaneRec* t) {
}
}
// 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] );
}

View file

@ -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?