mirror of
https://github.com/swift-project/pilotclient.git
synced 2026-04-06 10:15:38 +08:00
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.
2124 lines
106 KiB
C++
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
|