1
0
Fork 0

Refactored CPDLP.

This commit is contained in:
Henning Stahlke 2020-11-23 02:57:25 +01:00 committed by James Turner
parent 06bd0708b9
commit 527f58d353
7 changed files with 819 additions and 11 deletions

View file

@ -3,13 +3,17 @@ include(FlightGearComponent)
set(SOURCES
multiplaymgr.cxx
tiny_xdr.cxx
MPServerResolver.cxx
MPServerResolver.cxx
mpirc.cxx
cpdlc.cxx
)
set(HEADERS
multiplaymgr.hxx
tiny_xdr.hxx
MPServerResolver.hxx
MPServerResolver.hxx
mpirc.hxx
cpdlc.hxx
)
flightgear_component(MultiPlayer "${SOURCES}" "${HEADERS}")

182
src/MultiPlayer/cpdlc.cxx Normal file
View file

@ -0,0 +1,182 @@
//////////////////////////////////////////////////////////////////////
//
// cpdlc.cxx
//
// started November 2020
// Authors: Michael Filhol, Henning Stahlke
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
//
//////////////////////////////////////////////////////////////////////
#include "cpdlc.hxx"
#include <Main/fg_props.hxx>
///////////////////////////////////////////////////////////////////////////////
// CPDLCManager
///////////////////////////////////////////////////////////////////////////////
CPDLCManager::CPDLCManager(IRCConnection* irc) :
_irc(irc)
{
// CPDLC link status props
_pStatus = fgGetNode("/network/cpdlc/link/status", true);
_pStatus->setIntValue(_status);
_pDataAuthority = fgGetNode("/network/cpdlc/link/data-authority", true);
_pDataAuthority->setStringValue(_data_authority);
// CPDLC message output
_pMessage = fgGetNode("/network/cpdlc/rx/message", true);
_pMessage->setStringValue("");
_pNewMessage = fgGetNode("/network/cpdlc/rx/new-message", true);
_pNewMessage->setBoolValue(0);
}
CPDLCManager::~CPDLCManager()
{
}
/*
connect will be called by
1) user via a fgcommand defined in multiplaymgr
2) CPDLC::update() while waiting for IRC to become ready
authority parameter is accepted only when coming from disconnected state,
disconnect must be called first if user wants to change authority (however, ATC
handover is not blocked by this)
IRC connection will be initiated if necessary, it takes a while to connect to IRC
which is why connect is called from update() when in state CPDLC_WAIT_IRC_READY
*/
bool CPDLCManager::connect(const std::string authority = "")
{
// ensure we get an authority on first call but do not accept a change before
// resetting _data_authority in disconnect()
if (!_data_authority.size()) {
if (!authority.size()) {
SG_LOG(SG_NETWORK, SG_WARN, "cpdlcConnect not possible: empty argument!");
return false;
} else {
_data_authority = authority;
}
}
if (authority.size() && authority != _data_authority) {
SG_LOG(SG_NETWORK, SG_WARN, "cpdlcConnect: cannot change authority now, use disconnect first!");
return false;
}
// launch IRC connection as needed
if (!_irc->isConnected()) {
SG_LOG(SG_NETWORK, SG_DEV_WARN, "Connecting to IRC server...");
if (!_irc->login()) {
return false;
}
_status = CPDLC_WAIT_IRC_READY;
return true;
} else if (_irc->isReady() && _status != CPDLC_ONLINE) {
SG_LOG(SG_NETWORK, SG_DEV_WARN, "CPDLC send connect");
_status = CPDLC_CONNECTING;
_pStatus->setIntValue(_status);
return _irc->sendPrivmsg(_data_authority, CPDLC_MSGPREFIX_CONNECT);
}
return false;
}
void CPDLCManager::disconnect()
{
if (_irc && _irc->isConnected() && _data_authority.size()) {
_irc->sendPrivmsg(_data_authority, CPDLC_MSGPREFIX_DISCONNECT);
}
_data_authority = "";
_pDataAuthority->setStringValue(_data_authority);
_status = CPDLC_OFFLINE;
_pStatus->setIntValue(_status);
}
bool CPDLCManager::send(const std::string message)
{
std::string textline(CPDLC_MSGPREFIX_MSG); // TODO surely a way to format std string in line
textline += ' ';
textline += message;
return _irc->sendPrivmsg(_data_authority, textline);
}
// move next message from input queue to property tree
void CPDLCManager::getMessage()
{
if (!_incoming_messages.empty()) {
struct IRCMessage entry = _incoming_messages.front();
_incoming_messages.pop_front();
_pMessage->setStringValue(entry.textline.substr(CPDLC_MSGPREFIX_MSG.length() + 1));
if (_incoming_messages.empty()) {
_pNewMessage->setBoolValue(0);
}
}
}
/*
update() call this regularly to complete connect and check for new messages
update frequency requirement should be low (e.g. once per second) but frame rate
will not hurt as the code is slim
*/
void CPDLCManager::update()
{
if (_irc) {
if (_irc->isConnected()) {
if (_status == CPDLC_WAIT_IRC_READY && _irc->isReady()) {
SG_LOG(SG_NETWORK, SG_DEV_WARN, "CPDLC IRC ready, connecting...");
connect();
}
if (_irc->hasMessage()) {
processMessage(_irc->getMessage());
}
}
// IRC disconnected (unexpectedly)
else if (_status != CPDLC_OFFLINE) {
_status = CPDLC_OFFLINE;
_pStatus->setIntValue(_status);
}
}
}
// process incoming message
void CPDLCManager::processMessage(struct IRCMessage message)
{
SG_LOG(SG_NETWORK, SG_INFO, "CPDLC message");
// connection accepted by ATC, or new data authority (been transferred)
if (message.textline.find(CPDLC_MSGPREFIX_CONNECT) == 0) {
_data_authority = message.sender;
_pDataAuthority->setStringValue(_data_authority); // make this known to ACFT
_status = CPDLC_ONLINE;
_pStatus->setIntValue(_status);
}
// do not process message if sender does not match our current data authority
if (message.sender != _data_authority) {
SG_LOG(SG_NETWORK, SG_WARN, "Received CPDLC message from foreign authority.");
return;
}
// connection rejected, or terminated by ATC
if (message.textline.find(CPDLC_MSGPREFIX_DISCONNECT) == 0) {
if (message.sender == _data_authority) {
disconnect();
}
}
// store valid message in queue for later retrieval by aircraft
else if (message.textline.find(CPDLC_MSGPREFIX_MSG) == 0) {
_incoming_messages.push_back(message);
_pNewMessage->setBoolValue(1);
}
}

