]> git.mxchange.org Git - flightgear.git/blob - src/MultiPlayer/multiplayrxmgr.cxx
Improved tumbling behaviour -- the AI doesn't just freeze now, but
[flightgear.git] / src / MultiPlayer / multiplayrxmgr.cxx
1 // multiplayrxmgr.cxx -- routines for receiving multiplayer data
2 //                       for Flightgear
3 //
4 // Written by Duncan McCreanor, started February 2003.
5 // duncan.mccreanor@airservicesaustralia.com
6 //
7 // Copyright (C) 2003  Airservices Australia
8 //
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.
13 //
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.
18 //
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.
22 //
23
24
25 /******************************************************************
26 * $Id$
27 *
28 * Description: The multiplayer rx manager provides control over
29 * multiplayer data reception and processing for an interactive
30 * multiplayer FlightGear simulation.
31 *
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.
38 *
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.
44 *
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.
50 *
51 ******************************************************************/
52
53 #include <sys/types.h>
54 #ifndef _MSC_VER
55 # include <sys/socket.h>
56 # include <netinet/in.h>
57 # include <arpa/inet.h>
58 #endif
59 #include <plib/netSocket.h>
60 #include <stdlib.h>
61
62 #include <simgear/debug/logstream.hxx>
63 #include <Main/fg_props.hxx>
64
65 #include "multiplayrxmgr.hxx"
66 #include "mpmessages.hxx"
67 #include "mpplayer.hxx"
68
69 #define MAX_PACKET_SIZE 1024
70
71 // These constants are provided so that the ident command can list file versions.
72 const char sMULTIPLAYTXMGR_BID[] = "$Id$";
73 const char sMULTIPLAYRXMGR_HID[] = MULTIPLAYRXMGR_HID;
74
75
76
77 /******************************************************************
78 * Name: FGMultiplayRxMgr
79 * Description: Constructor.
80 ******************************************************************/
81 FGMultiplayRxMgr::FGMultiplayRxMgr() {
82
83     int iPlayerCnt;         // Count of players in player array
84
85     // Initialise private members
86     m_sRxAddress = "0";
87     m_iRxPort = 0;
88     m_bInitialised = false;
89
90     // Clear the player array
91     for (iPlayerCnt = 0; iPlayerCnt < MAX_PLAYERS; iPlayerCnt++) {
92         m_Player[iPlayerCnt] = NULL;
93     }
94
95 }
96
97
98 /******************************************************************
99 * Name: ~FGMultiplayRxMgr
100 * Description: Destructor. Closes and deletes objects owned by
101 * this object.
102 ******************************************************************/
103 FGMultiplayRxMgr::~FGMultiplayRxMgr() {
104
105     Close();
106
107 }
108
109
110 /******************************************************************
111 * Name: init
112 * Description: Initialises multiplayer receive.
113 ******************************************************************/
114 bool FGMultiplayRxMgr::init(void) {
115
116     bool bSuccess = true;                       // Result of initialisation
117
118     // Initialise object if not already done
119     if (!m_bInitialised) {
120
121         // Set members from property values
122         m_sCallsign = fgGetString("/sim/multiplay/callsign");
123         m_sRxAddress = fgGetString("/sim/multiplay/rxhost");
124         m_iRxPort = fgGetInt("/sim/multiplay/rxport");
125
126         SG_LOG( SG_NETWORK, SG_INFO, "FGMultiplayRxMgr::init - rxaddress= "
127                                      << m_sRxAddress );
128         SG_LOG( SG_NETWORK, SG_INFO, "FGMultiplayRxMgr::init - rxport= "
129                                      << m_iRxPort);
130         SG_LOG( SG_NETWORK, SG_INFO, "FGMultiplayRxMgr::init - callsign= "
131                                      << m_sCallsign );
132
133
134         // Create and open rx socket
135         mDataRxSocket = new netSocket();
136         if (!mDataRxSocket->open(false)) {
137             // Failed to open rx socket
138             SG_LOG( SG_NETWORK, SG_ALERT, "FGMultiplayRxMgr::Open - Failed to create data receive socket" );
139             bSuccess = false;
140         } else {
141
142             // Configure the socket
143             mDataRxSocket->setBlocking(false);
144             mDataRxSocket->setBroadcast(true);
145             if (mDataRxSocket->bind(m_sRxAddress.c_str(), m_iRxPort) != 0) {
146                 perror("bind");
147                 // Failed to bind
148                 SG_LOG( SG_NETWORK, SG_ALERT, "FGMultiplayRxMgr::Open - Failed to bind receive socket" );
149                 bSuccess = false;
150             }
151
152         }
153
154         // Save manager state
155         m_bInitialised = bSuccess;
156
157     } else {
158         SG_LOG( SG_NETWORK, SG_ALERT, "FGMultiplayRxMgr::OpenRx - Receiver open requested when receiver is already open" );
159         bSuccess = false;
160     }
161
162     /* Return true if open succeeds */
163     return bSuccess;
164
165 }
166
167
168 /******************************************************************
169 * Name: Close
170 * Description: Closes and deletes and player connections. Closes
171 * and deletes the rx socket. Resets the object state
172 * to unitialised.
173 ******************************************************************/
174 void FGMultiplayRxMgr::Close(void) {
175
176     int iPlayerCnt;         // Count of players in player array
177
178     // Delete any existing players
179     for (iPlayerCnt = 0; iPlayerCnt < MAX_PLAYERS; iPlayerCnt++) {
180         if (m_Player[iPlayerCnt] != NULL) {
181             delete m_Player[iPlayerCnt];
182             m_Player[iPlayerCnt] = NULL;
183         }
184     }
185
186     // Delete socket
187     if (mDataRxSocket) {
188         mDataRxSocket->close();
189         delete mDataRxSocket;
190         mDataRxSocket = NULL;
191     }
192
193     m_bInitialised = false;
194
195 }
196
197
198 /******************************************************************
199 * Name: ProcessData
200 * Description: Processes data waiting at the receive socket. The
201 * processing ends when there is no more data at the socket.
202 ******************************************************************/
203 void FGMultiplayRxMgr::ProcessData(void) {
204
205     char sMsg[MAX_PACKET_SIZE];         // Buffer for received message
206     int iBytes;                         // Bytes received
207     T_MsgHdr *MsgHdr;                   // Pointer to header in received data
208     T_ChatMsg *ChatMsg;                 // Pointer to chat message in received data
209     T_PositionMsg *PosMsg;              // Pointer to position message in received data
210     char *sIpAddress;                   // Address information from header
211     char *sModelName;                   // Model that the remote player is using
212     char *sCallsign;                    // Callsign of the remote player
213     struct in_addr PlayerAddress;       // Used for converting the player's address into dot notation
214     int iPlayerCnt;                     // Count of players in player array
215     bool bActivePlayer = false;         // The state of the player that sent the data
216     int iPort;                          // Port that the remote player receives on
217
218
219     if (m_bInitialised) {
220
221         // Read the receive socket and process any data
222         do {
223
224             // Although the recv call asks for MAX_PACKET_SIZE of data,
225             // the number of bytes returned will only be that of the next
226             // packet waiting to be processed.
227             iBytes = mDataRxSocket->recv(sMsg, MAX_PACKET_SIZE, 0);
228
229             // Data received
230             if (iBytes > 0) {
231                 if (iBytes >= sizeof(MsgHdr)) {
232
233                     // Read header
234                     MsgHdr = (T_MsgHdr *)sMsg;
235                     PlayerAddress.s_addr = MsgHdr->lReplyAddress;
236                     sIpAddress = inet_ntoa(PlayerAddress);
237                     iPort = MsgHdr->iReplyPort;
238                     sCallsign = MsgHdr->sCallsign;
239
240                     // Process the player data unless we generated it
241                     if (m_sCallsign != string(MsgHdr->sCallsign)) {
242
243
244                         // Process messages
245                         switch(MsgHdr->MsgId) {
246                             case CHAT_MSG_ID:
247                                 if (MsgHdr->iMsgLen == sizeof(T_MsgHdr) + sizeof(T_ChatMsg)) {
248                                     ChatMsg = (T_ChatMsg *)(sMsg + sizeof(T_MsgHdr));
249                                     SG_LOG( SG_NETWORK, SG_BULK, "Chat [" << MsgHdr->sCallsign << "]" << " " << ChatMsg->sText );
250                                 } else {
251                                     SG_LOG( SG_NETWORK, SG_ALERT, "FGMultiplayRxMgr::MP_ProcessData - Chat message received with insufficient data" );
252                                 }
253                                 break;
254
255                             case POS_DATA_ID:
256                                 if (MsgHdr->iMsgLen == sizeof(T_MsgHdr) + sizeof(T_PositionMsg)) {
257                                     PosMsg = (T_PositionMsg *)(sMsg + sizeof(T_MsgHdr));
258                                     sModelName = PosMsg->sModel;
259
260                                     // Check if the player is already in the game by using the Callsign.
261                                     bActivePlayer = false;
262                                     for (iPlayerCnt = 0; iPlayerCnt < MAX_PLAYERS; iPlayerCnt++) {
263                                         if (m_Player[iPlayerCnt] != NULL) {
264                                             if (m_Player[iPlayerCnt]->CompareCallsign(MsgHdr->sCallsign)) {
265
266                                                 // Player found. Update the data for the player.
267                                                 m_Player[iPlayerCnt]->SetPosition(PosMsg->PlayerPos);
268                                                 bActivePlayer = true;
269
270                                             }
271                                         }
272                                     }
273
274                                     // Player not active, so add as new player
275                                     if (!bActivePlayer) {
276                                         iPlayerCnt = 0;
277                                         do {
278                                             if (m_Player[iPlayerCnt] == NULL) {
279                                                 SG_LOG( SG_NETWORK, SG_INFO, "FGMultiplayRxMgr::ProcessRxData - Add new player. IP: " << sIpAddress << ", Call: " <<  sCallsign << ", model: " << sModelName );
280                                                 m_Player[iPlayerCnt] = new MPPlayer;
281                                                 m_Player[iPlayerCnt]->Open(string(sIpAddress), iPort, string(sCallsign), string(sModelName), false);
282                                                 m_Player[iPlayerCnt]->SetPosition(PosMsg->PlayerPos);
283                                                 bActivePlayer = true;
284                                             }
285                                             iPlayerCnt++;
286                                         } while (iPlayerCnt < MAX_PLAYERS && !bActivePlayer);
287
288                                         // Check if the player was added
289                                         if (!bActivePlayer) {
290                                             if (iPlayerCnt == MAX_PLAYERS) {
291                                                 SG_LOG( SG_NETWORK, SG_ALERT, "FGMultiplayRxMgr::MP_ProcessData - Unable to add new player (" << sCallsign << "). Too many players." );
292                                             }
293                                         }
294                                     }
295
296                                 } else {
297                                     SG_LOG( SG_NETWORK, SG_ALERT, "FGMultiplayRxMgr::MP_ProcessData - Position message received with insufficient data" );
298                                 }
299                                 break;
300
301                             default:
302                                 SG_LOG( SG_NETWORK, SG_ALERT, "FGMultiplayRxMgr::MP_ProcessData - Unknown message Id received: " << MsgHdr->MsgId );
303                                 break;
304
305
306                         }
307                     }
308                 }
309
310
311             // Error or no data
312             } else if (iBytes == -1) {
313                 if (errno != EAGAIN) {
314                     perror("FGMultiplayRxMgr::MP_ProcessData");
315                 }
316             }
317
318         } while (iBytes > 0);
319
320     }
321
322
323 }
324
325
326 /******************************************************************
327 * Name: Update
328 * Description: For each active player, tell the player object
329 * to update its position on the scene. If a player object
330 * returns status information indicating that it has not
331 * had an update for some time then the player is deleted.
332 ******************************************************************/
333 void FGMultiplayRxMgr::Update(void) {
334
335     MPPlayer::TPlayerDataState ePlayerDataState;
336     int iPlayerId;
337
338     for (iPlayerId = 0; iPlayerId < MAX_PLAYERS; iPlayerId++) {
339         if (m_Player[iPlayerId] != NULL) {
340             ePlayerDataState = m_Player[iPlayerId]->Draw();
341
342             // If the player has not received an update for some
343             // time then assume that the player has quit.
344             if (ePlayerDataState == MPPlayer::PLAYER_DATA_EXPIRED) {
345                 SG_LOG( SG_NETWORK, SG_BULK, "FGMultiplayRxMgr::Update - Deleting player from game. Callsign: "
346                         << m_Player[iPlayerId]->Callsign() );
347                 delete m_Player[iPlayerId];
348                 m_Player[iPlayerId] = NULL;
349             }
350
351         }
352     }
353
354 }
355
356