Progress towards AI/ATC communication without speaking all at once
This commit is contained in:
parent
ea0751eaa2
commit
380c69c8a0
7 changed files with 263 additions and 90 deletions
|
@ -319,7 +319,8 @@ void FGAILocalTraffic::Update(double dt) {
|
|||
trns += buf;
|
||||
trns += " ";
|
||||
trns += plane.callsign;
|
||||
Transmit(trns);
|
||||
pending_transmission = trns;
|
||||
Transmit(30.0);
|
||||
responseCounter = 0.0;
|
||||
contactTower = false;
|
||||
changeFreq = true;
|
||||
|
@ -414,7 +415,8 @@ void FGAILocalTraffic::Update(double dt) {
|
|||
holdingShort = false;
|
||||
string trns = "Cleared for take-off ";
|
||||
trns += plane.callsign;
|
||||
Transmit(trns);
|
||||
pending_transmission = trns;
|
||||
Transmit();
|
||||
StartTaxi();
|
||||
}
|
||||
//cout << "^" << flush;
|
||||
|
@ -476,7 +478,8 @@ void FGAILocalTraffic::Update(double dt) {
|
|||
trns += plane.callsign;
|
||||
trns += " on apron parking request taxi for traffic pattern";
|
||||
//cout << "trns = " << trns << endl;
|
||||
Transmit(trns);
|
||||
pending_transmission = trns;
|
||||
Transmit();
|
||||
taxiRequestCleared = false;
|
||||
taxiRequestPending = true;
|
||||
}
|
||||
|
@ -504,6 +507,9 @@ void FGAILocalTraffic::Update(double dt) {
|
|||
fgSetDouble("/AI/Local1/ortho-x", (ortho.ConvertToLocal(pos)).x());
|
||||
fgSetDouble("/AI/Local1/ortho-y", (ortho.ConvertToLocal(pos)).y());
|
||||
fgSetDouble("/AI/Local1/elev", pos.elev() * SG_METER_TO_FEET);
|
||||
|
||||
// And finally, call parent for transmission rendering
|
||||
FGAIPlane::Update(dt);
|
||||
}
|
||||
|
||||
void FGAILocalTraffic::RegisterTransmission(int code) {
|
||||
|
@ -947,7 +953,8 @@ void FGAILocalTraffic::TransmitPatternPositionReport(void) {
|
|||
// And add the airport name again
|
||||
trns += tower->get_name();
|
||||
|
||||
Transmit(trns);
|
||||
pending_transmission = trns; // FIXME - make up pending_transmission natively
|
||||
Transmit(90.0); // Assume a report of this leg will be invalid if we can't transmit within a minute and a half.
|
||||
}
|
||||
|
||||
void FGAILocalTraffic::ExitRunway(Point3D orthopos) {
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
#include <Main/fg_props.hxx>
|
||||
#include <simgear/math/point3d.hxx>
|
||||
#include <simgear/debug/logstream.hxx>
|
||||
#include <simgear/sound/soundmgr.hxx>
|
||||
#include <math.h>
|
||||
#include <string>
|
||||
SG_USING_STD(string);
|
||||
|
@ -32,12 +33,73 @@ SG_USING_STD(string);
|
|||
|
||||
FGAIPlane::FGAIPlane() {
|
||||
leg = LEG_UNKNOWN;
|
||||
tuned_station = NULL;
|
||||
pending_transmission = "";
|
||||
_timeout = 0;
|
||||
_pending = false;
|
||||
_callback = NULL;
|
||||
_transmit = false;
|
||||
_transmitting = false;
|
||||
voice = false;
|
||||
playing = false;
|
||||
voiceOK = false;
|
||||
vPtr = NULL;
|
||||
}
|
||||
|
||||
FGAIPlane::~FGAIPlane() {
|
||||
}
|
||||
|
||||
void FGAIPlane::Update(double dt) {
|
||||
if(_pending) {
|
||||
if(tuned_station) {
|
||||
if(tuned_station->FreqClear()) {
|
||||
_pending = false;
|
||||
_transmit = true;
|
||||
_transmitting = false;
|
||||
} else {
|
||||
if(_timeout > 0.0) { // allows count down to be avoided by initially setting it to zero
|
||||
_timeout -= dt;
|
||||
if(_timeout <= 0.0) {
|
||||
_timeout = 0.0;
|
||||
_pending = false;
|
||||
// timed out - don't render.
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Not tuned to ATC - Just go ahead and transmit
|
||||
_pending = false;
|
||||
_transmit = true;
|
||||
_transmitting = false;
|
||||
}
|
||||
}
|
||||
|
||||
// This turns on rendering if on the same freq as the user
|
||||
// TODO - turn it off if user switches to another freq - keep track of where in message we are etc.
|
||||
if(_transmit) {
|
||||
double user_freq0 = fgGetDouble("/radios/comm[0]/frequencies/selected-mhz");
|
||||
//comm1 is not used yet.
|
||||
_counter = 0.0;
|
||||
_max_count = 5.0; // FIXME - hardwired length of message - need to calculate it!
|
||||
|
||||
if(freq == user_freq0) {
|
||||
//cout << "Transmitting..." << endl;
|
||||
// we are on the same frequency, so check distance to the user plane
|
||||
if(1) {
|
||||
// For now assume in range !!!
|
||||
// TODO - implement range checking
|
||||
Render(plane.callsign, false);
|
||||
_transmit = false;
|
||||
_transmitting = true;
|
||||
}
|
||||
}
|
||||
} else if(_transmitting) {
|
||||
if(_counter >= _max_count) {
|
||||
NoRender(plane.callsign);
|
||||
_transmitting = false;
|
||||
}
|
||||
_counter += dt;
|
||||
}
|
||||
}
|
||||
|
||||
void FGAIPlane::Bank(double angle) {
|
||||
|
@ -55,23 +117,85 @@ void FGAIPlane::LevelWings(void) {
|
|||
}
|
||||
}
|
||||
|
||||
void FGAIPlane::Transmit(string msg) {
|
||||
SG_LOG(SG_ATC, SG_INFO, "Transmit called, msg = " << msg);
|
||||
double user_freq0 = fgGetDouble("/radios/comm[0]/frequencies/selected-mhz");
|
||||
//double user_freq0 = ("/radios/comm[0]/frequencies/selected-mhz");
|
||||
//comm1 is not used yet.
|
||||
|
||||
if(freq == user_freq0) {
|
||||
//cout << "Transmitting..." << endl;
|
||||
// we are on the same frequency, so check distance to the user plane
|
||||
if(1) {
|
||||
// For now (testing) assume in range !!!
|
||||
// TODO - implement range checking
|
||||
globals->get_ATC_display()->RegisterSingleMessage(msg, 0);
|
||||
}
|
||||
void FGAIPlane::Transmit(ai_plane_callback_t callback) {
|
||||
SG_LOG(SG_ATC, SG_INFO, "Transmit called for plane " << plane.callsign << ", msg = " << pending_transmission);
|
||||
_pending = true;
|
||||
_callback = callback;
|
||||
_timeout = 0.0;
|
||||
}
|
||||
|
||||
void FGAIPlane::Transmit(double timeout, ai_plane_callback_t callback) {
|
||||
SG_LOG(SG_ATC, SG_INFO, "Timed transmit called for plane " << plane.callsign << ", msg = " << pending_transmission);
|
||||
_pending = true;
|
||||
_callback = callback;
|
||||
_timeout = timeout;
|
||||
}
|
||||
|
||||
void FGAIPlane::ImmediateTransmit(ai_plane_callback_t callback) {
|
||||
Render(plane.callsign, false);
|
||||
if(_callback) {
|
||||
(*_callback)();
|
||||
}
|
||||
}
|
||||
|
||||
// Render a transmission
|
||||
// Outputs the transmission either on screen or as audio depending on user preference
|
||||
// The refname is a string to identify this sample to the sound manager
|
||||
// The repeating flag indicates whether the message should be repeated continuously or played once.
|
||||
void FGAIPlane::Render(string refname, bool repeating) {
|
||||
#ifdef ENABLE_AUDIO_SUPPORT
|
||||
voice = (voiceOK && fgGetBool("/sim/sound/audible")
|
||||
&& fgGetBool("/sim/sound/voice"));
|
||||
if(voice) {
|
||||
int len;
|
||||
unsigned char* buf = vPtr->WriteMessage((char*)pending_transmission.c_str(), len, voice);
|
||||
if(voice) {
|
||||
SGSimpleSound* simple = new SGSimpleSound(buf, len);
|
||||
// TODO - at the moment the volume is always set off comm1
|
||||
// and can't be changed after the transmission has started.
|
||||
simple->set_volume(5.0 * fgGetDouble("/radios/comm[0]/volume"));
|
||||
globals->get_soundmgr()->add(simple, refname);
|
||||
if(repeating) {
|
||||
globals->get_soundmgr()->play_looped(refname);
|
||||
} else {
|
||||
globals->get_soundmgr()->play_once(refname);
|
||||
}
|
||||
}
|
||||
delete[] buf;
|
||||
}
|
||||
#endif // ENABLE_AUDIO_SUPPORT
|
||||
if(!voice) {
|
||||
// first rip the underscores and the pause hints out of the string - these are for the convienience of the voice parser
|
||||
for(unsigned int i = 0; i < pending_transmission.length(); ++i) {
|
||||
if((pending_transmission.substr(i,1) == "_") || (pending_transmission.substr(i,1) == "/")) {
|
||||
pending_transmission[i] = ' ';
|
||||
}
|
||||
}
|
||||
globals->get_ATC_display()->RegisterSingleMessage(pending_transmission, 0.0);
|
||||
}
|
||||
playing = true;
|
||||
}
|
||||
|
||||
|
||||
// Cease rendering a transmission.
|
||||
void FGAIPlane::NoRender(string refname) {
|
||||
if(playing) {
|
||||
if(voice) {
|
||||
#ifdef ENABLE_AUDIO_SUPPORT
|
||||
globals->get_soundmgr()->stop(refname);
|
||||
globals->get_soundmgr()->remove(refname);
|
||||
#endif
|
||||
} else {
|
||||
globals->get_ATC_display()->CancelRepeatingMessage();
|
||||
}
|
||||
playing = false;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
*/
|
||||
|
||||
void FGAIPlane::RegisterTransmission(int code) {
|
||||
}
|
||||
|
||||
|
|
|
@ -72,7 +72,7 @@ public:
|
|||
virtual ~FGAIPlane();
|
||||
|
||||
// Run the internal calculations
|
||||
virtual void Update(double dt);
|
||||
void Update(double dt);
|
||||
|
||||
// Send a transmission *TO* the AIPlane.
|
||||
// FIXME int code is a hack - eventually this will receive Alexander's coded messages.
|
||||
|
@ -85,6 +85,9 @@ public:
|
|||
LandingType GetLandingOption();
|
||||
|
||||
protected:
|
||||
// callback type for derived classes to use
|
||||
typedef void (*ai_plane_callback_t) (void);
|
||||
|
||||
PlaneRec plane;
|
||||
|
||||
double mag_hdg; // degrees - the heading that the physical aircraft is *pointing*
|
||||
|
@ -106,12 +109,47 @@ protected:
|
|||
// Make radio transmission - this simply sends the transmission for physical rendering if the users
|
||||
// aircraft is on the same frequency and in range. It is up to the derived classes to let ATC know
|
||||
// what is going on.
|
||||
void Transmit(string msg);
|
||||
string pending_transmission; // derived classes set this string before calling Transmit(...)
|
||||
FGATC* tuned_station; // and this if they are tuned to ATC
|
||||
|
||||
// Transmit a message when channel becomes free of other dialog
|
||||
void Transmit(ai_plane_callback_t callback = NULL);
|
||||
|
||||
// Transmit a message if channel becomes free within timeout (seconds). timeout of zero implies no limit
|
||||
void Transmit(double timeout, ai_plane_callback_t callback = NULL);
|
||||
|
||||
// Transmit regardless of other dialog on the channel eg emergency
|
||||
void ImmediateTransmit(ai_plane_callback_t callback = NULL);
|
||||
|
||||
void Bank(double angle);
|
||||
void LevelWings(void);
|
||||
|
||||
PatternLeg leg;
|
||||
|
||||
private:
|
||||
bool _pending;
|
||||
double _timeout;
|
||||
ai_plane_callback_t _callback;
|
||||
bool _transmit; // we are to transmit
|
||||
bool _transmitting; // we are transmitting
|
||||
double _counter;
|
||||
double _max_count;
|
||||
|
||||
// Render a transmission (in string pending_transmission)
|
||||
// Outputs the transmission either on screen or as audio depending on user preference
|
||||
// The refname is a string to identify this sample to the sound manager
|
||||
// The repeating flag indicates whether the message should be repeated continuously or played once.
|
||||
void Render(string refname, bool repeating);
|
||||
|
||||
// Cease rendering a transmission.
|
||||
// Requires the sound manager refname if audio, else "".
|
||||
void NoRender(string refname);
|
||||
|
||||
// Rendering related stuff
|
||||
bool voice; // Flag - true if we are using voice
|
||||
bool playing; // Indicates a message in progress
|
||||
bool voiceOK; // Flag - true if at least one voice has loaded OK
|
||||
FGATCVoice* vPtr;
|
||||
};
|
||||
|
||||
#endif // _FG_AI_PLANE_HXX
|
||||
|
|
|
@ -29,6 +29,10 @@
|
|||
#include "ATC.hxx"
|
||||
#include "ATCdisplay.hxx"
|
||||
|
||||
FGATC::FGATC() {
|
||||
freqClear = true;
|
||||
}
|
||||
|
||||
FGATC::~FGATC() {
|
||||
}
|
||||
|
||||
|
@ -39,7 +43,7 @@ void FGATC::AddPlane(string pid) {
|
|||
}
|
||||
|
||||
int FGATC::RemovePlane() {
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void FGATC::SetDisplay() {
|
||||
|
@ -49,7 +53,7 @@ void FGATC::SetNoDisplay() {
|
|||
}
|
||||
|
||||
atc_type FGATC::GetType() {
|
||||
return INVALID;
|
||||
return INVALID;
|
||||
}
|
||||
|
||||
void FGATC::SetData(ATCData* d) {
|
||||
|
@ -70,9 +74,9 @@ void FGATC::SetData(ATCData* d) {
|
|||
// The refname is a string to identify this sample to the sound manager
|
||||
// The repeating flag indicates whether the message should be repeated continuously or played once.
|
||||
void FGATC::Render(string msg, string refname, bool repeating) {
|
||||
#ifdef ENABLE_AUDIO_SUPPORT
|
||||
#ifdef ENABLE_AUDIO_SUPPORT
|
||||
voice = (voiceOK && fgGetBool("/sim/sound/audible")
|
||||
&& fgGetBool("/sim/sound/voice"));
|
||||
&& fgGetBool("/sim/sound/voice"));
|
||||
if(voice) {
|
||||
int len;
|
||||
unsigned char* buf = vPtr->WriteMessage((char*)msg.c_str(), len, voice);
|
||||
|
@ -90,7 +94,7 @@ void FGATC::Render(string msg, string refname, bool repeating) {
|
|||
}
|
||||
delete[] buf;
|
||||
}
|
||||
#endif // ENABLE_AUDIO_SUPPORT
|
||||
#endif // ENABLE_AUDIO_SUPPORT
|
||||
if(!voice) {
|
||||
// first rip the underscores and the pause hints out of the string - these are for the convienience of the voice parser
|
||||
for(unsigned int i = 0; i < msg.length(); ++i) {
|
||||
|
@ -108,10 +112,10 @@ void FGATC::Render(string msg, string refname, bool repeating) {
|
|||
void FGATC::NoRender(string refname) {
|
||||
if(playing) {
|
||||
if(voice) {
|
||||
#ifdef ENABLE_AUDIO_SUPPORT
|
||||
#ifdef ENABLE_AUDIO_SUPPORT
|
||||
globals->get_soundmgr()->stop(refname);
|
||||
globals->get_soundmgr()->remove(refname);
|
||||
#endif
|
||||
#endif
|
||||
} else {
|
||||
globals->get_ATC_display()->CancelRepeatingMessage();
|
||||
}
|
||||
|
@ -120,21 +124,14 @@ void FGATC::NoRender(string refname) {
|
|||
}
|
||||
|
||||
ostream& operator << (ostream& os, atc_type atc) {
|
||||
switch(atc) {
|
||||
case(INVALID):
|
||||
return(os << "INVALID");
|
||||
case(ATIS):
|
||||
return(os << "ATIS");
|
||||
case(GROUND):
|
||||
return(os << "GROUND");
|
||||
case(TOWER):
|
||||
return(os << "TOWER");
|
||||
case(APPROACH):
|
||||
return(os << "APPROACH");
|
||||
case(DEPARTURE):
|
||||
return(os << "DEPARTURE");
|
||||
case(ENROUTE):
|
||||
return(os << "ENROUTE");
|
||||
}
|
||||
return(os << "ERROR - Unknown switch in atc_type operator << ");
|
||||
switch(atc) {
|
||||
case(INVALID): return(os << "INVALID");
|
||||
case(ATIS): return(os << "ATIS");
|
||||
case(GROUND): return(os << "GROUND");
|
||||
case(TOWER): return(os << "TOWER");
|
||||
case(APPROACH): return(os << "APPROACH");
|
||||
case(DEPARTURE): return(os << "DEPARTURE");
|
||||
case(ENROUTE): return(os << "ENROUTE");
|
||||
}
|
||||
return(os << "ERROR - Unknown switch in atc_type operator << ");
|
||||
}
|
||||
|
|
|
@ -58,13 +58,13 @@ struct PlaneRec {
|
|||
// Possible types of ATC type that the radios may be tuned to.
|
||||
// INVALID implies not tuned in to anything.
|
||||
enum atc_type {
|
||||
INVALID,
|
||||
ATIS,
|
||||
GROUND,
|
||||
TOWER,
|
||||
APPROACH,
|
||||
DEPARTURE,
|
||||
ENROUTE
|
||||
INVALID,
|
||||
ATIS,
|
||||
GROUND,
|
||||
TOWER,
|
||||
APPROACH,
|
||||
DEPARTURE,
|
||||
ENROUTE
|
||||
};
|
||||
|
||||
// DCL - new experimental ATC data store
|
||||
|
@ -98,28 +98,39 @@ struct RunwayDetails {
|
|||
ostream& operator << (ostream& os, atc_type atc);
|
||||
|
||||
class FGATC {
|
||||
|
||||
public:
|
||||
|
||||
virtual ~FGATC();
|
||||
|
||||
// Run the internal calculations
|
||||
virtual void Update(double dt);
|
||||
|
||||
// Add plane to a stack
|
||||
virtual void AddPlane(string pid);
|
||||
|
||||
// Remove plane from stack
|
||||
virtual int RemovePlane();
|
||||
|
||||
// Indicate that this instance should output to the display if appropriate
|
||||
virtual void SetDisplay();
|
||||
|
||||
// Indicate that this instance should not output to the display
|
||||
virtual void SetNoDisplay();
|
||||
|
||||
// Return the type of ATC station that the class represents
|
||||
virtual atc_type GetType();
|
||||
|
||||
public:
|
||||
|
||||
FGATC();
|
||||
virtual ~FGATC();
|
||||
|
||||
// Run the internal calculations
|
||||
virtual void Update(double dt);
|
||||
|
||||
// Add plane to a stack
|
||||
virtual void AddPlane(string pid);
|
||||
|
||||
// Remove plane from stack
|
||||
virtual int RemovePlane();
|
||||
|
||||
// Indicate that this instance should output to the display if appropriate
|
||||
virtual void SetDisplay();
|
||||
|
||||
// Indicate that this instance should not output to the display
|
||||
virtual void SetNoDisplay();
|
||||
|
||||
// Returns true if OK to transmit on this frequency
|
||||
inline bool FreqClear() { return freqClear; }
|
||||
// Indicate that the frequency is in use
|
||||
inline void FreqInUse() { freqClear = false; }
|
||||
// Under development!!
|
||||
// The idea is that AI traffic or the user ATC dialog box calls FreqInUse() when they begin transmitting,
|
||||
// and that the tower control sets freqClear back to true following a reply.
|
||||
// AI traffic should check FreqClear() is true prior to transmitting.
|
||||
// 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();
|
||||
|
||||
// Set the core ATC data
|
||||
void SetData(ATCData* d);
|
||||
|
@ -145,18 +156,18 @@ public:
|
|||
inline const char* get_name() {return name.c_str();}
|
||||
inline void set_name(const string nm) {name = nm;}
|
||||
|
||||
protected:
|
||||
|
||||
protected:
|
||||
|
||||
// Render a transmission
|
||||
// Outputs the transmission either on screen or as audio depending on user preference
|
||||
// The refname is a string to identify this sample to the sound manager
|
||||
// The repeating flag indicates whether the message should be repeated continuously or played once.
|
||||
void Render(string msg, string refname, bool repeating);
|
||||
|
||||
|
||||
// Cease rendering a transmission.
|
||||
// Requires the sound manager refname if audio, else "".
|
||||
void NoRender(string refname);
|
||||
|
||||
|
||||
double lon, lat, elev;
|
||||
double x, y, z;
|
||||
int freq;
|
||||
|
@ -169,6 +180,8 @@ protected:
|
|||
bool playing; // Indicates a message in progress
|
||||
bool voiceOK; // Flag - true if at least one voice has loaded OK
|
||||
FGATCVoice* vPtr;
|
||||
|
||||
bool freqClear; // Flag to indicate if the frequency is clear of ongoing dialog
|
||||
};
|
||||
|
||||
inline istream&
|
||||
|
@ -177,7 +190,7 @@ operator >> ( istream& fin, ATCData& a )
|
|||
double f;
|
||||
char ch;
|
||||
char tp;
|
||||
|
||||
|
||||
fin >> tp;
|
||||
|
||||
switch(tp) {
|
||||
|
@ -201,7 +214,7 @@ operator >> ( istream& fin, ATCData& a )
|
|||
a.type = INVALID;
|
||||
return fin >> skipeol;
|
||||
}
|
||||
|
||||
|
||||
fin >> a.lat >> a.lon >> a.elev >> f >> a.range
|
||||
>> a.ident;
|
||||
|
||||
|
|
|
@ -1248,6 +1248,10 @@ void FGTower::ReportRunwayVacated(string ID) {
|
|||
//cout << "Report Runway Vacated Called...\n";
|
||||
}
|
||||
|
||||
void FGTower::ReportDownwind(string ID) {
|
||||
// Tell the plane reporting what number she is in the circuit
|
||||
}
|
||||
|
||||
ostream& operator << (ostream& os, tower_traffic_type ttt) {
|
||||
switch(ttt) {
|
||||
case(CIRCUIT): return(os << "CIRCUIT");
|
||||
|
|
|
@ -124,6 +124,7 @@ public:
|
|||
void ReportGoingAround(string ID);
|
||||
void ReportRunwayVacated(string ID);
|
||||
void ReportReadyForDeparture(string ID);
|
||||
void ReportDownwind(string ID);
|
||||
|
||||
// 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);
|
||||
|
@ -146,15 +147,6 @@ public:
|
|||
bool GetCrosswindConstraint(double& cpos);
|
||||
bool GetDownwindConstraint(double& dpos);
|
||||
bool GetBaseConstraint(double& bpos);
|
||||
|
||||
// Returns true if OK to transmit on this frequency
|
||||
inline bool FreqClear() { return freqClear; }
|
||||
// Indicate that the frequency is in use
|
||||
inline void FreqInUse() { freqClear = false; }
|
||||
// The idea is that AI traffic or the user ATC dialog box calls FreqInUse() when they begin transmitting,
|
||||
// and that the tower control sets freqClear back to true following a reply.
|
||||
// AI traffic should check FreqClear() is true prior to transmitting.
|
||||
// The user will just have to wait for a gap in dialog as in real life.
|
||||
|
||||
private:
|
||||
FGATCMgr* ATCmgr;
|
||||
|
@ -198,8 +190,6 @@ private:
|
|||
bool display; // Flag to indicate whether we should be outputting to the ATC display.
|
||||
bool displaying; // Flag to indicate whether we are outputting to the ATC display.
|
||||
|
||||
bool freqClear; // Flag to indicate if the frequency is clear of ongoing dialog
|
||||
|
||||
double timeSinceLastDeparture; // Time in seconds since last departure from active rwy.
|
||||
bool departed; // set true when the above needs incrementing with time, false when it doesn't.
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue