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  {
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
285  }
286 
288  {
289  CStatusMessageList msgs;
290  if (!CBuildConfig::isLocalDeveloperDebugBuild()) { return msgs; }
291  msgs = CSimulatorFsCommon::debugVerifyStateAfterAllAircraftRemoved();
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  const QList<double> powerList { simulatorOwnAircraft.engine1RpmPct, simulatorOwnAircraft.engine2RpmPct,
747  simulatorOwnAircraft.engine3RpmPct, simulatorOwnAircraft.engine4RpmPct };
748 
749  for (int index = 0; index < simulatorOwnAircraft.numberOfEngines; ++index)
750  {
751  // this is a bit of a guess, but it seems that idle is around 30% in the sim, so
752  // I use that as 0% for better resolution
753  // because we read "GENERAL ENG PCT MAX RPM" and send "GENERAL ENG THROTTLE LEVER POSITION"
754  const double factor = 30.0;
755  double engineRpm = (powerList.value(index, 0) - factor) * 100 / (100 - factor);
756  if (engineRpm < 0) engineRpm = 0;
757  engines.push_back(CAircraftEngine(index + 1, helperList.value(index, false), engineRpm));
758  }
759 
760  const CAircraftParts parts(lights, dtb(simulatorOwnAircraft.gearHandlePosition),
761  qRound(simulatorOwnAircraft.flapsHandlePosition * 100),
762  dtb(simulatorOwnAircraft.spoilersHandlePosition), engines,
763  dtb(simulatorOwnAircraft.simOnGround), ts);
764 
765  // set values
766  this->updateOwnSituationAndGroundElevation(aircraftSituation);
767  this->updateOwnParts(parts);
768 
769  // When I change cockpit values in the sim (from GUI to simulator, not originating from simulator)
770  // it takes a little while before these values are set in the simulator.
771  // To avoid jitters, I wait some update cylces to stabilize the values
773  {
774  // defaults
775  CComSystem com1(myAircraft.getCom1System()); // set defaults
776  CComSystem com2(myAircraft.getCom2System());
777 
778  // updates:
779  // https://www.fsdeveloper.com/forum/threads/com-unit-receiving-status-com-transmit-x-com-test-1-and-volume.445187/
780  // COM: If you're set to transmit on a unit, you WILL receive that unit.
781  // Otherwise if you're NOT set to transmit on a unit, then it will only receive if COM RECEIVE ALL is true.
782  // There is no control of COM volume.
783  com1.setFrequencyActive(CFrequency(simulatorOwnAircraft.com1ActiveMHz, CFrequencyUnit::MHz()));
784  com1.setFrequencyStandby(CFrequency(simulatorOwnAircraft.com1StandbyMHz, CFrequencyUnit::MHz()));
785  const bool comReceiveAll = dtb(simulatorOwnAircraft.comReceiveAll);
786  const bool com1Test = dtb(simulatorOwnAircraft.comTest1);
787  const bool com1Transmit = dtb(simulatorOwnAircraft.comTransmit1);
788  const int com1Status =
789  qRound(simulatorOwnAircraft.comStatus1); // Radio status flag : -1 =Invalid 0 = OK 1 = Does not exist 2
790  // = No electricity 3 = Failed
791  com1.setTransmitEnabled(com1Status == 0 && com1Transmit);
792  com1.setReceiveEnabled(com1Status == 0 && (comReceiveAll || com1Transmit));
793 
794  const bool changedCom1Active =
795  myAircraft.getCom1System().getFrequencyActive() != com1.getFrequencyActive() &&
796  com1.getFrequencyActive() != m_lastCom1Active;
797  const bool changedCom1Standby =
798  myAircraft.getCom1System().getFrequencyStandby() != com1.getFrequencyStandby() &&
799  com1.getFrequencyStandby() != m_lastCom1Standby;
800 
801  // Avoid overwrite of 8.33 kHz frequency with data from simulator
802  if (!changedCom1Active) { com1.setFrequencyActive(myAircraft.getCom1System().getFrequencyActive()); }
803  else { m_lastCom1Active.setNull(); }
804 
805  if (!changedCom1Standby) { com1.setFrequencyStandby(myAircraft.getCom1System().getFrequencyStandby()); }
806  else { m_lastCom1Standby.setNull(); }
807 
808  const bool changedCom1 = myAircraft.getCom1System() != com1;
809 
810  m_simCom1 = com1;
811  Q_UNUSED(com1Test)
812 
813  com2.setFrequencyActive(CFrequency(simulatorOwnAircraft.com2ActiveMHz, CFrequencyUnit::MHz()));
814  com2.setFrequencyStandby(CFrequency(simulatorOwnAircraft.com2StandbyMHz, CFrequencyUnit::MHz()));
815  const bool com2Test = dtb(simulatorOwnAircraft.comTest2);
816  const bool com2Transmit = dtb(simulatorOwnAircraft.comTransmit2);
817  const int com2Status =
818  qRound(simulatorOwnAircraft.comStatus2); // Radio status flag : -1 =Invalid 0 = OK 1 = Does not exist 2
819  // = No electricity 3 = Failed
820  com2.setTransmitEnabled(com2Status == 0 && com2Transmit);
821  com2.setReceiveEnabled(com2Status == 0 && (comReceiveAll || com2Transmit));
822  const bool changedCom2Active =
823  myAircraft.getCom2System().getFrequencyActive() != com2.getFrequencyActive() &&
824  com2.getFrequencyActive() != m_lastCom2Active;
825  const bool changedCom2Standby =
826  myAircraft.getCom2System().getFrequencyStandby() != com2.getFrequencyStandby() &&
827  com2.getFrequencyStandby() != m_lastCom2Standby;
828 
829  // Avoid overwrite of 8.33 kHz frequency with data from simulator
830  if (!changedCom2Active) { com2.setFrequencyActive(myAircraft.getCom2System().getFrequencyActive()); }
831  else { m_lastCom2Active.setNull(); }
832 
833  if (!changedCom2Standby) { com2.setFrequencyStandby(myAircraft.getCom2System().getFrequencyStandby()); }
834  else { m_lastCom2Standby.setNull(); }
835 
836  const bool changedCom2 = myAircraft.getCom2System() != com2;
837 
838  m_simCom2 = com2;
839  Q_UNUSED(com2Test)
840 
841  CTransponder transponder(myAircraft.getTransponder());
842  transponder.setTransponderCode(qRound(simulatorOwnAircraft.transponderCode));
843  m_simTransponder = transponder;
844 
845  // if the simulator ever sends SELCAL, add it here.
846  // m_selcal SELCAL sync.would go here
847 
848  const bool changedXpr = (myAircraft.getTransponderCode() != transponder.getTransponderCode());
849 
850  if (changedCom1 || changedCom2 || changedXpr)
851  {
852  // set in own aircraft provider
853  this->updateCockpit(com1, com2, transponder, identifier());
854  }
855  }
856  else { --m_skipCockpitUpdateCycles; }
857 
858  // slower updates
859  if (m_ownAircraftUpdateCycles % 10 == 0)
860  {
861  // init terrain probes here has the advantage we can also switch it on/off at runtime
863  {
864  this->physicallyInitAITerrainProbes(position, 2); // init probe
865  }
866 
867  // SB3 offsets updating
868  m_simulatorInternals.setValue(QStringLiteral("fsx/sb3"), boolToEnabledDisabled(m_useSbOffsets));
869  m_simulatorInternals.setValue(QStringLiteral("fsx/sb3packets"), m_useSbOffsets ?
870  QString::number(m_sbDataReceived) :
871  QStringLiteral("disabled"));
872 
873  // CG
874  const CLength cg(simulatorOwnAircraft.cgToGroundFt, CLengthUnit::ft());
875  this->updateOwnCG(cg);
876 
877  // Simulated objects instead of NON ATC
878  m_simulatorInternals.setValue(QStringLiteral("fsx/addAsSimulatedObject"),
879  boolToEnabledDisabled(m_useAddSimulatedObj));
880 
881  } // slow updates
882 
883  m_ownAircraftUpdateCycles++; // with 50 updates/sec long enough even for 32bit
884  }
885 
886  void CSimulatorFsxCommon::triggerUpdateRemoteAircraftFromSimulator(const CSimConnectObject &simObject,
887  const DataDefinitionPosData &remoteAircraftData)
888  {
889  if (this->isShuttingDownOrDisconnected()) { return; }
890  QPointer<CSimulatorFsxCommon> myself(this);
891  QTimer::singleShot(0, this, [=] {
892  if (!myself) { return; }
893  myself->updateRemoteAircraftFromSimulator(simObject, remoteAircraftData);
894  });
895  }
896 
897  void CSimulatorFsxCommon::triggerUpdateRemoteAircraftFromSimulator(
898  const CSimConnectObject &simObject, const DataDefinitionRemoteAircraftModel &remoteAircraftModel)
899  {
900  if (this->isShuttingDownOrDisconnected()) { return; }
901  QPointer<CSimulatorFsxCommon> myself(this);
902  QTimer::singleShot(0, this, [=] {
903  if (!myself) { return; }
904  myself->updateRemoteAircraftFromSimulator(simObject, remoteAircraftModel);
905  });
906  }
907 
908  void CSimulatorFsxCommon::updateRemoteAircraftFromSimulator(const CSimConnectObject &simObject,
909  const DataDefinitionPosData &remoteAircraftData)
910  {
911  if (this->isShuttingDownOrDisconnected()) { return; }
912 
913  // Near ground we use faster updates
914  const CCallsign cs(simObject.getCallsign());
915  CAircraftSituation lastSituation = m_lastSentSituations[cs];
916  const bool moving = lastSituation.isMoving();
917  const bool onGround = remoteAircraftData.isOnGround();
918 
919  // CElevationPlane: deg, deg, feet
920  // we only remember near ground
921  const CElevationPlane elevation =
922  CElevationPlane(remoteAircraftData.latitudeDeg, remoteAircraftData.longitudeDeg,
923  remoteAircraftData.elevationFt, CElevationPlane::singlePointRadius());
924  if (remoteAircraftData.aboveGroundFt() < 250)
925  {
926  const CLength cg(remoteAircraftData.cgToGroundFt, CLengthUnit::ft());
927  this->rememberElevationAndSimulatorCG(cs, simObject.getAircraftModel(), onGround, elevation, cg);
928  }
929 
930  const bool log = this->isLogCallsign(cs);
931  if (log)
932  {
933  // update lat/lng/alt with real data from sim
934  const CAltitude alt(remoteAircraftData.altitudeFt, CAltitude::MeanSeaLevel, CAltitude::TrueAltitude,
935  CLengthUnit::ft());
936  lastSituation.setPosition(elevation);
937  lastSituation.setAltitude(alt);
938  lastSituation.setGroundElevation(elevation, CAircraftSituation::FromProvider);
939  this->addLoopbackSituation(lastSituation);
940  }
941 
942  if (moving && remoteAircraftData.aboveGroundFt() <= 100.0)
943  {
944  // switch to fast updates
945  if (simObject.getSimDataPeriod() != SIMCONNECT_PERIOD_VISUAL_FRAME)
946  {
947  this->requestPositionDataForSimObject(simObject, SIMCONNECT_PERIOD_VISUAL_FRAME);
948  }
949  }
950  else
951  {
952  // switch to slow updates
953  if (simObject.getSimDataPeriod() != SIMCONNECT_PERIOD_SECOND)
954  {
955  this->requestPositionDataForSimObject(simObject, SIMCONNECT_PERIOD_SECOND);
956  }
957  }
958  }
959 
960  void
961  CSimulatorFsxCommon::updateRemoteAircraftFromSimulator(const CSimConnectObject &simObject,
962  const DataDefinitionRemoteAircraftModel &remoteAircraftModel)
963  {
964  const CCallsign cs(simObject.getCallsign());
965  if (!m_simConnectObjects.contains(cs)) { return; } // no longer existing
966  CSimConnectObject &so = m_simConnectObjects[cs];
967  if (so.isPendingRemoved()) { return; }
968 
969  const QString modelString(remoteAircraftModel.title);
970  const CLength cg(remoteAircraftModel.cgToGroundFt, CLengthUnit::ft());
971  so.setAircraftCG(cg);
972  so.setAircraftModelString(modelString);
973 
974  // update in 2 providers
975  this->rememberElevationAndSimulatorCG(cs, simObject.getAircraftModel(), false, CElevationPlane::null(),
976  cg); // env. provider
977  this->updateCGAndModelString(cs, cg, modelString); // remote aircraft provider
978  }
979 
980  void CSimulatorFsxCommon::updateProbeFromSimulator(const CCallsign &callsign,
981  const DataDefinitionPosData &remoteAircraftData)
982  {
983  const CElevationPlane elevation(remoteAircraftData.latitudeDeg, remoteAircraftData.longitudeDeg,
984  remoteAircraftData.elevationFt, CElevationPlane::singlePointRadius());
985  this->callbackReceivedRequestedElevation(elevation, callsign, false);
986  }
987 
988  void CSimulatorFsxCommon::updateOwnAircraftFromSimulator(const DataDefinitionClientAreaSb &sbDataArea)
989  {
990  if (m_skipCockpitUpdateCycles > 0) { return; }
991 
992  // log SB offset
993  if (m_logSbOffsets) { CLogMessage(this).info(u"SB from sim: " % sbDataArea.toQString()); }
994 
995  // SB XPDR mode
996  CTransponder::TransponderMode newMode = CTransponder::StateIdent;
997  if (!sbDataArea.isIdent())
998  {
999  newMode = sbDataArea.isStandby() ? CTransponder::StateStandby : CTransponder::ModeC;
1000  }
1001  const CSimulatedAircraft myAircraft(this->getOwnAircraft());
1002  const bool changed = (myAircraft.getTransponderMode() != newMode);
1003  if (!changed) { return; }
1004  CTransponder xpdr = myAircraft.getTransponder();
1005  xpdr.setTransponderMode(newMode);
1006  this->updateCockpit(myAircraft.getCom1System(), myAircraft.getCom2System(), xpdr, this->identifier());
1007  }
1008 
1009  void CSimulatorFsxCommon::updateTransponderMode(const CTransponder::TransponderMode xpdrMode)
1010  {
1011  if (m_skipCockpitUpdateCycles > 0) { return; }
1012  const CSimulatedAircraft myAircraft(this->getOwnAircraft());
1013  const bool changed = (myAircraft.getTransponderMode() != xpdrMode);
1014  if (!changed) { return; }
1015  CTransponder myXpdr = myAircraft.getTransponder();
1016  myXpdr.setTransponderMode(xpdrMode);
1017  this->updateCockpit(myAircraft.getCom1System(), myAircraft.getCom2System(), myXpdr, this->identifier());
1018  }
1019 
1020  void CSimulatorFsxCommon::updateMSFSTransponderMode(const DataDefinitionMSFSTransponderMode transponderMode)
1021  {
1022  auto mode = CTransponder::StateIdent;
1023  if (!transponderMode.ident)
1024  {
1025  qRound(transponderMode.transponderMode) >= 3 ? mode = CTransponder::ModeC :
1026  mode = CTransponder::StateStandby;
1027  }
1028  this->updateTransponderMode(mode);
1029  }
1030 
1031  bool CSimulatorFsxCommon::simulatorReportedObjectAdded(DWORD objectId)
1032  {
1033  if (this->isShuttingDownOrDisconnected()) { return true; } // pretend everything is fine
1034  const CSimConnectObject simObject = m_simConnectObjects.getSimObjectForObjectId(objectId);
1035  const CCallsign callsign(simObject.getCallsign());
1036  if (!simObject.hasValidRequestAndObjectId() || callsign.isEmpty()) { return false; }
1037 
1038  // we know the object has been created. But it can happen it is directly removed afterwards
1039  const CSimulatedAircraft verifyAircraft(simObject.getAircraft());
1040  const QPointer<CSimulatorFsxCommon> myself(this);
1041  QTimer::singleShot(1000, this, [=] {
1042  // verify aircraft and also triggers new add if required
1043  // do not do this in the event loop, so we do this deferred
1044  if (!myself || this->isShuttingDownOrDisconnected()) { return; }
1045  this->verifyAddedRemoteAircraft(verifyAircraft);
1046  });
1047  return true;
1048  }
1049 
1050  void CSimulatorFsxCommon::verifyAddedRemoteAircraft(const CSimulatedAircraft &remoteAircraftIn)
1051  {
1052  if (this->isShuttingDownOrDisconnected()) { return; }
1053  if (remoteAircraftIn.isTerrainProbe())
1054  {
1055  this->verifyAddedTerrainProbe(remoteAircraftIn);
1056  return;
1057  }
1058 
1059  CStatusMessage msg;
1060  CSimulatedAircraft remoteAircraft = remoteAircraftIn;
1061  const CCallsign callsign(remoteAircraft.getCallsign());
1062 
1063  do {
1064  // no callsign
1065  if (callsign.isEmpty())
1066  {
1067  msg = CLogMessage(this).error(u"Cannot confirm AI object, empty callsign");
1068  break;
1069  }
1070 
1071  // removed in meantime
1072  const bool aircraftStillInRange = this->isAircraftInRange(callsign);
1073  if (!m_simConnectObjects.contains(callsign))
1074  {
1075  if (aircraftStillInRange)
1076  {
1077  msg = CLogMessage(this).warning(
1078  u"Callsign '%1' removed in meantime from AI objects, but still in range")
1079  << callsign.toQString();
1080  }
1081  else
1082  {
1083  this->removeFromAddPendingAndAddAgainAircraft(callsign);
1084  msg = CLogMessage(this).info(u"Callsign '%1' removed in meantime and no longer in range")
1085  << callsign.toQString();
1086  }
1087  break;
1088  }
1089 
1090  CSimConnectObject &simObject = m_simConnectObjects[callsign];
1091  remoteAircraft = simObject.getAircraft(); // update, if something has changed
1092 
1093  if (!simObject.hasValidRequestAndObjectId() || simObject.isPendingRemoved())
1094  {
1095  msg = CStatusMessage(this).warning(u"Object for callsign '%1'/id: %2 removed in meantime/invalid")
1096  << callsign.toQString() << simObject.getObjectId();
1097  break;
1098  }
1099 
1100  // P3D also has SimConnect_AIReleaseControlEx which also allows to destroy the aircraft
1101  const SIMCONNECT_DATA_REQUEST_ID requestReleaseId = this->obtainRequestIdForSimObjAircraft();
1102  const bool released = this->releaseAIControl(simObject, requestReleaseId);
1103 
1104  if (!released)
1105  {
1106  msg = CStatusMessage(this).error(u"Cannot confirm model '%1' %2")
1107  << remoteAircraft.getModelString() << simObject.toQString();
1108  break;
1109  }
1110 
1111  // confirm as added, this is also required to request light, etc
1112  Q_ASSERT_X(simObject.isPendingAdded(), Q_FUNC_INFO, "Already confirmed, this should be the only place");
1113  simObject.setConfirmedAdded(true); // aircraft
1114 
1115  // request data on object
1116  this->requestPositionDataForSimObject(simObject);
1117  this->requestLightsForSimObject(simObject);
1118  this->requestModelInfoForSimObject(simObject);
1119 
1120  this->removeFromAddPendingAndAddAgainAircraft(callsign); // no longer try to add
1121  const bool updated = this->updateAircraftRendered(callsign, true);
1122  if (updated)
1123  {
1124  static const QString debugMsg("CS: '%1' model: '%2' verified, request/object id: %3 %4");
1125  if (this->showDebugLogMessage())
1126  {
1127  this->debugLogMessage(Q_FUNC_INFO,
1128  debugMsg.arg(callsign.toQString(), remoteAircraft.getModelString())
1129  .arg(simObject.getRequestId())
1130  .arg(simObject.getObjectId()));
1131  }
1132 
1133  this->sendRemoteAircraftAtcDataToSimulator(simObject);
1134  emit this->aircraftRenderingChanged(simObject.getAircraft());
1135  }
1136  else
1137  {
1138  CLogMessage(this).warning(
1139  u"Verified aircraft '%1' model '%2', request/object id: %3 %4 was already marked rendered")
1140  << callsign.asString() << remoteAircraft.getModelString() << simObject.getRequestId()
1141  << simObject.getObjectId();
1142  }
1143 
1144  if (simObject.isConfirmedAdded() && simObject.getType() == CSimConnectObject::AircraftSimulatedObject)
1145  {
1146  CLogMessage(this).warning(u"Confirm added model '%1' '%2', but as '%3'")
1147  << remoteAircraft.getCallsignAsString() << remoteAircraft.getModelString()
1148  << simObject.getTypeAsString();
1149  this->triggerAutoTraceSendId(); // trace for some time (issues regarding this workaround?)
1150  simObject.decreaseAddingExceptions(); // if previously increased and now working, reset
1151  }
1152  }
1153  while (false);
1154 
1155  // log errors and emit signal
1156  if (!msg.isEmpty() && msg.isWarningOrAbove())
1157  {
1158  CLogMessage::preformatted(msg);
1159  emit this->physicallyAddingRemoteModelFailed(CSimulatedAircraft(), false, false, msg);
1160  }
1161 
1162  // trigger adding pending aircraft if there are any
1163  if (!m_addPendingAircraft.isEmpty()) { this->addPendingAircraftAfterAdded(); }
1164  }
1165 
1166  void CSimulatorFsxCommon::addingAircraftFailed(const CSimConnectObject &simObject)
1167  {
1168  if (CBuildConfig::isLocalDeveloperDebugBuild())
1169  {
1170  Q_ASSERT_X(simObject.isAircraft(), Q_FUNC_INFO, "Need aircraft");
1171  }
1172  if (!simObject.isAircraft()) { return; }
1173 
1174  // clean up
1176  this->removeFromAddPendingAndAddAgainAircraft(simObject.getCallsign());
1177 
1178  CLogMessage(this).warning(u"Model failed to be added: '%1' details: %2")
1179  << simObject.getAircraftModelString() << simObject.getAircraft().toQString(true);
1180  CStatusMessage verifyMsg;
1181  const bool verifiedAircraft = this->verifyFailedAircraftInfo(simObject, verifyMsg); // aircraft.cfg existing?
1182  if (!verifyMsg.isEmpty()) { CLogMessage::preformatted(verifyMsg); }
1183 
1184  CSimConnectObject simObjAddAgain(simObject);
1185  simObjAddAgain.increaseAddingExceptions();
1186  if (!simObject.hasCallsign())
1187  {
1188  SWIFT_VERIFY_X(false, Q_FUNC_INFO, "Missing callsign");
1189  return;
1190  }
1191 
1192  if (!verifiedAircraft || simObjAddAgain.getAddingExceptions() > ThresholdAddException)
1193  {
1194  const CStatusMessage msg =
1195  verifiedAircraft ?
1196  CLogMessage(this).warning(u"Model '%1' %2 failed %3 time(s) before and will be disabled")
1197  << simObjAddAgain.getAircraftModelString() << simObjAddAgain.toQString()
1198  << simObjAddAgain.getAddingExceptions() :
1199  CLogMessage(this).warning(u"Model '%1' %2 failed verification and will be disabled")
1200  << simObjAddAgain.getAircraftModelString() << simObjAddAgain.toQString();
1201  this->updateAircraftEnabled(simObjAddAgain.getCallsign(), false); // disable
1202  emit this->physicallyAddingRemoteModelFailed(simObjAddAgain.getAircraft(), true, true,
1203  msg); // verify failed
1204  }
1205  else
1206  {
1207  CLogMessage(this).info(u"Will try '%1' again, aircraft: %2")
1208  << simObject.getAircraftModelString() << simObject.getAircraft().toQString(true);
1209  QPointer<CSimulatorFsxCommon> myself(this);
1210  QTimer::singleShot(2000, this, [=] {
1211  if (!myself) { return; }
1212  if (this->isShuttingDownOrDisconnected()) { return; }
1213  m_addPendingAircraft.insert(simObjAddAgain, true); // add failed object
1214  });
1215  }
1216  }
1217 
1218  bool CSimulatorFsxCommon::verifyFailedAircraftInfo(const CSimConnectObject &simObject,
1219  CStatusMessage &details) const
1220  {
1221  CAircraftModel model = simObject.getAircraftModel();
1222 
1223  const CSpecializedSimulatorSettings settings = this->getSimulatorSettings();
1224  const bool fileExists = CFsCommonUtil::adjustFileDirectory(model, settings.getModelDirectoriesOrDefault());
1225  bool canBeUsed = true;
1226 
1227  CStatusMessageList messages;
1228  if (fileExists)
1229  {
1230  // we can access the aircraft.cfg file
1231  bool parsed = false;
1232  const CAircraftCfgEntriesList entries =
1233  CAircraftCfgParser::performParsingOfSingleFile(model.getFileName(), parsed, messages);
1234  if (parsed)
1235  {
1236  if (entries.containsTitle(model.getModelString()))
1237  {
1238  messages.push_back(CStatusMessage(this).info(u"Model '%1' exists in re-parsed file '%2'.")
1239  << model.getModelString() << model.getFileName());
1240  canBeUsed = true; // all OK
1241  }
1242  else
1243  {
1244  messages.push_back(
1245  CStatusMessage(this).warning(u"Model '%1' no longer in re-parsed file '%2'. Models are: %3.")
1246  << model.getModelString() << model.getFileName() << entries.getTitlesAsString(true));
1247  canBeUsed = false; // absolute no chance to use that one
1248  }
1249  }
1250  else
1251  {
1252  messages.push_back(CStatusMessage(this).warning(u"CS: '%1' Cannot parse file: '%2' (existing: %3)")
1253  << model.getCallsign().asString() << model.getFileName()
1255  }
1256  }
1257  else
1258  {
1259  // the file cannot be accessed right now, but the pilot client necessarily has access to them
1260  // so we just carry on
1261  messages = model.verifyModelData();
1262  }
1263 
1264  // as single message
1265  details = messages.toSingleMessage();
1266 
1267  // status
1268  return canBeUsed;
1269  }
1270 
1271  bool CSimulatorFsxCommon::logVerifyFailedAircraftInfo(const CSimConnectObject &simObject) const
1272  {
1273  CStatusMessage m;
1274  const bool r = verifyFailedAircraftInfo(simObject, m);
1275  if (!m.isEmpty()) { CLogMessage::preformatted(m); }
1276  return r;
1277  }
1278 
1279  void CSimulatorFsxCommon::verifyAddedTerrainProbe(const CSimulatedAircraft &remoteAircraftIn)
1280  {
1281  bool verified = false;
1282  CCallsign cs;
1283 
1284  // no simObject reference outside that block, because it will be deleted
1285  {
1286  CSimConnectObject &simObject = m_simConnectObjects[remoteAircraftIn.getCallsign()];
1287  simObject.setConfirmedAdded(true); // terrain probe
1288  simObject.resetTimestampToNow();
1289  cs = simObject.getCallsign();
1290  CLogMessage(this).info(u"Probe: '%1' '%2' confirmed, %3")
1291  << simObject.getCallsignAsString() << simObject.getAircraftModelString() << simObject.toQString();
1292 
1293  // fails for probe
1294  // SIMCONNECT_DATA_REQUEST_ID requestId = this->obtainRequestIdForSimObjTerrainProbe();
1295  // verified = this->releaseAIControl(simObject, requestId); // release probe
1296  verified = true;
1297  }
1298 
1299  if (!verified) // cppcheck-suppress knownConditionTrueFalse
1300  {
1301  CLogMessage(this).info(u"Disable probes: '%1' failed to relase control") << cs.asString();
1302  m_useFsxTerrainProbe = false;
1303  }
1304 
1305  // trigger new adding from pending if any
1306  if (!m_addPendingAircraft.isEmpty()) { this->addPendingAircraftAfterAdded(); }
1307  }
1308 
1309  void CSimulatorFsxCommon::timerBasedObjectAddOrRemove()
1310  {
1311  this->addPendingAircraft(AddByTimer);
1312  if (!this->isTestMode()) { this->physicallyRemoveAircraftNotInProvider(); }
1313  }
1314 
1315  void CSimulatorFsxCommon::addPendingAircraftAfterAdded()
1316  {
1317  this->addPendingAircraft(AddAfterAdded); // addPendingAircraft is already "non blocking"
1318  }
1319 
1320  void CSimulatorFsxCommon::addPendingAircraft(AircraftAddMode mode)
1321  {
1322  if (m_addPendingAircraft.isEmpty()) { return; }
1323  const CCallsignSet aircraftCallsignsInRange(this->getAircraftInRangeCallsigns());
1324  CSimulatedAircraftList toBeAddedAircraft; // aircraft still to be added
1325  CCallsignSet toBeRemovedCallsigns;
1326 
1327  for (const CSimConnectObject &pendingSimObj : std::as_const(m_addPendingAircraft))
1328  {
1329  SWIFT_VERIFY_X(pendingSimObj.hasCallsign(), Q_FUNC_INFO, "missing callsign");
1330  if (!pendingSimObj.hasCallsign()) { continue; }
1331  if (pendingSimObj.isTerrainProbe() || aircraftCallsignsInRange.contains(pendingSimObj.getCallsign()))
1332  {
1333  toBeAddedAircraft.push_back(pendingSimObj.getAircraft());
1334  }
1335  else { toBeRemovedCallsigns.push_back(pendingSimObj.getCallsign()); }
1336  }
1337 
1338  // no longer required to be added
1339  m_addPendingAircraft.removeCallsigns(toBeRemovedCallsigns);
1340  m_addAgainAircraftWhenRemoved.removeByCallsigns(toBeRemovedCallsigns);
1341 
1342  // add aircraft, but "non blocking"
1343  if (!toBeAddedAircraft.isEmpty())
1344  {
1345  const CSimConnectObject oldestSimObject = m_addPendingAircraft.getOldestObject();
1346  const CSimulatedAircraft nextPendingAircraft = oldestSimObject.getAircraft();
1347  if (nextPendingAircraft.hasModelString())
1348  {
1349  const QPointer<CSimulatorFsxCommon> myself(this);
1350  QTimer::singleShot(100, this, [=] {
1351  if (!myself) { return; }
1352  if (this->isShuttingDownDisconnectedOrNoAircraft(nextPendingAircraft.isTerrainProbe())) { return; }
1353  this->physicallyAddRemoteAircraftImpl(nextPendingAircraft, mode, oldestSimObject);
1354  });
1355  }
1356  else
1357  {
1358  CLogMessage(this).warning(u"Pending aircraft without model string will be removed");
1359  m_addPendingAircraft.removeByOtherSimObject(oldestSimObject);
1360  }
1361  }
1362  }
1363 
1364  CSimConnectObject CSimulatorFsxCommon::removeFromAddPendingAndAddAgainAircraft(const CCallsign &callsign)
1365  {
1366  CSimConnectObject simObjectOld;
1367  if (callsign.isEmpty()) { return simObjectOld; }
1368 
1370  if (m_addPendingAircraft.contains(callsign))
1371  {
1372  simObjectOld = m_addPendingAircraft[callsign];
1373  m_addPendingAircraft.remove(callsign);
1374  }
1375  return simObjectOld;
1376  }
1377 
1378  bool CSimulatorFsxCommon::simulatorReportedObjectRemoved(DWORD objectID)
1379  {
1380  if (this->isShuttingDownOrDisconnected()) { return false; }
1381  CSimConnectObject simObject = m_simConnectObjects.getSimObjectForObjectId(objectID);
1382  if (!simObject.hasValidRequestAndObjectId()) { return false; } // object id from somewhere else
1383 
1384  const CCallsign callsign(simObject.getCallsign());
1385  Q_ASSERT_X(!callsign.isEmpty(), Q_FUNC_INFO, "Missing callsign for removed object");
1386 
1387  if (simObject.isPendingRemoved())
1388  {
1389  // good case, object has been removed
1390  // we can remove the simulator object
1391  }
1392  else
1393  {
1394  // object was removed, but removal was not requested by us
1395  // this means we are out of the reality bubble or something else went wrong
1396  // Possible reasons:
1397  // 1) out of reality bubble, because we move to another airport or other reasons
1398  // 2) wrong position (in ground etc.)
1399  // 3) Simulator not running (ie in stopped mode)
1400  CStatusMessage msg;
1401  if (!simObject.getAircraftModelString().isEmpty() &&
1402  simObject.getAddingDirectlyRemoved() < ThresholdAddedAndDirectlyRemoved)
1403  {
1404  simObject.increaseAddingDirectlyRemoved();
1405  m_addPendingAircraft.insert(simObject, true); // insert removed objects and update ts
1407  simObject); // we have it in pending now, no need to keep it in this list
1408 
1411  msg = CLogMessage(this).warning(u"Aircraft removed, '%1' '%2' object id '%3' out of reality bubble or "
1412  u"other reason. Interpolator: '%4'")
1413  << callsign.toQString() << simObject.getAircraftModelString() << objectID
1414  << simObject.getInterpolatorInfo(setup.getInterpolatorMode());
1415  }
1416  else if (simObject.getAddingDirectlyRemoved() < ThresholdAddedAndDirectlyRemoved)
1417  {
1418  const CStatusMessage m =
1419  CLogMessage(this).warning(
1420  u"Aircraft removed again multiple times and will be disabled, '%1' '%2' object id '%3'")
1421  << callsign.toQString() << simObject.getAircraftModelString() << objectID;
1422  this->updateAircraftEnabled(simObject.getCallsign(), false);
1423  emit this->physicallyAddingRemoteModelFailed(simObject.getAircraft(), true, true,
1424  m); // directly removed again
1425  }
1426  else
1427  {
1428  msg = CLogMessage(this).warning(
1429  u"Removed '%1' from simulator, but was not initiated by us (swift): %1 '%2' object id %3")
1430  << callsign.toQString() << simObject.getAircraftModelString() << objectID;
1431  }
1432 
1433  // in all cases add verification details
1434  this->logVerifyFailedAircraftInfo(simObject);
1435 
1436  // relay messages
1437  if (!msg.isEmpty()) { emit this->driverMessages(msg); }
1438  }
1439 
1440  // in all cases we remove the object
1441  const int c = m_simConnectObjects.remove(callsign);
1442  const bool removedAny = (c > 0);
1443  const bool updated = this->updateAircraftRendered(simObject.getCallsign(), false);
1444  if (updated) { emit this->aircraftRenderingChanged(simObject.getAircraft()); }
1445 
1446  // models we have to add again after removing
1448  {
1449  const CSimulatedAircraft aircraftAddAgain = m_addAgainAircraftWhenRemoved.findFirstByCallsign(callsign);
1451  QPointer<CSimulatorFsxCommon> myself(this);
1452  QTimer::singleShot(2500, this, [=] {
1453  if (!myself) { return; }
1454  if (this->isShuttingDownOrDisconnected()) { return; }
1455  myself->physicallyAddRemoteAircraftImpl(aircraftAddAgain, AddedAfterRemoved);
1456  });
1457  }
1458  return removedAny;
1459  }
1460 
1461  bool CSimulatorFsxCommon::setSimConnectObjectId(DWORD requestId, DWORD objectId)
1462  {
1463  return m_simConnectObjects.setSimConnectObjectIdForRequestId(requestId, objectId);
1464  }
1465 
1466  bool CSimulatorFsxCommon::setCurrentLights(const CCallsign &callsign, const CAircraftLights &lights)
1467  {
1468  if (!m_simConnectObjects.contains(callsign)) { return false; }
1469  m_simConnectObjects[callsign].setCurrentLightsInSimulator(lights);
1470  return true;
1471  }
1472 
1473  bool CSimulatorFsxCommon::setLightsAsSent(const CCallsign &callsign, const CAircraftLights &lights)
1474  {
1475  if (!m_simConnectObjects.contains(callsign)) { return false; }
1476  m_simConnectObjects[callsign].setLightsAsSent(lights);
1477  return true;
1478  }
1479 
1481  {
1482  Q_UNUSED(event)
1483  if (this->isShuttingDown()) { return; }
1484  this->dispatch();
1485  }
1486 
1488 
1490  {
1491  // .driver sendid on|off
1492  if (parser.matchesPart(1, "sendid") && parser.hasPart(2))
1493  {
1494  const bool trace = parser.toBool(2);
1495  this->setTraceSendId(trace);
1496  CLogMessage(this, CLogCategories::cmdLine()).info(u"Tracing %1 driver sendIds is '%2'")
1497  << this->getSimulatorPluginInfo().getIdentifier() << boolToOnOff(trace);
1498  return true;
1499  }
1500 
1501  // .driver sboffsets on|off
1502  if (parser.matchesPart(1, "sboffsets") && parser.hasPart(2))
1503  {
1504  const bool on = parser.toBool(2);
1505  this->setUsingSbOffsetValues(on);
1506  CLogMessage(this, CLogCategories::cmdLine()).info(u"SB offsets is '%1'") << boolToOnOff(on);
1507  return true;
1508  }
1509 
1510  // .driver sblog on|off
1511  if (parser.matchesPart(1, "sblog") && parser.hasPart(2))
1512  {
1513  const bool on = parser.toBool(2);
1514  m_logSbOffsets = on;
1515  CLogMessage(this, CLogCategories::cmdLine()).info(u"SB log. offsets is '%1'") << boolToOnOff(on);
1516  return true;
1517  }
1518 
1519  return CSimulatorFsCommon::parseDetails(parser);
1520  }
1521 
1523  {
1524  if (CSimpleCommandParser::registered("swift::simplugin::fsxcommon::CSimulatorFsxCommon::CSimulatorFsxCommon"))
1525  {
1526  return;
1527  }
1528  CSimpleCommandParser::registerCommand({ ".drv", "alias: .driver .plugin" });
1529  CSimpleCommandParser::registerCommand({ ".drv sendid on|off", "Trace simConnect sendId on|off" });
1530  CSimpleCommandParser::registerCommand({ ".drv sboffsets on|off", "SB offsets via simConnect on|off" });
1531  CSimpleCommandParser::registerCommand({ ".drv sblog on|off", "SB offsets logging on|off" });
1532  }
1533 
1535  {
1536  const CCallsign cs = m_pendingProbeRequests.value(requestId);
1537  if (remove) { m_pendingProbeRequests.remove(requestId); }
1538  return cs;
1539  }
1540 
1541  const QString &CSimulatorFsxCommon::modeToString(CSimulatorFsxCommon::AircraftAddMode mode)
1542  {
1543  static const QString e("external call");
1544  static const QString pt("add pending by timer");
1545  static const QString oa("add pending after object added");
1546  static const QString ar("add again after removed");
1547  static const QString dontKnow("???");
1548 
1549  switch (mode)
1550  {
1551  case ExternalCall: return e;
1552  case AddByTimer: return pt;
1553  case AddAfterAdded: return oa;
1554  case AddedAfterRemoved: return ar;
1555  default: break;
1556  }
1557  return dontKnow;
1558  }
1559 
1560  void CSimulatorFsxCommon::dispatch()
1561  {
1562  // call CSimulatorFsxCommon::SimConnectProc or specialized P3D version
1563  Q_ASSERT_X(m_dispatchProc, Q_FUNC_INFO, "Missing DispatchProc");
1564 
1565  // statistics
1566  m_dispatchReceiveIdLast = SIMCONNECT_RECV_ID_NULL;
1567  m_dispatchRequestIdLast = CSimConnectDefinitions::RequestEndMarker;
1568  const qint64 start = QDateTime::currentMSecsSinceEpoch();
1569 
1570  // process
1571  const HRESULT hr = SimConnect_CallDispatch(m_hSimConnect, m_dispatchProc, this);
1572 
1573  // statistics
1574  const qint64 end = QDateTime::currentMSecsSinceEpoch();
1575  m_dispatchTimeMs = end - start;
1576  if (m_dispatchMaxTimeMs < m_dispatchTimeMs)
1577  {
1578  m_dispatchMaxTimeMs = m_dispatchTimeMs;
1579  m_dispatchReceiveIdMaxTime = m_dispatchReceiveIdLast;
1580  m_dispatchRequestIdMaxTime = m_dispatchRequestIdLast;
1581  }
1582 
1583  // error handling
1584  if (isFailure(hr))
1585  {
1586  // on FSX we normally receive this one here when simulator goes down, and NOT onSimExit
1587  // in that case sim status is Connected, but not PAUSED or SIMULATING
1588  const SimulatorStatus simStatus = this->getSimulatorStatus();
1589  const bool disconnectedOrNotSimulating =
1590  simStatus.testFlag(Disconnected) || !simStatus.testFlag(Simulating);
1591 
1592  m_dispatchErrors++;
1593  this->triggerAutoTraceSendId();
1594  if (m_dispatchErrors == 2)
1595  {
1596  // 2nd time, an error / avoid multiple messages
1597  // idea: if it happens once ignore
1598  const QString msg =
1599  QStringLiteral(u"%1: Dispatch error, sim.status: %2")
1600  .arg(this->getSimulatorPluginInfo().getIdentifier(), ISimulator::statusToString(simStatus));
1601  CLogMessage(this).log(
1602  disconnectedOrNotSimulating ? CStatusMessage::SeverityWarning : CStatusMessage::SeverityError, msg);
1603  }
1604  else if (m_dispatchErrors > 5)
1605  {
1606  // this normally happens during a FSX crash or shutdown with simconnect
1607  const QString msg =
1608  QStringLiteral(u"%1: Multiple dispatch errors, disconnecting. Sim.status: %2")
1609  .arg(this->getSimulatorPluginInfo().getIdentifier(), ISimulator::statusToString(simStatus));
1610  CLogMessage(this).log(
1611  disconnectedOrNotSimulating ? CStatusMessage::SeverityWarning : CStatusMessage::SeverityError, msg);
1612  this->disconnectFrom();
1613  }
1614  return;
1615  }
1616  m_dispatchErrors = 0;
1617  }
1618 
1619  bool CSimulatorFsxCommon::physicallyAddRemoteAircraftImpl(const CSimulatedAircraft &newRemoteAircraft,
1620  CSimulatorFsxCommon::AircraftAddMode addMode,
1621  const CSimConnectObject &correspondingSimObject)
1622  {
1623  const CCallsign callsign(newRemoteAircraft.getCallsign());
1624  const bool probe = newRemoteAircraft.isTerrainProbe();
1625 
1626  // entry checks
1627  Q_ASSERT_X(CThreadUtils::isInThisThread(this), Q_FUNC_INFO, "thread");
1628  Q_ASSERT_X(!callsign.isEmpty(), Q_FUNC_INFO, "empty callsign");
1629  Q_ASSERT_X(newRemoteAircraft.hasModelString(), Q_FUNC_INFO, "missing model string");
1630 
1631  // reset timer
1632  m_simObjectTimer.start(AddPendingAircraftIntervalMs); // restart
1633 
1634  // remove outdated objects
1635  const CSimConnectObjects outdatedAdded =
1636  m_simConnectObjects.removeOutdatedPendingAdded(CSimConnectObject::AllTypes);
1637  if (!outdatedAdded.isEmpty())
1638  {
1639  const CCallsignSet callsigns = outdatedAdded.getAllCallsigns(false);
1640  CLogMessage(this).warning(u"Removed %1 outdated object(s) pending for added: %2")
1641  << outdatedAdded.size() << callsigns.getCallsignsAsString(true);
1642  this->updateMultipleAircraftEnabled(callsigns, false);
1643 
1644  static const QString msgText("%1 outdated adding, %2");
1645  for (const CSimConnectObject &simObjOutdated : outdatedAdded)
1646  {
1647  const CStatusMessage msg = CStatusMessage(this).warning(
1648  msgText.arg(simObjOutdated.getCallsign().asString(), simObjOutdated.toQString()));
1649  emit this->physicallyAddingRemoteModelFailed(simObjOutdated.getAircraft(), true, true, msg); // outdated
1650  }
1651 
1652  // if this aircraft is also outdated, ignore
1653  if (callsigns.contains(newRemoteAircraft.getCallsign())) { return false; }
1654  }
1655 
1656  const bool hasPendingAdded = m_simConnectObjects.containsPendingAdded();
1657  bool canAdd = this->isSimulating() && !hasPendingAdded;
1658 
1659  Q_ASSERT_X(!hasPendingAdded || m_simConnectObjects.countPendingAdded() < 2, Q_FUNC_INFO,
1660  "There must be only 0..1 pending objects");
1661  if (this->showDebugLogMessage())
1662  {
1663  this->debugLogMessage(Q_FUNC_INFO, QStringLiteral("CS: '%1' mode: '%2' model: '%3'")
1664  .arg(newRemoteAircraft.getCallsignAsString(), modeToString(addMode),
1665  newRemoteAircraft.getModelString()));
1666  this->debugLogMessage(
1667  Q_FUNC_INFO, QStringLiteral("CS: '%1' pending callsigns: '%2', pending objects: '%3'")
1668  .arg(newRemoteAircraft.getCallsignAsString(),
1669  m_addPendingAircraft.getAllCallsignStrings(true).join(", "),
1671  }
1672 
1673  // do we need to remove/add again because something has changed?
1674  // this handles changed model strings or an update of the model
1675  if (m_simConnectObjects.contains(callsign))
1676  {
1677  const CSimConnectObject simObject = m_simConnectObjects[callsign];
1678  const QString newModelString(newRemoteAircraft.getModelString());
1679  const QString simObjModelString(simObject.getAircraftModelString());
1680  const bool sameModel =
1681  (simObjModelString ==
1682  newModelString); // compare on string only (other attributes might change such as mode)
1683 
1684  // same model, nothing will change, otherwise add again when removed
1685  if (sameModel)
1686  {
1687  CLogMessage(this).info(u"CS: '%1' re-added same model '%2'")
1688  << newRemoteAircraft.getCallsignAsString() << newModelString;
1689 
1690  // we restore rendered flag in case we are sure we are rendered
1691  // this is used with rematching
1692  const bool rendered = simObject.isConfirmedAdded() && simObject.isPending();
1693  if (rendered) { this->updateAircraftRendered(callsign, rendered); }
1694  return true;
1695  }
1696 
1697  this->physicallyRemoveRemoteAircraft(newRemoteAircraft.getCallsign());
1699  if (this->showDebugLogMessage())
1700  {
1701  this->debugLogMessage(Q_FUNC_INFO,
1702  QStringLiteral("CS: '%1' re-added changed model '%2', will be added again")
1703  .arg(newRemoteAircraft.getCallsignAsString(), newModelString));
1704  }
1705  return false;
1706  }
1707 
1708  // situation check
1709  CAircraftSituation situation(newRemoteAircraft.getSituation());
1710  if (canAdd && situation.isPositionOrAltitudeNull())
1711  {
1712  // invalid position because position or altitude is null
1713  const CAircraftSituationList situations(this->remoteAircraftSituations(callsign));
1714  if (situations.isEmpty())
1715  {
1716  CLogMessage(this).warning(u"No valid situations for '%1', will be added as pending")
1717  << callsign.asString();
1718  }
1719  else
1720  {
1721  CLogMessage(this).warning(u"Invalid aircraft situation for new aircraft '%1', use closest situation")
1722  << callsign.asString();
1723  situation = situations.findClosestTimeDistanceAdjusted(QDateTime::currentMSecsSinceEpoch());
1724  Q_ASSERT_X(!situation.isPositionOrAltitudeNull(), Q_FUNC_INFO, "Invalid situation for new aircraft");
1725  }
1726 
1727  // still invalid?
1728  canAdd = situation.isPositionOrAltitudeNull();
1729  if (CBuildConfig::isLocalDeveloperDebugBuild())
1730  {
1731  SWIFT_VERIFY_X(canAdd, Q_FUNC_INFO, "Expect valid situation");
1732  CLogMessage(this).warning(u"Invalid situation for '%1'") << callsign;
1733  }
1734  }
1735 
1736  // check if we can add, do not add if simulator is stopped or other objects pending
1737  if (!canAdd)
1738  {
1739  CSimConnectObject &addPendingObj = m_addPendingAircraft[newRemoteAircraft.getCallsign()];
1740  addPendingObj.setAircraft(newRemoteAircraft);
1741  addPendingObj.resetTimestampToNow();
1742  return false;
1743  }
1744 
1745  // remove from pending and keep for later to remember fail counters
1746  const CSimConnectObject removedPendingObj = this->removeFromAddPendingAndAddAgainAircraft(callsign);
1747 
1748  // create AI after crosschecking it
1749  if (!probe && !this->isAircraftInRangeOrTestMode(callsign))
1750  {
1751  CLogMessage(this).info(u"Skipping adding of '%1' since it is no longer in range") << callsign.asString();
1752  return false;
1753  }
1754 
1755  // setup
1757  this->getInterpolationSetupConsolidated(callsign, true);
1758  const bool sendGround = setup.isSendingGndFlagToSimulator();
1759 
1760  // FSX/P3D adding
1761  bool adding = false; // will be added flag
1762  const SIMCONNECT_DATA_REQUEST_ID requestId =
1764 
1765  // Initial situation, if possible from interpolation
1766  CAircraftSituation initialSituation = newRemoteAircraft.getSituation(); // default
1767  {
1768  // Dummy CSimConnectObject just for interpolation
1769  const CSimConnectObject dummyObject = CSimConnectObject(
1770  newRemoteAircraft, 0, this, this, this->getRemoteAircraftProvider(), &m_interpolationLogger);
1771  const CInterpolationResult result =
1772  dummyObject.getInterpolation(QDateTime::currentMSecsSinceEpoch(), setup, 0);
1773  if (result.getInterpolationStatus().isInterpolated())
1774  {
1775  initialSituation = result.getInterpolatedSituation();
1776  }
1777  }
1778 
1779  // under flow can cause a model not to be added
1780  // FSX: underflow and NO(!) gnd flag can cause adding/remove issue
1781  // P3D: underflow did not cause such issue
1782  CStatusMessage underflowStatus;
1783  const SIMCONNECT_DATA_INITPOSITION initialPosition =
1784  CSimulatorFsxCommon::aircraftSituationToFsxPosition(initialSituation, sendGround, true, &underflowStatus);
1785 
1786  const QString modelString(newRemoteAircraft.getModelString());
1787  if (this->showDebugLogMessage())
1788  {
1789  this->debugLogMessage(Q_FUNC_INFO, QStringLiteral("CS: '%1' model: '%2' request: %3, init pos: %4")
1790  .arg(callsign.toQString(), modelString)
1791  .arg(requestId)
1792  .arg(fsxPositionToString(initialPosition)));
1793  }
1794 
1795  const QByteArray modelStringBa = toFsxChar(modelString);
1796  const QByteArray csBa = toFsxChar(callsign.toQString().left(12));
1797  CSimConnectObject::SimObjectType type = CSimConnectObject::AircraftNonAtc;
1798  HRESULT hr = S_OK;
1799 
1800  if (probe)
1801  {
1802  hr = SimConnect_AICreateSimulatedObject(m_hSimConnect, modelStringBa.constData(), initialPosition,
1803  requestId);
1804  type = CSimConnectObject::TerrainProbe;
1805  }
1806  else
1807  {
1808  if (this->isAddingAsSimulatedObjectEnabled() && correspondingSimObject.hasCallsign() &&
1809  correspondingSimObject.getAddingExceptions() > 0 &&
1810  correspondingSimObject.getType() == CSimConnectObject::AircraftNonAtc)
1811  {
1812  CStatusMessage(this).warning(
1813  u"Model '%1' for '%2' failed %1 time(s) before, using AICreateSimulatedObject now")
1814  << newRemoteAircraft.getModelString() << callsign.toQString();
1815  hr = SimConnect_AICreateSimulatedObject(m_hSimConnect, modelStringBa.constData(), initialPosition,
1816  requestId);
1817  type = CSimConnectObject::AircraftSimulatedObject;
1818  }
1819  else
1820  {
1821  hr = SimConnect_AICreateNonATCAircraft(m_hSimConnect, modelStringBa.constData(), csBa.constData(),
1822  initialPosition, requestId);
1823  type = CSimConnectObject::AircraftNonAtc;
1824  }
1825  }
1826 
1827  if (!underflowStatus.isEmpty())
1828  {
1829  CStatusMessage(this).warning(u"Underflow detecion for '%1', details '%2'")
1830  << callsign.asString() << underflowStatus.getMessage();
1831  }
1832 
1833  if (isFailure(hr))
1834  {
1835  const CStatusMessage msg = CStatusMessage(this).error(u"SimConnect, can not create AI traffic: '%1' '%2'")
1836  << callsign.toQString() << modelString;
1837  CLogMessage::preformatted(msg);
1838  emit this->physicallyAddingRemoteModelFailed(newRemoteAircraft, true, true, msg); // SimConnect error
1839  }
1840  else
1841  {
1842  // we will request a new aircraft by request ID, later we will receive its object id
1843  // so far this object id is 0 (DWORD)
1844  const CSimConnectObject simObject =
1845  this->insertNewSimConnectObject(newRemoteAircraft, requestId, type, removedPendingObj);
1846  this->traceSendId(simObject, Q_FUNC_INFO,
1847  QStringLiteral("mode: %1").arg(CSimulatorFsxCommon::modeToString(addMode)), true);
1848  adding = true;
1849  }
1850  return adding;
1851  }
1852 
1853  bool CSimulatorFsxCommon::physicallyAddAITerrainProbe(const ICoordinateGeodetic &coordinate, int number)
1854  {
1855  if (coordinate.isNull()) { return false; }
1856  if (!this->isUsingFsxTerrainProbe()) { return false; }
1857  Q_ASSERT_X(CThreadUtils::isInThisThread(this), Q_FUNC_INFO, "thread");
1858 
1859  // static const QString modelString("OrcaWhale");
1860  // static const QString modelString("Water Drop"); // not working on P3Dx86/FSX, no requests on that id possible
1861  // static const QString modelString("A321ACA");
1862  // static const QString modelString("AI_Tracker_Object_0");
1863  // static const QString modelString("Piper Cub"); // P3Dv86 works as nonATC/SimulatedObject
1864  // static const QString modelString("Discovery Spaceshuttle"); // P3Dx86 works as nonATC/SimulatedObject
1865  static const QString modelString("swiftTerrainProbe0");
1866  static const QString pseudoCallsign("PROBE%1"); // max 12 chars
1867  static const CCountry ctry("SW", "SWIFT");
1868  static const CAirlineIcaoCode swiftAirline("SWI", "swift probe", ctry, "SWIFT", false, false);
1869  static const CLivery swiftLivery(CLivery::getStandardCode(swiftAirline), swiftAirline, "swift probe");
1870 
1871  const CCallsign cs(pseudoCallsign.arg(number));
1872  const CAircraftModel model(modelString, CAircraftModel::TypeTerrainProbe, QStringLiteral("swift terrain probe"),
1873  CAircraftIcaoCode::unassignedIcao(), swiftLivery);
1874  CAircraftSituation situation(cs, coordinate);
1875  situation.setAltitude(terrainProbeAltitude());
1876  situation.setZeroPBH();
1877  const CSimulatedAircraft pseudoAircraft(cs, model, CUser("123456", "swift", cs), situation);
1878  return this->physicallyAddRemoteAircraftImpl(pseudoAircraft, ExternalCall);
1879  }
1880 
1881  int CSimulatorFsxCommon::physicallyInitAITerrainProbes(const ICoordinateGeodetic &coordinate, int number)
1882  {
1883  if (number < 1) { return 0; }
1884  if (m_initFsxTerrainProbes) { return m_addedProbes; }
1885  m_initFsxTerrainProbes = true; // no multiple inits
1886  this->triggerAutoTraceSendId();
1887 
1888  int c = 0;
1889  for (int n = 0; n < number; ++n)
1890  {
1891  if (this->physicallyAddAITerrainProbe(coordinate, n)) { c++; }
1892  }
1893 
1894  CLogMessage(this).info(u"Adding %1 FSX terrain probes") << number;
1895  m_addedProbes = c;
1896  return c;
1897  }
1898 
1900  {
1901  // only remove from sim
1902  Q_ASSERT_X(CThreadUtils::isInThisThread(this), Q_FUNC_INFO, "wrong thread");
1903  if (callsign.isEmpty()) { return false; } // can happen if an object is not an aircraft
1904 
1905  // clean up anyway
1906  this->removeFromAddPendingAndAddAgainAircraft(callsign);
1907 
1908  // really remove from simulator
1909  if (!m_simConnectObjects.contains(callsign)) { return false; } // already fully removed or not yet added
1910  CSimConnectObject &simObject = m_simConnectObjects[callsign];
1911  if (simObject.isPendingRemoved()) { return true; }
1912  if (simObject.isTerrainProbe()) { return false; }
1913 
1914  // check for pending objects
1915  m_addPendingAircraft.remove(callsign); // just in case still in list of pending aircraft
1916  const bool pendingAdded = simObject.isPendingAdded(); // already added in simulator, but not yet confirmed
1917  const bool stillWaitingForLights = !simObject.hasCurrentLightsInSimulator();
1918  if (!simObject.isRemovedWhileAdding() && (pendingAdded || stillWaitingForLights))
1919  {
1920  // problem: we try to delete an aircraft just requested to be added
1921  // best solution so far, call remove again with a delay
1922  CLogMessage(this).warning(u"'%1' requested to be removed, but pending added (%2) / or pending lights(%3). "
1923  u"Object will be removed again: %4")
1924  << callsign.asString() << boolToYesNo(pendingAdded) << boolToYesNo(stillWaitingForLights)
1925  << simObject.toQString();
1926  simObject.setRemovedWhileAdding(true); // next time kill
1927  QPointer<CSimulatorFsxCommon> myself(this);
1928  QTimer::singleShot(2000, this, [=] {
1929  if (!myself) { return; }
1930  CLogMessage(this).info(u"Next trial to remove '%1'") << callsign.asString();
1931  myself->physicallyRemoveRemoteAircraft(callsign);
1932  });
1933  return false; // not yet deleted
1934  }
1935 
1936  // no more data from simulator
1937  this->stopRequestingDataForSimObject(simObject);
1938 
1939  // mark as removed
1940  simObject.setPendingRemoved(true);
1941  if (this->showDebugLogMessage())
1942  {
1943  this->debugLogMessage(Q_FUNC_INFO, QStringLiteral("CS: '%1' request/object id: %2/%3")
1944  .arg(callsign.toQString())
1945  .arg(simObject.getRequestId())
1946  .arg(simObject.getObjectId()));
1947  }
1948 
1949  // call in SIM
1950  const SIMCONNECT_DATA_REQUEST_ID requestId = simObject.getRequestId(CSimConnectDefinitions::SimObjectRemove);
1951  this->removeCamera(simObject);
1952  this->removeObserver(simObject);
1953  const HRESULT result = SimConnect_AIRemoveObject(
1954  m_hSimConnect, static_cast<SIMCONNECT_OBJECT_ID>(simObject.getObjectId()), requestId);
1955  if (isOk(result))
1956  {
1957  if (this->isTracingSendId()) { this->traceSendId(simObject, Q_FUNC_INFO); }
1958  }
1959  else { CLogMessage(this).warning(u"Removing aircraft '%1' from simulator failed") << callsign.asString(); }
1960 
1961  // mark in provider
1962  const bool updated = this->updateAircraftRendered(callsign, false);
1963  if (updated)
1964  {
1965  CSimulatedAircraft aircraft(simObject.getAircraft());
1966  aircraft.setRendered(false);
1967  emit this->aircraftRenderingChanged(aircraft);
1968  }
1969 
1970  // cleanup function, actually this should not be needed
1971  this->physicallyRemoveAircraftNotInProviderAsync();
1972 
1973  // bye
1974  return CSimulatorPluginCommon::physicallyRemoveRemoteAircraft(callsign);
1975  }
1976 
1978  {
1979  // make sure they are not added again
1980  // cleaning here is somewhat redundant, but double checks
1981  m_addPendingAircraft.clear();
1983 
1984  // remove one by one
1985  int r = 0;
1986  const CCallsignSet callsigns = m_simConnectObjects.getAllCallsigns();
1987  for (const CCallsign &cs : callsigns)
1988  {
1989  if (this->physicallyRemoveRemoteAircraft(cs)) { r++; }
1990  }
1991 
1992  CSimulatorFsCommon::physicallyRemoveAllRemoteAircraft();
1993  return r;
1994  }
1995 
1996  HRESULT CSimulatorFsxCommon::initEvents()
1997  {
1998  HRESULT hr = s_ok();
1999  // System events, see http://msdn.microsoft.com/en-us/library/cc526983.aspx#SimConnect_SubscribeToSystemEvent
2000  hr += SimConnect_SubscribeToSystemEvent(m_hSimConnect, SystemEventSimStatus, "Sim");
2001  hr += SimConnect_SubscribeToSystemEvent(m_hSimConnect, SystemEventObjectAdded, "ObjectAdded");
2002  hr += SimConnect_SubscribeToSystemEvent(m_hSimConnect, SystemEventObjectRemoved, "ObjectRemoved");
2003  hr += SimConnect_SubscribeToSystemEvent(m_hSimConnect, SystemEventFrame, "Frame");
2004  hr += SimConnect_SubscribeToSystemEvent(m_hSimConnect, SystemEventPause, "Pause");
2005  hr += SimConnect_SubscribeToSystemEvent(m_hSimConnect, SystemEventFlightLoaded, "FlightLoaded");
2006  if (isFailure(hr))
2007  {
2008  CLogMessage(this).error(u"FSX plugin error: %1") << "SimConnect_SubscribeToSystemEvent failed";
2009  return hr;
2010  }
2011 
2012  // Mapped events, see event ids here:
2013  // http://msdn.microsoft.com/en-us/library/cc526980.aspx
2014  // http://www.prepar3d.com/SDKv2/LearningCenter/utilities/variables/event_ids.html
2015  hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventPauseToggle, "PAUSE_TOGGLE");
2016  hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, SystemEventSlewToggle, "SLEW_TOGGLE");
2017  hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventFreezeLatLng,
2018  "FREEZE_LATITUDE_LONGITUDE_SET"); // FSX old standard
2019  hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventFreezeAlt,
2020  "FREEZE_ALTITUDE_SET"); // FSX old standard
2021  hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventFreezeAtt,
2022  "FREEZE_ATTITUDE_SET"); // FSX old standard
2023  hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventSetCom1Active, "COM_RADIO_SET");
2024  hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventSetCom1Standby, "COM_STBY_RADIO_SET");
2025  hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventSetCom2Active, "COM2_RADIO_SET");
2026  hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventSetCom2Standby, "COM2_STBY_RADIO_SET");
2027  hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventSetTransponderCode, "XPNDR_SET");
2028 
2029  hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventSetTimeZuluYear, "ZULU_YEAR_SET");
2030  hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventSetTimeZuluDay, "ZULU_DAY_SET");
2031  hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventSetTimeZuluHours, "ZULU_HOURS_SET");
2032  hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventSetTimeZuluMinutes, "ZULU_MINUTES_SET");
2033 
2034  hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventLandingLightsOff, "LANDING_LIGHTS_OFF");
2035  hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventLandinglightsOn, "LANDING_LIGHTS_ON");
2036  hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventLandingLightsSet, "LANDING_LIGHTS_SET");
2037  hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventLandingLightsToggle, "LANDING_LIGHTS_TOGGLE");
2038  hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventPanelLightsOff, "PANEL_LIGHTS_OFF");
2039  hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventPanelLightsOn, "PANEL_LIGHTS_ON");
2040  hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventPanelLightsSet, "PANEL_LIGHTS_SET");
2041  hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventStrobesOff, "STROBES_OFF");
2042  hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventStrobesOn, "STROBES_ON");
2043  hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventStrobesSet, "STROBES_SET");
2044  hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventStrobesToggle, "STROBES_TOGGLE");
2045  hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventToggleBeaconLights, "TOGGLE_BEACON_LIGHTS");
2046  hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventToggleCabinLights, "TOGGLE_CABIN_LIGHTS");
2047  hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventToggleLogoLights, "TOGGLE_LOGO_LIGHTS");
2048  hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventToggleNavLights, "TOGGLE_NAV_LIGHTS");
2049  hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventToggleRecognitionLights,
2050  "TOGGLE_RECOGNITION_LIGHTS");
2051  hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventToggleTaxiLights, "TOGGLE_TAXI_LIGHTS");
2052  hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventToggleWingLights, "TOGGLE_WING_LIGHTS");
2053 
2054  hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventFlapsSet, "FLAPS_SET");
2055 
2056  if (isFailure(hr))
2057  {
2058  CLogMessage(this).error(u"FSX plugin error: %1") << "SimConnect_MapClientEventToSimEvent failed";
2059  return hr;
2060  }
2061 
2062  // facility
2063  SIMCONNECT_DATA_REQUEST_ID requestId =
2064  static_cast<SIMCONNECT_DATA_REQUEST_ID>(CSimConnectDefinitions::RequestFacility);
2065  hr += SimConnect_SubscribeToFacilities(m_hSimConnect, SIMCONNECT_FACILITY_LIST_TYPE_AIRPORT, requestId);
2066  if (isFailure(hr))
2067  {
2068  CLogMessage(this).error(u"FSX plugin error: %1") << "SimConnect_SubscribeToFacilities failed";
2069  return hr;
2070  }
2071  return hr;
2072  }
2073 
2074  HRESULT CSimulatorFsxCommon::initDataDefinitionsWhenConnected()
2075  {
2078  }
2079 
2080  HRESULT CSimulatorFsxCommon::initWhenConnected()
2081  {
2082  // called when connected
2083 
2084  HRESULT hr = this->initEvents();
2085  if (isFailure(hr))
2086  {
2087  CLogMessage(this).error(u"FSX plugin: initEvents failed");
2088  return hr;
2089  }
2090 
2091  // init data definitions and SB data area
2092  hr += this->initDataDefinitionsWhenConnected();
2093  if (isFailure(hr))
2094  {
2095  CLogMessage(this).error(u"FSX plugin: initDataDefinitionsWhenConnected failed");
2096  return hr;
2097  }
2098 
2099  return hr;
2100  }
2101 
2102  void CSimulatorFsxCommon::updateRemoteAircraft()
2103  {
2104  static_assert(sizeof(DataDefinitionRemoteAircraftPartsWithoutLights) == sizeof(double) * 14,
2105  "DataDefinitionRemoteAircraftPartsWithoutLights has an incorrect size.");
2106  Q_ASSERT_X(CThreadUtils::isInThisThread(this), Q_FUNC_INFO, "thread");
2107 
2108  // nothing to do, reset request id and exit
2109  const int remoteAircraftNo = this->getAircraftInRangeCount();
2110  if (remoteAircraftNo < 1)
2111  {
2113  return;
2114  }
2115 
2116  // values used for position and parts
2117  const qint64 currentTimestamp = QDateTime::currentMSecsSinceEpoch();
2118  if (this->isUpdateAircraftLimitedWithStats(currentTimestamp))
2119  {
2120  this->finishUpdateRemoteAircraftAndSetStatistics(currentTimestamp, true);
2121  return;
2122  }
2124 
2125  // interpolation for all remote aircraft
2127 
2128  uint32_t simObjectNumber = 0;
2129  const bool traceSendId = this->isTracingSendId();
2130  const bool updateAllAircraft = this->isUpdateAllRemoteAircraft(currentTimestamp);
2131  for (const CSimConnectObject &simObject : simObjects)
2132  {
2133  // happening if aircraft is not yet added to simulator or to be deleted
2134  if (!simObject.isReadyToSend()) { continue; }
2135  if (!simObject.hasCurrentLightsInSimulator()) { continue; } // wait until we have light state
2136 
2137  const CCallsign callsign(simObject.getCallsign());
2138  const bool hasCs = !callsign.isEmpty();
2139  const bool hasValidIds = simObject.hasValidRequestAndObjectId();
2140  SWIFT_VERIFY_X(hasCs, Q_FUNC_INFO, "missing callsign");
2141  SWIFT_AUDIT_X(hasValidIds, Q_FUNC_INFO, "Missing ids");
2142  if (!hasCs || !hasValidIds) { continue; } // not supposed to happen
2143  const DWORD objectId = simObject.getObjectId();
2144 
2145  // setup
2147  this->getInterpolationSetupConsolidated(callsign, updateAllAircraft);
2148  const bool sendGround = setup.isSendingGndFlagToSimulator();
2149 
2150  // Interpolated situation
2151  // simObjectNumber is passed to equally distributed steps like guessing parts
2152  const bool slowUpdate = (((m_statsUpdateAircraftRuns + simObjectNumber) % 40) == 0);
2153  const CInterpolationResult result = simObject.getInterpolation(currentTimestamp, setup, simObjectNumber++);
2154  const bool forceUpdate = slowUpdate || updateAllAircraft || setup.isForcingFullInterpolation();
2156  {
2157  // update situation
2158  if (forceUpdate || !this->isEqualLastSent(result.getInterpolatedSituation()))
2159  {
2160  // adjust altitude to compensate for FS2020 temperature effect
2161  CAircraftSituation situation = result;
2162  const CLength relativeAltitude =
2164  const double altitudeDeltaWeight =
2165  2 - qBound(3000.0, relativeAltitude.abs().value(CLengthUnit::ft()), 6000.0) / 3000;
2166  situation.setAltitude({ situation.getAltitude() + m_altitudeDelta * altitudeDeltaWeight,
2167  situation.getAltitude().getReferenceDatum() });
2168 
2169  SIMCONNECT_DATA_INITPOSITION position = this->aircraftSituationToFsxPosition(situation, sendGround);
2170  const HRESULT hr = this->logAndTraceSendId(
2171  SimConnect_SetDataOnSimObject(m_hSimConnect,
2173  static_cast<SIMCONNECT_OBJECT_ID>(objectId), 0, 0,
2174  sizeof(SIMCONNECT_DATA_INITPOSITION), &position),
2175  traceSendId, simObject, "Failed to set position", Q_FUNC_INFO, "SimConnect_SetDataOnSimObject");
2176  if (isOk(hr))
2177  {
2178  this->rememberLastSent(result); // remember situation
2179  }
2180  }
2181  }
2182  else
2183  {
2184  // already logged in interpolator
2185  continue;
2186  }
2187 
2188  // Interpolated parts
2189  const bool updatedParts = this->updateRemoteAircraftParts(simObject, result, forceUpdate);
2190  Q_UNUSED(updatedParts)
2191 
2192  } // all callsigns
2193 
2194  // stats
2195  this->finishUpdateRemoteAircraftAndSetStatistics(currentTimestamp);
2196  }
2197 
2198  bool CSimulatorFsxCommon::updateRemoteAircraftParts(const CSimConnectObject &simObject,
2199  const CInterpolationResult &result, bool forcedUpdate)
2200  {
2201  if (!simObject.hasValidRequestAndObjectId()) { return false; }
2202  if (!simObject.isConfirmedAdded()) { return false; }
2203 
2204  const CAircraftParts parts = result;
2205  if (parts.isNull()) { return false; }
2206  if (parts.getPartsDetails() != CAircraftParts::GuessedParts && !result.getPartsStatus().isSupportingParts())
2207  {
2208  return false;
2209  }
2210 
2211  const CCallsign cs = simObject.getCallsign();
2212  if (!forcedUpdate && (result.getPartsStatus().isReusedParts() || this->isEqualLastSent(parts, cs)))
2213  {
2214  return true;
2215  }
2216 
2217  const bool ok = this->sendRemoteAircraftPartsToSimulator(simObject, parts);
2218  if (ok) { this->rememberLastSent(parts, cs); }
2219  return ok;
2220  }
2221 
2222  bool CSimulatorFsxCommon::sendRemoteAircraftPartsToSimulator(const CSimConnectObject &simObject,
2223  const CAircraftParts &parts)
2224  {
2225  Q_ASSERT(m_hSimConnect);
2226  if (!simObject.isReadyToSend()) { return false; }
2227 
2228  const DWORD objectId = simObject.getObjectId();
2229  const bool traceId = this->isTracingSendId();
2230 
2231  DataDefinitionRemoteAircraftPartsWithoutLights ddRemoteAircraftPartsWithoutLights(parts);
2232  const CAircraftLights lights = parts.getAdjustedLights();
2233 
2234  // in case we sent, we sent everything
2235  const bool simObjectAircraftType = simObject.isAircraftSimulatedObject(); // no real aircraft type
2236  const HRESULT hr1 =
2237  simObjectAircraftType ?
2238  S_OK :
2239  this->logAndTraceSendId(
2240  SimConnect_SetDataOnSimObject(
2241  m_hSimConnect, CSimConnectDefinitions::DataRemoteAircraftPartsWithoutLights,
2242  static_cast<SIMCONNECT_OBJECT_ID>(objectId), SIMCONNECT_DATA_SET_FLAG_DEFAULT, 0,
2243  sizeof(DataDefinitionRemoteAircraftPartsWithoutLights), &ddRemoteAircraftPartsWithoutLights),
2244  traceId, simObject, "Failed so set parts", Q_FUNC_INFO,
2245  "SimConnect_SetDataOnSimObject::ddRemoteAircraftPartsWithoutLights");
2246 
2247  // Sim variable version, not working, setting the value, but flaps retracting to 0 again
2248  // Sets flap handle to closest increment (0 to 16383)
2249  const DWORD flapsDw = static_cast<DWORD>(qMin(16383, qRound((parts.getFlapsPercent() / 100.0) * 16383)));
2250  const HRESULT hr2 = this->logAndTraceSendId(
2251  SimConnect_TransmitClientEvent(m_hSimConnect, objectId, EventFlapsSet, flapsDw,
2252  SIMCONNECT_GROUP_PRIORITY_HIGHEST,
2253  SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY),
2254  traceId, simObject, "Failed so set flaps", Q_FUNC_INFO, "SimConnect_TransmitClientEvent::EventFlapsSet");
2255 
2256  // lights we can set directly
2257  const HRESULT hr3 = this->logAndTraceSendId(
2258  SimConnect_TransmitClientEvent(m_hSimConnect, objectId, EventLandingLightsSet,
2259  lights.isLandingOn() ? 1.0 : 0.0, SIMCONNECT_GROUP_PRIORITY_HIGHEST,
2260  SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY),
2261  traceId, simObject, "Failed so set landing lights", Q_FUNC_INFO,
2262  "SimConnect_TransmitClientEvent::EventLandingLightsSet");
2263 
2264  const HRESULT hr4 =
2265  this->logAndTraceSendId(SimConnect_TransmitClientEvent(
2266  m_hSimConnect, objectId, EventStrobesSet, lights.isStrobeOn() ? 1.0 : 0.0,
2267  SIMCONNECT_GROUP_PRIORITY_HIGHEST, SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY),
2268  traceId, simObject, "Failed to set strobe lights", Q_FUNC_INFO,
2269  "SimConnect_TransmitClientEvent::EventStrobesSet");
2270 
2271  // lights we need to toggle
2272  // (potential risk with quickly changing values that we accidentally toggle back, also we need the light state
2273  // before we can toggle)
2274  this->sendToggledLightsToSimulator(simObject, lights);
2275 
2276  // done
2277  return isOk(hr1, hr2, hr3, hr4);
2278  }
2279 
2280  bool CSimulatorFsxCommon::sendRemoteAircraftAtcDataToSimulator(const CSimConnectObject &simObject)
2281  {
2282  if (!simObject.isReadyToSend()) { return false; }
2283  if (simObject.isTerrainProbe()) { return false; }
2284  // if (simObject.getType() != CSimConnectObject::AircraftNonAtc) { return false; } // otherwise errors
2285 
2286  const DWORD objectId = simObject.getObjectId();
2287  const bool traceId = this->isTracingSendId();
2288 
2289  DataDefinitionRemoteAtc ddAtc;
2290  ddAtc.setDefaultValues();
2291  const QByteArray csBa = simObject.getCallsignByteArray();
2292  const QByteArray airlineBa = simObject.getAircraft().getAirlineIcaoCode().getName().toLatin1();
2293  const QByteArray flightNumberBa = QString::number(simObject.getObjectId()).toLatin1();
2294 
2295  ddAtc.copyAtcId(csBa.constData());
2296  ddAtc.copyAtcAirline(airlineBa.constData());
2297  ddAtc.copyFlightNumber(flightNumberBa.constData());
2298 
2299  // in case we sent, we sent everything
2300  const HRESULT hr = this->logAndTraceSendId(
2302  static_cast<SIMCONNECT_OBJECT_ID>(objectId), SIMCONNECT_DATA_SET_FLAG_DEFAULT,
2303  0, sizeof(DataDefinitionRemoteAtc), &ddAtc),
2304  traceId, simObject, "Failed so aircraft ATC data", Q_FUNC_INFO, "SimConnect_SetDataOnSimObject");
2305  // done
2306  return isOk(hr);
2307  }
2308 
2309  void CSimulatorFsxCommon::sendToggledLightsToSimulator(const CSimConnectObject &simObj,
2310  const CAircraftLights &lightsWanted, bool force)
2311  {
2312  if (!simObj.isReadyToSend()) { return; } // stale
2313 
2314  const CAircraftLights lightsIsState = simObj.getCurrentLightsInSimulator();
2315  if (lightsWanted == lightsIsState) { return; }
2316  if (!force && lightsWanted == simObj.getLightsAsSent()) { return; }
2317  const CCallsign callsign(simObj.getCallsign());
2318 
2319  // Update data
2320  if (m_simConnectObjects.contains(callsign))
2321  {
2322  CSimConnectObject &simObjToUpdate = m_simConnectObjects[callsign];
2323  simObjToUpdate.setLightsAsSent(lightsWanted);
2324  }
2325 
2326  // state available, then I can toggle
2327  if (!lightsIsState.isNull())
2328  {
2329  const DWORD objectId = simObj.getObjectId();
2330  const bool trace = this->isTracingSendId();
2331 
2332  if (lightsWanted.isTaxiOn() != lightsIsState.isTaxiOn())
2333  {
2334  this->logAndTraceSendId(SimConnect_TransmitClientEvent(m_hSimConnect, objectId, EventToggleTaxiLights,
2335  0.0, SIMCONNECT_GROUP_PRIORITY_HIGHEST,
2336  SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY),
2337  trace, simObj, "Toggle taxi lights", Q_FUNC_INFO, "EventToggleTaxiLights");
2338  }
2339  if (lightsWanted.isNavOn() != lightsIsState.isNavOn())
2340  {
2341  this->logAndTraceSendId(SimConnect_TransmitClientEvent(m_hSimConnect, objectId, EventToggleNavLights,
2342  0.0, SIMCONNECT_GROUP_PRIORITY_HIGHEST,
2343  SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY),
2344  trace, simObj, "Toggle nav.lights", Q_FUNC_INFO, "EventToggleNavLights");
2345  }
2346  if (lightsWanted.isBeaconOn() != lightsIsState.isBeaconOn())
2347  {
2348  this->logAndTraceSendId(SimConnect_TransmitClientEvent(m_hSimConnect, objectId, EventToggleBeaconLights,
2349  0.0, SIMCONNECT_GROUP_PRIORITY_HIGHEST,
2350  SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY),
2351  trace, simObj, "Toggle becon lights", Q_FUNC_INFO, "EventToggleBeaconLights");
2352  }
2353  if (lightsWanted.isLogoOn() != lightsIsState.isLogoOn())
2354  {
2355  this->logAndTraceSendId(SimConnect_TransmitClientEvent(m_hSimConnect, objectId, EventToggleLogoLights,
2356  0.0, SIMCONNECT_GROUP_PRIORITY_HIGHEST,
2357  SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY),
2358  trace, simObj, "Toggle logo lights", Q_FUNC_INFO, "EventToggleLogoLights");
2359  }
2360  if (lightsWanted.isRecognitionOn() != lightsIsState.isRecognitionOn())
2361  {
2362  this->logAndTraceSendId(
2363  SimConnect_TransmitClientEvent(m_hSimConnect, objectId, EventToggleRecognitionLights, 0.0,
2364  SIMCONNECT_GROUP_PRIORITY_HIGHEST,
2365  SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY),
2366  trace, simObj, "Toggle recognition lights", Q_FUNC_INFO, "EventToggleRecognitionLights");
2367  }
2368  if (lightsWanted.isCabinOn() != lightsIsState.isCabinOn())
2369  {
2370  this->logAndTraceSendId(SimConnect_TransmitClientEvent(m_hSimConnect, objectId, EventToggleCabinLights,
2371  0.0, SIMCONNECT_GROUP_PRIORITY_HIGHEST,
2372  SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY),
2373  trace, simObj, "Toggle cabin lights", Q_FUNC_INFO, "EventToggleCabinLights");
2374  }
2375  return;
2376  }
2377 
2378  // missing lights info from simulator so far
2379  if (this->showDebugLogMessage())
2380  {
2381  this->debugLogMessage(Q_FUNC_INFO, QStringLiteral("Missing light state in simulator for '%1', model '%2'")
2382  .arg(callsign.asString(), simObj.getAircraftModelString()));
2383  }
2384 
2385  const QPointer<CSimulatorFsxCommon> myself(this);
2386  QTimer::singleShot(DeferResendingLights, this, [=] {
2387  if (!myself) { return; }
2388  if (!m_simConnectObjects.contains(callsign)) { return; }
2389  const CSimConnectObject currentSimObject = m_simConnectObjects[callsign];
2390  if (!currentSimObject.isReadyToSend()) { return; } // stale
2391  if (lightsWanted != currentSimObject.getLightsAsSent())
2392  {
2393  return;
2394  } // changed in between, so another call sendToggledLightsToSimulator is pending
2395  if (this->showDebugLogMessage())
2396  {
2397  this->debugLogMessage(Q_FUNC_INFO, QStringLiteral("Resending light state for '%1', model '%2'")
2398  .arg(callsign.asString(), simObj.getAircraftModelString()));
2399  }
2400  this->sendToggledLightsToSimulator(currentSimObject, lightsWanted, true);
2401  });
2402  }
2403 
2404  SIMCONNECT_DATA_INITPOSITION
2406  bool forceUnderflowDetection, CStatusMessage *details)
2407  {
2408  Q_ASSERT_X(!situation.isGeodeticHeightNull(), Q_FUNC_INFO, "Missing height (altitude)");
2409  Q_ASSERT_X(!situation.isPositionNull(), Q_FUNC_INFO, "Missing position");
2410 
2411  // lat/Lng, NO PBH
2412  CAircraftSituation::AltitudeCorrection altCorrection = CAircraftSituation::UnknownCorrection;
2413  SIMCONNECT_DATA_INITPOSITION position = CSimulatorFsxCommon::coordinateToFsxPosition(situation);
2414  if (forceUnderflowDetection)
2415  {
2416  const CAltitude alt = situation.getCorrectedAltitude(true, &altCorrection);
2417  position.Altitude = alt.value(CLengthUnit::ft());
2418  }
2419 
2420  // MSFS has inverted pitch and bank angles
2421  position.Pitch = -situation.getPitch().value(CAngleUnit::deg());
2422  position.Bank = -situation.getBank().value(CAngleUnit::deg());
2423  position.Heading = situation.getHeading().value(CAngleUnit::deg());
2424  position.OnGround = 0U; // not on ground
2425 
2426  const double gsKts = situation.getGroundSpeed().value(CSpeedUnit::kts());
2427  position.Airspeed = static_cast<DWORD>(qRound(gsKts));
2428 
2429  // sanity check
2430  if (gsKts < 0.0)
2431  {
2432  // we get negative GS for pushback and helicopters
2433  // here we handle them her with DWORD (unsigned)
2434  position.Airspeed = 0U;
2435  }
2436  else { position.Airspeed = static_cast<DWORD>(qRound(gsKts)); }
2437 
2438  // send GND flag also when underflow detection is available
2439  if ((sendGnd || forceUnderflowDetection) && situation.isOnGroundInfoAvailable())
2440  {
2441  const bool onGround = situation.isOnGround();
2442  position.OnGround = onGround ? 1U : 0U;
2443  }
2444 
2445  // if we have no GND flag yet (gnd flag prevents underflow)
2446  if (forceUnderflowDetection && position.OnGround == 0 &&
2447  !CAircraftSituation::isCorrectedAltitude(altCorrection))
2448  {
2449  // logical resolution failed so far, likely we have no CG or elevantion
2450  // primitive guessing
2451  do {
2452  if (position.Airspeed < 2)
2453  {
2454  position.OnGround = 1U;
2455  if (details)
2456  {
2457  *details = CStatusMessage(static_cast<CSimulatorFsxCommon *>(nullptr))
2458  .warning(u"Force GND flag for underflow protection");
2459  }
2460  break;
2461  }
2462  }
2463  while (false);
2464  }
2465 
2466  // crosscheck
2467  if (CBuildConfig::isLocalDeveloperDebugBuild())
2468  {
2469  SWIFT_VERIFY_X(isValidFsxPosition(position), Q_FUNC_INFO, "Invalid FSX pos.");
2470  }
2471 
2472  return position;
2473  }
2474 
2476  {
2477  // MSFS has inverted pitch and bank angles
2478  SIMCONNECT_DATA_PBH pbh;
2479  pbh.Pitch = -situation.getPitch().value(CAngleUnit::deg());
2480  pbh.Bank = -situation.getBank().value(CAngleUnit::deg());
2481  pbh.Heading = situation.getHeading().value(CAngleUnit::deg());
2482  return pbh;
2483  }
2484 
2485  SIMCONNECT_DATA_INITPOSITION CSimulatorFsxCommon::coordinateToFsxPosition(const ICoordinateGeodetic &coordinate)
2486  {
2487  SIMCONNECT_DATA_INITPOSITION position;
2488  position.Latitude = coordinate.latitude().value(CAngleUnit::deg());
2489  position.Longitude = coordinate.longitude().value(CAngleUnit::deg());
2490  position.Altitude = coordinate.geodeticHeight().value(
2491  CLengthUnit::ft()); // already corrected in interpolator if there is an underflow
2492  position.Heading = 0;
2493  position.Airspeed = 0;
2494  position.Pitch = 0;
2495  position.Bank = 0;
2496  position.OnGround = 0;
2497  return position;
2498  }
2499 
2500  SIMCONNECT_DATA_LATLONALT CSimulatorFsxCommon::coordinateToFsxLatLonAlt(const ICoordinateGeodetic &coordinate)
2501  {
2502  SIMCONNECT_DATA_LATLONALT lla;
2503  lla.Latitude = coordinate.latitude().value(CAngleUnit::deg());
2504  lla.Longitude = coordinate.longitude().value(CAngleUnit::deg());
2505  lla.Altitude = coordinate.geodeticHeight().value(
2506  CLengthUnit::ft()); // already corrected in interpolator if there is an underflow
2507  return lla;
2508  }
2509 
2510  bool CSimulatorFsxCommon::isValidFsxPosition(const SIMCONNECT_DATA_INITPOSITION &fsxPos)
2511  {
2512  // double Latitude; // degrees | double Longitude; // degrees | double Altitude; // feet
2513  // double Pitch; // degrees | double Bank; // degrees | double Heading; // degrees
2514  // DWORD OnGround; // 1=force to be on the ground | DWORD Airspeed; // knots
2515  // https://www.prepar3d.com/SDKv4/sdk/simconnect_api/references/simobject_functions.html
2516  // examples show heaading 180 => we assume values +-180deg
2517  if (!isValid180Deg(fsxPos.Pitch)) { return false; }
2518  if (!isValid180Deg(fsxPos.Bank)) { return false; }
2519  if (!isValid180Deg(fsxPos.Heading)) { return false; }
2520  if (!isValid180Deg(fsxPos.Latitude)) { return false; }
2521  if (!isValid180Deg(fsxPos.Longitude)) { return false; }
2522  return true;
2523  }
2524 
2525  bool CSimulatorFsxCommon::requestPositionDataForSimObject(const CSimConnectObject &simObject,
2526  SIMCONNECT_PERIOD period)
2527  {
2528  if (this->isShuttingDownOrDisconnected()) { return false; }
2529  if (!simObject.hasValidRequestAndObjectId()) { return false; }
2530  if (simObject.isPending()) { return false; } // wait until confirmed
2531  if (simObject.getSimDataPeriod() == period) { return true; } // already queried like this
2532  if (!m_simConnectObjects.contains(simObject.getCallsign())) { return false; } // removed in meantime
2533 
2534  // always request, not only when something has changed
2535  const SIMCONNECT_DATA_REQUEST_ID reqId = static_cast<SIMCONNECT_DATA_REQUEST_ID>(
2536  simObject.getRequestId(CSimConnectDefinitions::SimObjectPositionData));
2537  const HRESULT result = this->logAndTraceSendId(
2538  SimConnect_RequestDataOnSimObject(m_hSimConnect, reqId,
2540  simObject.getObjectId(), period),
2541  simObject, "Cannot request simulator data", Q_FUNC_INFO, "SimConnect_RequestDataOnSimObject");
2542 
2543  if (isOk(result))
2544  {
2545  m_requestSimObjectDataCount++;
2546  m_simConnectObjects[simObject.getCallsign()].setSimDataPeriod(period);
2547  return true;
2548  }
2549  return false;
2550  }
2551 
2552  bool CSimulatorFsxCommon::requestTerrainProbeData(const CSimConnectObject &simObject,
2553  const CCallsign &aircraftCallsign)
2554  {
2555  static const QString w("Cannot request terrain probe data for id '%1'");
2556  const SIMCONNECT_DATA_REQUEST_ID requestId =
2557  simObject.getRequestId(CSimConnectDefinitions::SimObjectPositionData);
2558  const DWORD objectId = simObject.getObjectId();
2559  const HRESULT result = this->logAndTraceSendId(
2560  SimConnect_RequestDataOnSimObject(m_hSimConnect, static_cast<SIMCONNECT_DATA_REQUEST_ID>(requestId),
2562  static_cast<SIMCONNECT_OBJECT_ID>(objectId), SIMCONNECT_PERIOD_ONCE),
2563  simObject, w.arg(requestId), Q_FUNC_INFO, "SimConnect_RequestDataOnSimObject");
2564  const bool ok = isOk(result);
2565  if (ok) { m_pendingProbeRequests.insert(requestId, aircraftCallsign); }
2566  return ok;
2567  }
2568 
2569  bool CSimulatorFsxCommon::requestLightsForSimObject(const CSimConnectObject &simObject)
2570  {
2571  if (!this->isValidSimObjectNotPendingRemoved(simObject)) { return false; }
2572  if (!m_hSimConnect) { return false; }
2573 
2574  // always request, not only when something has changed
2575  const SIMCONNECT_DATA_REQUEST_ID requestId = simObject.getRequestId(CSimConnectDefinitions::SimObjectLights);
2576  const HRESULT result = this->logAndTraceSendId(
2577  SimConnect_RequestDataOnSimObject(m_hSimConnect, requestId,
2578  CSimConnectDefinitions::DataRemoteAircraftLights, simObject.getObjectId(),
2579  SIMCONNECT_PERIOD_SECOND),
2580  simObject, "Cannot request lights data", Q_FUNC_INFO, "SimConnect_RequestDataOnSimObject");
2581  return isOk(result);
2582  }
2583 
2584  bool CSimulatorFsxCommon::requestModelInfoForSimObject(const CSimConnectObject &simObject)
2585  {
2586  if (!this->isValidSimObjectNotPendingRemoved(simObject)) { return false; }
2587  if (!m_hSimConnect) { return false; }
2588 
2589  // always request, not only when something has changed
2590  const SIMCONNECT_DATA_REQUEST_ID requestId = simObject.getRequestId(CSimConnectDefinitions::SimObjectModel);
2591  const HRESULT result = this->logAndTraceSendId(
2592  SimConnect_RequestDataOnSimObject(m_hSimConnect, requestId,
2594  simObject.getObjectId(), SIMCONNECT_PERIOD_ONCE),
2595  simObject, "Cannot request model info", Q_FUNC_INFO, "SimConnect_RequestDataOnSimObject");
2596  return isOk(result);
2597  }
2598 
2599  bool CSimulatorFsxCommon::stopRequestingDataForSimObject(const CSimConnectObject &simObject)
2600  {
2601  if (!simObject.hasValidRequestAndObjectId()) { return false; }
2602  if (!m_hSimConnect) { return false; }
2603 
2604  // stop by setting SIMCONNECT_PERIOD_NEVER
2605  SIMCONNECT_DATA_REQUEST_ID requestId = simObject.getRequestId(CSimConnectDefinitions::SimObjectPositionData);
2606  const HRESULT hr1 = this->logAndTraceSendId(
2607  SimConnect_RequestDataOnSimObject(m_hSimConnect, requestId,
2609  simObject.getObjectId(), SIMCONNECT_PERIOD_NEVER),
2610  simObject, "Stopping position request", Q_FUNC_INFO, "SimConnect_RequestDataOnSimObject");
2611 
2612  requestId = simObject.getRequestId(CSimConnectDefinitions::SimObjectLights);
2613  const HRESULT hr2 = this->logAndTraceSendId(
2614  SimConnect_RequestDataOnSimObject(m_hSimConnect, requestId,
2615  CSimConnectDefinitions::DataRemoteAircraftLights, simObject.getObjectId(),
2616  SIMCONNECT_PERIOD_NEVER),
2617  simObject, "Stopping lights request", Q_FUNC_INFO, "SimConnect_RequestDataOnSimObject");
2618  return isOk(hr1, hr2);
2619  }
2620 
2622  {
2623  CSimulatorFsCommon::initSimulatorInternals();
2624  m_simulatorInternals.setValue("fsx/simConnectVersion", m_simConnectVersion);
2625  }
2626 
2628  {
2629  this->safeKillTimer();
2630 
2631  // cleared below:
2632  // physicallyRemoveAllRemoteAircraft
2633  // m_simConnectObjects
2634  // m_simConnectObjectsPositionAndPartsTraces
2635  // m_addPendingAircraft
2636  // m_updateRemoteAircraftInProgress
2637  CSimulatorFsCommon::reset(); // clears all pending aircraft etc
2638 
2639  // reset values
2640  m_simulatingChangedTs = -1;
2641  m_simConnected = false;
2642  m_simSimulating = false;
2643  m_sbDataReceived = 0;
2644  m_requestIdSimObjAircraft = static_cast<SIMCONNECT_DATA_REQUEST_ID>(RequestSimObjAircraftStart);
2645  m_dispatchErrors = 0;
2646  m_receiveExceptionCount = 0;
2647  m_addedProbes = 0;
2648  m_initFsxTerrainProbes = false;
2649  m_sendIdTraces.clear();
2650  }
2651 
2653  {
2654  const bool reinitProbe =
2655  m_useFsxTerrainProbe && m_initFsxTerrainProbes; // re-init if enabled and was initialized
2656  this->removeAllProbes();
2657 
2658  // m_addAgainAircraftWhenRemoved cleared below
2659  CSimulatorFsCommon::clearAllRemoteAircraftData(); // also removes aircraft
2661  m_addPendingAircraft.clear();
2662  m_simConnectObjectsPositionAndPartsTraces.clear();
2663 
2664  if (reinitProbe)
2665  {
2666  // if we are still alive we re-init the probes
2667  QPointer<CSimulatorFsxCommon> myself(this);
2668  QTimer::singleShot(2000, this, [=] {
2669  // Shutdown or unloaded
2670  if (this->isShuttingDown() || !myself) { return; }
2671  m_initFsxTerrainProbes = false; // probes will re-init
2672  });
2673  }
2674  }
2675 
2677  {
2678  m_sbDataReceived = 0;
2679  CSimulatorFsCommon::onOwnModelChanged(newModel);
2680  }
2681 
2682  QString CSimulatorFsxCommon::fsxPositionToString(const SIMCONNECT_DATA_INITPOSITION &position)
2683  {
2684  static const QString positionStr(
2685  "Lat: %1deg lng: %2deg alt: %3ft pitch: %4deg bank: %5deg hdg: %6deg airspeed: %7kts onGround: %8");
2686  return positionStr.arg(position.Latitude)
2687  .arg(position.Longitude)
2688  .arg(position.Altitude)
2689  .arg(position.Pitch)
2690  .arg(position.Bank)
2691  .arg(position.Heading)
2692  .arg(position.Airspeed)
2693  .arg(position.OnGround);
2694  }
2695 
2696  CCallsignSet CSimulatorFsxCommon::getCallsignsMissingInProvider() const
2697  {
2698  if (m_simConnectObjects.isEmpty()) { return CCallsignSet(); }
2699  const CCallsignSet simObjectCallsigns(m_simConnectObjects.getAllCallsigns(true));
2700  const CCallsignSet providerCallsigns(this->getAircraftInRangeCallsigns());
2701  return simObjectCallsigns.difference(providerCallsigns);
2702  }
2703 
2704  void CSimulatorFsxCommon::traceSendId(const CSimConnectObject &simObject, const QString &functionName,
2705  const QString &details, bool forceTrace)
2706  {
2707  if (!forceTrace && !this->isTracingSendId()) { return; }
2708  if (MaxSendIdTraces < 1) { return; } // cppcheck-suppress knownConditionTrueFalse
2709  DWORD dwLastId = 0;
2710  const HRESULT hr = SimConnect_GetLastSentPacketID(m_hSimConnect, &dwLastId);
2711  if (isFailure(hr)) { return; }
2712  if (m_sendIdTraces.size() > MaxSendIdTraces) { m_sendIdTraces.removeLast(); }
2713  const TraceFsxSendId trace(dwLastId, simObject,
2714  details.isEmpty() ? functionName : details % u", " % functionName);
2715  m_sendIdTraces.push_front(trace);
2716  }
2717 
2718  HRESULT CSimulatorFsxCommon::logAndTraceSendId(HRESULT hr, const QString &warningMsg, const QString &functionName,
2719  const QString &functionDetails)
2720  {
2721  const CSimConnectObject empty;
2722  return this->logAndTraceSendId(hr, empty, warningMsg, functionName, functionDetails);
2723  }
2724 
2725  HRESULT CSimulatorFsxCommon::logAndTraceSendId(HRESULT hr, const CSimConnectObject &simObject,
2726  const QString &warningMsg, const QString &functionName,
2727  const QString &functionDetails)
2728  {
2729  return this->logAndTraceSendId(hr, this->isTracingSendId(), simObject, warningMsg, functionName,
2730  functionDetails);
2731  }
2732 
2733  HRESULT CSimulatorFsxCommon::logAndTraceSendId(HRESULT hr, bool traceSendId, const CSimConnectObject &simObject,
2734  const QString &warningMsg, const QString &functionName,
2735  const QString &functionDetails)
2736  {
2737  if (traceSendId) { this->traceSendId(simObject, functionName, functionDetails); }
2738  if (isOk(hr)) { return hr; }
2739  if (!warningMsg.isEmpty()) { CLogMessage(this).warning(warningMsg % u" SimObject: " % simObject.toQString()); }
2740  this->triggerAutoTraceSendId();
2741  return hr;
2742  }
2743 
2744  QByteArray CSimulatorFsxCommon::toFsxChar(const QString &string) { return string.toLatin1(); }
2745 
2746  TraceFsxSendId CSimulatorFsxCommon::getSendIdTrace(DWORD sendId) const
2747  {
2748  for (const TraceFsxSendId &trace : m_sendIdTraces)
2749  {
2750  if (trace.sendId == sendId) { return trace; }
2751  }
2752  return TraceFsxSendId::invalid();
2753  }
2754 
2755  QString CSimulatorFsxCommon::getSendIdTraceDetails(DWORD sendId) const
2756  {
2757  const TraceFsxSendId trace = this->getSendIdTrace(sendId);
2758  if (trace.sendId == sendId) { return this->getSendIdTraceDetails(trace); }
2759  return {};
2760  }
2761 
2762  QString CSimulatorFsxCommon::getSendIdTraceDetails(const TraceFsxSendId &trace) const
2763  {
2764  static const QString d("Send id: %1 obj.id.: %2 SimObj: %3 | '%4'");
2765  if (trace.isInvalid()) { return QString(); }
2766 
2767  // update with latest sim object
2768  const CSimConnectObject simObject = this->getSimObjectForTrace(trace);
2769  return d.arg(trace.sendId).arg(simObject.getObjectId()).arg(simObject.toQString(), trace.comment);
2770  }
2771 
2772  int CSimulatorFsxCommon::removeAllProbes()
2773  {
2774  if (!m_hSimConnect) { return 0; } // already disconnected
2776 
2777  int c = 0;
2778  for (const CSimConnectObject &probeSimObject : probes)
2779  {
2780  if (!probeSimObject.isConfirmedAdded()) { continue; }
2781  const SIMCONNECT_DATA_REQUEST_ID requestId =
2782  probeSimObject.getRequestId(CSimConnectDefinitions::SimObjectRemove);
2783  const HRESULT result = SimConnect_AIRemoveObject(
2784  m_hSimConnect, static_cast<SIMCONNECT_OBJECT_ID>(probeSimObject.getObjectId()), requestId);
2785  if (isOk(result)) { c++; }
2786  else
2787  {
2788  CLogMessage(this).warning(u"Removing probe '%1' from simulator failed") << probeSimObject.getObjectId();
2789  }
2790  }
2793  return c;
2794  }
2795 
2796  CSimConnectObject CSimulatorFsxCommon::insertNewSimConnectObject(const CSimulatedAircraft &aircraft,
2797  DWORD requestId,
2799  const CSimConnectObject &removedPendingObject)
2800  {
2801  if (m_simConnectObjects.contains(aircraft.getCallsign()))
2802  {
2803  // error, ...?
2804  CSimConnectObject &simObject = m_simConnectObjects[aircraft.getCallsign()];
2805  simObject.copyAddingFailureCounters(removedPendingObject);
2806  simObject.resetTimestampToNow();
2807  return simObject;
2808  }
2809 
2810  CSimConnectObject simObject;
2811  if (m_simConnectObjectsPositionAndPartsTraces.contains(aircraft.getCallsign()))
2812  {
2813  // if in traces, get the object and reuse it
2814  simObject = m_simConnectObjectsPositionAndPartsTraces[aircraft.getCallsign()];
2815  m_simConnectObjectsPositionAndPartsTraces.remove(aircraft.getCallsign());
2816  simObject.resetState();
2817  simObject.setRequestId(requestId);
2818  simObject.setAircraft(aircraft);
2819  simObject.attachInterpolatorLogger(&m_interpolationLogger); // setting a logger does not start logging
2820  }
2821  else
2822  {
2823  simObject = CSimConnectObject(aircraft, requestId, this, this, this->getRemoteAircraftProvider(),
2825  }
2826  simObject.copyAddingFailureCounters(removedPendingObject);
2827  simObject.setType(type);
2828  m_simConnectObjects.insert(simObject, true); // update timestamp
2829  return simObject;
2830  }
2831 
2832  const CAltitude &CSimulatorFsxCommon::terrainProbeAltitude()
2833  {
2834  static const CAltitude alt(50000, CLengthUnit::ft());
2835  return alt;
2836  }
2837 
2838  QString CSimulatorFsxCommon::fsxCharToQString(const char *fsxChar, int size)
2839  {
2840  return QString::fromLatin1(fsxChar, size);
2841  }
2842 
2843  QString CSimulatorFsxCommon::requestIdToString(DWORD requestId)
2844  {
2846  {
2848  }
2849 
2852 
2853  static const QString req("%1 %2 %3");
2854  return req.arg(requestId)
2855  .arg(CSimConnectObject::typeToString(simType))
2857  }
2858 
2859  DWORD CSimulatorFsxCommon::unitTestRequestId(CSimConnectObject::SimObjectType type)
2860  {
2861  int start;
2862  int end;
2863  switch (type)
2864  {
2865  case CSimConnectObject::TerrainProbe:
2866  start = RequestSimObjTerrainProbeStart;
2867  end = RequestSimObjTerrainProbeEnd;
2868  break;
2869  case CSimConnectObject::AircraftNonAtc:
2870  case CSimConnectObject::AircraftSimulatedObject:
2871  default:
2872  start = RequestSimObjAircraftStart;
2873  end = RequestSimObjAircraftEnd;
2874  break;
2875  }
2876 
2877  const int id = CMathUtils::randomInteger(start, end);
2878  return static_cast<DWORD>(id);
2879  }
2880 
2881  CCallsignSet CSimulatorFsxCommon::physicallyRemoveAircraftNotInProvider()
2882  {
2883  const CCallsignSet callsignsToBeRemoved(this->getCallsignsMissingInProvider());
2884  if (callsignsToBeRemoved.isEmpty()) { return callsignsToBeRemoved; }
2885  for (const CCallsign &callsign : callsignsToBeRemoved) { this->physicallyRemoveRemoteAircraft(callsign); }
2886 
2887  if (this->showDebugLogMessage())
2888  {
2889  this->debugLogMessage(Q_FUNC_INFO,
2890  QStringLiteral("CS: '%1'").arg(callsignsToBeRemoved.toStringList().join(", ")));
2891  }
2892  return callsignsToBeRemoved;
2893  }
2894 
2895  void CSimulatorFsxCommon::physicallyRemoveAircraftNotInProviderAsync()
2896  {
2897  const QPointer<CSimulatorFsxCommon> myself(this);
2898  QTimer::singleShot(100, this, [=] {
2899  if (!myself || this->isShuttingDown()) { return; }
2900  CSimulatorFsxCommon::physicallyRemoveAircraftNotInProvider();
2901  });
2902  }
2903 
2905  : ISimulatorListener(info)
2906  {
2907  m_timer.setInterval(MinQueryIntervalMs);
2908  m_timer.setObjectName(this->objectName().append(":m_timer"));
2909  connect(&m_timer, &QTimer::timeout, this, &CSimulatorFsxCommonListener::checkConnection);
2910  }
2911 
2913  {
2914  m_simulatorVersion.clear();
2915  m_simConnectVersion.clear();
2916  m_simulatorName.clear();
2917  m_simulatorDetails.clear();
2918 
2919  m_timer.start();
2920  }
2921 
2923  {
2924  m_timer.stop();
2925  this->disconnectFromSimulator();
2926  }
2927 
2929  {
2930  if (!m_timer.isActive()) { return; }
2931  if (this->isShuttingDown()) { return; }
2932 
2934  QTimer::singleShot(0, this, [=] {
2935  if (!myself || !sApp || sApp->isShuttingDown()) { return; }
2936  this->checkConnection();
2937  });
2938 
2939  // restart because we have just checked now
2940  m_timer.start();
2941  }
2942 
2944  {
2945  if (m_simulatorName.isEmpty()) { return ISimulatorListener::backendInfo(); }
2946  return m_simulatorDetails;
2947  }
2948 
2949  void CSimulatorFsxCommonListener::checkConnection()
2950  {
2951  Q_ASSERT_X(!CThreadUtils::thisIsMainThread(), Q_FUNC_INFO, "Expect to run in background");
2952 
2953  // check before we access the sim. connection
2954  if (this->isShuttingDown() || this->thread()->isInterruptionRequested())
2955  {
2956  this->stopImpl();
2957  return;
2958  }
2959 
2960  QElapsedTimer t;
2961  t.start();
2962  bool check = false;
2963  do {
2964  // if we can connect, but not dispatch, it can mean a previously started FSX/P3D
2965  // blocks remote calls -> RESTART
2966  if (!this->connectToSimulator()) { break; }
2967 
2968  // check if we have the right sim.
2969  // this check on a remote FSX/P3D not running/existing might TAKE LONG!
2970  const HRESULT result =
2971  SimConnect_CallDispatch(m_hSimConnect, CSimulatorFsxCommonListener::SimConnectProc, this);
2972 
2973  // make sure we did not stop in meantime
2974  if (this->isShuttingDown() || this->thread()->isInterruptionRequested())
2975  {
2976  this->stopImpl();
2977  return;
2978  }
2979 
2980  if (isFailure(result)) { break; } // means serious failure
2981  check = this->checkVersionAndSimulator();
2982  }
2983  while (false);
2984 
2985  this->adjustTimerInterval(t.elapsed());
2986  if (check) { emit this->simulatorStarted(this->getPluginInfo()); }
2987  }
2988 
2989  void CSimulatorFsxCommonListener::adjustTimerInterval(qint64 checkTimeMs)
2990  {
2991  const QString sim = this->getPluginInfo().getSimulatorInfo().toQString(true);
2992  CLogMessage(this).debug(u"Checked sim.'%1' connection in %2ms") << sim << checkTimeMs;
2993  if (checkTimeMs > qRound(1.25 * MinQueryIntervalMs))
2994  {
2995  const int newIntervalMs = qRound(1.2 * checkTimeMs / 1000.0) * 1000;
2996  CLogMessage(this).debug(u"Check for simulator sim.'%1' connection in %2ms, too slow. Setting %3ms")
2997  << sim << checkTimeMs << newIntervalMs;
2998  if (m_timer.interval() != newIntervalMs) { m_timer.setInterval(newIntervalMs); }
2999  }
3000  else
3001  {
3002  if (m_timer.interval() != MinQueryIntervalMs) { m_timer.setInterval(MinQueryIntervalMs); }
3003  }
3004 
3005  // restart
3006  m_timer.start();
3007  }
3008 
3009  bool CSimulatorFsxCommonListener::checkVersionAndSimulator() const
3010  {
3011  const CSimulatorInfo pluginSim(getPluginInfo().getIdentifier());
3012  const QString connectedSimName = m_simulatorName.toLower().trimmed();
3013 
3014  if (connectedSimName.isEmpty()) { return false; }
3015  if (pluginSim.isP3D())
3016  {
3017  // P3D drivers only works with P3D
3018  return connectedSimName.contains("lockheed") || connectedSimName.contains("martin") ||
3019  connectedSimName.contains("p3d") || connectedSimName.contains("prepar");
3020  }
3021  else if (pluginSim.isFSX())
3022  {
3023  // FSX drivers only works with FSX
3024  return connectedSimName.contains("fsx") || connectedSimName.contains("microsoft") ||
3025  connectedSimName.contains("simulator x");
3026  }
3027  else if (pluginSim.isMSFS())
3028  {
3029  // MSFS 2020 drivers only works with MSFS
3030  return connectedSimName.contains("kittyhawk");
3031  }
3032  else if (pluginSim.isMSFS2024())
3033  {
3034  // MSFS2024 drivers only works with MSFS2024
3035  return connectedSimName.contains("sunrise");
3036  }
3037  return false;
3038  }
3039 
3040  bool CSimulatorFsxCommonListener::checkSimConnectDll() const
3041  {
3042  static const CWinDllUtils::DLLInfo simConnectInfo = CSimConnectUtilities::simConnectDllInfo();
3043  if (!simConnectInfo.errorMsg.isEmpty()) { return false; }
3044  return true;
3045  }
3046 
3047  bool CSimulatorFsxCommonListener::connectToSimulator()
3048  {
3049  if (m_simConnected) { return true; }
3050  const HRESULT result = SimConnect_Open(&m_hSimConnect, sApp->swiftVersionChar(), nullptr, 0, nullptr, 0);
3051  const bool ok = isOk(result);
3052  m_simConnected = ok;
3053  return ok;
3054  }
3055 
3056  bool CSimulatorFsxCommonListener::disconnectFromSimulator()
3057  {
3058  if (!m_simConnected) { return false; }
3059  SimConnect_Close(m_hSimConnect);
3060  m_hSimConnect = nullptr;
3061  m_simConnected = false;
3062  return true;
3063  }
3064 
3065  void CSimulatorFsxCommonListener::SimConnectProc(SIMCONNECT_RECV *pData, DWORD cbData, void *pContext)
3066  {
3067  Q_UNUSED(cbData)
3068  CSimulatorFsxCommonListener *simListener = static_cast<CSimulatorFsxCommonListener *>(pContext);
3069  switch (pData->dwID)
3070  {
3071  case SIMCONNECT_RECV_ID_OPEN:
3072  {
3073  SIMCONNECT_RECV_OPEN *event = static_cast<SIMCONNECT_RECV_OPEN *>(pData);
3074  simListener->m_simulatorVersion = QStringLiteral("%1.%2.%3.%4")
3075  .arg(event->dwApplicationVersionMajor)
3076  .arg(event->dwApplicationVersionMinor)
3077  .arg(event->dwApplicationBuildMajor)
3078  .arg(event->dwApplicationBuildMinor);
3079  simListener->m_simConnectVersion = QStringLiteral("%1.%2.%3.%4")
3080  .arg(event->dwSimConnectVersionMajor)
3081  .arg(event->dwSimConnectVersionMinor)
3082  .arg(event->dwSimConnectBuildMajor)
3083  .arg(event->dwSimConnectBuildMinor);
3084  simListener->m_simulatorName = CSimulatorFsxCommon::fsxCharToQString(event->szApplicationName);
3085  simListener->m_simulatorDetails = QStringLiteral("Name: '%1' Version: %2 SimConnect: %3")
3086  .arg(simListener->m_simulatorName, simListener->m_simulatorVersion,
3087  simListener->m_simConnectVersion);
3088  const CStatusMessage msg = CStatusMessage(simListener).info(u"Connect to %1: '%2'")
3089  << simListener->getPluginInfo().getIdentifier() << simListener->backendInfo();
3090 
3091  // avoid the same message over and over again
3092  if (msg.getMessage() != simListener->m_lastMessage.getMessage())
3093  {
3094  CLogMessage::preformatted(msg);
3095  simListener->m_lastMessage = msg;
3096  }
3097  break;
3098  }
3099  case SIMCONNECT_RECV_ID_EXCEPTION: break;
3100  default: break;
3101  }
3102  }
3103 } // 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:260
bool showDebugLogMessage() const
Show log messages?
Definition: simulator.cpp:156
bool addLoopbackSituation(const swift::misc::aviation::CAircraftSituation &situation)
Add a loopback situation if logging is enabled.
Definition: simulator.cpp:186
bool isEqualLastSent(const swift::misc::aviation::CAircraftSituation &compare) const
Equal to last sent situation.
Definition: simulator.cpp:557
bool updateOwnSituationAndGroundElevation(const swift::misc::aviation::CAircraftSituation &situation)
Update own aircraft position and if suitable use it to update ground elevation.
Definition: simulator.cpp:1063
bool isUpdateAllRemoteAircraft(qint64 currentTimestamp=-1) const
Do update all remote aircraft?
Definition: simulator.cpp:218
virtual bool isShuttingDown() const
Is overall (swift) application shutting down.
Definition: simulator.h:214
int m_timerId
dispatch timer id
Definition: simulator.h:561
void requestedElevation(const swift::misc::aviation::CCallsign &callsign)
Requested elevation, call pending.
bool m_updateRemoteAircraftInProgress
currently updating remote aircraft
Definition: simulator.h:559
virtual bool isShuttingDownDisconnectedOrNoAircraft() const
Shutting down, disconnected, or no remote aircraft.
Definition: simulator.h:220
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:776
bool isAircraftInRangeOrTestMode(const swift::misc::aviation::CCallsign &callsign) const
Test version aware version of isAircraftInRange.
Definition: simulator.cpp:955
void finishUpdateRemoteAircraftAndSetStatistics(qint64 startTime, bool limited=false)
Update stats and flags.
Definition: simulator.cpp:1035
bool isTestMode() const
Test mode? (driver can skip code parts etc., driver dependent)
Definition: simulator.h:183
void debugLogMessage(const QString &msg)
Display a debug log message based on swift::misc::simulation::CInterpolationAndRenderingSetup remark ...
Definition: simulator.cpp:140
void emitSimulatorCombinedStatus(SimulatorStatus oldStatus=Unspecified)
Emit the combined status.
Definition: simulator.cpp:818
int m_statsUpdateAircraftRuns
statistics update count
Definition: simulator.h:562
swift::misc::simulation::CSimulatedAircraftList m_addAgainAircraftWhenRemoved
add this model again when removed, normally used to change model
Definition: simulator.h:590
swift::misc::simulation::CSimulatorInternals m_simulatorInternals
setup read from the sim
Definition: simulator.h:581
void logAddingAircraftModel(const swift::misc::simulation::CSimulatedAircraft &aircraft) const
Unified qeeing aircraft message.
Definition: simulator.cpp:1138
@ 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:243
virtual SimulatorStatus getSimulatorStatus() const
Combined status.
Definition: simulator.cpp:54
swift::misc::simulation::settings::CSpecializedSimulatorSettings getSimulatorSettings() const
Settings for current simulator.
Definition: simulator.h:162
swift::misc::simulation::CInterpolationLogger m_interpolationLogger
log.interpolation
Definition: simulator.h:582
bool isUpdateAircraftLimitedWithStats(qint64 startTime=-1)
Limited as ISimulator::isUpdateAircraftLimited plus updating statistics.
Definition: simulator.cpp:884
void safeKillTimer()
Kill timer if id is valid.
Definition: simulator.cpp:235
void aircraftRenderingChanged(const swift::misc::simulation::CSimulatedAircraft &aircraft)
Aircraft rendering changed.
bool isFlightNetworkConnected() const
Is the flight network connected.
Definition: simulator.h:159
virtual bool isShuttingDownOrDisconnected() const
Shutting down or disconnected?
Definition: simulator.h:217
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:585
void rememberLastSent(const swift::misc::aviation::CAircraftSituation &sent)
Remember as last sent.
Definition: simulator.cpp:573
Interface to a simulator listener.
Definition: simulator.h:633
const swift::misc::simulation::CSimulatorPluginInfo & getPluginInfo() const
Corresponding info.
Definition: simulator.h:641
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:1262
void check()
Check simulator availability.
Definition: simulator.cpp:1305
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)
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:35
QString getCallsignsAsString(bool sorted=false, const QString &separator=", ") const
Callsigns as string.
Definition: callsignset.cpp:43
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.
const aviation::CAltitude & geodeticHeight() const
Height, ellipsoidal or geodetic height (used in GPS)
void setLatitude(const CLatitude &latitude)
Set latitude.
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:74
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)
Update cockpit, but only send signals when applicable.
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:261
SWIFT_MISC_EXPORT const QString & boolToEnabledDisabled(bool v)
Bool to enabled/disabled.
QByteArray & append(QByteArrayView data)
const char * constData() const const
char * data()
qsizetype size() const const
qint64 currentMSecsSinceEpoch()
QDateTime fromMSecsSinceEpoch(qint64 msecs)
QString toString(QStringView format) const const
qint64 elapsed() const const
void clear()
bool contains(const Key &key) const const
bool isEmpty() const const
QList< Key > keys() const const
bool remove(const Key &key)
T value(const Key &key) const const
QList< T > values() const const
void clear()
QMap< Key, T >::iterator insert(QMap< Key, T >::const_iterator pos, const Key &key, const T &value)
QMap< Key, T >::size_type remove(const Key &key)
T value(const Key &key, const T &defaultValue) const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
virtual bool event(QEvent *e)
void setObjectName(QAnyStringView name)
int startTimer(int interval, Qt::TimerType timerType)
QThread * thread() const const
QString arg(Args &&... args) const const
void clear()
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
QString fromLatin1(QByteArrayView str)
bool isEmpty() const const
QString left(qsizetype n) &&
QString number(double n, char format, int precision)
QByteArray toLatin1() const const
QString toLower() const const
QString trimmed() const const
QString join(QChar separator) const const
const std::string & boolToYesNo(bool t)
Yes/no from bool.
Definition: qtfreeutils.h:129
void setInterval(int msec)
bool isActive() const const
void start()
void stop()
void timeout()
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