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>
43 #include <plib/netSocket.h>
45 #include <simgear/debug/logstream.hxx>
47 #include <Main/fg_props.hxx>
48 #include "multiplaymgr.hxx"
49 #include "mpmessages.hxx"
50 #include "mpplayer.hxx"
52 #define MAX_PACKET_SIZE 1024
54 // These constants are provided so that the ident
55 // command can list file versions
56 const char sMULTIPLAYMGR_BID[] =
58 const char sMULTIPLAYMGR_HID[] = MULTIPLAYTXMGR_HID;
60 //////////////////////////////////////////////////////////////////////
62 // MultiplayMgr constructor
64 //////////////////////////////////////////////////////////////////////
65 FGMultiplayMgr::FGMultiplayMgr()
67 m_Initialised = false;
71 m_Initialised = false;
73 } // FGMultiplayMgr::FGMultiplayMgr()
74 //////////////////////////////////////////////////////////////////////
76 //////////////////////////////////////////////////////////////////////
78 // MultiplayMgr destructor
80 //////////////////////////////////////////////////////////////////////
81 FGMultiplayMgr::~FGMultiplayMgr()
84 } // FGMultiplayMgr::~FGMultiplayMgr()
85 //////////////////////////////////////////////////////////////////////
87 //////////////////////////////////////////////////////////////////////
91 //////////////////////////////////////////////////////////////////////
93 FGMultiplayMgr::init (void)
95 string TxAddress; // Destination address
98 //////////////////////////////////////////////////
99 // Initialise object if not already done
100 //////////////////////////////////////////////////
103 SG_LOG( SG_NETWORK, SG_WARN,
104 "FGMultiplayMgr::init - already initialised" );
107 //////////////////////////////////////////////////
108 // Set members from property values
109 //////////////////////////////////////////////////
110 TxAddress = fgGetString ("/sim/multiplay/txhost");
111 TxPort = fgGetInt ("/sim/multiplay/txport");
112 m_Callsign = fgGetString ("/sim/multiplay/callsign");
113 m_RxAddress = fgGetString ("/sim/multiplay/rxhost");
114 m_RxPort = fgGetInt ("/sim/multiplay/rxport");
119 if (m_Callsign == "")
121 // FIXME: use getpwuid
122 m_Callsign = "JohnDoe";
124 if (m_RxAddress == "")
126 m_RxAddress = "127.0.0.1";
128 if ((TxPort > 0) && (TxAddress != ""))
131 m_Server.set (TxAddress.c_str(), TxPort);
133 SG_LOG(SG_NETWORK,SG_INFO,"FGMultiplayMgr::init-txaddress= "<<TxAddress);
134 SG_LOG(SG_NETWORK,SG_INFO,"FGMultiplayMgr::init-txport= "<<TxPort );
135 SG_LOG(SG_NETWORK,SG_INFO,"FGMultiplayMgr::init-rxaddress="<<m_RxAddress );
136 SG_LOG(SG_NETWORK,SG_INFO,"FGMultiplayMgr::init-rxport= "<<m_RxPort);
137 SG_LOG(SG_NETWORK,SG_INFO,"FGMultiplayMgr::init-callsign= "<<m_Callsign);
138 m_DataSocket = new netSocket();
139 if (!m_DataSocket->open(false))
141 SG_LOG( SG_NETWORK, SG_ALERT,
142 "FGMultiplayMgr::init - Failed to create data socket" );
145 m_DataSocket->setBlocking(false);
146 m_DataSocket->setBroadcast(true);
147 if (m_DataSocket->bind(m_RxAddress.c_str(), m_RxPort) != 0)
150 SG_LOG( SG_NETWORK, SG_ALERT,
151 "FGMultiplayMgr::Open - Failed to bind receive socket" );
154 m_LocalPlayer = new MPPlayer();
155 if (!m_LocalPlayer->Open(m_RxAddress, m_RxPort, m_Callsign,
156 fgGetString("/sim/model/path"), true))
158 SG_LOG( SG_NETWORK, SG_ALERT,
159 "FGMultiplayMgr::init - Failed to create local player" );
162 m_Initialised = true;
164 } // FGMultiplayMgr::init()
165 //////////////////////////////////////////////////////////////////////
167 //////////////////////////////////////////////////////////////////////
169 // Closes and deletes the local player object. Closes
170 // and deletes the tx socket. Resets the object state to unitialised.
172 //////////////////////////////////////////////////////////////////////
174 FGMultiplayMgr::Close (void)
176 //////////////////////////////////////////////////
177 // Delete local player
178 //////////////////////////////////////////////////
181 delete m_LocalPlayer;
182 m_LocalPlayer = NULL;
184 //////////////////////////////////////////////////
185 // Delete any existing players
186 //////////////////////////////////////////////////
187 t_MPClientListIterator CurrentPlayer;
188 t_MPClientListIterator P;
189 CurrentPlayer = m_MPClientList.begin ();
190 while (CurrentPlayer != m_MPClientList.end ())
195 CurrentPlayer = m_MPClientList.erase (P);
197 //////////////////////////////////////////////////
199 //////////////////////////////////////////////////
202 m_DataSocket->close();
206 m_Initialised = false;
207 } // FGMultiplayMgr::Close(void)
208 //////////////////////////////////////////////////////////////////////
210 //////////////////////////////////////////////////////////////////////
212 // Description: Sends the position data for the local position.
214 //////////////////////////////////////////////////////////////////////
216 FGMultiplayMgr::SendMyPosition
218 const sgQuat PlayerOrientation,
219 const sgdVec3 PlayerPosition
223 T_PositionMsg PosMsg;
224 char Msg[sizeof(T_MsgHdr) + sizeof(T_PositionMsg)];
226 if ((! m_Initialised) || (! m_HaveServer))
229 SG_LOG( SG_NETWORK, SG_ALERT,
230 "FGMultiplayMgr::SendMyPosition - not initialised" );
232 SG_LOG( SG_NETWORK, SG_ALERT,
233 "FGMultiplayMgr::SendMyPosition - no server" );
236 m_LocalPlayer->SetPosition(PlayerOrientation, PlayerPosition);
237 m_LocalPlayer->FillPosMsg(&MsgHdr, &PosMsg);
238 memcpy(Msg, &MsgHdr, sizeof(T_MsgHdr));
239 memcpy(Msg + sizeof(T_MsgHdr), &PosMsg, sizeof(T_PositionMsg));
240 m_DataSocket->sendto (Msg,
241 sizeof(T_MsgHdr) + sizeof(T_PositionMsg), 0, &m_Server);
242 } // FGMultiplayMgr::SendMyPosition()
243 //////////////////////////////////////////////////////////////////////
245 //////////////////////////////////////////////////////////////////////
247 // Name: SendTextMessage
248 // Description: Sends a message to the player. The message must
249 // contain a valid and correctly filled out header and optional
252 //////////////////////////////////////////////////////////////////////
254 FGMultiplayMgr::SendTextMessage
256 const string &MsgText
261 unsigned int iNextBlockPosition = 0;
262 char Msg[sizeof(T_MsgHdr) + sizeof(T_ChatMsg)];
264 if ((! m_Initialised) || (! m_HaveServer))
268 m_LocalPlayer->FillMsgHdr(&MsgHdr, CHAT_MSG_ID);
269 //////////////////////////////////////////////////
270 // Divide the text string into blocks that fit
271 // in the message and send the blocks.
272 //////////////////////////////////////////////////
273 while (iNextBlockPosition < MsgText.length())
275 strncpy (ChatMsg.Text,
276 MsgText.substr(iNextBlockPosition, MAX_CHAT_MSG_LEN - 1).c_str(),
278 ChatMsg.Text[MAX_CHAT_MSG_LEN - 1] = '\0';
279 memcpy (Msg, &MsgHdr, sizeof(T_MsgHdr));
280 memcpy (Msg + sizeof(T_MsgHdr), &ChatMsg, sizeof(T_ChatMsg));
281 m_DataSocket->sendto (Msg,
282 sizeof(T_MsgHdr) + sizeof(T_ChatMsg), 0, &m_Server);
283 iNextBlockPosition += MAX_CHAT_MSG_LEN - 1;
285 } // FGMultiplayMgr::SendTextMessage ()
286 //////////////////////////////////////////////////////////////////////
288 //////////////////////////////////////////////////////////////////////
291 // Description: Processes data waiting at the receive socket. The
292 // processing ends when there is no more data at the socket.
294 //////////////////////////////////////////////////////////////////////
296 FGMultiplayMgr::ProcessData (void)
298 char Msg[MAX_PACKET_SIZE]; // Buffer for received message
299 int Bytes; // Bytes received
300 T_MsgHdr* MsgHdr; // Pointer to header in received data
301 netAddress SenderAddress;
305 SG_LOG( SG_NETWORK, SG_ALERT,
306 "FGMultiplayMgr::ProcessData - not initialised" );
309 //////////////////////////////////////////////////
310 // Read the receive socket and process any data
311 //////////////////////////////////////////////////
313 //////////////////////////////////////////////////
314 // Although the recv call asks for
315 // MAX_PACKET_SIZE of data, the number of bytes
316 // returned will only be that of the next
317 // packet waiting to be processed.
318 //////////////////////////////////////////////////
319 Bytes = m_DataSocket->recvfrom (Msg, MAX_PACKET_SIZE, 0,
321 //////////////////////////////////////////////////
323 //////////////////////////////////////////////////
328 perror("FGMultiplayMgr::MP_ProcessData");
332 if (Bytes <= (int)sizeof(MsgHdr))
334 SG_LOG( SG_NETWORK, SG_ALERT,
335 "FGMultiplayMgr::MP_ProcessData - "
336 << "received message with insufficient data" );
339 //////////////////////////////////////////////////
341 //////////////////////////////////////////////////
342 MsgHdr = (T_MsgHdr *)Msg;
343 MsgHdr->Magic = XDR_decode_uint32 (MsgHdr->Magic);
344 MsgHdr->Version = XDR_decode_uint32 (MsgHdr->Version);
345 MsgHdr->MsgId = XDR_decode_uint32 (MsgHdr->MsgId);
346 MsgHdr->MsgLen = XDR_decode_uint32 (MsgHdr->MsgLen);
347 MsgHdr->ReplyPort = XDR_decode_uint32 (MsgHdr->ReplyPort);
348 if (MsgHdr->Magic != MSG_MAGIC)
350 SG_LOG( SG_NETWORK, SG_ALERT,
351 "FGMultiplayMgr::MP_ProcessData - "
352 << "message has invalid magic number!" );
354 if (MsgHdr->Version != PROTO_VER)
356 SG_LOG( SG_NETWORK, SG_ALERT,
357 "FGMultiplayMgr::MP_ProcessData - "
358 << "message has invalid protocoll number!" );
360 //////////////////////////////////////////////////
361 // Process the player data unless we generated it
362 //////////////////////////////////////////////////
363 if (m_Callsign == MsgHdr->Callsign)
367 //////////////////////////////////////////////////
369 //////////////////////////////////////////////////
370 switch(MsgHdr->MsgId)
373 ProcessChatMsg ((char*) & Msg, SenderAddress);
376 ProcessPosMsg ((char*) & Msg, SenderAddress);
379 SG_LOG( SG_NETWORK, SG_ALERT,
380 "FGMultiplayMgr::MP_ProcessData - "
381 << "Unknown message Id received: "
386 } // FGMultiplayMgr::ProcessData(void)
387 //////////////////////////////////////////////////////////////////////
389 //////////////////////////////////////////////////////////////////////
391 // handle a position message
393 //////////////////////////////////////////////////////////////////////
395 FGMultiplayMgr::ProcessPosMsg
398 netAddress & SenderAddress
401 T_PositionMsg* PosMsg; // Pointer to position message in received data
402 T_MsgHdr* MsgHdr; // Pointer to header in received data
406 t_MPClientListIterator CurrentPlayer;
408 ActivePlayer = false;
409 MsgHdr = (T_MsgHdr *)Msg;
410 if (MsgHdr->MsgLen != sizeof(T_MsgHdr) + sizeof(T_PositionMsg))
412 SG_LOG( SG_NETWORK, SG_ALERT,
413 "FGMultiplayMgr::MP_ProcessData - "
414 << "Position message received with insufficient data" );
417 PosMsg = (T_PositionMsg *)(Msg + sizeof(T_MsgHdr));
418 Position[0] = XDR_decode_double (PosMsg->PlayerPosition[0]);
419 Position[1] = XDR_decode_double (PosMsg->PlayerPosition[1]);
420 Position[2] = XDR_decode_double (PosMsg->PlayerPosition[2]);
421 Orientation[0] = XDR_decode_float (PosMsg->PlayerOrientation[0]);
422 Orientation[1] = XDR_decode_float (PosMsg->PlayerOrientation[1]);
423 Orientation[2] = XDR_decode_float (PosMsg->PlayerOrientation[2]);
424 Orientation[3] = XDR_decode_float (PosMsg->PlayerOrientation[3]);
425 //////////////////////////////////////////////////
426 // Check if the player is already in the game
427 // by using the Callsign
428 //////////////////////////////////////////////////
429 for (CurrentPlayer = m_MPClientList.begin ();
430 CurrentPlayer != m_MPClientList.end ();
433 if ((*CurrentPlayer)->CompareCallsign(MsgHdr->Callsign))
435 // Player found. Update the data for the player.
436 (*CurrentPlayer)->SetPosition(Orientation, Position);
441 { // nothing more to do
444 //////////////////////////////////////////////////
445 // Player not active, so add as new player
446 //////////////////////////////////////////////////
448 NewPlayer = new MPPlayer;
449 NewPlayer->Open(SenderAddress.getHost(), MsgHdr->ReplyPort,
450 MsgHdr->Callsign, PosMsg->Model, false);
451 NewPlayer->SetPosition(Orientation, Position);
452 m_MPClientList.push_back (NewPlayer);
453 } // FGMultiplayMgr::ProcessPosMsg()
454 //////////////////////////////////////////////////////////////////////
456 //////////////////////////////////////////////////////////////////////
458 // handle a chat message
459 // FIXME: display chat message withi flightgear
461 //////////////////////////////////////////////////////////////////////
463 FGMultiplayMgr::ProcessChatMsg
466 netAddress & SenderAddress
469 T_ChatMsg* ChatMsg; // Pointer to chat message in received data
470 T_MsgHdr* MsgHdr; // Pointer to header in received data
472 MsgHdr = (T_MsgHdr *)Msg;
473 if (MsgHdr->MsgLen != sizeof(T_MsgHdr) + sizeof(T_ChatMsg))
475 SG_LOG( SG_NETWORK, SG_ALERT,
476 "FGMultiplayMgr::MP_ProcessData - "
477 << "Chat message received with insufficient data" );
480 ChatMsg = (T_ChatMsg *)(Msg + sizeof(T_MsgHdr));
481 SG_LOG ( SG_NETWORK, SG_ALERT,
482 "Chat [" << MsgHdr->Callsign << "]" << " " << ChatMsg->Text << endl);
483 } // FGMultiplayMgr::ProcessChatMsg ()
484 //////////////////////////////////////////////////////////////////////
486 //////////////////////////////////////////////////////////////////////
488 // For each active player, tell the player object
489 // to update its position on the scene. If a player object
490 // returns status information indicating that it has not
491 // had an update for some time then the player is deleted.
493 //////////////////////////////////////////////////////////////////////
495 FGMultiplayMgr::Update (void)
497 MPPlayer::TPlayerDataState ePlayerDataState;
498 t_MPClientListIterator CurrentPlayer;
500 CurrentPlayer = m_MPClientList.begin ();
501 while (CurrentPlayer != m_MPClientList.end ())
503 ePlayerDataState = (*CurrentPlayer)->Draw();
504 //////////////////////////////////////////////////
505 // If the player has not received an update for
506 // some time then assume that the player has quit.
507 //////////////////////////////////////////////////
508 if (ePlayerDataState == MPPlayer::PLAYER_DATA_EXPIRED)
510 SG_LOG( SG_NETWORK, SG_ALERT, "FGMultiplayMgr::Update - "
511 << "Deleting player from game. Callsign: "
512 << (*CurrentPlayer)->Callsign() );
513 t_MPClientListIterator P;
517 CurrentPlayer = m_MPClientList.erase (P);
519 else CurrentPlayer++;
521 } // FGMultiplayMgr::Update()
522 //////////////////////////////////////////////////////////////////////
524 #endif // FG_MPLAYER_AS