descending = false;
targetDescentRate = 0.0;
+ goAround = false;
+ goAroundCalled = false;
}
FGAILocalTraffic::~FGAILocalTraffic() {
} else {
//cout << "Unable to find airport details in FGAILocalTraffic::Init()\n";
}
+
+ // Get the active runway details (and copy them into rwy)
+ GetRwyDetails();
// Get the airport elevation
aptElev = dclGetAirportElev(airportID.c_str()) * SG_FEET_TO_METER;
// since we've got the implementation for this case already.
// TODO - implement proper generic in_pattern startup.
- tuned_station = tower;
-
- // Get the active runway details (and copy them into rwy)
- GetRwyDetails();
-
- // Initial position on threshold for now
- pos.setlat(rwy.threshold_pos.lat());
- pos.setlon(rwy.threshold_pos.lon());
- pos.setelev(rwy.threshold_pos.elev());
- hdg = rwy.hdg;
+ // 18/10/03 - adding the ability to start on downwind (mainly to speed testing of the go-around code!!)
- // Now we've set the position we can do the ground elev
- // This might not always be necessary if we implement in-air start
- elevInitGood = false;
- inAir = false;
- DoGroundElev();
+ //cout << "Starting in pattern...\n";
- pitch = 0.0;
- roll = 0.0;
- leg = TAKEOFF_ROLL;
- vel = 0.0;
- slope = 0.0;
+ tuned_station = tower;
circuitsToFly = 0; // ie just fly this circuit and then stop
touchAndGo = false;
if(rwy.rwyID.size() == 3) {
patternDirection = (rwy.rwyID.substr(2,1) == "R" ? 1 : -1);
}
-
+
+ if(initialLeg == DOWNWIND) {
+ pos = ortho.ConvertFromLocal(Point3D(1000*patternDirection, 800, 0.0));
+ pos.setelev(rwy.threshold_pos.elev() + 1000 * SG_FEET_TO_METER);
+ hdg = rwy.hdg + 180.0;
+ leg = DOWNWIND;
+ elevInitGood = false;
+ inAir = true;
+ track = rwy.hdg - (180 * patternDirection); //should tend to bring track back into the 0->360 range
+ slope = 0.0;
+ pitch = 0.0;
+ roll = 0.0;
+ IAS = 90.0;
+ descending = false;
+ aip.setVisible(true);
+ tower->RegisterAIPlane(plane, this, CIRCUIT, DOWNWIND);
+ } else {
+ // Default to initial position on threshold for now
+ pos.setlat(rwy.threshold_pos.lat());
+ pos.setlon(rwy.threshold_pos.lon());
+ pos.setelev(rwy.threshold_pos.elev());
+ hdg = rwy.hdg;
+
+ // Now we've set the position we can do the ground elev
+ // This might not always be necessary if we implement in-air start
+ elevInitGood = false;
+ inAir = false;
+ DoGroundElev();
+
+ pitch = 0.0;
+ roll = 0.0;
+ leg = TAKEOFF_ROLL;
+ vel = 0.0;
+ slope = 0.0;
+ }
+
operatingState = IN_PATTERN;
Transform();
switch(operatingState) {
case IN_PATTERN:
//cout << "In IN_PATTERN\n";
- if(!inAir) DoGroundElev();
- if(!elevInitGood) {
- if(aip.getSGLocation()->get_cur_elev_m() > -9990.0) {
- pos.setelev(aip.getSGLocation()->get_cur_elev_m() + wheelOffset);
- //cout << "TAKEOFF_ROLL, POS = " << pos.lon() << ", " << pos.lat() << ", " << pos.elev() << '\n';
- //Transform();
- aip.setVisible(true);
- //cout << "Making plane visible!\n";
- elevInitGood = true;
+ if(!inAir) {
+ DoGroundElev();
+ if(!elevInitGood) {
+ if(aip.getSGLocation()->get_cur_elev_m() > -9990.0) {
+ pos.setelev(aip.getSGLocation()->get_cur_elev_m() + wheelOffset);
+ //cout << "TAKEOFF_ROLL, POS = " << pos.lon() << ", " << pos.lat() << ", " << pos.elev() << '\n';
+ //Transform();
+ aip.setVisible(true);
+ //cout << "Making plane visible!\n";
+ elevInitGood = true;
+ }
}
}
FlyTrafficPattern(dt);
if(circuitsToFly) {
if((taxiRequestPending) && (taxiRequestCleared)) {
//cout << "&" << flush;
- // Get the active runway details (and copy them into rwy)
+ // Get the active runway details (in case they've changed since init)
GetRwyDetails();
// Get the takeoff node for the active runway, get a path to it and start taxiing
clearedToTakeOff = true;
SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " cleared to take-off...");
break;
-// case 13: // Go around!
-// responseCounter = 0;
-// goAround = true;
-// SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " told to go-around!!");
-// break;
+ case 13: // Go around!
+ responseCounter = 0;
+ goAround = true;
+ SG_LOG(SG_ATC, SG_INFO, "AI local traffic " << plane.callsign << " told to go-around!!");
+ break;
default:
break;
}
break;
case CLIMBOUT:
track = rwy.hdg;
- // Turn to crosswind if above 600ft AND if other traffic allows
+ // Turn to crosswind if above 700ft AND if other traffic allows
// (decided in FGTower and accessed through GetCrosswindConstraint(...)).
- if((pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET > 600) {
+ // According to AIM, traffic should climb to within 300ft of pattern altitude before commencing crosswind turn.
+ // TODO - At hot 'n high airports this may be 500ft AGL though - need to make this a variable.
+ if((pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET > 700) {
double cc = 0.0;
if(tower->GetCrosswindConstraint(cc)) {
if(orthopos.y() > cc) {
leg = TURN1;
}
} else if(orthopos.y() > 1500.0) { // Added this constraint as a hack to prevent turning too early when going around.
+ // TODO - We should be doing it as a distance from takeoff end, not theshold end though.
cout << "Turning to crosswind, distance from threshold = " << orthopos.y() << '\n';
leg = TURN1;
}
pitch = 0.0;
IAS = 80.0; // FIXME - use smooth transistion to new speed and attitude.
}
+ if(goAround && !goAroundCalled) {
+ if(responseCounter > 5.5) {
+ pending_transmission = plane.callsign;
+ pending_transmission += " going around";
+ Transmit();
+ goAroundCalled = true;
+ }
+ }
break;
case TURN1:
track += (360.0 / turn_time) * dt * patternDirection;
}
break;
case CROSSWIND:
+ goAround = false;
LevelWings();
track = rwy.hdg + (90.0 * patternDirection);
if((pos.elev() - rwy.threshold_pos.elev()) * SG_METER_TO_FEET > 1000) {
}
break;
case FINAL:
+ if(goAround && responseCounter > 2.0) {
+ leg = CLIMBOUT;
+ pitch = 8.0;
+ IAS = best_rate_of_climb_speed;
+ slope = 5.0; // A bit less steep than the initial climbout.
+ inAir = true;
+ goAroundCalled = false;
+ break;
+ }
LevelWings();
if(!transmitted) {
TransmitPatternPositionReport();
bool clearedToLineUp;
bool clearedToTakeOff;
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
longFinalAcknowledged(false),
finalReported(false),
finalAcknowledged(false),
+instructedToGoAround(false),
onRwy(false),
nextOnRwy(false),
opType(TTT_UNKNOWN),
longFinalAcknowledged(false),
finalReported(false),
finalAcknowledged(false),
+instructedToGoAround(false),
onRwy(false),
nextOnRwy(false),
opType(TTT_UNKNOWN),
longFinalAcknowledged(false),
finalReported(false),
finalAcknowledged(false),
+instructedToGoAround(false),
onRwy(false),
nextOnRwy(false),
opType(TTT_UNKNOWN),
longFinalAcknowledged(false),
finalReported(false),
finalAcknowledged(false),
+instructedToGoAround(false),
onRwy(false),
nextOnRwy(false),
opType(TTT_UNKNOWN),
// cout << " dt = " << dt << " timeSinceLastDeparture = " << timeSinceLastDeparture << '\n';
}
+ //cout << ident << " respond = " << respond << " responseReqd = " << responseReqd << '\n';
if(respond) {
if(!responseReqd) SG_LOG(SG_ATC, SG_ALERT, "ERROR - respond is true and responseReqd is false in FGTower::Update(...)");
Respond();
}
t->holdShortReported = false;
} else if(t->finalReported && !(t->finalAcknowledged)) {
+ bool disp = true;
string trns = t->plane.callsign;
if(t->nextOnRwy && !rwyOccupied) {
if(t->landingType == FULL_STOP) {
}
// TODO - add winds
t->clearedToLand = true;
+ } 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".
+ disp = false;
} else {
trns += " continue approach";
t->clearedToLand = false;
}
- if(display) {
+ if(display && disp) {
globals->get_ATC_display()->RegisterSingleMessage(trns, 0);
}
t->finalAcknowledged = true;
case CROSSWIND:
crosswind_leg_pos = tortho.y();
//cout << "crosswind_leg_pos = " << crosswind_leg_pos << '\n';
+ t->instructedToGoAround = false;
break;
case TURN1:
// Fall through to climbout
if(t->leg == FINAL) {
if(t->landingType == FULL_STOP) t->opType = INBOUND;
+ if(t->eta < 12 && rwyList.size() && !(t->instructedToGoAround)) {
+ // TODO - need to make this more sophisticated
+ // eg. is the plane accelerating down the runway taking off [OK],
+ // or stationary near the start [V. BAD!!].
+ // For now this should stop the AI plane landing on top of the user.
+ string trns = t->plane.callsign;
+ trns += " GO AROUND TRAFFIC ON RUNWAY I REPEAT GO AROUND";
+ if(display) {
+ globals->get_ATC_display()->RegisterSingleMessage(trns, 0);
+ }
+ t->instructedToGoAround = true;
+ if(t->planePtr) {
+ cout << "Registering Go-around transmission with AI plane\n";
+ t->planePtr->RegisterTransmission(13);
+ }
+ }
} else if(t->leg == LANDING_ROLL) {
rwyList.push_front(t);
// TODO - if(!clearedToLand) shout something!!
return(holding ? firstTime : conflict);
}
+// 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.
+bool FGTower::AddToCircuitList(TowerPlaneRec* t) {
+ //cout << "ADD: " << circuitList.size();
+ //cout << "AddToCircuitList called, 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;
+ for(twrItr = circuitList.begin(); twrItr != circuitList.end(); twrItr++) {
+ TowerPlaneRec* tpr = *twrItr;
+
+ if(t->eta < tpr->eta) {
+ // Ugg - this one's tricky.
+ // It depends on what the two planes are doing and whether there's a conflict what we do.
+ if(tpr->eta - t->eta > separation_time) { // No probs, plane 2 can squeeze in before plane 1 with no apparent conflict
+ circuitList.insert(twrItr, t);
+ } else { // Ooops - this ones tricky - we have a potential conflict!
+ conflict = true;
+ // HACK - just add anyway for now and flag conflict.
+ circuitList.insert(twrItr, t);
+ }
+ //cout << "\tC\t" << circuitList.size() << '\n';
+ return(conflict);
+ }
+ }
+ // If we get here we must be at the end of the list, or maybe the list is empty.
+ circuitList.push_back(t); // TODO - check the separation with the preceding plane for the conflict flag.
+ //cout << "\tE\t" << circuitList.size() << endl;
+ return(conflict);
+}
+
// Calculate the eta of a plane to the threshold.
// For ground traffic this is the fastest they can get there.
t->clearedToLineUp = false;
t->clearedToTakeOff = false;
t->opType = operation;
+ t->pos = requestee->GetPos();
//cout << "Hold Short reported by " << plane.callsign << '\n';
+ SG_LOG(SG_ATC, SG_BULK, "Hold Short reported by " << plane.callsign);
/*
bool next = AddToTrafficList(t, true);
responseReqd = true;
}
+// Register the presence of an AI plane at a point where contact would already have been made in real life
+// CAUTION - currently it is assumed that this plane's callsign is unique - it is up to AIMgr to generate unique callsigns.
+void FGTower::RegisterAIPlane(PlaneRec plane, FGAIPlane* ai, tower_traffic_type op, PatternLeg lg) {
+ // At the moment this is only going to be tested with inserting an AI plane on downwind
+ TowerPlaneRec* t = new TowerPlaneRec;
+ t->plane = plane;
+ t->planePtr = ai;
+ t->opType = op;
+ t->leg = lg;
+ t->pos = ai->GetPos();
+
+ CalcETA(t);
+
+ if(op == CIRCUIT && lg != LEG_UNKNOWN) {
+ AddToCircuitList(t);
+ } else {
+ // FLAG A WARNING
+ }
+
+ doThresholdUseOrder();
+}
+
void FGTower::RequestLandingClearance(string ID) {
//cout << "Request Landing Clearance called...\n";
bool longFinalAcknowledged;
bool finalReported;
bool finalAcknowledged;
+ bool instructedToGoAround; // set true if told by tower to go around
bool onRwy; // is physically on the runway
bool nextOnRwy; // currently projected by tower to be the next on the runway
// Contact tower when at a hold short for departure - for now we'll assume plane - maybe vehicles might want to cross runway eventually?
void ContactAtHoldShort(PlaneRec plane, FGAIPlane* requestee, tower_traffic_type operation);
+ // Register the presence of an AI plane at a point where contact would already have been made in real life
+ // 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);
+
// 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; }
// Add a tower plane rec with ETA to the traffic list in the correct position ETA-wise.
// Returns true if this could cause a threshold ETA conflict with other traffic, false otherwise.
bool AddToTrafficList(TowerPlaneRec* t, bool holding = false);
+
+ bool AddToCircuitList(TowerPlaneRec* t);
// Ground can be separate or handled by tower in real life.
// In the program we will always use a separate FGGround class, but we need to know