82
src/MultiPlayer/cpdlc.hxx Normal file
View file

@ -0,0 +1,82 @@
//////////////////////////////////////////////////////////////////////
//
// cpdlc.hxx
//
// started November 2020
// Authors: Henning Stahlke, Michael Filhol
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
//
//////////////////////////////////////////////////////////////////////
#ifndef CPDLC_H
#define CPDLC_H
#include <deque>
#include <string>
#include <simgear/compiler.h>
#include <simgear/props/props.hxx>
#include <simgear/io/raw_socket.hxx>
#include <simgear/io/sg_socket.hxx>
#include "mpirc.hxx"
const std::string CPDLC_IRC_SERVER {"mpirc.flightgear.org"};
const std::string CPDLC_MSGPREFIX_CONNECT {"___CPDLC_CONNECT___"};
const std::string CPDLC_MSGPREFIX_MSG {"___CPDLC_MSG___"};
const std::string CPDLC_MSGPREFIX_DISCONNECT {"___CPDLC_DISCONNECT___"};
enum CPDLCStatus {
CPDLC_OFFLINE,
CPDLC_CONNECTING,
CPDLC_ONLINE,
// private (not exposed to property tree) below this line:
CPDLC_WAIT_IRC_READY,
};
//
// CPDLCManager implements a ControllerPilotDataLinkConnection via an IRC connection
//
class CPDLCManager
{
public:
CPDLCManager(IRCConnection* irc);
~CPDLCManager();
bool connect(const std::string authority);
void disconnect();
bool send(const std::string message);
void getMessage();
void update();
private:
IRCConnection *_irc;
std::string _data_authority {""};
std::deque<struct IRCMessage> _incoming_messages;
CPDLCStatus _status {CPDLC_OFFLINE};
SGPropertyNode *_pStatus {nullptr};
SGPropertyNode *_pDataAuthority {nullptr};
SGPropertyNode *_pMessage {nullptr};
SGPropertyNode *_pNewMessage {nullptr};
void processMessage(struct IRCMessage entry);
};
#endif

304
src/MultiPlayer/mpirc.cxx Normal file
View file

