Files
pilotclient/src/plugins/simulator/fs9/simulatorfs9.cpp
Lars Toenning 18d0b1eefc refactor(fs): Move FSUIPC from fscommon to FS9
With default settings, FSUIPC was not really used
by FS simulators (except FS9) as the transponder
mode readout was done with SB offsets through SimConnect.
For simplification, this removes FSUIPC from fscommon and moves it to FS9.
Therefor this also removes the option for FSX/P3D users to toggle FSUIPC.
2024-04-16 21:23:08 +02:00

568 lines
22 KiB
C++

// SPDX-FileCopyrightText: Copyright (C) 2014 swift Project Community / Contributors
// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-swift-pilot-client-1
#include "fs9.h"
#include "directplayerror.h"
#include "simulatorfs9.h"
#include "fs9client.h"
#include "multiplayerpackets.h"
#include "multiplayerpacketparser.h"
#include "registermetadata.h"
#include "../fscommon/simulatorfscommonfunctions.h"
#include "blackmisc/network/textmessage.h"
#include "blackmisc/simulation/fscommon/fscommonutil.h"
#include "blackmisc/simulation/simulatorplugininfo.h"
#include "blackmisc/logmessage.h"
#include "blackmisc/propertyindexallclasses.h"
#include "blackmisc/verify.h"
#include "blackconfig/buildconfig.h"
#include <QTimer>
#include <QPointer>
#include <algorithm>
using namespace BlackMisc;
using namespace BlackMisc::Aviation;
using namespace BlackMisc::Network;
using namespace BlackMisc::PhysicalQuantities;
using namespace BlackMisc::Geo;
using namespace BlackMisc::Network;
using namespace BlackMisc::Simulation;
using namespace BlackMisc::Simulation::FsCommon;
using namespace BlackMisc::Weather;
using namespace BlackCore;
using namespace BlackSimPlugin::FsCommon;
using namespace BlackConfig;
namespace BlackSimPlugin::Fs9
{
CSimulatorFs9::CSimulatorFs9(const CSimulatorPluginInfo &info,
const QSharedPointer<CFs9Host> &fs9Host,
const QSharedPointer<CLobbyClient> &lobbyClient,
IOwnAircraftProvider *ownAircraftProvider,
IRemoteAircraftProvider *remoteAircraftProvider,
IWeatherGridProvider *weatherGridProvider,
IClientProvider *clientProvider,
QObject *parent) : CSimulatorFsCommon(info, ownAircraftProvider, remoteAircraftProvider, weatherGridProvider, clientProvider, parent),
m_fs9Host(fs9Host),
m_lobbyClient(lobbyClient),
m_fsuipc(new CFsuipc(this))
{
// disabled CG/elevation parts
this->setSimulationProviderEnabled(false, false);
//! \fixme KB 7/2017 change or remove comment when reviewed Could we just use: connect(lobbyClient.data(), &CLobbyClient::disconnected, this, &CSimulatorFs9::disconnectFrom);
connect(lobbyClient.data(), &CLobbyClient::disconnected, this, [=] {
this->emitSimulatorCombinedStatus();
});
this->setDefaultModel(
{ "Boeing 737-400", CAircraftModel::TypeModelMatchingDefaultModel,
"B737-400 default model", CAircraftIcaoCode("B734", "L2J") });
}
bool CSimulatorFs9::isConnected() const
{
return m_simConnected;
}
bool CSimulatorFs9::connectTo()
{
Q_ASSERT_X(m_fs9Host, Q_FUNC_INFO, "No FS9 host");
if (!m_fs9Host->isConnected()) { return false; } // host not available, we quit
Q_ASSERT_X(m_fsuipc, Q_FUNC_INFO, "No FSUIPC");
m_connectionHostMessages = connect(m_fs9Host.data(), &CFs9Host::customPacketReceived, this, &CSimulatorFs9::processFs9Message);
m_fsuipc->open();
this->initSimulatorInternals();
m_timerId = startTimer(50);
return true;
}
bool CSimulatorFs9::disconnectFrom()
{
if (!m_simConnected) { return true; }
// Don't forward messages when disconnected
disconnect(m_connectionHostMessages);
safeKillTimer();
disconnectAllClients();
if (m_fsuipc) { m_fsuipc->close(); }
// emit status
CSimulatorFsCommon::disconnectFrom();
m_simConnected = false;
emitSimulatorCombinedStatus();
return true;
}
bool CSimulatorFs9::physicallyAddRemoteAircraft(const CSimulatedAircraft &newRemoteAircraft)
{
const CCallsign callsign = newRemoteAircraft.getCallsign();
this->logAddingAircraftModel(newRemoteAircraft);
if (m_hashFs9Clients.contains(callsign))
{
// already exists, remove first
this->physicallyRemoveRemoteAircraft(callsign);
}
CFs9Client *client = new CFs9Client(newRemoteAircraft, CTime(25, CTimeUnit::ms()), &m_interpolationLogger, this);
client->setHostAddress(m_fs9Host->getHostAddress());
client->setPlayerUserId(m_fs9Host->getPlayerUserId());
connect(client, &CFs9Client::statusChanged, this, &CSimulatorFs9::updateRenderStatus);
client->start();
m_hashFs9Clients.insert(callsign, client);
return true;
}
bool CSimulatorFs9::physicallyRemoveRemoteAircraft(const CCallsign &callsign)
{
if (!m_hashFs9Clients.contains(callsign)) { return false; }
auto fs9Client = m_hashFs9Clients.value(callsign);
delete fs9Client;
m_hashFs9Clients.remove(callsign);
updateAircraftRendered(callsign, false);
CLogMessage(this).info(u"FS9: Removed aircraft '%1'") << callsign.toQString();
return CSimulatorFsCommon::physicallyRemoveRemoteAircraft(callsign);
}
int CSimulatorFs9::physicallyRemoveAllRemoteAircraft()
{
if (m_hashFs9Clients.isEmpty()) { return 0; }
QList<CCallsign> callsigns(m_hashFs9Clients.keys());
int r = 0;
for (const CCallsign &cs : callsigns)
{
if (physicallyRemoveRemoteAircraft(cs)) { r++; }
}
CSimulatorFsCommon::physicallyRemoveAllRemoteAircraft();
return r;
}
CCallsignSet CSimulatorFs9::physicallyRenderedAircraft() const
{
return CCollection<CCallsign>(m_hashFs9Clients.keys());
}
bool CSimulatorFs9::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 CTransponder newTransponder = ownAircraft.getTransponder();
bool changed = false;
if (newTransponder.getTransponderCode() != m_simTransponder.getTransponderCode()) { changed = true; }
if (newTransponder.getTransponderMode() != m_simTransponder.getTransponderMode()) { changed = true; }
m_fsuipc->write(newTransponder);
// avoid changes of cockpit back to old values due to an outdated read back value
if (changed) { m_skipCockpitUpdateCycles = SkipUpdateCyclesForCockpit; }
// bye
return changed;
}
bool CSimulatorFs9::updateOwnSimulatorSelcal(const CSelcal &selcal, const CIdentifier &originator)
{
if (originator == this->identifier()) { return false; }
if (!this->isSimulating()) { return false; }
bool changed = false;
if (selcal != m_selcal)
{
//! KB 2018-11 that would need to go to updateOwnAircraftFromSimulator if the simulator ever supports SELCAL
//! KB 2018-11 as we would need to send the value to FS9/FSX (currently we only deal with it on FS9/FSX level)
m_selcal = selcal;
changed = true;
}
return changed;
}
void CSimulatorFs9::displayStatusMessage(const CStatusMessage &message) const
{
if (!m_fs9Host.data()) { return; }
// Avoid errors from CDirectPlayPeer as it may end in infinite loop
if (message.getSeverity() == CStatusMessage::SeverityError && message.isFromClass<CDirectPlayPeer>())
{
return;
}
if (message.getSeverity() != CStatusMessage::SeverityDebug)
{
QMetaObject::invokeMethod(m_fs9Host.data(), "sendTextMessage", Q_ARG(QString, message.toQString()));
}
}
void CSimulatorFs9::displayTextMessage(const CTextMessage &message) const
{
if (!m_fs9Host.data()) { return; }
QMetaObject::invokeMethod(m_fs9Host.data(), "sendTextMessage", Q_ARG(QString, message.asString(true, true)));
}
CStatusMessageList CSimulatorFs9::getInterpolationMessages(const CCallsign &callsign) const
{
if (!m_hashFs9Clients.contains(callsign)) { return CStatusMessageList(); }
const CFs9Client *client = m_hashFs9Clients[callsign].data();
if (!client) { return CStatusMessageList(); }
const CInterpolationAndRenderingSetupPerCallsign setup = this->getInterpolationSetupPerCallsignOrDefault(callsign);
return client->getInterpolationMessages(setup.getInterpolatorMode());
}
bool CSimulatorFs9::testSendSituationAndParts(const CCallsign &callsign, const CAircraftSituation &situation, const CAircraftParts &parts)
{
if (!m_hashFs9Clients.contains(callsign)) { return false; }
CFs9Client *client = m_hashFs9Clients[callsign].data();
if (!client) { return false; }
Q_UNUSED(parts)
int u = 0;
if (!situation.isNull())
{
u++;
client->sendMultiplayerPosition(situation);
if (!parts.isNull()) { client->sendMultiplayerParts(parts); }
}
return u > 0;
}
bool CSimulatorFs9::isPhysicallyRenderedAircraft(const CCallsign &callsign) const
{
return m_hashFs9Clients.contains(callsign);
}
void CSimulatorFs9::timerEvent(QTimerEvent *event)
{
Q_UNUSED(event)
dispatch();
}
void CSimulatorFs9::dispatch()
{
if (m_fsuipc && m_fsuipc->isOpened())
{
CSimulatedAircraft fsuipcAircraft(getOwnAircraft());
const bool ok = m_fsuipc->read(fsuipcAircraft, true, true, true);
if (ok)
{
updateOwnAircraftFromSimulator(fsuipcAircraft);
}
else
{
// FSUIPC read error means almost always that FS9 closed. Shutdown the driver.
CLogMessage(this).debug() << "Lost connection to FSUIPC. Shutting down.";
disconnectFrom();
}
synchronizeTime();
}
}
QString getChangedParamsAsString(const MPParam &old, const MPParam &newParam)
{
// for debugging
QString str;
if (old.unknown8 != newParam.unknown8) str += "unknown8 " + QString::number(newParam.unknown8) + "\n";
if (old.unknown9 != newParam.unknown9) str += "unknown9 " + QString::number(newParam.unknown9) + "\n";
if (old.flaps_left != newParam.flaps_left) str += "flaps_left " + QString::number(newParam.flaps_left) + "\n";
if (old.flaps_right != newParam.flaps_right) str += "flaps_right " + QString::number(newParam.flaps_right) + "\n";
if (old.unknown12 != newParam.unknown12) str += "unknown12 " + QString::number(newParam.unknown12) + "\n";
if (old.unknown13 != newParam.unknown13) str += "unknown13 " + QString::number(newParam.unknown13) + "\n";
if (old.unknown14 != newParam.unknown14) str += "unknown14 " + QString::number(newParam.unknown14) + "\n";
if (old.unknown15 != newParam.unknown15) str += "unknown15 " + QString::number(newParam.unknown15) + "\n";
if (old.unknown16 != newParam.unknown16) str += "unknown16 " + QString::number(newParam.unknown16) + "\n";
if (old.unknown17 != newParam.unknown17) str += "unknown17 " + QString::number(newParam.unknown17) + "\n";
if (old.unknown18 != newParam.unknown18) str += "unknown18 " + QString::number(newParam.unknown18) + "\n";
if (old.unknown19 != newParam.unknown19) str += "unknown19 " + QString::number(newParam.unknown19) + "\n";
if (old.gear_center != newParam.gear_center) str += "gear_center " + QString::number(newParam.gear_center) + "\n";
if (old.gear_left != newParam.gear_left) str += "gear_left " + QString::number(newParam.gear_left) + "\n";
if (old.gear_right != newParam.gear_right) str += "gear_right " + QString::number(newParam.gear_right) + "\n";
if (old.engine_1 != newParam.engine_1) str += "engine_1 " + QString::number(newParam.engine_1) + "\n";
if (old.engine_2 != newParam.engine_2) str += "engine_2 " + QString::number(newParam.engine_2) + "\n";
if (old.unknown25 != newParam.unknown25) str += "unknown25 " + QString::number(newParam.unknown25) + "\n";
if (old.unknown26 != newParam.unknown26) str += "unknown26 " + QString::number(newParam.unknown26) + "\n";
if (old.unknown27 != newParam.unknown27) str += "unknown27 " + QString::number(newParam.unknown27) + "\n";
return str;
}
void CSimulatorFs9::processFs9Message(const QByteArray &message)
{
if (!m_simConnected)
{
m_simConnected = true;
emitSimulatorCombinedStatus();
}
CFs9Sdk::MULTIPLAYER_PACKET_ID messageType = MultiPlayerPacketParser::readType(message);
switch (messageType)
{
case CFs9Sdk::MULTIPLAYER_PACKET_ID_PARAMS:
{
MPParam mpParam;
MultiPlayerPacketParser::readMessage(message, mpParam);
// For debugging:
// QTextStream qtstdout(stdout);
// QString paramString = getChangedParamsAsString(m_lastParameters, mpParam);
// if (! paramString.isEmpty())
// {
// qtstdout << message.mid(4 * sizeof(qint32)).toHex() << Qt::endl;
// qtstdout << paramString << Qt::endl;
// }
// m_lastParameters = mpParam;
break;
}
case CFs9Sdk::MULTIPLAYER_PACKET_ID_CHANGE_PLAYER_PLANE:
{
MPChangePlayerPlane mpChangePlayerPlane;
MultiPlayerPacketParser::readMessage(message, mpChangePlayerPlane);
reverseLookupAndUpdateOwnAircraftModel(mpChangePlayerPlane.aircraft_name);
break;
}
case CFs9Sdk::MULTIPLAYER_PACKET_ID_POSITION_VELOCITY:
{
break;
}
case CFs9Sdk::MPCHAT_PACKET_ID_CHAT_TEXT_SEND:
{
MPChatText mpChatText;
MultiPlayerPacketParser::readMessage(message, mpChatText);
break;
}
default: break;
}
}
void CSimulatorFs9::updateOwnAircraftFromSimulator(const CSimulatedAircraft &simDataOwnAircraft)
{
// 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)
{
// we always use COM1 and COM2 from swift
const CComSystem oldCom1 = getOwnComSystem(CComSystem::Com1);
const CComSystem oldCom2 = getOwnComSystem(CComSystem::Com2);
this->updateCockpit(oldCom1, oldCom2, simDataOwnAircraft.getTransponder(), this->identifier());
}
else
{
--m_skipCockpitUpdateCycles;
}
const CAircraftSituation aircraftSituation = simDataOwnAircraft.getSituation();
this->updateOwnSituationAndGroundElevation(aircraftSituation);
this->updateOwnParts(simDataOwnAircraft.getParts());
if (m_isWeatherActivated)
{
if (CWeatherScenario::isRealWeatherScenario(m_weatherScenarioSettings.get()))
{
if (m_lastWeatherPosition.isNull() ||
calculateGreatCircleDistance(m_lastWeatherPosition, aircraftSituation).value(CLengthUnit::mi()) > 20)
{
m_lastWeatherPosition = aircraftSituation;
requestWeatherGrid(aircraftSituation, this->identifier());
}
}
} // weather
// slow updates
if (m_ownAircraftUpdateCycles % 25 == 0)
{
this->reverseLookupAndUpdateOwnAircraftModel(simDataOwnAircraft.getModelString());
const CLength cg = simDataOwnAircraft.getCG();
if (!cg.isNull()) { this->updateOwnCG(cg); }
} // slow updates
m_ownAircraftUpdateCycles++;
}
void CSimulatorFs9::updateRenderStatus(const CSimulatedAircraft &remoteAircraft, CFs9Client::ClientStatus)
{
const bool updated = updateAircraftRendered(remoteAircraft.getCallsign(), true);
CSimulatedAircraft remoteAircraftCopy(remoteAircraft);
remoteAircraftCopy.setRendered(true);
if (updated)
{
emit aircraftRenderingChanged(remoteAircraftCopy);
}
CLogMessage(this).info(u"FS9: Added aircraft '%1'") << remoteAircraft.getCallsignAsString();
}
void CSimulatorFs9::disconnectAllClients()
{
// Stop all FS9 client tasks
const QList<CCallsign> callsigns(m_hashFs9Clients.keys());
for (auto fs9Client : callsigns)
{
physicallyRemoveRemoteAircraft(fs9Client);
}
}
void CSimulatorFs9::synchronizeTime()
{
if (!m_simTimeSynced) { return; }
if (!this->isConnected()) { return; }
if (!m_fsuipc) { return; }
if (!m_fsuipc->isOpened()) { return; }
QDateTime myDateTime = QDateTime::currentDateTimeUtc();
if (!m_syncTimeOffset.isZeroEpsilonConsidered())
{
int offsetSeconds = m_syncTimeOffset.valueInteger(CTimeUnit::s());
myDateTime = myDateTime.addSecs(offsetSeconds);
}
const QTime myTime = myDateTime.time();
const int h = myTime.hour();
const int m = myTime.minute();
m_fsuipc->setSimulatorTime(h, m);
}
void CSimulatorFs9::injectWeatherGrid(const CWeatherGrid &weatherGrid)
{
if (this->isShuttingDownOrDisconnected()) { return; }
if (weatherGrid.isEmpty()) { return; }
if (!CThreadUtils::isInThisThread(this))
{
BLACK_VERIFY_X(!CBuildConfig::isLocalDeveloperDebugBuild(), Q_FUNC_INFO, "Wrong thread");
QPointer<CSimulatorFs9> myself(this);
QTimer::singleShot(0, this, [=] {
if (!myself) { return; }
myself->injectWeatherGrid(weatherGrid);
});
return;
}
if (!m_fsuipc) { return; }
if (!m_fsuipc->isOpened()) { return; }
if (weatherGrid.isEmpty())
{
CLogMessage(this).info(u"Empty FS9 weather grid");
return;
}
m_fsuipc->write(weatherGrid);
}
CSimulatorFs9Listener::CSimulatorFs9Listener(const CSimulatorPluginInfo &info,
const QSharedPointer<CFs9Host> &fs9Host,
const QSharedPointer<CLobbyClient> &lobbyClient) : ISimulatorListener(info),
m_timer(new QTimer(this)),
m_fs9Host(fs9Host),
m_lobbyClient(lobbyClient),
m_fsuipc(new CFsuipc(this))
{
const int QueryInterval = 5 * 1000; // 5 seconds
m_timer->setInterval(QueryInterval);
m_timer->setObjectName(this->objectName() + ":m_timer");
// Test whether we can lobby connect at all.
const bool canLobbyConnect = m_lobbyClient->canLobbyConnect();
// check connection
QPointer<CSimulatorFs9Listener> myself(this);
connect(m_timer, &QTimer::timeout, [=]() {
if (!myself) { return; }
this->checkConnection(canLobbyConnect);
});
}
void CSimulatorFs9Listener::startImpl()
{
m_isStarted = false;
m_timer->start();
}
void CSimulatorFs9Listener::stopImpl()
{
m_timer->stop();
}
void CSimulatorFs9Listener::checkImpl()
{
if (this->isShuttingDown()) { return; }
if (m_timer) { m_timer->start(); }
QPointer<CSimulatorFs9Listener> myself(this);
QTimer::singleShot(10, this, [=] {
if (!myself) { return; }
const bool canLobbyConnect = m_lobbyClient->canLobbyConnect();
this->checkConnection(canLobbyConnect);
});
}
bool CSimulatorFs9Listener::checkConnection(bool canLobbyConnect)
{
m_fsuipc->open();
if (!m_fsuipc->isOpen()) { return false; }
m_fsuipc->close();
if (m_fs9Host->getHostAddress().isEmpty()) { return false; } // host not yet set up
if (canLobbyConnect)
{
if (m_isConnecting || isOk(m_lobbyClient->connectFs9ToHost(m_fs9Host->getHostAddress())))
{
m_isConnecting = true;
CLogMessage(this).info(u"swift is joining FS9 to the multiplayer session ...");
}
}
if (!m_isStarted && m_fs9Host->isConnected())
{
m_isStarted = true;
m_isConnecting = false;
emit this->simulatorStarted(this->getPluginInfo());
}
return m_isConnecting;
}
static void cleanupFs9Host(CFs9Host *host)
{
delete host;
}
static void cleanupLobbyClient(CLobbyClient *lobbyClient)
{
delete lobbyClient;
}
CSimulatorFs9Factory::CSimulatorFs9Factory(QObject *parent) : QObject(parent),
m_fs9Host(new CFs9Host, cleanupFs9Host),
m_lobbyClient(new CLobbyClient, cleanupLobbyClient)
{
registerMetadata();
/* After FS9 is disconnected, reset its data stored in the host */
connect(m_lobbyClient.data(), &CLobbyClient::disconnected, m_fs9Host.data(), &CFs9Host::reset);
}
CSimulatorFs9Factory::~CSimulatorFs9Factory()
{}
ISimulator *CSimulatorFs9Factory::create(const CSimulatorPluginInfo &info,
IOwnAircraftProvider *ownAircraftProvider,
IRemoteAircraftProvider *remoteAircraftProvider,
IWeatherGridProvider *weatherGridProvider,
IClientProvider *clientProvider)
{
return new CSimulatorFs9(info, m_fs9Host, m_lobbyClient, ownAircraftProvider, remoteAircraftProvider, weatherGridProvider, clientProvider, this);
}
ISimulatorListener *CSimulatorFs9Factory::createListener(const CSimulatorPluginInfo &info)
{
return new CSimulatorFs9Listener(info, m_fs9Host, m_lobbyClient);
}
} // namespace