mirror of
https://github.com/swift-project/pilotclient.git
synced 2026-03-22 23:05:36 +08:00
Up to know, the initial weather injection was done in the constructor of ISimulator implementation. This was to early, since in the best case the injection was not doing anything and in the worst case, crashed the application because the plugin wasn't ready yet. This change also replaces ps_reloadSettings with a specific non-slot version to avoid code duplication.
479 lines
18 KiB
C++
479 lines
18 KiB
C++
/* Copyright (C) 2014
|
|
* 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 "fs9.h"
|
|
#include "directplayerror.h"
|
|
#include "simulatorfs9.h"
|
|
#include "fs9client.h"
|
|
#include "multiplayerpackets.h"
|
|
#include "multiplayerpacketparser.h"
|
|
#include "registermetadata.h"
|
|
#include "blackmisc/interpolatorlinear.h"
|
|
#include "blackmisc/network/textmessage.h"
|
|
#include "blackmisc/simulation/simulatorplugininfo.h"
|
|
#include "blackmisc/logmessage.h"
|
|
#include "blackmisc/propertyindexallclasses.h"
|
|
#include "blackmisc/simulation/fscommon/fscommonutil.h"
|
|
#include <QTimer>
|
|
#include <algorithm>
|
|
|
|
using namespace BlackMisc;
|
|
using namespace BlackMisc::Aviation;
|
|
using namespace BlackMisc::Network;
|
|
using namespace BlackMisc::Simulation;
|
|
using namespace BlackMisc::PhysicalQuantities;
|
|
using namespace BlackMisc::Geo;
|
|
using namespace BlackMisc::Simulation;
|
|
using namespace BlackMisc::Simulation::FsCommon;
|
|
using namespace BlackMisc::Weather;
|
|
using namespace BlackSimPlugin::Fs9;
|
|
using namespace BlackSimPlugin::FsCommon;
|
|
|
|
namespace BlackSimPlugin
|
|
{
|
|
namespace Fs9
|
|
{
|
|
CAircraftSituation aircraftSituationfromFS9(const MPPositionVelocity &positionVelocity)
|
|
{
|
|
CAircraftSituation situation;
|
|
|
|
double dHigh = positionVelocity.lat_i;
|
|
double dLow = positionVelocity.lat_f;
|
|
|
|
dLow = dLow / 65536.0;
|
|
if (dHigh > 0)
|
|
dHigh = dHigh + dLow;
|
|
else
|
|
dHigh = dHigh - dLow;
|
|
|
|
CCoordinateGeodetic position;
|
|
position.setLatitude(CLatitude(dHigh * 90.0 / 10001750.0, CAngleUnit::deg()));
|
|
|
|
dHigh = positionVelocity.lon_hi;
|
|
dLow = positionVelocity.lon_lo;
|
|
|
|
dLow = dLow / 65536.0;
|
|
if (dHigh > 0)
|
|
dHigh = dHigh + dLow;
|
|
else
|
|
dHigh = dHigh - dLow;
|
|
|
|
position.setLongitude(CLongitude(dHigh * 360.0 / (65536.0 * 65536.0), CAngleUnit::deg()));
|
|
|
|
dHigh = positionVelocity.alt_i;
|
|
dLow = positionVelocity.alt_f;
|
|
|
|
dLow = dLow / 65536.0;
|
|
|
|
situation.setPosition(position);
|
|
situation.setAltitude(CAltitude(dHigh + dLow, CAltitude::MeanSeaLevel, CLengthUnit::m()));
|
|
double groundSpeed = positionVelocity.ground_velocity / 65536.0;
|
|
situation.setGroundSpeed(CSpeed(groundSpeed, CSpeedUnit::m_s()));
|
|
|
|
FS_PBH pbhstrct;
|
|
pbhstrct.pbh = positionVelocity.pbh;
|
|
double pitch = pbhstrct.pitch / CFs9Sdk::pitchMultiplier();
|
|
if (pitch > 180.0)
|
|
pitch -= 360;
|
|
|
|
double bank = pbhstrct.bank / CFs9Sdk::bankMultiplier();
|
|
if (bank > 180.0)
|
|
bank -= 360;
|
|
|
|
situation.setPitch(CAngle(pitch, CAngleUnit::deg()));
|
|
situation.setBank(CAngle(bank, CAngleUnit::deg()));
|
|
situation.setHeading(CHeading(pbhstrct.hdg / CFs9Sdk::headingMultiplier(), CHeading::Magnetic, CAngleUnit::deg()));
|
|
|
|
return situation;
|
|
}
|
|
|
|
CSimulatorFs9::CSimulatorFs9(
|
|
const CSimulatorPluginInfo &info,
|
|
const QSharedPointer<CFs9Host> &fs9Host,
|
|
const QSharedPointer<CLobbyClient> &lobbyClient,
|
|
IOwnAircraftProvider *ownAircraftProvider,
|
|
IRemoteAircraftProvider *remoteAircraftProvider,
|
|
IWeatherGridProvider *weatherGridProvider,
|
|
QObject *parent) :
|
|
CSimulatorFsCommon(info, ownAircraftProvider, remoteAircraftProvider, weatherGridProvider, parent),
|
|
m_fs9Host(fs9Host),
|
|
m_lobbyClient(lobbyClient)
|
|
{
|
|
connect(lobbyClient.data(), &CLobbyClient::disconnected, this, std::bind(&CSimulatorFs9::simulatorStatusChanged, this, 0));
|
|
this->m_interpolator = new BlackMisc::CInterpolatorLinear(remoteAircraftProvider, this);
|
|
m_modelMatcher.setDefaultModel(CAircraftModel(
|
|
"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::ps_processFs9Message);
|
|
|
|
if (m_useFsuipc)
|
|
{
|
|
m_fsuipc->connect(); // connect FSUIPC too
|
|
}
|
|
reloadWeatherSettings();
|
|
m_dispatchTimerId = startTimer(50);
|
|
return true;
|
|
}
|
|
|
|
bool CSimulatorFs9::disconnectFrom()
|
|
{
|
|
if (!m_simConnected) { return true; }
|
|
|
|
// Don't forward messages when disconnected
|
|
disconnect(m_connectionHostMessages);
|
|
killTimer(m_dispatchTimerId);
|
|
m_dispatchTimerId = -1;
|
|
disconnectAllClients();
|
|
|
|
// disconnect FSUIPC and status
|
|
CSimulatorFsCommon::disconnectFrom();
|
|
m_simConnected = false;
|
|
emitSimulatorCombinedStatus();
|
|
return true;
|
|
}
|
|
|
|
bool CSimulatorFs9::physicallyAddRemoteAircraft(const CSimulatedAircraft &newRemoteAircraft)
|
|
{
|
|
CCallsign callsign = newRemoteAircraft.getCallsign();
|
|
if (m_hashFs9Clients.contains(callsign))
|
|
{
|
|
// already exists, remove first
|
|
this->physicallyRemoveRemoteAircraft(callsign);
|
|
}
|
|
|
|
CSimulatedAircraft newRemoteAircraftCopy(newRemoteAircraft);
|
|
// matched models
|
|
CAircraftModel aircraftModel = getClosestMatch(newRemoteAircraftCopy);
|
|
Q_ASSERT(newRemoteAircraft.getCallsign() == aircraftModel.getCallsign());
|
|
updateAircraftModel(newRemoteAircraft.getCallsign(), aircraftModel);
|
|
updateAircraftRendered(newRemoteAircraft.getCallsign(), true);
|
|
CSimulatedAircraft aircraftAfterModelApplied(getAircraftInRangeForCallsign(newRemoteAircraft.getCallsign()));
|
|
aircraftAfterModelApplied.setRendered(true);
|
|
emit modelMatchingCompleted(aircraftAfterModelApplied);
|
|
|
|
CFs9Client *client = new CFs9Client(callsign, aircraftModel.getModelString(), m_interpolator, CTime(25, CTimeUnit::ms()), this);
|
|
client->setHostAddress(m_fs9Host->getHostAddress());
|
|
client->setPlayerUserId(m_fs9Host->getPlayerUserId());
|
|
|
|
client->start();
|
|
m_hashFs9Clients.insert(callsign, client);
|
|
updateAircraftRendered(callsign, true);
|
|
CLogMessage(this).info("FS9: Added aircraft %1") << callsign.toQString();
|
|
return true;
|
|
}
|
|
|
|
bool CSimulatorFs9::physicallyRemoveRemoteAircraft(const CCallsign &callsign)
|
|
{
|
|
if (!m_hashFs9Clients.contains(callsign)) { return false; }
|
|
|
|
auto fs9Client = m_hashFs9Clients.value(callsign);
|
|
fs9Client->quit();
|
|
m_hashFs9Clients.remove(callsign);
|
|
updateAircraftRendered(callsign, false);
|
|
CLogMessage(this).info("FS9: Removed aircraft %1") << callsign.toQString();
|
|
return true;
|
|
}
|
|
|
|
int CSimulatorFs9::physicallyRemoveAllRemoteAircraft()
|
|
{
|
|
if (this->m_hashFs9Clients.isEmpty()) { return 0; }
|
|
QList<CCallsign> callsigns(this->m_hashFs9Clients.keys());
|
|
int r = 0;
|
|
for (const CCallsign &cs : callsigns)
|
|
{
|
|
if (physicallyRemoveRemoteAircraft(cs)) { r++; }
|
|
}
|
|
return r;
|
|
|
|
}
|
|
|
|
CCallsignSet CSimulatorFs9::physicallyRenderedAircraft() const
|
|
{
|
|
return CCollection<CCallsign>(this->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
|
|
CComSystem newCom1 = ownAircraft.getCom1System();
|
|
CComSystem newCom2 = ownAircraft.getCom2System();
|
|
CTransponder newTransponder = ownAircraft.getTransponder();
|
|
|
|
bool changed = false;
|
|
if (newCom1.getFrequencyActive() != this->m_simCom1.getFrequencyActive())
|
|
{
|
|
// CFrequency newFreq = newCom1.getFrequencyActive();
|
|
changed = true;
|
|
|
|
}
|
|
if (newCom1.getFrequencyStandby() != this->m_simCom1.getFrequencyStandby())
|
|
{
|
|
// CFrequency newFreq = newCom1.getFrequencyStandby();
|
|
changed = true;
|
|
}
|
|
|
|
if (newCom2.getFrequencyActive() != this->m_simCom2.getFrequencyActive())
|
|
{
|
|
// CFrequency newFreq = newCom2.getFrequencyActive();
|
|
changed = true;
|
|
}
|
|
if (newCom2.getFrequencyStandby() != this->m_simCom2.getFrequencyStandby())
|
|
{
|
|
// CFrequency newFreq = newCom2.getFrequencyStandby();
|
|
changed = true;
|
|
}
|
|
|
|
if (newTransponder.getTransponderCode() != this->m_simTransponder.getTransponderCode())
|
|
{
|
|
changed = true;
|
|
}
|
|
|
|
if (newTransponder.getTransponderMode() != this->m_simTransponder.getTransponderMode())
|
|
{
|
|
}
|
|
|
|
// avoid changes of cockpit back to old values due to an outdated read back value
|
|
|
|
// bye
|
|
return changed;
|
|
}
|
|
|
|
void CSimulatorFs9::displayStatusMessage(const BlackMisc::CStatusMessage &message) const
|
|
{
|
|
/* Avoid errors from CDirectPlayPeer as it may end in infinite loop */
|
|
if (message.getSeverity() == BlackMisc::CStatusMessage::SeverityError && message.isFromClass<CDirectPlayPeer>())
|
|
return;
|
|
|
|
if (message.getSeverity() != BlackMisc::CStatusMessage::SeverityDebug)
|
|
{
|
|
QMetaObject::invokeMethod(m_fs9Host.data(), "sendTextMessage", Q_ARG(QString, message.toQString()));
|
|
}
|
|
}
|
|
|
|
void CSimulatorFs9::displayTextMessage(const BlackMisc::Network::CTextMessage &message) const
|
|
{
|
|
this->displayStatusMessage(message.asStatusMessage(true, true));
|
|
}
|
|
|
|
bool CSimulatorFs9::isPhysicallyRenderedAircraft(const CCallsign &callsign) const
|
|
{
|
|
return m_hashFs9Clients.contains(callsign);
|
|
}
|
|
|
|
void CSimulatorFs9::timerEvent(QTimerEvent *event)
|
|
{
|
|
Q_UNUSED(event);
|
|
ps_dispatch();
|
|
}
|
|
|
|
void CSimulatorFs9::ps_dispatch()
|
|
{
|
|
if (m_useFsuipc && m_fsuipc)
|
|
{
|
|
CSimulatedAircraft fsuipcAircraft(getOwnAircraft());
|
|
bool ok = m_fsuipc->read(fsuipcAircraft, true, true, true);
|
|
if (ok)
|
|
{
|
|
updateOwnAircraftFromSimulator(fsuipcAircraft);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CSimulatorFs9::ps_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:
|
|
{
|
|
break;
|
|
}
|
|
case CFs9Sdk::MULTIPLAYER_PACKET_ID_CHANGE_PLAYER_PLANE:
|
|
{
|
|
MPChangePlayerPlane mpChangePlayerPlane;
|
|
MultiPlayerPacketParser::readMessage(message, mpChangePlayerPlane);
|
|
setOwnAircraftModel(mpChangePlayerPlane.aircraft_name);
|
|
break;
|
|
}
|
|
case CFs9Sdk::MULTIPLAYER_PACKET_ID_POSITION_VELOCITY:
|
|
{
|
|
MPPositionVelocity mpPositionVelocity;
|
|
MultiPlayerPacketParser::readMessage(message, mpPositionVelocity);
|
|
auto aircraftSituation = aircraftSituationfromFS9(mpPositionVelocity);
|
|
updateOwnSituation(aircraftSituation);
|
|
|
|
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, &CSimulatorFs9::injectWeatherGrid });
|
|
}
|
|
|
|
break;
|
|
}
|
|
case CFs9Sdk::MPCHAT_PACKET_ID_CHAT_TEXT_SEND:
|
|
{
|
|
MPChatText mpChatText;
|
|
MultiPlayerPacketParser::readMessage(message, mpChatText);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void CSimulatorFs9::updateOwnAircraftFromSimulator(const CSimulatedAircraft &simDataOwnAircraft)
|
|
{
|
|
this->updateCockpit(
|
|
simDataOwnAircraft.getCom1System(),
|
|
simDataOwnAircraft.getCom2System(),
|
|
simDataOwnAircraft.getTransponder(),
|
|
this->identifier());
|
|
}
|
|
|
|
void CSimulatorFs9::disconnectAllClients()
|
|
{
|
|
// Stop all FS9 client tasks
|
|
for (auto fs9Client : m_hashFs9Clients.keys())
|
|
{
|
|
physicallyRemoveRemoteAircraft(fs9Client);
|
|
}
|
|
}
|
|
|
|
void CSimulatorFs9::injectWeatherGrid(const Weather::CWeatherGrid &weatherGrid)
|
|
{
|
|
m_fsuipc->write(weatherGrid);
|
|
}
|
|
|
|
void CSimulatorFs9::reloadWeatherSettings()
|
|
{
|
|
if (m_fsuipc->isConnected())
|
|
{
|
|
auto selectedWeatherScenario = m_weatherScenarioSettings.get();
|
|
if (!CWeatherScenario::isRealWeatherScenario(selectedWeatherScenario))
|
|
{
|
|
m_lastWeatherPosition = {};
|
|
injectWeatherGrid(CWeatherGrid::getByScenario(selectedWeatherScenario));
|
|
}
|
|
}
|
|
}
|
|
|
|
CSimulatorFs9Listener::CSimulatorFs9Listener(const CSimulatorPluginInfo &info,
|
|
const QSharedPointer<CFs9Host> &fs9Host,
|
|
const QSharedPointer<CLobbyClient> &lobbyClient) :
|
|
BlackCore::ISimulatorListener(info),
|
|
m_timer(new QTimer(this)),
|
|
m_fs9Host(fs9Host),
|
|
m_lobbyClient(lobbyClient)
|
|
{
|
|
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.
|
|
bool canLobbyConnect = m_lobbyClient->canLobbyConnect();
|
|
|
|
connect(m_timer, &QTimer::timeout, [this, canLobbyConnect]()
|
|
{
|
|
if (m_fs9Host->getHostAddress().isEmpty()) { return; } // host not yet set up
|
|
if (canLobbyConnect)
|
|
{
|
|
if (m_isConnecting || m_lobbyClient->connectFs9ToHost(m_fs9Host->getHostAddress()) == S_OK)
|
|
{
|
|
m_isConnecting = true;
|
|
CLogMessage(this).info("Swift is joining FS9 to the multiplayer session...");
|
|
}
|
|
}
|
|
|
|
if (!m_isStarted && m_fs9Host->isConnected())
|
|
{
|
|
emit simulatorStarted(getPluginInfo());
|
|
m_isStarted = true;
|
|
m_isConnecting = false;
|
|
}
|
|
});
|
|
}
|
|
|
|
void CSimulatorFs9Listener::start()
|
|
{
|
|
m_isStarted = false;
|
|
m_timer->start();
|
|
}
|
|
|
|
void CSimulatorFs9Listener::stop()
|
|
{
|
|
m_timer->stop();
|
|
}
|
|
|
|
static void cleanupFs9Host(CFs9Host *host)
|
|
{
|
|
host->quitAndWait();
|
|
}
|
|
|
|
CSimulatorFs9Factory::CSimulatorFs9Factory(QObject *parent) :
|
|
QObject(parent),
|
|
m_fs9Host(new CFs9Host(this), cleanupFs9Host),
|
|
m_lobbyClient(new CLobbyClient(this))
|
|
{
|
|
registerMetadata();
|
|
|
|
/* After FS9 is disconnected, reset its data stored in the host */
|
|
connect(m_lobbyClient.data(), &CLobbyClient::disconnected, m_fs9Host.data(), &CFs9Host::reset);
|
|
|
|
m_fs9Host->start();
|
|
}
|
|
|
|
CSimulatorFs9Factory::~CSimulatorFs9Factory()
|
|
{
|
|
}
|
|
|
|
BlackCore::ISimulator *CSimulatorFs9Factory::create(
|
|
const CSimulatorPluginInfo &info,
|
|
IOwnAircraftProvider *ownAircraftProvider,
|
|
IRemoteAircraftProvider *remoteAircraftProvider,
|
|
IWeatherGridProvider *weatherGridProvider)
|
|
{
|
|
return new CSimulatorFs9(info, m_fs9Host, m_lobbyClient, ownAircraftProvider, remoteAircraftProvider, weatherGridProvider, this);
|
|
}
|
|
|
|
BlackCore::ISimulatorListener *CSimulatorFs9Factory::createListener(const CSimulatorPluginInfo &info)
|
|
{
|
|
return new CSimulatorFs9Listener(info, m_fs9Host, m_lobbyClient);
|
|
}
|
|
|
|
} // namespace
|
|
} // namespace
|