@ -0,0 +1,304 @@
//////////////////////////////////////////////////////////////////////
//
// mpirc.cxx
//
// started November 2020
// Authors: Michael Filhol, Henning Stahlke
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
//
//////////////////////////////////////////////////////////////////////
#include "mpirc.hxx"
#include <Main/fg_props.hxx>
const std::string IRC_TEST_CHANNEL{"#mptest"}; // for development
IRCConnection::IRCConnection(const std::string nickname, const std::string servername, const std::string port) : SGSocket(servername, port, "tcp"),
_nickname(nickname)
{
}
IRCConnection::~IRCConnection()
{
}
// setup properties to reflect the status of this IRC connection in the prop tree
void IRCConnection::setupProperties(std::string path)
{
if (path.back() != '/') path.push_back('/');
if (!_pReadyFlag) _pReadyFlag = fgGetNode(path + "irc-ready", true);
_pReadyFlag->setBoolValue(_logged_in);
if (!_pMessageCountIn) _pMessageCountIn = fgGetNode(path + "msg-count-in", true);
if (!_pMessageCountOut) _pMessageCountOut = fgGetNode(path + "msg-count-out", true);
if (!_pIRCReturnCode) _pIRCReturnCode = fgGetNode(path + "last-return-code", true);
}
bool IRCConnection::login(const std::string nickname)
{
if (!_connected && !connect()) {
return false;
}
if (nickname.length()) {
_nickname = nickname;
} else {
return false;
}
std::string lines("NICK ");
lines += _nickname;
lines += IRC_MSG_TERMINATOR;
lines += "USER ";
lines += _nickname; //IRC <user>
lines += " 0 * :"; //IRC <mode> <unused>
lines += _nickname; //IRC <realname>
lines += IRC_MSG_TERMINATOR;
return writestring(lines.c_str());
}
// login with nickname given to constructor
bool IRCConnection::login()
{
return login(_nickname);
}
// the polite way to leave
void IRCConnection::quit()
{
if (!_connected) return;
writestring("QUIT goodbye\r\n");
disconnect();
}
bool IRCConnection::sendPrivmsg(const std::string recipient, const std::string textline)
{
if (!_logged_in) return false;
std::string line("PRIVMSG ");
line += recipient;
line += " :";
line += textline;
line += IRC_MSG_TERMINATOR;
if (writestring(line.c_str())) {
if (_pMessageCountOut) _pMessageCountOut->setIntValue(_pMessageCountOut->getIntValue() + 1);
if (_pIRCReturnCode) _pIRCReturnCode->setStringValue("");
return true;
} else {
return false;
}
}
// join an IRC channel
bool IRCConnection::join(const std::string channel)
{
if (!_logged_in) return false;
std::string lines("JOIN ");
lines += channel;
lines += IRC_MSG_TERMINATOR;
return writestring(lines.c_str());
}
// leave an IRC channel
bool IRCConnection::part(const std::string channel)
{
if (!_logged_in) return false;
std::string lines("PART ");
lines += channel;
lines += IRC_MSG_TERMINATOR;
return writestring(lines.c_str());
}
/*
Call update() regularly to maintain connection (ping/pong) and process messages.
For information only:
The ping timeout appears to depend on the server settings and can be in the order
of minutes. However, for smooth message processing the update frequency should be
at least a few times per second and calling this at frame rate should not hurt.
*/
void IRCConnection::update()
{
if (_connected && readline(_read_buffer, sizeof(_read_buffer) - 1) > 0) {
std::string line(_read_buffer); // TODO: buffer size check required?
parseReceivedLine(line);
}
}
///////////////////////////////////////////////////////////////////////////////
// private methods
///////////////////////////////////////////////////////////////////////////////
// open a connection to IRC server
bool IRCConnection::connect()
{
if (_connected) return false;
_connected = open(SG_IO_OUT);
if (_connected) {
nonblock();
} else {
disconnect();
SG_LOG(SG_NETWORK, SG_WARN, "IRCConnection::connect error");
}
return _connected;
}
void IRCConnection::disconnect()
{
_logged_in = false;
if (_pReadyFlag) _pReadyFlag->setBoolValue(_logged_in);
if (_connected) {
_connected = false;
close();
SG_LOG(SG_NETWORK, SG_INFO, "IRCConnection::disconnect");
}
}
void IRCConnection::pong(const std::string recipient)
{
if (!_connected) return;
std::string line("PONG ");
line += recipient;
line += IRC_MSG_TERMINATOR;
writestring(line.c_str());
}
bool IRCConnection::parseReceivedLine(std::string line)
{
/*
https://tools.ietf.org/html/rfc2812#section-3.7.2
2.3.1 Message format in Augmented BNF
The protocol messages must be extracted from the contiguous stream of
octets. The current solution is to designate two characters, CR and
LF, as message separators. Empty messages are silently ignored,
which permits use of the sequence CR-LF between messages without
extra problems.
The extracted message is parsed into the components <prefix>,
<command> and list of parameters (<params>).
The Augmented BNF representation for this is:
message = [ ":" prefix SPACE ] command [ params ] crlf
prefix = servername / ( nickname [ [ "!" user ] "@" host ] )
command = 1*letter / 3digit
params = *14( SPACE middle ) [ SPACE ":" trailing ]
=/ 14( SPACE middle ) [ SPACE [ ":" ] trailing ]
nospcrlfcl = %x01-09 / %x0B-0C / %x0E-1F / %x21-39 / %x3B-FF
; any octet except NUL, CR, LF, " " and ":"
middle = nospcrlfcl *( ":" / nospcrlfcl )
trailing = *( ":" / " " / nospcrlfcl )
SPACE = %x20 ; space character
crlf = %x0D %x0A ; "carriage return" "linefeed"
*/
// removes trailing '\r\n'
// TODO: for length(IRC_MSG_TERMINATOR) do line.pop_back();
line.pop_back();
line.pop_back();
std::string prefix;
std::string command;
std::string params;
std::size_t pos = line.find(" ", 1);
//prefix
if (line.at(0) == ':') {
prefix = line.substr(1, pos - 1); // remove leading ":"
std::size_t end = line.find(" ", pos + 1);
command = line.substr(pos + 1, end - pos - 1);
pos = end;
} else {
command = line.substr(0, pos);
}
params = line.substr(pos + 1);
// uncomment next line for debug output
//cout << "[prefix]" << prefix << "[cmd]" << command << "[params]" << params << "[end]" << endl;
// receiving a message
if (command == "PRIVMSG") {
if (_pMessageCountIn) _pMessageCountIn->setIntValue(_pMessageCountIn->getIntValue() + 1);
std::string recipient = params.substr(0, params.find(" :"));
// direct private message
if (recipient == _nickname) {
struct IRCMessage rcv;
rcv.sender = prefix.substr(0, prefix.find("!"));
rcv.textline = params.substr(params.find(":") + 1);
_incoming_private_messages.push_back(rcv);
} else {
// Most likely from an IRC channel if we joined any. In this case
// recipient equals channel name (e.g. "#mptest"). IRC channel
// support could be implemented here in future.
SG_LOG(SG_NETWORK, SG_DEV_WARN, "Ignoring PRIVMSG to '" + recipient + "' (should be '" + _nickname + "')");
}
} else if (command == "PING") {
// server pings us
std::string server = params.substr(0, params.find(" "));
pong(server);
} else if (command == "JOIN") {
// server acks our join request
std::string channel = params.substr(0, params.find(" "));
SG_LOG(SG_NETWORK, SG_DEV_WARN, "Joined IRC channel " + channel); //DEBUG
} else if (command == IRC_RPL_WELCOME) {
// after welcome we are logged in and allowed to send commands/messages to the IRC
_logged_in = true;
if (_pReadyFlag) _pReadyFlag->setBoolValue(1);
//joining channel might help while development, maybe removed later
//join(IRC_TEST_CHANNEL);
}
else if (command == IRC_RPL_MOTD) {
}
else if (command == IRC_RPL_MOTDSTART) {
}
else if (command == IRC_RPL_ENDOFMOTD) {
}
else if (command == IRC_ERR_NOSUCHNICK) {
// server return code if we send to invalid nickname
if (_pIRCReturnCode) _pIRCReturnCode->setStringValue(IRC_ERR_NOSUCHNICK);
}
else if (command == "ERROR") {
if (_pIRCReturnCode) _pIRCReturnCode->setStringValue(params);
disconnect();
}
// unexpected IRC message
else {
//SG_LOG(SG_NETWORK, SG_MANDATORY_INFO, "Unhandled IRC message "); //DEBUG
//cout << "[prefix]" << prefix << "[cmd]" << command << "[params]" << params << "[end]" << endl;
// TODO: anything sensitive here that we should handle?
// e.g. IRC user has disconnected and username == {current-cpdlc-authority}
}
return true;
}
IRCMessage IRCConnection::getMessage()
{
struct IRCMessage entry {
"", ""
};
if (!_incoming_private_messages.empty()) {
entry = _incoming_private_messages.front();
_incoming_private_messages.pop_front();
}
return entry;
}

