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.
25 /******************************************************************
28 * Description: The multiplayer rx manager provides control over
29 * multiplayer data reception and processing for an interactive
30 * multiplayer FlightGear simulation.
32 * The objects that hold player information are accessed via
33 * a fixed size array. A fixed array is used since it provides
34 * speed benefits over working with linked lists and is easier
35 * to code. Also, there is no point allowing for an unlimited
36 * number of players as too many players will slow the game
37 * down to the point where it is unplayable.
39 * When player position data is received, the callsign of
40 * the player is checked against existing players. If the
41 * player does not exist, a new player is created in the
42 * next free slot of the player array. If the player does
43 * exist, the player's positional matrix is updated.
45 * The Update method is used to move the players on the
46 * scene. The return value from calling MPPlayer::Draw
47 * indicates the state of the player. If data for a player
48 * has not been received data for some time, the player object
49 * is deleted and the array element freed.
51 ******************************************************************/
53 #include <sys/socket.h>
54 #include <netinet/in.h>
55 #include <arpa/inet.h>
56 #include <plib/netSocket.h>
58 #include <Main/fg_props.hxx>
60 #include "multiplayrxmgr.hxx"
61 #include "mpmessages.hxx"
62 #include "mpplayer.hxx"
64 #define MAX_PACKET_SIZE 1024
66 // These constants are provided so that the ident command can list file versions.
67 const char sMULTIPLAYTXMGR_BID[] = "$Id$";
68 const char sMULTIPLAYRXMGR_HID[] = MULTIPLAYRXMGR_HID;
72 /******************************************************************
73 * Name: FGMultiplayRxMgr
74 * Description: Constructor.
75 ******************************************************************/
76 FGMultiplayRxMgr::FGMultiplayRxMgr() {
78 int iPlayerCnt; // Count of players in player array
80 // Initialise private members
83 m_bInitialised = false;
85 // Clear the player array
86 for (iPlayerCnt = 0; iPlayerCnt < MAX_PLAYERS; iPlayerCnt++) {
87 m_Player[iPlayerCnt] = NULL;
93 /******************************************************************
94 * Name: ~FGMultiplayRxMgr
95 * Description: Destructor. Closes and deletes objects owned by
97 ******************************************************************/
98 FGMultiplayRxMgr::~FGMultiplayRxMgr() {
105 /******************************************************************
107 * Description: Initialises multiplayer receive.
108 ******************************************************************/
109 bool FGMultiplayRxMgr::init(void) {
111 bool bSuccess = true; // Result of initialisation
113 // Initialise object if not already done
114 if (!m_bInitialised) {
116 // Set members from property values
117 m_sCallsign = fgGetString("/sim/multiplay/callsign");
118 m_sRxAddress = fgGetString("/sim/multiplay/rxhost");
119 m_iRxPort = fgGetInt("/sim/multiplay/rxport");
121 cout << "FGMultiplayRxMgr::init - rxaddress= " << m_sRxAddress << endl;
122 cout << "FGMultiplayRxMgr::init - rxport= " << m_iRxPort << endl;
123 cout << "FGMultiplayRxMgr::init - callsign= " << m_sCallsign << endl;
125 // Create and open rx socket
126 mDataRxSocket = new netSocket();
127 if (!mDataRxSocket->open(false)) {
128 // Failed to open rx socket
129 cerr << "FGMultiplayRxMgr::Open - Failed to create data receive socket" << endl;
133 // Configure the socket
134 mDataRxSocket->setBlocking(false);
135 mDataRxSocket->setBroadcast(true);
136 if (mDataRxSocket->bind(m_sRxAddress.c_str(), m_iRxPort) != 0) {
139 cerr << "FGMultiplayRxMgr::Open - Failed to bind receive socket" << endl;
145 // Save manager state
146 m_bInitialised = bSuccess;
149 cerr << "FGMultiplayRxMgr::OpenRx - Receiver open requested when receiver is already open" << endl;
153 /* Return true if open succeeds */
159 /******************************************************************
161 * Description: Closes and deletes and player connections. Closes
162 * and deletes the rx socket. Resets the object state
164 ******************************************************************/
165 void FGMultiplayRxMgr::Close(void) {
167 int iPlayerCnt; // Count of players in player array
169 // Delete any existing players
170 for (iPlayerCnt = 0; iPlayerCnt < MAX_PLAYERS; iPlayerCnt++) {
171 if (m_Player[iPlayerCnt] != NULL) {
172 delete m_Player[iPlayerCnt];
173 m_Player[iPlayerCnt] = NULL;
179 mDataRxSocket->close();
180 delete mDataRxSocket;
181 mDataRxSocket = NULL;
184 m_bInitialised = false;
189 /******************************************************************
191 * Description: Processes data waiting at the receive socket. The
192 * processing ends when there is no more data at the socket.
193 ******************************************************************/
194 void FGMultiplayRxMgr::ProcessData(void) {
196 char sMsg[MAX_PACKET_SIZE]; // Buffer for received message
197 int iBytes; // Bytes received
198 T_MsgHdr *MsgHdr; // Pointer to header in received data
199 T_ChatMsg *ChatMsg; // Pointer to chat message in received data
200 T_PositionMsg *PosMsg; // Pointer to position message in received data
201 char *sIpAddress; // Address information from header
202 char *sModelName; // Model that the remote player is using
203 char *sCallsign; // Callsign of the remote player
204 struct in_addr PlayerAddress; // Used for converting the player's address into dot notation
205 int iPlayerCnt; // Count of players in player array
206 bool bActivePlayer = false; // The state of the player that sent the data
207 int iPort; // Port that the remote player receives on
210 if (m_bInitialised) {
212 // Read the receive socket and process any data
215 // Although the recv call asks for MAX_PACKET_SIZE of data,
216 // the number of bytes returned will only be that of the next
217 // packet waiting to be processed.
218 iBytes = mDataRxSocket->recv(sMsg, MAX_PACKET_SIZE, 0);
222 if (iBytes >= sizeof(MsgHdr)) {
225 MsgHdr = (T_MsgHdr *)sMsg;
226 PlayerAddress.s_addr = MsgHdr->lReplyAddress;
227 sIpAddress = inet_ntoa(PlayerAddress);
228 iPort = MsgHdr->iReplyPort;
229 sCallsign = MsgHdr->sCallsign;
231 // Process the player data unless we generated it
232 if (m_sCallsign != string(MsgHdr->sCallsign)) {
236 switch(MsgHdr->MsgId) {
238 if (MsgHdr->iMsgLen == sizeof(T_MsgHdr) + sizeof(T_ChatMsg)) {
239 ChatMsg = (T_ChatMsg *)(sMsg + sizeof(T_MsgHdr));
240 cout << "Chat [" << MsgHdr->sCallsign << "]" << " " << ChatMsg->sText << endl;
242 cerr << "FGMultiplayRxMgr::MP_ProcessData - Chat message received with insufficient data" << endl;
247 if (MsgHdr->iMsgLen == sizeof(T_MsgHdr) + sizeof(T_PositionMsg)) {
248 PosMsg = (T_PositionMsg *)(sMsg + sizeof(T_MsgHdr));
249 sModelName = PosMsg->sModel;
251 // Check if the player is already in the game by using the Callsign.
252 bActivePlayer = false;
253 for (iPlayerCnt = 0; iPlayerCnt < MAX_PLAYERS; iPlayerCnt++) {
254 if (m_Player[iPlayerCnt] != NULL) {
255 if (m_Player[iPlayerCnt]->CompareCallsign(MsgHdr->sCallsign)) {
257 // Player found. Update the data for the player.
258 m_Player[iPlayerCnt]->SetPosition(PosMsg->PlayerPos);
259 bActivePlayer = true;
265 // Player not active, so add as new player
266 if (!bActivePlayer) {
269 if (m_Player[iPlayerCnt] == NULL) {
270 cout << "FGMultiplayRxMgr::ProcessRxData - Add new player. IP: " << sIpAddress
271 << ", Call: " << sCallsign << ", model: " << sModelName << endl;
272 m_Player[iPlayerCnt] = new MPPlayer;
273 m_Player[iPlayerCnt]->Open(string(sIpAddress), iPort, string(sCallsign), string(sModelName), false);
274 m_Player[iPlayerCnt]->SetPosition(PosMsg->PlayerPos);
275 bActivePlayer = true;
278 } while (iPlayerCnt < MAX_PLAYERS && !bActivePlayer);
280 // Check if the player was added
281 if (!bActivePlayer) {
282 if (iPlayerCnt == MAX_PLAYERS) {
283 cerr << "FGMultiplayRxMgr::MP_ProcessData - Unable to add new player (" << sCallsign << "). Too many players." << endl;
289 cerr << "FGMultiplayRxMgr::MP_ProcessData - Position message received with insufficient data" << endl;
294 cerr << "FGMultiplayRxMgr::MP_ProcessData - Unknown message Id received: " << MsgHdr->MsgId << endl;
304 } else if (iBytes == -1) {
305 if (errno != EAGAIN) {
306 perror("FGMultiplayRxMgr::MP_ProcessData");
310 } while (iBytes > 0);
318 /******************************************************************
320 * Description: For each active player, tell the player object
321 * to update its position on the scene. If a player object
322 * returns status information indicating that it has not
323 * had an update for some time then the player is deleted.
324 ******************************************************************/
325 void FGMultiplayRxMgr::Update(void) {
327 int iPlayerDataState;
330 for (iPlayerId = 0; iPlayerId < MAX_PLAYERS; iPlayerId++) {
331 if (m_Player[iPlayerId] != NULL) {
332 iPlayerDataState = m_Player[iPlayerId]->Draw();
334 // If the player has not received an update for some
335 // time then assume that the player has quit.
336 if (iPlayerDataState == PLAYER_DATA_EXPIRED) {
337 delete m_Player[iPlayerId];
338 m_Player[iPlayerId] = NULL;