1 //////////////////////////////////////////////////////////////////////
5 // Written by Duncan McCreanor, started February 2003.
6 // duncan.mccreanor@airservicesaustralia.com
8 // Copyright (C) 2003 Airservices Australia
9 // Copyright (C) 2005 Oliver Schroeder
11 // This program is free software; you can redistribute it and/or
12 // modify it under the terms of the GNU General Public License as
13 // published by the Free Software Foundation; either version 2 of the
14 // License, or (at your option) any later version.
16 // This program is distributed in the hope that it will be useful, but
17 // WITHOUT ANY WARRANTY; without even the implied warranty of
18 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 // General Public License for more details.
21 // You should have received a copy of the GNU General Public License
22 // along with this program; if not, write to the Free Software
23 // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
27 //////////////////////////////////////////////////////////////////////
35 #include <sys/types.h>
36 #if !(defined(_MSC_VER) || defined(__MINGW32__))
37 # include <sys/socket.h>
38 # include <netinet/in.h>
39 # include <arpa/inet.h>
41 #include <plib/netSocket.h>
43 #include <simgear/debug/logstream.hxx>
44 #include <Main/fg_props.hxx>
45 #include "multiplaymgr.hpp"
46 #include "mpmessages.hxx"
47 #include "mpplayer.hxx"
48 #define MAX_PACKET_SIZE 1024
50 // These constants are provided so that the ident
51 // command can list file versions
52 const char sMULTIPLAYMGR_BID[] =
54 const char sMULTIPLAYMGR_HID[] = MULTIPLAYTXMGR_HID;
56 //////////////////////////////////////////////////////////////////////
58 // MultiplayMgr constructor
60 //////////////////////////////////////////////////////////////////////
61 FGMultiplayMgr::FGMultiplayMgr()
63 m_Initialised = false;
67 m_Initialised = false;
69 } // FGMultiplayMgr::FGMultiplayMgr()
70 //////////////////////////////////////////////////////////////////////
72 //////////////////////////////////////////////////////////////////////
74 // MultiplayMgr destructor
76 //////////////////////////////////////////////////////////////////////
77 FGMultiplayMgr::~FGMultiplayMgr()
80 } // FGMultiplayMgr::~FGMultiplayMgr()
81 //////////////////////////////////////////////////////////////////////
83 //////////////////////////////////////////////////////////////////////
87 //////////////////////////////////////////////////////////////////////
89 FGMultiplayMgr::init (void)
91 string TxAddress; // Destination address
94 //////////////////////////////////////////////////
95 // Initialise object if not already done
96 //////////////////////////////////////////////////
99 SG_LOG( SG_NETWORK, SG_WARN,
100 "FGMultiplayMgr::init - already initialised" );
103 //////////////////////////////////////////////////
104 // Set members from property values
105 //////////////////////////////////////////////////
106 TxAddress = fgGetString ("/sim/multiplay/txhost");
107 TxPort = fgGetInt ("/sim/multiplay/txport");
108 m_Callsign = fgGetString ("/sim/multiplay/callsign");
109 m_RxAddress = fgGetString ("/sim/multiplay/rxhost");
110 m_RxPort = fgGetInt ("/sim/multiplay/rxport");
115 if (m_Callsign == "")
117 // FIXME: use getpwuid
118 m_Callsign = "JohnDoe";
120 if (m_RxAddress == "")
122 m_RxAddress = "127.0.0.1";
124 if ((TxPort > 0) && (TxAddress != ""))
127 m_Server.set (TxAddress.c_str(), TxPort);
129 SG_LOG(SG_NETWORK,SG_INFO,"FGMultiplayMgr::init-txaddress= "<<TxAddress);
130 SG_LOG(SG_NETWORK,SG_INFO,"FGMultiplayMgr::init-txport= "<<TxPort );
131 SG_LOG(SG_NETWORK,SG_INFO,"FGMultiplayMgr::init-rxaddress="<<m_RxAddress );
132 SG_LOG(SG_NETWORK,SG_INFO,"FGMultiplayMgr::init-rxport= "<<m_RxPort);
133 SG_LOG(SG_NETWORK,SG_INFO,"FGMultiplayMgr::init-callsign= "<<m_Callsign);
134 m_DataSocket = new netSocket();
135 if (!m_DataSocket->open(false))
137 SG_LOG( SG_NETWORK, SG_ALERT,
138 "FGMultiplayMgr::init - Failed to create data socket" );
141 m_DataSocket->setBlocking(false);
142 m_DataSocket->setBroadcast(true);
143 if (m_DataSocket->bind(m_RxAddress.c_str(), m_RxPort) != 0)
146 SG_LOG( SG_NETWORK, SG_ALERT,
147 "FGMultiplayMgr::Open - Failed to bind receive socket" );
150 m_LocalPlayer = new MPPlayer();
151 if (!m_LocalPlayer->Open(m_RxAddress, m_RxPort, m_Callsign,
152 fgGetString("/sim/model/path"), true))
154 SG_LOG( SG_NETWORK, SG_ALERT,
155 "FGMultiplayMgr::init - Failed to create local player" );
158 m_Initialised = true;
160 } // FGMultiplayMgr::init()
161 //////////////////////////////////////////////////////////////////////
163 //////////////////////////////////////////////////////////////////////
165 // Closes and deletes the local player object. Closes
166 // and deletes the tx socket. Resets the object state to unitialised.
168 //////////////////////////////////////////////////////////////////////
170 FGMultiplayMgr::Close (void)
172 //////////////////////////////////////////////////
173 // Delete local player
174 //////////////////////////////////////////////////
177 delete m_LocalPlayer;
178 m_LocalPlayer = NULL;
180 //////////////////////////////////////////////////
181 // Delete any existing players
182 //////////////////////////////////////////////////
183 t_MPClientListIterator CurrentPlayer;
184 t_MPClientListIterator P;
185 CurrentPlayer = m_MPClientList.begin ();
186 while (CurrentPlayer != m_MPClientList.end ())
191 CurrentPlayer = m_MPClientList.erase (P);
193 //////////////////////////////////////////////////
195 //////////////////////////////////////////////////
198 m_DataSocket->close();
202 m_Initialised = false;
203 } // FGMultiplayMgr::Close(void)
204 //////////////////////////////////////////////////////////////////////
206 //////////////////////////////////////////////////////////////////////
208 // Description: Sends the position data for the local position.
210 //////////////////////////////////////////////////////////////////////
212 FGMultiplayMgr::SendMyPosition
214 const sgQuat PlayerOrientation,
215 const sgdVec3 PlayerPosition
219 T_PositionMsg PosMsg;
220 char Msg[sizeof(T_MsgHdr) + sizeof(T_PositionMsg)];
222 if ((! m_Initialised) || (! m_HaveServer))
225 SG_LOG( SG_NETWORK, SG_ALERT,
226 "FGMultiplayMgr::SendMyPosition - not initialised" );
228 SG_LOG( SG_NETWORK, SG_ALERT,
229 "FGMultiplayMgr::SendMyPosition - no server" );
232 m_LocalPlayer->SetPosition(PlayerOrientation, PlayerPosition);
233 m_LocalPlayer->FillPosMsg(&MsgHdr, &PosMsg);
234 memcpy(Msg, &MsgHdr, sizeof(T_MsgHdr));
235 memcpy(Msg + sizeof(T_MsgHdr), &PosMsg, sizeof(T_PositionMsg));
236 m_DataSocket->sendto (Msg,
237 sizeof(T_MsgHdr) + sizeof(T_PositionMsg), 0, &m_Server);
238 } // FGMultiplayMgr::SendMyPosition()
239 //////////////////////////////////////////////////////////////////////
241 //////////////////////////////////////////////////////////////////////
243 // Name: SendTextMessage
244 // Description: Sends a message to the player. The message must
245 // contain a valid and correctly filled out header and optional
248 //////////////////////////////////////////////////////////////////////
250 FGMultiplayMgr::SendTextMessage
252 const string &MsgText
257 unsigned int iNextBlockPosition = 0;
258 char Msg[sizeof(T_MsgHdr) + sizeof(T_ChatMsg)];
260 if ((! m_Initialised) || (! m_HaveServer))
264 m_LocalPlayer->FillMsgHdr(&MsgHdr, CHAT_MSG_ID);
265 //////////////////////////////////////////////////
266 // Divide the text string into blocks that fit
267 // in the message and send the blocks.
268 //////////////////////////////////////////////////
269 while (iNextBlockPosition < MsgText.length())
271 strncpy (ChatMsg.Text,
272 MsgText.substr(iNextBlockPosition, MAX_CHAT_MSG_LEN - 1).c_str(),
274 ChatMsg.Text[MAX_CHAT_MSG_LEN - 1] = '\0';
275 memcpy (Msg, &MsgHdr, sizeof(T_MsgHdr));
276 memcpy (Msg + sizeof(T_MsgHdr), &ChatMsg, sizeof(T_ChatMsg));
277 m_DataSocket->sendto (Msg,
278 sizeof(T_MsgHdr) + sizeof(T_ChatMsg), 0, &m_Server);
279 iNextBlockPosition += MAX_CHAT_MSG_LEN - 1;
281 } // FGMultiplayMgr::SendTextMessage ()
282 //////////////////////////////////////////////////////////////////////
284 //////////////////////////////////////////////////////////////////////
287 // Description: Processes data waiting at the receive socket. The
288 // processing ends when there is no more data at the socket.
290 //////////////////////////////////////////////////////////////////////
292 FGMultiplayMgr::ProcessData (void)
294 char Msg[MAX_PACKET_SIZE]; // Buffer for received message
295 int Bytes; // Bytes received
296 T_MsgHdr* MsgHdr; // Pointer to header in received data
297 netAddress SenderAddress;
301 SG_LOG( SG_NETWORK, SG_ALERT,
302 "FGMultiplayMgr::ProcessData - not initialised" );
305 //////////////////////////////////////////////////
306 // Read the receive socket and process any data
307 //////////////////////////////////////////////////
309 //////////////////////////////////////////////////
310 // Although the recv call asks for
311 // MAX_PACKET_SIZE of data, the number of bytes
312 // returned will only be that of the next
313 // packet waiting to be processed.
314 //////////////////////////////////////////////////
315 Bytes = m_DataSocket->recvfrom (Msg, MAX_PACKET_SIZE, 0,
317 //////////////////////////////////////////////////
319 //////////////////////////////////////////////////
324 perror("FGMultiplayMgr::MP_ProcessData");
328 if (Bytes <= (int)sizeof(MsgHdr))
330 SG_LOG( SG_NETWORK, SG_ALERT,
331 "FGMultiplayMgr::MP_ProcessData - "
332 << "received message with insufficient data" );
335 //////////////////////////////////////////////////
337 //////////////////////////////////////////////////
338 MsgHdr = (T_MsgHdr *)Msg;
339 MsgHdr->Magic = XDR_decode_uint32 (MsgHdr->Magic);
340 MsgHdr->Version = XDR_decode_uint32 (MsgHdr->Version);
341 MsgHdr->MsgId = XDR_decode_uint32 (MsgHdr->MsgId);
342 MsgHdr->MsgLen = XDR_decode_uint32 (MsgHdr->MsgLen);
343 MsgHdr->ReplyPort = XDR_decode_uint32 (MsgHdr->ReplyPort);
344 if (MsgHdr->Magic != MSG_MAGIC)
346 SG_LOG( SG_NETWORK, SG_ALERT,
347 "FGMultiplayMgr::MP_ProcessData - "
348 << "message has invalid magic number!" );
350 if (MsgHdr->Version != PROTO_VER)
352 SG_LOG( SG_NETWORK, SG_ALERT,
353 "FGMultiplayMgr::MP_ProcessData - "
354 << "message has invalid protocoll number!" );
356 //////////////////////////////////////////////////
357 // Process the player data unless we generated it
358 //////////////////////////////////////////////////
359 if (m_Callsign == MsgHdr->Callsign)
363 //////////////////////////////////////////////////
365 //////////////////////////////////////////////////
366 switch(MsgHdr->MsgId)
369 ProcessChatMsg ((char*) & Msg, SenderAddress);
372 ProcessPosMsg ((char*) & Msg, SenderAddress);
375 SG_LOG( SG_NETWORK, SG_ALERT,
376 "FGMultiplayMgr::MP_ProcessData - "
377 << "Unknown message Id received: "
382 } // FGMultiplayMgr::ProcessData(void)
383 //////////////////////////////////////////////////////////////////////
385 //////////////////////////////////////////////////////////////////////
387 // handle a position message
389 //////////////////////////////////////////////////////////////////////
391 FGMultiplayMgr::ProcessPosMsg
394 netAddress & SenderAddress
397 T_PositionMsg* PosMsg; // Pointer to position message in received data
398 T_MsgHdr* MsgHdr; // Pointer to header in received data
402 struct in_addr PlayerAddress;
403 t_MPClientListIterator CurrentPlayer;
407 ActivePlayer = false;
408 MsgHdr = (T_MsgHdr *)Msg;
409 if (MsgHdr->MsgLen != sizeof(T_MsgHdr) + sizeof(T_PositionMsg))
411 SG_LOG( SG_NETWORK, SG_ALERT,
412 "FGMultiplayMgr::MP_ProcessData - "
413 << "Position message received with insufficient data" );
416 PosMsg = (T_PositionMsg *)(Msg + sizeof(T_MsgHdr));
417 Position[0] = XDR_decode_double (PosMsg->PlayerPosition[0]);
418 Position[1] = XDR_decode_double (PosMsg->PlayerPosition[1]);
419 Position[2] = XDR_decode_double (PosMsg->PlayerPosition[2]);
420 Orientation[0] = XDR_decode_float (PosMsg->PlayerOrientation[0]);
421 Orientation[1] = XDR_decode_float (PosMsg->PlayerOrientation[1]);
422 Orientation[2] = XDR_decode_float (PosMsg->PlayerOrientation[2]);
423 Orientation[3] = XDR_decode_float (PosMsg->PlayerOrientation[3]);
424 //////////////////////////////////////////////////
425 // Check if the player is already in the game
426 // by using the Callsign
427 //////////////////////////////////////////////////
428 for (CurrentPlayer = m_MPClientList.begin ();
429 CurrentPlayer != m_MPClientList.end ();
432 if ((*CurrentPlayer)->CompareCallsign(MsgHdr->Callsign))
434 // Player found. Update the data for the player.
435 (*CurrentPlayer)->SetPosition(Orientation, Position);
440 { // nothing more to do
443 //////////////////////////////////////////////////
444 // Player not active, so add as new player
445 //////////////////////////////////////////////////
447 NewPlayer = new MPPlayer;
448 NewPlayer->Open(SenderAddress.getHost(), MsgHdr->ReplyPort,
449 MsgHdr->Callsign, PosMsg->Model, false);
450 NewPlayer->SetPosition(Orientation, Position);
451 m_MPClientList.push_back (NewPlayer);
452 } // FGMultiplayMgr::ProcessPosMsg()
453 //////////////////////////////////////////////////////////////////////
455 //////////////////////////////////////////////////////////////////////
457 // handle a chat message
458 // FIXME: display chat message withi flightgear
460 //////////////////////////////////////////////////////////////////////
462 FGMultiplayMgr::ProcessChatMsg
465 netAddress & SenderAddress
468 T_ChatMsg* ChatMsg; // Pointer to chat message in received data
469 T_MsgHdr* MsgHdr; // Pointer to header in received data
471 MsgHdr = (T_MsgHdr *)Msg;
472 if (MsgHdr->MsgLen != sizeof(T_MsgHdr) + sizeof(T_ChatMsg))
474 SG_LOG( SG_NETWORK, SG_ALERT,
475 "FGMultiplayMgr::MP_ProcessData - "
476 << "Chat message received with insufficient data" );
479 ChatMsg = (T_ChatMsg *)(Msg + sizeof(T_MsgHdr));
480 SG_LOG ( SG_NETWORK, SG_ALERT,
481 "Chat [" << MsgHdr->Callsign << "]" << " " << ChatMsg->Text << endl);
482 } // FGMultiplayMgr::ProcessChatMsg ()
483 //////////////////////////////////////////////////////////////////////
485 //////////////////////////////////////////////////////////////////////
487 // For each active player, tell the player object
488 // to update its position on the scene. If a player object
489 // returns status information indicating that it has not
490 // had an update for some time then the player is deleted.
492 //////////////////////////////////////////////////////////////////////
494 FGMultiplayMgr::Update (void)
496 MPPlayer::TPlayerDataState ePlayerDataState;
497 t_MPClientListIterator CurrentPlayer;
499 CurrentPlayer = m_MPClientList.begin ();
500 while (CurrentPlayer != m_MPClientList.end ())
502 ePlayerDataState = (*CurrentPlayer)->Draw();
503 //////////////////////////////////////////////////
504 // If the player has not received an update for
505 // some time then assume that the player has quit.
506 //////////////////////////////////////////////////
507 if (ePlayerDataState == MPPlayer::PLAYER_DATA_EXPIRED)
509 SG_LOG( SG_NETWORK, SG_ALERT, "FGMultiplayMgr::Update - "
510 << "Deleting player from game. Callsign: "
511 << (*CurrentPlayer)->Callsign() );
512 t_MPClientListIterator P;
516 CurrentPlayer = m_MPClientList.erase (P);
518 else CurrentPlayer++;
520 } // FGMultiplayMgr::Update()
521 //////////////////////////////////////////////////////////////////////
523 #endif // FG_MPLAYER_AS