diff --git a/Include/ACInfoWnd.h b/Include/ACInfoWnd.h
index 01673ec9..738dfbc9 100644
--- a/Include/ACInfoWnd.h
+++ b/Include/ACInfoWnd.h
@@ -38,7 +38,7 @@ class TFACSearchEditWidget : public TFTextFieldWidget
TFACSearchEditWidget (XPWidgetID _me = NULL, const char* szKey = NULL);
// Find my aircraft
- const LTFlightData* SearchFlightData (const std::string ac_key);
+ const LTFlightData* SearchFlightData (std::string ac_key);
void SetAcKey (const LTFlightData::FDKeyTy& _key);
// Get the found aircraft
diff --git a/Include/Constants.h b/Include/Constants.h
index 388adfb8..e306f5bc 100644
--- a/Include/Constants.h
+++ b/Include/Constants.h
@@ -29,7 +29,7 @@
//
// MARK: Version Information (CHANGE VERSION HERE)
//
-constexpr float VERSION_NR = 2.02f;
+constexpr float VERSION_NR = 2.03f;
constexpr bool VERSION_BETA = true;
extern float verXPlaneOrg; // version on X-Plane.org
extern int verDateXPlaneOrg; // and its date
@@ -105,6 +105,7 @@ constexpr double MDL_TIRE_SLOW_TIME = 5.0; ///< [s] time till tires stop rotati
constexpr double MDL_TIRE_MAX_RPM = 2000; ///< [rpm] max tire rotation speed
constexpr double MDL_TIRE_CF_M = 3.2; ///< [m] tire circumfence (3.2m for a 40-inch tire)
constexpr double MDL_GEAR_DEFL_TIME = 0.5; ///< [s] time for gear deflection (one direction...up down is twice this value)
+constexpr double MDL_CAR_MAX_TAXI = 80.0; ///< [kn] Maximum allowed taxi speed for ground vehicles (before they turn into planes)
constexpr int COLOR_YELLOW = 0xFFFF00;
constexpr int COLOR_RED = 0xFF0000;
@@ -145,9 +146,10 @@ constexpr int LT_NEW_VER_CHECK_TIME = 48; // [h] between two checks of a new
//MARK: Text Constants
#define LIVE_TRAFFIC "LiveTraffic"
+#define LIVE_TRAFFIC_XPMP2 " LT" ///< short form for logging by XPMP2, so that log entries are aligned
#define LT_CFG_VER_NM_CONV "1.0" // version of config file format, from which to convert distances from km to nm
#define LT_CFG_VERSION "1.1" // current version of config file format
-#define LT_FM_VERSION "1.1" // version of flight model file format
+#define LT_FM_VERSION "2.0" // version of flight model file format
#define PLUGIN_SIGNATURE "TwinFan.plugin.LiveTraffic"
#define PLUGIN_DESCRIPTION "Create Multiplayer Aircraft based on live traffic."
#define LT_DOWNLOAD_URL "https://forums.x-plane.org/index.php?/files/file/49749-livetraffic/"
@@ -158,6 +160,7 @@ constexpr int LT_NEW_VER_CHECK_TIME = 48; // [h] between two checks of a new
#define MSG_REINIT "LiveTraffic is re-initializing itself"
#define MSG_DISABLE_MYSELF "LiveTraffic disables itself due to unhandable exceptions"
#define MSG_LT_NEW_VER_AVAIL "The new version %01.2f of LiveTraffic is available at X-Plane.com!"
+#define MSG_TIMESTAMPS "Current System time is %sZ, current simulated time is %sZ"
#define MSG_AI_LOAD_ACF "Changing AI control: X-Plane is now loading AI Aircraft models..."
#define MSG_REQUESTING_LIVE_FD "Requesting live flight data online..."
#define MSG_READING_HIST_FD "Reading historic flight data..."
@@ -168,6 +171,8 @@ constexpr int LT_NEW_VER_CHECK_TIME = 48; // [h] between two checks of a new
#define MSG_ADSBEX_LIMITE "%ld / %ld requests left"
#define INFO_AC_ADDED "Added aircraft %s, operator '%s', a/c model '%s', flight model [%s], bearing %.0f, distance %.1fnm, from channel %s"
#define INFO_AC_MDL_CHANGED "Changed CSL model for aircraft %s, operator '%s': a/c model now '%s'"
+#define INFO_GND_VEHICLE_APT "Vehicle %s: Decided for ground vehicle based on operator name '%s'"
+#define INFO_GND_VEHICLE_CALL "Vehicle %s: Decided for ground vehicle based on call sign '%s'"
#define INFO_AC_REMOVED "Removed aircraft %s"
#define INFO_AC_ALL_REMOVED "Removed all aircraft"
#define INFO_WND_AUTO_AC "AUTO"
@@ -183,6 +188,7 @@ constexpr int LT_NEW_VER_CHECK_TIME = 48; // [h] between two checks of a new
#define CSL_DEFAULT_ICAO_TYPE "A320"
#define CSL_CAR_ICAO_TYPE "ZZZC" // fake code for a ground vehicle
#define FM_MAP_SECTION "Map"
+#define FM_CAR_SECTION "GroundVehicles"
#define FM_PARENT_SEPARATOR ":"
#define CFG_CSL_SECTION "[CSLPaths]"
#define CFG_DEFAULT_AC_TYPE "DEFAULT_AC_TYPE"
@@ -273,7 +279,7 @@ constexpr int SERR_LEN = 100; // size of buffer for IO error t
#define ERR_CH_UNKNOWN_NAME "(unknown channel)"
#define ERR_CH_INVALID "%s: Channel invalid and disabled"
#define ERR_CH_MAX_ERR_INV "%s: Channel invalid and disabled after too many errors"
-#define ERR_NO_AC_TYPE "Tracking data for '%s' (man '%s', mdl '%s') lacks ICAO a/c type code, will be rendered with standard a/c %s"
+#define ERR_NO_AC_TYPE "Tracking data for '%s' (man '%s', mdl '%s') lacks ICAO a/c type code, can't derive type -> will be rendered with standard a/c %s"
#define ERR_NO_AC_TYPE_BUT_MDL "Tracking data for '%s' (man '%s', mdl '%s') lacks ICAO a/c type code, but derived %s from mdl text"
#define ERR_DATAREF_FIND "Could not find DataRef/CmdRef: %s"
#define ERR_DATAREF_ACCESSOR "Could not register accessor for DataRef: %s"
@@ -318,7 +324,7 @@ constexpr int SERR_LEN = 100; // size of buffer for IO error t
#define ERR_CFG_AC_DEFAULT "A/c default ICAO type '%s' invalid, still using '%s' as default. Verify Settings > CSL!"
#define ERR_CFG_CAR_DEFAULT "Car default ICAO type '%s' invalid, still using '%s' as default. Verify Settings > CSL!"
#define ERR_CFG_TYPE_INVALID "%s, line %d: ICAO type designator '%s' unknown"
-#define ERR_FM_NOT_AFTER_MAP "Remainder after [Map] section ignored"
+#define ERR_FM_NOT_AFTER_MAP "Unknown section after [Map] section ignored"
#define ERR_FM_NOT_BEFORE_SEC "Lines before first section ignored"
#define ERR_FM_UNKNOWN_NAME "Unknown parameter in '%s', line %d: %s"
#define ERR_FM_UNKNOWN_SECTION "Referring to unknown model section in '%s', line %d: %s"
diff --git a/Include/DataRefs.h b/Include/DataRefs.h
index bc51130a..78f15cfa 100644
--- a/Include/DataRefs.h
+++ b/Include/DataRefs.h
@@ -241,7 +241,6 @@ enum dataRefsLT {
DR_CFG_AUTO_START,
DR_CFG_AI_ON_REQUEST,
DR_CFG_AI_UNDER_CONTROL,
- DR_CFG_AI_SKIP_NOPLANE,
DR_CFG_LABELS,
DR_CFG_LABEL_SHOWN,
DR_CFG_LABEL_COL_DYN,
@@ -477,7 +476,6 @@ class DataRefs
// generic config values
int bAutoStart = true; // shall display a/c right after startup?
int bAIonRequest = false;// acquire multiplayer control for TCAS on request only, not automatically?
- int nAISkipAssignNoPlane = -1; ///< Skip assigning NoPlane.acf to AI planes (-1 = use defaults, 0 = force off, 1 = force on)
// which elements make up an a/c label?
LabelCfgTy labelCfg = { 0,1,0,0,0,0,0,0, 0,0,0,0,0,0 };
LabelShowCfgTy labelShown = { 1, 1, 1 }; // when to show? (default: always)
@@ -626,7 +624,6 @@ class DataRefs
// specific access
inline bool GetAutoStart() const { return bAutoStart != 0; }
inline bool IsAIonRequest() const { return bAIonRequest != 0; }
- bool ShallAISkipAssignNoPlane() const;
static int HaveAIUnderControl(void* =NULL) { return XPMPHasControlOfAIAircraft(); }
inline LabelCfgTy GetLabelCfg() const { return labelCfg; }
inline LabelShowCfgTy GetLabelShowCfg() const { return labelShown; }
diff --git a/Include/LTAircraft.h b/Include/LTAircraft.h
index a7a286d4..9ce55cfc 100644
--- a/Include/LTAircraft.h
+++ b/Include/LTAircraft.h
@@ -287,8 +287,10 @@ class LTAircraft : public XPCAircraft
public:
static bool ReadFlightModelFile ();
- static const FlightModel& FindFlightModel (const std::string acTypeIcao);
- static const FlightModel* GetFlightModel (const std::string modelName);
+ static const FlightModel& FindFlightModel (const std::string& acTypeIcao);
+ static const FlightModel* GetFlightModel (const std::string& modelName);
+ /// Tests if the given call sign matches typical call signs of ground vehicles
+ static bool MatchesCar (const std::string& _callSign);
};
public:
@@ -350,6 +352,7 @@ class LTAircraft : public XPCAircraft
#ifdef DEBUG
bool bIsSelected = false; // is selected for logging/debugging?
#endif
+ bool bChangeModel = false; ///< shall the model be updated at next chance?
bool bSendNewInfoData = false; ///< is there new static data to announce?
// visibility
bool bVisible = true; // is a/c visible?
@@ -368,8 +371,6 @@ class LTAircraft : public XPCAircraft
void LabelUpdate();
// stringify e.g. for debugging info purposes
operator std::string() const;
- // change the model (e.g. when model-defining static data changed)
- void ChangeModel (const LTFlightData::FDStaticData& statData);
// current position
inline const positionTy& GetPPos() const { return ppos; }
inline positionTy GetPPosLocal() const { return positionTy(ppos).WorldToLocal(); }
@@ -407,6 +408,8 @@ class LTAircraft : public XPCAircraft
// object valid? (set to false after exceptions)
inline bool IsValid() const { return bValid; }
void SetInvalid() { bValid = false; }
+ bool ShallUpdateModel () const { return bChangeModel; }
+ void SetUpdateModel () { bChangeModel = true; }
inline bool ShallSendNewInfoData () const { return bSendNewInfoData; }
inline void SetSendNewInfoData () { bSendNewInfoData = true; }
// Visibility
@@ -434,6 +437,10 @@ class LTAircraft : public XPCAircraft
/// Determines AI priority based on bearing to user's plane and ground status
void CalcAIPrio ();
+ /// @brief change the model (e.g. when model-defining static data changed)
+ /// @note Should be used in main thread only
+ void ChangeModel ();
+
protected:
// *** Camera view ***
static LTAircraft* pExtViewAc; // the a/c to show in external view, NULL if none/stop ext view
diff --git a/Include/LTApt.h b/Include/LTApt.h
index 415b8af5..a3c0d4db 100644
--- a/Include/LTApt.h
+++ b/Include/LTApt.h
@@ -63,7 +63,7 @@ bool LTAptSnap (LTFlightData& fd, dequePositionTy::iterator& posIter,
/// Cleanup
void LTAptDisable ();
-#ifdef DEBUG
+#ifdef APT_DUMP
/// @brief Dumps the entire taxi network into a CSV file readable by GPS Visualizer
/// @see https://www.gpsvisualizer.com/
void LTAptDump (const std::string& _aptId);
diff --git a/Include/LTFlightData.h b/Include/LTFlightData.h
index c66c0d9a..80a54f4d 100644
--- a/Include/LTFlightData.h
+++ b/Include/LTFlightData.h
@@ -140,8 +140,9 @@ class LTFlightData
FDStaticData(FDStaticData&&) = default;
FDStaticData& operator=(const FDStaticData&) = default;
FDStaticData& operator=(FDStaticData&&) = default;
- // 'merge' data, i.e. copy only filled fields from 'other'
- FDStaticData& operator |= (const FDStaticData& other);
+ /// @brief Merges data, i.e. copy only filled fields from 'other'
+ /// @return Have matching-relevant fields now changed?
+ bool merge (const FDStaticData& other);
// returns flight, call sign, registration, or provieded _default (e.g. transp hex code)
std::string acId (const std::string _default) const;
// route (this is "originAp-destAp", but considers empty txt)
@@ -151,6 +152,8 @@ class LTFlightData
// best guess for an airline livery: opIcao if exists, otherwise first 3 digits of call sign
inline std::string airlineCode() const
{ return opIcao.empty() ? call.substr(0,3) : opIcao; }
+ /// is this a ground vehicle?
+ bool isGrndVehicle() const;
// has been initialized at least once?
bool isInit() const { return bInit; }
};
diff --git a/Lib/CURL/libcurl.pdb b/Lib/CURL/libcurl.pdb
new file mode 100644
index 00000000..35dec749
Binary files /dev/null and b/Lib/CURL/libcurl.pdb differ
diff --git a/Lib/XPMP2/XPMP2.framework/Versions/1.0/Headers/XPCAircraft.h b/Lib/XPMP2/XPMP2.framework/Versions/1.0/Headers/XPCAircraft.h
index 2d709614..26ad637a 100644
--- a/Lib/XPMP2/XPMP2.framework/Versions/1.0/Headers/XPCAircraft.h
+++ b/Lib/XPMP2/XPMP2.framework/Versions/1.0/Headers/XPCAircraft.h
@@ -44,11 +44,18 @@ XPCAircraft : public XPMP2::Aircraft {
public:
- /// Legacy constructor creates a plane and puts it under control of XPlaneMP
- XPCAircraft(const char* inICAOCode,
- const char* inAirline,
- const char* inLivery,
- const char* inModelName = nullptr); // this new parameter is defaulted, so that old code should compile
+ /// @brief Legacy constructor creates a plane and puts it under control of XPlaneMP
+ /// @exception XPMP2::XPMP2Error Mode S id invalid or duplicate, no model found during model matching
+ /// @param _icaoType ICAO aircraft type designator, like 'A320', 'B738', 'C172'
+ /// @param _icaoAirline ICAO airline code, like 'BAW', 'DLH', can be an empty string
+ /// @param _livery Special livery designator, can be an empty string
+ /// @param _modeS_id (optional) Unique identification of the plane [0x01..0xFFFFFF], e.g. the 24bit mode S transponder code. XPMP2 assigns an arbitrary unique number of not given
+ /// @param _modelId (optional) specific model id to be used (no folder/package name, just the id as defined in the `OBJ8_AIRCRAFT` line)
+ XPCAircraft(const char* _icaoType,
+ const char* _icaoAirline,
+ const char* _livery,
+ XPMPPlaneID _modeS_id = 0, // new parameters are defaulted, so that old code should compile
+ const char* _modelId = nullptr);
/// Legacy: Called before rendering to query plane's current position, overwrite to provide your implementation
virtual XPMPPlaneCallbackResult GetPlanePosition(XPMPPlanePosition_t* outPosition) = 0;
@@ -61,7 +68,7 @@ XPCAircraft : public XPMP2::Aircraft {
{ return xpmpData_Unavailable; }
/// Just calls all 4 previous `Get...` functions and copies the provided values into `drawInfo` and `v`
- virtual void UpdatePosition ();
+ virtual void UpdatePosition (float _elapsedSinceLastCall, int _flCounter);
};
diff --git a/Lib/XPMP2/XPMP2.framework/Versions/1.0/Headers/XPMPAircraft.h b/Lib/XPMP2/XPMP2.framework/Versions/1.0/Headers/XPMPAircraft.h
index 946e8b3a..237318b2 100644
--- a/Lib/XPMP2/XPMP2.framework/Versions/1.0/Headers/XPMPAircraft.h
+++ b/Lib/XPMP2/XPMP2.framework/Versions/1.0/Headers/XPMPAircraft.h
@@ -89,8 +89,11 @@ enum DR_VALS {
class Aircraft {
protected:
- /// Legacy: The id of the represented plane (in XPMP2, this now is an arbitrary, ever increasing number)
- XPMPPlaneID mPlane = 0;
+ /// @brief A plane is uniquely identified by a 24bit number [0x01..0xFFFFFF]
+ /// @details This number is directly used as `modeS_id` n the new
+ /// [TCAS override](https://developer.x-plane.com/article/overriding-tcas-and-providing-traffic-information/)
+ /// approach.
+ XPMPPlaneID modeS_id = 0;
public:
std::string acIcaoType; ///< ICAO aircraft type of this plane
@@ -144,8 +147,8 @@ class Aircraft {
/// X-Plane instance handles for all objects making up the model
std::list listInst;
- /// Which sim/multiplayer/plane-index used last?
- int multiIdx = -1;
+ /// Which sim/cockpit2/tcas/targets-index used last?
+ int tcasTargetIdx = -1;
/// Timestamp of last update of camera dist/bearing
float camTimLstUpd = 0.0f;
@@ -162,31 +165,38 @@ class Aircraft {
int mapIconCol = 0; ///< map icon coordinates, column
float mapX = 0.0f; ///< temporary: map coordinates (NAN = not to be drawn)
float mapY = 0.0f; ///< temporary: map coordinates (NAN = not to be drawn)
+ std::string mapLabel; ///< label for map drawing
public:
- /// Constructor
+ /// Constructor creates a new aircraft object, which will be managed and displayed
+ /// @exception XPMP2::XPMP2Error Mode S id invalid or duplicate, no model found during model matching
+ /// @param _icaoType ICAO aircraft type designator, like 'A320', 'B738', 'C172'
+ /// @param _icaoAirline ICAO airline code, like 'BAW', 'DLH', can be an empty string
+ /// @param _livery Special livery designator, can be an empty string
+ /// @param _modeS_id (optional) Unique identification of the plane [0x01..0xFFFFFF], e.g. the 24bit mode S transponder code. XPMP2 assigns an arbitrary unique number of not given
+ /// @param _modelId (optional) specific model id to be used (no folder/package name, just the id as defined in the `OBJ8_AIRCRAFT` line)
Aircraft (const std::string& _icaoType,
const std::string& _icaoAirline,
const std::string& _livery,
- const std::string& _modelName = "");
+ XPMPPlaneID _modeS_id = 0,
+ const std::string& _modelId = "");
/// Destructor cleans up all resources acquired
virtual ~Aircraft();
/// return the XPMP2 plane id
- XPMPPlaneID GetPlaneID () const { return mPlane; }
- /// @brief return the current multiplayer-index
- /// @note This is a 0-based index into our internal tables!
- /// It is one less than the number used in sim/multiplayer/plane dataRefs,
- /// use Aircraft::GetAIPlaneIdx() for that purpose.
- int GetMultiIdx () const { return multiIdx; }
- /// @brief return the plane's index in XP's sim/multiplayer/plane dataRefs
- int GetAIPlaneIdx () const { return multiIdx >= 0 ? multiIdx+1 : -1; }
- /// Is this plane currently also being tracked by X-Plane's AI/multiplayer, ie. will appear on TCAS?
- bool IsCurrentlyShownAsAI () const { return multiIdx >= 0; }
- /// Will this plane show up on TCAS / in multiplayer views? (It will if transponder is not switched off)
+ XPMPPlaneID GetModeS_ID () const { return modeS_id; }
+ /// Is this object a ground vehicle?
+ bool IsGroundVehicle() const;
+ /// @brief return the current TCAS target index (into sim/cockpit2/tcas/targets), 1-based, -1 if not used
+ int GetTcasTargetIdx () const { return tcasTargetIdx; }
+ /// Is this plane currently also being tracked as a TCAS target, ie. will appear on TCAS?
+ bool IsCurrentlyShownAsTcasTarget () const { return tcasTargetIdx >= 1; }
+ /// Is this plane currently also being tracked by X-Plane's classic AI/multiplayer?
+ bool IsCurrentlyShownAsAI () const { return 1 <= tcasTargetIdx && tcasTargetIdx <= 20; }
+ /// Is this plane to be drawn on TCAS? (It will if transponder is not switched off)
bool ShowAsAIPlane () const { return IsVisible() && acRadar.mode != xpmpTransponderMode_Standby; }
- /// Reset multiplayer slot index
- void ResetMultiIdx () { multiIdx = -1; }
+ /// Reset TCAS target slot index
+ void ResetTcasTargetIdx () { tcasTargetIdx = -1; }
/// (Potentially) change the plane's model after doing a new match attempt
int ChangeModel (const std::string& _icaoType,
@@ -208,7 +218,11 @@ class Aircraft {
/// @brief Called right before updating the aircraft's placement in the world
/// @details Abstract virtual function. Override in derived classes and fill
/// `drawInfo`, the `v` array of dataRefs, `label`, and `infoTexts` with current values.
- virtual void UpdatePosition () = 0;
+ /// @see See [XPLMFlightLoop_f](https://developer.x-plane.com/sdk/XPLMProcessing/#XPLMFlightLoop_f)
+ /// for background on the two passed-on parameters:
+ /// @param _elapsedSinceLastCall The wall time since last call
+ /// @param _flCounter A monotonically increasing counter, bumped once per flight loop dispatch from the sim.
+ virtual void UpdatePosition (float _elapsedSinceLastCall, int _flCounter) = 0;
/// Distance to camera [m]
float GetCameraDist () const { return camDist; }
/// Bearing from camera [°]
@@ -254,12 +268,13 @@ class Aircraft {
bool CreateInstances ();
/// Destroy all instances
void DestroyInstances ();
+
+ /// Put together the map label, depends on tcasTargetIdx
+ virtual void ComputeMapLabel ();
// The following functions are implemented in AIMultiplayer.cpp:
- /// AI/Multiplayer handling: Find next AI slot
- int AISlotReserve ();
- /// AI/Multiplayer handling: Clear AI slot
- void AISlotClear ();
+ /// Define the TCAS target index in use
+ virtual void SetTcasTargetIdx (int _idx) { tcasTargetIdx = _idx; }
// These functions are called from AIMultiUpdate()
friend void AIMultiUpdate ();
};
@@ -271,7 +286,7 @@ Aircraft* AcFindByID (XPMPPlaneID _id);
// MARK: XPMP2 Exception class
//
-/// XPMP2 Exception class, e.g. thrown if there are no CSL models when creating an Aircraft
+/// XPMP2 Exception class, e.g. thrown if there are no CSL models or duplicate modeS_ids when creating an Aircraft
class XPMP2Error : public std::logic_error {
protected:
std::string fileName; ///< filename of the line of code where exception occurred
diff --git a/Lib/XPMP2/XPMP2.framework/Versions/1.0/Headers/XPMPMultiplayer.h b/Lib/XPMP2/XPMP2.framework/Versions/1.0/Headers/XPMPMultiplayer.h
index a8013a88..b8e7f85f 100644
--- a/Lib/XPMP2/XPMP2.framework/Versions/1.0/Headers/XPMPMultiplayer.h
+++ b/Lib/XPMP2/XPMP2.framework/Versions/1.0/Headers/XPMPMultiplayer.h
@@ -233,8 +233,15 @@ enum XPMPPlaneCallbackResult {
/// @brief Unique ID for an aircraft created by a plugin.
/// @note In XPMP2 this value is no longer a pointer to an internal memory address,
-/// but just an ever increasing number. Don't use it as a pointer.
-typedef void * XPMPPlaneID;
+/// but will directly be used as `modeS_id` in the new
+/// [TCAS override](https://developer.x-plane.com/article/overriding-tcas-and-providing-traffic-information/)
+/// approach.
+typedef unsigned XPMPPlaneID;
+
+/// Minimum allowed XPMPPlaneID / mode S id
+constexpr XPMPPlaneID MIN_MODE_S_ID = 0x00000001;
+/// Maximum allowed XPMPPlaneID / mode S id
+constexpr XPMPPlaneID MAX_MODE_S_ID = 0x00FFFFFF;
/************************************************************************************
@@ -247,8 +254,6 @@ typedef void * XPMPPlaneID;
// Config key definitions
#define XPMP_CFG_ITM_CLAMPALL "clamp_all_to_ground" ///< Config key: Ensure no plane sinks below ground, no matter of Aircraft::bClampToGround
-#define XPMP_CFG_ITM_SKIP_NOPLANE "skip_assign_noplane" ///< Config key: For faking TCAS, all AI/multiplayer planes are assigned the `NoPlane.acf` type when XPMP2 takes over control. This option allows to skip assining `NoPlane.acf`...just in case.
-#define XPMP_CFG_ITM_COPY_NOPLANE "copy_noplane" ///< Config key: On startup, shall `NoPlane.acf` be copied from the resource directory to `/Aircraft/`? This is highly recommended to reduce side-effects of using `NoPlane` as AI Aircraft model.
#define XPMP_CFG_ITM_LOGLEVEL "log_level" ///< Config key: General level of logging into Log.txt (0 = Debug, 1 = Info, 2 = Warning, 3 = Error, 4 = Fatal)
#define XPMP_CFG_ITM_MODELMATCHING "model_matching" ///< Config key: Write information on model matching into Log.txt
@@ -259,8 +264,6 @@ typedef void * XPMPPlaneID;
/// `section | key | type | default | description`\n
/// `------- | ------------------- | ---- | ------- | -------------------------------------------------------------------------`\n
/// `planes | clamp_all_to_ground | int | 1 | Ensure no plane sinks below ground, no matter of Aircraft::bClampToGround`\n
-/// `planes | skip_assign_noplane | int | 0 | If non-zero, then AI planes will not be assigned the NoPlane.acf type.`\n
-/// `planes | copy_noplane | int | 1 | Shall NoPlane.acf be copied to Aircraft folder if missing/newer? (Recommended!)`\n
/// `debug | log_level | int | 2 | General level of logging into Log.txt (0 = Debug, 1 = Info, 2 = Warning, 3 = Error, 4 = Fatal)`\n
/// `debug | model_matching | int | 0 | Write information on model matching into Log.txt`\n
/// @note There is no immediate requirement to check the value of `_section` in your implementation.
@@ -281,25 +284,31 @@ const char * XPMPMultiplayerInitLegacyData(const char* inCSLFolder,
const char* inPluginName,
const char* resourceDir,
XPMPIntPrefsFuncTy inIntPrefsFunc = nullptr,
- const char* inDefaultICAO = nullptr);
+ const char* inDefaultICAO = nullptr,
+ const char* inPluginLogAcronym = nullptr);
/// @brief Initializes the XPMP2 library. This shall be your first call to the library.
/// @note Parameters changed compared to libxplanemp!
-/// @param inPluginName Your plugin's name, used only in output to `Log.txt`
-/// @param resourceDir The directory where XPMP2 finds all required supplemental files (`Doc8643.txt`, `MapIcons.png`, `NoPlane.acf`, `related.txt`)
-/// @param inIntPrefsFunc A pointer to a callback function providing integer config values. See ::XPMPIntPrefsFuncTy for details.
-/// @param inDefaultICAO A fallback aircraft type if no type can be deduced otherwise for an aircraft.
+/// @param inPluginName Your plugin's name, used as map layer name, and as folder name under `Aircraft`
+/// @param resourceDir The directory where XPMP2 finds all required supplemental files (`Doc8643.txt`, `MapIcons.png`, `related.txt`)
+/// @param inIntPrefsFunc (optional) A pointer to a callback function providing integer config values. See ::XPMPIntPrefsFuncTy for details.
+/// @param inDefaultICAO (optional) A fallback aircraft type if no type can be deduced otherwise for an aircraft.
+/// @param inPluginLogAcronym (optional) A short text to be used in log output. If not given then `inPluginName` is used also for this purpse.
/// @return Empty string in case of success, otherwise a human-readable error message.
const char * XPMPMultiplayerInit(const char* inPluginName,
const char* resourceDir,
XPMPIntPrefsFuncTy inIntPrefsFunc = nullptr,
- const char* inDefaultICAO = nullptr);
+ const char* inDefaultICAO = nullptr,
+ const char* inPluginLogAcronym = nullptr);
/// @brief Overrides the plugin's name to be used in Log output
/// @details The same as providing a plugin name with XPMPMultiplayerInit().
/// If no name is provided, it defaults to the plugin's name as set in XPluginStart().
/// @note Replaces the compile-time macro `XPMP_CLIENT_LONGNAME` needed in `libxplanemp`.
-void XPMPSetPluginName (const char* inPluginName);
+/// @param inPluginName Your plugin's name, used as map layer name, and as folder name under `Aircraft`
+/// @param inPluginLogAcronym (optional) A short text to be used in log output. If not given then `inPluginName` is used also for this purpse.
+void XPMPSetPluginName (const char* inPluginName,
+ const char* inPluginLogAcronym = nullptr);
/// @brief Clean up the multiplayer library
@@ -459,12 +468,14 @@ typedef XPMPPlaneCallbackResult (* XPMPPlaneData_f)(XPMPPlaneID inPlane,
/// @param inLivery Special livery designator, can be an empty string
/// @param inDataFunc Callback function called by XPMP2 to fetch updated data
/// @param inRefcon A refcon value passed back to you in all calls to the `inDataFunc`
+/// @param inModeS_id (optional) Unique identification of the plane [0x01..0xFFFFFF], e.g. the 24bit mode S transponder code. XPMP2 assigns an arbitrary unique number of not given
[[deprecated("Subclass XPMP2::Aircraft instead")]]
XPMPPlaneID XPMPCreatePlane(const char * inICAOCode,
const char * inAirline,
const char * inLivery,
XPMPPlaneData_f inDataFunc,
- void * inRefcon);
+ void * inRefcon,
+ XPMPPlaneID inModeS_id = 0);
/// @brief Creates a new plane, providing a specific CSL model name
/// @deprecated Subclass XPMP2::Aircraft instead
@@ -474,13 +485,15 @@ XPMPPlaneID XPMPCreatePlane(const char * inICAOCode,
/// @param inLivery Special livery designator, can be an empty string
/// @param inDataFunc Callback function called by XPMP2 to fetch updated data
/// @param inRefcon A refcon value passed back to you in all calls to the `inDataFunc`
+/// @param inModeS_id (optional) Unique identification of the plane [0x01..0xFFFFFF], e.g. the 24bit mode S transponder code. XPMP2 assigns an arbitrary unique number of not given
[[deprecated("Subclass XPMP2::Aircraft instead")]]
XPMPPlaneID XPMPCreatePlaneWithModelName(const char * inModelName,
const char * inICAOCode,
const char * inAirline,
const char * inLivery,
- XPMPPlaneData_f inDataFunc,
- void * inRefcon);
+ XPMPPlaneData_f inDataFunc,
+ void * inRefcon,
+ XPMPPlaneID inModeS_id = 0);
/// @brief [Deprecated] Removes a plane previously created with XPMPCreatePlane()
/// @deprecated Delete subclassed XPMP2::Aircraft object instead.
@@ -492,20 +505,15 @@ void XPMPDestroyPlane(XPMPPlaneID);
void XPMPSetPlaneVisibility(XPMPPlaneID _id, bool _bVisible);
-/*
- * XPMPChangePlaneModel
- *
- * This routine lets you change an aircraft's model. This can be useful if a remote
- * player changes planes or new information comes over the network asynchronously.
- *
- * this function returns an integer which, abstractly, represents the match quality.
- *
- * lower numbers are better, 2 or lower indicates an exact match on model.
- * negative values indicate failure to match at all.
- *
- */
-int XPMPChangePlaneModel(
- XPMPPlaneID inPlaneID,
+/// @brief Perform model matching again and change the CSL model to the resulting match
+/// @note Effectively calls XPMP2::Aircraft::ChangeModel(),
+/// so if you have the aircraft object, prefer call that function directly.
+/// @param inPlaneID Which plane to change?
+/// @param inICAOCode ICAO aircraft type designator, like 'A320', 'B738', 'C172'
+/// @param inAirline ICAO airline code, like 'BAW', 'DLH', can be an empty string
+/// @param inLivery Special livery designator, can be an empty string
+/// @return Match quality, the lower the better
+int XPMPChangePlaneModel(XPMPPlaneID inPlaneID,
const char * inICAOCode,
const char * inAirline,
const char * inLivery);
@@ -582,14 +590,11 @@ XPMPPlaneID XPMPGetNthPlane(
long index);
-/*
- * XPMPSetDefaultPlaneICAO
- *
- * This routine controls what ICAO is used as a backup search criteria for a not-found plane.
- *
- */
-void XPMPSetDefaultPlaneICAO(
- const char * inICAO);
+/// @brief Define default aircraft and ground vehicle ICAO types
+/// @param _acIcaoType Default ICAO aircraft type designator, used when matching returns nothing
+/// @param _carIcaoType Type used to identify ground vehicels (internally defaults to "ZZZC")
+void XPMPSetDefaultPlaneICAO(const char* _acIcaoType,
+ const char* _carIcaoType = nullptr);
/************************************************************************************
* MARK: PLANE OBSERVATION API
diff --git a/Lib/XPMP2/XPMP2.framework/Versions/1.0/XPMP2 b/Lib/XPMP2/XPMP2.framework/Versions/1.0/XPMP2
index 57d9a69e..ffee4e54 100644
Binary files a/Lib/XPMP2/XPMP2.framework/Versions/1.0/XPMP2 and b/Lib/XPMP2/XPMP2.framework/Versions/1.0/XPMP2 differ
diff --git a/Lib/XPMP2/XPMP2.lib b/Lib/XPMP2/XPMP2.lib
index f3470f54..b43c1472 100644
Binary files a/Lib/XPMP2/XPMP2.lib and b/Lib/XPMP2/XPMP2.lib differ
diff --git a/Lib/XPMP2/libXPMP2.a b/Lib/XPMP2/libXPMP2.a
index 7179a5e5..85e3c452 100644
Binary files a/Lib/XPMP2/libXPMP2.a and b/Lib/XPMP2/libXPMP2.a differ
diff --git a/LiveTraffic.rc b/LiveTraffic.rc
index e290b3c0..0909e396 100644
Binary files a/LiveTraffic.rc and b/LiveTraffic.rc differ
diff --git a/LiveTraffic.vcxproj b/LiveTraffic.vcxproj
index e41f4558..976dfc99 100644
--- a/LiveTraffic.vcxproj
+++ b/LiveTraffic.vcxproj
@@ -159,10 +159,8 @@
$(IntDir)$(TargetName).lib
- copy /Y "$(TargetDir)$(TargetName)$(TargetExt)" "D:\X-Plane 11\Resources\plugins\LiveTraffic\win_x64)"
-copy /Y "$(TargetDir)$(TargetName)$(TargetExt)" "D:\X-Plane BETA\Resources\plugins\LiveTraffic\win_x64"
-copy /Y "$(TargetDir)$(TargetName)$(TargetExt)" "G:\11\Resources\plugins\LiveTraffic\win_x64"
-copy /Y "$(TargetDir)$(TargetName)$(TargetExt)" "G:\BETA\Resources\plugins\LiveTraffic\win_x64"
+ copy /Y "$(TargetDir)$(TargetName)$(TargetExt)" "C:\X-Plane\11\Resources\plugins\LiveTraffic\win_x64"
+copy /Y "$(TargetDir)$(TargetName)$(TargetExt)" "C:\X-Plane\BETA\Resources\plugins\LiveTraffic\win_x64"
Copying win.xpl into my X-Plane plugin paths
@@ -208,10 +206,8 @@ copy /Y "$(TargetDir)$(TargetName)$(TargetExt)" "G:\BETA\Resources\plugins\LiveT
$(IntDir)$(TargetName).lib
- copy /Y "$(TargetDir)$(TargetName)$(TargetExt)" "D:\X-Plane 11\Resources\plugins\LiveTraffic\win_x64)"
-copy /Y "$(TargetDir)$(TargetName)$(TargetExt)" "D:\X-Plane BETA\Resources\plugins\LiveTraffic\win_x64"
-copy /Y "$(TargetDir)$(TargetName)$(TargetExt)" "G:\11\Resources\plugins\LiveTraffic\win_x64"
-copy /Y "$(TargetDir)$(TargetName)$(TargetExt)" "G:\BETA\Resources\plugins\LiveTraffic\win_x64"
+ copy /Y "$(TargetDir)$(TargetName)$(TargetExt)" "C:\X-Plane\11\Resources\plugins\LiveTraffic\win_x64"
+copy /Y "$(TargetDir)$(TargetName)$(TargetExt)" "C:\X-Plane\BETA\Resources\plugins\LiveTraffic\win_x64"
Copying win.xpl into my X-Plane plugin paths
diff --git a/LiveTraffic.xcodeproj/project.xcworkspace/xcuserdata/birger.xcuserdatad/xcdebugger/Expressions.xcexplist b/LiveTraffic.xcodeproj/project.xcworkspace/xcuserdata/birger.xcuserdatad/xcdebugger/Expressions.xcexplist
index 48e7ab19..2759bf95 100644
--- a/LiveTraffic.xcodeproj/project.xcworkspace/xcuserdata/birger.xcuserdatad/xcdebugger/Expressions.xcexplist
+++ b/LiveTraffic.xcodeproj/project.xcworkspace/xcuserdata/birger.xcuserdatad/xcdebugger/Expressions.xcexplist
@@ -45,13 +45,13 @@
contextName = "Apt::FindEdgesForHeading(double, double, std::__1::vector<unsigned long, std::__1::allocator<unsigned long> >&, TaxiEdge::edgeTy) const:LTApt.cpp">
+ value = "vecTaxiEdges[222]">
+ value = "lst[1385]">
+ value = "vecTaxiEdgesIdxHead[3321]">
@@ -84,10 +84,10 @@
contextName = "LTFlightData::AddDynData(LTFlightData::FDDynamicData const&, int, int, positionTy*):LTFlightData.cpp">
+ value = "posDeque">
+ value = "dynDataDeque">
@@ -215,10 +215,10 @@
value = "cull">
+ value = "iter->second">
+ value = "tcas">
@@ -394,7 +394,7 @@
value = "from">
+ value = "ppos.v[6]">
@@ -403,7 +403,7 @@
value = "ppos">
+ value = "turn">
@@ -493,10 +493,10 @@
value = "mapFd">
+ value = "sizeof(bool)">
+ value = "dataRefs">
@@ -509,6 +509,9 @@
+
+
@@ -823,6 +826,9 @@
+
+
@@ -973,10 +979,10 @@
value = "_startTime">
+ value = "_targetTime">
+ value = "_deltaDist">
diff --git a/LiveTraffic.xcodeproj/xcuserdata/birger.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/LiveTraffic.xcodeproj/xcuserdata/birger.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist
index f25bc9d1..e96eec2e 100644
--- a/LiveTraffic.xcodeproj/xcuserdata/birger.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist
+++ b/LiveTraffic.xcodeproj/xcuserdata/birger.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist
@@ -3,22 +3,4 @@
uuid = "C8095CDD-1C39-4D01-99B0-6AD4723390BD"
type = "1"
version = "2.0">
-
-
-
-
-
-
diff --git a/Resources/FlightModels.prf b/Resources/FlightModels.prf
index eca5113c..39a8b082 100644
--- a/Resources/FlightModels.prf
+++ b/Resources/FlightModels.prf
@@ -1,4 +1,4 @@
-LiveTraffic 1.1
+LiveTraffic 2.0
# LiveTraffic FlightModels configuration file
@@ -187,5 +187,20 @@ LightAC ^-;
Heli ^L;H
# This last line matches anything and serves as a default:
-
MediumJets .*
+
+[GroundVehicles]
+# This section contains regular expressions to mach against call signs
+# to help identifying ground vehicles
+^[[:alpha:]]{2}[[:digit:]]{1,2}$
+^[[:alpha:]]{1}[[:digit:]]{1,3}$
+^DFS[[:digit:]]*$
+^FRA[[:digit:]]*$
+^LEOS[[:digit:]]*$
+^TUG[[:digit:]]*$
+^AIR[[:digit:]]*$
+^AGL[[:digit:]]*$
+^SAFE[[:digit:]]*$
+^MEDIC[[:digit:]]*$
+^ARFF[[:digit:]]*$
+^MICC$
diff --git a/Src/ACInfoWnd.cpp b/Src/ACInfoWnd.cpp
index c9619312..5874214d 100644
--- a/Src/ACInfoWnd.cpp
+++ b/Src/ACInfoWnd.cpp
@@ -193,10 +193,11 @@ TFTextFieldWidget(_me)
// - registration
// - call sign
// - flight number
-const LTFlightData* TFACSearchEditWidget::SearchFlightData (const std::string ac_key)
+const LTFlightData* TFACSearchEditWidget::SearchFlightData (std::string ac_key)
{
mapLTFlightDataTy::const_iterator fdIter = mapFd.cend();
+ trim(ac_key);
if (!ac_key.empty()) {
// is it a small integer number, i.e. used as index?
if (ac_key.length() <= 3 &&
diff --git a/Src/CoordCalc.cpp b/Src/CoordCalc.cpp
index bd0e39c7..728f749c 100644
--- a/Src/CoordCalc.cpp
+++ b/Src/CoordCalc.cpp
@@ -378,7 +378,7 @@ const char* positionTy::GrndE2String (onGrndE grnd)
std::string positionTy::dbgTxt () const
{
- char buf[120];
+ char buf[200];
snprintf(buf, sizeof(buf), "%.1f: (%7.5f, %7.5f) %7.1fft %8.8s %3.3s %2.2s %-13.13s %4.*zu {h %3.0f%c, p %3.0f, r %3.0f}",
ts(),
lat(), lon(),
diff --git a/Src/DataRefs.cpp b/Src/DataRefs.cpp
index 58d50c48..019c4278 100644
--- a/Src/DataRefs.cpp
+++ b/Src/DataRefs.cpp
@@ -396,7 +396,6 @@ DataRefs::dataRefDefinitionT DATA_REFS_LT[CNT_DATAREFS_LT] = {
{"livetraffic/cfg/auto_start", DataRefs::LTGetInt, DataRefs::LTSetCfgValue, GET_VAR, true },
{"livetraffic/cfg/ai_on_request", DataRefs::LTGetInt, DataRefs::LTSetCfgValue, GET_VAR, true },
{"livetraffic/cfg/ai_controlled", DataRefs::HaveAIUnderControl, NULL, NULL, false },
- {"livetraffic/cfg/ai_skip_noplane", DataRefs::LTGetInt, DataRefs::LTSetCfgValue, GET_VAR, true },
{"livetraffic/cfg/labels", DataRefs::LTGetInt, DataRefs::LTSetCfgValue, GET_VAR, true },
{"livetraffic/cfg/label_shown", DataRefs::LTGetInt, DataRefs::LTSetCfgValue, GET_VAR, true },
{"livetraffic/cfg/label_col_dyn", DataRefs::LTGetInt, DataRefs::LTSetCfgValue, GET_VAR, true },
@@ -451,7 +450,6 @@ void* DataRefs::getVarAddr (dataRefsLT dr)
case DR_CFG_AIRCRAFT_DISPLAYED: return &bShowingAircraft;
case DR_CFG_AUTO_START: return &bAutoStart;
case DR_CFG_AI_ON_REQUEST: return &bAIonRequest;
- case DR_CFG_AI_SKIP_NOPLANE: return &nAISkipAssignNoPlane;
case DR_CFG_LABELS: return &labelCfg;
case DR_CFG_LABEL_SHOWN: return &labelShown;
case DR_CFG_LABEL_COL_DYN: return &bLabelColDynamic;
@@ -1376,19 +1374,6 @@ float DataRefs::GetCfgFloat (dataRefsLT dr)
}
-bool DataRefs::ShallAISkipAssignNoPlane() const
-{
- // Forced a certain value?
- if (nAISkipAssignNoPlane == 0)
- return false;
- if (nAISkipAssignNoPlane == 1)
- return true;
-
- // Using defaults
- return false;
-}
-
-
// more than 24h passed since last version check?
bool DataRefs::NeedNewVerCheck () const
{
@@ -1852,6 +1837,7 @@ bool DataRefs::SetDefaultCarIcaoType(const std::string type)
{
if (1 <= type.length() && type.length() <= 4) {
sDefaultCarIcaoType = type;
+ XPMPSetDefaultPlaneICAO(nullptr, type.c_str()); // inform libxplanemp
LOG_MSG(logINFO,CFG_DEFAULT_CAR_TYP_INFO,sDefaultCarIcaoType.c_str());
return true;
}
diff --git a/Src/LTADSBEx.cpp b/Src/LTADSBEx.cpp
index bf0ebf42..3fb9d749 100644
--- a/Src/LTADSBEx.cpp
+++ b/Src/LTADSBEx.cpp
@@ -143,60 +143,70 @@ bool ADSBExchangeConnection::ProcessFetchedData (mapLTFlightDataTy& fdMap)
if ( fd.empty() )
fd.SetKey(fdKey);
- // fill static data
+ // -- fill static data --
+ LTFlightData::FDStaticData stat;
+ stat.reg = jog_s(pJAc, ADSBEX_REG);
+ stat.country = jog_s(pJAc, ADSBEX_COUNTRY);
+ stat.acTypeIcao = jog_s(pJAc, ADSBEX_AC_TYPE_ICAO);
+ stat.mil = jog_sb(pJAc, ADSBEX_MIL);
+ stat.trt = transpTy(jog_sl(pJAc,ADSBEX_TRT));
+ stat.opIcao = jog_s(pJAc, ADSBEX_OP_ICAO);
+ stat.call = jog_s(pJAc, ADSBEX_CALL);
+
+ // -- dynamic data --
+ LTFlightData::FDDynamicData dyn;
+
+ // ADS-B returns Java tics, that is milliseconds, we use seconds
+ double posTime = jog_sn(pJAc, ADSBEX_POS_TIME) / 1000.0;
+
+ // non-positional dynamic data
+ dyn.radar.code = jog_sl(pJAc, ADSBEX_RADAR_CODE);
+ dyn.gnd = jog_sb(pJAc, ADSBEX_GND);
+ dyn.heading = jog_sn_nan(pJAc, ADSBEX_HEADING);
+ dyn.spd = jog_sn(pJAc, ADSBEX_SPD);
+ dyn.vsi = jog_sn(pJAc, ADSBEX_VSI);
+ dyn.ts = posTime;
+ dyn.pChannel = this;
+
+ // altitude, if airborne; correct for baro pressure difference
+ const double alt_ft = dyn.gnd ? NAN : jog_sn_nan(pJAc, ADSBEX_ELEVATION);
+
+ // position and its ground status
+ positionTy pos (jog_sn_nan(pJAc, ADSBEX_LAT),
+ jog_sn_nan(pJAc, ADSBEX_LON),
+ alt_ft * M_per_FT,
+ posTime,
+ dyn.heading);
+ pos.f.onGrnd = dyn.gnd ? GND_ON : GND_OFF;
+
+ // -- Ground vehicle identification --
+ // ADSBEx doesn't send a clear indicator, but data analysis
+ // suggests that EngType/Mount == 0 is a good indicator
+ if (stat.acTypeIcao.empty() && // don't know a/c type yet
+ dyn.gnd && // on the ground
+ dyn.spd < 50.0 && // reasonable speed
+ stat.engType == 0 && // no engines
+ stat.engMount == 0)
{
- LTFlightData::FDStaticData stat;
- stat.reg = jog_s(pJAc, ADSBEX_REG);
- stat.country = jog_s(pJAc, ADSBEX_COUNTRY);
- stat.acTypeIcao = jog_s(pJAc, ADSBEX_AC_TYPE_ICAO);
- stat.mil = jog_sb(pJAc, ADSBEX_MIL);
- stat.trt = transpTy(jog_sl(pJAc,ADSBEX_TRT));
- stat.opIcao = jog_s(pJAc, ADSBEX_OP_ICAO);
- stat.call = jog_s(pJAc, ADSBEX_CALL);
-
- // update the a/c's master data
- fd.UpdateData(std::move(stat));
+ // we assume ground vehicle
+ stat.acTypeIcao = dataRefs.GetDefaultCarIcaoType();
}
-
- // dynamic data
- { // unconditional...block is only for limiting local variables
- LTFlightData::FDDynamicData dyn;
-
- // ADS-B returns Java tics, that is milliseconds, we use seconds
- double posTime = jog_sn(pJAc, ADSBEX_POS_TIME) / 1000.0;
-
- // non-positional dynamic data
- dyn.radar.code = jog_sl(pJAc, ADSBEX_RADAR_CODE);
- dyn.gnd = jog_sb(pJAc, ADSBEX_GND);
- dyn.heading = jog_sn_nan(pJAc, ADSBEX_HEADING);
- dyn.spd = jog_sn(pJAc, ADSBEX_SPD);
- dyn.vsi = jog_sn(pJAc, ADSBEX_VSI);
- dyn.ts = posTime;
- dyn.pChannel = this;
-
- // altitude, if airborne; correct for baro pressure difference
- const double alt_ft = dyn.gnd ? NAN : jog_sn_nan(pJAc, ADSBEX_ELEVATION);
- // position and its ground status
- positionTy pos (jog_sn_nan(pJAc, ADSBEX_LAT),
- jog_sn_nan(pJAc, ADSBEX_LON),
- alt_ft * M_per_FT,
- posTime,
- dyn.heading);
- pos.f.onGrnd = dyn.gnd ? GND_ON : GND_OFF;
-
- // position is rather important, we check for validity
- if ( pos.isNormal(true) ) {
- // ADSBEx, especially the RAPID API version, returns
- // aircraft regardless of distance. To avoid planes
- // created and immediately removed due to distanced settings
- // we continue only if pos is within wanted range
- if ( pos.dist(viewPos) <= dataRefs.GetFdStdDistance_m() )
- fd.AddDynData(dyn, 0, 0, &pos);
- }
- else
- LOG_MSG(logDEBUG,ERR_POS_UNNORMAL,fdKey.c_str(),pos.dbgTxt().c_str());
+ // update the a/c's master data
+ fd.UpdateData(std::move(stat));
+
+ // position is rather important, we check for validity
+ if ( pos.isNormal(true) ) {
+ // ADSBEx, especially the RAPID API version, returns
+ // aircraft regardless of distance. To avoid planes
+ // created and immediately removed due to distanced settings
+ // we continue only if pos is within wanted range
+ if ( pos.dist(viewPos) <= dataRefs.GetFdStdDistance_m() )
+ fd.AddDynData(dyn, 0, 0, &pos);
}
+ else
+ LOG_MSG(logDEBUG,ERR_POS_UNNORMAL,fdKey.c_str(),pos.dbgTxt().c_str());
+
} catch(const std::system_error& e) {
LOG_MSG(logERR, ERR_LOCK_ERROR, "mapFd", e.what());
}
diff --git a/Src/LTAircraft.cpp b/Src/LTAircraft.cpp
index 191a266e..7b24996d 100644
--- a/Src/LTAircraft.cpp
+++ b/Src/LTAircraft.cpp
@@ -789,6 +789,9 @@ std::list listFlightModels;
typedef std::pair regexFM;
std::list listFMRegex;
+/// List of regular expressions matching call signs of ground vehicles
+std::list listCarRegex;
+
// global constant for a default model
const LTAircraft::FlightModel MDL_DEFAULT;
@@ -934,6 +937,24 @@ bool fm_processMapLine (const char* fileName, int ln,
return true;
}
+/// Processes a line in the [GroundVehicles] section by just storing the regEx
+bool fm_processCarLine (const char* fileName, int ln,
+ const std::string& text)
+{
+ // check for valid regular expression
+ std::regex carRe;
+ try {
+ carRe.assign (text);
+ } catch (const std::regex_error& e) {
+ LOG_MSG(logWARN, ERR_FM_REGEX, e.what(), fileName, ln, text.c_str());
+ return false;
+ }
+
+ // add a list entry
+ listCarRegex.emplace_back(std::move(carRe));
+ return true;
+}
+
// read and process the FlightNodel.prf file
bool LTAircraft::FlightModel::ReadFlightModelFile ()
@@ -966,8 +987,14 @@ bool LTAircraft::FlightModel::ReadFlightModelFile ()
}
// then follow sections and their entries
- // state signifies what kind of section we are currently reading
- enum fmFileStateTy { FM_NO_SECTION, FM_MODEL_SECTION, FM_MAP } fmState = FM_NO_SECTION;
+ /// state signifies what kind of section we are currently reading
+ enum fmFileStateTy {
+ FM_NO_SECTION = 0, ///< before the first flight model section
+ FM_MODEL_SECTION, ///< processing a flight model section
+ FM_MAP, ///< Processing the [Map] section
+ FM_CAR, ///< Processing the [GroundVehicles] section
+ FM_IGNORE, ///< Found a section after [Map], which we just ignore
+ } fmState = FM_NO_SECTION;
FlightModel fm;
int errCnt = 0;
for (int ln=1; fIn && errCnt <= ERR_CFG_FILE_MAXWARN; ln++) {
@@ -999,23 +1026,26 @@ bool LTAircraft::FlightModel::ReadFlightModelFile ()
text.erase(text.length()-1);
// finish previous model section first
- // (as [Map] has to be last this will safely be executed for all FlightModel sections)
+ // (as [Map] has to be after FlightModel sections
+ // this will safely be executed for all FlightModel sections)
if (fm && fmState == FM_MODEL_SECTION) {
// i.e. add the defined model to the list
push_back_unique(listFlightModels, fm);
}
- else if (fmState == FM_MAP) {
- // there should be no section after [Map],
- // ignore remainder of file
- LOG_MSG(logWARN, ERR_CFG_FORMAT, sFileName.c_str(), ln,
- ERR_FM_NOT_AFTER_MAP);
- errCnt++;
- break;
- }
// identify map section?
if (text == FM_MAP_SECTION) {
fmState = FM_MAP;
+ // identify cars section?
+ } else if (text == FM_CAR_SECTION) {
+ fmState = FM_CAR;
+ // are we beyond all flightModel sections already?
+ } else if (fmState > FM_MODEL_SECTION) {
+ // There must not be unknown section any longer!
+ LOG_MSG(logWARN, ERR_CFG_FORMAT, sFileName.c_str(), ln,
+ ERR_FM_NOT_AFTER_MAP);
+ errCnt++;
+ fmState = FM_IGNORE;
} else {
// no, so it must be a new model section
fmState = FM_MODEL_SECTION;
@@ -1064,6 +1094,14 @@ bool LTAircraft::FlightModel::ReadFlightModelFile ()
if (!fm_processMapLine(sFileName.c_str(), ln, text))
errCnt++;
break;
+
+ case FM_CAR:
+ if (!fm_processCarLine(sFileName.c_str(), ln, text))
+ errCnt++;
+ break;
+
+ case FM_IGNORE:
+ break;
}
}
@@ -1093,14 +1131,14 @@ bool LTAircraft::FlightModel::ReadFlightModelFile ()
// based on an aircraft type find a matching flight model
const LTAircraft::FlightModel& LTAircraft::FlightModel::FindFlightModel
- (const std::string acTypeIcao)
+ (const std::string& acTypeIcao)
{
// 1. find aircraft type specification in the Doc8643
const Doc8643& acType = Doc8643::get(acTypeIcao);
const std::string acSpec (acType); // the string to match
// 2. walk through the Flight Model map list and try each regEx pattern
- for (auto mapIt: listFMRegex) {
+ for (const auto& mapIt: listFMRegex) {
std::smatch m;
std::regex_search(acSpec, m, mapIt.first);
if (m.size() > 0) // matches?
@@ -1116,7 +1154,7 @@ const LTAircraft::FlightModel& LTAircraft::FlightModel::FindFlightModel
// return a ptr to a flight model based on its model or [section] name
// returns nullptr if not found
const LTAircraft::FlightModel* LTAircraft::FlightModel::GetFlightModel
- (const std::string modelName)
+ (const std::string& modelName)
{
// search through list of flight models, match by modelName
auto fmIt = std::find_if(listFlightModels.cbegin(),
@@ -1126,6 +1164,20 @@ const LTAircraft::FlightModel* LTAircraft::FlightModel::GetFlightModel
return fmIt == listFlightModels.cend() ? nullptr : &*fmIt;
}
+// Tests if the given call sign matches typical call signs of ground vehicles
+bool LTAircraft::FlightModel::MatchesCar (const std::string& _callSign)
+{
+ // Walk the car regEx list and try each pattern
+ for (const std::regex& re: listCarRegex) {
+ std::smatch m;
+ std::regex_search(_callSign, m, re);
+ if (m.size() > 0) // matches?
+ return true;
+ }
+ // no match found
+ return false;
+}
+
//
//MARK: LTAircraft::FlightPhase
//
@@ -1168,7 +1220,8 @@ LTAircraft::LTAircraft(LTFlightData& inFd) :
// Debug options to set fixed type/op/livery take precedence
XPCAircraft(str_first_non_empty({dataRefs.cslFixAcIcaoType, inFd.WaitForSafeCopyStat().acTypeIcao}).c_str(),
str_first_non_empty({dataRefs.cslFixOpIcao, inFd.WaitForSafeCopyStat().airlineCode()}).c_str(),
- str_first_non_empty({dataRefs.cslFixLivery, inFd.WaitForSafeCopyStat().reg}).c_str()),
+ str_first_non_empty({dataRefs.cslFixLivery, inFd.WaitForSafeCopyStat().reg}).c_str(),
+ (XPMPPlaneID)inFd.key().num),
// class members
fd(inFd),
mdl(FlightModel::FindFlightModel(inFd.WaitForSafeCopyStat().acTypeIcao)), // find matching flight model
@@ -1906,6 +1959,14 @@ void LTAircraft::CalcFlightModel (const positionTy& /*from*/, const positionTy&
if ( !bOnGrndPrev && bOnGrnd && bFPhPrev != FPH_UNKNOWN ) {
phase = FPH_TOUCH_DOWN;
}
+
+ // *** Cars will not actually fly, so we only allow for a limited set of status
+ if (fd.GetUnsafeStat().isGrndVehicle() &&
+ phase != FPH_TAXI &&
+ phase != FPH_STOPPED_ON_RWY)
+ {
+ phase = FPH_TAXI;
+ }
// *** take action based on flight phase (change) ***
@@ -2612,6 +2673,10 @@ XPMPPlaneCallbackResult LTAircraft::GetPlanePosition(XPMPPlanePosition_t* outPos
if (!dataRefs.IsReInitAll() && // avoid any calc if to be re-initialized
CalcPPos())
{
+ // If needed update the chosen CSL model
+ if (ShallUpdateModel())
+ ChangeModel();
+
// copy ppos (by type conversion)
*outPosition = ppos;
@@ -2804,14 +2869,19 @@ XPMPPlaneCallbackResult LTAircraft::GetInfoTexts(XPMPInfoTexts_t* outInfo)
}
// change the model (e.g. when model-defining static data changed)
-void LTAircraft::ChangeModel (const LTFlightData::FDStaticData& statData)
+void LTAircraft::ChangeModel ()
{
+ // Try to fetch the static data
+ LTFlightData::FDStaticData statData;
+ if (!fd.TryGetSafeCopy(statData))
+ return;
+
+ // Save previous model name to identify an actual change
const std::string oldModelName(GetModelName());
CalcLabelInternal(statData);
- XPMPChangePlaneModel(mPlane,
- statData.acTypeIcao.c_str(),
- statData.opIcao.c_str(),
- statData.reg.c_str());
+ XPMP2::Aircraft::ChangeModel(str_first_non_empty({dataRefs.cslFixAcIcaoType, statData.acTypeIcao}),
+ str_first_non_empty({dataRefs.cslFixOpIcao, statData.airlineCode()}),
+ str_first_non_empty({dataRefs.cslFixLivery, statData.reg}));
// if there was an actual change inform the log
if (oldModelName != GetModelName()) {
@@ -2820,4 +2890,7 @@ void LTAircraft::ChangeModel (const LTFlightData::FDStaticData& statData)
statData.opIcao.c_str(),
GetModelName().c_str());
}
+
+ // reset the flag that we needed to change the model
+ bChangeModel = false;
}
diff --git a/Src/LTApt.cpp b/Src/LTApt.cpp
index 13b067f0..35562e6f 100644
--- a/Src/LTApt.cpp
+++ b/Src/LTApt.cpp
@@ -1851,7 +1851,7 @@ void Apt::AddApt (Apt&& apt)
listPaths.clear();
vecPathEnds.clear();
-#ifdef DEBUG
+#ifdef APT_DUMP
if (dataRefs.GetLogLevel() == logDEBUG)
LTAptDump(key);
#endif
@@ -2606,7 +2606,7 @@ void LTAptDisable ()
}
-#ifdef DEBUG
+#ifdef APT_DUMP
// Dumps the entire taxi network into a CSV file readable by GPS Visualizer
/// @see For a suggestion of settings for display:
/// https://www.gpsvisualizer.com/map_input?bg_map=google_openstreetmap&bg_opacity=70&form=leaflet&google_wpt_sym=diamond&trk_list=0&trk_opacity=100&trk_width=2&units=metric&width=1400&wpt_color=aqua
diff --git a/Src/LTFlightData.cpp b/Src/LTFlightData.cpp
index bf1b3fff..a8cd6d76 100644
--- a/Src/LTFlightData.cpp
+++ b/Src/LTFlightData.cpp
@@ -70,22 +70,38 @@ std::string LTFlightData::FDDynamicData::GetSquawk() const
}
}
-LTFlightData::FDStaticData& LTFlightData::FDStaticData::operator |= (const FDStaticData& other)
+// Merges data, i.e. copy only filled fields from 'other'
+bool LTFlightData::FDStaticData::merge (const FDStaticData& other)
{
+ // Have matching-relevant fields changed?
+ bool bRet = false;
+
// copy filled, and only filled data over current data
// do it field-by-field only for fields which are actually filled
-
- // acTypeICAO: accept another value only if our current one is not helpful
- if ((acTypeIcao.empty() ||
- acTypeIcao == dataRefs.GetDefaultAcIcaoType() ||
- acTypeIcao == dataRefs.GetDefaultCarIcaoType()) &&
- !other.acTypeIcao.empty())
- acTypeIcao = other.acTypeIcao;
+
+ // acTypeICAO
+ // We never overwrite with nothing, ie. the new value must be _something_
+ if (!other.acTypeIcao.empty() &&
+ acTypeIcao != other.acTypeIcao)
+ {
+ // Accept anything if we are currently empty (unknown/default) or a car, can't be worse...
+ if (acTypeIcao.empty() ||
+ acTypeIcao == dataRefs.GetDefaultCarIcaoType())
+ {
+ acTypeIcao = other.acTypeIcao;
+ bRet = true;
+ }
+ // else: we are non-empty, non-default -> no change, no matter what is delivered,
+ // to avoid ping-ponging the a/c plane when different channels have different opinion
+ }
// a/c details
if (!other.country.empty()) country = other.country;
if (!other.man.empty()) man = other.man;
- if (other.mdl.length() > mdl.length()) mdl = other.mdl;
+ if (other.mdl.length() > mdl.length()) {
+ mdl = other.mdl;
+ bRet = true;
+ }
if (!other.catDescr.empty()) catDescr = other.catDescr;
if (other.year) year = other.year;
if (other.mil) mil = other.mil; // this only overwrite if 'true'...
@@ -105,8 +121,18 @@ LTFlightData::FDStaticData& LTFlightData::FDStaticData::operator |= (const FDSta
// operator / Airline
if (!other.op.empty()) op = other.op;
- if (!other.opIcao.empty()) opIcao = other.opIcao;
+ // operator ICAO: we only accept a change from nothing to something
+ if (opIcao.empty() && !other.opIcao.empty()) {
+ opIcao = other.opIcao;
+ bRet = true;
+ }
+ // registration: we only accept a change from nothing to something
+ if (reg.empty() && !other.reg.empty()) {
+ reg = other.reg;
+ bRet = true;
+ }
+
// now initialized
bInit = true;
@@ -118,7 +144,7 @@ LTFlightData::FDStaticData& LTFlightData::FDStaticData::operator |= (const FDSta
if (mdl.empty())
mdl = pDoc8643->model;
- return *this;
+ return bRet;
}
// route (this is "originAp - destAp", but considers emoty txt)
@@ -163,6 +189,12 @@ std::string LTFlightData::FDStaticData::flightRoute() const
return (flight + ": ") + r;
}
+// is this a ground vehicle?
+bool LTFlightData::FDStaticData::isGrndVehicle() const
+{
+ return acTypeIcao == dataRefs.GetDefaultCarIcaoType();
+}
+
// set the key value
std::string LTFlightData::FDKeyTy::SetKey (FDKeyType _eType, unsigned long _num)
{
@@ -943,7 +975,7 @@ bool LTFlightData::CalcNextPos ( double simTime )
stopPos.f.flightPhase != FPH_TOUCH_DOWN && // don't copy touch down pos, that looks ugly, and hinders auto-land/stop
stopPos.f.flightPhase != FPH_STOPPED_ON_RWY) // avoid adding several stops
{
- stopPos.ts() += simTime + 10.0; // just assume _some_ time
+ stopPos.ts() = simTime + 10.0; // just assume _some_ time
stopPos.f.flightPhase = FPH_STOPPED_ON_RWY; // indicator for aritifical stop (not only on rwy now...)
if (dataRefs.GetDebugAcPos(key()))
LOG_MSG(logDEBUG, "%s: Added stop-position %s",
@@ -2121,38 +2153,49 @@ void LTFlightData::UpdateData (const LTFlightData::FDStaticData& inStat)
LTACMasterdataChannel::RequestMasterData (key(), inStat.call);
}
- // merge inStat into our statData (copy only filled fields,
- // which are not model-defining):
- statData |= inStat;
-
- // *** take care of changes in model-defining fields ***
- // (a/c type, operator, registration)
+ // If no a/c type is yet known try if the call sign / operator looks like a ground vehicle
bool bMdlInfoChange = false;
-
- // operator ICAO: we only accept a change from nothing to something
- if (statData.opIcao.empty() && !inStat.opIcao.empty())
+ if (statData.acTypeIcao.empty() && inStat.acTypeIcao.empty())
{
- statData.opIcao = inStat.opIcao;
- bMdlInfoChange = true;
+ // Try operator first
+ if (!inStat.op.empty()) {
+ std::string op_u = inStat.op;
+ str_toupper(op_u);
+ if (op_u.find("AIRPORT") != std::string::npos) {
+ statData.op = inStat.op;
+ statData.acTypeIcao = dataRefs.GetDefaultCarIcaoType();
+ LOG_MSG(logINFO, INFO_GND_VEHICLE_APT, key().c_str(), inStat.op.c_str());
+ bMdlInfoChange = true;
+ }
+ }
+
+ // Try callsign next
+ if (statData.acTypeIcao.empty() &&
+ !inStat.call.empty() && inStat.call != statData.call &&
+ LTAircraft::FlightModel::MatchesCar(inStat.call))
+ {
+ statData.call = inStat.call;
+ statData.acTypeIcao = dataRefs.GetDefaultCarIcaoType();
+ LOG_MSG(logINFO, INFO_GND_VEHICLE_CALL, key().c_str(), inStat.call.c_str());
+ bMdlInfoChange = true;
+ }
}
- // registration: we only accept a change from nothing to something
- if (statData.reg.empty() && !inStat.reg.empty())
- {
- statData.reg = inStat.reg;
+ // merge inStat into our statData and save if matching-relevant stuff changed
+ if (statData.merge(inStat))
bMdlInfoChange = true;
- }
// Re-determine a/c model (only if it was determined before:
- // the first determination shall be made as late as possible
+ // the very first determination shall be made as late as possible
// in LTFlightData::CreateAircraft())
if (pAc && DetermineAcModel())
bMdlInfoChange = true;
- // if model-defining fields changed then (potentially) change the CSL model
if (pAc) {
+ // if model-defining fields changed then (potentially) change the CSL model
if (bMdlInfoChange)
- pAc->ChangeModel (statData);
+ pAc->SetUpdateModel();
+ // Make Aircraft send updated info texts
pAc->SetSendNewInfoData();
}
@@ -2262,10 +2305,16 @@ bool LTFlightData::AircraftMaintenance ( double simTime )
// try interpreting model text or check for ground vehicle
bool LTFlightData::DetermineAcModel()
{
- // We don't change the a/c type if it is already something reasonable
const std::string prevType = statData.acTypeIcao;
+
+ // Debugging model matching: If the model is fixed, then it is what it is
+ if (!dataRefs.cslFixAcIcaoType.empty()) {
+ statData.acTypeIcao = dataRefs.cslFixAcIcaoType;
+ return statData.acTypeIcao != prevType;
+ }
+
+ // We don't change the a/c type if it is already something reasonable
if (!prevType.empty() &&
- prevType != dataRefs.GetDefaultAcIcaoType() &&
prevType != dataRefs.GetDefaultCarIcaoType())
return false;
@@ -2286,47 +2335,29 @@ bool LTFlightData::DetermineAcModel()
return false;
}
- // Ground vehicle maybe? Shall be on the ground then
- if ((
- (pAc && pAc->IsOnGrnd() && pAc->GetSpeed_kt() < pAc->mdl.MAX_TAXI_SPEED) ||
- (!posDeque.empty() && posDeque.front().IsOnGnd())
- ) &&
- // OpenSky only delivers "category description" and has a
- // pretty clear indicator for a ground vehicle
- (statData.catDescr.find(OPSKY_MD_TEXT_VEHICLE) != std::string::npos ||
- // I'm having the feeling that if nearly all is empty and the category description is "No Info" then it's often also a ground vehicle
- (statData.catDescr.find(OPSKY_MD_TEXT_NO_CAT) != std::string::npos &&
- statData.man.empty() && statData.mdl.empty() && statData.opIcao.empty()) ||
- // ADSBEx doesn't send as clear an indicator, but data analysis
- // suggests that EngType/Mount == 0 is a good indicator
- (statData.engType == 0 && statData.engMount == 0) ||
- // for RealTraffic it is even more difficult...we best identify RT-only data with no operator (opIcao is taken from call sign)
- (statData.op.empty() && statData.reg.empty() && statData.destAp.empty()))
- )
+ // Ground vehicle maybe? Shall be on the ground then with reasonable speed
+ // (The info if this _could_ be a car is delivered by the channels via
+ // the acTypeIcao, here we just validate if the dynamic situation
+ // fits a car.)
+ if (prevType == dataRefs.GetDefaultCarIcaoType())
{
- // We would now decide for surface vehicle
- // but we only do so if we had no other type before
- // (ie. if we previously had decided for standard a/c then we keep that!)
- if (prevType.empty()) {
+ if ((pAc && // plane exists?
+ pAc->IsOnGrnd() && // must be on ground with reasonable speed
+ pAc->GetSpeed_kt() <= MDL_CAR_MAX_TAXI) ||
+ (!pAc && // no plane yet:
+ posDeque.size() >= 2 && // analyse ground status of and speed between first two positions
+ posDeque.front().IsOnGnd() && posDeque[1].IsOnGnd() &&
+ posDeque.front().speed_kt(posDeque[1]) <= MDL_CAR_MAX_TAXI))
+ {
+ // We now decide for surface vehicle
statData.acTypeIcao = dataRefs.GetDefaultCarIcaoType();
- return true;
- } else {
- statData.acTypeIcao = std::move(prevType);
- return false;
+ return statData.acTypeIcao != prevType;
}
}
-
- // we have no better idea than standard
- statData.acTypeIcao = dataRefs.GetDefaultAcIcaoType();
- if (prevType != statData.acTypeIcao)
- {
- LOG_MSG(logWARN,ERR_NO_AC_TYPE,
- key().c_str(),
- statData.man.c_str(), statData.mdl.c_str(),
- statData.acTypeIcao.c_str());
- return true;
- }
- return false;
+
+ // we have no better idea than default
+ statData.acTypeIcao.clear();
+ return prevType != statData.acTypeIcao;
}
// checks if there is a slot available to create this a/c, tries to remove the farest a/c if too many a/c rendered
@@ -2415,7 +2446,13 @@ bool LTFlightData::CreateAircraft ( double simTime )
// Make sure we have a valid a/c model now
DetermineAcModel();
-
+ if (statData.acTypeIcao.empty()) { // we don't...
+ LOG_MSG(logWARN,ERR_NO_AC_TYPE,
+ key().c_str(),
+ statData.man.c_str(), statData.mdl.c_str(),
+ dataRefs.GetDefaultAcIcaoType().c_str());
+ }
+
// create the object (constructor will recursively re-access the lock)
try {
pAc = new LTAircraft(*this);
@@ -2465,7 +2502,7 @@ void LTFlightData::UpdateAllModels ()
// if there is an aircraft update it's flight model
LTAircraft* pAc = fdPair.second.GetAircraft();
if (pAc)
- pAc->ChangeModel(fdPair.second.GetUnsafeStat());
+ pAc->SetUpdateModel();
}
} catch(const std::system_error& e) {
LOG_MSG(logERR, ERR_LOCK_ERROR, "mapFd", e.what());
diff --git a/Src/LTMain.cpp b/Src/LTMain.cpp
index 4474a7d3..fc7296de 100644
--- a/Src/LTMain.cpp
+++ b/Src/LTMain.cpp
@@ -28,6 +28,9 @@
#include // for ShellExecuteA
#endif
+// Puts some timestamps into the log for analysis purposes
+void LogTimestamps ();
+
//MARK: Path helpers
// construct path: if passed-in base is a full path just take it
@@ -268,6 +271,8 @@ float LoopCBAircraftMaintenance (float inElapsedSinceLastCall, float, int, void*
dataRefs.SetUseHistData(dataRefs.GetUseHistData(), true);
// and reset the re-init flag
dataRefs.SetReInitAll(false);
+ // Log a new timestamp
+ LogTimestamps();
}
} catch (const std::exception& e) {
// Exception during re-init...we give up and disable ourselves
@@ -305,8 +310,12 @@ float LoopCBAircraftMaintenance (float inElapsedSinceLastCall, float, int, void*
int MPIntPrefsFunc (const char*, const char* key, int iDefault)
{
// debug XPMP's CSL model matching if requested
- if (!strcmp(key, XPMP_CFG_ITM_MODELMATCHING))
- return dataRefs.GetDebugModelMatching();
+ if (!strcmp(key, XPMP_CFG_ITM_MODELMATCHING)) {
+ if constexpr (VERSION_BETA) // force logging of model-matching in BETA versions
+ return true;
+ else
+ return dataRefs.GetDebugModelMatching();
+ }
// logging level to match ours
if (!strcmp(key, XPMP_CFG_ITM_LOGLEVEL)) {
if constexpr (VERSION_BETA) // force DEBUG-level logging in BETA versions
@@ -317,10 +326,6 @@ int MPIntPrefsFunc (const char*, const char* key, int iDefault)
// We don't want clamping to the ground, we take care of the ground ourselves
if (!strcmp(key, XPMP_CFG_ITM_CLAMPALL)) return 0;
- // Backdoor to skip assigning the NoPlane.acf to AI planes
- if (!strcmp(key, XPMP_CFG_ITM_SKIP_NOPLANE))
- return dataRefs.ShallAISkipAssignNoPlane();
-
// dont' know/care about the option, return the default value
return iDefault;
}
@@ -374,7 +379,8 @@ bool LTMainInit ()
const char* cszResult = XPMPMultiplayerInit (LIVE_TRAFFIC,
LTCalcFullPluginPath(PATH_RESOURCES).c_str(),
&MPIntPrefsFunc,
- dataRefs.GetDefaultAcIcaoType().c_str());
+ dataRefs.GetDefaultAcIcaoType().c_str(),
+ LIVE_TRAFFIC_XPMP2);
if ( cszResult[0] ) {
LOG_MSG(logFATAL,ERR_XPMP_ENABLE, cszResult);
XPMPMultiplayerCleanup();
@@ -495,6 +501,7 @@ static float CBToggleAI (float, float, int, void *)
LTMainReleaseAIAircraft();
else
LTMainTryGetAIAircraft();
+ MenuUpdateAllItemStatus();
return 0.0f;
}
@@ -540,6 +547,9 @@ void LTMainHideAircraft ()
// hide aircraft, disconnect internet streams
LTFlightDataHideAircraft ();
+
+ // Remove any message about seeing planes
+ CreateMsgWindow(float(AC_MAINT_INTVL), 0, 0, -1);
// disable the flight loop callback
XPLMSetFlightLoopCallbackInterval(LoopCBAircraftMaintenance,
@@ -548,7 +558,9 @@ void LTMainHideAircraft ()
NULL);
// disable aircraft drawing, free up multiplayer planes
- XPMPMultiplayerDisable();
+ // (the "soft way", which requires a few more drawing cycles,
+ // this will _not_ work while being shut down)
+ LTMainToggleAI(false);
// tell the user there are no more
SHOW_MSG(logINFO, MSG_NUM_AC_ZERO);
@@ -562,6 +574,7 @@ void LTMainDisable ()
// remove aircraft...just to be sure
dataRefs.SetAircraftDisplayed(false);
+ LTMainReleaseAIAircraft(); // to be absolutely sure
// disable fetching flight data
LTFlightDataDisable();
diff --git a/Src/LTOpenSky.cpp b/Src/LTOpenSky.cpp
index f65fec25..77718e4d 100644
--- a/Src/LTOpenSky.cpp
+++ b/Src/LTOpenSky.cpp
@@ -245,16 +245,6 @@ bool OpenSkyAcMasterdata::FetchAllData (const positionTy& /*pos*/)
break;
case HTTP_NOT_FOUND: // doesn't know a/c, don't query again
invIcaos.emplace_back(info.acKey.icao);
-
- // We add "model=[?], owner=[?]". By this way we don't trigger the car detection,
- // that means: If we don't know the transponder at all we'd rather decide on standard a/c but not car
- if (data.length() > 1) // concatenate both JSON groups
- data += ", ";
- data += "\"" OPSKY_MD_GROUP "\": { \"" OPSKY_MD_TRANSP_ICAO "\": \"";
- data += currKey;
- data += "\", \"" OPSKY_MD_MDL "\": \"" OPSKY_MD_MDL_UNKNOWN "\", "
- "\"" OPSKY_MD_OP "\": \"" OPSKY_MD_MDL_UNKNOWN "\" }";
-
bChannelOK = true; // but technically a valid response
break;
case HTTP_BAD_REQUEST: // uh uh...done something wrong, don't do that again
@@ -412,6 +402,21 @@ bool OpenSkyAcMasterdata::ProcessFetchedData (mapLTFlightDataTy& /*fdMap*/)
statDat.catDescr = jog_s(pJAc, OPSKY_MD_CAT_DESCR);
statDat.op = jog_s(pJAc, OPSKY_MD_OP);
statDat.opIcao = jog_s(pJAc, OPSKY_MD_OP_ICAO);
+
+ // -- Ground vehicle identification --
+ // OpenSky only delivers "category description" and has a
+ // pretty clear indicator for a ground vehicle
+ if (statDat.acTypeIcao.empty() && // don't know a/c type yet
+ (statDat.catDescr.find(OPSKY_MD_TEXT_VEHICLE) != std::string::npos ||
+ // I'm having the feeling that if nearly all is empty and the category description is "No Info" then it's often also a ground vehicle
+ (statDat.catDescr.find(OPSKY_MD_TEXT_NO_CAT) != std::string::npos &&
+ statDat.man.empty() &&
+ statDat.mdl.empty() &&
+ statDat.opIcao.empty())))
+ {
+ // we assume ground vehicle
+ statDat.acTypeIcao = dataRefs.GetDefaultCarIcaoType();
+ }
}
// *** Route Information ***
diff --git a/Src/LTRealTraffic.cpp b/Src/LTRealTraffic.cpp
index 961a3c19..14591ed0 100644
--- a/Src/LTRealTraffic.cpp
+++ b/Src/LTRealTraffic.cpp
@@ -224,7 +224,7 @@ std::string RealTrafficConnection::GetStatusStr() const
{
switch (status) {
case RT_STATUS_NONE: return "";
- case RT_STATUS_STARTING: return "Starting...";
+ case RT_STATUS_STARTING: return "Waiting for RealTraffic...";
case RT_STATUS_CONNECTED_PASSIVELY: return "Connected passively";
case RT_STATUS_CONNECTED_TO: return "Connected, waiting...";
case RT_STATUS_CONNECTED_FULL: return "Fully connected";
@@ -719,69 +719,79 @@ bool RealTrafficConnection::ProcessRecvedTrafficData (const char* traffic)
if ( fd.empty() )
fd.SetKey(fdKey);
- // fill static data
- {
- LTFlightData::FDStaticData stat;
-
- stat.acTypeIcao = tfc[RT_TFC_TYPE];
- stat.call = tfc[RT_TFC_CS];
-
- if (tfc[RT_TFC_MSG_TYPE] == RT_TRAFFIC_AITFC) {
- stat.reg = tfc[RT_TFC_TAIL];
- stat.originAp = tfc[RT_TFC_FROM];
- stat.destAp = tfc[RT_TFC_TO];
- }
+ // -- fill static data --
+ LTFlightData::FDStaticData stat;
+
+ stat.acTypeIcao = tfc[RT_TFC_TYPE];
+ stat.call = tfc[RT_TFC_CS];
+
+ if (tfc[RT_TFC_MSG_TYPE] == RT_TRAFFIC_AITFC) {
+ stat.reg = tfc[RT_TFC_TAIL];
+ stat.originAp = tfc[RT_TFC_FROM];
+ stat.destAp = tfc[RT_TFC_TO];
+ }
- fd.UpdateData(std::move(stat));
+ // -- dynamic data --
+ LTFlightData::FDDynamicData dyn;
+
+ // non-positional dynamic data
+ dyn.gnd = tfc[RT_TFC_AIRBORNE] == "0";
+ dyn.heading = std::stoi(tfc[RT_TFC_HDG]);
+ dyn.spd = std::stoi(tfc[RT_TFC_SPD]);
+ dyn.vsi = std::stoi(tfc[RT_TFC_VS]);
+ dyn.ts = posTime;
+ dyn.pChannel = this;
+
+ // *** gnd detection hack ***
+ // RealTraffic keeps the airborne flag always 1,
+ // even with traffic which definitely sits on the gnd.
+ // Also, reported altitude never seems to become negative,
+ // though this would be required in high pressure weather
+ // at airports roughly at sea level.
+ // And altitude is rounded to 250ft which means that close
+ // to the ground it could be rounded down to 0!
+ //
+ // If "0" is reported we need to assume "on gnd" and bypass
+ // the pressure correction.
+ // If at the same time VSI is reported significantly (> +/- 100)
+ // then we assume plane is already/still flying, but as we
+ // don't know exact altitude we just skip this record.
+ if (tfc[RT_TFC_ALT] == "0") {
+ // skip this dynamic record in case VSI is too large
+ if (std::abs(dyn.vsi) > RT_VSI_AIRBORNE)
+ return true;
+ // have proper gnd altitude calculated
+ pos.alt_m() = NAN;
+ dyn.gnd = true;
+ } else {
+ // probably not on gnd, so take care of altitude
+ // altitude comes without local pressure applied
+ double alt_f = std::stod(tfc[RT_TFC_ALT]);
+ alt_f += (hPa - HPA_STANDARD) * FT_per_HPA;
+ pos.SetAltFt(alt_f);
}
- // dynamic data
- { // unconditional...block is only for limiting local variables
- LTFlightData::FDDynamicData dyn;
-
- // non-positional dynamic data
- dyn.gnd = tfc[RT_TFC_AIRBORNE] == "0";
- dyn.heading = std::stoi(tfc[RT_TFC_HDG]);
- dyn.spd = std::stoi(tfc[RT_TFC_SPD]);
- dyn.vsi = std::stoi(tfc[RT_TFC_VS]);
- dyn.ts = posTime;
- dyn.pChannel = this;
-
- // *** gnd detection hack ***
- // RealTraffic keeps the airborne flag always 1,
- // even with traffic which definitely sits on the gnd.
- // Also, reported altitude never seems to become negative,
- // though this would be required in high pressure weather
- // at airports roughly at sea level.
- // And altitude is rounded to 250ft which means that close
- // to the ground it could be rounded down to 0!
- //
- // If "0" is reported we need to assume "on gnd" and bypass
- // the pressure correction.
- // If at the same time VSI is reported significantly (> +/- 100)
- // then we assume plane is already/still flying, but as we
- // don't know exact altitude we just skip this record.
- if (tfc[RT_TFC_ALT] == "0") {
- // skip this dynamic record in case VSI is too large
- if (std::abs(dyn.vsi) > RT_VSI_AIRBORNE)
- return true;
- // have proper gnd altitude calculated
- pos.alt_m() = NAN;
- dyn.gnd = true;
- } else {
- // probably not on gnd, so take care of altitude
- // altitude comes without local pressure applied
- double alt_f = std::stod(tfc[RT_TFC_ALT]);
- alt_f += (hPa - HPA_STANDARD) * FT_per_HPA;
- pos.SetAltFt(alt_f);
- }
-
- // don't forget gnd-flag in position
- pos.f.onGrnd = dyn.gnd ? GND_ON : GND_OFF;
-
- // add dynamic data
- fd.AddDynData(dyn, 0, 0, &pos);
+ // don't forget gnd-flag in position
+ pos.f.onGrnd = dyn.gnd ? GND_ON : GND_OFF;
+
+ // -- Ground vehicle identification --
+ // is really difficult with RealTraffic as we only have very few information:
+ if (stat.acTypeIcao.empty() && // don't know a/c type yet
+ dyn.gnd && // on the ground
+ dyn.spd < 50.0 && // reasonable speed
+ stat.reg.empty() && // no tail number
+ stat.destAp.empty()) // no destination airport
+ {
+ // we assume ground vehicle
+ stat.acTypeIcao = dataRefs.GetDefaultCarIcaoType();
}
+
+ // add the static data
+ fd.UpdateData(std::move(stat));
+
+ // add the dynamic data
+ fd.AddDynData(dyn, 0, 0, &pos);
+
} catch(const std::system_error& e) {
LOG_MSG(logERR, ERR_LOCK_ERROR, "mapFd", e.what());
return false;
diff --git a/Src/LiveTraffic.cpp b/Src/LiveTraffic.cpp
index d5ca4428..2f4ccadb 100755
--- a/Src/LiveTraffic.cpp
+++ b/Src/LiveTraffic.cpp
@@ -342,6 +342,26 @@ bool RegisterCommandHandlers ()
//MARK: One-Time Setup (Flight Loop Callback)
+/// Puts some timestamps into the log for analysis purposes
+void LogTimestamps ()
+{
+ // current Zulu time
+ char tZuluS[100];
+ struct tm zulu;
+ std::time_t t = std::time(nullptr);
+ gmtime_s(&zulu, &t);
+ std::strftime(tZuluS, sizeof(tZuluS), "%d-%b-%Y %T", &zulu);
+
+ // current simTime
+ char tSimZ[100];
+ t = std::time_t(dataRefs.GetSimTime());
+ gmtime_s(&zulu, &t);
+ std::strftime(tSimZ, sizeof(tSimZ), "%d-%b-%Y %T", &zulu);
+
+ // Log it
+ LOG_MSG(logMSG, MSG_TIMESTAMPS, tZuluS, tSimZ);
+}
+
// For informing dataRe Editor and tool see
// http://www.xsquawkbox.net/xpsdk/mediawiki/DataRefEditor and
// https://github.com/leecbaker/datareftool/blob/master/src/plugin_custom_dataref.cpp
@@ -383,6 +403,9 @@ float LoopCBOneTimeSetup (float, float, int, void*)
return 2;
case ONCE_CB_AUTOSTART:
+ // Log a timestamp to synch timing for analysis purposes
+ LogTimestamps ();
+
// Auto Start display of aircraft
if (dataRefs.GetAutoStart())
dataRefs.SetAircraftDisplayed(true);
@@ -444,6 +467,9 @@ PLUGIN_API int XPluginStart(
// use native paths, i.e. Posix style (as opposed to HFS style)
// https://developer.x-plane.com/2014/12/mac-plugin-developers-you-should-be-using-native-paths/
XPLMEnableFeature("XPLM_USE_NATIVE_PATHS",1);
+
+ // For nice logging, let XPMP2 know our names already
+ XPMPSetPluginName(LIVE_TRAFFIC, LIVE_TRAFFIC_XPMP2);
// init DataRefs
if (!dataRefs.Init()) { DestroyWindow(); return 0; }
diff --git a/Src/TFWidgets.cpp b/Src/TFWidgets.cpp
index 0123e2d2..0e676089 100644
--- a/Src/TFWidgets.cpp
+++ b/Src/TFWidgets.cpp
@@ -218,7 +218,7 @@ std::string TFGetWidgetDescriptor (XPWidgetID me)
return std::string();
char* buf = new char[len+1];
- XPGetWidgetDescriptor (me, buf, len);
+ XPGetWidgetDescriptor (me, buf, len+1);
std::string ret = buf;
delete[] buf;
return ret;
diff --git a/Src/TextIO.cpp b/Src/TextIO.cpp
index 7820fa06..3161626f 100644
--- a/Src/TextIO.cpp
+++ b/Src/TextIO.cpp
@@ -317,23 +317,14 @@ const char* GetLogString (const char* szPath, int ln, const char* szFunc,
// prepare timestamp
if (lvl < logMSG) // normal messages without, all other with location info
{
- // prepare current simTime in string form
- const double simTime = dataRefs.GetSimTime();
- std::time_t t = std::time_t(simTime);
- struct tm zulu;
- gmtime_s(&zulu, &t);
- char tZuluS[100];
- std::strftime(tZuluS, sizeof(tZuluS), "%d-%b %T", &zulu);
-
#if IBM
const char* szFile = strrchr(szPath, '\\'); // extract file from path
#else
const char* szFile = strrchr(szPath, '/'); // extract file from path
#endif
if (!szFile) szFile = szPath; else szFile++;
- snprintf(aszMsg, sizeof(aszMsg), "%u:%02u:%06.3f " LIVE_TRAFFIC " %s%5.4fZ %s %s:%d/%s: ",
+ snprintf(aszMsg, sizeof(aszMsg), "%u:%02u:%06.3f " LIVE_TRAFFIC " %s %s:%d/%s: ",
runH, runM, runS, // Running time stamp
- tZuluS, fmod(simTime, 1.0), // simTime formated as "DD-MMM HH:MM:SS.SSS"
LOG_LEVEL[lvl], // logging level
szFile, ln, szFunc); // source code location info
}
diff --git a/docs/readme.html b/docs/readme.html
index 440ee31b..5cc5e1fe 100755
--- a/docs/readme.html
+++ b/docs/readme.html
@@ -32,11 +32,11 @@ Documentation
Upgrade from v1.5 to v2.0
- Please follow
+
Please follow
upgrade instructions
for upgrading an existing LiveTraffic installation to version 2.0.
-
+
Essential Installation
LiveTraffic plugin
@@ -83,24 +83,106 @@ Resulting Directory Structure
Release Notes
The number-link refers to the issue in GitHub with (often technical) details.
-
+
v2.0
-
- v2.01 and v2.02
-
- The published Windows version of v2.01 had not included all fixes, so v2.02
- is published only to also provide the correct Windows binary.
-
+
+ v2.03
+
Please follow
upgrade instructions
for upgrading an existing LiveTraffic v1.5 (or earlier) installation to version 2.0.
-
+
+ For updating coming from v2.01 or v2.02
+
+ - copy
+
lin|mac|win_x64/LiveTraffic.xpl
,
+ Resources/CSL2XSB.py
, and
+ Resources/FlightModels.prf
.
+
+
+ - remove
Resources/NoPlane.acf
as well as
+ <X-Plane>/Aircraft/LiveTraffic/
.
+
+ - It is recommended to
+ run
CSL2XSB.py
+ on your CSL models again if you ever did so before,
+ no matter the settings you had used.
+
+
+
+ Change log:
+
+
+ - CHANGED TCAS handling to new
+ TCAS override
+ approach that was introduced with X-Plane 11.50 Beta 8.
+
+ - TCAS functionality requires X-Plane 11.50b8 or later.
+ On earlier version TCAS can no longer be activated.
+ - Up to 63 TCAS blibs will show on Laminar's instruments.
+ - You no longer need to configure AI Aircraft
+ in X-Plane's settings just for LiveTraffic's purposes.
+ - Other plugins' TCAS implementations as well as 3rd party plugins
+ currently accessing the classic up to 19 multiplayer
+ dataRefs will first need to adapt to the new approach
+ to benefit from the up to 63 planes.
+ - Attention developers,
+ if using LiveTraffic's AI multiplayer data:
+ The previous slotting mechanism is removed, that means
+ that planes can potentially change AI slots with every
+ flight loop cycle. They are sorted by distance.
+ This is because the new
+ TCAS target mechanism
+ offers the
sim/cockpit2/tcas/targets/modeS_id[64]
+ dataRef array to keep track of an aircraft's identity.
+
+
+ - TEMPORARILY DEACTIVATED LiveTraffic's own rendering of the map due to a
+ bug in X-Plane 11.50b9, causing crashes when the location changes.
+ Instead, X-Plane's rendering of the now up to 63 TCAS planes
+ is activated so that you see at least some standard icons.
+ Bug is filed with Laminar. Map will come back once that bug is fixed.
+ - CHANGED #186
+ identification of ground vehicles, takes operator and call sign
+ into consideration. Call sign matches configured in
+
FlightModel.prf
.
+ - FIXED identification of ground vehicles for the purpose of chosing a map icon
+ if car ICAO type is configured different from standard ZZZC.
+
- FIXED XPMP2 #9:
+ Allowing models with same name from different CSL packages.
+ Matching will pick randomly, also across packages.
+ This removes many "Duplicate model" warnings from the log.
+ It remains a warning if the same name is reused within one
+ package.
+ - FIXED #183
+ ghost planes: Some CSL models were not drawn due to a bug while reading
xsb_aircraft.txt
.
+ - FIXED #185
+ garbage character read from Settings text fields.
+ - FIXED #184:
+ Aircraft on the ground, for which no updates are available any longer,
+ were not properly removed but could just sit there forever.
+ - REMOVED handling of
ICAO
/AIRLINE
lines from
+ CSL2XSB.py
, which previously was meant to define default models
+ per aircraft type and airline. With XPMP2's random pick mechanism this is no longer needed
+ and would even reduce the match quality of the models previously chosen as defaults.
+ Execute CSL2XSB.py
again on your models to benefit
+ from this change.
+ - In Beta versions like this, Log Model Matching
+ (Debug Settings)
+ is forced to be activated.
+
+
+ v2.01 and v2.02
+
+ The published Windows version of v2.01 had not included all fixes, so v2.02
+ is published only to also provide the correct Windows binary.
+
For updating coming from v2.00 copy lin|mac|win_x64/LiveTraffic.xpl
- and Resources/NoPlane.acf
-
+ and Resources/NoPlane.acf
.
+
-
+
- CSL Models: If
VERT_OFFSET
is not specified in xsb_aircraft.txt
then it is extracted from the model's .obj
file.
@@ -140,9 +222,9 @@ v2.01 and v2.02
-
+
v2.00
-
+
- Supports X-Plane 11 only.
@@ -185,15 +267,19 @@ v2.00
is neither support nor needed any longer.
The "Missing Gear" issue is history when using instancing.
+
+ In Beta versions like this, Log Level
+ (Advanced Settings)
+ is forced to be Debug.
v1.5
-
+
v1.52
-
+
To update coming from v1.50 or v1.51: Just copy the 3 executables
LiveTraffic/64/*.xpl
.
-
+
- FIXED #174:
Aircraft sometimes failed to create when best available data was just half
@@ -205,25 +291,25 @@
v1.52
(introduced with v1.51)
-
+
v1.51
-
+
To update coming from v1.50: Just copy the 3 executables
LiveTraffic/64/*.xpl
.
-
+
- PARTICIALLY FIXED #174:
Should avoid crashing after a rare and yet unexplained event leading to missing
data while trying to create a new aircraft.
-
+
v1.50
To update coming from v1.24: Copy the 3 executables
LiveTraffic/64/*.xpl
and the updated
Resources/Doc8643.txt
file.
-
+
- ADDED #100:
Snapping to taxiways to make aircraft stay on taxiways more often.
@@ -252,7 +338,7 @@
v1.50
v1.2
-
+
v1.24
@@ -269,9 +355,9 @@ v1.23
- copy the updated files
LiveTraffic/Resources/Doc8643.txt
and LiveTraffic/Resources/related.txt
.
-
+
Changes to LiveTraffic:
-
+
- FIXED handling of OpenSky master data, avoiding duplicate requests.
Reduces likelihood of HTTP 503 error and improves handling of it.
@@ -296,7 +382,7 @@ v1.23
v1.22 Fixits
-
+
-
ADDED #44
@@ -321,9 +407,9 @@
v1.22 Fixits
-
+
v1.21 Fixits
-
+
-
ADDED #152
@@ -458,9 +544,9 @@
v1.20.190718 CSL / AI / Multiplayer enhancements
v1.10
v1.16.190507 ADSBEx with RapidAPI
-
+
-
+
-
ADDED #138
@@ -481,7 +567,7 @@
v1.16.190507 ADSBEx with RapidAPI
using LiveTraffic even on outdated vesions like XP10.45 (untested).
-
+
v1.15.190428 ADSBEx with API Key
@@ -502,7 +588,7 @@ v1.15.190428 ADSBEx with API Key
errorneous warning valid OBJ8 part types are LIGHTS or SOLID. Got LIGHTS.
.
-
+
v1.10.190406 RealTraffic, ForeFlight