105
src/MultiPlayer/mpirc.hxx Normal file
View file

@ -0,0 +1,105 @@
//////////////////////////////////////////////////////////////////////
//
// mpirc.hxx
//
// started November 2020
// Authors: Henning Stahlke, Michael Filhol
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
//
//////////////////////////////////////////////////////////////////////
#ifndef MPIRC_H
#define MPIRC_H
#include <deque>
#include <string>
#include <simgear/compiler.h>
#include <simgear/props/props.hxx>
#include <simgear/io/raw_socket.hxx>
#include <simgear/io/sg_socket.hxx>
const std::string IRC_DEFAULT_PORT {"6667"};
const std::string IRC_MSG_TERMINATOR {"\r\n"};
// https://www.alien.net.au/irc/irc2numerics.html
const std::string IRC_RPL_WELCOME {"001"};
const std::string IRC_RPL_YOURID {"042"};
const std::string IRC_RPL_MOTD {"372"};
const std::string IRC_RPL_MOTDSTART {"375"};
const std::string IRC_RPL_ENDOFMOTD {"376"};
const std::string IRC_ERR_NOSUCHNICK {"401"};
const int IRC_BUFFER_SIZE = 1024;
struct IRCMessage {
std::string sender;
std::string textline;
};
/*
IRCConnection implements a basic IRC client for transmitting and receiving
private messages via an IRC server
In general it is possible to have multiple instances of this class but you
have to consider the following points:
- you cannot connect to a server with the same nickname more than once at a time
- if you want to expose status info to the property tree, you have to pass
an unique prefix to setupProperties() per instance
*/
class IRCConnection : SGSocket
{
public:
IRCConnection(const std::string nickname, const std::string servername, const std::string port = IRC_DEFAULT_PORT);
~IRCConnection();
void setupProperties(const std::string path);
void update();
bool login(const std::string nickname);
bool login();
void quit();
bool sendPrivmsg(const std::string recipient, const std::string textline);
bool join(const std::string channel);
bool part(const std::string channel);
bool isConnected() const { return _connected; }
bool isReady() const { return _logged_in; }
bool hasMessage() const { return !_incoming_private_messages.empty(); }
IRCMessage getMessage();
private:
bool connect();
void disconnect();
void pong(const std::string recipient);
bool parseReceivedLine(std::string irc_line);
bool _connected {false}; // TCP session ok
bool _logged_in {false}; // IRC login completed
std::string _nickname {""};
char _read_buffer[IRC_BUFFER_SIZE];
std::deque<struct IRCMessage> _incoming_private_messages;
SGPropertyNode *_pReadyFlag {nullptr};
SGPropertyNode *_pMessageCountIn {nullptr};
SGPropertyNode *_pMessageCountOut {nullptr};
SGPropertyNode *_pIRCReturnCode {nullptr};
};
#endif

