1 #include "pch.h"
2 #include "gameclient.hpp"
3 #include "xtime.hpp"
4 #include "../ogre/common/Def_Str.h"
5 
6 #include <MyGUI_LanguageManager.h>
7 #include <ctime>
8 
9 
P2PGameClient(GameClientCallback * callback,int port)10 P2PGameClient::P2PGameClient(GameClientCallback* callback, int port)
11 	: m_callback(callback), m_client(*this, port), m_state(DISCONNECTED),
12 	m_mutex(), m_cond(), m_playerInfo(), m_game(), m_carState()
13 {
14 	std::srand(time(0));  // randomize, based on current time
15 	m_playerInfo.random_id = std::rand(); // Client id is based on this
16 	m_game.packet_type = -1; // Invalidate until set
17 	m_carState.packet_type = -1; // Invalidate until set
18 }
19 
~P2PGameClient()20 P2PGameClient::~P2PGameClient()
21 {
22 	// Shuts down possibly running threads
23 	m_state = DISCONNECTED;
24 	m_cond.notify_all();
25 	m_senderThread.join();
26 }
27 
connect(const std::string & address,int port,std::string password)28 void P2PGameClient::connect(const std::string& address, int port, std::string password)
29 {
30 	setPassword(password);
31 	startLobby();
32 	m_client.connect(address, port, NULL);
33 }
34 
updatePlayerInfo(const std::string & name,const std::string & car)35 void P2PGameClient::updatePlayerInfo(const std::string& name, const std::string& car)
36 {
37 	boost::mutex::scoped_lock lock(m_mutex);
38 	m_playerInfo.name = name;
39 	m_playerInfo.car = car;
40 	m_playerInfo.address = m_client.getAddress();
41 }
42 
setPassword(const std::string & password)43 void P2PGameClient::setPassword(const std::string& password)
44 {
45 	boost::mutex::scoped_lock lock(m_mutex);
46 	m_playerInfo.password = password;
47 }
48 
toggleReady()49 void P2PGameClient::toggleReady()
50 {
51 	boost::mutex::scoped_lock lock(m_mutex);
52 	m_playerInfo.ready = !m_playerInfo.ready;
53 }
54 
isReady() const55 bool P2PGameClient::isReady() const
56 {
57 	return m_playerInfo.ready;
58 }
59 
sendMessage(const std::string & msg)60 void P2PGameClient::sendMessage(const std::string& msg)
61 {
62 	m_client.broadcast(char(protocol::TEXT_MESSAGE) + msg, net::PACKET_RELIABLE);
63 	if (m_callback)
64 	{
65 		PeerInfo pi;
66 		pi.name = m_playerInfo.name;
67 		m_callback->peerMessage(pi, msg);
68 	}
69 }
70 
startLobby()71 void P2PGameClient::startLobby()
72 {
73 	if (m_state == LOBBY)
74 	{
75 		LogO("== Netw startLobby  WRONG already");
76 		return;
77 	} else
78 		m_state = LOBBY;
79 
80 	if (!m_senderThread.joinable())
81 		m_senderThread = boost::thread(boost::bind(&P2PGameClient::senderThread, boost::ref(*this)));
82 	else
83 		LogO("== Netw startLobby  WRONG? !m_senderThread.joinable()");
84 }
85 
startGame(bool broadcast)86 void P2PGameClient::startGame(bool broadcast)
87 {
88 	if (m_state == GAME)
89 	{
90 		LogO("== Netw startGame  WRONG already");
91 		return;
92 	}
93 	LogO("== Netw startGame");
94 	m_state = GAME;
95 	{
96 		//  Clean up all zombie peers
97 		boost::mutex::scoped_lock lock(m_mutex);
98 
99 		PeerMap::iterator it = m_peers.begin();
100 		while (it != m_peers.end())
101 		{
102 			PeerInfo& pi = it->second;
103 			pi.loaded = false;  //  Reset loading status to be sure
104 
105 			//  Check condition
106 			if (pi.connection != PeerInfo::CONNECTED || pi.name.empty() || !pi.authenticated)
107 			{
108 				if (pi.peer_id != 0)
109 					m_client.disconnect(pi.peer_id, true);
110 				m_peers.erase(it++);
111 			} else
112 				++it;
113 		}
114 		m_playerInfo.loaded = false;
115 
116 		//  Assign id numbers
117 		recountPeersAndAssignIds(true);
118 	}
119 	//  Send notification
120 	if (broadcast)
121 	{
122 		LogO("== Netw startGame broadcast");
123 		m_client.broadcast(char(protocol::START_GAME) + std::string(" "), net::PACKET_RELIABLE);
124 	} else
125 		LogO("== Netw startGame !broadcast");
126 }
127 
loadingFinished()128 void P2PGameClient::loadingFinished()
129 {
130 	LogO("== Netw loadingFinished");
131 	m_client.broadcast(char(protocol::START_COUNTDOWN) + std::string(" "), net::PACKET_RELIABLE);
132 	boost::mutex::scoped_lock lock(m_mutex);
133 	m_playerInfo.loaded = true;
134 
135 	// Check for callback, because we might be the last one to finish loading
136 	if (!m_callback)
137 	{
138 		LogO("== Netw loadingFinished  WRONG !m_callback");
139 		return;
140 	}
141 	for (PeerMap::const_iterator it = m_peers.begin(); it != m_peers.end(); ++it)
142 		if (!it->second.loaded)
143 		{
144 			LogO("== Netw loadingFinished (but others not ready yet)");
145 			return;
146 		}
147 	lock.unlock(); // Mutex unlocked in callback to avoid dead-locks
148 
149 	// Wait a tiny bit to give the network some time
150 	boost::this_thread::sleep(boost::posix_time::milliseconds(50));
151 	m_callback->startRace();
152 }
153 
lap(uint8_t num,double time)154 void P2PGameClient::lap(uint8_t num, double time)
155 {
156 	protocol::TimeInfoPackage tip(num, time);
157 	m_client.broadcast(net::convert(tip), net::PACKET_RELIABLE);
158 	LogO("== Netw Lap " +toStr(num) +" finished by U time:"+ toStr(float(time)));
159 }
160 
returnToLobby(bool broadcast)161 void P2PGameClient::returnToLobby(bool broadcast)
162 {
163 	if (m_state != GAME)
164 	{
165 		LogO("== Netw returnToLobby  WRONG we should be in-game");
166 		return;
167 	}
168 	LogO("== Netw returnToLobby");
169 	boost::mutex::scoped_lock lock(m_mutex);
170 	m_state = LOBBY;
171 	m_playerInfo.loaded = false;
172 	m_playerInfo.ready = false;
173 	for (PeerMap::iterator it = m_peers.begin(); it != m_peers.end(); ++it)
174 		it->second.loaded = false;
175 	if (broadcast)
176 		m_client.broadcast(char(protocol::RETURN_LOBBY) + std::string(" "), net::PACKET_RELIABLE);
177 }
178 
senderThread()179 void P2PGameClient::senderThread()
180 {
181 	do {
182 		boost::mutex::scoped_lock lock(m_mutex);
183 		if (m_state == LOBBY)
184 		{
185 			// Broadcast local player's meta info
186 			protocol::PlayerInfoPacket pip = (protocol::PlayerInfoPacket)m_playerInfo;
187 			broadcastToAuthed(net::convert(pip));
188 
189 			// If game info is set, broadcast it
190 			if (m_game.packet_type == protocol::GAME_STATUS)
191 				broadcastToAuthed(net::convert(m_game));
192 
193 			// Loop all peers
194 			for (PeerMap::iterator it = m_peers.begin(); it != m_peers.end(); ++it)
195 			{	PeerInfo& pi = it->second;
196 
197 				// Check if we should try connecting to the peer
198 				// TODO: Should we also do this during GAME phase ?..
199 				if (pi.connection == PeerInfo::DISCONNECTED)
200 				{
201 					//LogO("== Netw  Connecting to "+pi.address);
202 					pi.connection = PeerInfo::CONNECTING;
203 					m_client.connect(pi.address, NULL);
204 				}
205 				// Broadcast peer's info
206 				protocol::PeerAddressPacket pap(it->second.address);
207 				broadcastToAuthed(net::convert(pap));
208 			}
209 
210 			// Wait some
211 			m_cond.timed_wait(lock, now() + 2.0);
212 		}
213 		else if (m_state == GAME)
214 		{
215 			// Broadcast car state
216 			if ((bool)m_carState)
217 				m_client.broadcast(net::convert(m_carState));
218 
219 			// Wait some
220 			m_cond.timed_wait(lock, now() + 0.050); // 20 FPS
221 		}
222 		if (m_state == DISCONNECTED)
223 		{	//LogO("== Netw DISCONNECTED");
224 			break;
225 		}
226 	} while (true);
227 }
228 
setLocalCarState(protocol::CarStatePackage const & cs)229 void P2PGameClient::setLocalCarState(protocol::CarStatePackage const& cs)
230 {
231 	boost::mutex::scoped_lock lock(m_mutex);
232 	m_carState = cs;
233 }
234 
getReceivedCarStates()235 protocol::CarStates P2PGameClient::getReceivedCarStates()
236 {
237 	boost::mutex::scoped_lock lock(m_mutex);
238 	protocol::CarStates states = m_receivedCarStates;
239 	m_receivedCarStates.clear();
240 	return states;
241 }
242 
getPeerCount() const243 size_t P2PGameClient::getPeerCount() const
244 {
245 	boost::mutex::scoped_lock lock(m_mutex);
246 	return m_playerInfo.peers;
247 }
248 
getPeer(ClientID id) const249 PeerInfo P2PGameClient::getPeer(ClientID id) const
250 {
251 	boost::mutex::scoped_lock lock(m_mutex);
252 	for (PeerMap::const_iterator it = m_peers.begin(); it != m_peers.end(); ++it)
253 		if (it->second.id == id)
254 			return it->second;
255 	return PeerInfo();
256 }
257 
broadcastGameInfo(const protocol::GameInfo & game)258 void P2PGameClient::broadcastGameInfo(const protocol::GameInfo &game)
259 {
260 	boost::mutex::scoped_lock lock(m_mutex);
261 	m_game = game;
262 	m_game.packet_type = protocol::GAME_STATUS; // Just to make sure
263 }
264 
265 // Mutex should be already locked when this is called
recountPeersAndAssignIds(bool validate)266 void P2PGameClient::recountPeersAndAssignIds(bool validate)
267 {
268 	LogO("== Netw recountPeersAndAssignIds");
269 	m_playerInfo.address = m_client.getAddress();
270 	m_playerInfo.peers = 0;
271 	m_playerInfo.id = -1;
272 	typedef std::map<int32_t, PeerInfo*> IDSorter;
273 	IDSorter idsorter;
274 	idsorter[m_playerInfo.random_id] = &m_playerInfo;
275 	for (PeerMap::iterator it = m_peers.begin(); it != m_peers.end(); ++it) {
276 		if (it->second.connection == PeerInfo::CONNECTED
277 			&& !it->second.name.empty()
278 			&& it->second.authenticated)
279 		{
280 			idsorter[it->second.random_id] = &it->second;
281 			++m_playerInfo.peers;
282 		}
283 		it->second.id = -1;
284 	}
285 	// Assign IDs
286 	ClientID id = 0;
287 	for (IDSorter::iterator it = idsorter.begin(); it != idsorter.end(); ++it, ++id)
288 		it->second->id = id;
289 
290 	if (!validate)
291 	{	LogO("== Netw recountPeersAndAssignIds  !validate");
292 		return;
293 	}
294 	// Validate unique IDs
295 	for (PeerMap::const_iterator it = m_peers.begin(); it != m_peers.end(); ++it)
296 		if (it->second.id == -1)
297 		{	LogO("== Netw recountPeersAndAssignIds  Unassigned or duplicate client ID!");
298 			throw std::runtime_error("Unassigned or duplicate client ID!");
299 		}
300 }
301 
302 // Mutex should be already locked when this is called
broadcastToAuthed(net::NetworkTraffic const & msg,int flags)303 void P2PGameClient::broadcastToAuthed(net::NetworkTraffic const& msg, int flags)
304 {
305 	for (PeerMap::const_iterator it = m_peers.begin(); it != m_peers.end(); ++it)
306 		if (it->second.authenticated)
307 			m_client.send(it->second.peer_id, msg, flags);
308 }
309 
connectionEvent(net::NetworkTraffic const & e)310 void P2PGameClient::connectionEvent(net::NetworkTraffic const& e)
311 {
312 	LogO("== Netw  Connected "+e.peer_address.str());
313 	if (m_state == LOBBY)
314 	{
315 		if (m_playerInfo.address == e.peer_address)
316 		{
317 			LogO("== Netw  CConnected  reject, same !!");
318 			return;
319 		}
320 		boost::mutex::scoped_lock lock(m_mutex);
321 		PeerInfo& pi = m_peers[e.peer_address.str()];
322 		pi.address = e.peer_address;
323 		pi.peer_id = e.peer_id;
324 		pi.connection = PeerInfo::CONNECTED;
325 		pi.ping = e.ping;
326 		protocol::HandshakePackage hp(m_playerInfo.password);
327 		m_client.send(e.peer_id, net::convert(hp), net::PACKET_RELIABLE);
328 		// No connection callback here, because player info is not yet received
329 	}
330 }
331 
disconnectEvent(net::NetworkTraffic const & e)332 void P2PGameClient::disconnectEvent(net::NetworkTraffic const& e)
333 {
334 	LogO("== Netw  Disconnected "+e.peer_address.str());
335 	if (m_callback && e.event_data == protocol::INCOMPATIBLE_GAME_PROTOCOL)
336 		m_callback->error(TR("#{NetGameProtocolError}"));
337 	if (m_callback && e.event_data == protocol::WRONG_PASSWORD)
338 		m_callback->error(TR("#{NetWrongPassword}"));
339 	PeerInfo picopy;
340 	{
341 		boost::mutex::scoped_lock lock(m_mutex);
342 		picopy = m_peers[e.peer_address.str()];
343 		picopy.connection = PeerInfo::DISCONNECTED;
344 		m_peers.erase(e.peer_address.str());
345 		recountPeersAndAssignIds();
346 	}
347 	// Callback (mutex unlocked to avoid dead-locks)
348 	if (m_callback)
349 		m_callback->peerDisconnected(picopy);
350 }
351 
receiveEvent(net::NetworkTraffic const & e)352 void P2PGameClient::receiveEvent(net::NetworkTraffic const& e)
353 {
354 	if (e.packet_length <= 0 || !e.packet_data)
355 	{
356 		LogO("== Netw receive event BAD");
357 		return;
358 	}
359 	switch (e.packet_data[0])
360 	{
361 		case protocol::HANDSHAKE:
362 		{
363 			if (m_state != LOBBY)
364 			{	LogO("== Netw HANDSHAKE  WRONG !lobby");
365 				break;
366 			}
367 			protocol::HandshakePackage hp = *reinterpret_cast<protocol::HandshakePackage const*>(e.packet_data);
368 			if (hp.game_protocol_version != protocol::GAME_PROTOCOL_VERSION)
369 			{
370 				m_client.disconnect(e.peer_id, false, protocol::INCOMPATIBLE_GAME_PROTOCOL);
371 				if (m_callback)
372 					m_callback->error(TR("#{NetGameProtocolError}"));
373 				LogO("== Netw HANDSHAKE  WRONG protocol ver");
374 				break;
375 			}
376 			boost::mutex::scoped_lock lock(m_mutex);
377 			if (std::string(m_playerInfo.password) != std::string(hp.password))
378 			{
379 				m_client.disconnect(e.peer_id, false, protocol::WRONG_PASSWORD);
380 				LogO("== Netw HANDSHAKE  WRONG passwod");
381 				break;
382 			}
383 			PeerInfo& pi = m_peers[e.peer_address.str()];
384 			pi.authenticated = true;
385 			pi.ping = e.ping;
386 			LogO("== Netw HANDSHAKE  ok");
387 			break;
388 		}
389 		case protocol::PEER_ADDRESS:
390 		{
391 			if (m_state != LOBBY)
392 			{	LogO("== Netw PEER_ADDRESS  WRONG !lobby");
393 				break;
394 			}
395 			protocol::PeerAddressPacket pap = *reinterpret_cast<protocol::PeerAddressPacket const*>(e.packet_data);
396 			boost::mutex::scoped_lock lock(m_mutex);
397 			m_peers[pap.address.str()].address = pap.address;
398 			m_peers[e.peer_address.str()].ping = e.ping;
399 			break;
400 		}
401 		case protocol::TEXT_MESSAGE:
402 		{
403 			std::string msg((const char*)e.packet_data, e.packet_length);
404 			msg = msg.substr(1);  // Remove the packet_type char
405 			LogO("== Netw TEXT_MESSAGE received: "+msg);
406 			if (m_callback)
407 			{
408 				boost::mutex::scoped_lock lock(m_mutex);
409 				PeerInfo& pi = m_peers[e.peer_address.str()];
410 				pi.ping = e.ping;
411 
412 				PeerInfo picopy = pi;
413 				lock.unlock();  // Mutex unlocked in callback to avoid dead-locks
414 				m_callback->peerMessage(picopy, msg);
415 			} else
416 				LogO("== Netw TEXT_MESSAGE  !callback");
417 			break;
418 		}
419 		case protocol::PLAYER_INFO:
420 		{
421 			LogO("== Netw PLAYER_INFO  start");
422 			protocol::PlayerInfoPacket pip = *reinterpret_cast<protocol::PlayerInfoPacket const*>(e.packet_data);
423 
424 			if (std::string(pip.name) == m_playerInfo.name)
425 			{
426 				LogO("== Netw PLAYER_INFO  reject, same !!");
427 				break;
428 			}
429 
430 			boost::mutex::scoped_lock lock(m_mutex);
431 
432 			PeerInfo& pi = m_peers[e.peer_address.str()];
433 			bool isNew = pi.name.empty();
434 
435 			//LogO("== Netw PI0  adr: "+pi.address.str()+"  name: "+pi.name+"  id: "+toStr(pi.id)+"  rid: "+toStr(pi.random_id)+"  peers: "+toStr(pi.peers)+"  rdy: "+toStr(pi.ready?1:0)+"  ld: "+toStr(pi.loaded?1:0)+"  ping: "+toStr(pi.ping)+"  aut: "+toStr(pi.authenticated));
436 			pi = pip;
437 			pi.ping = e.ping;
438 			//LogO("== Netw PI1  adr: "+pi.address.str()+"  name: "+pi.name+"  id: "+toStr(pi.id)+"  rid: "+toStr(pi.random_id)+"  peers: "+toStr(pi.peers)+"  rdy: "+toStr(pi.ready?1:0)+"  ld: "+toStr(pi.loaded?1:0)+"  ping: "+toStr(pi.ping)+"  aut: "+toStr(pi.authenticated));
439 
440 			if (m_callback)  // Callback
441 			{
442 				LogO(std::string("== Netw PI  callb  ")+(isNew?"new":""));
443 				PeerInfo picopy = pi;
444 				if (isNew)
445 					recountPeersAndAssignIds();
446 
447 				lock.unlock();  // Mutex unlocked in callback to avoid dead-locks
448 
449 				if (isNew)	// First info means completed connection
450 					m_callback->peerConnected(picopy);
451 				else	// Callback regardless if the info changed in order to give ping updates
452 					m_callback->peerInfo(picopy);
453 			} else
454 				LogO("== Netw START_GAME  !callback");
455 			break;
456 		}
457 		case protocol::START_GAME:
458 		{
459 			LogO("== Netw START_GAME  start");
460 			startGame(false);
461 			if (m_callback)
462 			{
463 				boost::mutex::scoped_lock lock(m_mutex);
464 				PeerInfo& pi = m_peers[e.peer_address.str()];
465 				pi.ping = e.ping;
466 
467 				PeerInfo picopy = pi;
468 				lock.unlock(); // Mutex unlocked in callback to avoid dead-locks
469 				m_callback->peerState(picopy, e.packet_data[0]);
470 				LogO("== Netw START_GAME  end");
471 			} else
472 				LogO("== Netw START_GAME  WRONG !callback");
473 			break;
474 		}
475 		case protocol::START_COUNTDOWN:
476 		{
477 			LogO("== Netw START_COUNTDOWN  start");
478 			if (m_callback)
479 			{
480 				boost::mutex::scoped_lock lock(m_mutex);
481 				PeerInfo& pi = m_peers[e.peer_address.str()];
482 				pi.ping = e.ping;
483 				pi.loaded = true;
484 				if (!m_playerInfo.loaded)
485 				{
486 					LogO("== Netw START_COUNTDOWN  WRONG !m_playerInfo.loaded");
487 					break;
488 				}
489 
490 				bool start = true;
491 				for (PeerMap::const_iterator it = m_peers.begin(); it != m_peers.end(); ++it)
492 					if (!it->second.loaded)
493 					{	start = false;	break;	}
494 
495 				lock.unlock(); // Mutex unlocked in callback to avoid dead-locks
496 				if (start)
497 				{
498 					LogO("== Netw START_COUNTDOWN  startRace");
499 					m_callback->startRace();
500 				}
501 				LogO("== Netw START_COUNTDOWN  end");
502 			} else
503 				LogO("== Netw START_COUNTDOWN  WRONG !callback");
504 			break;
505 		}
506 		case protocol::CAR_UPDATE:
507 		{
508 			if (m_state != GAME)
509 			{	LogO("== Netw CAR_UPDATE  WRONG !game");
510 				break;
511 			}
512 			protocol::CarStatePackage csp = *reinterpret_cast<protocol::CarStatePackage const*>(e.packet_data);
513 			boost::mutex::scoped_lock lock(m_mutex);
514 
515 			PeerInfo& pi = m_peers[e.peer_address.str()];
516 			pi.ping = e.ping;
517 			if (pi.id < 0)
518 			{	LogO("== Netw CAR_UPDATE  WRONG pi.id < 0");
519 				break;
520 			}
521 			m_receivedCarStates[pi.id] = csp;
522 			break;
523 		}
524 		case protocol::GAME_STATUS:
525 		{
526 			if (m_state != LOBBY || !m_callback)
527 			{	LogO("== Netw GAME_STATUS  WRONG !lobby or !callback");
528 				break;
529 			}
530 			protocol::GameInfo game = *reinterpret_cast<protocol::GameInfo const*>(e.packet_data);
531 			m_callback->gameInfo(game);
532 			break;
533 		}
534 		case protocol::TIME_INFO:
535 		{
536 			if (m_state != GAME || !m_callback)
537 			{	LogO("== Netw TIME_INFO  WRONG !game or !callback");
538 				break;
539 			}
540 			protocol::TimeInfoPackage time = *reinterpret_cast<protocol::TimeInfoPackage const*>(e.packet_data);
541 			boost::mutex::scoped_lock lock(m_mutex);
542 
543 			ClientID id = m_peers[e.peer_address.str()].id;
544 			lock.unlock();  // Mutex unlocked in callback to avoid dead-locks
545 			m_callback->timeInfo(id, time.lap, time.time);
546 			break;
547 		}
548 		case protocol::RETURN_LOBBY:
549 		{
550 			returnToLobby(false);
551 			m_callback->returnToLobby();
552 			break;
553 		}
554 		default:
555 		{
556 			LogO("== Netw  Received unknown packet type: "+toStr((int)e.packet_data[0]));
557 			break;
558 		}
559 	}
560 }
561