Files
pilotclient/src/plugins/simulator/fsxcommon/simulatorfsxcommon.cpp
Roland Winklmeier b16af78142 Load SimConnect dynamically at runtime.
Previously the FSX/P3D driver tried to link a very specific SimConnect version
from the Windows Assembly cache. This caused issues with machines
which did not have this specific version of SimConnect in the assembly
cache since drivers refused to load without much information. Having a different
version of SimConnect in the assembly cache or even in the bin folder did
not help.
To fix this problem, SimConnect is now loaded and resolved manually at
runtime. This allows to try loading different versions from the assembly
cache or prefer to load the shipped one.
For this, the manifests are linked into the binary's resource section and
activated manually before loading. This has the positive effect, that
loading errors can be handled by code instead of raising a cryptic error and
aborting.
2018-07-12 18:29:45 +02:00

2124 lines
106 KiB
C++

/* Copyright (C) 2013
* swift Project Community / Contributors
*
* This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level
* directory of this distribution and at http://www.swift-project.org/license.html. No part of swift project,
* including this file, may be copied, modified, propagated, or distributed except according to the terms
* contained in the LICENSE file.
*/
#include "simulatorfsxcommon.h"
#include "simconnectsymbols.h"
#include "blackcore/application.h"
#include "blackmisc/network/textmessage.h"
#include "blackmisc/simulation/fsx/simconnectutilities.h"
#include "blackmisc/simulation/fscommon/bcdconversions.h"
#include "blackmisc/simulation/fscommon/fscommonutil.h"
#include "blackmisc/simulation/aircraftmodel.h"
#include "blackmisc/simulation/interpolatormulti.h"
#include "blackmisc/simulation/simulatorplugininfo.h"
#include "blackmisc/aviation/airportlist.h"
#include "blackmisc/geo/elevationplane.h"
#include "blackmisc/math/mathutils.h"
#include "blackmisc/logmessage.h"
#include "blackmisc/statusmessagelist.h"
#include "blackmisc/threadutils.h"
#include "blackmisc/verify.h"
#include "blackconfig/buildconfig.h"
#include <QTimer>
#include <QPointer>
#include <type_traits>
using namespace BlackConfig;
using namespace BlackMisc;
using namespace BlackMisc::Aviation;
using namespace BlackMisc::PhysicalQuantities;
using namespace BlackMisc::Geo;
using namespace BlackMisc::Network;
using namespace BlackMisc::Math;
using namespace BlackMisc::Simulation;
using namespace BlackMisc::Simulation::FsCommon;
using namespace BlackMisc::Simulation::Fsx;
using namespace BlackMisc::Weather;
using namespace BlackCore;
namespace BlackSimPlugin
{
namespace FsxCommon
{
CSimulatorFsxCommon::CSimulatorFsxCommon(const CSimulatorPluginInfo &info,
IOwnAircraftProvider *ownAircraftProvider,
IRemoteAircraftProvider *remoteAircraftProvider,
IWeatherGridProvider *weatherGridProvider,
IClientProvider *clientProvider,
QObject *parent) :
CSimulatorFsCommon(info, ownAircraftProvider, remoteAircraftProvider, weatherGridProvider, clientProvider, parent)
{
Q_ASSERT_X(ownAircraftProvider, Q_FUNC_INFO, "Missing provider");
Q_ASSERT_X(remoteAircraftProvider, Q_FUNC_INFO, "Missing provider");
Q_ASSERT_X(sApp, Q_FUNC_INFO, "Missing global object");
m_addPendingSimObjTimer.setInterval(AddPendingAircraftIntervalMs);
m_useFsuipc = false;
// default model will be set in derived class
CSimulatorFsxCommon::registerHelp();
connect(&m_addPendingSimObjTimer, &QTimer::timeout, this, &CSimulatorFsxCommon::addPendingAircraftByTimer);
}
CSimulatorFsxCommon::~CSimulatorFsxCommon()
{
this->disconnectFrom();
// fsuipc is disconnected in CSimulatorFsCommon
}
bool CSimulatorFsxCommon::isConnected() const
{
return m_simConnected && m_hSimConnect;
}
bool CSimulatorFsxCommon::isSimulating() const
{
return m_simSimulating && this->isConnected();
}
bool CSimulatorFsxCommon::connectTo()
{
if (this->isConnected()) { return true; }
this->reset();
if (FAILED(SimConnect_Open(&m_hSimConnect, sApp->swiftVersionChar(), nullptr, 0, 0, 0)))
{
// reset state as expected for unconnected
return false;
}
if (m_useFsuipc) { m_fsuipc->connect(); } // FSUIPC too
// set structures and move on
this->initEvents();
this->initEventsP3D();
this->initDataDefinitionsWhenConnected();
m_timerId = this->startTimer(DispatchIntervalMs);
// do not start m_addPendingAircraftTimer here, it will be started when object was added
return true;
}
bool CSimulatorFsxCommon::disconnectFrom()
{
if (!m_simConnected) { return true; }
this->safeKillTimer();
m_simSimulating = false; // treat as stopped, just setting the flag here avoids overhead of on onSimStopped
m_traceAutoTs = -1;
m_traceSendId = false;
if (m_hSimConnect)
{
SimConnect_Close(m_hSimConnect);
m_hSimConnect = nullptr;
}
this->reset(); // mark as disconnected and reset all values
// emit status and disconnect FSUIPC
return CSimulatorFsCommon::disconnectFrom();
}
bool CSimulatorFsxCommon::physicallyAddRemoteAircraft(const CSimulatedAircraft &newRemoteAircraft)
{
return this->physicallyAddRemoteAircraftImpl(newRemoteAircraft, ExternalCall);
}
bool CSimulatorFsxCommon::updateOwnSimulatorCockpit(const CSimulatedAircraft &ownAircraft, const CIdentifier &originator)
{
if (originator == this->identifier()) { return false; }
if (!this->isSimulating()) { return false; }
// actually those data should be the same as ownAircraft
const CComSystem newCom1 = ownAircraft.getCom1System();
const CComSystem newCom2 = ownAircraft.getCom2System();
const CTransponder newTransponder = ownAircraft.getTransponder();
bool changed = false;
if (newCom1.getFrequencyActive() != m_simCom1.getFrequencyActive())
{
const CFrequency newFreq = newCom1.getFrequencyActive();
SimConnect_TransmitClientEvent(m_hSimConnect, 0, EventSetCom1Active,
CBcdConversions::comFrequencyToBcdHz(newFreq), SIMCONNECT_GROUP_PRIORITY_HIGHEST, SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY);
changed = true;
}
if (newCom1.getFrequencyStandby() != m_simCom1.getFrequencyStandby())
{
const CFrequency newFreq = newCom1.getFrequencyStandby();
SimConnect_TransmitClientEvent(m_hSimConnect, 0, EventSetCom1Standby,
CBcdConversions::comFrequencyToBcdHz(newFreq), SIMCONNECT_GROUP_PRIORITY_HIGHEST, SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY);
changed = true;
}
if (newCom2.getFrequencyActive() != m_simCom2.getFrequencyActive())
{
const CFrequency newFreq = newCom2.getFrequencyActive();
SimConnect_TransmitClientEvent(m_hSimConnect, 0, EventSetCom2Active,
CBcdConversions::comFrequencyToBcdHz(newFreq), SIMCONNECT_GROUP_PRIORITY_HIGHEST, SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY);
changed = true;
}
if (newCom2.getFrequencyStandby() != m_simCom2.getFrequencyStandby())
{
const CFrequency newFreq = newCom2.getFrequencyStandby();
SimConnect_TransmitClientEvent(m_hSimConnect, 0, EventSetCom2Standby,
CBcdConversions::comFrequencyToBcdHz(newFreq), SIMCONNECT_GROUP_PRIORITY_HIGHEST, SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY);
changed = true;
}
if (newTransponder.getTransponderCode() != m_simTransponder.getTransponderCode())
{
SimConnect_TransmitClientEvent(m_hSimConnect, 0, EventSetTransponderCode,
CBcdConversions::transponderCodeToBcd(newTransponder), SIMCONNECT_GROUP_PRIORITY_HIGHEST, SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY);
changed = true;
}
if (newTransponder.getTransponderMode() != m_simTransponder.getTransponderMode())
{
if (m_useSbOffsets)
{
byte ident = newTransponder.isIdentifying() ? 1U : 0U; // 1 is ident
byte standby = newTransponder.isInStandby() ? 1U : 0U; // 1 is standby
HRESULT hr = S_OK;
hr += SimConnect_SetClientData(m_hSimConnect, ClientAreaSquawkBox, CSimConnectDefinitions::DataClientAreaSbIdent, SIMCONNECT_CLIENT_DATA_REQUEST_FLAG_DEFAULT, 0, 1, &ident);
hr += SimConnect_SetClientData(m_hSimConnect, ClientAreaSquawkBox, CSimConnectDefinitions::DataClientAreaSbStandby, SIMCONNECT_CLIENT_DATA_REQUEST_FLAG_DEFAULT, 0, 1, &standby);
if (hr != S_OK)
{
this->triggerAutoTraceSendId();
CLogMessage(this).warning("Setting transponder mode failed (SB offsets)");
}
}
changed = true;
}
// avoid changes of cockpit back to old values due to an outdated read back value
if (changed) { m_skipCockpitUpdateCycles = SkipUpdateCyclesForCockpit; }
// bye
return changed;
}
bool CSimulatorFsxCommon::updateOwnSimulatorSelcal(const CSelcal &selcal, const CIdentifier &originator)
{
if (originator == this->identifier()) { return false; }
if (!this->isSimulating()) { return false; }
//! \fixme KB 2017/8 use SELCAL
Q_UNUSED(selcal);
return false;
}
void CSimulatorFsxCommon::displayStatusMessage(const CStatusMessage &message) const
{
QByteArray m = message.getMessage().toLatin1().constData();
m.append('\0');
SIMCONNECT_TEXT_TYPE type = SIMCONNECT_TEXT_TYPE_PRINT_BLACK;
switch (message.getSeverity())
{
case CStatusMessage::SeverityDebug: return;
case CStatusMessage::SeverityInfo: type = SIMCONNECT_TEXT_TYPE_PRINT_GREEN; break;
case CStatusMessage::SeverityWarning: type = SIMCONNECT_TEXT_TYPE_PRINT_YELLOW; break;
case CStatusMessage::SeverityError: type = SIMCONNECT_TEXT_TYPE_PRINT_RED; break;
}
const HRESULT hr = SimConnect_Text(m_hSimConnect, type, 7.5, EventTextMessage, static_cast<DWORD>(m.size()), m.data());
Q_UNUSED(hr);
}
void CSimulatorFsxCommon::displayTextMessage(const CTextMessage &message) const
{
this->displayStatusMessage(message.asStatusMessage(true, true));
}
bool CSimulatorFsxCommon::isPhysicallyRenderedAircraft(const CCallsign &callsign) const
{
return m_simConnectObjects.contains(callsign);
}
CCallsignSet CSimulatorFsxCommon::physicallyRenderedAircraft() const
{
CCallsignSet callsigns(m_simConnectObjects.keys());
callsigns.push_back(m_addAgainAircraftWhenRemoved.getCallsigns()); // not really rendered right now, but very soon
callsigns.push_back(m_addPendingAircraft.getCallsigns()); // not really rendered, but for the logic it should look like it is
return CCallsignSet(m_simConnectObjects.keys());
}
CStatusMessageList CSimulatorFsxCommon::debugVerifyStateAfterAllAircraftRemoved() const
{
CStatusMessageList msgs;
if (CBuildConfig::isLocalDeveloperDebugBuild()) { return msgs; }
msgs = CSimulatorFsCommon::debugVerifyStateAfterAllAircraftRemoved();
if (!m_simConnectObjects.isEmpty()) { msgs.push_back(CStatusMessage(this).error("m_simConnectObjects not empty: '%1'") << m_simConnectObjects.getAllCallsignStringsAsString(true)); }
if (!m_simConnectObjectsPositionAndPartsTraces.isEmpty()) { msgs.push_back(CStatusMessage(this).error("m_simConnectObjectsPositionAndPartsTraces not empty: '%1'") << m_simConnectObjectsPositionAndPartsTraces.getAllCallsignStringsAsString(true)); }
if (!m_addAgainAircraftWhenRemoved.isEmpty()) { msgs.push_back(CStatusMessage(this).error("m_addAgainAircraftWhenRemoved not empty: '%1'") << m_addAgainAircraftWhenRemoved.getCallsignStrings(true).join(", ")); }
return msgs;
}
QString CSimulatorFsxCommon::getStatisticsSimulatorSpecific() const
{
static const QString specificInfo("dispatch #: %1 %2 times (cur/max): %3ms (%4ms) %5ms (%6ms) %7 %8 simData#: %9");
return specificInfo.
arg(m_dispatchProcCount).arg(m_dispatchProcEmptyCount).
arg(m_dispatchTimeMs).arg(m_dispatchProcTimeMs).arg(m_dispatchMaxTimeMs).arg(m_dispatchProcMaxTimeMs).
arg(CSimConnectUtilities::simConnectReceiveIdToString(m_dispatchReceiveIdMaxTime), requestIdToString(m_dispatchRequestIdMaxTime)).
arg(m_requestSimObjectDataCount);
}
bool CSimulatorFsxCommon::requestElevation(const ICoordinateGeodetic &reference, const CCallsign &callsign)
{
if (this->isShuttingDownOrDisconnected()) { return false; }
if (reference.isNull()) { return false; }
static const CAltitude alt(50000, CLengthUnit::ft());
CCoordinateGeodetic pos(reference);
pos.setGeodeticHeight(alt);
if (m_simConnectProbes.isEmpty()) { return this->physicallyAddAITerrainProbe(pos); }
if (m_simConnectProbes.countConfirmedAdded() < 1) { return false; } // pending probes
const CSimConnectObject simObject = m_simConnectProbes.values().front();
SIMCONNECT_DATA_INITPOSITION position = this->coordinateToFsxPosition(pos);
const HRESULT hr = SimConnect_SetDataOnSimObject(m_hSimConnect, CSimConnectDefinitions::DataRemoteAircraftSetPosition,
simObject.getObjectId(), 0, 0,
sizeof(SIMCONNECT_DATA_INITPOSITION), &position);
if (this->isTracingSendId()) { this->traceSendId(simObject.getObjectId(), Q_FUNC_INFO); }
if (hr == S_OK)
{
this->requestTerrainProbeData(callsign);
emit this->requestedElevation(callsign);
}
else
{
this->triggerAutoTraceSendId();
const CStatusMessage msg = CStatusMessage(this).error("Can not request AI position: '%1'") << callsign.asString();
CLogMessage::preformatted(msg);
}
return hr == S_OK;
}
bool CSimulatorFsxCommon::isTracingSendId() const
{
if (m_traceSendId) { return true; }
if (m_traceAutoTs < 0) { return false; }
const qint64 ts = (QDateTime::currentMSecsSinceEpoch() - AutoTraceOffsetMs);
const bool trace = ts < m_traceAutoTs;
return trace;
}
void CSimulatorFsxCommon::setTractingSendId(bool trace)
{
m_traceSendId = trace;
m_traceAutoTs = -1;
}
void CSimulatorFsxCommon::resetAircraftStatistics()
{
m_dispatchProcCount = 0;
m_dispatchProcEmptyCount = 0;
m_dispatchMaxTimeMs = -1;
m_dispatchProcMaxTimeMs = -1;
m_dispatchTimeMs = -1;
m_dispatchProcTimeMs = -1;
m_requestSimObjectDataCount = 0;
m_dispatchReceiveIdLast = SIMCONNECT_RECV_ID_NULL;
m_dispatchReceiveIdMaxTime = SIMCONNECT_RECV_ID_NULL;
m_dispatchRequestIdLast = CSimConnectDefinitions::RequestEndMarker;
m_dispatchRequestIdMaxTime = CSimConnectDefinitions::RequestEndMarker;
CSimulatorPluginCommon::resetAircraftStatistics();
}
CSimConnectDefinitions::SimObjectRequest CSimulatorFsxCommon::requestToSimObjectRequest(DWORD requestId)
{
DWORD v = static_cast<DWORD>(CSimConnectDefinitions::SimObjectEndMarker);
if (isRequestForSimObjAircraft(requestId))
{
v = (requestId - RequestSimObjAircraftStart) / MaxSimObjAircraft;
}
else if (isRequestForSimObjTerrainProbe(requestId))
{
v = (requestId - RequestSimObjTerrainProbeStart) / MaxSimObjProbes;
}
Q_ASSERT_X(v <= CSimConnectDefinitions::SimObjectEndMarker, Q_FUNC_INFO, "Invalid value");
return static_cast<CSimConnectDefinitions::SimObjectRequest>(v);
}
bool CSimulatorFsxCommon::stillDisplayReceiveExceptions()
{
m_receiveExceptionCount++;
return m_receiveExceptionCount < IgnoreReceiveExceptions;
}
CSimConnectObject CSimulatorFsxCommon::getSimObjectForObjectId(DWORD objectId) const
{
return this->getSimConnectObjects().getSimObjectForObjectId(objectId);
}
CSimConnectObject CSimulatorFsxCommon::getProbeForObjectId(DWORD objectId) const
{
return this->getSimConnectProbes().getSimObjectForObjectId(objectId);
}
void CSimulatorFsxCommon::setSimConnected()
{
m_simConnected = true;
this->initSimulatorInternals();
this->emitSimulatorCombinedStatus();
// Internals depends on sim data which take a while to be read
// this is a trick and I re-init again after a while (which is not really expensive)
const QPointer<CSimulatorFsxCommon> myself(this);
QTimer::singleShot(1000, this, [myself]
{
if (myself.isNull()) { return; }
myself->initSimulatorInternals();
});
}
void CSimulatorFsxCommon::onSimRunning()
{
const QPointer<CSimulatorFsxCommon> myself(this);
QTimer::singleShot(DeferSimulatingFlagMs, this, [ = ]
{
if (myself.isNull()) { return; }
m_simulatingChangedTs = QDateTime::currentMSecsSinceEpoch();
this->onSimRunningDeferred(m_simulatingChangedTs);
});
}
void CSimulatorFsxCommon::onSimRunningDeferred(qint64 referenceTs)
{
if (m_simSimulating) { return; }
if (referenceTs != m_simulatingChangedTs) { return; } // changed, so no longer valid
m_simSimulating = true; // only place where this should be set to true
m_simConnected = true;
HRESULT hr = SimConnect_RequestDataOnSimObject(m_hSimConnect, CSimConnectDefinitions::RequestOwnAircraft,
CSimConnectDefinitions::DataOwnAircraft, SIMCONNECT_OBJECT_ID_USER, SIMCONNECT_PERIOD_VISUAL_FRAME);
hr += SimConnect_RequestDataOnSimObject(m_hSimConnect, CSimConnectDefinitions::RequestOwnAircraftTitle,
CSimConnectDefinitions::DataOwnAircraftTitle,
SIMCONNECT_OBJECT_ID_USER, SIMCONNECT_PERIOD_SECOND, SIMCONNECT_DATA_REQUEST_FLAG_CHANGED);
hr += SimConnect_RequestDataOnSimObject(m_hSimConnect, CSimConnectDefinitions::RequestSimEnvironment,
CSimConnectDefinitions::DataSimEnvironment,
SIMCONNECT_OBJECT_ID_USER, SIMCONNECT_PERIOD_SECOND, SIMCONNECT_DATA_REQUEST_FLAG_CHANGED);
if (hr != S_OK)
{
this->triggerAutoTraceSendId();
CLogMessage(this).error("FSX plugin: SimConnect_RequestDataOnSimObject failed");
return;
}
// Request the data from SB only when its changed and only ONCE so we don't have to run a 1sec event to get/set this info ;)
hr += SimConnect_RequestClientData(m_hSimConnect, ClientAreaSquawkBox, CSimConnectDefinitions::RequestSbData,
CSimConnectDefinitions::DataClientAreaSb, SIMCONNECT_CLIENT_DATA_PERIOD_SECOND, SIMCONNECT_CLIENT_DATA_REQUEST_FLAG_CHANGED);
if (hr != S_OK)
{
this->triggerAutoTraceSendId();
CLogMessage(this).error("FSX plugin: SimConnect_RequestClientData failed");
return;
}
this->emitSimulatorCombinedStatus(); // force sending status
}
void CSimulatorFsxCommon::onSimStopped()
{
// stopping events in FSX: Load menu, weather and season
CLogMessage(this).info("Simulator stopped: %1") << this->getSimulatorDetails();
const SimulatorStatus oldStatus = this->getSimulatorStatus();
m_simSimulating = false;
m_simulatingChangedTs = QDateTime::currentMSecsSinceEpoch();
this->emitSimulatorCombinedStatus(oldStatus);
}
void CSimulatorFsxCommon::onSimFrame()
{
if (m_updateRemoteAircraftInProgress) { return; }
QPointer<CSimulatorFsxCommon> myself(this);
QTimer::singleShot(0, this, [ = ]
{
// run decoupled from simconnect event queue
if (!myself) { return; }
myself->updateRemoteAircraft();
});
}
void CSimulatorFsxCommon::onSimExit()
{
CLogMessage(this).info("Simulator exit: %1") << this->getSimulatorDetails();
// reset complete state, we are going down
m_simulatingChangedTs = QDateTime::currentMSecsSinceEpoch();
this->safeKillTimer();
// if called from dispatch function, avoid that SimConnectProc disconnects itself while in SimConnectProc
QPointer<CSimulatorFsxCommon> myself(this);
QTimer::singleShot(0, this, [ = ]
{
if (myself.isNull()) { return; }
myself->disconnectFrom();
});
}
SIMCONNECT_DATA_REQUEST_ID CSimulatorFsxCommon::obtainRequestIdForSimObjAircraft()
{
const SIMCONNECT_DATA_REQUEST_ID id = m_requestIdSimObjAircraft++;
if (id > RequestSimObjAircraftEnd) { m_requestIdSimObjAircraft = RequestSimObjAircraftStart; }
return id;
}
SIMCONNECT_DATA_REQUEST_ID CSimulatorFsxCommon::obtainRequestIdForSimObjTerrainProbe()
{
const SIMCONNECT_DATA_REQUEST_ID id = m_requestIdSimObjTerrainProbe++;
if (id > RequestSimObjTerrainProbeEnd) { m_requestIdSimObjTerrainProbe = RequestSimObjTerrainProbeStart; }
return id;
}
bool CSimulatorFsxCommon::isValidSimObjectNotPendingRemoved(const CSimConnectObject &simObject) const
{
if (!simObject.hasValidRequestAndObjectId()) { return false; }
if (simObject.isPendingRemoved()) { return false; }
if (!m_simConnectObjects.contains(simObject.getCallsign())) { return false; } // removed in meantime
return true;
}
bool CSimulatorFsxCommon::triggerAutoTraceSendId()
{
if (m_traceSendId) { return false; } // no need
if (this->isShuttingDownOrDisconnected()) { return false; }
const qint64 ts = QDateTime::currentMSecsSinceEpoch();
m_traceAutoTs = ts; // auto trace on
CLogMessage(this).warning("Triggered auto trace until %1") << ts;
const QPointer<CSimulatorFsxCommon> myself(this);
QTimer::singleShot(AutoTraceOffsetMs * 1.2, this, [ = ]
{
// triggered by mself (ts check), otherwise ignore
if (myself.isNull()) { return; }
if (myself->m_traceAutoTs == ts)
{
CLogMessage(this).warning("Auto trace id off");
myself->m_traceAutoTs = -1;
}
});
return true;
}
void CSimulatorFsxCommon::updateOwnAircraftFromSimulator(const DataDefinitionOwnAircraft &simulatorOwnAircraft)
{
CSimulatedAircraft myAircraft(getOwnAircraft());
CCoordinateGeodetic position;
position.setLatitude(CLatitude(simulatorOwnAircraft.latitude, CAngleUnit::deg()));
position.setLongitude(CLongitude(simulatorOwnAircraft.longitude, CAngleUnit::deg()));
if (simulatorOwnAircraft.pitch < -90.0 || simulatorOwnAircraft.pitch >= 90.0)
{
CLogMessage(this).warning("FSX: Pitch value (own aircraft) out of limits: %1") << simulatorOwnAircraft.pitch;
}
CAircraftSituation aircraftSituation;
aircraftSituation.setPosition(position);
// MSFS has inverted pitch and bank angles
aircraftSituation.setPitch(CAngle(-simulatorOwnAircraft.pitch, CAngleUnit::deg()));
aircraftSituation.setBank(CAngle(-simulatorOwnAircraft.bank, CAngleUnit::deg()));
aircraftSituation.setHeading(CHeading(simulatorOwnAircraft.trueHeading, CHeading::True, CAngleUnit::deg()));
aircraftSituation.setGroundSpeed(CSpeed(simulatorOwnAircraft.velocity, CSpeedUnit::kts()));
aircraftSituation.setGroundElevation(CAltitude(simulatorOwnAircraft.elevation, CAltitude::MeanSeaLevel, CLengthUnit::ft()), CAircraftSituation::FromProvider);
aircraftSituation.setAltitude(CAltitude(simulatorOwnAircraft.altitude, CAltitude::MeanSeaLevel, CLengthUnit::ft()));
aircraftSituation.setPressureAltitude(CAltitude(simulatorOwnAircraft.pressureAltitude, CAltitude::MeanSeaLevel, CAltitude::PressureAltitude, CLengthUnit::m()));
// set on ground also in situation for consistency and future usage
// it is duplicated in parts
aircraftSituation.setOnGround(simulatorOwnAircraft.simOnGround ? CAircraftSituation::OnGround : CAircraftSituation::NotOnGround, CAircraftSituation::OutOnGroundOwnAircraft);
const CAircraftLights lights(simulatorOwnAircraft.lightStrobe, simulatorOwnAircraft.lightLanding, simulatorOwnAircraft.lightTaxi,
simulatorOwnAircraft.lightBeacon, simulatorOwnAircraft.lightNav, simulatorOwnAircraft.lightLogo);
CAircraftEngineList engines;
const QList<bool> helperList
{
simulatorOwnAircraft.engine1Combustion != 0, simulatorOwnAircraft.engine2Combustion != 0,
simulatorOwnAircraft.engine3Combustion != 0, simulatorOwnAircraft.engine4Combustion != 0
};
for (int index = 0; index < simulatorOwnAircraft.numberOfEngines; ++index)
{
engines.push_back(CAircraftEngine(index + 1, helperList.at(index)));
}
const CAircraftParts parts(lights, simulatorOwnAircraft.gearHandlePosition,
simulatorOwnAircraft.flapsHandlePosition * 100,
simulatorOwnAircraft.spoilersHandlePosition,
engines,
simulatorOwnAircraft.simOnGround);
// set values
this->updateOwnSituation(aircraftSituation);
this->updateOwnParts(parts);
// When I change cockpit values in the sim (from GUI to simulator, not originating from simulator)
// it takes a little while before these values are set in the simulator.
// To avoid jitters, I wait some update cylces to stabilize the values
if (m_skipCockpitUpdateCycles < 1)
{
// defaults
CComSystem com1(myAircraft.getCom1System()); // set defaults
CComSystem com2(myAircraft.getCom2System());
CTransponder transponder(myAircraft.getTransponder());
// updates
com1.setFrequencyActive(CFrequency(simulatorOwnAircraft.com1ActiveMHz, CFrequencyUnit::MHz()));
com1.setFrequencyStandby(CFrequency(simulatorOwnAircraft.com1StandbyMHz, CFrequencyUnit::MHz()));
const bool changedCom1 = myAircraft.getCom1System() != com1;
m_simCom1 = com1;
com2.setFrequencyActive(CFrequency(simulatorOwnAircraft.com2ActiveMHz, CFrequencyUnit::MHz()));
com2.setFrequencyStandby(CFrequency(simulatorOwnAircraft.com2StandbyMHz, CFrequencyUnit::MHz()));
const bool changedCom2 = myAircraft.getCom2System() != com2;
m_simCom2 = com2;
transponder.setTransponderCode(simulatorOwnAircraft.transponderCode);
const bool changedXpr = (myAircraft.getTransponderCode() != transponder.getTransponderCode());
if (changedCom1 || changedCom2 || changedXpr)
{
this->updateCockpit(com1, com2, transponder, identifier());
}
}
else
{
--m_skipCockpitUpdateCycles;
}
if (m_isWeatherActivated)
{
const auto currentPosition = CCoordinateGeodetic { aircraftSituation.latitude(), aircraftSituation.longitude(), {0} };
if (CWeatherScenario::isRealWeatherScenario(m_weatherScenarioSettings.get()) &&
calculateGreatCircleDistance(m_lastWeatherPosition, currentPosition).value(CLengthUnit::mi()) > 20)
{
m_lastWeatherPosition = currentPosition;
const auto weatherGrid = CWeatherGrid { { "GLOB", currentPosition } };
requestWeatherGrid(weatherGrid, { this, &CSimulatorFsxCommon::injectWeatherGrid });
}
}
}
void CSimulatorFsxCommon::triggerUpdateRemoteAircraftFromSimulator(const CSimConnectObject &simObject, const DataDefinitionPosData &remoteAircraftData)
{
if (this->isShuttingDownOrDisconnected()) { return; }
QPointer<CSimulatorFsxCommon> myself(this);
QTimer::singleShot(0, this, [ = ]
{
if (!myself) { return; }
myself->updateRemoteAircraftFromSimulator(simObject, remoteAircraftData);
});
}
void CSimulatorFsxCommon::triggerUpdateRemoteAircraftFromSimulator(const CSimConnectObject &simObject, const DataDefinitionRemoteAircraftModel &remoteAircraftModel)
{
if (this->isShuttingDownOrDisconnected()) { return; }
QPointer<CSimulatorFsxCommon> myself(this);
QTimer::singleShot(0, this, [ = ]
{
if (!myself) { return; }
myself->updateRemoteAircraftFromSimulator(simObject, remoteAircraftModel);
});
}
void CSimulatorFsxCommon::updateRemoteAircraftFromSimulator(const CSimConnectObject &simObject, const DataDefinitionPosData &remoteAircraftData)
{
if (this->isShuttingDownOrDisconnected()) { return; }
// Near ground we use faster updates
const CCallsign cs(simObject.getCallsign());
const CAircraftSituation lastSituation = m_lastSentSituations[cs];
const bool moving = lastSituation.isMoving();
if (moving && remoteAircraftData.aboveGroundFt() <= 100.0)
{
// switch to fast updates
if (simObject.getSimDataPeriod() != SIMCONNECT_PERIOD_VISUAL_FRAME)
{
this->requestPositionDataForSimObject(simObject, SIMCONNECT_PERIOD_VISUAL_FRAME);
}
}
else
{
// switch to slow updates
if (simObject.getSimDataPeriod() != SIMCONNECT_PERIOD_SECOND)
{
this->requestPositionDataForSimObject(simObject, SIMCONNECT_PERIOD_SECOND);
}
}
// CElevationPlane: deg, deg, feet
// we only remember near ground
if (remoteAircraftData.aboveGroundFt() < 250)
{
CElevationPlane elevation(remoteAircraftData.latitudeDeg, remoteAircraftData.longitudeDeg, remoteAircraftData.elevationFt);
elevation.setSinglePointRadius();
this->rememberElevationAndCG(cs, simObject.getAircraftModelString(), elevation, CLength(remoteAircraftData.cgToGroundFt, CLengthUnit::ft()));
}
}
void CSimulatorFsxCommon::updateRemoteAircraftFromSimulator(const CSimConnectObject &simObject, const DataDefinitionRemoteAircraftModel &remoteAircraftModel)
{
const CCallsign cs(simObject.getCallsign());
if (!m_simConnectObjects.contains(cs)) { return; } // no longer existing
CSimConnectObject &so = m_simConnectObjects[cs];
if (so.isPendingRemoved()) { return; }
const QString modelString(remoteAircraftModel.title);
const CLength cg(remoteAircraftModel.cgToGroundFt, CLengthUnit::ft());
so.setAircraftCG(cg);
so.setAircraftModelString(modelString);
this->insertCG(cg, modelString, cs); // env. provider
this->updateCGAndModelString(cs, cg, modelString); // remote aircraft provider
}
void CSimulatorFsxCommon::updateProbeFromSimulator(const CCallsign &callsign, const DataDefinitionPosData &remoteAircraftData)
{
const CElevationPlane elevation(remoteAircraftData.latitudeDeg, remoteAircraftData.longitudeDeg, remoteAircraftData.elevationFt, CElevationPlane::singlePointRadius());
this->callbackReceivedRequestedElevation(elevation, callsign);
}
void CSimulatorFsxCommon::updateOwnAircraftFromSimulator(const DataDefinitionClientAreaSb &sbDataArea)
{
CTransponder::TransponderMode newMode = CTransponder::StateIdent;
if (!sbDataArea.isIdent())
{
newMode = sbDataArea.isStandby() ? CTransponder::StateStandby : CTransponder::ModeC;
}
const CSimulatedAircraft myAircraft(this->getOwnAircraft());
const bool changed = (myAircraft.getTransponderMode() != newMode);
if (!changed) { return; }
CTransponder xpdr = myAircraft.getTransponder();
xpdr.setTransponderMode(newMode);
this->updateCockpit(myAircraft.getCom1System(), myAircraft.getCom2System(), xpdr, this->identifier());
}
bool CSimulatorFsxCommon::simulatorReportedObjectAdded(DWORD objectId)
{
if (this->isShuttingDownOrDisconnected()) { return true; } // pretend everything is fine
const CSimConnectObject simObject = m_simConnectObjects.getSimObjectForObjectId(objectId);
const CCallsign callsign(simObject.getCallsign());
if (!simObject.hasValidRequestAndObjectId() || callsign.isEmpty()) { return false; }
// we know the object has been created. But it can happen it is directly removed afterwards
const CSimulatedAircraft verifyAircraft(simObject.getAircraft());
const QPointer<CSimulatorFsxCommon> myself(this);
QTimer::singleShot(1000, this, [ = ]
{
// also triggers new add if required
if (myself.isNull()) { return; }
this->verifyAddedRemoteAircraft(verifyAircraft);
});
return true;
}
bool CSimulatorFsxCommon::simulatorReportedProbeAdded(DWORD objectId)
{
if (this->isShuttingDownOrDisconnected()) { return true; } // pretend everything is fine
const CSimConnectObject simObject = m_simConnectProbes.markObjectAsAdded(objectId);
const bool valid(simObject.hasValidRequestAndObjectId() && simObject.isConfirmedAdded());
return valid;
}
void CSimulatorFsxCommon::verifyAddedRemoteAircraft(const CSimulatedAircraft &remoteAircraftIn)
{
if (this->isShuttingDownOrDisconnected()) { return; }
CStatusMessage msg;
CSimulatedAircraft remoteAircraft = remoteAircraftIn;
const CCallsign callsign(remoteAircraft.getCallsign());
do
{
// no callsign
if (callsign.isEmpty())
{
msg = CLogMessage(this).error("Cannot confirm AI object, empty callsign");
break;
}
// removed in meantime
const bool aircraftStillInRange = this->isAircraftInRange(callsign);
if (!m_simConnectObjects.contains(callsign))
{
if (aircraftStillInRange)
{
msg = CLogMessage(this).warning("Callsign '%1' removed in meantime, but still in range") << callsign.toQString();
}
else
{
this->removeFromAddPendingAndAddAgainAircraft(callsign);
msg = CLogMessage(this).info("Callsign '%1' removed in meantime and no longer in range") << callsign.toQString();
}
break;
}
CSimConnectObject &simObject = m_simConnectObjects[callsign];
remoteAircraft = simObject.getAircraft(); // update, if something has changed
if (!simObject.hasValidRequestAndObjectId() || simObject.isPendingRemoved())
{
msg = CStatusMessage(this).warning("Object for callsign '%1'/id: %2 removed in meantime/invalid") << callsign.toQString() << simObject.getObjectId();
break;
}
Q_ASSERT_X(simObject.isPendingAdded(), Q_FUNC_INFO, "already confirmed, this should be the only place");
simObject.setConfirmedAdded(true);
// P3D also has SimConnect_AIReleaseControlEx which also allows to destroy the aircraft
const DWORD objectId = simObject.getObjectId();
const SIMCONNECT_DATA_REQUEST_ID requestId = simObject.getRequestId(CSimConnectDefinitions::SimObjectMisc);
HRESULT hr = SimConnect_AIReleaseControl(m_hSimConnect, objectId, requestId);
hr += SimConnect_TransmitClientEvent(m_hSimConnect, objectId, EventFreezeLat, 1, SIMCONNECT_GROUP_PRIORITY_HIGHEST, SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY);
hr += SimConnect_TransmitClientEvent(m_hSimConnect, objectId, EventFreezeAlt, 1, SIMCONNECT_GROUP_PRIORITY_HIGHEST, SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY);
hr += SimConnect_TransmitClientEvent(m_hSimConnect, objectId, EventFreezeAtt, 1, SIMCONNECT_GROUP_PRIORITY_HIGHEST, SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY);
if (hr != S_OK)
{
msg = CStatusMessage(this).error("Cannot confirm object %1, cs: '%2' model: '%3'") << objectId << remoteAircraft.getCallsignAsString() << remoteAircraft.getModelString();
break;
}
// request data on object
this->requestPositionDataForSimObject(simObject);
this->requestLightsForSimObject(simObject);
this->requestModelInfoForSimObject(simObject);
this->removeFromAddPendingAndAddAgainAircraft(callsign); // no longer try to add
const bool updated = this->updateAircraftRendered(callsign, true);
if (updated)
{
emit aircraftRenderingChanged(simObject.getAircraft());
static const QString debugMsg("CS: '%1' model: '%2' verified, request/object id: %3 %4");
if (this->showDebugLogMessage()) { this->debugLogMessage(Q_FUNC_INFO, debugMsg.arg(callsign.toQString(), remoteAircraft.getModelString()).arg(requestId).arg(objectId)); }
}
else
{
CLogMessage(this).warning("Verified aircraft '%1' model '%2', request/object id: %3 %4 already rendered") <<
callsign.asString() << remoteAircraft.getModelString() << objectId;
}
}
while (false);
// log errors and emit signal
if (!msg.isEmpty())
{
CLogMessage::preformatted(msg);
emit this->physicallyAddingRemoteModelFailed(CSimulatedAircraft(), msg);
}
// trigger new adding from pending if any
if (!m_addPendingAircraft.isEmpty())
{
this->addPendingAircraftAfterAdded();
}
}
void CSimulatorFsxCommon::addPendingAircraftByTimer()
{
this->addPendingAircraft(AddByTimer);
}
void CSimulatorFsxCommon::addPendingAircraftAfterAdded()
{
this->addPendingAircraft(AddAfterAdded);
}
void CSimulatorFsxCommon::addPendingAircraft(AircraftAddMode mode)
{
if (m_addPendingAircraft.isEmpty()) { return; }
const CCallsignSet aircraftCallsignsInRange(getAircraftInRangeCallsigns());
CSimulatedAircraftList toBeAddedAircraft;
CCallsignSet toBeRemovedCallsigns;
for (const CSimulatedAircraft &aircraft : as_const(m_addPendingAircraft))
{
Q_ASSERT_X(!aircraft.getCallsign().isEmpty(), Q_FUNC_INFO, "missing callsign");
if (aircraftCallsignsInRange.contains(aircraft.getCallsign()))
{
toBeAddedAircraft.push_back(aircraft);
}
else
{
toBeRemovedCallsigns.push_back(aircraft.getCallsign());
}
}
// no longer required to be added
m_addPendingAircraft.removeByCallsigns(toBeRemovedCallsigns);
m_addAgainAircraftWhenRemoved.removeByCallsigns(toBeRemovedCallsigns);
// add aircraft, but "non blocking"
if (!toBeAddedAircraft.isEmpty())
{
const CSimulatedAircraft nextPendingAircraft(m_addPendingAircraft.front());
const QPointer <CSimulatorFsxCommon> myself(this);
QTimer::singleShot(100, this, [ = ]
{
if (myself.isNull()) { return; }
this->physicallyAddRemoteAircraftImpl(nextPendingAircraft, mode);
});
}
}
void CSimulatorFsxCommon::removeFromAddPendingAndAddAgainAircraft(const CCallsign &callsign)
{
if (callsign.isEmpty()) { return; }
m_addPendingAircraft.removeByCallsign(callsign);
m_addAgainAircraftWhenRemoved.removeByCallsign(callsign);
}
bool CSimulatorFsxCommon::simulatorReportedObjectRemoved(DWORD objectID)
{
if (this->isShuttingDownOrDisconnected()) { return false; }
const CSimConnectObject simObject = m_simConnectObjects.getSimObjectForObjectId(objectID);
if (!simObject.hasValidRequestAndObjectId()) { return false; } // object id from somewhere else
const CCallsign callsign(simObject.getCallsign());
Q_ASSERT_X(!callsign.isEmpty(), Q_FUNC_INFO, "Missing callsign for removed object");
if (simObject.isPendingRemoved())
{
// good case, object has been removed
// we can remove the simulator object
}
else
{
// object was removed, but removal was not requested by us
// this means we are out of the reality bubble or something else went wrong
// Possible reasons:
// 1) out of reality bubble, because we move to another airport or other reasons
// 2) wrong position (in ground etc.)
// 3) Simulator not running (ie in stopped mode)
CStatusMessage msg;
if (!simObject.getAircraftModelString().isEmpty())
{
const CInterpolationAndRenderingSetupPerCallsign setup = this->getInterpolationSetupPerCallsignOrDefault(callsign);
m_addPendingAircraft.replaceOrAddByCallsign(simObject.getAircraft());
msg = CLogMessage(this).warning("Aircraft removed, '%1' '%2' object id '%3' out of reality bubble or other reason. Interpolator: '%4'")
<< callsign.toQString() << simObject.getAircraftModelString()
<< objectID << simObject.getInterpolatorInfo(setup.getInterpolatorMode());
}
else
{
msg = CLogMessage(this).warning("Removed '%1' from simulator, but was not initiated by us: %1 '%2' object id %3") << callsign.toQString() << simObject.getAircraftModelString() << objectID;
}
emit this->driverMessages(msg);
}
// in all cases we remove the object
const int c = m_simConnectObjects.remove(callsign);
const bool removedAny = (c > 0);
const bool updated = this->updateAircraftRendered(simObject.getCallsign(), false);
if (updated)
{
emit this->aircraftRenderingChanged(simObject.getAircraft());
}
// models we have to add again after removing
if (m_addAgainAircraftWhenRemoved.containsCallsign(callsign))
{
const CSimulatedAircraft aircraftAddAgain = m_addAgainAircraftWhenRemoved.findFirstByCallsign(callsign);
m_addAgainAircraftWhenRemoved.removeByCallsign(callsign);
QPointer<CSimulatorFsxCommon> myself(this);
QTimer::singleShot(2500, this, [ = ]
{
if (myself.isNull()) { return; }
myself->physicallyAddRemoteAircraftImpl(aircraftAddAgain, AddedAfterRemoved);
});
}
return removedAny;
}
bool CSimulatorFsxCommon::setSimConnectObjectId(DWORD requestId, DWORD objectId)
{
return m_simConnectObjects.setSimConnectObjectIdForRequestId(requestId, objectId);
}
bool CSimulatorFsxCommon::setSimConnectProbeId(DWORD requestId, DWORD objectId)
{
return m_simConnectProbes.setSimConnectObjectIdForRequestId(requestId, objectId);
}
bool CSimulatorFsxCommon::setCurrentLights(const CCallsign &callsign, const CAircraftLights &lights)
{
if (!m_simConnectObjects.contains(callsign)) { return false; }
m_simConnectObjects[callsign].setCurrentLightsInSimulator(lights);
return true;
}
bool CSimulatorFsxCommon::setLightsAsSent(const CCallsign &callsign, const CAircraftLights &lights)
{
if (!m_simConnectObjects.contains(callsign)) { return false; }
m_simConnectObjects[callsign].setLightsAsSent(lights);
return true;
}
void CSimulatorFsxCommon::timerEvent(QTimerEvent *event)
{
Q_UNUSED(event);
if (this->isShuttingDown()) { return; }
this->dispatch();
}
bool CSimulatorFsxCommon::parseDetails(const CSimpleCommandParser &parser)
{
// .driver sendid on|off
if (parser.matchesPart(1, "sendid") && parser.hasPart(2))
{
const bool trace = parser.toBool(2);
this->setTraceSendId(trace);
CLogMessage(this, CLogCategory::cmdLine()).info("Tracing %1 driver sendIds is '%2'") << this->getSimulatorPluginInfo().getIdentifier() << boolToOnOff(trace);
return true;
}
return CSimulatorFsCommon::parseDetails(parser);
}
void CSimulatorFsxCommon::registerHelp()
{
if (CSimpleCommandParser::registered("BlackSimPlugin::CSimulatorFsxCommon::CSimulatorFsxCommon")) { return; }
CSimpleCommandParser::registerCommand({".drv", "alias: .driver .plugin"});
CSimpleCommandParser::registerCommand({".drv sendid on|off", "Trace simConnect sendId on|off"});
}
CCallsign CSimulatorFsxCommon::getCallsignForPendingProbeRequests(DWORD requestId, bool remove)
{
const CCallsign cs = m_pendingProbeRequests.value(requestId);
if (remove) { m_pendingProbeRequests.remove(requestId); }
return cs;
}
const QString &CSimulatorFsxCommon::modeToString(CSimulatorFsxCommon::AircraftAddMode mode)
{
static const QString e("external call");
static const QString pt("add pending by timer");
static const QString oa("add pending after object added");
static const QString ar("add again after removed");
static const QString dontKnow("???");
switch (mode)
{
case ExternalCall: return e;
case AddByTimer: return pt;
case AddAfterAdded: return oa;
case AddedAfterRemoved: return ar;
default: break;
}
return dontKnow;
}
void CSimulatorFsxCommon::dispatch()
{
// call CSimulatorFsxCommon::SimConnectProc or specialized P3D version
Q_ASSERT_X(m_dispatchProc, Q_FUNC_INFO, "Missing DispatchProc");
// statistics
m_dispatchReceiveIdLast = SIMCONNECT_RECV_ID_NULL;
m_dispatchRequestIdLast = CSimConnectDefinitions::RequestEndMarker;
const qint64 start = QDateTime::currentMSecsSinceEpoch();
// process
const HRESULT hr = SimConnect_CallDispatch(m_hSimConnect, m_dispatchProc, this);
// statistics
const qint64 end = QDateTime::currentMSecsSinceEpoch();
m_dispatchTimeMs = end - start;
if (m_dispatchMaxTimeMs < m_dispatchTimeMs)
{
m_dispatchMaxTimeMs = m_dispatchTimeMs;
m_dispatchReceiveIdMaxTime = m_dispatchReceiveIdLast;
m_dispatchRequestIdMaxTime = m_dispatchRequestIdLast;
}
// error handling
if (hr != S_OK)
{
m_dispatchErrors++;
this->triggerAutoTraceSendId();
if (m_dispatchErrors == 2)
{
// 2nd time, an error / avoid multiple messages
// idea: if it happens once ignore
CLogMessage(this).error("%1: Dispatch error") << this->getSimulatorPluginInfo().getIdentifier();
}
else if (m_dispatchErrors > 5)
{
// this normally happens during a FSX crash or shutdown with simconnect
CLogMessage(this).error("%1: Multiple dispatch errors, disconnecting") << this->getSimulatorPluginInfo().getIdentifier();
this->disconnectFrom();
}
return;
}
m_dispatchErrors = 0;
if (m_useFsuipc && m_fsuipc)
{
CSimulatedAircraft fsuipcAircraft(getOwnAircraft());
//! \fixme split in high / low frequency reads
bool ok = m_fsuipc->read(fsuipcAircraft, true, true, true);
if (ok)
{
// do whatever is required
Q_UNUSED(fsuipcAircraft);
}
}
}
bool CSimulatorFsxCommon::physicallyAddRemoteAircraftImpl(const CSimulatedAircraft &newRemoteAircraft, CSimulatorFsxCommon::AircraftAddMode addMode)
{
const CCallsign callsign(newRemoteAircraft.getCallsign());
// entry checks
Q_ASSERT_X(CThreadUtils::isCurrentThreadObjectThread(this), Q_FUNC_INFO, "thread");
Q_ASSERT_X(!callsign.isEmpty(), Q_FUNC_INFO, "empty callsign");
Q_ASSERT_X(newRemoteAircraft.hasModelString(), Q_FUNC_INFO, "missing model string");
// reset timer
m_addPendingSimObjTimer.start(AddPendingAircraftIntervalMs); // restart
const bool hasPendingAdded = m_simConnectObjects.containsPendingAdded();
bool canAdd = this->isSimulating() && !hasPendingAdded;
Q_ASSERT_X(!hasPendingAdded || m_simConnectObjects.countPendingAdded() < 2, Q_FUNC_INFO, "There must be only 0..1 pending objects");
if (this->showDebugLogMessage())
{
this->debugLogMessage(Q_FUNC_INFO, QString("CS: '%1' mode: '%2' model: '%3'").arg(newRemoteAircraft.getCallsignAsString(), modeToString(addMode), newRemoteAircraft.getModelString()));
this->debugLogMessage(Q_FUNC_INFO, QString("CS: '%1' pending callsigns: '%2', pending objects: '%3'").arg(newRemoteAircraft.getCallsignAsString(), m_addPendingAircraft.getCallsignStrings().join(", "), m_simConnectObjects.getPendingAddedCallsigns().getCallsignStrings().join(", ")));
}
// do we need to remove/add again because something has changed?
if (m_simConnectObjects.contains(callsign))
{
const CSimConnectObject simObject = m_simConnectObjects[callsign];
const QString newModelString(newRemoteAircraft.getModelString());
const QString simObjModelString(simObject.getAircraftModelString());
const bool sameModel = (simObjModelString == newModelString); // compare on string only (other attributes might change such as mode)
// same model, nothing will change, otherwise add again when removed
if (sameModel)
{
if (this->showDebugLogMessage()) { this->debugLogMessage(Q_FUNC_INFO, QString("CS: '%1' re-added same model '%2'").arg(newRemoteAircraft.getCallsignAsString(), newModelString)); }
return true;
}
this->physicallyRemoveRemoteAircraft(newRemoteAircraft.getCallsign());
m_addAgainAircraftWhenRemoved.replaceOrAddByCallsign(newRemoteAircraft);
if (this->showDebugLogMessage()) { this->debugLogMessage(Q_FUNC_INFO, QString("CS: '%1' re-added changed model '%2', will be added again").arg(newRemoteAircraft.getCallsignAsString(), newModelString)); }
return false;
}
// situation check
CAircraftSituation situation(newRemoteAircraft.getSituation());
if (canAdd && situation.isPositionOrAltitudeNull())
{
// invalid position because position or altitude is null
const CAircraftSituationList situations(this->remoteAircraftSituations(callsign));
if (situations.isEmpty())
{
CLogMessage(this).warning("No valid situations for '%1', will be added as pending") << callsign.asString();
canAdd = false;
}
else
{
CLogMessage(this).warning("Invalid aircraft situation for new aircraft '%1', use closest situation") << callsign.asString();
situation = situations.findClosestTimeDistanceAdjusted(QDateTime::currentMSecsSinceEpoch());
Q_ASSERT_X(!situation.isPositionOrAltitudeNull(), Q_FUNC_INFO, "Invalid situation for new aircraft");
}
// still invalid?
const bool invalidSituation = situation.isPositionOrAltitudeNull();
canAdd = invalidSituation;
if (CBuildConfig::isLocalDeveloperDebugBuild())
{
BLACK_VERIFY_X(invalidSituation, Q_FUNC_INFO, "Expect valid situation");
const CStatusMessage sm = CStatusMessage(this).warning("Invalid situation for '%1'") << callsign;
this->clampedLog(callsign, sm);
}
}
// check if we can add, do not add if simulator is stopped or other objects pending
if (!canAdd)
{
m_addPendingAircraft.replaceOrAddByCallsign(newRemoteAircraft);
return false;
}
this->removeFromAddPendingAndAddAgainAircraft(callsign);
// create AI
if (!this->isAircraftInRange(callsign))
{
CLogMessage(this).info("Skipping adding of '%1' since it is no longer in range") << callsign.asString();
return false;
}
// setup
const CInterpolationAndRenderingSetupPerCallsign setup = this->getInterpolationSetupConsolidated(callsign);
const bool sendGround = setup.isSendingGndFlagToSimulator();
// FSX/P3D adding
bool adding = false; // will be added flag
const SIMCONNECT_DATA_REQUEST_ID requestId = this->obtainRequestIdForSimObjAircraft(); // add
const SIMCONNECT_DATA_INITPOSITION initialPosition = CSimulatorFsxCommon::aircraftSituationToFsxPosition(newRemoteAircraft.getSituation(), sendGround);
const QString modelString(newRemoteAircraft.getModelString());
if (this->showDebugLogMessage()) { this->debugLogMessage(Q_FUNC_INFO, QString("CS: '%1' model: '%2' request: %3, init pos: %4").arg(callsign.toQString(), modelString).arg(requestId).arg(fsxPositionToString(initialPosition))); }
const HRESULT hr = SimConnect_AICreateNonATCAircraft(m_hSimConnect, qPrintable(modelString), qPrintable(callsign.toQString().left(12)), initialPosition, requestId);
if (hr != S_OK)
{
const CStatusMessage msg = CStatusMessage(this).error("SimConnect, can not create AI traffic: '%1' '%2'") << callsign.toQString() << modelString;
CLogMessage::preformatted(msg);
emit this->physicallyAddingRemoteModelFailed(newRemoteAircraft, msg);
}
else
{
// we will request a new aircraft by request ID, later we will receive its object id
// so far this object id is -1
const CSimConnectObject simObject = this->insertNewSimConnectObject(newRemoteAircraft, requestId);
if (this->isTracingSendId()) { this->traceSendId(simObject.getObjectId(), Q_FUNC_INFO);}
adding = true;
}
return adding;
}
bool CSimulatorFsxCommon::physicallyAddAITerrainProbe(const ICoordinateGeodetic &coordinate)
{
if (coordinate.isNull()) { return false; }
Q_ASSERT_X(CThreadUtils::isCurrentThreadObjectThread(this), Q_FUNC_INFO, "thread");
// static const QString modelString = this->getDefaultModel().getModelString();
static const QString modelString("OrcaWhale");
static const QString pseudoCallsign("swift pr: %1"); // max 12 chars
const int index = m_simConnectProbes.size() + 1;
const CCallsign cs(pseudoCallsign.arg(index));
const SIMCONNECT_DATA_REQUEST_ID requestId = this->obtainRequestIdForSimObjTerrainProbe(); // add
const SIMCONNECT_DATA_INITPOSITION initialPosition = CSimulatorFsxCommon::coordinateToFsxPosition(coordinate);
const HRESULT hr = SimConnect_AICreateNonATCAircraft(m_hSimConnect, qPrintable(modelString), qPrintable(cs.asString().right(12)), initialPosition, requestId);
// const HRESULT hr = SimConnect_AICreateSimulatedObject(m_hSimConnect, qPrintable(modelString), initialPosition, requestId);
if (this->isTracingSendId()) { this->traceSendId(0, Q_FUNC_INFO, QString("Adding probe, req.id: %1").arg(requestId));}
bool ok = false;
if (hr == S_OK)
{
ok = true;
const CAircraftModel model(modelString, CAircraftModel::TypeTerrainProbe, QStringLiteral("swift terrain probe"), CAircraftIcaoCode::unassignedIcao());
const CAircraftSituation situation(cs, coordinate);
const CSimulatedAircraft pseudoAircraft(cs, model, CUser("123456", "swift", cs), situation);
CSimConnectObject simObject(CSimConnectObject::TerrainProbe);
simObject.setRequestId(requestId);
simObject.setAircraft(pseudoAircraft);
m_simConnectProbes.insert(cs, simObject);
}
else
{
const CStatusMessage msg = CStatusMessage(this).error("SimConnect, can not create terrain AI: '%1'") << requestId;
CLogMessage::preformatted(msg);
}
return ok;
}
bool CSimulatorFsxCommon::physicallyRemoveRemoteAircraft(const CCallsign &callsign)
{
// only remove from sim
Q_ASSERT_X(CThreadUtils::isCurrentThreadObjectThread(this), Q_FUNC_INFO, "wrong thread");
if (callsign.isEmpty()) { return false; } // can happen if an object is not an aircraft
// clean up anyway
this->removeFromAddPendingAndAddAgainAircraft(callsign);
// really remove from simulator
if (!m_simConnectObjects.contains(callsign)) { return false; } // already fully removed or not yet added
CSimConnectObject &simObject = m_simConnectObjects[callsign];
if (simObject.isPendingRemoved()) { return true; }
// check for pending objects
m_addPendingAircraft.removeByCallsign(callsign); // just in case still in list of pending aircraft
const bool pendingAdded = simObject.isPendingAdded(); // already added in simulator, but not yet confirmed
const bool stillWaitingForLights = !simObject.hasCurrentLightsInSimulator();
if (pendingAdded || stillWaitingForLights)
{
// problem: we try to delete an aircraft just requested to be added
// best solution so far, call remove again with a delay
CLogMessage(this).warning("Object: %1 pending added: %2 / lights: %3 about to be removed")
<< simObject.toQString() << boolToYesNo(pendingAdded) << boolToYesNo(stillWaitingForLights);
simObject.fakeCurrentLightsInSimulator(); // next time looks like we have lights
QPointer<CSimulatorFsxCommon> myself(this);
QTimer::singleShot(2000, this, [ = ]
{
if (myself.isNull()) { return; }
myself->physicallyRemoveRemoteAircraft(callsign);
});
return false; // not yet deleted
}
// no more data from simulator
this->stopRequestingDataForSimObject(simObject);
// mark as removed
simObject.setPendingRemoved(true);
if (this->showDebugLogMessage()) { this->debugLogMessage(Q_FUNC_INFO, QString("CS: '%1' request/object id: %2/%3").arg(callsign.toQString()).arg(simObject.getRequestId()).arg(simObject.getObjectId())); }
// call in SIM
const SIMCONNECT_DATA_REQUEST_ID requestId = simObject.getRequestId(CSimConnectDefinitions::SimObjectRemove);
const HRESULT result = SimConnect_AIRemoveObject(m_hSimConnect, static_cast<SIMCONNECT_OBJECT_ID>(simObject.getObjectId()), requestId);
if (result == S_OK)
{
if (this->isTracingSendId()) { this->traceSendId(simObject.getObjectId(), Q_FUNC_INFO);}
}
else
{
CLogMessage(this).warning("Removing aircraft '%1' from simulator failed") << callsign.asString();
}
// mark in provider
const bool updated = this->updateAircraftRendered(callsign, false);
if (updated)
{
CSimulatedAircraft aircraft(simObject.getAircraft());
aircraft.setRendered(false);
emit this->aircraftRenderingChanged(aircraft);
}
// cleanup function, actually this should not be needed
const QPointer<CSimulatorFsxCommon> myself(this);
QTimer::singleShot(100, this, [ = ]
{
if (myself.isNull()) { return; }
CSimulatorFsxCommon::physicallyRemoveAircraftNotInProvider();
});
// bye
return true;
}
int CSimulatorFsxCommon::physicallyRemoveAllRemoteAircraft()
{
// make sure they are not added again
// cleaning here is somewhat redundant, but double checks
this->resetHighlighting();
m_addPendingAircraft.clear();
m_addAgainAircraftWhenRemoved.clear();
// remove one by one
int r = 0;
const CCallsignSet callsigns = m_simConnectObjects.getAllCallsigns();
for (const CCallsign &cs : callsigns)
{
if (this->physicallyRemoveRemoteAircraft(cs)) { r++; }
}
return r;
}
HRESULT CSimulatorFsxCommon::initEvents()
{
HRESULT hr = S_OK;
// System events, see http://msdn.microsoft.com/en-us/library/cc526983.aspx#SimConnect_SubscribeToSystemEvent
hr += SimConnect_SubscribeToSystemEvent(m_hSimConnect, SystemEventSimStatus, "Sim");
hr += SimConnect_SubscribeToSystemEvent(m_hSimConnect, SystemEventObjectAdded, "ObjectAdded");
hr += SimConnect_SubscribeToSystemEvent(m_hSimConnect, SystemEventObjectRemoved, "ObjectRemoved");
hr += SimConnect_SubscribeToSystemEvent(m_hSimConnect, SystemEventFrame, "Frame");
hr += SimConnect_SubscribeToSystemEvent(m_hSimConnect, SystemEventPause, "Pause");
hr += SimConnect_SubscribeToSystemEvent(m_hSimConnect, SystemEventFlightLoaded, "FlightLoaded");
if (hr != S_OK)
{
CLogMessage(this).error("FSX plugin error: %1") << "SimConnect_SubscribeToSystemEvent failed";
return hr;
}
// Mapped events, see event ids here:
// http://msdn.microsoft.com/en-us/library/cc526980.aspx
// http://www.prepar3d.com/SDKv2/LearningCenter/utilities/variables/event_ids.html
hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventPauseToggle, "PAUSE_TOGGLE");
hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, SystemEventSlewToggle, "SLEW_TOGGLE");
hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventFreezeLat, "FREEZE_LATITUDE_LONGITUDE_SET");
hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventFreezeAlt, "FREEZE_ALTITUDE_SET");
hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventFreezeAtt, "FREEZE_ATTITUDE_SET");
hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventSetCom1Active, "COM_RADIO_SET");
hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventSetCom1Standby, "COM_STBY_RADIO_SET");
hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventSetCom2Active, "COM2_RADIO_SET");
hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventSetCom2Standby, "COM2_STBY_RADIO_SET");
hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventSetTransponderCode, "XPNDR_SET");
hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventSetTimeZuluYear, "ZULU_YEAR_SET");
hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventSetTimeZuluDay, "ZULU_DAY_SET");
hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventSetTimeZuluHours, "ZULU_HOURS_SET");
hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventSetTimeZuluMinutes, "ZULU_MINUTES_SET");
hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventLandingLightsOff, "LANDING_LIGHTS_OFF");
hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventLandinglightsOn, "LANDING_LIGHTS_ON");
hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventLandingLightsSet, "LANDING_LIGHTS_SET");
hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventLandingLightsToggle, "LANDING_LIGHTS_TOGGLE");
hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventPanelLightsOff, "PANEL_LIGHTS_OFF");
hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventPanelLightsOn, "PANEL_LIGHTS_ON");
hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventPanelLightsSet, "PANEL_LIGHTS_SET");
hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventStrobesOff, "STROBES_OFF");
hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventStrobesOn, "STROBES_ON");
hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventStrobesSet, "STROBES_SET");
hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventStrobesToggle, "STROBES_TOGGLE");
hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventToggleBeaconLights, "TOGGLE_BEACON_LIGHTS");
hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventToggleCabinLights, "TOGGLE_CABIN_LIGHTS");
hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventToggleLogoLights, "TOGGLE_LOGO_LIGHTS");
hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventToggleNavLights, "TOGGLE_NAV_LIGHTS");
hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventToggleRecognitionLights, "TOGGLE_RECOGNITION_LIGHTS");
hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventToggleTaxiLights, "TOGGLE_TAXI_LIGHTS");
hr += SimConnect_MapClientEventToSimEvent(m_hSimConnect, EventToggleWingLights, "TOGGLE_WING_LIGHTS");
if (hr != S_OK)
{
CLogMessage(this).error("FSX plugin error: %1") << "SimConnect_MapClientEventToSimEvent failed";
return hr;
}
// facility
SIMCONNECT_DATA_REQUEST_ID requestId = static_cast<SIMCONNECT_DATA_REQUEST_ID>(CSimConnectDefinitions::RequestFacility);
hr += SimConnect_SubscribeToFacilities(m_hSimConnect, SIMCONNECT_FACILITY_LIST_TYPE_AIRPORT, requestId);
if (hr != S_OK)
{
CLogMessage(this).error("FSX plugin error: %1") << "SimConnect_SubscribeToFacilities failed";
return hr;
}
return hr;
}
HRESULT CSimulatorFsxCommon::initDataDefinitionsWhenConnected()
{
return CSimConnectDefinitions::initDataDefinitionsWhenConnected(m_hSimConnect);
}
HRESULT CSimulatorFsxCommon::initWhenConnected()
{
// called when connected
HRESULT hr = this->initEvents();
if (hr != S_OK)
{
CLogMessage(this).error("FSX plugin: initEvents failed");
return hr;
}
// init data definitions and SB data area
hr += this->initDataDefinitionsWhenConnected();
if (hr != S_OK)
{
CLogMessage(this).error("FSX plugin: initDataDefinitionsWhenConnected failed");
return hr;
}
return hr;
}
void CSimulatorFsxCommon::updateRemoteAircraft()
{
static_assert(sizeof(DataDefinitionRemoteAircraftPartsWithoutLights) == sizeof(double) * 10, "DataDefinitionRemoteAircraftPartsWithoutLights has an incorrect size.");
Q_ASSERT_X(CThreadUtils::isCurrentThreadObjectThread(this), Q_FUNC_INFO, "thread");
// Freeze interpolation while paused
if (this->isPaused() && m_pausedSimFreezesInterpolation) { return; }
// nothing to do, reset request id and exit
const int remoteAircraftNo = this->getAircraftInRangeCount();
if (remoteAircraftNo < 1) { m_statsUpdateAircraftRuns = 0; return; }
// values used for position and parts
const qint64 currentTimestamp = QDateTime::currentMSecsSinceEpoch();
if (this->isUpdateAircraftLimitedWithStats(currentTimestamp)) { return; }
m_updateRemoteAircraftInProgress = true;
// interpolation for all remote aircraft
const QList<CSimConnectObject> simObjects(m_simConnectObjects.values());
int simObjectNumber = 0;
for (const CSimConnectObject &simObject : simObjects)
{
// happening if aircraft is not yet added to simulator or to be deleted
if (simObject.isPendingAdded()) { continue; }
if (simObject.isPendingRemoved()) { continue; }
if (!simObject.hasCurrentLightsInSimulator()) { continue; } // wait until we have light state
const CCallsign callsign(simObject.getCallsign());
Q_ASSERT_X(!callsign.isEmpty(), Q_FUNC_INFO, "missing callsign");
Q_ASSERT_X(simObject.hasValidRequestAndObjectId(), Q_FUNC_INFO, "Missing ids");
const DWORD objectId = simObject.getObjectId();
// setup
const CInterpolationAndRenderingSetupPerCallsign setup = this->getInterpolationSetupConsolidated(callsign);
const bool sendGround = setup.isSendingGndFlagToSimulator();
// Interpolated situation
const CInterpolationResult result = simObject.getInterpolation(currentTimestamp, setup, simObjectNumber++);
if (result.getInterpolationStatus().hasValidSituation())
{
// update situation
if (!this->isEqualLastSent(result))
{
SIMCONNECT_DATA_INITPOSITION position = this->aircraftSituationToFsxPosition(result, sendGround);
const HRESULT hr = SimConnect_SetDataOnSimObject(m_hSimConnect, CSimConnectDefinitions::DataRemoteAircraftSetPosition,
static_cast<SIMCONNECT_OBJECT_ID>(objectId), 0, 0,
sizeof(SIMCONNECT_DATA_INITPOSITION), &position);
if (hr == S_OK)
{
this->rememberLastSent(result); // remember
if (this->isTracingSendId()) { this->traceSendId(objectId, Q_FUNC_INFO, simObject.toQString()); }
this->removedClampedLog(callsign);
}
else
{
CLogMessage(this).warning("Failed so set position on SimObject '%1' callsign: '%2'") << objectId << callsign;
}
}
}
else
{
static const QString so("SimObject id: %1");
const QString msg = this->getInvalidSituationLogMessage(callsign, result.getInterpolationStatus(), so.arg(objectId));
const CStatusMessage sm(this, CStatusMessage::SeverityWarning, msg);
this->clampedLog(callsign, sm);
}
// Interpolated parts
this->updateRemoteAircraftParts(simObject, result);
} // all callsigns
// stats
this->setStatsRemoteAircraftUpdate(currentTimestamp);
}
bool CSimulatorFsxCommon::updateRemoteAircraftParts(const CSimConnectObject &simObject, const CInterpolationResult &result)
{
if (!simObject.hasValidRequestAndObjectId()) { return false; }
const CAircraftParts parts = result;
if (parts.getPartsDetails() != CAircraftParts::GuessedParts && !result.getPartsStatus().isSupportingParts()) { return false; }
if (result.getPartsStatus().isReusedParts() || this->isEqualLastSent(parts, simObject.getCallsign())) { return true; }
DataDefinitionRemoteAircraftPartsWithoutLights ddRemoteAircraftPartsWithoutLights(parts); // no init, all values will be set
return this->sendRemoteAircraftPartsToSimulator(simObject, ddRemoteAircraftPartsWithoutLights, parts.getAdjustedLights());
}
void CSimulatorFsxCommon::triggerUpdateAirports(const CAirportList &airports)
{
if (this->isShuttingDownOrDisconnected()) { return; }
if (airports.isEmpty()) { return; }
QPointer<CSimulatorFsxCommon> myself(this);
QTimer::singleShot(0, this, [ = ]
{
if (!myself) { return; }
this->updateAirports(airports);
});
}
void CSimulatorFsxCommon::updateAirports(const CAirportList &airports)
{
if (airports.isEmpty()) { return; }
static const CLength maxDistance(200.0, CLengthUnit::NM());
const CCoordinateGeodetic posAircraft(this->getOwnAircraftPosition());
for (const CAirport &airport : airports)
{
CAirport consolidatedAirport(airport);
const CLength d = consolidatedAirport.calculcateAndUpdateRelativeDistanceAndBearing(posAircraft);
if (d > maxDistance) { continue; }
consolidatedAirport.updateMissingParts(this->getWebServiceAirport(airport.getIcao()));
m_airportsInRangeFromSimulator.replaceOrAddByIcao(consolidatedAirport);
if (m_airportsInRangeFromSimulator.size() > this->maxAirportsInRange())
{
m_airportsInRangeFromSimulator.sortByDistanceToOwnAircraft();
m_airportsInRangeFromSimulator.truncate(this->maxAirportsInRange());
}
}
}
bool CSimulatorFsxCommon::sendRemoteAircraftPartsToSimulator(const CSimConnectObject &simObject, DataDefinitionRemoteAircraftPartsWithoutLights &ddRemoteAircraftPartsWithoutLights, const CAircraftLights &lights)
{
Q_ASSERT(m_hSimConnect);
const DWORD objectId = simObject.getObjectId();
// in case we sent, we sent everything
const HRESULT hr = SimConnect_SetDataOnSimObject(m_hSimConnect, CSimConnectDefinitions::DataRemoteAircraftParts,
objectId, SIMCONNECT_DATA_SET_FLAG_DEFAULT, 0,
sizeof(DataDefinitionRemoteAircraftPartsWithoutLights), &ddRemoteAircraftPartsWithoutLights);
if (hr == S_OK && m_simConnectObjects.contains(simObject.getCallsign()))
{
if (this->isTracingSendId()) { this->traceSendId(simObject.getObjectId(), Q_FUNC_INFO);}
}
else
{
CLogMessage(this).warning("Failed so set parts on SimObject '%1' callsign: '%2'") << simObject.getObjectId() << simObject.getCallsign();
}
// lights we can set directly
SimConnect_TransmitClientEvent(m_hSimConnect, objectId, EventLandingLightsSet, lights.isLandingOn() ? 1.0 : 0.0, SIMCONNECT_GROUP_PRIORITY_HIGHEST, SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY);
SimConnect_TransmitClientEvent(m_hSimConnect, objectId, EventStrobesSet, lights.isStrobeOn() ? 1.0 : 0.0, SIMCONNECT_GROUP_PRIORITY_HIGHEST, SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY);
// lights we need to toggle
// (potential risk with quickly changing values that we accidentally toggle back, also we need the light state before we can toggle)
this->sendToggledLightsToSimulator(simObject, lights);
// done
const bool ok = (hr == S_OK);
if (!ok) { this->triggerAutoTraceSendId(); }
return ok;
}
void CSimulatorFsxCommon::sendToggledLightsToSimulator(const CSimConnectObject &simObj, const CAircraftLights &lightsWanted, bool force)
{
if (!simObj.hasValidRequestAndObjectId()) { return; } // stale
const CAircraftLights lightsIsState = simObj.getCurrentLightsInSimulator();
if (lightsWanted == lightsIsState) { return; }
if (!force && lightsWanted == simObj.getLightsAsSent()) { return; }
const CCallsign callsign(simObj.getCallsign());
// Update data
if (m_simConnectObjects.contains(callsign))
{
CSimConnectObject &simObjToUpdate = m_simConnectObjects[callsign];
simObjToUpdate.setLightsAsSent(lightsWanted);
}
// state available, then I can toggle
if (!lightsIsState.isNull())
{
const DWORD objectId = simObj.getObjectId();
if (lightsWanted.isTaxiOn() != lightsIsState.isTaxiOn())
{
SimConnect_TransmitClientEvent(m_hSimConnect, objectId, EventToggleTaxiLights, 0.0, SIMCONNECT_GROUP_PRIORITY_HIGHEST, SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY);
}
if (lightsWanted.isNavOn() != lightsIsState.isNavOn())
{
SimConnect_TransmitClientEvent(m_hSimConnect, objectId, EventToggleNavLights, 0.0, SIMCONNECT_GROUP_PRIORITY_HIGHEST, SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY);
}
if (lightsWanted.isBeaconOn() != lightsIsState.isBeaconOn())
{
SimConnect_TransmitClientEvent(m_hSimConnect, objectId, EventToggleBeaconLights, 0.0, SIMCONNECT_GROUP_PRIORITY_HIGHEST, SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY);
}
if (lightsWanted.isLogoOn() != lightsIsState.isLogoOn())
{
SimConnect_TransmitClientEvent(m_hSimConnect, objectId, EventToggleLogoLights, 0.0, SIMCONNECT_GROUP_PRIORITY_HIGHEST, SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY);
}
if (lightsWanted.isRecognitionOn() != lightsIsState.isRecognitionOn())
{
SimConnect_TransmitClientEvent(m_hSimConnect, objectId, EventToggleRecognitionLights, 0.0, SIMCONNECT_GROUP_PRIORITY_HIGHEST, SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY);
}
if (lightsWanted.isCabinOn() != lightsIsState.isCabinOn())
{
SimConnect_TransmitClientEvent(m_hSimConnect, objectId, EventToggleCabinLights, 0.0, SIMCONNECT_GROUP_PRIORITY_HIGHEST, SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY);
}
return;
}
// missing lights info from simulator so far
if (this->showDebugLogMessage()) { this->debugLogMessage(Q_FUNC_INFO, QString("Missing light state in simulator for '%1', model '%2'").arg(callsign.asString(), simObj.getAircraftModelString())); }
const QPointer<CSimulatorFsxCommon> myself(this);
QTimer::singleShot(DeferResendingLights, this, [ = ]
{
if (myself.isNull()) { return; }
if (!m_simConnectObjects.contains(callsign)) { return; }
const CSimConnectObject currentSimObject = m_simConnectObjects[callsign];
if (!currentSimObject.hasValidRequestAndObjectId()) { return; } // stale
if (lightsWanted != currentSimObject.getLightsAsSent()) { return; } // changed in between, so another call sendToggledLightsToSimulator is pending
if (this->showDebugLogMessage()) { this->debugLogMessage(Q_FUNC_INFO, QString("Resending light state for '%1', model '%2'").arg(callsign.asString(), simObj.getAircraftModelString())); }
this->sendToggledLightsToSimulator(currentSimObject, lightsWanted, true);
});
}
SIMCONNECT_DATA_INITPOSITION CSimulatorFsxCommon::aircraftSituationToFsxPosition(const CAircraftSituation &situation, bool sendGnd)
{
Q_ASSERT_X(!situation.isGeodeticHeightNull(), Q_FUNC_INFO, "Missing height (altitude)");
Q_ASSERT_X(!situation.isPositionNull(), Q_FUNC_INFO, "Missing position");
SIMCONNECT_DATA_INITPOSITION position = CSimulatorFsxCommon::coordinateToFsxPosition(situation);
position.Heading = situation.getHeading().value(CAngleUnit::deg());
position.Airspeed = situation.getGroundSpeed().value(CSpeedUnit::kts());
// MSFS has inverted pitch and bank angles
position.Pitch = -situation.getPitch().value(CAngleUnit::deg());
position.Bank = -situation.getBank().value(CAngleUnit::deg());
position.OnGround = 0U;
if (sendGnd && situation.isOnGroundInfoAvailable())
{
const bool onGround = (situation.getOnGround() == CAircraftSituation::OnGround);
position.OnGround = onGround ? 1U : 0U;
}
return position;
}
SIMCONNECT_DATA_INITPOSITION CSimulatorFsxCommon::coordinateToFsxPosition(const ICoordinateGeodetic &coordinate)
{
SIMCONNECT_DATA_INITPOSITION position;
position.Latitude = coordinate.latitude().value(CAngleUnit::deg());
position.Longitude = coordinate.longitude().value(CAngleUnit::deg());
position.Altitude = coordinate.geodeticHeight().value(CLengthUnit::ft()); // already corrected in interpolator if there is an underflow
position.Heading = 0;
position.Airspeed = 0;
position.Pitch = 0;
position.Bank = 0;
position.OnGround = 0;
return position;
}
void CSimulatorFsxCommon::synchronizeTime(const CTime &zuluTimeSim, const CTime &localTimeSim)
{
if (!m_simTimeSynced) { return; }
if (!this->isConnected()) { return; }
if (m_syncDeferredCounter > 0)
{
--m_syncDeferredCounter;
}
Q_UNUSED(localTimeSim);
QDateTime myDateTime = QDateTime::currentDateTimeUtc();
if (!m_syncTimeOffset.isZeroEpsilonConsidered())
{
int offsetSeconds = m_syncTimeOffset.valueRounded(CTimeUnit::s(), 0);
myDateTime = myDateTime.addSecs(offsetSeconds);
}
const QTime myTime = myDateTime.time();
const DWORD h = static_cast<DWORD>(myTime.hour());
const DWORD m = static_cast<DWORD>(myTime.minute());
const int targetMins = myTime.hour() * 60 + myTime.minute();
const int simMins = zuluTimeSim.valueRounded(CTimeUnit::min());
const int diffMins = qAbs(targetMins - simMins);
if (diffMins < 2) { return; }
HRESULT hr = S_OK;
hr += SimConnect_TransmitClientEvent(m_hSimConnect, 0, EventSetTimeZuluHours, h, SIMCONNECT_GROUP_PRIORITY_STANDARD, SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY);
hr += SimConnect_TransmitClientEvent(m_hSimConnect, 0, EventSetTimeZuluMinutes, m, SIMCONNECT_GROUP_PRIORITY_STANDARD, SIMCONNECT_EVENT_FLAG_GROUPID_IS_PRIORITY);
if (hr != S_OK)
{
CLogMessage(this).warning("Sending time sync failed!");
}
else
{
m_syncDeferredCounter = 5; // allow some time to sync
CLogMessage(this).info("Synchronized time to UTC: '%1'") << myTime.toString();
}
}
void CSimulatorFsxCommon::injectWeatherGrid(const Weather::CWeatherGrid &weatherGrid)
{
// So far, there is only global weather
auto glob = weatherGrid.frontOrDefault();
glob.setIdentifier("GLOB");
const QString metar = CSimConnectUtilities::convertToSimConnectMetar(glob);
SimConnect_WeatherSetModeCustom(m_hSimConnect);
SimConnect_WeatherSetModeGlobal(m_hSimConnect);
SimConnect_WeatherSetObservation(m_hSimConnect, 0, qPrintable(metar));
}
bool CSimulatorFsxCommon::requestPositionDataForSimObject(const CSimConnectObject &simObject, SIMCONNECT_PERIOD period)
{
if (this->isShuttingDownOrDisconnected()) { return false; }
if (!simObject.hasValidRequestAndObjectId()) { return false; }
if (simObject.isPending()) { return false; } // wait until confirmed
if (simObject.getSimDataPeriod() == period) { return true; } // already queried like this
if (!m_simConnectObjects.contains(simObject.getCallsign())) { return false; } // removed in meantime
// always request, not only when something has changed
const SIMCONNECT_DATA_REQUEST_ID reqId = static_cast<SIMCONNECT_DATA_REQUEST_ID>(simObject.getRequestId(CSimConnectDefinitions::SimObjectPositionData));
const HRESULT result = SimConnect_RequestDataOnSimObject(
m_hSimConnect, reqId,
CSimConnectDefinitions::DataRemoteAircraftGetPosition,
simObject.getObjectId(), period);
if (result == S_OK)
{
m_requestSimObjectDataCount++;
if (this->isTracingSendId()) { this->traceSendId(simObject.getObjectId(), Q_FUNC_INFO);}
m_simConnectObjects[simObject.getCallsign()].setSimDataPeriod(period);
return true;
}
// failure
CLogMessage(this).error("Cannot request simulator data on object '%1'") << simObject.getObjectId();
return false;
}
bool CSimulatorFsxCommon::requestTerrainProbeData(const CCallsign &callsign)
{
if (m_simConnectProbes.countConfirmedAdded() < 1) { return false; }
if (!m_simConnectObjects.contains(callsign)) { return false; } // removed in meantime
const CSimConnectObject simObject = m_simConnectProbes.values().front();
const SIMCONNECT_DATA_REQUEST_ID requestId = simObject.getRequestId(CSimConnectDefinitions::SimObjectPositionData);
const DWORD objectId = simObject.getObjectId();
const HRESULT result = SimConnect_RequestDataOnSimObject(
m_hSimConnect, requestId,
CSimConnectDefinitions::DataRemoteAircraftGetPosition,
objectId, SIMCONNECT_PERIOD_ONCE);
if (result == S_OK)
{
if (this->isTracingSendId()) { this->traceSendId(requestId, Q_FUNC_INFO); }
m_pendingProbeRequests.insert(requestId, callsign);
return true;
}
CLogMessage(this).error("Cannot request terrain probe data for id '%1' ''%2") << requestId << callsign.asString();
return false;
}
bool CSimulatorFsxCommon::requestLightsForSimObject(const CSimConnectObject &simObject)
{
if (!this->isValidSimObjectNotPendingRemoved(simObject)) { return false; }
if (!m_hSimConnect) { return false; }
// always request, not only when something has changed
const SIMCONNECT_DATA_REQUEST_ID requestId = simObject.getRequestId(CSimConnectDefinitions::SimObjectLights);
const HRESULT result = SimConnect_RequestDataOnSimObject(
m_hSimConnect, requestId,
CSimConnectDefinitions::DataRemoteAircraftLights, simObject.getObjectId(),
SIMCONNECT_PERIOD_SECOND);
if (result == S_OK)
{
if (this->isTracingSendId()) { this->traceSendId(simObject.getObjectId(), Q_FUNC_INFO);}
return true;
}
CLogMessage(this).error("Cannot request lights data on object '%1'") << simObject.getObjectId();
return false;
}
bool CSimulatorFsxCommon::requestModelInfoForSimObject(const CSimConnectObject &simObject)
{
if (!this->isValidSimObjectNotPendingRemoved(simObject)) { return false; }
if (!m_hSimConnect) { return false; }
// always request, not only when something has changed
const SIMCONNECT_DATA_REQUEST_ID requestId = simObject.getRequestId(CSimConnectDefinitions::SimObjectModel);
const HRESULT result = SimConnect_RequestDataOnSimObject(
m_hSimConnect, requestId,
CSimConnectDefinitions::DataRemoteAircraftModelData, simObject.getObjectId(),
SIMCONNECT_PERIOD_ONCE);
if (result == S_OK)
{
if (this->isTracingSendId()) { this->traceSendId(simObject.getObjectId(), Q_FUNC_INFO);}
return true;
}
CLogMessage(this).error("Cannot request model info on object '%1'") << simObject.getObjectId();
return false;
}
bool CSimulatorFsxCommon::stopRequestingDataForSimObject(const CSimConnectObject &simObject)
{
if (!simObject.hasValidRequestAndObjectId()) { return false; }
if (!m_hSimConnect) { return false; }
// stop by setting SIMCONNECT_PERIOD_NEVER
SIMCONNECT_DATA_REQUEST_ID requestId = simObject.getRequestId(CSimConnectDefinitions::SimObjectPositionData);
HRESULT result = SimConnect_RequestDataOnSimObject(
m_hSimConnect, requestId,
CSimConnectDefinitions::DataRemoteAircraftGetPosition,
simObject.getObjectId(), SIMCONNECT_PERIOD_NEVER);
if (result == S_OK) { if (this->isTracingSendId()) { this->traceSendId(simObject.getObjectId(), Q_FUNC_INFO, "Position");} }
requestId = simObject.getRequestId(CSimConnectDefinitions::SimObjectLights);
result = SimConnect_RequestDataOnSimObject(
m_hSimConnect, requestId,
CSimConnectDefinitions::DataRemoteAircraftLights, simObject.getObjectId(),
SIMCONNECT_PERIOD_NEVER);
if (result == S_OK) { if (this->isTracingSendId()) { this->traceSendId(simObject.getObjectId(), Q_FUNC_INFO, "Lights");} }
Q_UNUSED(result);
return true;
}
void CSimulatorFsxCommon::initSimulatorInternals()
{
CSimulatorFsCommon::initSimulatorInternals();
CSimulatorInternals s = m_simulatorInternals;
const QString fsxPath = CFsCommonUtil::fsxDirFromRegistry(); // can be empty for remote FSX
if (!fsxPath.isEmpty()) { s.setSimulatorInstallationDirectory(fsxPath); }
s.setValue("fsx/simConnectCfgFilename", CSimConnectUtilities::getSwiftLocalSimConnectCfgFilename());
s.setValue("fsx/simConnectVersion", m_simConnectVersion);
m_simulatorInternals = s;
}
void CSimulatorFsxCommon::reset()
{
this->safeKillTimer();
m_simulatingChangedTs = -1;
m_simConnected = false;
m_simSimulating = false;
m_syncDeferredCounter = 0;
m_skipCockpitUpdateCycles = 0;
m_requestIdSimObjAircraft = static_cast<SIMCONNECT_DATA_REQUEST_ID>(RequestSimObjAircraftStart);
m_dispatchErrors = 0;
m_receiveExceptionCount = 0;
m_sendIdTraces.clear();
this->removeAllProbes();
// cleared below:
// m_simConnectObjects
// m_simConnectObjectsPositionAndPartsTraces
// m_addPendingAircraft
// m_updateRemoteAircraftInProgress
CSimulatorFsCommon::reset(); // clears all pending aircraft etc
}
void CSimulatorFsxCommon::clearAllRemoteAircraftData()
{
m_simConnectObjects.clear();
m_addPendingAircraft.clear();
m_simConnectObjectsPositionAndPartsTraces.clear();
this->removeAllProbes();
// m_addAgainAircraftWhenRemoved cleared below
CSimulatorFsCommon::clearAllRemoteAircraftData();
}
QString CSimulatorFsxCommon::fsxPositionToString(const SIMCONNECT_DATA_INITPOSITION &position)
{
static const QString positionStr("Lat: %1deg lng: %2deg alt: %3ft pitch: %4deg bank: %5deg hdg: %6deg airspeed: %7kts onGround: %8");
return positionStr.
arg(position.Latitude).arg(position.Longitude).arg(position.Altitude).
arg(position.Pitch).arg(position.Bank).arg(position.Heading).arg(position.Airspeed).arg(position.OnGround);
}
CCallsignSet CSimulatorFsxCommon::getCallsignsMissingInProvider() const
{
const CCallsignSet simObjectCallsigns(m_simConnectObjects.keys());
const CCallsignSet providerCallsigns(this->getAircraftInRangeCallsigns());
return simObjectCallsigns.difference(providerCallsigns);
}
void CSimulatorFsxCommon::traceSendId(DWORD simObjectId, const QString &function, const QString &details)
{
if (!this->isTracingSendId()) { return; }
if (MaxSendIdTraces < 1) { return; }
DWORD dwLastId = 0;
const HRESULT hr = SimConnect_GetLastSentPacketID(m_hSimConnect, &dwLastId);
if (hr != S_OK) { return; }
if (m_sendIdTraces.size() > MaxSendIdTraces) { m_sendIdTraces.removeFirst(); }
const TraceFsxSendId trace(dwLastId, simObjectId,
details.isEmpty() ? function : details + ", " + function);
m_sendIdTraces.push_back(trace);
}
QString CSimulatorFsxCommon::getSendIdTraceDetails(DWORD sendId) const
{
for (const TraceFsxSendId &trace : m_sendIdTraces)
{
if (trace.sendId == sendId)
{
static const QString d("Send id: %1 obj.id.: %2 cs.: %4 '%3'");
const CCallsign cs = m_simConnectObjects.getCallsignForObjectId(trace.simObjectId);
return d.arg(sendId).arg(trace.simObjectId).arg(cs.asString(), trace.comment);
}
}
return "";
}
int CSimulatorFsxCommon::removeAllProbes()
{
if (m_simConnectProbes.isEmpty()) { return 0; }
int c = 0;
for (const CSimConnectObject &probeSimObject : m_simConnectProbes.values())
{
if (!probeSimObject.isConfirmedAdded()) { continue; }
const SIMCONNECT_DATA_REQUEST_ID requestId = probeSimObject.getRequestId(CSimConnectDefinitions::SimObjectRemove);
const HRESULT result = SimConnect_AIRemoveObject(m_hSimConnect, static_cast<SIMCONNECT_OBJECT_ID>(probeSimObject.getObjectId()), requestId);
if (result == S_OK)
{
c++;
}
else
{
CLogMessage(this).warning("Removing probe '%1' from simulator failed") << probeSimObject.getObjectId();
}
}
m_simConnectProbes.clear();
m_pendingProbeRequests.clear();
return c;
}
CSimConnectObject CSimulatorFsxCommon::insertNewSimConnectObject(const CSimulatedAircraft &aircraft, DWORD requestId)
{
if (m_simConnectObjects.contains(aircraft.getCallsign()))
{
// error, ...?
return m_simConnectObjects[aircraft.getCallsign()];
}
CSimConnectObject simObject;
if (m_simConnectObjectsPositionAndPartsTraces.contains(aircraft.getCallsign()))
{
// if in traces, get the object and reuse it
simObject = m_simConnectObjectsPositionAndPartsTraces[aircraft.getCallsign()];
m_simConnectObjectsPositionAndPartsTraces.remove(aircraft.getCallsign());
simObject.resetState();
simObject.setRequestId(requestId);
simObject.setAircraft(aircraft);
simObject.attachInterpolatorLogger(&m_interpolationLogger); // setting a logger does not start logging
}
else
{
simObject = CSimConnectObject(aircraft, requestId, this, this, this->getRemoteAircraftProvider(), &m_interpolationLogger);
}
m_simConnectObjects.insert(aircraft.getCallsign(), simObject);
return simObject;
}
QString CSimulatorFsxCommon::fsxCharToQString(const char *fsxChar, int size)
{
return QString::fromLatin1(fsxChar, size);
}
QString CSimulatorFsxCommon::requestIdToString(DWORD requestId)
{
if (requestId <= CSimConnectDefinitions::RequestEndMarker)
{
return CSimConnectDefinitions::requestToString(static_cast<CSimConnectDefinitions::Request>(requestId));
}
const CSimConnectDefinitions::SimObjectRequest simRequest = requestToSimObjectRequest(requestId);
const CSimConnectObject::SimObjectType simType = CSimConnectObject::requestIdToType(requestId);
static const QString req("%1 %2 %3");
return req.arg(requestId).arg(CSimConnectObject::typeToString(simType)).arg(CSimConnectDefinitions::simObjectRequestToString(simRequest));
}
DWORD CSimulatorFsxCommon::unitTestRequestId(CSimConnectObject::SimObjectType type)
{
int start;
int end;
switch (type)
{
case CSimConnectObject::TerrainProbe:
start = RequestSimObjTerrainProbeStart; end = RequestSimObjTerrainProbeEnd;
break;
case CSimConnectObject::Aircraft:
default:
start = RequestSimObjAircraftStart; end = RequestSimObjAircraftEnd;
break;
}
const int id = CMathUtils::randomInteger(start, end);
return static_cast<DWORD>(id);
}
CCallsignSet CSimulatorFsxCommon::physicallyRemoveAircraftNotInProvider()
{
const CCallsignSet callsignsToBeRemoved(getCallsignsMissingInProvider());
if (callsignsToBeRemoved.isEmpty()) { return callsignsToBeRemoved; }
for (const CCallsign &callsign : callsignsToBeRemoved)
{
this->physicallyRemoveRemoteAircraft(callsign);
}
if (this->showDebugLogMessage()) { this->debugLogMessage(Q_FUNC_INFO, QString("CS: '%1'").arg(callsignsToBeRemoved.toStringList().join(", "))); }
return callsignsToBeRemoved;
}
CSimulatorFsxCommonListener::CSimulatorFsxCommonListener(const CSimulatorPluginInfo &info) :
ISimulatorListener(info)
{
constexpr int QueryInterval = 5 * 1000; // 5 seconds
m_timer.setInterval(QueryInterval);
m_timer.setObjectName(this->objectName().append(":m_timer"));
loadAndResolveSimConnect(true);
connect(&m_timer, &QTimer::timeout, this, &CSimulatorFsxCommonListener::checkConnection);
}
void CSimulatorFsxCommonListener::startImpl()
{
m_simulatorVersion.clear();
m_simConnectVersion.clear();
m_simulatorName.clear();
m_simulatorDetails.clear();
m_timer.start();
}
void CSimulatorFsxCommonListener::stopImpl()
{
m_timer.stop();
}
QString CSimulatorFsxCommonListener::backendInfo() const
{
if (m_simulatorName.isEmpty()) { return ISimulatorListener::backendInfo(); }
return m_simulatorDetails;
}
void CSimulatorFsxCommonListener::checkConnection()
{
if (this->isShuttingDown()) { return; }
Q_ASSERT_X(!CThreadUtils::isCurrentThreadApplicationThread(), Q_FUNC_INFO, "Expect to run in background");
HANDLE hSimConnect;
HRESULT result = SimConnect_Open(&hSimConnect, sApp->swiftVersionChar(), nullptr, 0, 0, 0);
bool check = false;
if (result == S_OK)
{
for (int i = 0; !check && i < 3 && !this->isShuttingDown(); i++)
{
// result not always in first dispatch as we first have to obtain simulator name
result = SimConnect_CallDispatch(hSimConnect, CSimulatorFsxCommonListener::SimConnectProc, this);
if (result != S_OK) { break; } // means serious failure
check = this->checkVersionAndSimulator();
if (!check) { sApp->processEventsFor(500); }
}
}
SimConnect_Close(hSimConnect);
if (check)
{
emit this->simulatorStarted(this->getPluginInfo());
}
}
bool CSimulatorFsxCommonListener::checkVersionAndSimulator() const
{
const CSimulatorInfo pluginSim(getPluginInfo().getIdentifier());
const QString connectedSimName = m_simulatorName.toLower().trimmed();
if (connectedSimName.isEmpty()) { return false; }
if (pluginSim.isP3D())
{
// P3D drivers only works with P3D
return connectedSimName.contains("lockheed") || connectedSimName.contains("martin") || connectedSimName.contains("p3d") || connectedSimName.contains("prepar");
}
else if (pluginSim.isFSX())
{
// FSX drivers only works with FSX
return connectedSimName.contains("fsx") || connectedSimName.contains("microsoft") || connectedSimName.contains("simulator x");
}
return false;
}
bool CSimulatorFsxCommonListener::checkSimConnectDll() const
{
static const CWinDllUtils::DLLInfo simConnectInfo = CSimConnectUtilities::simConnectDllInfo();
if (!simConnectInfo.errorMsg.isEmpty()) { return false; }
return true;
}
void CSimulatorFsxCommonListener::SimConnectProc(SIMCONNECT_RECV *pData, DWORD cbData, void *pContext)
{
Q_UNUSED(cbData);
CSimulatorFsxCommonListener *simListener = static_cast<CSimulatorFsxCommonListener *>(pContext);
switch (pData->dwID)
{
case SIMCONNECT_RECV_ID_OPEN:
{
SIMCONNECT_RECV_OPEN *event = (SIMCONNECT_RECV_OPEN *)pData;
simListener->m_simulatorVersion = QString("%1.%2.%3.%4").arg(event->dwApplicationVersionMajor).arg(event->dwApplicationVersionMinor).arg(event->dwApplicationBuildMajor).arg(event->dwApplicationBuildMinor);
simListener->m_simConnectVersion = QString("%1.%2.%3.%4").arg(event->dwSimConnectVersionMajor).arg(event->dwSimConnectVersionMinor).arg(event->dwSimConnectBuildMajor).arg(event->dwSimConnectBuildMinor);
simListener->m_simulatorName = CSimulatorFsxCommon::fsxCharToQString(event->szApplicationName);
simListener->m_simulatorDetails = QString("Name: '%1' Version: %2 SimConnect: %3").arg(simListener->m_simulatorName, simListener->m_simulatorVersion, simListener->m_simConnectVersion);
CLogMessage(static_cast<CSimulatorFsxCommonListener *>(nullptr)).info("Connect to %1: '%2'") << simListener->getPluginInfo().getIdentifier() << simListener->backendInfo();
break;
}
case SIMCONNECT_RECV_ID_EXCEPTION:
break;
default:
break;
}
}
} // namespace
} // namespace