View file

@ -33,16 +33,17 @@
#include <algorithm>
#include <cstring>
#include <errno.h>
#include <memory>
#include <simgear/misc/stdint.hxx>
#include <simgear/misc/sg_dir.hxx>
#include <simgear/timing/timestamp.hxx>
#include <simgear/debug/logstream.hxx>
#include <simgear/misc/sg_dir.hxx>
#include <simgear/misc/stdint.hxx>
#include <simgear/misc/strutils.hxx>
#include <simgear/props/props.hxx>
#include <simgear/props/props_io.hxx>
#include <simgear/structure/commands.hxx>
#include <simgear/structure/event_mgr.hxx>
#include <simgear/misc/strutils.hxx>
#include <simgear/timing/timestamp.hxx>
#include <AIModel/AIManager.hxx>
#include <AIModel/AIMultiplayer.hxx>
@ -59,6 +60,7 @@
#endif
using namespace std;
#define MAX_PACKET_SIZE 1200
#define MAX_TEXT_SIZE 768 // Increased for 2017.3 to allow for long Emesary messages.
/*
@ -943,6 +945,86 @@ do_multiplayer_refreshserverlist (const SGPropertyNode * arg, SGPropertyNode * r
return true;
}
//////////////////////////////////////////////////////////////////////
//
// CPDLC commands: cpdlc-connect, cpdlc-send-msg, cpdlc-disconnect
//
//////////////////////////////////////////////////////////////////////
static bool do_cpdlc_connect(const SGPropertyNode* arg, SGPropertyNode* root)
{
FGMultiplayMgr* self = (FGMultiplayMgr*)globals->get_subsystem("mp");
if (!self) {
SG_LOG(SG_NETWORK, SG_WARN, "Multiplayer subsystem not available.");
return false;
}
// check for atc argument
std::string authority = arg->getStringValue("atc");
// otherwise see if we got a property name to read out
if (!authority.size()) {
std::string name = arg->getStringValue("property");
if (name.size()) {
SGPropertyNode* pNode = globals->get_props()->getNode(name);
if (!pNode) { return false; }
authority = pNode->getStringValue();
}
}
if (self->getCPDLC()) {
return self->getCPDLC()->connect(authority);
}
return false;
}
static bool do_cpdlc_send_msg(const SGPropertyNode* arg, SGPropertyNode* root)
{
FGMultiplayMgr* self = (FGMultiplayMgr*)globals->get_subsystem("mp");
if (!self) {
SG_LOG(SG_NETWORK, SG_WARN, "Multiplayer subsystem not available.");
return false;
}
// check for message argument
std::string message = arg->getStringValue("message");
// otherwise see if we got a property name to read out
if (!message.size()) {
std::string name = arg->getStringValue("property");
if (name.size()) {
SGPropertyNode* pNode = globals->get_props()->getNode(name);
if (!pNode) { return false; }
message = pNode->getStringValue();
}
}
if (self->getCPDLC()) {
return self->getCPDLC()->send(message);
}
return false;
}
static bool do_cpdlc_next_msg(const SGPropertyNode* arg, SGPropertyNode* root)
{
FGMultiplayMgr* self = (FGMultiplayMgr*)globals->get_subsystem("mp");
if (!self) {
SG_LOG(SG_NETWORK, SG_WARN, "Multiplayer subsystem not available.");
return false;
}
if (self->getCPDLC()) self->getCPDLC()->getMessage();
return true;
}
static bool do_cpdlc_disconnect(const SGPropertyNode* arg, SGPropertyNode* root)
{
FGMultiplayMgr* self = (FGMultiplayMgr*)globals->get_subsystem("mp");
if (!self) {
SG_LOG(SG_NETWORK, SG_WARN, "Multiplayer subsystem not available.");
return false;
}
if (self->getCPDLC()) self->getCPDLC()->disconnect();
return true;
}
//////////////////////////////////////////////////////////////////////
//
// MultiplayMgr constructor
@ -956,6 +1038,13 @@ FGMultiplayMgr::FGMultiplayMgr()
globals->get_commands()->addCommand("multiplayer-connect", do_multiplayer_connect);
globals->get_commands()->addCommand("multiplayer-disconnect", do_multiplayer_disconnect);
globals->get_commands()->addCommand("multiplayer-refreshserverlist", do_multiplayer_refreshserverlist);
globals->get_commands()->addCommand("cpdlc-connect", do_cpdlc_connect);
globals->get_commands()->addCommand("cpdlc-send", do_cpdlc_send_msg);
globals->get_commands()->addCommand("cpdlc-next-message", do_cpdlc_next_msg);
globals->get_commands()->addCommand("cpdlc-disconnect", do_cpdlc_disconnect);
pXmitLen = fgGetNode("/sim/multiplay/last-xmit-packet-len", true);
pProtocolVersion = fgGetNode("/sim/multiplay/protocol-version", true);
pMultiPlayDebugLevel = fgGetNode("/sim/multiplay/debug-level", true);
@ -963,6 +1052,8 @@ FGMultiplayMgr::FGMultiplayMgr()
pMultiPlayRange = fgGetNode("/sim/multiplay/visibility-range-nm", true);
pMultiPlayRange->setIntValue(100);
pReplayState = fgGetNode("/sim/replay/replay-state", true);
} // FGMultiplayMgr::FGMultiplayMgr()
//////////////////////////////////////////////////////////////////////
@ -976,6 +1067,11 @@ FGMultiplayMgr::~FGMultiplayMgr()
globals->get_commands()->removeCommand("multiplayer-connect");
globals->get_commands()->removeCommand("multiplayer-disconnect");
globals->get_commands()->removeCommand("multiplayer-refreshserverlist");
globals->get_commands()->removeCommand("cpdlc-connect");
globals->get_commands()->removeCommand("cpdlc-send");
globals->get_commands()->removeCommand("cpdlc-next-message");
globals->get_commands()->removeCommand("cpdlc-disconnect");
} // FGMultiplayMgr::~FGMultiplayMgr()
//////////////////////////////////////////////////////////////////////
@ -1080,6 +1176,16 @@ FGMultiplayMgr::init (void)
// multiplayer depends on AI module
fgSetBool("/sim/ai/enabled", true);
}
// MP IRC CONNECTION SETUP
std::string host = fgHasNode(MPIRC_SERVER_HOST_PROPERTY) ? fgGetString(MPIRC_SERVER_HOST_PROPERTY) : MPIRC_SERVER_HOST_DEFAULT;
std::string port = fgHasNode(MPIRC_SERVER_PORT_PROPERTY) ? fgGetString(MPIRC_SERVER_PORT_PROPERTY) : IRC_DEFAULT_PORT;
SG_LOG(SG_NETWORK, SG_DEBUG, "Creating socket to MP IRC service " + host + " on port " + port);
_mpirc = std::make_unique<IRCConnection>(MPIRC_NICK_PREFIX + mCallsign, host, port);
_mpirc->setupProperties("/network/mpirc/");
_cpdlc = std::make_unique<CPDLCManager>(_mpirc.get());
} // FGMultiplayMgr::init()
//////////////////////////////////////////////////////////////////////
@ -1113,7 +1219,13 @@ FGMultiplayMgr::shutdown (void)
}
mInitialised = false;
} // FGMultiplayMgr::Close(void)
if (_cpdlc) _cpdlc->disconnect();
if (_mpirc) _mpirc->quit();
_cpdlc.reset();
_mpirc.reset();
} // FGMultiplayMgr::shutdown(void)
//////////////////////////////////////////////////////////////////////
void
@ -1827,7 +1939,7 @@ int FGMultiplayMgr::GetMsg(MsgBuf& msgBuf, simgear::IPAddress& SenderAddress)
//////////////////////////////////////////////////////////////////////
//
// Name: ProcessData
// Name: update
// Description: Processes data waiting at the receive socket. The
// processing ends when there is no more data at the socket.
//
@ -1926,7 +2038,14 @@ FGMultiplayMgr::update(double dt)
} else
++it;
}
} // FGMultiplayMgr::ProcessData(void)
if (_mpirc) {
_mpirc->update();
}
if (_cpdlc) {
_cpdlc->update();
}
} // FGMultiplayMgr::update(void)
//////////////////////////////////////////////////////////////////////
void FGMultiplayMgr::ClearMotion()

View file

@ -44,11 +44,20 @@ const int MAX_MP_PROTOCOL_VERSION = 2;
#include <simgear/io/raw_socket.hxx>
#include <simgear/structure/subsystem_mgr.hxx>
#include "mpirc.hxx"
#include "cpdlc.hxx"
const std::string MPIRC_SERVER_HOST_DEFAULT {"mpirc.flightgear.org"};
const std::string MPIRC_SERVER_HOST_PROPERTY {"/network/mpirc/server-host"};
const std::string MPIRC_SERVER_PORT_PROPERTY {"/network/mpirc/server-port"};
const std::string MPIRC_NICK_PREFIX {"MP_IRC_"};
struct FGExternalMotionData;
class MPPropertyListener;
struct T_MsgHdr;
class FGAIMultiplayer;
class FGMultiplayMgr : public SGSubsystem
{
public:
@ -69,7 +78,7 @@ public:
void SendTextMessage(const std::string &sMsgText);
// receiver
FGAIMultiplayer* getMultiplayer(const std::string& callsign);
FGAIMultiplayer* getMultiplayer(const std::string& callsign);
std::shared_ptr<vector<char>> popMessageHistory();
void pushMessageHistory(std::shared_ptr<vector<char>> message);
@ -77,8 +86,11 @@ public:
// Remove motion information for all multiplayer aircraft, e.g. when
// scrubbing during replay.
void ClearMotion();
CPDLCManager *getCPDLC() { return _cpdlc.get(); };
private:
std::unique_ptr<IRCConnection> _mpirc;
std::unique_ptr<CPDLCManager> _cpdlc;
friend class MPPropertyListener;
void setPropertiesChanged()