swift
simulatorfsxcommon.cpp
1 // SPDX-FileCopyrightText: Copyright (C) 2013 swift Project Community / Contributors
2 // SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-swift-pilot-client-1
3 
4 #include "simulatorfsxcommon.h"
5 
6 #include <type_traits>
7 
8 #include <QElapsedTimer>
9 #include <QPointer>
10 #include <QStringBuilder>
11 #include <QTimer>
12 
13 #include "../fscommon/simulatorfscommonfunctions.h"
14 #include "simconnectsymbols.h"
15 
16 #include "config/buildconfig.h"
17 #include "core/application.h"
19 #include "misc/country.h"
21 #include "misc/logmessage.h"
22 #include "misc/math/mathutils.h"
32 #include "misc/statusmessagelist.h"
33 #include "misc/threadutils.h"
34 #include "misc/verify.h"
35 
36 using namespace swift::config;
37 using namespace swift::misc;
38 using namespace swift::misc::aviation;
39 using namespace swift::misc::physical_quantities;
40 using namespace swift::misc::geo;
41 using namespace swift::misc::network;
42 using namespace swift::misc::math;
43 using namespace swift::misc::simulation;
44 using namespace swift::misc::simulation::fscommon;
45 using namespace swift::misc::simulation::fsx;
46 using namespace swift::misc::simulation::settings;
47 using namespace swift::core;
48 using namespace swift::simplugin::fscommon;
49 
50 namespace swift::simplugin::fsxcommon
51 {
52  CSimulatorFsxCommon::CSimulatorFsxCommon(const CSimulatorPluginInfo &info,
53  IOwnAircraftProvider *ownAircraftProvider,
54  IRemoteAircraftProvider *remoteAircraftProvider,
55  IClientProvider *clientProvider, QObject *parent)
56  : CSimulatorFsCommon(info, ownAircraftProvider, remoteAircraftProvider, clientProvider, parent)
57  {
58  Q_ASSERT_X(ownAircraftProvider, Q_FUNC_INFO, "Missing provider");
59  Q_ASSERT_X(remoteAircraftProvider, Q_FUNC_INFO, "Missing provider");
60  Q_ASSERT_X(sApp, Q_FUNC_INFO, "Missing global object");
61 
62  m_simObjectTimer.setInterval(AddPendingAircraftIntervalMs);
63  // default model will be set in derived class
64 
66  connect(&m_simObjectTimer, &QTimer::timeout, this, &CSimulatorFsxCommon::timerBasedObjectAddOrRemove);
67  }
68 
70 
71  bool CSimulatorFsxCommon::isConnected() const { return m_simConnected && m_hSimConnect; }
72 
73  bool CSimulatorFsxCommon::isSimulating() const { return m_simSimulating && this->isConnected(); }
74 
76  {
77  if (this->isConnected()) { return true; }
78  this->reset();
79 
80  const HRESULT hr = SimConnect_Open(&m_hSimConnect, sApp->swiftVersionChar(), nullptr, 0, nullptr, 0);
81  if (isFailure(hr))
82  {
83  // reset state as expected for unconnected
84  this->reset();
85  return false;
86  }
87 
88  // set structures and move on
89  this->triggerAutoTraceSendId(); // we trace the init phase, so in case something goes wrong there
90  this->initEvents();
91  this->initEventsP3D();
92  this->initDataDefinitionsWhenConnected();
93 
94  m_timerId = this->startTimer(DispatchIntervalMs);
95  // do not start m_addPendingAircraftTimer here, it will be started when object was added
96  return true;
97  }
98 
100  {
101  if (!m_simConnected) { return true; }
102  m_simSimulating = false; // thread as stopped, just setting the flag here avoids overhead of on onSimStopped
103  m_traceAutoUntilTs = -1;
104  m_traceSendId = false;
105  this->reset(); // mark as disconnected and reset all values
106 
107  if (m_hSimConnect)
108  {
109  SimConnect_Close(m_hSimConnect);
110  m_hSimConnect = nullptr;
111  m_simConnected = false;
112  }
113 
114  // emit status
115  return CSimulatorFsCommon::disconnectFrom();
116  }
117 
119  {
120  this->logAddingAircraftModel(newRemoteAircraft);
121  return this->physicallyAddRemoteAircraftImpl(newRemoteAircraft, ExternalCall);
122  }
123 
124  bool CSimulatorFsxCommon::updateCOMFromSwiftToSimulator(const CFrequency &newFreq, const CFrequency &lastSimFreq,
125  CFrequency &last25kHzSimFreq, EventIds id)
126  {
127  if (newFreq == lastSimFreq) { return false; }
128 
129  if (CComSystem::isExclusiveWithin8_33kHzChannel(newFreq) && last25kHzSimFreq.isNull())
130  {
131  // Switch from 25 to 8.33
132  // Store last 25 kHz frequency and do not send to simulator
133  last25kHzSimFreq = lastSimFreq;
134  return false;
135  }
136 
137  if (CComSystem::isWithin25kHzChannel(newFreq))
138  {
139  // Send to simulator
140  last25kHzSimFreq.setNull();
141  SimConnect_TransmitClientEvent(m_hSimConnect, 0, id, CBcdConversions::comFrequencyToBcdHz(newFreq),
142  SIMCONNECT_GROUP_PRIORITY_HIGHEST,
143  SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY);
144  return true;
145  }
146 
147  // Already 8.33 -> nothing to do
148  return false;
149  }
150 
152  const CIdentifier &originator)
153  {
154  if (originator == this->identifier()) { return false; }
155  if (!this->isSimulating()) { return false; }
156 
157  // actually those data should be the same as ownAircraft
158  const CComSystem newCom1 = ownAircraft.getCom1System();
159  const CComSystem newCom2 = ownAircraft.getCom2System();
160  const CTransponder newTransponder = ownAircraft.getTransponder();
161 
162  bool changed = false;
163 
164  changed |= updateCOMFromSwiftToSimulator(newCom1.getFrequencyActive(), m_simCom1.getFrequencyActive(),
165  m_lastCom1Active, EventSetCom1Active);
166  changed |= updateCOMFromSwiftToSimulator(newCom1.getFrequencyStandby(), m_simCom1.getFrequencyStandby(),
167  m_lastCom1Standby, EventSetCom1Standby);
168  changed |= updateCOMFromSwiftToSimulator(newCom2.getFrequencyActive(), m_simCom2.getFrequencyActive(),
169  m_lastCom2Active, EventSetCom2Active);
170  changed |= updateCOMFromSwiftToSimulator(newCom2.getFrequencyStandby(), m_simCom2.getFrequencyStandby(),
171  m_lastCom2Standby, EventSetCom2Standby);
172 
173  if (newTransponder.getTransponderMode() != m_simTransponder.getTransponderMode())
174  {
175  if (m_useSbOffsets)
176  {
177  byte ident = newTransponder.isIdentifying() ? 1U : 0U; // 1 is ident
178  byte standby = newTransponder.isInStandby() ? 1U : 0U; // 1 is standby
179  HRESULT hr = s_ok();
180 
181  hr += SimConnect_SetClientData(m_hSimConnect, ClientAreaSquawkBox,
183  SIMCONNECT_CLIENT_DATA_SET_FLAG_DEFAULT, 0, 1, &ident);
184  hr += SimConnect_SetClientData(m_hSimConnect, ClientAreaSquawkBox,
186  SIMCONNECT_CLIENT_DATA_SET_FLAG_DEFAULT, 0, 1, &standby);
187  if (isFailure(hr))
188  {
189  this->triggerAutoTraceSendId();
190  CLogMessage(this).warning(u"Setting transponder mode failed (SB offsets)");
191  }
192  else
193  {
194  if (m_logSbOffsets)
195  {
196  const QString lm =
197  "SB sent: ident " % QString::number(ident) % u" standby " % QString::number(standby);
198  CLogMessage(this).info(lm);
199  }
200  }
201  changed = true;
202  }
203  else if (this->getSimulatorPluginInfo().getSimulatorInfo().isMSFS())
204  {
206  t.transponderMode = (newTransponder.isInStandby() ? 1 : 4);
207  t.ident = newTransponder.isIdentifying();
208 
209  HRESULT hr = s_ok();
210 
211  hr += SimConnect_SetDataOnSimObject(m_hSimConnect, CSimConnectDefinitions::DataTransponderModeMSFS,
212  SIMCONNECT_OBJECT_ID_USER, SIMCONNECT_DATA_SET_FLAG_DEFAULT, 0,
214 
215  if (isFailure(hr)) { CLogMessage(this).warning(u"Setting transponder mode failed (MSFS)"); }
216 
217  changed = true;
218  }
219  }
220 
221  // avoid changes of cockpit back to old values due to an outdated read back value
222  if (changed) { m_skipCockpitUpdateCycles = SkipUpdateCyclesForCockpit; }
223 
224  // bye
225  return changed;
226  }
227 
229  {
230  if (originator == this->identifier()) { return false; }
231  if (!this->isSimulating()) { return false; }
232 
235  m_selcal = selcal;
236  return false;
237  }
238 
240  {
241  QByteArray m = message.getMessage().toLatin1().constData();
242  m.append('\0');
243 
244  SIMCONNECT_TEXT_TYPE type = SIMCONNECT_TEXT_TYPE_PRINT_BLACK;
245  switch (message.getSeverity())
246  {
247  case CStatusMessage::SeverityDebug: return;
248  case CStatusMessage::SeverityInfo: type = SIMCONNECT_TEXT_TYPE_PRINT_GREEN; break;
249  case CStatusMessage::SeverityWarning: type = SIMCONNECT_TEXT_TYPE_PRINT_YELLOW; break;
250  case CStatusMessage::SeverityError: type = SIMCONNECT_TEXT_TYPE_PRINT_RED; break;
251  }
252  const HRESULT hr =
253  SimConnect_Text(m_hSimConnect, type, 7.5, EventTextMessage, static_cast<DWORD>(m.size()), m.data());
254  Q_UNUSED(hr)
255  }
256 
258  {
259  QByteArray m = message.asString(true, true).toLatin1().constData();
260  m.append('\0');
261 
262  SIMCONNECT_TEXT_TYPE type = SIMCONNECT_TEXT_TYPE_PRINT_BLACK;
263  if (message.isSupervisorMessage()) { type = SIMCONNECT_TEXT_TYPE_PRINT_RED; }
264  else if (message.isPrivateMessage()) { type = SIMCONNECT_TEXT_TYPE_PRINT_YELLOW; }
265  else if (message.isRadioMessage()) { type = SIMCONNECT_TEXT_TYPE_PRINT_GREEN; }
266 
267  const HRESULT hr =
268  SimConnect_Text(m_hSimConnect, type, 7.5, EventTextMessage, static_cast<DWORD>(m.size()), m.data());
269  Q_UNUSED(hr)
270  }
271 
273  {
274  return m_simConnectObjects.contains(callsign);
275  }
276 
278  {
279  CCallsignSet callsigns(m_simConnectObjects.keys());
280  callsigns.push_back(
281  m_addAgainAircraftWhenRemoved.getCallsigns()); // not really rendered right now, but very soon
282  callsigns.push_back(
283  m_addPendingAircraft.keys()); // not really rendered, but for the logic it should look like it is
284  return CCallsignSet(m_simConnectObjects.keys());
285  }
286 
288  {
289  CStatusMessageList msgs;
290  if (!CBuildConfig::isLocalDeveloperDebugBuild()) { return msgs; }
291  msgs = CSimulatorFsCommon::debugVerifyStateAfterAllAircraftRemoved();
292  if (!m_simConnectObjects.isEmpty())
293  {
294  msgs.push_back(CStatusMessage(this).error(u"m_simConnectObjects not empty: '%1'")
296  }
297  if (!m_simConnectObjectsPositionAndPartsTraces.isEmpty())
298  {
299  msgs.push_back(CStatusMessage(this).error(u"m_simConnectObjectsPositionAndPartsTraces not empty: '%1'")
300  << m_simConnectObjectsPositionAndPartsTraces.getAllCallsignStringsAsString(true));
301  }
303  {
304  msgs.push_back(CStatusMessage(this).error(u"m_addAgainAircraftWhenRemoved not empty: '%1'")
306  }
307  return msgs;
308  }
309 
311  {
312  static const QString specificInfo(
313  "dispatch #: %1 %2 times (cur/max): %3ms (%4ms) %5ms (%6ms) %7 %8 simData#: %9");
314  return specificInfo.arg(m_dispatchProcCount)
315  .arg(m_dispatchProcEmptyCount)
316  .arg(m_dispatchTimeMs)
317  .arg(m_dispatchProcTimeMs)
318  .arg(m_dispatchMaxTimeMs)
319  .arg(m_dispatchProcMaxTimeMs)
320  .arg(CSimConnectUtilities::simConnectReceiveIdToString(static_cast<DWORD>(m_dispatchReceiveIdMaxTime)),
321  requestIdToString(m_dispatchRequestIdMaxTime))
322  .arg(m_requestSimObjectDataCount);
323  }
324 
325  bool CSimulatorFsxCommon::requestElevation(const ICoordinateGeodetic &reference, const CCallsign &aircraftCallsign)
326  {
327  // this is the 32bit FSX version, the P3D x64 is overridden!
328 
329  if (this->isShuttingDownOrDisconnected()) { return false; }
330  if (!this->isUsingFsxTerrainProbe()) { return false; }
331  if (reference.isNull()) { return false; }
332  const CSimConnectObject simObject = m_simConnectObjects.getOldestNotPendingProbe(); // probes round robin
333  if (!simObject.isConfirmedAdded()) { return false; }
334  m_simConnectObjects[simObject.getCallsign()].resetTimestampToNow(); // mark probe as just used
335 
336  CCoordinateGeodetic pos(reference);
337  pos.setGeodeticHeight(terrainProbeAltitude());
338 
339  SIMCONNECT_DATA_INITPOSITION position = this->coordinateToFsxPosition(pos);
340  const HRESULT hr = this->logAndTraceSendId(
342  simObject.getObjectId(), 0, 0, sizeof(SIMCONNECT_DATA_INITPOSITION),
343  &position),
344  simObject, "Cannot request AI elevation", Q_FUNC_INFO, "SimConnect_SetDataOnSimObject");
345 
346  if (isFailure(hr)) { return false; }
347 
348  const bool ok = this->requestTerrainProbeData(simObject, aircraftCallsign);
349  if (ok) { emit this->requestedElevation(aircraftCallsign); }
350  return ok;
351  }
352 
354  {
355  if (m_traceSendId) { return true; } // explicit
356  if (m_traceAutoUntilTs < 0) { return false; } // no auto
357  const qint64 ts = QDateTime::currentMSecsSinceEpoch();
358  const bool trace = ts <= m_traceAutoUntilTs;
359  return trace;
360  }
361 
363  {
364  m_traceSendId = trace;
365  m_traceAutoUntilTs = -1;
366  }
367 
369  {
370  m_useAddSimulatedObj = enabled;
371  const CSimulatorInfo sim = this->getSimulatorInfo();
372  CFsxP3DSettings settings = m_detailsSettings.getSettings(sim);
373  settings.setAddingAsSimulatedObjectEnabled(enabled);
374  m_detailsSettings.setSettings(settings, sim);
375  }
376 
378  {
379  m_useSbOffsets = enabled;
380  const CSimulatorInfo sim = this->getSimulatorInfo();
381  CFsxP3DSettings settings = m_detailsSettings.getSettings(sim);
382  settings.setSbOffsetsEnabled(enabled);
383  m_detailsSettings.setSettings(settings, sim);
384  }
385 
387  {
388  m_dispatchProcCount = 0;
389  m_dispatchProcEmptyCount = 0;
390  m_dispatchMaxTimeMs = -1;
391  m_dispatchProcMaxTimeMs = -1;
392  m_dispatchTimeMs = -1;
393  m_dispatchProcTimeMs = -1;
394  m_requestSimObjectDataCount = 0;
395  m_dispatchReceiveIdLast = SIMCONNECT_RECV_ID_NULL;
396  m_dispatchReceiveIdMaxTime = SIMCONNECT_RECV_ID_NULL;
397  m_dispatchRequestIdLast = CSimConnectDefinitions::RequestEndMarker;
398  m_dispatchRequestIdMaxTime = CSimConnectDefinitions::RequestEndMarker;
399  CSimulatorPluginCommon::resetAircraftStatistics();
400  }
401 
403  {
404  // toggled?
405  if (connected == !this->isFlightNetworkConnected())
406  {
407  // toggling, we trace for a while to better monitor those "critical" phases
408  this->triggerAutoTraceSendId();
409  }
410 
411  // update SB area network connected
412  byte sbNetworkConnected = connected ? 1u : 0u;
413  const HRESULT hr = SimConnect_SetClientData(m_hSimConnect, ClientAreaSquawkBox,
415  SIMCONNECT_CLIENT_DATA_SET_FLAG_DEFAULT, 0, 1, &sbNetworkConnected);
416  if (isFailure(hr)) { CLogMessage(this).warning(u"Setting network connected failed (SB offsets)"); }
417 
418  ISimulator::setFlightNetworkConnected(connected);
419  }
420 
422  {
423  if (!m_simConnectObjects.contains(callsign)) { return CStatusMessageList(); }
425  this->getInterpolationSetupConsolidated(callsign, false);
426  return (m_simConnectObjects[callsign]).getInterpolationMessages(setup.getInterpolatorMode());
427  }
428 
430  const CAircraftParts &parts)
431  {
432  if (!m_simConnectObjects.contains(callsign)) { return false; }
433  CSimConnectObject simObject = m_simConnectObjects.value(callsign);
434  int u = 0;
435  if (!parts.isNull())
436  {
437  this->sendRemoteAircraftPartsToSimulator(simObject, parts);
438  u++;
439  }
440  if (!situation.isNull())
441  {
442  SIMCONNECT_DATA_INITPOSITION position = this->aircraftSituationToFsxPosition(situation, true);
443  const bool traceSendId = this->isTracingSendId();
444  const HRESULT hr = this->logAndTraceSendId(
446  static_cast<SIMCONNECT_OBJECT_ID>(simObject.getObjectId()), 0, 0,
447  sizeof(SIMCONNECT_DATA_INITPOSITION), &position),
448  traceSendId, simObject, "Failed to set position", Q_FUNC_INFO, "SimConnect_SetDataOnSimObject");
449  if (hr == S_OK) { u++; }
450  }
451  return u > 0;
452  }
453 
455  {
457  if (isRequestForSimObjAircraft(requestId)) { v = (requestId - RequestSimObjAircraftStart) / MaxSimObjAircraft; }
458  else if (isRequestForSimObjTerrainProbe(requestId))
459  {
460  v = (requestId - RequestSimObjTerrainProbeStart) / MaxSimObjProbes;
461  }
462  Q_ASSERT_X(v <= CSimConnectDefinitions::SimObjectEndMarker, Q_FUNC_INFO, "Invalid value");
463  return static_cast<CSimConnectDefinitions::SimObjectRequest>(v);
464  }
465 
466  bool CSimulatorFsxCommon::stillDisplayReceiveExceptions()
467  {
468  m_receiveExceptionCount++;
469  return m_receiveExceptionCount < IgnoreReceiveExceptions;
470  }
471 
472  CSimConnectObject CSimulatorFsxCommon::getSimObjectForObjectId(DWORD objectId) const
473  {
474  return this->getSimConnectObjects().getSimObjectForObjectId(objectId);
475  }
476 
477  void CSimulatorFsxCommon::setSimConnected()
478  {
479  m_simConnected = true;
480  this->initSimulatorInternals();
482 
483  // Internals depends on simulator data which take a while to be read
484  // this is a trick and I re-init again after a while (which is not really expensive)
485  const QPointer<CSimulatorFsxCommon> myself(this);
486  QTimer::singleShot(2500, this, [myself] {
487  if (!myself) { return; }
488  myself->initSimulatorInternals();
489  });
490  }
491 
492  void CSimulatorFsxCommon::onSimRunning()
493  {
494  const QPointer<CSimulatorFsxCommon> myself(this);
495  QTimer::singleShot(DeferSimulatingFlagMs, this, [=] {
496  if (!myself) { return; }
497  m_simulatingChangedTs = QDateTime::currentMSecsSinceEpoch();
498  this->onSimRunningDeferred(m_simulatingChangedTs);
499  });
500  }
501 
502  void CSimulatorFsxCommon::onSimRunningDeferred(qint64 referenceTs)
503  {
504  if (m_simSimulating) { return; } // already simulatig
505  if (referenceTs != m_simulatingChangedTs) { return; } // changed, so no longer valid
506  m_simSimulating = true; // only place where this should be set to true
507  m_simConnected = true;
508 
509  const CFsxP3DSettings settings = m_detailsSettings.getSettings(this->getSimulatorInfo());
510  m_useAddSimulatedObj = settings.isAddingAsSimulatedObjectEnabled();
511  m_useSbOffsets = settings.isSbOffsetsEnabled();
512  if (this->getSimulatorPluginInfo().getSimulatorInfo().isMSFS() ||
513  this->getSimulatorPluginInfo().getSimulatorInfo().isMSFS2024())
514  {
515  m_useSbOffsets = false; // Always disable SbOffsets for MSFS. Using new transponder mode property directly
516  }
517 
518  HRESULT hr = s_ok();
519  hr += this->logAndTraceSendId(
520  SimConnect_RequestDataOnSimObject(m_hSimConnect, CSimConnectDefinitions::RequestOwnAircraft,
521  CSimConnectDefinitions::DataOwnAircraft, SIMCONNECT_OBJECT_ID_USER,
522  SIMCONNECT_PERIOD_VISUAL_FRAME),
523  "Cannot request own aircraft data", Q_FUNC_INFO, "SimConnect_RequestDataOnSimObject");
524 
525  hr += this->logAndTraceSendId(
526  SimConnect_RequestDataOnSimObject(m_hSimConnect, CSimConnectDefinitions::RequestOwnAircraftTitle,
527  CSimConnectDefinitions::DataOwnAircraftTitle, SIMCONNECT_OBJECT_ID_USER,
528  SIMCONNECT_PERIOD_SECOND, SIMCONNECT_DATA_REQUEST_FLAG_CHANGED),
529  "Cannot request title", Q_FUNC_INFO, "SimConnect_RequestDataOnSimObject");
530 
531  // TODO TZ use MSFS2024 FSUIPC?
532  if (!this->getSimulatorPluginInfo().getSimulatorInfo().isMSFS() &&
533  !this->getSimulatorPluginInfo().getSimulatorInfo().isMSFS2024())
534  {
535  // Request the data from SB only when its changed and only ONCE so we don't have to run a 1sec event to
536  // get/set this info ;) there was a bug with SIMCONNECT_CLIENT_DATA_PERIOD_ON_SET, see
537  // https://www.prepar3d.com/forum/viewtopic.php?t=124789
538  hr += this->logAndTraceSendId(SimConnect_RequestClientData(m_hSimConnect, ClientAreaSquawkBox,
541  SIMCONNECT_CLIENT_DATA_PERIOD_SECOND,
542  SIMCONNECT_CLIENT_DATA_REQUEST_FLAG_CHANGED),
543  "Cannot request client data", Q_FUNC_INFO, "SimConnect_RequestClientData");
544  }
545  else
546  {
547  hr += this->logAndTraceSendId(
548  SimConnect_RequestDataOnSimObject(m_hSimConnect, CSimConnectDefinitions::RequestMSFSTransponder,
549  CSimConnectDefinitions::DataTransponderModeMSFS,
550  SIMCONNECT_OBJECT_ID_USER, SIMCONNECT_PERIOD_VISUAL_FRAME,
551  SIMCONNECT_DATA_REQUEST_FLAG_CHANGED),
552  "Cannot request MSFS transponder data", Q_FUNC_INFO, "SimConnect_RequestDataOnSimObject");
553  }
554 
555  if (isFailure(hr)) { return; }
556  this->emitSimulatorCombinedStatus(); // force sending status
557  }
558 
559  void CSimulatorFsxCommon::onSimStopped()
560  {
561  // stopping events in FSX: Load menu, weather and season
562  CLogMessage(this).info(u"Simulator stopped: %1") << this->getSimulatorDetails();
563  const SimulatorStatus oldStatus = this->getSimulatorStatus();
564  m_simSimulating = false;
565  m_simulatingChangedTs = QDateTime::currentMSecsSinceEpoch();
566  this->emitSimulatorCombinedStatus(oldStatus);
567  }
568 
569  void CSimulatorFsxCommon::onSimFrame()
570  {
571  if (m_updateRemoteAircraftInProgress) { return; }
572  QPointer<CSimulatorFsxCommon> myself(this);
573  QTimer::singleShot(0, this, [=] {
574  // run decoupled from simconnect event queue
575  if (!myself) { return; }
576  myself->updateRemoteAircraft();
577  });
578  }
579 
580  void CSimulatorFsxCommon::onSimExit()
581  {
582  CLogMessage(this).info(u"Simulator exit: %1") << this->getSimulatorDetails();
583 
584  // reset complete state, we are going down
585  m_simulatingChangedTs = QDateTime::currentMSecsSinceEpoch();
586  this->safeKillTimer();
587 
588  // if called from dispatch function, avoid that SimConnectProc disconnects itself while in SimConnectProc
589  QPointer<CSimulatorFsxCommon> myself(this);
590  QTimer::singleShot(0, this, [=] {
591  if (!myself) { return; }
592  this->disconnectFrom();
593  });
594  }
595 
597  {
598  const SIMCONNECT_DATA_REQUEST_ID id = m_requestIdSimObjAircraft++;
599  if (id > RequestSimObjAircraftEnd) { m_requestIdSimObjAircraft = RequestSimObjAircraftStart; }
600  return id;
601  }
602 
604  {
605  const SIMCONNECT_DATA_REQUEST_ID id = m_requestIdSimObjTerrainProbe++;
606  if (id > RequestSimObjTerrainProbeEnd) { m_requestIdSimObjTerrainProbe = RequestSimObjTerrainProbeStart; }
607  return id;
608  }
609 
610  bool CSimulatorFsxCommon::releaseAIControl(const CSimConnectObject &simObject, SIMCONNECT_DATA_REQUEST_ID requestId)
611  {
612  const SIMCONNECT_OBJECT_ID objectId = simObject.getObjectId();
613  const HRESULT hr1 =
614  this->logAndTraceSendId(SimConnect_AIReleaseControl(m_hSimConnect, objectId, requestId), simObject,
615  "Release control", Q_FUNC_INFO, "SimConnect_AIReleaseControl");
616  const HRESULT hr2 =
617  this->logAndTraceSendId(SimConnect_TransmitClientEvent(m_hSimConnect, objectId, EventFreezeLatLng, 1,
618  SIMCONNECT_GROUP_PRIORITY_HIGHEST,
619  SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY),
620  simObject, "EventFreezeLatLng", Q_FUNC_INFO, "SimConnect_TransmitClientEvent");
621  const HRESULT hr3 =
622  this->logAndTraceSendId(SimConnect_TransmitClientEvent(m_hSimConnect, objectId, EventFreezeAlt, 1,
623  SIMCONNECT_GROUP_PRIORITY_HIGHEST,
624  SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY),
625  simObject, "EventFreezeAlt", Q_FUNC_INFO, "SimConnect_TransmitClientEvent");
626  const HRESULT hr4 =
627  this->logAndTraceSendId(SimConnect_TransmitClientEvent(m_hSimConnect, objectId, EventFreezeAtt, 1,
628  SIMCONNECT_GROUP_PRIORITY_HIGHEST,
629  SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY),
630  simObject, "EventFreezeAtt", Q_FUNC_INFO, "SimConnect_TransmitClientEvent");
631 
632  return isOk(hr1, hr2, hr3, hr4);
633  }
634 
636  {
637  if (!simObject.hasValidRequestAndObjectId()) { return false; }
638  if (simObject.isPendingRemoved()) { return false; }
639  if (!m_simConnectObjects.contains(simObject.getCallsign())) { return false; } // removed in meantime
640  return true;
641  }
642 
644  {
646  }
647 
649  {
651  }
652 
654  {
655  // not in FSX
656  Q_UNUSED(simObject)
657  }
658 
660  {
661  // not in FSX
662  Q_UNUSED(simObject)
663  }
664 
666  {
667  if (m_traceSendId) { return false; } // no need
668  if (this->isShuttingDownOrDisconnected()) { return false; }
669  const qint64 ts = QDateTime::currentMSecsSinceEpoch();
670  const qint64 traceUntil = traceTimeMs + ts;
671  if (traceUntil <= m_traceAutoUntilTs) { return false; }
672  m_traceAutoUntilTs = traceUntil;
673 
674  static const QString format("hh:mm:ss.zzz");
675  const QString untilString = QDateTime::fromMSecsSinceEpoch(traceUntil).toString(format);
676  CLogMessage(this).info(u"Triggered FSX/P3D auto trace until %1") << untilString;
677  const QPointer<CSimulatorFsxCommon> myself(this);
678  QTimer::singleShot(traceTimeMs * 1.2, this, [=] {
679  // triggered by mself (ts check), otherwise ignore
680  if (!myself) { return; }
681  if (m_traceAutoUntilTs > QDateTime::currentMSecsSinceEpoch()) { return; }
682  if (m_traceAutoUntilTs < 0) { return; } // alread off
683  CLogMessage(this).info(u"Auto trace id off");
684  m_traceAutoUntilTs = -1;
685  });
686  return true;
687  }
688 
690  const DataDefinitionOwnAircraft &simulatorOwnAircraft)
691  {
692  aircraftSituation.setAltitude(
693  CAltitude(simulatorOwnAircraft.altitudeFt, CAltitude::MeanSeaLevel, CLengthUnit::ft()));
694  }
695 
696  void CSimulatorFsxCommon::updateOwnAircraftFromSimulator(const DataDefinitionOwnAircraft &simulatorOwnAircraft)
697  {
698  const qint64 ts = QDateTime::currentMSecsSinceEpoch();
699 
700  CSimulatedAircraft myAircraft(getOwnAircraft());
701  CCoordinateGeodetic position;
702  position.setLatitude(CLatitude(simulatorOwnAircraft.latitudeDeg, CAngleUnit::deg()));
703  position.setLongitude(CLongitude(simulatorOwnAircraft.longitudeDeg, CAngleUnit::deg()));
704 
705  if (simulatorOwnAircraft.pitchDeg < -90.0 || simulatorOwnAircraft.pitchDeg >= 90.0)
706  {
707  CLogMessage(this).warning(u"FSX: Pitch value (own aircraft) out of limits: %1")
708  << simulatorOwnAircraft.pitchDeg;
709  }
710  CAircraftSituation aircraftSituation;
711  aircraftSituation.setMSecsSinceEpoch(ts);
712  aircraftSituation.setPosition(position);
713  // MSFS has inverted pitch and bank angles
714  aircraftSituation.setPitch(CAngle(-simulatorOwnAircraft.pitchDeg, CAngleUnit::deg()));
715  aircraftSituation.setBank(CAngle(-simulatorOwnAircraft.bankDeg, CAngleUnit::deg()));
716  aircraftSituation.setHeading(CHeading(simulatorOwnAircraft.trueHeadingDeg, CHeading::True, CAngleUnit::deg()));
717  aircraftSituation.setGroundSpeed(CSpeed(simulatorOwnAircraft.velocity, CSpeedUnit::kts()));
718  aircraftSituation.setGroundElevation(
719  CAltitude(simulatorOwnAircraft.elevationFt, CAltitude::MeanSeaLevel, CLengthUnit::ft()),
720  CAircraftSituation::FromProvider);
721  setTrueAltitude(aircraftSituation, simulatorOwnAircraft);
722  aircraftSituation.setPressureAltitude(CAltitude(simulatorOwnAircraft.pressureAltitudeM, CAltitude::MeanSeaLevel,
723  CAltitude::PressureAltitude, CLengthUnit::m()));
724  // set on ground also in situation for consistency and future usage
725  // it is duplicated in parts
726  aircraftSituation.setOnGroundInfo(
727  { dtb(simulatorOwnAircraft.simOnGround) ? COnGroundInfo::OnGround : COnGroundInfo::NotOnGround,
728  COnGroundInfo::OutOnGroundOwnAircraft });
729 
730  CAircraftVelocity aircraftVelocity(
731  simulatorOwnAircraft.velocityWorldX, simulatorOwnAircraft.velocityWorldY,
732  simulatorOwnAircraft.velocityWorldZ, CSpeedUnit::ft_s(), simulatorOwnAircraft.rotationVelocityBodyX,
733  simulatorOwnAircraft.rotationVelocityBodyZ, simulatorOwnAircraft.rotationVelocityBodyY, CAngleUnit::rad(),
734  CTimeUnit::s());
735  aircraftSituation.setVelocity(aircraftVelocity);
736 
737  const CAircraftLights lights(dtb(simulatorOwnAircraft.lightStrobe), dtb(simulatorOwnAircraft.lightLanding),
738  dtb(simulatorOwnAircraft.lightTaxi), dtb(simulatorOwnAircraft.lightBeacon),
739  dtb(simulatorOwnAircraft.lightNav), dtb(simulatorOwnAircraft.lightLogo));
740 
741  CAircraftEngineList engines;
742  const QList<bool> helperList { dtb(simulatorOwnAircraft.engine1Combustion),
743  dtb(simulatorOwnAircraft.engine2Combustion),
744  dtb(simulatorOwnAircraft.engine3Combustion),
745  dtb(simulatorOwnAircraft.engine4Combustion) };
746 
747  for (int index = 0; index < simulatorOwnAircraft.numberOfEngines; ++index)
748  {
749  engines.push_back(CAircraftEngine(index + 1, helperList.value(index, true)));
750  }
751 
752  const CAircraftParts parts(lights, dtb(simulatorOwnAircraft.gearHandlePosition),
753  qRound(simulatorOwnAircraft.flapsHandlePosition * 100),
754  dtb(simulatorOwnAircraft.spoilersHandlePosition), engines,
755  dtb(simulatorOwnAircraft.simOnGround), ts);
756 
757  // set values
758  this->updateOwnSituationAndGroundElevation(aircraftSituation);
759  this->updateOwnParts(parts);
760 
761  // When I change cockpit values in the sim (from GUI to simulator, not originating from simulator)
762  // it takes a little while before these values are set in the simulator.
763  // To avoid jitters, I wait some update cylces to stabilize the values
765  {
766  // defaults
767  CComSystem com1(myAircraft.getCom1System()); // set defaults
768  CComSystem com2(myAircraft.getCom2System());
769 
770  // updates:
771  // https://www.fsdeveloper.com/forum/threads/com-unit-receiving-status-com-transmit-x-com-test-1-and-volume.445187/
772  // COM: If you're set to transmit on a unit, you WILL receive that unit.
773  // Otherwise if you're NOT set to transmit on a unit, then it will only receive if COM RECEIVE ALL is true.
774  // There is no control of COM volume.
775  com1.setFrequencyActive(CFrequency(simulatorOwnAircraft.com1ActiveMHz, CFrequencyUnit::MHz()));
776  com1.setFrequencyStandby(CFrequency(simulatorOwnAircraft.com1StandbyMHz, CFrequencyUnit::MHz()));
777  const bool comReceiveAll = dtb(simulatorOwnAircraft.comReceiveAll);
778  const bool com1Test = dtb(simulatorOwnAircraft.comTest1);
779  const bool com1Transmit = dtb(simulatorOwnAircraft.comTransmit1);
780  const int com1Status =
781  qRound(simulatorOwnAircraft.comStatus1); // Radio status flag : -1 =Invalid 0 = OK 1 = Does not exist 2
782  // = No electricity 3 = Failed
783  com1.setTransmitEnabled(com1Status == 0 && com1Transmit);
784  com1.setReceiveEnabled(com1Status == 0 && (comReceiveAll || com1Transmit));
785 
786  const bool changedCom1Active =
787  myAircraft.getCom1System().getFrequencyActive() != com1.getFrequencyActive() &&
788  com1.getFrequencyActive() != m_lastCom1Active;
789  const bool changedCom1Standby =
790  myAircraft.getCom1System().getFrequencyStandby() != com1.getFrequencyStandby() &&
791  com1.getFrequencyStandby() != m_lastCom1Standby;
792 
793  // Avoid overwrite of 8.33 kHz frequency with data from simulator
794  if (!changedCom1Active) { com1.setFrequencyActive(myAircraft.getCom1System().getFrequencyActive()); }
795  else { m_lastCom1Active.setNull(); }
796 
797  if (!changedCom1Standby) { com1.setFrequencyStandby(myAircraft.getCom1System().getFrequencyStandby()); }
798  else { m_lastCom1Standby.setNull(); }
799 
800  const bool changedCom1 = myAircraft.getCom1System() != com1;
801 
802  m_simCom1 = com1;
803  Q_UNUSED(com1Test)
804 
805  com2.setFrequencyActive(CFrequency(simulatorOwnAircraft.com2ActiveMHz, CFrequencyUnit::MHz()));
806  com2.setFrequencyStandby(CFrequency(simulatorOwnAircraft.com2StandbyMHz, CFrequencyUnit::MHz()));
807  const bool com2Test = dtb(simulatorOwnAircraft.comTest2);
808  const bool com2Transmit = dtb(simulatorOwnAircraft.comTransmit2);
809  const int com2Status =
810  qRound(simulatorOwnAircraft.comStatus2); // Radio status flag : -1 =Invalid 0 = OK 1 = Does not exist 2
811  // = No electricity 3 = Failed
812  com2.setTransmitEnabled(com2Status == 0 && com2Transmit);
813  com2.setReceiveEnabled(com2Status == 0 && (comReceiveAll || com2Transmit));
814  const bool changedCom2Active =
815  myAircraft.getCom2System().getFrequencyActive() != com2.getFrequencyActive() &&
816  com2.getFrequencyActive() != m_lastCom2Active;
817  const bool changedCom2Standby =
818  myAircraft.getCom2System().getFrequencyStandby() != com2.getFrequencyStandby() &&
819  com2.getFrequencyStandby() != m_lastCom2Standby;
820 
821  // Avoid overwrite of 8.33 kHz frequency with data from simulator
822  if (!changedCom2Active) { com2.setFrequencyActive(myAircraft.getCom2System().getFrequencyActive()); }
823  else { m_lastCom2Active.setNull(); }
824 
825  if (!changedCom2Standby) { com2.setFrequencyStandby(myAircraft.getCom2System().getFrequencyStandby()); }
826  else { m_lastCom2Standby.setNull(); }
827 
828  const bool changedCom2 = myAircraft.getCom2System() != com2;
829 
830  m_simCom2 = com2;
831  Q_UNUSED(com2Test)
832 
833  CTransponder transponder(myAircraft.getTransponder());
834  transponder.setTransponderCode(qRound(simulatorOwnAircraft.transponderCode));
835  m_simTransponder = transponder;
836 
837  // if the simulator ever sends SELCAL, add it here.
838  // m_selcal SELCAL sync.would go here
839 
840  const bool changedXpr = (myAircraft.getTransponderCode() != transponder.getTransponderCode());
841 
842  if (changedCom1 || changedCom2 || changedXpr)
843  {
844  // set in own aircraft provider
845  this->updateCockpit(com1, com2, transponder, identifier());
846  }
847  }
848  else { --m_skipCockpitUpdateCycles; }
849 
850  // slower updates
851  if (m_ownAircraftUpdateCycles % 10 == 0)
852  {
853  // init terrain probes here has the advantage we can also switch it on/off at runtime
855  {
856  this->physicallyInitAITerrainProbes(position, 2); // init probe
857  }
858 
859  // SB3 offsets updating
860  m_simulatorInternals.setValue(QStringLiteral("fsx/sb3"), boolToEnabledDisabled(m_useSbOffsets));
861  m_simulatorInternals.setValue(QStringLiteral("fsx/sb3packets"), m_useSbOffsets ?
862  QString::number(m_sbDataReceived) :
863  QStringLiteral("disabled"));
864 
865  // CG
866  const CLength cg(simulatorOwnAircraft.cgToGroundFt, CLengthUnit::ft());
867  this->updateOwnCG(cg);
868 
869  // Simulated objects instead of NON ATC
870  m_simulatorInternals.setValue(QStringLiteral("fsx/addAsSimulatedObject"),
871  boolToEnabledDisabled(m_useAddSimulatedObj));
872 
873  } // slow updates
874 
875  m_ownAircraftUpdateCycles++; // with 50 updates/sec long enough even for 32bit
876  }
877 
878  void CSimulatorFsxCommon::triggerUpdateRemoteAircraftFromSimulator(const CSimConnectObject &simObject,
879  const DataDefinitionPosData &remoteAircraftData)
880  {
881  if (this->isShuttingDownOrDisconnected()) { return; }
882  QPointer<CSimulatorFsxCommon> myself(this);
883  QTimer::singleShot(0, this, [=] {
884  if (!myself) { return; }
885  myself->updateRemoteAircraftFromSimulator(simObject, remoteAircraftData);
886  });
887  }
888 
889  void CSimulatorFsxCommon::triggerUpdateRemoteAircraftFromSimulator(
890  const CSimConnectObject &simObject, const DataDefinitionRemoteAircraftModel &remoteAircraftModel)
891  {
892  if (this->isShuttingDownOrDisconnected()) { return; }
893  QPointer<CSimulatorFsxCommon> myself(this);
894  QTimer::singleShot(0, this, [=] {
895  if (!myself) { return; }
896  myself->updateRemoteAircraftFromSimulator(simObject, remoteAircraftModel);
897  });
898  }
899 
900  void CSimulatorFsxCommon::updateRemoteAircraftFromSimulator(const CSimConnectObject &simObject,
901  const DataDefinitionPosData &remoteAircraftData)
902  {
903  if (this->isShuttingDownOrDisconnected()) { return; }
904 
905  // Near ground we use faster updates
906  const CCallsign cs(simObject.getCallsign());
907  CAircraftSituation lastSituation = m_lastSentSituations[cs];
908  const bool moving = lastSituation.isMoving();
909  const bool onGround = remoteAircraftData.isOnGround();
910 
911  // CElevationPlane: deg, deg, feet
912  // we only remember near ground
913  const CElevationPlane elevation =
914  CElevationPlane(remoteAircraftData.latitudeDeg, remoteAircraftData.longitudeDeg,
915  remoteAircraftData.elevationFt, CElevationPlane::singlePointRadius());
916  if (remoteAircraftData.aboveGroundFt() < 250)
917  {
918  const CLength cg(remoteAircraftData.cgToGroundFt, CLengthUnit::ft());
919  this->rememberElevationAndSimulatorCG(cs, simObject.getAircraftModel(), onGround, elevation, cg);
920  }
921 
922  const bool log = this->isLogCallsign(cs);
923  if (log)
924  {
925  // update lat/lng/alt with real data from sim
926  const CAltitude alt(remoteAircraftData.altitudeFt, CAltitude::MeanSeaLevel, CAltitude::TrueAltitude,
927  CLengthUnit::ft());
928  lastSituation.setPosition(elevation);
929  lastSituation.setAltitude(alt);
930  lastSituation.setGroundElevation(elevation, CAircraftSituation::FromProvider);
931  this->addLoopbackSituation(lastSituation);
932  }
933 
934  if (moving && remoteAircraftData.aboveGroundFt() <= 100.0)
935  {
936  // switch to fast updates
937  if (simObject.getSimDataPeriod() != SIMCONNECT_PERIOD_VISUAL_FRAME)
938  {
939  this->requestPositionDataForSimObject(simObject, SIMCONNECT_PERIOD_VISUAL_FRAME);
940  }
941  }
942  else
943  {
944  // switch to slow updates
945  if (simObject.getSimDataPeriod() != SIMCONNECT_PERIOD_SECOND)
946  {
947  this->requestPositionDataForSimObject(simObject, SIMCONNECT_PERIOD_SECOND);
948  }
949  }
950  }
951 
952  void
953  CSimulatorFsxCommon::updateRemoteAircraftFromSimulator(const CSimConnectObject &simObject,
954  const DataDefinitionRemoteAircraftModel &remoteAircraftModel)
955  {
956  const CCallsign cs(simObject.getCallsign());
957  if (!m_simConnectObjects.contains(cs)) { return; } // no longer existing
958  CSimConnectObject &so = m_simConnectObjects[cs];
959  if (so.isPendingRemoved()) { return; }
960 
961  const QString modelString(remoteAircraftModel.title);
962  const CLength cg(remoteAircraftModel.cgToGroundFt, CLengthUnit::ft());
963  so.setAircraftCG(cg);
964  so.setAircraftModelString(modelString);
965 
966  // update in 2 providers
967  this->rememberElevationAndSimulatorCG(cs, simObject.getAircraftModel(), false, CElevationPlane::null(),
968  cg); // env. provider
969  this->updateCGAndModelString(cs, cg, modelString); // remote aircraft provider
970  }
971 
972  void CSimulatorFsxCommon::updateProbeFromSimulator(const CCallsign &callsign,
973  const DataDefinitionPosData &remoteAircraftData)
974  {
975  const CElevationPlane elevation(remoteAircraftData.latitudeDeg, remoteAircraftData.longitudeDeg,
976  remoteAircraftData.elevationFt, CElevationPlane::singlePointRadius());
977  this->callbackReceivedRequestedElevation(elevation, callsign, false);
978  }
979 
980  void CSimulatorFsxCommon::updateOwnAircraftFromSimulator(const DataDefinitionClientAreaSb &sbDataArea)
981  {
982  if (m_skipCockpitUpdateCycles > 0) { return; }
983 
984  // log SB offset
985  if (m_logSbOffsets) { CLogMessage(this).info(u"SB from sim: " % sbDataArea.toQString()); }
986 
987  // SB XPDR mode
988  CTransponder::TransponderMode newMode = CTransponder::StateIdent;
989  if (!sbDataArea.isIdent())
990  {
991  newMode = sbDataArea.isStandby() ? CTransponder::StateStandby : CTransponder::ModeC;
992  }
993  const CSimulatedAircraft myAircraft(this->getOwnAircraft());
994  const bool changed = (myAircraft.getTransponderMode() != newMode);
995  if (!changed) { return; }
996  CTransponder xpdr = myAircraft.getTransponder();
997  xpdr.setTransponderMode(newMode);
998  this->updateCockpit(myAircraft.getCom1System(), myAircraft.getCom2System(), xpdr, this->identifier());
999  }
1000 
1001  void CSimulatorFsxCommon::updateTransponderMode(const CTransponder::TransponderMode xpdrMode)
1002  {
1003  if (m_skipCockpitUpdateCycles > 0) { return; }
1004  const CSimulatedAircraft myAircraft(this->getOwnAircraft());
1005  const bool changed = (myAircraft.getTransponderMode() != xpdrMode);
1006  if (!changed) { return; }
1007  CTransponder myXpdr = myAircraft.getTransponder();
1008  myXpdr.setTransponderMode(xpdrMode);
1009  this->updateCockpit(myAircraft.getCom1System(), myAircraft.getCom2System(), myXpdr, this->identifier());
1010  }
1011 
1012  void CSimulatorFsxCommon::updateMSFSTransponderMode(const DataDefinitionMSFSTransponderMode transponderMode)
1013  {
1014  auto mode = CTransponder::StateIdent;
1015  if (!transponderMode.ident)
1016  {
1017  qRound(transponderMode.transponderMode) >= 3 ? mode = CTransponder::ModeC :
1018  mode = CTransponder::StateStandby;
1019  }
1020  this->updateTransponderMode(mode);
1021  }
1022 
1023  bool CSimulatorFsxCommon::simulatorReportedObjectAdded(DWORD objectId)
1024  {
1025  if (this->isShuttingDownOrDisconnected()) { return true; } // pretend everything is fine
1026  const CSimConnectObject simObject = m_simConnectObjects.getSimObjectForObjectId(objectId);
1027  const CCallsign callsign(simObject.getCallsign());
1028  if (!simObject.hasValidRequestAndObjectId() || callsign.isEmpty()) { return false; }
1029 
1030  // we know the object has been created. But it can happen it is directly removed afterwards
1031  const CSimulatedAircraft verifyAircraft(simObject.getAircraft());
1032  const QPointer<CSimulatorFsxCommon> myself(this);
1033  QTimer::singleShot(1000, this, [=] {
1034  // verify aircraft and also triggers new add if required
1035  // do not do this in the event loop, so we do this deferred
1036  if (!myself || this->isShuttingDownOrDisconnected()) { return; }
1037  this->verifyAddedRemoteAircraft(verifyAircraft);
1038  });
1039  return true;
1040  }
1041 
1042  void CSimulatorFsxCommon::verifyAddedRemoteAircraft(const CSimulatedAircraft &remoteAircraftIn)
1043  {
1044  if (this->isShuttingDownOrDisconnected()) { return; }
1045  if (remoteAircraftIn.isTerrainProbe())
1046  {
1047  this->verifyAddedTerrainProbe(remoteAircraftIn);
1048  return;
1049  }
1050 
1051  CStatusMessage msg;
1052  CSimulatedAircraft remoteAircraft = remoteAircraftIn;
1053  const CCallsign callsign(remoteAircraft.getCallsign());
1054 
1055  do {
1056  // no callsign
1057  if (callsign.isEmpty())
1058  {
1059  msg = CLogMessage(this).error(u"Cannot confirm AI object, empty callsign");
1060  break;
1061  }
1062 
1063  // removed in meantime
1064  const bool aircraftStillInRange = this->isAircraftInRange(callsign);
1065  if (!m_simConnectObjects.contains(callsign))
1066  {
1067  if (aircraftStillInRange)
1068  {
1069  msg = CLogMessage(this).warning(
1070  u"Callsign '%1' removed in meantime from AI objects, but still in range")
1071  << callsign.toQString();
1072  }
1073  else
1074  {
1075  this->removeFromAddPendingAndAddAgainAircraft(callsign);
1076  msg = CLogMessage(this).info(u"Callsign '%1' removed in meantime and no longer in range")
1077  << callsign.toQString();
1078  }
1079  break;
1080  }
1081 
1082  CSimConnectObject &simObject = m_simConnectObjects[callsign];
1083  remoteAircraft = simObject.getAircraft(); // update, if something has changed
1084 
1085  if (!simObject.hasValidRequestAndObjectId() || simObject.isPendingRemoved())
1086  {
1087  msg = CStatusMessage(this).warning(u"Object for callsign '%1'/id: %2 removed in meantime/invalid")
1088  << callsign.toQString() << simObject.getObjectId();
1089  break;
1090  }
1091 
1092  // P3D also has SimConnect_AIReleaseControlEx which also allows to destroy the aircraft
1093  const SIMCONNECT_DATA_REQUEST_ID requestReleaseId = this->obtainRequestIdForSimObjAircraft();
1094  const bool released = this->releaseAIControl(simObject, requestReleaseId);
1095 
1096  if (!released)
1097  {
1098  msg = CStatusMessage(this).error(u"Cannot confirm model '%1' %2")
1099  << remoteAircraft.getModelString() << simObject.toQString();
1100  break;
1101  }
1102 
1103  // confirm as added, this is also required to request light, etc
1104  Q_ASSERT_X(simObject.isPendingAdded(), Q_FUNC_INFO, "Already confirmed, this should be the only place");
1105  simObject.setConfirmedAdded(true); // aircraft
1106 
1107  // request data on object
1108  this->requestPositionDataForSimObject(simObject);
1109  this->requestLightsForSimObject(simObject);
1110  this->requestModelInfoForSimObject(simObject);
1111 
1112  this->removeFromAddPendingAndAddAgainAircraft(callsign); // no longer try to add
1113  const bool updated = this->updateAircraftRendered(callsign, true);
1114  if (updated)
1115  {
1116  static const QString debugMsg("CS: '%1' model: '%2' verified, request/object id: %3 %4");
1117  if (this->showDebugLogMessage())
1118  {
1119  this->debugLogMessage(Q_FUNC_INFO,
1120  debugMsg.arg(callsign.toQString(), remoteAircraft.getModelString())
1121  .arg(simObject.getRequestId())
1122  .arg(simObject.getObjectId()));
1123  }
1124 
1125  this->sendRemoteAircraftAtcDataToSimulator(simObject);
1126  emit this->aircraftRenderingChanged(simObject.getAircraft());
1127  }
1128  else
1129  {
1130  CLogMessage(this).warning(
1131  u"Verified aircraft '%1' model '%2', request/object id: %3 %4 was already marked rendered")
1132  << callsign.asString() << remoteAircraft.getModelString() << simObject.getRequestId()
1133  << simObject.getObjectId();
1134  }
1135 
1136  if (simObject.isConfirmedAdded() && simObject.getType() == CSimConnectObject::AircraftSimulatedObject)
1137  {
1138  CLogMessage(this).warning(u"Confirm added model '%1' '%2', but as '%3'")
1139  << remoteAircraft.getCallsignAsString() << remoteAircraft.getModelString()
1140  << simObject.getTypeAsString();
1141  this->triggerAutoTraceSendId(); // trace for some time (issues regarding this workaround?)
1142  simObject.decreaseAddingExceptions(); // if previously increased and now working, reset
1143  }
1144  }
1145  while (false);
1146 
1147  // log errors and emit signal
1148  if (!msg.isEmpty() && msg.isWarningOrAbove())
1149  {
1150  CLogMessage::preformatted(msg);
1151  emit this->physicallyAddingRemoteModelFailed(CSimulatedAircraft(), false, false, msg);
1152  }
1153 
1154  // trigger adding pending aircraft if there are any
1155  if (!m_addPendingAircraft.isEmpty()) { this->addPendingAircraftAfterAdded(); }
1156  }
1157 
1158  void CSimulatorFsxCommon::addingAircraftFailed(const CSimConnectObject &simObject)
1159  {
1160  if (CBuildConfig::isLocalDeveloperDebugBuild())
1161  {
1162  Q_ASSERT_X(simObject.isAircraft(), Q_FUNC_INFO, "Need aircraft");
1163  }
1164  if (!simObject.isAircraft()) { return; }
1165 
1166  // clean up
1168  this->removeFromAddPendingAndAddAgainAircraft(simObject.getCallsign());
1169 
1170  CLogMessage(this).warning(u"Model failed to be added: '%1' details: %2")
1171  << simObject.getAircraftModelString() << simObject.getAircraft().toQString(true);
1172  CStatusMessage verifyMsg;
1173  const bool verifiedAircraft = this->verifyFailedAircraftInfo(simObject, verifyMsg); // aircraft.cfg existing?
1174  if (!verifyMsg.isEmpty()) { CLogMessage::preformatted(verifyMsg); }
1175 
1176  CSimConnectObject simObjAddAgain(simObject);
1177  simObjAddAgain.increaseAddingExceptions();
1178  if (!simObject.hasCallsign())
1179  {
1180  SWIFT_VERIFY_X(false, Q_FUNC_INFO, "Missing callsign");
1181  return;
1182  }
1183 
1184  if (!verifiedAircraft || simObjAddAgain.getAddingExceptions() > ThresholdAddException)
1185  {
1186  const CStatusMessage msg =
1187  verifiedAircraft ?
1188  CLogMessage(this).warning(u"Model '%1' %2 failed %3 time(s) before and will be disabled")
1189  << simObjAddAgain.getAircraftModelString() << simObjAddAgain.toQString()
1190  << simObjAddAgain.getAddingExceptions() :
1191  CLogMessage(this).warning(u"Model '%1' %2 failed verification and will be disabled")
1192  << simObjAddAgain.getAircraftModelString() << simObjAddAgain.toQString();
1193  this->updateAircraftEnabled(simObjAddAgain.getCallsign(), false); // disable
1194  emit this->physicallyAddingRemoteModelFailed(simObjAddAgain.getAircraft(), true, true,
1195  msg); // verify failed
1196  }
1197  else
1198  {
1199  CLogMessage(this).info(u"Will try '%1' again, aircraft: %2")
1200  << simObject.getAircraftModelString() << simObject.getAircraft().toQString(true);
1201  QPointer<CSimulatorFsxCommon> myself(this);
1202  QTimer::singleShot(2000, this, [=] {
1203  if (!myself) { return; }
1204  if (this->isShuttingDownOrDisconnected()) { return; }
1205  m_addPendingAircraft.insert(simObjAddAgain, true); // add failed object
1206  });
1207  }
1208  }
1209 
1210  bool CSimulatorFsxCommon::verifyFailedAircraftInfo(const CSimConnectObject &simObject,
1211  CStatusMessage &details) const
1212  {
1213  CAircraftModel model = simObject.getAircraftModel();
1214 
1215  const CSpecializedSimulatorSettings settings = this->getSimulatorSettings();
1216  const bool fileExists = CFsCommonUtil::adjustFileDirectory(model, settings.getModelDirectoriesOrDefault());
1217  bool canBeUsed = true;
1218 
1219  CStatusMessageList messages;
1220  if (fileExists)
1221  {
1222  // we can access the aircraft.cfg file
1223  bool parsed = false;
1224  const CAircraftCfgEntriesList entries =
1225  CAircraftCfgParser::performParsingOfSingleFile(model.getFileName(), parsed, messages);
1226  if (parsed)
1227  {
1228  if (entries.containsTitle(model.getModelString()))
1229  {
1230  messages.push_back(CStatusMessage(this).info(u"Model '%1' exists in re-parsed file '%2'.")
1231  << model.getModelString() << model.getFileName());
1232  canBeUsed = true; // all OK
1233  }
1234  else
1235  {
1236  messages.push_back(
1237  CStatusMessage(this).warning(u"Model '%1' no longer in re-parsed file '%2'. Models are: %3.")
1238  << model.getModelString() << model.getFileName() << entries.getTitlesAsString(true));
1239  canBeUsed = false; // absolute no chance to use that one
1240  }
1241  }
1242  else
1243  {
1244  messages.push_back(CStatusMessage(this).warning(u"CS: '%1' Cannot parse file: '%2' (existing: %3)")
1245  << model.getCallsign().asString() << model.getFileName()
1247  }
1248  }
1249  else
1250  {
1251  // the file cannot be accessed right now, but the pilot client necessarily has access to them
1252  // so we just carry on
1253  messages = model.verifyModelData();
1254  }
1255 
1256  // as single message
1257  details = messages.toSingleMessage();
1258 
1259  // status
1260  return canBeUsed;
1261  }
1262 
1263  bool CSimulatorFsxCommon::logVerifyFailedAircraftInfo(const CSimConnectObject &simObject) const
1264  {
1265  CStatusMessage m;
1266  const bool r = verifyFailedAircraftInfo(simObject, m);
1267  if (!m.isEmpty()) { CLogMessage::preformatted(m); }
1268  return r;
1269  }
1270 
1271  void CSimulatorFsxCommon::verifyAddedTerrainProbe(const CSimulatedAircraft &remoteAircraftIn)
1272  {
1273  bool verified = false;
1274  CCallsign cs;
1275 
1276  // no simObject reference outside that block, because it will be deleted
1277  {
1278  CSimConnectObject &simObject = m_simConnectObjects[remoteAircraftIn.getCallsign()];
1279  simObject.setConfirmedAdded(true); // terrain probe
1280  simObject.resetTimestampToNow();
1281  cs = simObject.getCallsign();
1282  CLogMessage(this).info(u"Probe: '%1' '%2' confirmed, %3")
1283  << simObject.getCallsignAsString() << simObject.getAircraftModelString() << simObject.toQString();
1284 
1285  // fails for probe
1286  // SIMCONNECT_DATA_REQUEST_ID requestId = this->obtainRequestIdForSimObjTerrainProbe();
1287  // verified = this->releaseAIControl(simObject, requestId); // release probe
1288  verified = true;
1289  }
1290 
1291  if (!verified) // cppcheck-suppress knownConditionTrueFalse
1292  {
1293  CLogMessage(this).info(u"Disable probes: '%1' failed to relase control") << cs.asString();
1294  m_useFsxTerrainProbe = false;
1295  }
1296 
1297  // trigger new adding from pending if any
1298  if (!m_addPendingAircraft.isEmpty()) { this->addPendingAircraftAfterAdded(); }
1299  }
1300 
1301  void CSimulatorFsxCommon::timerBasedObjectAddOrRemove()
1302  {
1303  this->addPendingAircraft(AddByTimer);
1304  if (!this->isTestMode()) { this->physicallyRemoveAircraftNotInProvider(); }
1305  }
1306 
1307  void CSimulatorFsxCommon::addPendingAircraftAfterAdded()
1308  {
1309  this->addPendingAircraft(AddAfterAdded); // addPendingAircraft is already "non blocking"
1310  }
1311 
1312  void CSimulatorFsxCommon::addPendingAircraft(AircraftAddMode mode)
1313  {
1314  if (m_addPendingAircraft.isEmpty()) { return; }
1315  const CCallsignSet aircraftCallsignsInRange(this->getAircraftInRangeCallsigns());
1316  CSimulatedAircraftList toBeAddedAircraft; // aircraft still to be added
1317  CCallsignSet toBeRemovedCallsigns;
1318 
1319  for (const CSimConnectObject &pendingSimObj : std::as_const(m_addPendingAircraft))
1320  {
1321  SWIFT_VERIFY_X(pendingSimObj.hasCallsign(), Q_FUNC_INFO, "missing callsign");
1322  if (!pendingSimObj.hasCallsign()) { continue; }
1323  if (pendingSimObj.isTerrainProbe() || aircraftCallsignsInRange.contains(pendingSimObj.getCallsign()))
1324  {
1325  toBeAddedAircraft.push_back(pendingSimObj.getAircraft());
1326  }
1327  else { toBeRemovedCallsigns.push_back(pendingSimObj.getCallsign()); }
1328  }
1329 
1330  // no longer required to be added
1331  m_addPendingAircraft.removeCallsigns(toBeRemovedCallsigns);
1332  m_addAgainAircraftWhenRemoved.removeByCallsigns(toBeRemovedCallsigns);
1333 
1334  // add aircraft, but "non blocking"
1335  if (!toBeAddedAircraft.isEmpty())
1336  {
1337  const CSimConnectObject oldestSimObject = m_addPendingAircraft.getOldestObject();
1338  const CSimulatedAircraft nextPendingAircraft = oldestSimObject.getAircraft();
1339  if (nextPendingAircraft.hasModelString())
1340  {
1341  const QPointer<CSimulatorFsxCommon> myself(this);
1342  QTimer::singleShot(100, this, [=] {
1343  if (!myself) { return; }
1344  if (this->isShuttingDownDisconnectedOrNoAircraft(nextPendingAircraft.isTerrainProbe())) { return; }
1345  this->physicallyAddRemoteAircraftImpl(nextPendingAircraft, mode, oldestSimObject);
1346  });
1347  }
1348  else
1349  {
1350  CLogMessage(this).warning(u"Pending aircraft without model string will be removed");
1351  m_addPendingAircraft.removeByOtherSimObject(oldestSimObject);
1352  }
1353  }
1354  }
1355 
1356  CSimConnectObject CSimulatorFsxCommon::removeFromAddPendingAndAddAgainAircraft(const CCallsign &callsign)
1357  {
1358  CSimConnectObject simObjectOld;
1359  if (callsign.isEmpty()) { return simObjectOld; }
1360 
1362  if (m_addPendingAircraft.contains(callsign))
1363  {
1364  simObjectOld = m_addPendingAircraft[callsign];
1365  m_addPendingAircraft.remove(callsign);
1366  }
1367  return simObjectOld;
1368  }
1369 
1370  bool CSimulatorFsxCommon::simulatorReportedObjectRemoved(DWORD objectID)
1371  {
1372  if (this->isShuttingDownOrDisconnected()) { return false; }
1373  CSimConnectObject simObject = m_simConnectObjects.getSimObjectForObjectId(objectID);
1374  if (!simObject.hasValidRequestAndObjectId()) { return false; } // object id from somewhere else
1375 
1376  const CCallsign callsign(simObject.getCallsign());
1377  Q_ASSERT_X(!callsign.isEmpty(), Q_FUNC_INFO, "Missing callsign for removed object");
1378 
1379  if (simObject.isPendingRemoved())
1380  {
1381  // good case, object has been removed
1382  // we can remove the simulator object
1383  }
1384  else
1385  {
1386  // object was removed, but removal was not requested by us
1387  // this means we are out of the reality bubble or something else went wrong
1388  // Possible reasons:
1389  // 1) out of reality bubble, because we move to another airport or other reasons
1390  // 2) wrong position (in ground etc.)
1391  // 3) Simulator not running (ie in stopped mode)
1392  CStatusMessage msg;
1393  if (!simObject.getAircraftModelString().isEmpty() &&
1394  simObject.getAddingDirectlyRemoved() < ThresholdAddedAndDirectlyRemoved)
1395  {
1396  simObject.increaseAddingDirectlyRemoved();
1397  m_addPendingAircraft.insert(simObject, true); // insert removed objects and update ts
1399  simObject); // we have it in pending now, no need to keep it in this list
1400 
1403  msg = CLogMessage(this).warning(u"Aircraft removed, '%1' '%2' object id '%3' out of reality bubble or "
1404  u"other reason. Interpolator: '%4'")
1405  << callsign.toQString() << simObject.getAircraftModelString() << objectID
1406  << simObject.getInterpolatorInfo(setup.getInterpolatorMode());
1407  }
1408  else if (simObject.getAddingDirectlyRemoved() < ThresholdAddedAndDirectlyRemoved)
1409  {
1410  const CStatusMessage m =
1411  CLogMessage(this).warning(
1412  u"Aircraft removed again multiple times and will be disabled, '%1' '%2' object id '%3'")
1413  << callsign.toQString() << simObject.getAircraftModelString() << objectID;
1414  this->updateAircraftEnabled(simObject.getCallsign(), false);
1415  emit this->physicallyAddingRemoteModelFailed(simObject.getAircraft(), true, true,
1416  m); // directly removed again
1417  }
1418  else
1419  {
1420  msg = CLogMessage(this).warning(
1421  u"Removed '%1' from simulator, but was not initiated by us (swift): %1 '%2' object id %3")
1422  << callsign.toQString() << simObject.getAircraftModelString() << objectID;
1423  }
1424 
1425  // in all cases add verification details
1426  this->logVerifyFailedAircraftInfo(simObject);
1427 
1428  // relay messages
1429  if (!msg.isEmpty()) { emit this->driverMessages(msg); }
1430  }
1431 
1432  // in all cases we remove the object
1433  const int c = m_simConnectObjects.remove(callsign);
1434  const bool removedAny = (c > 0);
1435  const bool updated = this->updateAircraftRendered(simObject.getCallsign(), false);
1436  if (updated) { emit this->aircraftRenderingChanged(simObject.getAircraft()); }
1437 
1438  // models we have to add again after removing
1440  {
1441  const CSimulatedAircraft aircraftAddAgain = m_addAgainAircraftWhenRemoved.findFirstByCallsign(callsign);
1443  QPointer<CSimulatorFsxCommon> myself(this);
1444  QTimer::singleShot(2500, this, [=] {
1445  if (!myself) { return; }
1446  if (this->isShuttingDownOrDisconnected()) { return; }
1447  myself->physicallyAddRemoteAircraftImpl(aircraftAddAgain, AddedAfterRemoved);
1448  });
1449  }
1450  return removedAny;
1451  }
1452 
1453  bool CSimulatorFsxCommon::setSimConnectObjectId(DWORD requestId, DWORD objectId)
1454  {
1455  return m_simConnectObjects.setSimConnectObjectIdForRequestId(requestId, objectId);
1456  }
1457 
1458  bool CSimulatorFsxCommon::setCurrentLights(const CCallsign &callsign, const CAircraftLights &lights)
1459  {
1460  if (!m_simConnectObjects.contains(callsign)) { return false; }
1461  m_simConnectObjects[callsign].setCurrentLightsInSimulator(lights);
1462  return true;
1463  }
1464 
1465  bool CSimulatorFsxCommon::setLightsAsSent(const CCallsign &callsign, const CAircraftLights &lights)
1466  {
1467  if (!m_simConnectObjects.contains(callsign)) { return false; }
1468  m_simConnectObjects[callsign].setLightsAsSent(lights);
1469  return true;
1470  }
1471 
1472  void CSimulatorFsxCommon::timerEvent(QTimerEvent *event)
1473  {
1474  Q_UNUSED(event)
1475  if (this->isShuttingDown()) { return; }
1476  this->dispatch();
1477  }
1478 
1480 
1482  {
1483  // .driver sendid on|off
1484  if (parser.matchesPart(1, "sendid") && parser.hasPart(2))
1485  {
1486  const bool trace = parser.toBool(2);
1487  this->setTraceSendId(trace);
1488  CLogMessage(this, CLogCategories::cmdLine()).info(u"Tracing %1 driver sendIds is '%2'")
1489  << this->getSimulatorPluginInfo().getIdentifier() << boolToOnOff(trace);
1490  return true;
1491  }
1492 
1493  // .driver sboffsets on|off
1494  if (parser.matchesPart(1, "sboffsets") && parser.hasPart(2))
1495  {
1496  const bool on = parser.toBool(2);
1497  this->setUsingSbOffsetValues(on);
1498  CLogMessage(this, CLogCategories::cmdLine()).info(u"SB offsets is '%1'") << boolToOnOff(on);
1499  return true;
1500  }
1501 
1502  // .driver sblog on|off
1503  if (parser.matchesPart(1, "sblog") && parser.hasPart(2))
1504  {
1505  const bool on = parser.toBool(2);
1506  m_logSbOffsets = on;
1507  CLogMessage(this, CLogCategories::cmdLine()).info(u"SB log. offsets is '%1'") << boolToOnOff(on);
1508  return true;
1509  }
1510 
1511  return CSimulatorFsCommon::parseDetails(parser);
1512  }
1513 
1515  {
1516  if (CSimpleCommandParser::registered("swift::simplugin::fsxcommon::CSimulatorFsxCommon::CSimulatorFsxCommon"))
1517  {
1518  return;
1519  }
1520  CSimpleCommandParser::registerCommand({ ".drv", "alias: .driver .plugin" });
1521  CSimpleCommandParser::registerCommand({ ".drv sendid on|off", "Trace simConnect sendId on|off" });
1522  CSimpleCommandParser::registerCommand({ ".drv sboffsets on|off", "SB offsets via simConnect on|off" });
1523  CSimpleCommandParser::registerCommand({ ".drv sblog on|off", "SB offsets logging on|off" });
1524  }
1525 
1527  {
1528  const CCallsign cs = m_pendingProbeRequests.value(requestId);
1529  if (remove) { m_pendingProbeRequests.remove(requestId); }
1530  return cs;
1531  }
1532 
1533  const QString &CSimulatorFsxCommon::modeToString(CSimulatorFsxCommon::AircraftAddMode mode)
1534  {
1535  static const QString e("external call");
1536  static const QString pt("add pending by timer");
1537  static const QString oa("add pending after object added");
1538  static const QString ar("add again after removed");
1539  static const QString dontKnow("???");
1540 
1541  switch (mode)
1542  {
1543  case ExternalCall: return e;
1544  case AddByTimer: return pt;
1545  case AddAfterAdded: return oa;
1546  case AddedAfterRemoved: return ar;
1547  default: break;
1548  }
1549  return dontKnow;
1550  }
1551 
1552  void CSimulatorFsxCommon::dispatch()
1553  {
1554  // call CSimulatorFsxCommon::SimConnectProc or specialized P3D version
1555  Q_ASSERT_X(m_dispatchProc, Q_FUNC_INFO, "Missing DispatchProc");
1556 
1557  // statistics
1558  m_dispatchReceiveIdLast = SIMCONNECT_RECV_ID_NULL;
1559  m_dispatchRequestIdLast = CSimConnectDefinitions::RequestEndMarker;
1560  const qint64 start = QDateTime::currentMSecsSinceEpoch();
1561 
1562  // process
1563  const HRESULT hr = SimConnect_CallDispatch(m_hSimConnect, m_dispatchProc, this);
1564 
1565  // statistics
1566  const qint64 end = QDateTime::currentMSecsSinceEpoch();
1567  m_dispatchTimeMs = end - start;
1568  if (m_dispatchMaxTimeMs < m_dispatchTimeMs)
1569  {
1570  m_dispatchMaxTimeMs = m_dispatchTimeMs;
1571  m_dispatchReceiveIdMaxTime = m_dispatchReceiveIdLast;
1572  m_dispatchRequestIdMaxTime = m_dispatchRequestIdLast;
1573  }
1574 
1575  // error handling
1576  if (isFailure(hr))
1577  {
1578  // on FSX we normally receive this one here when simulator goes down, and NOT onSimExit
1579  // in that case sim status is Connected, but not PAUSED or SIMULATING
1580  const SimulatorStatus simStatus = this->getSimulatorStatus();
1581  const bool disconnectedOrNotSimulating =
1582  simStatus.testFlag(Disconnected) || !simStatus.testFlag(Simulating);
1583 
1584  m_dispatchErrors++;
1585  this->triggerAutoTraceSendId();
1586  if (m_dispatchErrors == 2)
1587  {
1588  // 2nd time, an error / avoid multiple messages
1589  // idea: if it happens once ignore
1590  const QString msg =
1591  QStringLiteral(u"%1: Dispatch error, sim.status: %2")
1592  .arg(this->getSimulatorPluginInfo().getIdentifier(), ISimulator::statusToString(simStatus));
1593  CLogMessage(this).log(
1594  disconnectedOrNotSimulating ? CStatusMessage::SeverityWarning : CStatusMessage::SeverityError, msg);
1595  }
1596  else if (m_dispatchErrors > 5)
1597  {
1598  // this normally happens during a FSX crash or shutdown with simconnect
1599  const QString msg =
1600  QStringLiteral(u"%1: Multiple dispatch errors, disconnecting. Sim.status: %2")
1601  .arg(this->getSimulatorPluginInfo().getIdentifier(), ISimulator::statusToString(simStatus));
1602  CLogMessage(this).log(
1603  disconnectedOrNotSimulating ? CStatusMessage::SeverityWarning : CStatusMessage::SeverityError, msg);
1604  this->disconnectFrom();
1605  }
1606  return;
1607  }
1608  m_dispatchErrors = 0;
1609  }
1610 
1611  bool CSimulatorFsxCommon::physicallyAddRemoteAircraftImpl(const CSimulatedAircraft &newRemoteAircraft,
1612  CSimulatorFsxCommon::AircraftAddMode addMode,
1613  const CSimConnectObject &correspondingSimObject)
1614  {
1615  const CCallsign callsign(newRemoteAircraft.getCallsign());
1616  const bool probe = newRemoteAircraft.isTerrainProbe();
1617 
1618  // entry checks
1619  Q_ASSERT_X(CThreadUtils::isInThisThread(this), Q_FUNC_INFO, "thread");
1620  Q_ASSERT_X(!callsign.isEmpty(), Q_FUNC_INFO, "empty callsign");
1621  Q_ASSERT_X(newRemoteAircraft.hasModelString(), Q_FUNC_INFO, "missing model string");
1622 
1623  // reset timer
1624  m_simObjectTimer.start(AddPendingAircraftIntervalMs); // restart
1625 
1626  // remove outdated objects
1627  const CSimConnectObjects outdatedAdded =
1628  m_simConnectObjects.removeOutdatedPendingAdded(CSimConnectObject::AllTypes);
1629  if (!outdatedAdded.isEmpty())
1630  {
1631  const CCallsignSet callsigns = outdatedAdded.getAllCallsigns(false);
1632  CLogMessage(this).warning(u"Removed %1 outdated object(s) pending for added: %2")
1633  << outdatedAdded.size() << callsigns.getCallsignsAsString(true);
1634  this->updateMultipleAircraftEnabled(callsigns, false);
1635 
1636  static const QString msgText("%1 outdated adding, %2");
1637  for (const CSimConnectObject &simObjOutdated : outdatedAdded)
1638  {
1639  const CStatusMessage msg = CStatusMessage(this).warning(
1640  msgText.arg(simObjOutdated.getCallsign().asString(), simObjOutdated.toQString()));
1641  emit this->physicallyAddingRemoteModelFailed(simObjOutdated.getAircraft(), true, true, msg); // outdated
1642  }
1643 
1644  // if this aircraft is also outdated, ignore
1645  if (callsigns.contains(newRemoteAircraft.getCallsign())) { return false; }
1646  }
1647 
1648  const bool hasPendingAdded = m_simConnectObjects.containsPendingAdded();
1649  bool canAdd = this->isSimulating() && !hasPendingAdded;
1650 
1651  Q_ASSERT_X(!hasPendingAdded || m_simConnectObjects.countPendingAdded() < 2, Q_FUNC_INFO,
1652  "There must be only 0..1 pending objects");
1653  if (this->showDebugLogMessage())
1654  {
1655  this->debugLogMessage(Q_FUNC_INFO, QStringLiteral("CS: '%1' mode: '%2' model: '%3'")
1656  .arg(newRemoteAircraft.getCallsignAsString(), modeToString(addMode),
1657  newRemoteAircraft.getModelString()));
1658  this->debugLogMessage(
1659  Q_FUNC_INFO, QStringLiteral("CS: '%1' pending callsigns: '%2', pending objects: '%3'")
1660  .arg(newRemoteAircraft.getCallsignAsString(),
1661  m_addPendingAircraft.getAllCallsignStrings(true).join(", "),
1663  }
1664 
1665  // do we need to remove/add again because something has changed?
1666  // this handles changed model strings or an update of the model
1667  if (m_simConnectObjects.contains(callsign))
1668  {
1669  const CSimConnectObject simObject = m_simConnectObjects[callsign];
1670  const QString newModelString(newRemoteAircraft.getModelString());
1671  const QString simObjModelString(simObject.getAircraftModelString());
1672  const bool sameModel =
1673  (simObjModelString ==
1674  newModelString); // compare on string only (other attributes might change such as mode)
1675 
1676  // same model, nothing will change, otherwise add again when removed
1677  if (sameModel)
1678  {
1679  CLogMessage(this).info(u"CS: '%1' re-added same model '%2'")
1680  << newRemoteAircraft.getCallsignAsString() << newModelString;
1681 
1682  // we restore rendered flag in case we are sure we are rendered
1683  // this is used with rematching
1684  const bool rendered = simObject.isConfirmedAdded() && simObject.isPending();
1685  if (rendered) { this->updateAircraftRendered(callsign, rendered); }
1686  return true;
1687  }
1688 
1689  this->physicallyRemoveRemoteAircraft(newRemoteAircraft.getCallsign());
1691  if (this->showDebugLogMessage())
1692  {
1693  this->debugLogMessage(Q_FUNC_INFO,
1694  QStringLiteral("CS: '%1' re-added changed model '%2', will be added again")
1695  .arg(newRemoteAircraft.getCallsignAsString(), newModelString));
1696  }
1697  return false;
1698  }
1699 
1700  // situation check
1701  CAircraftSituation situation(newRemoteAircraft.getSituation());
1702  if (canAdd && situation.isPositionOrAltitudeNull())
1703  {
1704  // invalid position because position or altitude is null
1705  const CAircraftSituationList situations(this->remoteAircraftSituations(callsign));
1706  if (situations.isEmpty())
1707  {
1708  CLogMessage(this).warning(u"No valid situations for '%1', will be added as pending")
1709  << callsign.asString();
1710  }
1711  else
1712  {
1713  CLogMessage(this).warning(u"Invalid aircraft situation for new aircraft '%1', use closest situation")
1714  << callsign.asString();
1715  situation = situations.findClosestTimeDistanceAdjusted(QDateTime::currentMSecsSinceEpoch());
1716  Q_ASSERT_X(!situation.isPositionOrAltitudeNull(), Q_FUNC_INFO, "Invalid situation for new aircraft");
1717  }
1718 
1719  // still invalid?
1720  canAdd = situation.isPositionOrAltitudeNull();
1721  if (CBuildConfig::isLocalDeveloperDebugBuild())
1722  {
1723  SWIFT_VERIFY_X(canAdd, Q_FUNC_INFO, "Expect valid situation");
1724  CLogMessage(this).warning(u"Invalid situation for '%1'") << callsign;
1725  }
1726  }
1727 
1728  // check if we can add, do not add if simulator is stopped or other objects pending
1729  if (!canAdd)
1730  {
1731  CSimConnectObject &addPendingObj = m_addPendingAircraft[newRemoteAircraft.getCallsign()];
1732  addPendingObj.setAircraft(newRemoteAircraft);
1733  addPendingObj.resetTimestampToNow();
1734  return false;
1735  }
1736 
1737  // remove from pending and keep for later to remember fail counters
1738  const CSimConnectObject removedPendingObj = this->removeFromAddPendingAndAddAgainAircraft(callsign);
1739 
1740  // create AI after crosschecking it
1741  if (!probe && !this->isAircraftInRangeOrTestMode(callsign))
1742  {
1743  CLogMessage(this).info(u"Skipping adding of '%1' since it is no longer in range") << callsign.asString();
1744  return false;
1745  }
1746 
1747  // setup
1749  this->getInterpolationSetupConsolidated(callsign, true);
1750  const bool sendGround = setup.isSendingGndFlagToSimulator();
1751 
1752  // FSX/P3D adding
1753  bool adding = false; // will be added flag
1754  const SIMCONNECT_DATA_REQUEST_ID requestId =
1756 
1757  // Initial situation, if possible from interpolation
1758  CAircraftSituation initialSituation = newRemoteAircraft.getSituation(); // default
1759  {
1760  // Dummy CSimConnectObject just for interpolation
1761  const CSimConnectObject dummyObject = CSimConnectObject(
1762  newRemoteAircraft, 0, this, this, this->getRemoteAircraftProvider(), &m_interpolationLogger);
1763  const CInterpolationResult result =
1764  dummyObject.getInterpolation(QDateTime::currentMSecsSinceEpoch(), setup, 0);
1765  if (result.getInterpolationStatus().isInterpolated())
1766  {
1767  initialSituation = result.getInterpolatedSituation();
1768  }
1769  }
1770 
1771  // under flow can cause a model not to be added
1772  // FSX: underflow and NO(!) gnd flag can cause adding/remove issue
1773  // P3D: underflow did not cause such issue
1774  CStatusMessage underflowStatus;
1775  const SIMCONNECT_DATA_INITPOSITION initialPosition =
1776  CSimulatorFsxCommon::aircraftSituationToFsxPosition(initialSituation, sendGround, true, &underflowStatus);
1777 
1778  const QString modelString(newRemoteAircraft.getModelString());
1779  if (this->showDebugLogMessage())
1780  {
1781  this->debugLogMessage(Q_FUNC_INFO, QStringLiteral("CS: '%1' model: '%2' request: %3, init pos: %4")
1782  .arg(callsign.toQString(), modelString)
1783  .arg(requestId)
1784  .arg(fsxPositionToString(initialPosition)));
1785  }
1786 
1787  const QByteArray modelStringBa = toFsxChar(modelString);
1788  const QByteArray csBa = toFsxChar(callsign.toQString().left(12));
1789  CSimConnectObject::SimObjectType type = CSimConnectObject::AircraftNonAtc;
1790  HRESULT hr = S_OK;
1791 
1792  if (probe)
1793  {
1794  hr = SimConnect_AICreateSimulatedObject(m_hSimConnect, modelStringBa.constData(), initialPosition,
1795  requestId);
1796  type = CSimConnectObject::TerrainProbe;
1797  }
1798  else
1799  {
1800  if (this->isAddingAsSimulatedObjectEnabled() && correspondingSimObject.hasCallsign() &&
1801  correspondingSimObject.getAddingExceptions() > 0 &&
1802  correspondingSimObject.getType() == CSimConnectObject::AircraftNonAtc)
1803  {
1804  CStatusMessage(this).warning(
1805  u"Model '%1' for '%2' failed %1 time(s) before, using AICreateSimulatedObject now")
1806  << newRemoteAircraft.getModelString() << callsign.toQString();
1807  hr = SimConnect_AICreateSimulatedObject(m_hSimConnect, modelStringBa.constData(), initialPosition,
1808  requestId);
1809  type = CSimConnectObject::AircraftSimulatedObject;
1810  }
1811  else
1812  {
1813  hr = SimConnect_AICreateNonATCAircraft(m_hSimConnect, modelStringBa.constData(), csBa.constData(),
1814  initialPosition, requestId);
1815  type = CSimConnectObject::AircraftNonAtc;
1816  }
1817  }
1818 
1819  if (!underflowStatus.isEmpty())
1820  {
1821  CStatusMessage(this).warning(u"Underflow detecion for '%1', details '%2'")
1822  << callsign.asString() << underflowStatus.getMessage();
1823  }
1824 
1825  if (isFailure(hr))
1826  {
1827  const CStatusMessage msg = CStatusMessage(this).error(u"SimConnect, can not create AI traffic: '%1' '%2'")
1828  << callsign.toQString() << modelString;
1829  CLogMessage::preformatted(msg);
1830  emit this->physicallyAddingRemoteModelFailed(newRemoteAircraft, true, true, msg); // SimConnect error
1831  }
1832  else
1833  {
1834  // we will request a new aircraft by request ID, later we will receive its object id
1835  // so far this object id is 0 (DWORD)
1836  const CSimConnectObject simObject =
1837  this->insertNewSimConnectObject(newRemoteAircraft, requestId, type, removedPendingObj);
1838  this->traceSendId(simObject, Q_FUNC_INFO,
1839  QStringLiteral("mode: %1").arg(CSimulatorFsxCommon::modeToString(addMode)), true);
1840  adding = true;
1841  }
1842  return adding;
1843  }
1844 
1845  bool CSimulatorFsxCommon::physicallyAddAITerrainProbe(const ICoordinateGeodetic &coordinate, int number)
1846  {
1847  if (coordinate.isNull()) { return false; }
1848  if (!this->isUsingFsxTerrainProbe()) { return false; }
1849  Q_ASSERT_X(CThreadUtils::isInThisThread(this), Q_FUNC_INFO, "thread");
1850 
1851  // static const QString modelString("OrcaWhale");
1852  // static const QString modelString("Water Drop"); // not working on P3Dx86/FSX, no requests on that id possible
1853  // static const QString modelString("A321ACA");
1854  // static const QString modelString("AI_Tracker_Object_0");
1855  // static const QString modelString("Piper Cub"); // P3Dv86 works as nonATC/SimulatedObject
1856  // static const QString modelString("Discovery Spaceshuttle"); // P3Dx86 works as nonATC/SimulatedObject
1857  static const QString modelString("swiftTerrainProbe0");
1858  static const QString pseudoCallsign("PROBE%1"); // max 12 chars
1859  static const CCountry ctry("SW", "SWIFT");
1860  static const CAirlineIcaoCode swiftAirline("SWI", "swift probe", ctry, "SWIFT", false, false);
1861  static const CLivery swiftLivery(CLivery::getStandardCode(swiftAirline), swiftAirline, "swift probe");
1862 
1863  const CCallsign cs(pseudoCallsign.arg(number));
1864  const CAircraftModel model(modelString, CAircraftModel::TypeTerrainProbe, QStringLiteral("swift terrain probe"),
1865  CAircraftIcaoCode::unassignedIcao(), swiftLivery);
1866  CAircraftSituation situation(cs, coordinate);
1867  situation.setAltitude(terrainProbeAltitude());
1868  situation.setZeroPBH();
1869  const CSimulatedAircraft pseudoAircraft(cs, model, CUser("123456", "swift", cs), situation);
1870  return this->physicallyAddRemoteAircraftImpl(pseudoAircraft, ExternalCall);
1871  }
1872 
1873  int CSimulatorFsxCommon::physicallyInitAITerrainProbes(const ICoordinateGeodetic &coordinate, int number)
1874  {
1875  if (number < 1) { return 0; }
1876  if (m_initFsxTerrainProbes) { return m_addedProbes; }
1877  m_initFsxTerrainProbes = true; // no multiple inits
1878  this->triggerAutoTraceSendId();
1879 
1880  int c = 0;
1881  for (int n = 0; n < number; ++n)
1882  {
1883  if (this->physicallyAddAITerrainProbe(coordinate, n)) { c++; }
1884  }
1885 
1886  CLogMessage(this).info(u"Adding %1 FSX terrain probes") << number;
1887  m_addedProbes = c;
1888  return c;
1889  }
1890 
1892  {
1893  // only remove from sim
1894  Q_ASSERT_X(CThreadUtils::isInThisThread(this), Q_FUNC_INFO, "wrong thread");
1895  if (callsign.isEmpty()) { return false; } // can happen if an object is not an aircraft
1896 
1897  // clean up anyway
1898  this->removeFromAddPendingAndAddAgainAircraft(callsign);
1899 
1900  // really remove from simulator
1901  if (!m_simConnectObjects.contains(callsign)) { return false; } // already fully removed or not yet added
1902  CSimConnectObject &simObject = m_simConnectObjects[callsign];
1903  if (simObject.isPendingRemoved()) { return true; }
1904  if (simObject.isTerrainProbe()) { return false; }
1905 
1906  // check for pending objects
1907  m_addPendingAircraft.remove(callsign); // just in case still in list of pending aircraft
1908  const bool pendingAdded = simObject.isPendingAdded(); // already added in simulator, but not yet confirmed
1909  const bool stillWaitingForLights = !simObject.hasCurrentLightsInSimulator();
1910  if (!simObject.isRemovedWhileAdding() && (pendingAdded || stillWaitingForLights))
1911  {
1912  // problem: we try to delete an aircraft just requested to be added
1913  // best solution so far, call remove again with a delay
1914  CLogMessage(this).warning(u"'%1' requested to be removed, but pending added (%2) / or pending lights(%3). "
1915  u"Object will be removed again: %4")
1916  << callsign.asString() << boolToYesNo(pendingAdded) << boolToYesNo(stillWaitingForLights)
1917  << simObject.toQString();
1918  simObject.setRemovedWhileAdding(true); // next time kill
1919  QPointer<CSimulatorFsxCommon> myself(this);
1920  QTimer::singleShot(2000, this, [=] {
1921  if (!myself) { return; }
1922  CLogMessage(this).info(u"Next trial to remove '%1'") << callsign.asString();
1923  myself->physicallyRemoveRemoteAircraft(callsign);
1924  });
1925  return false; // not yet deleted
1926  }
1927 
1928  // no more data from simulator
1929  this->stopRequestingDataForSimObject(simObject);
1930 
1931  // mark as removed
1932  simObject.setPendingRemoved(true);
1933  if (this->showDebugLogMessage())
1934  {
1935  this->debugLogMessage(Q_FUNC_INFO, QStringLiteral("CS: '%1' request/object id: %2/%3")
1936  .arg(callsign.toQString())
1937  .arg(simObject.getRequestId())
1938  .arg(simObject.getObjectId()));
1939  }
1940 
1941  // call in SIM
1942  const SIMCONNECT_DATA_REQUEST_ID requestId = simObject.getRequestId(CSimConnectDefinitions::SimObjectRemove);
1943  this->removeCamera(simObject);
1944  this->removeObserver(simObject);
1945  const HRESULT result = SimConnect_AIRemoveObject(
1946  m_hSimConnect, static_cast<SIMCONNECT_OBJECT_ID>(simObject.getObjectId()), requestId);
1947  if (isOk(result))
1948  {
1949  if (this->isTracingSendId()) { this->traceSendId(simObject, Q_FUNC_INFO); }
1950  }
1951  else { CLogMessage(this).warning(u"Removing aircraft '%1' from simulator failed") << callsign.asString(); }
1952 
1953  // mark in provider
1954  const bool updated = this->updateAircraftRendered(callsign, false);
1955  if (updated)
1956  {
1957  CSimulatedAircraft aircraft(simObject.getAircraft());
1958  aircraft.setRendered(false);
1959  emit this->aircraftRenderingChanged(aircraft);
1960  }
1961 
1962  // cleanup function, actually this should not be needed
1963  this->physicallyRemoveAircraftNotInProviderAsync();
1964 
1965  // bye
1966  return CSimulatorPluginCommon::physicallyRemoveRemoteAircraft(callsign);
1967  }
1968 
1970  {
1971  // make sure they are not added again
1972  // cleaning here is somewhat redundant, but double checks
1973  m_addPendingAircraft.clear();
1975 
1976  // remove one by one
1977  int r = 0;
1978  const CCallsignSet callsigns = m_simConnectObjects.getAllCallsigns();
1979  for (const CCallsign &cs : callsigns)
1980  {
1981  if (this->physicallyRemoveRemoteAircraft(cs)) { r++; }
1982  }
1983 
1984  CSimulatorFsCommon::physicallyRemoveAllRemoteAircraft();
1985  return r;
1986  }
1987 
1988  HRESULT CSimulatorFsxCommon::initEvents()
1989  {
1990  HRESULT hr = s_ok();
1991  // System events, see http://msdn.microsoft.com/en-us/library/cc526983.aspx#SimConnect_SubscribeToSystemEvent
1992  hr += SimConnect_SubscribeToSystemEvent(m_hSimConnect, SystemEventSimStatus, "Sim");
1993  hr += SimConnect_SubscribeToSystemEvent(m_hSimConnect, SystemEventObjectAdded, "ObjectAdded");
1994  hr += SimConnect_SubscribeToSystemEvent(m_hSimConnect, SystemEventObjectRemoved, "ObjectRemoved");
1995  hr += SimConnect_SubscribeToSystemEvent(m_hSimConnect, SystemEventFrame, "Frame");
1996  hr += SimConnect_SubscribeToSystemEvent(m_hSimConnect, SystemEventPause, "Pause");
1997  hr += SimConnect_SubscribeToSystemEvent(m_hSimConnect, SystemEventFlightLoaded, "FlightLoaded");
1998  if (isFailure(hr))
1999  {
2000  CLogMessage(this).error(u"FSX plugin error: %1") << "SimConnect_SubscribeToSystemEvent failed";
2001  return hr;
2002  }
2003 
2004  // Mapped events, see event ids here:
2005  // http://msdn.microsoft.com/en-us/library/cc526980.aspx
2006  // http://www.prepar3d.com/SDKv2/LearningCenter/utilities/variables/event_ids.html
2007  hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventPauseToggle, "PAUSE_TOGGLE");
2008  hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, SystemEventSlewToggle, "SLEW_TOGGLE");
2009  hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventFreezeLatLng,
2010  "FREEZE_LATITUDE_LONGITUDE_SET"); // FSX old standard
2011  hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventFreezeAlt,
2012  "FREEZE_ALTITUDE_SET"); // FSX old standard
2013  hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventFreezeAtt,
2014  "FREEZE_ATTITUDE_SET"); // FSX old standard
2015  hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventSetCom1Active, "COM_RADIO_SET");
2016  hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventSetCom1Standby, "COM_STBY_RADIO_SET");
2017  hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventSetCom2Active, "COM2_RADIO_SET");
2018  hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventSetCom2Standby, "COM2_STBY_RADIO_SET");
2019  hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventSetTransponderCode, "XPNDR_SET");
2020 
2021  hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventSetTimeZuluYear, "ZULU_YEAR_SET");
2022  hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventSetTimeZuluDay, "ZULU_DAY_SET");
2023  hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventSetTimeZuluHours, "ZULU_HOURS_SET");
2024  hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventSetTimeZuluMinutes, "ZULU_MINUTES_SET");
2025 
2026  hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventLandingLightsOff, "LANDING_LIGHTS_OFF");
2027  hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventLandinglightsOn, "LANDING_LIGHTS_ON");
2028  hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventLandingLightsSet, "LANDING_LIGHTS_SET");
2029  hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventLandingLightsToggle, "LANDING_LIGHTS_TOGGLE");
2030  hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventPanelLightsOff, "PANEL_LIGHTS_OFF");
2031  hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventPanelLightsOn, "PANEL_LIGHTS_ON");
2032  hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventPanelLightsSet, "PANEL_LIGHTS_SET");
2033  hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventStrobesOff, "STROBES_OFF");
2034  hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventStrobesOn, "STROBES_ON");
2035  hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventStrobesSet, "STROBES_SET");
2036  hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventStrobesToggle, "STROBES_TOGGLE");
2037  hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventToggleBeaconLights, "TOGGLE_BEACON_LIGHTS");
2038  hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventToggleCabinLights, "TOGGLE_CABIN_LIGHTS");
2039  hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventToggleLogoLights, "TOGGLE_LOGO_LIGHTS");
2040  hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventToggleNavLights, "TOGGLE_NAV_LIGHTS");
2041  hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventToggleRecognitionLights,
2042  "TOGGLE_RECOGNITION_LIGHTS");
2043  hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventToggleTaxiLights, "TOGGLE_TAXI_LIGHTS");
2044  hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventToggleWingLights, "TOGGLE_WING_LIGHTS");
2045 
2046  hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventFlapsSet, "FLAPS_SET");
2047 
2048  if (isFailure(hr))
2049  {
2050  CLogMessage(this).error(u"FSX plugin error: %1") << "SimConnect_MapClientEventToSimEvent failed";
2051  return hr;
2052  }
2053 
2054  // facility
2055  SIMCONNECT_DATA_REQUEST_ID requestId =
2056  static_cast<SIMCONNECT_DATA_REQUEST_ID>(CSimConnectDefinitions::RequestFacility);
2057  hr += SimConnect_SubscribeToFacilities(m_hSimConnect, SIMCONNECT_FACILITY_LIST_TYPE_AIRPORT, requestId);
2058  if (isFailure(hr))
2059  {
2060  CLogMessage(this).error(u"FSX plugin error: %1") << "SimConnect_SubscribeToFacilities failed";
2061  return hr;
2062  }
2063  return hr;
2064  }
2065 
2066  HRESULT CSimulatorFsxCommon::initDataDefinitionsWhenConnected()
2067  {
2070  }
2071 
2072  HRESULT CSimulatorFsxCommon::initWhenConnected()
2073  {
2074  // called when connected
2075 
2076  HRESULT hr = this->initEvents();
2077  if (isFailure(hr))
2078  {
2079  CLogMessage(this).error(u"FSX plugin: initEvents failed");
2080  return hr;
2081  }
2082 
2083  // init data definitions and SB data area
2084  hr += this->initDataDefinitionsWhenConnected();
2085  if (isFailure(hr))
2086  {
2087  CLogMessage(this).error(u"FSX plugin: initDataDefinitionsWhenConnected failed");
2088  return hr;
2089  }
2090 
2091  return hr;
2092  }
2093 
2094  void CSimulatorFsxCommon::updateRemoteAircraft()
2095  {
2096  static_assert(sizeof(DataDefinitionRemoteAircraftPartsWithoutLights) == sizeof(double) * 10,
2097  "DataDefinitionRemoteAircraftPartsWithoutLights has an incorrect size.");
2098  Q_ASSERT_X(CThreadUtils::isInThisThread(this), Q_FUNC_INFO, "thread");
2099 
2100  // nothing to do, reset request id and exit
2101  const int remoteAircraftNo = this->getAircraftInRangeCount();
2102  if (remoteAircraftNo < 1)
2103  {
2105  return;
2106  }
2107 
2108  // values used for position and parts
2109  const qint64 currentTimestamp = QDateTime::currentMSecsSinceEpoch();
2110  if (this->isUpdateAircraftLimitedWithStats(currentTimestamp))
2111  {
2112  this->finishUpdateRemoteAircraftAndSetStatistics(currentTimestamp, true);
2113  return;
2114  }
2116 
2117  // interpolation for all remote aircraft
2118  const QList<CSimConnectObject> simObjects(m_simConnectObjects.values());
2119 
2120  uint32_t simObjectNumber = 0;
2121  const bool traceSendId = this->isTracingSendId();
2122  const bool updateAllAircraft = this->isUpdateAllRemoteAircraft(currentTimestamp);
2123  for (const CSimConnectObject &simObject : simObjects)
2124  {
2125  // happening if aircraft is not yet added to simulator or to be deleted
2126  if (!simObject.isReadyToSend()) { continue; }
2127  if (!simObject.hasCurrentLightsInSimulator()) { continue; } // wait until we have light state
2128 
2129  const CCallsign callsign(simObject.getCallsign());
2130  const bool hasCs = !callsign.isEmpty();
2131  const bool hasValidIds = simObject.hasValidRequestAndObjectId();
2132  SWIFT_VERIFY_X(hasCs, Q_FUNC_INFO, "missing callsign");
2133  SWIFT_AUDIT_X(hasValidIds, Q_FUNC_INFO, "Missing ids");
2134  if (!hasCs || !hasValidIds) { continue; } // not supposed to happen
2135  const DWORD objectId = simObject.getObjectId();
2136 
2137  // setup
2139  this->getInterpolationSetupConsolidated(callsign, updateAllAircraft);
2140  const bool sendGround = setup.isSendingGndFlagToSimulator();
2141 
2142  // Interpolated situation
2143  // simObjectNumber is passed to equally distributed steps like guessing parts
2144  const bool slowUpdate = (((m_statsUpdateAircraftRuns + simObjectNumber) % 40) == 0);
2145  const CInterpolationResult result = simObject.getInterpolation(currentTimestamp, setup, simObjectNumber++);
2146  const bool forceUpdate = slowUpdate || updateAllAircraft || setup.isForcingFullInterpolation();
2148  {
2149  // update situation
2150  if (forceUpdate || !this->isEqualLastSent(result.getInterpolatedSituation()))
2151  {
2152  // adjust altitude to compensate for FS2020 temperature effect
2153  CAircraftSituation situation = result;
2154  const CLength relativeAltitude =
2156  const double altitudeDeltaWeight =
2157  2 - qBound(3000.0, relativeAltitude.abs().value(CLengthUnit::ft()), 6000.0) / 3000;
2158  situation.setAltitude({ situation.getAltitude() + m_altitudeDelta * altitudeDeltaWeight,
2159  situation.getAltitude().getReferenceDatum() });
2160 
2161  SIMCONNECT_DATA_INITPOSITION position = this->aircraftSituationToFsxPosition(situation, sendGround);
2162  const HRESULT hr = this->logAndTraceSendId(
2163  SimConnect_SetDataOnSimObject(m_hSimConnect,
2165  static_cast<SIMCONNECT_OBJECT_ID>(objectId), 0, 0,
2166  sizeof(SIMCONNECT_DATA_INITPOSITION), &position),
2167  traceSendId, simObject, "Failed to set position", Q_FUNC_INFO, "SimConnect_SetDataOnSimObject");
2168  if (isOk(hr))
2169  {
2170  this->rememberLastSent(result); // remember situation
2171  }
2172  }
2173  }
2174  else
2175  {
2176  // already logged in interpolator
2177  continue;
2178  }
2179 
2180  // Interpolated parts
2181  const bool updatedParts = this->updateRemoteAircraftParts(simObject, result, forceUpdate);
2182  Q_UNUSED(updatedParts)
2183 
2184  } // all callsigns
2185 
2186  // stats
2187  this->finishUpdateRemoteAircraftAndSetStatistics(currentTimestamp);
2188  }
2189 
2190  bool CSimulatorFsxCommon::updateRemoteAircraftParts(const CSimConnectObject &simObject,
2191  const CInterpolationResult &result, bool forcedUpdate)
2192  {
2193  if (!simObject.hasValidRequestAndObjectId()) { return false; }
2194  if (!simObject.isConfirmedAdded()) { return false; }
2195 
2196  const CAircraftParts parts = result;
2197  if (parts.isNull()) { return false; }
2198  if (parts.getPartsDetails() != CAircraftParts::GuessedParts && !result.getPartsStatus().isSupportingParts())
2199  {
2200  return false;
2201  }
2202 
2203  const CCallsign cs = simObject.getCallsign();
2204  if (!forcedUpdate && (result.getPartsStatus().isReusedParts() || this->isEqualLastSent(parts, cs)))
2205  {
2206  return true;
2207  }
2208 
2209  const bool ok = this->sendRemoteAircraftPartsToSimulator(simObject, parts);
2210  if (ok) { this->rememberLastSent(parts, cs); }
2211  return ok;
2212  }
2213 
2214  bool CSimulatorFsxCommon::sendRemoteAircraftPartsToSimulator(const CSimConnectObject &simObject,
2215  const CAircraftParts &parts)
2216  {
2217  Q_ASSERT(m_hSimConnect);
2218  if (!simObject.isReadyToSend()) { return false; }
2219 
2220  const DWORD objectId = simObject.getObjectId();
2221  const bool traceId = this->isTracingSendId();
2222 
2223  DataDefinitionRemoteAircraftPartsWithoutLights ddRemoteAircraftPartsWithoutLights(parts);
2224  const CAircraftLights lights = parts.getAdjustedLights();
2225 
2226  // in case we sent, we sent everything
2227  const bool simObjectAircraftType = simObject.isAircraftSimulatedObject(); // no real aircraft type
2228  const HRESULT hr1 =
2229  simObjectAircraftType ?
2230  S_OK :
2231  this->logAndTraceSendId(
2232  SimConnect_SetDataOnSimObject(
2233  m_hSimConnect, CSimConnectDefinitions::DataRemoteAircraftPartsWithoutLights,
2234  static_cast<SIMCONNECT_OBJECT_ID>(objectId), SIMCONNECT_DATA_SET_FLAG_DEFAULT, 0,
2235  sizeof(DataDefinitionRemoteAircraftPartsWithoutLights), &ddRemoteAircraftPartsWithoutLights),
2236  traceId, simObject, "Failed so set parts", Q_FUNC_INFO,
2237  "SimConnect_SetDataOnSimObject::ddRemoteAircraftPartsWithoutLights");
2238 
2239  // Sim variable version, not working, setting the value, but flaps retracting to 0 again
2240  // Sets flap handle to closest increment (0 to 16383)
2241  const DWORD flapsDw = static_cast<DWORD>(qMin(16383, qRound((parts.getFlapsPercent() / 100.0) * 16383)));
2242  const HRESULT hr2 = this->logAndTraceSendId(
2243  SimConnect_TransmitClientEvent(m_hSimConnect, objectId, EventFlapsSet, flapsDw,
2244  SIMCONNECT_GROUP_PRIORITY_HIGHEST,
2245  SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY),
2246  traceId, simObject, "Failed so set flaps", Q_FUNC_INFO, "SimConnect_TransmitClientEvent::EventFlapsSet");
2247 
2248  // lights we can set directly
2249  const HRESULT hr3 = this->logAndTraceSendId(
2250  SimConnect_TransmitClientEvent(m_hSimConnect, objectId, EventLandingLightsSet,
2251  lights.isLandingOn() ? 1.0 : 0.0, SIMCONNECT_GROUP_PRIORITY_HIGHEST,
2252  SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY),
2253  traceId, simObject, "Failed so set landing lights", Q_FUNC_INFO,
2254  "SimConnect_TransmitClientEvent::EventLandingLightsSet");
2255 
2256  const HRESULT hr4 =
2257  this->logAndTraceSendId(SimConnect_TransmitClientEvent(
2258  m_hSimConnect, objectId, EventStrobesSet, lights.isStrobeOn() ? 1.0 : 0.0,
2259  SIMCONNECT_GROUP_PRIORITY_HIGHEST, SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY),
2260  traceId, simObject, "Failed to set strobe lights", Q_FUNC_INFO,
2261  "SimConnect_TransmitClientEvent::EventStrobesSet");
2262 
2263  // lights we need to toggle
2264  // (potential risk with quickly changing values that we accidentally toggle back, also we need the light state
2265  // before we can toggle)
2266  this->sendToggledLightsToSimulator(simObject, lights);
2267 
2268  // done
2269  return isOk(hr1, hr2, hr3, hr4);
2270  }
2271 
2272  bool CSimulatorFsxCommon::sendRemoteAircraftAtcDataToSimulator(const CSimConnectObject &simObject)
2273  {
2274  if (!simObject.isReadyToSend()) { return false; }
2275  if (simObject.isTerrainProbe()) { return false; }
2276  // if (simObject.getType() != CSimConnectObject::AircraftNonAtc) { return false; } // otherwise errors
2277 
2278  const DWORD objectId = simObject.getObjectId();
2279  const bool traceId = this->isTracingSendId();
2280 
2281  DataDefinitionRemoteAtc ddAtc;
2282  ddAtc.setDefaultValues();
2283  const QByteArray csBa = simObject.getCallsignByteArray();
2284  const QByteArray airlineBa = simObject.getAircraft().getAirlineIcaoCode().getName().toLatin1();
2285  const QByteArray flightNumberBa = QString::number(simObject.getObjectId()).toLatin1();
2286 
2287  ddAtc.copyAtcId(csBa.constData());
2288  ddAtc.copyAtcAirline(airlineBa.constData());
2289  ddAtc.copyFlightNumber(flightNumberBa.constData());
2290 
2291  // in case we sent, we sent everything
2292  const HRESULT hr = this->logAndTraceSendId(
2294  static_cast<SIMCONNECT_OBJECT_ID>(objectId), SIMCONNECT_DATA_SET_FLAG_DEFAULT,
2295  0, sizeof(DataDefinitionRemoteAtc), &ddAtc),
2296  traceId, simObject, "Failed so aircraft ATC data", Q_FUNC_INFO, "SimConnect_SetDataOnSimObject");
2297  // done
2298  return isOk(hr);
2299  }
2300 
2301  void CSimulatorFsxCommon::sendToggledLightsToSimulator(const CSimConnectObject &simObj,
2302  const CAircraftLights &lightsWanted, bool force)
2303  {
2304  if (!simObj.isReadyToSend()) { return; } // stale
2305 
2306  const CAircraftLights lightsIsState = simObj.getCurrentLightsInSimulator();
2307  if (lightsWanted == lightsIsState) { return; }
2308  if (!force && lightsWanted == simObj.getLightsAsSent()) { return; }
2309  const CCallsign callsign(simObj.getCallsign());
2310 
2311  // Update data
2312  if (m_simConnectObjects.contains(callsign))
2313  {
2314  CSimConnectObject &simObjToUpdate = m_simConnectObjects[callsign];
2315  simObjToUpdate.setLightsAsSent(lightsWanted);
2316  }
2317 
2318  // state available, then I can toggle
2319  if (!lightsIsState.isNull())
2320  {
2321  const DWORD objectId = simObj.getObjectId();
2322  const bool trace = this->isTracingSendId();
2323 
2324  if (lightsWanted.isTaxiOn() != lightsIsState.isTaxiOn())
2325  {
2326  this->logAndTraceSendId(SimConnect_TransmitClientEvent(m_hSimConnect, objectId, EventToggleTaxiLights,
2327  0.0, SIMCONNECT_GROUP_PRIORITY_HIGHEST,
2328  SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY),
2329  trace, simObj, "Toggle taxi lights", Q_FUNC_INFO, "EventToggleTaxiLights");
2330  }
2331  if (lightsWanted.isNavOn() != lightsIsState.isNavOn())
2332  {
2333  this->logAndTraceSendId(SimConnect_TransmitClientEvent(m_hSimConnect, objectId, EventToggleNavLights,
2334  0.0, SIMCONNECT_GROUP_PRIORITY_HIGHEST,
2335  SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY),
2336  trace, simObj, "Toggle nav.lights", Q_FUNC_INFO, "EventToggleNavLights");
2337  }
2338  if (lightsWanted.isBeaconOn() != lightsIsState.isBeaconOn())
2339  {
2340  this->logAndTraceSendId(SimConnect_TransmitClientEvent(m_hSimConnect, objectId, EventToggleBeaconLights,
2341  0.0, SIMCONNECT_GROUP_PRIORITY_HIGHEST,
2342  SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY),
2343  trace, simObj, "Toggle becon lights", Q_FUNC_INFO, "EventToggleBeaconLights");
2344  }
2345  if (lightsWanted.isLogoOn() != lightsIsState.isLogoOn())
2346  {
2347  this->logAndTraceSendId(SimConnect_TransmitClientEvent(m_hSimConnect, objectId, EventToggleLogoLights,
2348  0.0, SIMCONNECT_GROUP_PRIORITY_HIGHEST,
2349  SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY),
2350  trace, simObj, "Toggle logo lights", Q_FUNC_INFO, "EventToggleLogoLights");
2351  }
2352  if (lightsWanted.isRecognitionOn() != lightsIsState.isRecognitionOn())
2353  {
2354  this->logAndTraceSendId(
2355  SimConnect_TransmitClientEvent(m_hSimConnect, objectId, EventToggleRecognitionLights, 0.0,
2356  SIMCONNECT_GROUP_PRIORITY_HIGHEST,
2357  SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY),
2358  trace, simObj, "Toggle recognition lights", Q_FUNC_INFO, "EventToggleRecognitionLights");
2359  }
2360  if (lightsWanted.isCabinOn() != lightsIsState.isCabinOn())
2361  {
2362  this->logAndTraceSendId(SimConnect_TransmitClientEvent(m_hSimConnect, objectId, EventToggleCabinLights,
2363  0.0, SIMCONNECT_GROUP_PRIORITY_HIGHEST,
2364  SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY),
2365  trace, simObj, "Toggle cabin lights", Q_FUNC_INFO, "EventToggleCabinLights");
2366  }
2367  return;
2368  }
2369 
2370  // missing lights info from simulator so far
2371  if (this->showDebugLogMessage())
2372  {
2373  this->debugLogMessage(Q_FUNC_INFO, QStringLiteral("Missing light state in simulator for '%1', model '%2'")
2374  .arg(callsign.asString(), simObj.getAircraftModelString()));
2375  }
2376 
2377  const QPointer<CSimulatorFsxCommon> myself(this);
2378  QTimer::singleShot(DeferResendingLights, this, [=] {
2379  if (!myself) { return; }
2380  if (!m_simConnectObjects.contains(callsign)) { return; }
2381  const CSimConnectObject currentSimObject = m_simConnectObjects[callsign];
2382  if (!currentSimObject.isReadyToSend()) { return; } // stale
2383  if (lightsWanted != currentSimObject.getLightsAsSent())
2384  {
2385  return;
2386  } // changed in between, so another call sendToggledLightsToSimulator is pending
2387  if (this->showDebugLogMessage())
2388  {
2389  this->debugLogMessage(Q_FUNC_INFO, QStringLiteral("Resending light state for '%1', model '%2'")
2390  .arg(callsign.asString(), simObj.getAircraftModelString()));
2391  }
2392  this->sendToggledLightsToSimulator(currentSimObject, lightsWanted, true);
2393  });
2394  }
2395 
2396  SIMCONNECT_DATA_INITPOSITION
2398  bool forceUnderflowDetection, CStatusMessage *details)
2399  {
2400  Q_ASSERT_X(!situation.isGeodeticHeightNull(), Q_FUNC_INFO, "Missing height (altitude)");
2401  Q_ASSERT_X(!situation.isPositionNull(), Q_FUNC_INFO, "Missing position");
2402 
2403  // lat/Lng, NO PBH
2404  CAircraftSituation::AltitudeCorrection altCorrection = CAircraftSituation::UnknownCorrection;
2405  SIMCONNECT_DATA_INITPOSITION position = CSimulatorFsxCommon::coordinateToFsxPosition(situation);
2406  if (forceUnderflowDetection)
2407  {
2408  const CAltitude alt = situation.getCorrectedAltitude(true, &altCorrection);
2409  position.Altitude = alt.value(CLengthUnit::ft());
2410  }
2411 
2412  // MSFS has inverted pitch and bank angles
2413  position.Pitch = -situation.getPitch().value(CAngleUnit::deg());
2414  position.Bank = -situation.getBank().value(CAngleUnit::deg());
2415  position.Heading = situation.getHeading().value(CAngleUnit::deg());
2416  position.OnGround = 0U; // not on ground
2417 
2418  const double gsKts = situation.getGroundSpeed().value(CSpeedUnit::kts());
2419  position.Airspeed = static_cast<DWORD>(qRound(gsKts));
2420 
2421  // sanity check
2422  if (gsKts < 0.0)
2423  {
2424  // we get negative GS for pushback and helicopters
2425  // here we handle them her with DWORD (unsigned)
2426  position.Airspeed = 0U;
2427  }
2428  else { position.Airspeed = static_cast<DWORD>(qRound(gsKts)); }
2429 
2430  // send GND flag also when underflow detection is available
2431  if ((sendGnd || forceUnderflowDetection) && situation.isOnGroundInfoAvailable())
2432  {
2433  const bool onGround = situation.isOnGround();
2434  position.OnGround = onGround ? 1U : 0U;
2435  }
2436 
2437  // if we have no GND flag yet (gnd flag prevents underflow)
2438  if (forceUnderflowDetection && position.OnGround == 0 &&
2439  !CAircraftSituation::isCorrectedAltitude(altCorrection))
2440  {
2441  // logical resolution failed so far, likely we have no CG or elevantion
2442  // primitive guessing
2443  do {
2444  if (position.Airspeed < 2)
2445  {
2446  position.OnGround = 1U;
2447  if (details)
2448  {
2449  *details = CStatusMessage(static_cast<CSimulatorFsxCommon *>(nullptr))
2450  .warning(u"Force GND flag for underflow protection");
2451  }
2452  break;
2453  }
2454  }
2455  while (false);
2456  }
2457 
2458  // crosscheck
2459  if (CBuildConfig::isLocalDeveloperDebugBuild())
2460  {
2461  SWIFT_VERIFY_X(isValidFsxPosition(position), Q_FUNC_INFO, "Invalid FSX pos.");
2462  }
2463 
2464  return position;
2465  }
2466 
2468  {
2469  // MSFS has inverted pitch and bank angles
2470  SIMCONNECT_DATA_PBH pbh;
2471  pbh.Pitch = -situation.getPitch().value(CAngleUnit::deg());
2472  pbh.Bank = -situation.getBank().value(CAngleUnit::deg());
2473  pbh.Heading = situation.getHeading().value(CAngleUnit::deg());
2474  return pbh;
2475  }
2476 
2477  SIMCONNECT_DATA_INITPOSITION CSimulatorFsxCommon::coordinateToFsxPosition(const ICoordinateGeodetic &coordinate)
2478  {
2479  SIMCONNECT_DATA_INITPOSITION position;
2480  position.Latitude = coordinate.latitude().value(CAngleUnit::deg());
2481  position.Longitude = coordinate.longitude().value(CAngleUnit::deg());
2482  position.Altitude = coordinate.geodeticHeight().value(
2483  CLengthUnit::ft()); // already corrected in interpolator if there is an underflow
2484  position.Heading = 0;
2485  position.Airspeed = 0;
2486  position.Pitch = 0;
2487  position.Bank = 0;
2488  position.OnGround = 0;
2489  return position;
2490  }
2491 
2492  SIMCONNECT_DATA_LATLONALT CSimulatorFsxCommon::coordinateToFsxLatLonAlt(const ICoordinateGeodetic &coordinate)
2493  {
2494  SIMCONNECT_DATA_LATLONALT lla;
2495  lla.Latitude = coordinate.latitude().value(CAngleUnit::deg());
2496  lla.Longitude = coordinate.longitude().value(CAngleUnit::deg());
2497  lla.Altitude = coordinate.geodeticHeight().value(
2498  CLengthUnit::ft()); // already corrected in interpolator if there is an underflow
2499  return lla;
2500  }
2501 
2502  bool CSimulatorFsxCommon::isValidFsxPosition(const SIMCONNECT_DATA_INITPOSITION &fsxPos)
2503  {
2504  // double Latitude; // degrees | double Longitude; // degrees | double Altitude; // feet
2505  // double Pitch; // degrees | double Bank; // degrees | double Heading; // degrees
2506  // DWORD OnGround; // 1=force to be on the ground | DWORD Airspeed; // knots
2507  // https://www.prepar3d.com/SDKv4/sdk/simconnect_api/references/simobject_functions.html
2508  // examples show heaading 180 => we assume values +-180deg
2509  if (!isValid180Deg(fsxPos.Pitch)) { return false; }
2510  if (!isValid180Deg(fsxPos.Bank)) { return false; }
2511  if (!isValid180Deg(fsxPos.Heading)) { return false; }
2512  if (!isValid180Deg(fsxPos.Latitude)) { return false; }
2513  if (!isValid180Deg(fsxPos.Longitude)) { return false; }
2514  return true;
2515  }
2516 
2517  bool CSimulatorFsxCommon::requestPositionDataForSimObject(const CSimConnectObject &simObject,
2518  SIMCONNECT_PERIOD period)
2519  {
2520  if (this->isShuttingDownOrDisconnected()) { return false; }
2521  if (!simObject.hasValidRequestAndObjectId()) { return false; }
2522  if (simObject.isPending()) { return false; } // wait until confirmed
2523  if (simObject.getSimDataPeriod() == period) { return true; } // already queried like this
2524  if (!m_simConnectObjects.contains(simObject.getCallsign())) { return false; } // removed in meantime
2525 
2526  // always request, not only when something has changed
2527  const SIMCONNECT_DATA_REQUEST_ID reqId = static_cast<SIMCONNECT_DATA_REQUEST_ID>(
2528  simObject.getRequestId(CSimConnectDefinitions::SimObjectPositionData));
2529  const HRESULT result = this->logAndTraceSendId(
2530  SimConnect_RequestDataOnSimObject(m_hSimConnect, reqId,
2532  simObject.getObjectId(), period),
2533  simObject, "Cannot request simulator data", Q_FUNC_INFO, "SimConnect_RequestDataOnSimObject");
2534 
2535  if (isOk(result))
2536  {
2537  m_requestSimObjectDataCount++;
2538  m_simConnectObjects[simObject.getCallsign()].setSimDataPeriod(period);
2539  return true;
2540  }
2541  return false;
2542  }
2543 
2544  bool CSimulatorFsxCommon::requestTerrainProbeData(const CSimConnectObject &simObject,
2545  const CCallsign &aircraftCallsign)
2546  {
2547  static const QString w("Cannot request terrain probe data for id '%1'");
2548  const SIMCONNECT_DATA_REQUEST_ID requestId =
2549  simObject.getRequestId(CSimConnectDefinitions::SimObjectPositionData);
2550  const DWORD objectId = simObject.getObjectId();
2551  const HRESULT result = this->logAndTraceSendId(
2552  SimConnect_RequestDataOnSimObject(m_hSimConnect, static_cast<SIMCONNECT_DATA_REQUEST_ID>(requestId),
2554  static_cast<SIMCONNECT_OBJECT_ID>(objectId), SIMCONNECT_PERIOD_ONCE),
2555  simObject, w.arg(requestId), Q_FUNC_INFO, "SimConnect_RequestDataOnSimObject");
2556  const bool ok = isOk(result);
2557  if (ok) { m_pendingProbeRequests.insert(requestId, aircraftCallsign); }
2558  return ok;
2559  }
2560 
2561  bool CSimulatorFsxCommon::requestLightsForSimObject(const CSimConnectObject &simObject)
2562  {
2563  if (!this->isValidSimObjectNotPendingRemoved(simObject)) { return false; }
2564  if (!m_hSimConnect) { return false; }
2565 
2566  // always request, not only when something has changed
2567  const SIMCONNECT_DATA_REQUEST_ID requestId = simObject.getRequestId(CSimConnectDefinitions::SimObjectLights);
2568  const HRESULT result = this->logAndTraceSendId(
2569  SimConnect_RequestDataOnSimObject(m_hSimConnect, requestId,
2570  CSimConnectDefinitions::DataRemoteAircraftLights, simObject.getObjectId(),
2571  SIMCONNECT_PERIOD_SECOND),
2572  simObject, "Cannot request lights data", Q_FUNC_INFO, "SimConnect_RequestDataOnSimObject");
2573  return isOk(result);
2574  }
2575 
2576  bool CSimulatorFsxCommon::requestModelInfoForSimObject(const CSimConnectObject &simObject)
2577  {
2578  if (!this->isValidSimObjectNotPendingRemoved(simObject)) { return false; }
2579  if (!m_hSimConnect) { return false; }
2580 
2581  // always request, not only when something has changed
2582  const SIMCONNECT_DATA_REQUEST_ID requestId = simObject.getRequestId(CSimConnectDefinitions::SimObjectModel);
2583  const HRESULT result = this->logAndTraceSendId(
2584  SimConnect_RequestDataOnSimObject(m_hSimConnect, requestId,
2586  simObject.getObjectId(), SIMCONNECT_PERIOD_ONCE),
2587  simObject, "Cannot request model info", Q_FUNC_INFO, "SimConnect_RequestDataOnSimObject");
2588  return isOk(result);
2589  }
2590 
2591  bool CSimulatorFsxCommon::stopRequestingDataForSimObject(const CSimConnectObject &simObject)
2592  {
2593  if (!simObject.hasValidRequestAndObjectId()) { return false; }
2594  if (!m_hSimConnect) { return false; }
2595 
2596  // stop by setting SIMCONNECT_PERIOD_NEVER
2597  SIMCONNECT_DATA_REQUEST_ID requestId = simObject.getRequestId(CSimConnectDefinitions::SimObjectPositionData);
2598  const HRESULT hr1 = this->logAndTraceSendId(
2599  SimConnect_RequestDataOnSimObject(m_hSimConnect, requestId,
2601  simObject.getObjectId(), SIMCONNECT_PERIOD_NEVER),
2602  simObject, "Stopping position request", Q_FUNC_INFO, "SimConnect_RequestDataOnSimObject");
2603 
2604  requestId = simObject.getRequestId(CSimConnectDefinitions::SimObjectLights);
2605  const HRESULT hr2 = this->logAndTraceSendId(
2606  SimConnect_RequestDataOnSimObject(m_hSimConnect, requestId,
2607  CSimConnectDefinitions::DataRemoteAircraftLights, simObject.getObjectId(),
2608  SIMCONNECT_PERIOD_NEVER),
2609  simObject, "Stopping lights request", Q_FUNC_INFO, "SimConnect_RequestDataOnSimObject");
2610  return isOk(hr1, hr2);
2611  }
2612 
2614  {
2615  CSimulatorFsCommon::initSimulatorInternals();
2616  m_simulatorInternals.setValue("fsx/simConnectVersion", m_simConnectVersion);
2617  }
2618 
2620  {
2621  this->safeKillTimer();
2622 
2623  // cleared below:
2624  // physicallyRemoveAllRemoteAircraft
2625  // m_simConnectObjects
2626  // m_simConnectObjectsPositionAndPartsTraces
2627  // m_addPendingAircraft
2628  // m_updateRemoteAircraftInProgress
2629  CSimulatorFsCommon::reset(); // clears all pending aircraft etc
2630 
2631  // reset values
2632  m_simulatingChangedTs = -1;
2633  m_simConnected = false;
2634  m_simSimulating = false;
2635  m_sbDataReceived = 0;
2636  m_requestIdSimObjAircraft = static_cast<SIMCONNECT_DATA_REQUEST_ID>(RequestSimObjAircraftStart);
2637  m_dispatchErrors = 0;
2638  m_receiveExceptionCount = 0;
2639  m_addedProbes = 0;
2640  m_initFsxTerrainProbes = false;
2641  m_sendIdTraces.clear();
2642  }
2643 
2645  {
2646  const bool reinitProbe =
2647  m_useFsxTerrainProbe && m_initFsxTerrainProbes; // re-init if enabled and was initialized
2648  this->removeAllProbes();
2649 
2650  // m_addAgainAircraftWhenRemoved cleared below
2651  CSimulatorFsCommon::clearAllRemoteAircraftData(); // also removes aircraft
2652  m_simConnectObjects.clear();
2653  m_addPendingAircraft.clear();
2654  m_simConnectObjectsPositionAndPartsTraces.clear();
2655 
2656  if (reinitProbe)
2657  {
2658  // if we are still alive we re-init the probes
2659  QPointer<CSimulatorFsxCommon> myself(this);
2660  QTimer::singleShot(2000, this, [=] {
2661  // Shutdown or unloaded
2662  if (this->isShuttingDown() || !myself) { return; }
2663  m_initFsxTerrainProbes = false; // probes will re-init
2664  });
2665  }
2666  }
2667 
2669  {
2670  m_sbDataReceived = 0;
2671  CSimulatorFsCommon::onOwnModelChanged(newModel);
2672  }
2673 
2674  QString CSimulatorFsxCommon::fsxPositionToString(const SIMCONNECT_DATA_INITPOSITION &position)
2675  {
2676  static const QString positionStr(
2677  "Lat: %1deg lng: %2deg alt: %3ft pitch: %4deg bank: %5deg hdg: %6deg airspeed: %7kts onGround: %8");
2678  return positionStr.arg(position.Latitude)
2679  .arg(position.Longitude)
2680  .arg(position.Altitude)
2681  .arg(position.Pitch)
2682  .arg(position.Bank)
2683  .arg(position.Heading)
2684  .arg(position.Airspeed)
2685  .arg(position.OnGround);
2686  }
2687 
2688  CCallsignSet CSimulatorFsxCommon::getCallsignsMissingInProvider() const
2689  {
2690  if (m_simConnectObjects.isEmpty()) { return CCallsignSet(); }
2691  const CCallsignSet simObjectCallsigns(m_simConnectObjects.getAllCallsigns(true));
2692  const CCallsignSet providerCallsigns(this->getAircraftInRangeCallsigns());
2693  return simObjectCallsigns.difference(providerCallsigns);
2694  }
2695 
2696  void CSimulatorFsxCommon::traceSendId(const CSimConnectObject &simObject, const QString &functionName,
2697  const QString &details, bool forceTrace)
2698  {
2699  if (!forceTrace && !this->isTracingSendId()) { return; }
2700  if (MaxSendIdTraces < 1) { return; } // cppcheck-suppress knownConditionTrueFalse
2701  DWORD dwLastId = 0;
2702  const HRESULT hr = SimConnect_GetLastSentPacketID(m_hSimConnect, &dwLastId);
2703  if (isFailure(hr)) { return; }
2704  if (m_sendIdTraces.size() > MaxSendIdTraces) { m_sendIdTraces.removeLast(); }
2705  const TraceFsxSendId trace(dwLastId, simObject,
2706  details.isEmpty() ? functionName : details % u", " % functionName);
2707  m_sendIdTraces.push_front(trace);
2708  }
2709 
2710  HRESULT CSimulatorFsxCommon::logAndTraceSendId(HRESULT hr, const QString &warningMsg, const QString &functionName,
2711  const QString &functionDetails)
2712  {
2713  const CSimConnectObject empty;
2714  return this->logAndTraceSendId(hr, empty, warningMsg, functionName, functionDetails);
2715  }
2716 
2717  HRESULT CSimulatorFsxCommon::logAndTraceSendId(HRESULT hr, const CSimConnectObject &simObject,
2718  const QString &warningMsg, const QString &functionName,
2719  const QString &functionDetails)
2720  {
2721  return this->logAndTraceSendId(hr, this->isTracingSendId(), simObject, warningMsg, functionName,
2722  functionDetails);
2723  }
2724 
2725  HRESULT CSimulatorFsxCommon::logAndTraceSendId(HRESULT hr, bool traceSendId, const CSimConnectObject &simObject,
2726  const QString &warningMsg, const QString &functionName,
2727  const QString &functionDetails)
2728  {
2729  if (traceSendId) { this->traceSendId(simObject, functionName, functionDetails); }
2730  if (isOk(hr)) { return hr; }
2731  if (!warningMsg.isEmpty()) { CLogMessage(this).warning(warningMsg % u" SimObject: " % simObject.toQString()); }
2732  this->triggerAutoTraceSendId();
2733  return hr;
2734  }
2735 
2736  QByteArray CSimulatorFsxCommon::toFsxChar(const QString &string) { return string.toLatin1(); }
2737 
2738  TraceFsxSendId CSimulatorFsxCommon::getSendIdTrace(DWORD sendId) const
2739  {
2740  for (const TraceFsxSendId &trace : m_sendIdTraces)
2741  {
2742  if (trace.sendId == sendId) { return trace; }
2743  }
2744  return TraceFsxSendId::invalid();
2745  }
2746 
2747  QString CSimulatorFsxCommon::getSendIdTraceDetails(DWORD sendId) const
2748  {
2749  const TraceFsxSendId trace = this->getSendIdTrace(sendId);
2750  if (trace.sendId == sendId) { return this->getSendIdTraceDetails(trace); }
2751  return {};
2752  }
2753 
2754  QString CSimulatorFsxCommon::getSendIdTraceDetails(const TraceFsxSendId &trace) const
2755  {
2756  static const QString d("Send id: %1 obj.id.: %2 SimObj: %3 | '%4'");
2757  if (trace.isInvalid()) { return QString(); }
2758 
2759  // update with latest sim object
2760  const CSimConnectObject simObject = this->getSimObjectForTrace(trace);
2761  return d.arg(trace.sendId).arg(simObject.getObjectId()).arg(simObject.toQString(), trace.comment);
2762  }
2763 
2764  int CSimulatorFsxCommon::removeAllProbes()
2765  {
2766  if (!m_hSimConnect) { return 0; } // already disconnected
2767  const QList<CSimConnectObject> probes = m_simConnectObjects.getProbes();
2768 
2769  int c = 0;
2770  for (const CSimConnectObject &probeSimObject : probes)
2771  {
2772  if (!probeSimObject.isConfirmedAdded()) { continue; }
2773  const SIMCONNECT_DATA_REQUEST_ID requestId =
2774  probeSimObject.getRequestId(CSimConnectDefinitions::SimObjectRemove);
2775  const HRESULT result = SimConnect_AIRemoveObject(
2776  m_hSimConnect, static_cast<SIMCONNECT_OBJECT_ID>(probeSimObject.getObjectId()), requestId);
2777  if (isOk(result)) { c++; }
2778  else
2779  {
2780  CLogMessage(this).warning(u"Removing probe '%1' from simulator failed") << probeSimObject.getObjectId();
2781  }
2782  }
2784  m_pendingProbeRequests.clear();
2785  return c;
2786  }
2787 
2788  CSimConnectObject CSimulatorFsxCommon::insertNewSimConnectObject(const CSimulatedAircraft &aircraft,
2789  DWORD requestId,
2791  const CSimConnectObject &removedPendingObject)
2792  {
2793  if (m_simConnectObjects.contains(aircraft.getCallsign()))
2794  {
2795  // error, ...?
2796  CSimConnectObject &simObject = m_simConnectObjects[aircraft.getCallsign()];
2797  simObject.copyAddingFailureCounters(removedPendingObject);
2798  simObject.resetTimestampToNow();
2799  return simObject;
2800  }
2801 
2802  CSimConnectObject simObject;
2803  if (m_simConnectObjectsPositionAndPartsTraces.contains(aircraft.getCallsign()))
2804  {
2805  // if in traces, get the object and reuse it
2806  simObject = m_simConnectObjectsPositionAndPartsTraces[aircraft.getCallsign()];
2807  m_simConnectObjectsPositionAndPartsTraces.remove(aircraft.getCallsign());
2808  simObject.resetState();
2809  simObject.setRequestId(requestId);
2810  simObject.setAircraft(aircraft);
2811  simObject.attachInterpolatorLogger(&m_interpolationLogger); // setting a logger does not start logging
2812  }
2813  else
2814  {
2815  simObject = CSimConnectObject(aircraft, requestId, this, this, this->getRemoteAircraftProvider(),
2817  }
2818  simObject.copyAddingFailureCounters(removedPendingObject);
2819  simObject.setType(type);
2820  m_simConnectObjects.insert(simObject, true); // update timestamp
2821  return simObject;
2822  }
2823 
2824  const CAltitude &CSimulatorFsxCommon::terrainProbeAltitude()
2825  {
2826  static const CAltitude alt(50000, CLengthUnit::ft());
2827  return alt;
2828  }
2829 
2830  QString CSimulatorFsxCommon::fsxCharToQString(const char *fsxChar, int size)
2831  {
2832  return QString::fromLatin1(fsxChar, size);
2833  }
2834 
2835  QString CSimulatorFsxCommon::requestIdToString(DWORD requestId)
2836  {
2838  {
2840  }
2841 
2844 
2845  static const QString req("%1 %2 %3");
2846  return req.arg(requestId)
2847  .arg(CSimConnectObject::typeToString(simType))
2849  }
2850 
2851  DWORD CSimulatorFsxCommon::unitTestRequestId(CSimConnectObject::SimObjectType type)
2852  {
2853  int start;
2854  int end;
2855  switch (type)
2856  {
2857  case CSimConnectObject::TerrainProbe:
2858  start = RequestSimObjTerrainProbeStart;
2859  end = RequestSimObjTerrainProbeEnd;
2860  break;
2861  case CSimConnectObject::AircraftNonAtc:
2862  case CSimConnectObject::AircraftSimulatedObject:
2863  default:
2864  start = RequestSimObjAircraftStart;
2865  end = RequestSimObjAircraftEnd;
2866  break;
2867  }
2868 
2869  const int id = CMathUtils::randomInteger(start, end);
2870  return static_cast<DWORD>(id);
2871  }
2872 
2873  CCallsignSet CSimulatorFsxCommon::physicallyRemoveAircraftNotInProvider()
2874  {
2875  const CCallsignSet callsignsToBeRemoved(this->getCallsignsMissingInProvider());
2876  if (callsignsToBeRemoved.isEmpty()) { return callsignsToBeRemoved; }
2877  for (const CCallsign &callsign : callsignsToBeRemoved) { this->physicallyRemoveRemoteAircraft(callsign); }
2878 
2879  if (this->showDebugLogMessage())
2880  {
2881  this->debugLogMessage(Q_FUNC_INFO,
2882  QStringLiteral("CS: '%1'").arg(callsignsToBeRemoved.toStringList().join(", ")));
2883  }
2884  return callsignsToBeRemoved;
2885  }
2886 
2887  void CSimulatorFsxCommon::physicallyRemoveAircraftNotInProviderAsync()
2888  {
2889  const QPointer<CSimulatorFsxCommon> myself(this);
2890  QTimer::singleShot(100, this, [=] {
2891  if (!myself || this->isShuttingDown()) { return; }
2892  CSimulatorFsxCommon::physicallyRemoveAircraftNotInProvider();
2893  });
2894  }
2895 
2897  : ISimulatorListener(info)
2898  {
2899  m_timer.setInterval(MinQueryIntervalMs);
2900  m_timer.setObjectName(this->objectName().append(":m_timer"));
2901  connect(&m_timer, &QTimer::timeout, this, &CSimulatorFsxCommonListener::checkConnection);
2902  }
2903 
2905  {
2906  m_simulatorVersion.clear();
2907  m_simConnectVersion.clear();
2908  m_simulatorName.clear();
2909  m_simulatorDetails.clear();
2910 
2911  m_timer.start();
2912  }
2913 
2915  {
2916  m_timer.stop();
2917  this->disconnectFromSimulator();
2918  }
2919 
2921  {
2922  if (!m_timer.isActive()) { return; }
2923  if (this->isShuttingDown()) { return; }
2924 
2925  QPointer<CSimulatorFsxCommonListener> myself(this);
2926  QTimer::singleShot(0, this, [=] {
2927  if (!myself || !sApp || sApp->isShuttingDown()) { return; }
2928  this->checkConnection();
2929  });
2930 
2931  // restart because we have just checked now
2932  m_timer.start();
2933  }
2934 
2936  {
2937  if (m_simulatorName.isEmpty()) { return ISimulatorListener::backendInfo(); }
2938  return m_simulatorDetails;
2939  }
2940 
2941  void CSimulatorFsxCommonListener::checkConnection()
2942  {
2943  Q_ASSERT_X(!CThreadUtils::thisIsMainThread(), Q_FUNC_INFO, "Expect to run in background");
2944 
2945  // check before we access the sim. connection
2946  if (this->isShuttingDown() || this->thread()->isInterruptionRequested())
2947  {
2948  this->stopImpl();
2949  return;
2950  }
2951 
2952  QElapsedTimer t;
2953  t.start();
2954  bool check = false;
2955  do {
2956  // if we can connect, but not dispatch, it can mean a previously started FSX/P3D
2957  // blocks remote calls -> RESTART
2958  if (!this->connectToSimulator()) { break; }
2959 
2960  // check if we have the right sim.
2961  // this check on a remote FSX/P3D not running/existing might TAKE LONG!
2962  const HRESULT result =
2963  SimConnect_CallDispatch(m_hSimConnect, CSimulatorFsxCommonListener::SimConnectProc, this);
2964 
2965  // make sure we did not stop in meantime
2966  if (this->isShuttingDown() || this->thread()->isInterruptionRequested())
2967  {
2968  this->stopImpl();
2969  return;
2970  }
2971 
2972  if (isFailure(result)) { break; } // means serious failure
2973  check = this->checkVersionAndSimulator();
2974  }
2975  while (false);
2976 
2977  this->adjustTimerInterval(t.elapsed());
2978  if (check) { emit this->simulatorStarted(this->getPluginInfo()); }
2979  }
2980 
2981  void CSimulatorFsxCommonListener::adjustTimerInterval(qint64 checkTimeMs)
2982  {
2983  const QString sim = this->getPluginInfo().getSimulatorInfo().toQString(true);
2984  CLogMessage(this).debug(u"Checked sim.'%1' connection in %2ms") << sim << checkTimeMs;
2985  if (checkTimeMs > qRound(1.25 * MinQueryIntervalMs))
2986  {
2987  const int newIntervalMs = qRound(1.2 * checkTimeMs / 1000.0) * 1000;
2988  CLogMessage(this).debug(u"Check for simulator sim.'%1' connection in %2ms, too slow. Setting %3ms")
2989  << sim << checkTimeMs << newIntervalMs;
2990  if (m_timer.interval() != newIntervalMs) { m_timer.setInterval(newIntervalMs); }
2991  }
2992  else
2993  {
2994  if (m_timer.interval() != MinQueryIntervalMs) { m_timer.setInterval(MinQueryIntervalMs); }
2995  }
2996 
2997  // restart
2998  m_timer.start();
2999  }
3000 
3001  bool CSimulatorFsxCommonListener::checkVersionAndSimulator() const
3002  {
3003  const CSimulatorInfo pluginSim(getPluginInfo().getIdentifier());
3004  const QString connectedSimName = m_simulatorName.toLower().trimmed();
3005 
3006  if (connectedSimName.isEmpty()) { return false; }
3007  if (pluginSim.isP3D())
3008  {
3009  // P3D drivers only works with P3D
3010  return connectedSimName.contains("lockheed") || connectedSimName.contains("martin") ||
3011  connectedSimName.contains("p3d") || connectedSimName.contains("prepar");
3012  }
3013  else if (pluginSim.isFSX())
3014  {
3015  // FSX drivers only works with FSX
3016  return connectedSimName.contains("fsx") || connectedSimName.contains("microsoft") ||
3017  connectedSimName.contains("simulator x");
3018  }
3019  else if (pluginSim.isMSFS())
3020  {
3021  // MSFS 2020 drivers only works with MSFS
3022  return connectedSimName.contains("kittyhawk");
3023  }
3024  else if (pluginSim.isMSFS2024())
3025  {
3026  // MSFS2024 drivers only works with MSFS2024
3027  return connectedSimName.contains("sunrise");
3028  }
3029  return false;
3030  }
3031 
3032  bool CSimulatorFsxCommonListener::checkSimConnectDll() const
3033  {
3034  static const CWinDllUtils::DLLInfo simConnectInfo = CSimConnectUtilities::simConnectDllInfo();
3035  if (!simConnectInfo.errorMsg.isEmpty()) { return false; }
3036  return true;
3037  }
3038 
3039  bool CSimulatorFsxCommonListener::connectToSimulator()
3040  {
3041  if (m_simConnected) { return true; }
3042  const HRESULT result = SimConnect_Open(&m_hSimConnect, sApp->swiftVersionChar(), nullptr, 0, nullptr, 0);
3043  const bool ok = isOk(result);
3044  m_simConnected = ok;
3045  return ok;
3046  }
3047 
3048  bool CSimulatorFsxCommonListener::disconnectFromSimulator()
3049  {
3050  if (!m_simConnected) { return false; }
3051  SimConnect_Close(m_hSimConnect);
3052  m_hSimConnect = nullptr;
3053  m_simConnected = false;
3054  return true;
3055  }
3056 
3057  void CSimulatorFsxCommonListener::SimConnectProc(SIMCONNECT_RECV *pData, DWORD cbData, void *pContext)
3058  {
3059  Q_UNUSED(cbData)
3060  CSimulatorFsxCommonListener *simListener = static_cast<CSimulatorFsxCommonListener *>(pContext);
3061  switch (pData->dwID)
3062  {
3063  case SIMCONNECT_RECV_ID_OPEN:
3064  {
3065  SIMCONNECT_RECV_OPEN *event = static_cast<SIMCONNECT_RECV_OPEN *>(pData);
3066  simListener->m_simulatorVersion = QStringLiteral("%1.%2.%3.%4")
3067  .arg(event->dwApplicationVersionMajor)
3068  .arg(event->dwApplicationVersionMinor)
3069  .arg(event->dwApplicationBuildMajor)
3070  .arg(event->dwApplicationBuildMinor);
3071  simListener->m_simConnectVersion = QStringLiteral("%1.%2.%3.%4")
3072  .arg(event->dwSimConnectVersionMajor)
3073  .arg(event->dwSimConnectVersionMinor)
3074  .arg(event->dwSimConnectBuildMajor)
3075  .arg(event->dwSimConnectBuildMinor);
3076  simListener->m_simulatorName = CSimulatorFsxCommon::fsxCharToQString(event->szApplicationName);
3077  simListener->m_simulatorDetails = QStringLiteral("Name: '%1' Version: %2 SimConnect: %3")
3078  .arg(simListener->m_simulatorName, simListener->m_simulatorVersion,
3079  simListener->m_simConnectVersion);
3080  const CStatusMessage msg = CStatusMessage(simListener).info(u"Connect to %1: '%2'")
3081  << simListener->getPluginInfo().getIdentifier() << simListener->backendInfo();
3082 
3083  // avoid the same message over and over again
3084  if (msg.getMessage() != simListener->m_lastMessage.getMessage())
3085  {
3086  CLogMessage::preformatted(msg);
3087  simListener->m_lastMessage = msg;
3088  }
3089  break;
3090  }
3091  case SIMCONNECT_RECV_ID_EXCEPTION: break;
3092  default: break;
3093  }
3094  }
3095 } // namespace swift::simplugin::fsxcommon
SWIFT_CORE_EXPORT swift::core::CApplication * sApp
Single instance of application object.
Definition: application.cpp:71
const char * swiftVersionChar()
swift info string
bool isShuttingDown() const
Is application shutting down?
virtual void callbackReceivedRequestedElevation(const swift::misc::geo::CElevationPlane &plane, const swift::misc::aviation::CCallsign &callsign, bool isWater)
A requested elevation has been received.
Definition: simulator.cpp:254
bool showDebugLogMessage() const
Show log messages?
Definition: simulator.cpp:150
bool addLoopbackSituation(const swift::misc::aviation::CAircraftSituation &situation)
Add a loopback situation if logging is enabled.
Definition: simulator.cpp:180
bool isEqualLastSent(const swift::misc::aviation::CAircraftSituation &compare) const
Equal to last sent situation.
Definition: simulator.cpp:551
bool updateOwnSituationAndGroundElevation(const swift::misc::aviation::CAircraftSituation &situation)
Update own aircraft position and if suitable use it to update ground elevation.
Definition: simulator.cpp:1057
bool isUpdateAllRemoteAircraft(qint64 currentTimestamp=-1) const
Do update all remote aircraft?
Definition: simulator.cpp:212
virtual bool isShuttingDown() const
Is overall (swift) application shutting down.
Definition: simulator.h:211
int m_timerId
dispatch timer id
Definition: simulator.h:558
void requestedElevation(const swift::misc::aviation::CCallsign &callsign)
Requested elevation, call pending.
bool m_updateRemoteAircraftInProgress
currently updating remote aircraft
Definition: simulator.h:556
virtual bool isShuttingDownDisconnectedOrNoAircraft() const
Shutting down, disconnected, or no remote aircraft.
Definition: simulator.h:217
void physicallyAddingRemoteModelFailed(const swift::misc::simulation::CSimulatedAircraft &remoteAircraft, bool disabled, bool requestFailover, const swift::misc::CStatusMessage &message)
Adding the remote model failed.
void rememberElevationAndSimulatorCG(const swift::misc::aviation::CCallsign &callsign, const swift::misc::simulation::CAircraftModel &model, bool likelyOnGroundElevation, const swift::misc::geo::CElevationPlane &elevation, const swift::misc::physical_quantities::CLength &simulatorCG)
Set elevation and CG in the providers and for auto publishing.
Definition: simulator.cpp:770
bool isAircraftInRangeOrTestMode(const swift::misc::aviation::CCallsign &callsign) const
Test version aware version of isAircraftInRange.
Definition: simulator.cpp:949
void finishUpdateRemoteAircraftAndSetStatistics(qint64 startTime, bool limited=false)
Update stats and flags.
Definition: simulator.cpp:1029
bool isTestMode() const
Test mode? (driver can skip code parts etc., driver dependent)
Definition: simulator.h:180
void debugLogMessage(const QString &msg)
Display a debug log message based on swift::misc::simulation::CInterpolationAndRenderingSetup remark ...
Definition: simulator.cpp:134
void emitSimulatorCombinedStatus(SimulatorStatus oldStatus=Unspecified)
Emit the combined status.
Definition: simulator.cpp:812
int m_statsUpdateAircraftRuns
statistics update count
Definition: simulator.h:559
swift::misc::simulation::CSimulatedAircraftList m_addAgainAircraftWhenRemoved
add this model again when removed, normally used to change model
Definition: simulator.h:587
swift::misc::simulation::CSimulatorInternals m_simulatorInternals
setup read from the sim
Definition: simulator.h:578
void logAddingAircraftModel(const swift::misc::simulation::CSimulatedAircraft &aircraft) const
Unified qeeing aircraft message.
Definition: simulator.cpp:1132
@ Disconnected
not connected, and hence not simulating/paused
Definition: simulator.h:69
@ Simulating
Is the simulator actually simulating?
Definition: simulator.h:71
swift::misc::simulation::CInterpolationAndRenderingSetupPerCallsign getInterpolationSetupConsolidated(const swift::misc::aviation::CCallsign &callsign, bool forceFullUpdate) const
Consolidate setup with other data like from swift::misc::simulation::IRemoteAircraftProvider.
Definition: simulator.cpp:237
virtual SimulatorStatus getSimulatorStatus() const
Combined status.
Definition: simulator.cpp:54
swift::misc::simulation::settings::CSpecializedSimulatorSettings getSimulatorSettings() const
Settings for current simulator.
Definition: simulator.h:159
swift::misc::simulation::CInterpolationLogger m_interpolationLogger
log.interpolation
Definition: simulator.h:579
bool isUpdateAircraftLimitedWithStats(qint64 startTime=-1)
Limited as ISimulator::isUpdateAircraftLimited plus updating statistics.
Definition: simulator.cpp:878
void safeKillTimer()
Kill timer if id is valid.
Definition: simulator.cpp:229
void aircraftRenderingChanged(const swift::misc::simulation::CSimulatedAircraft &aircraft)
Aircraft rendering changed.
bool isFlightNetworkConnected() const
Is the flight network connected.
Definition: simulator.h:156
virtual bool isShuttingDownOrDisconnected() const
Shutting down or disconnected?
Definition: simulator.h:214
void driverMessages(const swift::misc::CStatusMessageList &messages)
Relevant simulator messages to be explicitly displayed.
swift::misc::aviation::CAircraftSituationPerCallsign m_lastSentSituations
last situations sent to simulator
Definition: simulator.h:582
void rememberLastSent(const swift::misc::aviation::CAircraftSituation &sent)
Remember as last sent.
Definition: simulator.cpp:567
Interface to a simulator listener.
Definition: simulator.h:630
const swift::misc::simulation::CSimulatorPluginInfo & getPluginInfo() const
Corresponding info.
Definition: simulator.h:638
void simulatorStarted(const swift::misc::simulation::CSimulatorPluginInfo &info)
Emitted when the listener discovers the simulator running.
virtual bool isShuttingDown() const
Overall (swift) application shutting down.
Definition: simulator.cpp:1256
void check()
Check simulator availability.
Definition: simulator.cpp:1299
iterator push_back(const T &value)
Synonym for insert.
Definition: collection.h:238
const CIdentifier & identifier() const
Get identifier.
Definition: identifiable.h:27
Value object encapsulating information identifying a component of a modular distributed swift process...
Definition: identifier.h:29
Class for emitting a log message.
Definition: logmessage.h:27
Derived & warning(const char16_t(&format)[N])
Set the severity to warning, providing a format string.
bool isEmpty() const
Message empty.
Derived & log(StatusSeverity s, const char16_t(&m)[N])
Set the severity and format string.
Derived & error(const char16_t(&format)[N])
Set the severity to error, providing a format string.
Derived & debug()
Set the severity to debug.
Derived & info(const char16_t(&format)[N])
Set the severity to info, providing a format string.
bool contains(const T &object) const
Return true if there is an element equal to given object. Uses the most efficient implementation avai...
Definition: range.h:109
void push_back(const T &value)
Appends an element at the end of the sequence.
Definition: sequence.h:305
void clear()
Removes all elements in the sequence.
Definition: sequence.h:288
bool isEmpty() const
Synonym for empty.
Definition: sequence.h:285
Utility methods for simple line parsing used with the command line.
bool toBool(int index, bool def=false) const
Part as bool.
bool matchesPart(int index, const QString &toMatch, Qt::CaseSensitivity cs=Qt::CaseInsensitive) const
Matches given part.
bool hasPart(int index) const
Existing part.
Streamable status message, e.g.
bool isWarningOrAbove() const
Warning or above.
StatusSeverity getSeverity() const
Message severity.
QString getMessage() const
Message.
Status messages, e.g. from Core -> GUI.
CStatusMessage toSingleMessage() const
Merge into a single message.
void setMSecsSinceEpoch(qint64 mSecsSinceEpoch)
Timestamp as ms value.
Value object encapsulating information about aircraft's engines.
Value object encapsulating a list of aircraft engines.
Value object encapsulating information about aircraft's lights.
bool isRecognitionOn() const
Recognition lights on?
bool isCabinOn() const
Cabin lights on?
bool isLandingOn() const
Landing lights on?
bool isBeaconOn() const
Beacon lights on?
bool isNavOn() const
Nac lights on?
bool isLogoOn() const
Logo lights on?
bool isTaxiOn() const
Taxi lights on?
bool isStrobeOn() const
Strobes lights on?
Value object encapsulating information of aircraft's parts.
Definition: aircraftparts.h:26
bool isNull() const
NULL parts object?
CAircraftLights getAdjustedLights() const
Lights adjusted depending on engines.
PartsDetails getPartsDetails() const
Get parts details.
int getFlapsPercent() const
Get flaps position in percent.
Value object encapsulating information of an aircraft's situation.
void setPressureAltitude(const CAltitude &altitude)
Set pressure altitude.
void setGroundSpeed(const physical_quantities::CSpeed &groundspeed)
Set ground speed.
bool setGroundElevation(const aviation::CAltitude &altitude, GndElevationInfo info, bool transferred=false)
Elevation of the ground directly beneath at the given situation.
const CHeading & getHeading() const
Get heading.
void setBank(const physical_quantities::CAngle &bank)
Set bank (angle)
void setHeading(const CHeading &heading)
Set heading.
const CAltitude & geodeticHeight() const
Height, ellipsoidal or geodetic height (used in GPS)
AltitudeCorrection
How was altitude corrected?
bool isOnGroundInfoAvailable() const
On ground info available?
void setAltitude(const CAltitude &altitude)
Set altitude.
CAltitude getCorrectedAltitude(bool enableDragToGround=true, AltitudeCorrection *correction=nullptr) const
Get altitude under consideration of ground elevation and ground flag.
const physical_quantities::CSpeed & getGroundSpeed() const
Get ground speed.
void setOnGroundInfo(const aviation::COnGroundInfo &info)
Set the on ground info.
const CAltitude & getAltitude() const
Get altitude.
void setPitch(const physical_quantities::CAngle &pitch)
Set pitch.
const physical_quantities::CAngle & getBank() const
Get bank (angle)
virtual bool isNull() const
Null situation.
const physical_quantities::CAngle & getPitch() const
Get pitch.
void setVelocity(const CAircraftVelocity &velocity)
Set 6DOF velocity.
bool isMoving() const
Is moving? Means ground speed > epsilon.
void setPosition(const geo::CCoordinateGeodetic &position)
Set position.
Velocity and angular velocity for 6DOF bodies.
Value object for ICAO classification.
Altitude as used in aviation, can be AGL or MSL altitude.
Definition: altitude.h:52
ReferenceDatum getReferenceDatum() const
Get reference datum (MSL or AGL)
Definition: altitude.h:145
Value object encapsulating information of a callsign.
Definition: callsign.h:30
const QString & asString() const
Get callsign (normalized)
Definition: callsign.h:96
bool isEmpty() const
Is empty?
Definition: callsign.h:63
Value object for a set of callsigns.
Definition: callsignset.h:26
QStringList getCallsignStrings(bool sorted=false) const
The callsign strings.
Definition: callsignset.cpp:37
QString getCallsignsAsString(bool sorted=false, const QString &separator=", ") const
Callsigns as string.
Definition: callsignset.cpp:45
COM system (aka "radio")
Definition: comsystem.h:37
void setFrequencyActive(const physical_quantities::CFrequency &frequency)
Set active frequency.
Definition: comsystem.cpp:37
Heading as used in aviation, can be true or magnetic heading.
Definition: heading.h:41
Value object encapsulating information about an airpot.
Definition: livery.h:29
swift::misc::physical_quantities::CFrequency getFrequencyStandby() const
Standby frequency.
Definition: modulator.cpp:36
swift::misc::physical_quantities::CFrequency getFrequencyActive() const
Active frequency.
Definition: modulator.cpp:30
Value object for SELCAL.
Definition: selcal.h:31
bool isIdentifying() const
Standby?
Definition: transponder.h:89
bool isInStandby() const
Standby?
Definition: transponder.h:86
bool setTransponderMode(TransponderMode mode)
Set transponder mode.
Definition: transponder.cpp:97
TransponderMode getTransponderMode() const
Transponder mode.
Definition: transponder.h:95
int removeByCallsigns(const CCallsignSet &callsigns)
Remove all objects with callsigns.
OBJ findFirstByCallsign(const CCallsign &callsign, const OBJ &ifNotFound={}) const
Find the first aircraft by callsign, if none return given one.
int removeByCallsign(const CCallsign &callsign)
Remove all objects with callsign.
swift::misc::aviation::CCallsignSet getCallsigns() const
All callsigns.
bool containsCallsign(const CCallsign &callsign) const
Contains callsign?
QStringList getCallsignStrings(bool sorted=false) const
Get callsign string list.
void setLatitude(const CLatitude &latitude)
Set latitude.
virtual const aviation::CAltitude & geodeticHeight() const
Height, ellipsoidal or geodetic height (used in GPS)
void setGeodeticHeight(const aviation::CAltitude &height)
Set height (ellipsoidal or geodetic height)
void setLongitude(const CLongitude &longitude)
Set longitude.
Plane of same elevation, can be a single point or larger area (e.g. airport)
Geodetic coordinate, a position in 3D space relative to the reference geoid.
virtual CLongitude longitude() const =0
Longitude.
virtual bool isNull() const
Is null, means vector x, y, z == 0.
virtual const aviation::CAltitude & geodeticHeight() const =0
Height, ellipsoidal or geodetic height (used in GPS)
bool isGeodeticHeightNull() const
Geodetic height null?
virtual CLatitude latitude() const =0
Latitude.
QString toQString(bool i18n=false) const
Cast as QString.
Definition: mixinstring.h:76
Value object encapsulating information of a text message.
Definition: textmessage.h:31
bool isPrivateMessage() const
Is private message?
Definition: textmessage.cpp:53
QString asString(bool withSender, bool withRecipient, const QString &separator=", ") const
Whole message as formatted string. Used to display message in a console window.
bool isRadioMessage() const
Is radio message?
bool isSupervisorMessage() const
Supervisor message?
Definition: textmessage.cpp:58
Value object encapsulating information of a user.
Definition: user.h:28
Direct in memory access to client (network client) data.
Physical unit angle (radians, degrees)
Definition: angle.h:23
Physical unit length (length)
Definition: length.h:18
double value(MU unit) const
Value in given unit.
Aircraft model (used by another pilot, my models on disk)
Definition: aircraftmodel.h:71
const aviation::CCallsign & getCallsign() const
Corresponding callsign if applicable.
bool hasExistingCorrespondingFile() const
Does the corresponding file exist?
const QString & getModelString() const
Model key, either queried or loaded from simulator model.
CStatusMessageList verifyModelData() const
Verify the model data.
const QString & getFileName() const
File name (corresponding data for simulator, only available if representing simulator model.
bool isForcingFullInterpolation() const
Full interpolation (skip optimizations like checking if aircraft moves etc.)
Value object for interpolator and rendering per callsign.
const CInterpolationStatus & getInterpolationStatus() const
Get status.
const aviation::CAircraftSituation & getInterpolatedSituation() const
Get situation.
const CPartsStatus & getPartsStatus() const
Get status.
bool hasValidSituation() const
Is the corresponding position valid?
bool isInterpolated() const
Did interpolation succeed?
bool updateOwnParts(const aviation::CAircraftParts &parts)
Update own parts.
bool updateOwnCG(const physical_quantities::CLength &cg)
Update own aircraft's CG (aka vertical offset)
bool updateCockpit(const swift::misc::simulation::CSimulatedAircraft &aircraft, const swift::misc::CIdentifier &originator)
swift::misc::geo::CCoordinateGeodetic getOwnAircraftPosition() const
Own aircraft's position.
CSimulatedAircraft getOwnAircraft() const
Own aircraft.
bool isReusedParts() const
Is a reused parts object?
Definition: partsstatus.h:33
bool isSupportingParts() const
Supporting parts.
Definition: partsstatus.h:26
aviation::CCallsignSet getAircraftInRangeCallsigns() const
Unique callsigns for aircraft in range.
bool isAircraftInRange(const aviation::CCallsign &callsign) const
Is aircraft in range?
bool updateMultipleAircraftEnabled(const aviation::CCallsignSet &callsigns, bool enabledForRendering)
Enable/disable aircraft.
aviation::CAircraftSituationList remoteAircraftSituations(const aviation::CCallsign &callsign) const
Rendered aircraft situations (per callsign, time history)
int getAircraftInRangeCount() const
Count remote aircraft.
bool updateAircraftEnabled(const aviation::CCallsign &callsign, bool enabledForRendering)
Enable/disable aircraft and follow up logic like sending signals.
IRemoteAircraftProvider * getRemoteAircraftProvider() const
Get the aircraft provider.
bool updateCGAndModelString(const aviation::CCallsign &callsign, const physical_quantities::CLength &cg, const QString &modelString)
Update the CG and model string.
bool updateAircraftRendered(const aviation::CCallsign &callsign, bool rendered)
Set aircraft rendered.
Comprehensive information of an aircraft.
bool hasModelString() const
Has model string?
const aviation::CAircraftSituation & getSituation() const
Get situation.
const aviation::CComSystem & getCom2System() const
Get COM2 system.
const aviation::CTransponder & getTransponder() const
Get transponder.
const aviation::CCallsign & getCallsign() const
Get callsign.
QString getCallsignAsString() const
Get callsign.
const aviation::CComSystem & getCom1System() const
Get COM1 system.
const QString & getModelString() const
Get model string.
Value object encapsulating a list of aircraft.
bool replaceOrAddByCallsign(const CSimulatedAircraft &aircraft)
Replace or add by callsign.
Simple hardcoded info about the corresponding simulator.
Definition: simulatorinfo.h:41
void setValue(const QString &name, const QString &value)
Set value.
const QString & getIdentifier() const
Identifier.
const CSimulatorInfo & getSimulatorInfo() const
Simulator info object.
bool isLogCallsign(const aviation::CCallsign &callsign) const
Is callsign marked for logging.
CInterpolationAndRenderingSetupPerCallsign getInterpolationSetupPerCallsignOrDefault(const aviation::CCallsign &callsign) const
Get the setup for callsign, if not existing the global setup.
Direct threadsafe in memory access to own aircraft.
Direct thread safe in memory access to remote aircraft.
CSimulatorPluginInfo getSimulatorPluginInfo() const
Get the represented plugin.
CSimulatorInfo getSimulatorInfo() const
Get the represented simulator.
QString getSimulatorDetails() const
Simulator details as set from the running simulator.
QString getTitlesAsString(bool sorted, const QString &separator=", ") const
All titles as string.
bool containsTitle(const QString &title) const
Can check if a title (model string) is known.
bool isAddingAsSimulatedObjectEnabled() const
Use simulated object adding.
void setSbOffsetsEnabled(bool enable)
Use SB offsets?
void setAddingAsSimulatedObjectEnabled(bool enable)
Use simulated object adding.
bool isSbOffsetsEnabled() const
Are SB offsets enabled.
CFsxP3DSettings getSettings(const CSimulatorInfo &sim) const
Settings per simulator.
CStatusMessage setSettings(const CFsxP3DSettings &settings, const CSimulatorInfo &simulator)
Set settings per simulator.
Allows to have specific utility functions for each simulator.
QStringList getModelDirectoriesOrDefault() const
Model directories or default.
Common base class for MS flight simulators.
swift::misc::aviation::CComSystem m_simCom1
cockpit COM1 state in simulator
swift::misc::aviation::CSelcal m_selcal
SELCAL as in cockpit.
swift::misc::aviation::CComSystem m_simCom2
cockpit COM2 state in simulator
int m_ownAircraftUpdateCycles
own aircraft updates, even with 50 updates/sec long enough even for 32bit
swift::misc::aviation::CTransponder m_simTransponder
cockpit xpdr state in simulator
int m_skipCockpitUpdateCycles
skip some update cycles to allow changes in simulator cockpit to be set
SimObjectRequest
SimObject requests used for AI aircraft and probes.
@ SimObjectEndMarker
end marker, do NOT remove, also means invalid
static const QString & requestToString(Request request)
Request to string.
static HRESULT initDataDefinitionsWhenConnected(const HANDLE hSimConnect, const swift::misc::simulation::CSimulatorInfo &simInfo)
Initialize all data definitions.
static const QString & simObjectRequestToString(SimObjectRequest simObjectRequest)
Request to string.
@ DataClientAreaSbConnected
SB connected with network 0x7b81/1.
@ DataClientAreaSb
whole SB area, see http://squawkbox.ca/doc/sdk/fsuipc.php
@ DataRemoteAircraftModelData
model data eventually used and reported back from simulator
@ DataRemoteAircraftGetPosition
get position to evaluate altitude / AGL
Class representing a SimConnect object.
bool isPendingAdded() const
Object is requested in simulator, not yet confirmed added.
bool isConfirmedAdded() const
Adding is confirmed.
DWORD getObjectId() const
Get SimConnect object id.
QString toQString() const
SimObject as string.
bool hasValidRequestAndObjectId() const
Was the object really added to simulator.
bool hasCurrentLightsInSimulator() const
Received lights in simulator.
bool isPending() const
Pending added or removed?
bool isRemovedWhileAdding() const
Special states.
const swift::misc::simulation::CSimulatedAircraft & getAircraft() const
Simulated aircraft (as added)
static SimObjectType requestIdToType(DWORD requestId)
Type of id.
static const QString & typeToString(SimObjectType type)
Type to string.
DWORD getRequestId() const
Get SimConnect request id.
const swift::misc::aviation::CCallsign & getCallsign() const
Get callsign.
SIMCONNECT_PERIOD getSimDataPeriod() const
How often do we request data from simulator for this remote aircraft.
void setRemovedWhileAdding(bool removedWhileAdding)
Special states.
bool isPendingRemoved() const
Removing is pending.
void setPendingRemoved(bool pending)
Marked as pending for removal.
bool removeByOtherSimObject(const CSimConnectObject &otherSimObj)
Remove by object id or request id.
int countPendingAdded() const
Number of pending added.
swift::misc::aviation::CCallsignSet getAllCallsigns(bool withoutProbes=true) const
Get all callsigns.
CSimConnectObject getSimObjectForObjectId(DWORD objectId) const
Get object per object id.
bool insert(const CSimConnectObject &simObject, bool updateTimestamp=false)
Insert.
CSimConnectObject getSimObjectForOtherSimObject(const CSimConnectObject &otherSimObj) const
Get by request or object id, just as possible.
bool setSimConnectObjectIdForRequestId(DWORD requestId, DWORD objectId)
Set ID of a SimConnect object, so far we only have an request id in the object.
CSimConnectObject getOldestNotPendingProbe() const
Get a non pending probe.
swift::misc::aviation::CCallsignSet getPendingAddedCallsigns() const
Callsigns of pending added callsigns.
CSimConnectObject getOldestObject() const
Get the oldest object.
QString getAllCallsignStringsAsString(bool sorted=false, const QString &separator=", ") const
Get all callsign strings as string.
bool containsPendingAdded() const
Pending add condition.
int removeCallsigns(const swift::misc::aviation::CCallsignSet &callsigns)
Remove callsigns.
QList< CSimConnectObject > getProbes() const
All probes.
CSimConnectObjects removeOutdatedPendingAdded(CSimConnectObject::SimObjectType type)
Remove all pending added objects.
QStringList getAllCallsignStrings(bool sorted=false, bool withoutProbes=true) const
Get all callsign strings.
bool isUsingFsxTerrainProbe() const
FSX Terrain probe.
virtual bool updateOwnSimulatorSelcal(const swift::misc::aviation::CSelcal &selcal, const swift::misc::CIdentifier &originator)
Update own aircraft cockpit (usually from context)
virtual bool isPhysicallyRenderedAircraft(const swift::misc::aviation::CCallsign &callsign) const
Is the aircraft rendered (displayed in simulator)? This shall only return true if the aircraft is rea...
virtual void setTrueAltitude(swift::misc::aviation::CAircraftSituation &aircraftSituation, const swift::simplugin::fsxcommon::DataDefinitionOwnAircraft &simulatorOwnAircraft)
Customization point for adjusting altitude to compensate for temperature effect.
virtual void displayTextMessage(const swift::misc::network::CTextMessage &message) const
Display a text message.
virtual swift::misc::CStatusMessageList debugVerifyStateAfterAllAircraftRemoved() const
Debug function to check state after all aircraft have been removed.
SIMCONNECT_DATA_REQUEST_ID obtainRequestIdForSimObjTerrainProbe()
Get new request id, overflow safe.
static bool isValid180Deg(double deg)
Valid 180degrees value.
bool removeSimObjectForTrace(const TraceFsxSendId &trace)
Remove the CSimConnectObject linked in the trace.
virtual void setFlightNetworkConnected(bool connected)
Flight network has been connected.
CSimConnectObjects m_simConnectObjects
AI objects and their object and request ids.
swift::misc::physical_quantities::CLength m_altitudeDelta
FS2020 effect of temperature on altitude.
QMap< DWORD, swift::misc::aviation::CCallsign > m_pendingProbeRequests
pending elevation requests: requestId/aircraft callsign
static QString fsxCharToQString(const char *fsxChar, int size=-1)
Encapsulates creating QString from FSX string data.
static SIMCONNECT_DATA_INITPOSITION coordinateToFsxPosition(const swift::misc::geo::ICoordinateGeodetic &coordinate)
Format conversion.
virtual bool requestElevation(const swift::misc::geo::ICoordinateGeodetic &reference, const swift::misc::aviation::CCallsign &aircraftCallsign)
Request elevation, there is no guarantee the requested elevation will be available in the provider.
static bool isRequestForSimObjTerrainProbe(DWORD requestId)
Request for probe (elevation)?
virtual void displayStatusMessage(const swift::misc::CStatusMessage &message) const
Display a status message in the simulator.
void setTractingSendId(bool trace)
Set tracing on/off.
HRESULT logAndTraceSendId(HRESULT hr, const QString &warningMsg, const QString &functionName, const QString &functionDetails={})
Trace if required, log errors.
virtual void removeCamera(CSimConnectObject &simObject)
Remove camera if any.
virtual int physicallyRemoveAllRemoteAircraft()
Remove all remote aircraft and their data via ISimulator::clearAllRemoteAircraftData.
HANDLE m_hSimConnect
handle to SimConnect object
void setUsingSbOffsetValues(bool use)
Use SB offset values.
virtual bool physicallyAddRemoteAircraft(const swift::misc::simulation::CSimulatedAircraft &newRemoteAircraft)
Add new remote aircraft physically to the simulator.
SIMCONNECT_DATA_REQUEST_ID obtainRequestIdForSimObjAircraft()
Get new request id, overflow safe.
DispatchProc m_dispatchProc
called function for dispatch, can be overriden by specialized P3D function
virtual void removeObserver(CSimConnectObject &simObject)
Remove observer if any.
virtual void initSimulatorInternals()
Init the internal objects.
virtual bool testSendSituationAndParts(const swift::misc::aviation::CCallsign &callsign, const swift::misc::aviation::CAircraftSituation &situation, const swift::misc::aviation::CAircraftParts &parts)
Send situation/parts for testing.
virtual bool releaseAIControl(const CSimConnectObject &simObject, SIMCONNECT_DATA_REQUEST_ID requestId)
Release AI control.
virtual HRESULT initEventsP3D()
Specific P3D events.
virtual bool disconnectFrom()
Disconnect from simulator.
swift::misc::aviation::CCallsign getCallsignForPendingProbeRequests(DWORD requestId, bool remove)
Callsign for pending request.
bool isAddingAsSimulatedObjectEnabled() const
Allow adding as simulated object instead of non ATC.
void setAddingAsSimulatedObjectEnabled(bool enabled)
Allow adding as simulated object instead of non ATC.
virtual void onOwnModelChanged(const swift::misc::simulation::CAircraftModel &newModel)
Own model has been changed.
virtual swift::misc::aviation::CCallsignSet physicallyRenderedAircraft() const
Physically rendered (displayed in simulator) This shall only return aircraft handled in the simulator...
virtual bool isConnected() const
Are we connected to the simulator?
virtual void resetAircraftStatistics()
Reset the statistics.
virtual bool isSimulating() const
Simulator running?
static SIMCONNECT_DATA_INITPOSITION aircraftSituationToFsxPosition(const swift::misc::aviation::CAircraftSituation &situation, bool sendGnd=true, bool forceUnderflowDetection=false, swift::misc::CStatusMessage *details=nullptr)
Format conversion.
bool m_initFsxTerrainProbes
initialized terrain probes
static CSimConnectDefinitions::SimObjectRequest requestToSimObjectRequest(DWORD requestId)
Sub request type.
virtual QString getStatisticsSimulatorSpecific() const
Allows to print out simulator specific statistics.
static SIMCONNECT_DATA_LATLONALT coordinateToFsxLatLonAlt(const swift::misc::geo::ICoordinateGeodetic &coordinate)
Format conversion.
virtual bool updateOwnSimulatorCockpit(const swift::misc::simulation::CSimulatedAircraft &ownAircraft, const swift::misc::CIdentifier &originator)
Update own aircraft cockpit (usually from context)
bool triggerAutoTraceSendId(qint64 traceTimeMs=AutoTraceOffsetMs)
Trigger tracing ids for some while.
static bool isRequestForSimObjAircraft(DWORD requestId)
Request for sim data (request in range of sim data)?
virtual void timerEvent(QTimerEvent *event)
Timer event (our SimConnect event loop), runs dispatch.
static QByteArray toFsxChar(const QString &string)
Convert to FSX char array.
virtual bool connectTo()
Connect to simulator.
bool isValidSimObjectNotPendingRemoved(const CSimConnectObject &simObject) const
Valid CSimConnectObject which is NOT pendig removed.
static bool isValidFsxPosition(const SIMCONNECT_DATA_INITPOSITION &fsxPos)
Valid FSX/P3D position.
virtual void clearAllRemoteAircraftData()
Clear all aircraft related data, but do not physically remove the aircraft.
virtual swift::misc::CStatusMessageList getInterpolationMessages(const swift::misc::aviation::CCallsign &callsign) const
Interpolation messages for callsign.
CSimConnectObject getSimObjectForTrace(const TraceFsxSendId &trace) const
CSimConnectObject for trace.
static SIMCONNECT_DATA_PBH aircraftSituationToFsxPBH(const swift::misc::aviation::CAircraftSituation &situation)
Format conversion.
virtual bool physicallyRemoveRemoteAircraft(const swift::misc::aviation::CCallsign &callsign)
Remove remote aircraft from simulator.
virtual void startImpl()
Plugin specific implementation to start listener.
CSimulatorFsxCommonListener(const swift::misc::simulation::CSimulatorPluginInfo &info)
Constructor.
virtual void stopImpl()
Plugin specific implementation to stop listener.
virtual void checkImpl()
Plugin specific implementation to check.
virtual QString backendInfo() const
Info about the backend system (if available)
virtual bool parseDetails(const swift::misc::CSimpleCommandParser &parser)
Backend services of the swift project, like dealing with the network or the simulators.
Definition: actionbind.cpp:7
Free functions in swift::misc.
SWIFT_MISC_EXPORT const QString & boolToOnOff(bool v)
Bool to on/off.
T::const_iterator end(const LockFreeReader< T > &reader)
Non-member begin() and end() for so LockFree containers can be used in ranged for loops.
Definition: lockfree.h:338
SWIFT_MISC_EXPORT const QString & boolToEnabledDisabled(bool v)
Bool to enabled/disabled.
auto singleShot(int msec, QObject *target, F &&task)
Starts a single-shot timer which will call a task in the thread of the given object when it times out...
Definition: threadutils.h:30
const std::string & boolToYesNo(bool t)
Yes/no from bool.
Definition: qtfreeutils.h:129
unsigned long DWORD
Fake Windows DWORD.
HRESULT s_ok()
Correctly casted values/checks.
bool isFailure(HRESULT result)
Correctly casted values/checks.
bool dtb(double doubleBool)
Correctly casted values/checks.
EventIds
SimConnect Event IDs.
adding struct SIMCONNECT_DATA_PBH not existing in SimConnect FSX
QString errorMsg
error message if any
Definition: windllutils.h:27
Data structure for MSFS transponder mode information.
double comTransmit1
COM1 transmit, means also receiving.
double comReceiveAll
all COMs receiving, or COM:x transmitting or receiving
double comTransmit2
COM2 transmit, means also receiving.
double flapsHandlePosition
Flaps handle position in percent.
static const TraceFsxSendId & invalid()
Invalid object.
CSimConnectObject simObject
CSimConnectObject at the time of the trace.
#define SWIFT_AUDIT_X(COND, WHERE, WHAT)
A weaker kind of verify.
Definition: verify.h:38
#define SWIFT_VERIFY_X(COND, WHERE, WHAT)
A weaker kind of assert.
Definition: verify.h:26