1 // multiplayrxmgr.cxx -- routines for receiving multiplayer data
4 // Written by Duncan McCreanor, started February 2003.
5 // duncan.mccreanor@airservicesaustralia.com
7 // Copyright (C) 2003 Airservices Australia
9 // This program is free software; you can redistribute it and/or
10 // modify it under the terms of the GNU General Public License as
11 // published by the Free Software Foundation; either version 2 of the
12 // License, or (at your option) any later version.
14 // This program is distributed in the hope that it will be useful, but
15 // WITHOUT ANY WARRANTY; without even the implied warranty of
16 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 // General Public License for more details.
19 // You should have received a copy of the GNU General Public License
20 // along with this program; if not, write to the Free Software
21 // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
30 /******************************************************************
33 * Description: The multiplayer rx manager provides control over
34 * multiplayer data reception and processing for an interactive
35 * multiplayer FlightGear simulation.
37 * The objects that hold player information are accessed via
38 * a fixed size array. A fixed array is used since it provides
39 * speed benefits over working with linked lists and is easier
40 * to code. Also, there is no point allowing for an unlimited
41 * number of players as too many players will slow the game
42 * down to the point where it is unplayable.
44 * When player position data is received, the callsign of
45 * the player is checked against existing players. If the
46 * player does not exist, a new player is created in the
47 * next free slot of the player array. If the player does
48 * exist, the player's positional matrix is updated.
50 * The Update method is used to move the players on the
51 * scene. The return value from calling MPPlayer::Draw
52 * indicates the state of the player. If data for a player
53 * has not been received data for some time, the player object
54 * is deleted and the array element freed.
56 ******************************************************************/
58 #include <sys/types.h>
59 #if !(defined(_MSC_VER) || defined(__MINGW32__))
60 # include <sys/socket.h>
61 # include <netinet/in.h>
62 # include <arpa/inet.h>
64 #include <plib/netSocket.h>
67 #include <simgear/debug/logstream.hxx>
68 #include <Main/fg_props.hxx>
70 #include "multiplayrxmgr.hxx"
71 #include "mpmessages.hxx"
72 #include "mpplayer.hxx"
74 #define MAX_PACKET_SIZE 1024
76 // These constants are provided so that the ident command can list file versions.
77 const char sMULTIPLAYTXMGR_BID[] = "$Id$";
78 const char sMULTIPLAYRXMGR_HID[] = MULTIPLAYRXMGR_HID;
82 /******************************************************************
83 * Name: FGMultiplayRxMgr
84 * Description: Constructor.
85 ******************************************************************/
86 FGMultiplayRxMgr::FGMultiplayRxMgr() {
88 int iPlayerCnt; // Count of players in player array
90 // Initialise private members
93 m_bInitialised = false;
95 // Clear the player array
96 for (iPlayerCnt = 0; iPlayerCnt < MAX_PLAYERS; iPlayerCnt++) {
97 m_Player[iPlayerCnt] = NULL;
103 /******************************************************************
104 * Name: ~FGMultiplayRxMgr
105 * Description: Destructor. Closes and deletes objects owned by
107 ******************************************************************/
108 FGMultiplayRxMgr::~FGMultiplayRxMgr() {
115 /******************************************************************
117 * Description: Initialises multiplayer receive.
118 ******************************************************************/
119 bool FGMultiplayRxMgr::init(void) {
121 bool bSuccess = true; // Result of initialisation
123 // Initialise object if not already done
124 if (!m_bInitialised) {
126 // Set members from property values
127 m_sCallsign = fgGetString("/sim/multiplay/callsign");
128 m_sRxAddress = fgGetString("/sim/multiplay/rxhost");
129 m_iRxPort = fgGetInt("/sim/multiplay/rxport");
131 SG_LOG( SG_NETWORK, SG_INFO, "FGMultiplayRxMgr::init - rxaddress= "
133 SG_LOG( SG_NETWORK, SG_INFO, "FGMultiplayRxMgr::init - rxport= "
135 SG_LOG( SG_NETWORK, SG_INFO, "FGMultiplayRxMgr::init - callsign= "
139 // Create and open rx socket
140 mDataRxSocket = new netSocket();
141 if (!mDataRxSocket->open(false)) {
142 // Failed to open rx socket
143 SG_LOG( SG_NETWORK, SG_ALERT, "FGMultiplayRxMgr::Open - Failed to create data receive socket" );
147 // Configure the socket
148 mDataRxSocket->setBlocking(false);
149 mDataRxSocket->setBroadcast(true);
150 if (mDataRxSocket->bind(m_sRxAddress.c_str(), m_iRxPort) != 0) {
153 SG_LOG( SG_NETWORK, SG_ALERT, "FGMultiplayRxMgr::Open - Failed to bind receive socket" );
159 // Save manager state
160 m_bInitialised = bSuccess;
163 SG_LOG( SG_NETWORK, SG_ALERT, "FGMultiplayRxMgr::OpenRx - Receiver open requested when receiver is already open" );
167 /* Return true if open succeeds */
173 /******************************************************************
175 * Description: Closes and deletes and player connections. Closes
176 * and deletes the rx socket. Resets the object state
178 ******************************************************************/
179 void FGMultiplayRxMgr::Close(void) {
181 int iPlayerCnt; // Count of players in player array
183 // Delete any existing players
184 for (iPlayerCnt = 0; iPlayerCnt < MAX_PLAYERS; iPlayerCnt++) {
185 if (m_Player[iPlayerCnt] != NULL) {
186 delete m_Player[iPlayerCnt];
187 m_Player[iPlayerCnt] = NULL;
193 mDataRxSocket->close();
194 delete mDataRxSocket;
195 mDataRxSocket = NULL;
198 m_bInitialised = false;
203 /******************************************************************
205 * Description: Processes data waiting at the receive socket. The
206 * processing ends when there is no more data at the socket.
207 ******************************************************************/
208 void FGMultiplayRxMgr::ProcessData(void) {
210 char sMsg[MAX_PACKET_SIZE]; // Buffer for received message
211 int iBytes; // Bytes received
212 T_MsgHdr *MsgHdr; // Pointer to header in received data
215 if (! m_bInitialised) {
219 // Read the receive socket and process any data
222 // Although the recv call asks for MAX_PACKET_SIZE of data,
223 // the number of bytes returned will only be that of the next
224 // packet waiting to be processed.
225 iBytes = mDataRxSocket->recv(sMsg, MAX_PACKET_SIZE, 0);
229 if (errno != EAGAIN) {
230 perror("FGMultiplayRxMgr::MP_ProcessData");
234 if (iBytes <= (int)sizeof(MsgHdr)) {
235 SG_LOG( SG_NETWORK, SG_ALERT,
236 "FGMultiplayRxMgr::MP_ProcessData - received message with insufficient data" );
240 MsgHdr = (T_MsgHdr *)sMsg;
241 MsgHdr->Magic = XDR_decode_uint32 (MsgHdr->Magic);
242 MsgHdr->Version = XDR_decode_uint32 (MsgHdr->Version);
243 MsgHdr->MsgId = XDR_decode_uint32 (MsgHdr->MsgId);
244 MsgHdr->iMsgLen = XDR_decode_uint32 (MsgHdr->iMsgLen);
245 MsgHdr->iReplyPort = XDR_decode_uint32 (MsgHdr->iReplyPort);
246 if (MsgHdr->Magic != MSG_MAGIC) {
247 SG_LOG( SG_NETWORK, SG_ALERT,
248 "FGMultiplayRxMgr::MP_ProcessData - message has invalid magic number!" );
250 if (MsgHdr->Version != PROTO_VER) {
251 SG_LOG( SG_NETWORK, SG_ALERT,
252 "FGMultiplayRxMgr::MP_ProcessData - message has invalid protocoll number!" );
255 // Process the player data unless we generated it
256 if (m_sCallsign == MsgHdr->sCallsign) {
261 switch(MsgHdr->MsgId) {
263 ProcessChatMsg ((char*) & sMsg);
267 ProcessPosMsg ((char*) & sMsg);
271 SG_LOG( SG_NETWORK, SG_ALERT,
272 "FGMultiplayRxMgr::MP_ProcessData - Unknown message Id received: "
276 } while (iBytes > 0);
280 void FGMultiplayRxMgr::ProcessPosMsg ( const char *Msg ) {
282 T_PositionMsg *PosMsg; // Pointer to position message in received data
283 T_MsgHdr *MsgHdr; // Pointer to header in received data
284 int iPlayerCnt; // Count of players in player array
285 bool bActivePlayer = false; // The state of the player that sent the data
288 char *sIpAddress; // Address information from header
289 struct in_addr PlayerAddress; // Used for converting the player's address into dot notation
291 MsgHdr = (T_MsgHdr *)Msg;
292 if (MsgHdr->iMsgLen != sizeof(T_MsgHdr) + sizeof(T_PositionMsg)) {
293 SG_LOG( SG_NETWORK, SG_ALERT,
294 "FGMultiplayRxMgr::MP_ProcessData - Position message received with insufficient data" );
298 PosMsg = (T_PositionMsg *)(Msg + sizeof(T_MsgHdr));
299 Position[0] = XDR_decode_double (PosMsg->PlayerPosition[0]);
300 Position[1] = XDR_decode_double (PosMsg->PlayerPosition[1]);
301 Position[2] = XDR_decode_double (PosMsg->PlayerPosition[2]);
302 Orientation[0] = XDR_decode_float (PosMsg->PlayerOrientation[0]);
303 Orientation[1] = XDR_decode_float (PosMsg->PlayerOrientation[1]);
304 Orientation[2] = XDR_decode_float (PosMsg->PlayerOrientation[2]);
305 Orientation[3] = XDR_decode_float (PosMsg->PlayerOrientation[3]);
307 // Check if the player is already in the game by using the Callsign.
308 for (iPlayerCnt = 0; iPlayerCnt < MAX_PLAYERS; iPlayerCnt++) {
309 if (m_Player[iPlayerCnt] != NULL) {
310 if (m_Player[iPlayerCnt]->CompareCallsign(MsgHdr->sCallsign)) {
311 // Player found. Update the data for the player.
312 m_Player[iPlayerCnt]->SetPosition(Orientation, Position);
313 bActivePlayer = true;
319 // nothing more to do
322 // Player not active, so add as new player
325 if (m_Player[iPlayerCnt] == NULL) {
326 PlayerAddress.s_addr = MsgHdr->lReplyAddress; // which is unecoded
327 sIpAddress = inet_ntoa(PlayerAddress);
328 SG_LOG( SG_NETWORK, SG_ALERT,
329 "FGMultiplayRxMgr::ProcessRxData - Add new player. IP: " << sIpAddress
330 << ", Call: " << MsgHdr->sCallsign
331 << ", model: " << PosMsg->sModel);
332 m_Player[iPlayerCnt] = new MPPlayer;
333 m_Player[iPlayerCnt]->Open(sIpAddress, MsgHdr->iReplyPort,
334 MsgHdr->sCallsign, PosMsg->sModel, false);
335 m_Player[iPlayerCnt]->SetPosition(Orientation, Position);
336 bActivePlayer = true;
339 } while (iPlayerCnt < MAX_PLAYERS && !bActivePlayer);
341 // Check if the player was added
342 if (!bActivePlayer) {
343 if (iPlayerCnt == MAX_PLAYERS) {
344 SG_LOG( SG_NETWORK, SG_ALERT,
345 "FGMultiplayRxMgr::MP_ProcessData - Unable to add new player ("
347 << "). Too many players." );
353 void FGMultiplayRxMgr::ProcessChatMsg ( const char *Msg ) {
355 T_ChatMsg *ChatMsg; // Pointer to chat message in received data
356 T_MsgHdr *MsgHdr; // Pointer to header in received data
358 MsgHdr = (T_MsgHdr *)Msg;
359 if (MsgHdr->iMsgLen != sizeof(T_MsgHdr) + sizeof(T_ChatMsg)) {
360 SG_LOG( SG_NETWORK, SG_ALERT,
361 "FGMultiplayRxMgr::MP_ProcessData - Chat message received with insufficient data" );
365 ChatMsg = (T_ChatMsg *)(Msg + sizeof(T_MsgHdr));
366 SG_LOG( SG_NETWORK, SG_BULK, "Chat [" << MsgHdr->sCallsign << "]" << " " << ChatMsg->sText );
369 /******************************************************************
371 * Description: For each active player, tell the player object
372 * to update its position on the scene. If a player object
373 * returns status information indicating that it has not
374 * had an update for some time then the player is deleted.
375 ******************************************************************/
376 void FGMultiplayRxMgr::Update(void) {
378 MPPlayer::TPlayerDataState ePlayerDataState;
381 for (iPlayerId = 0; iPlayerId < MAX_PLAYERS; iPlayerId++) {
382 if (m_Player[iPlayerId] != NULL) {
383 ePlayerDataState = m_Player[iPlayerId]->Draw();
385 // If the player has not received an update for some
386 // time then assume that the player has quit.
387 if (ePlayerDataState == MPPlayer::PLAYER_DATA_EXPIRED) {
388 SG_LOG( SG_NETWORK, SG_BULK, "FGMultiplayRxMgr::Update - Deleting player from game. Callsign: "
389 << m_Player[iPlayerId]->Callsign() );
390 delete m_Player[iPlayerId];
391 m_Player[iPlayerId] = NULL;
399 #endif // FG_MPLAYER_AS