1 /*****************************************************************************
2 * PokerTH - The open source texas holdem engine *
3 * Copyright (C) 2006-2012 Felix Hammer, Florian Thauer, Lothar May *
4 * *
5 * This program is free software: you can redistribute it and/or modify *
6 * it under the terms of the GNU Affero General Public License as *
7 * published by the Free Software Foundation, either version 3 of the *
8 * License, or (at your option) any later version. *
9 * *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU Affero General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU Affero General Public License *
16 * along with this program. If not, see <http://www.gnu.org/licenses/>. *
17 * *
18 * *
19 * Additional permission under GNU AGPL version 3 section 7 *
20 * *
21 * If you modify this program, or any covered work, by linking or *
22 * combining it with the OpenSSL project's OpenSSL library (or a *
23 * modified version of that library), containing parts covered by the *
24 * terms of the OpenSSL or SSLeay licenses, the authors of PokerTH *
25 * (Felix Hammer, Florian Thauer, Lothar May) grant you additional *
26 * permission to convey the resulting work. *
27 * Corresponding Source for a non-source form of such a combination *
28 * shall include the source code for the parts of OpenSSL used as well *
29 * as that of the covered work. *
30 *****************************************************************************/
31
32 #include <boost/asio.hpp>
33 #include <net/socket_helper.h>
34 #include <net/clientthread.h>
35 #include <net/clientstate.h>
36 #include <net/clientcontext.h>
37 #include <net/senderhelper.h>
38 #include <net/downloaderthread.h>
39 #include <net/clientexception.h>
40 #include <net/socket_msg.h>
41 #include <net/net_helper.h>
42 #include <net/asioreceivebuffer.h>
43 #include <core/avatarmanager.h>
44 #include <core/loghelper.h>
45 #include <clientenginefactory.h>
46 #include <game.h>
47 #include <log.h>
48 #include <qttoolsinterface.h>
49
50 #include <boost/lambda/lambda.hpp>
51 #include <boost/foreach.hpp>
52 #include <boost/filesystem.hpp>
53 #include <sstream>
54 #include <fstream>
55 #include <memory>
56 #include <cassert>
57 #include <gsasl.h>
58
59 #define TEMP_AVATAR_FILENAME "avatar.tmp"
60 #define TEMP_GUID_FILENAME "guid.tmp"
61 #define CLIENT_GUID_SIZE 16
62 #define CLIENT_AVATAR_LOOP_MSEC 100
63 #define CLIENT_SEND_LOOP_MSEC 50
64
65 using namespace std;
66 using namespace boost::filesystem;
67 using boost::asio::ip::tcp;
68
69 #ifdef BOOST_ASIO_HAS_STD_CHRONO
70 using namespace std::chrono;
71 #else
72 using namespace boost::chrono;
73 #endif
74
ClientThread(GuiInterface & gui,AvatarManager & avatarManager,Log * myLog)75 ClientThread::ClientThread(GuiInterface &gui, AvatarManager &avatarManager, Log *myLog)
76 : m_ioService(new boost::asio::io_service), m_clientLog(myLog), m_curState(NULL), m_gui(gui),
77 m_avatarManager(avatarManager), m_isServerSelected(false),
78 m_curGameId(0), m_curGameNum(1), m_guiPlayerId(0), m_sessionEstablished(false),
79 m_stateTimer(*m_ioService), m_avatarTimer(*m_ioService)
80 {
81 m_context.reset(new ClientContext);
82 myQtToolsInterface.reset(CreateQtToolsWrapper());
83 m_senderHelper.reset(new SenderHelper(m_ioService));
84 }
85
~ClientThread()86 ClientThread::~ClientThread()
87 {
88 }
89
90 void
Init(const string & serverAddress,const string & serverListUrl,const string & serverPassword,bool useServerList,unsigned serverPort,bool ipv6,bool sctp,const string & avatarServerAddress,const string & playerName,const string & avatarFile,const string & cacheDir)91 ClientThread::Init(
92 const string &serverAddress, const string &serverListUrl,
93 const string &serverPassword,
94 bool useServerList, unsigned serverPort, bool ipv6, bool sctp,
95 const string &avatarServerAddress, const string &playerName,
96 const string &avatarFile, const string &cacheDir)
97 {
98 if (IsRunning()) {
99 assert(false);
100 return;
101 }
102
103 ClientContext &context = GetContext();
104
105 context.SetSctp(sctp);
106 context.SetAddrFamily(ipv6 ? AF_INET6 : AF_INET);
107 context.SetServerAddr(serverAddress);
108 context.SetServerListUrl(serverListUrl);
109 context.SetServerPassword(serverPassword);
110 context.SetUseServerList(useServerList);
111 context.SetServerPort(serverPort);
112 context.SetAvatarServerAddr(avatarServerAddress);
113 context.SetPlayerName(playerName);
114 context.SetAvatarFile(avatarFile);
115 context.SetCacheDir(cacheDir);
116
117 ReadSessionGuidFromFile();
118 }
119
120 void
SignalTermination()121 ClientThread::SignalTermination()
122 {
123 Thread::SignalTermination();
124 m_ioService->stop();
125 }
126
127 void
SendKickPlayer(unsigned playerId)128 ClientThread::SendKickPlayer(unsigned playerId)
129 {
130 boost::shared_ptr<NetPacket> packet(new NetPacket);
131 packet->GetMsg()->set_messagetype(PokerTHMessage::Type_KickPlayerRequestMessage);
132 KickPlayerRequestMessage *netKick = packet->GetMsg()->mutable_kickplayerrequestmessage();
133 netKick->set_gameid(GetGameId());
134 netKick->set_playerid(playerId);
135 m_ioService->post(boost::bind(&ClientThread::SendSessionPacket, shared_from_this(), packet));
136 }
137
138 void
SendLeaveCurrentGame()139 ClientThread::SendLeaveCurrentGame()
140 {
141 boost::shared_ptr<NetPacket> packet(new NetPacket);
142 packet->GetMsg()->set_messagetype(PokerTHMessage::Type_LeaveGameRequestMessage);
143 LeaveGameRequestMessage *netLeave = packet->GetMsg()->mutable_leavegamerequestmessage();
144 netLeave->set_gameid(GetGameId());
145 m_ioService->post(boost::bind(&ClientThread::SendSessionPacket, shared_from_this(), packet));
146 }
147
148 void
SendStartEvent(bool fillUpWithCpuPlayers)149 ClientThread::SendStartEvent(bool fillUpWithCpuPlayers)
150 {
151 // Warning: This function is called in the context of the GUI thread.
152 // Create a network packet for the server start event.
153 boost::shared_ptr<NetPacket> packet(new NetPacket);
154 packet->GetMsg()->set_messagetype(PokerTHMessage::Type_StartEventMessage);
155 StartEventMessage *netStartEvent = packet->GetMsg()->mutable_starteventmessage();
156 netStartEvent->set_starteventtype(StartEventMessage::startEvent);
157 netStartEvent->set_gameid(GetGameId());
158 netStartEvent->set_fillwithcomputerplayers(fillUpWithCpuPlayers);
159 m_ioService->post(boost::bind(&ClientThread::SendSessionPacket, shared_from_this(), packet));
160 }
161
162 void
SendPlayerAction()163 ClientThread::SendPlayerAction()
164 {
165 // Warning: This function is called in the context of the GUI thread.
166 // Create a network packet containing the current player action.
167 {
168 boost::mutex::scoped_lock lock(m_pingDataMutex);
169 m_pingData.StartPing();
170 }
171 boost::shared_ptr<NetPacket> packet(new NetPacket);
172 packet->GetMsg()->set_messagetype(PokerTHMessage::Type_MyActionRequestMessage);
173 MyActionRequestMessage *netMyAction = packet->GetMsg()->mutable_myactionrequestmessage();
174 netMyAction->set_gameid(GetGameId());
175 boost::shared_ptr<PlayerInterface> myPlayer = GetGame()->getSeatsList()->front();
176 netMyAction->set_handnum(GetGame()->getCurrentHandID());
177 netMyAction->set_gamestate(static_cast<NetGameState>(GetGame()->getCurrentHand()->getCurrentRound()));
178 netMyAction->set_myaction(static_cast<NetPlayerAction>(myPlayer->getMyAction()));
179 // Only send last bet if not fold/checked.
180 if (myPlayer->getMyAction() != PLAYER_ACTION_FOLD && myPlayer->getMyAction() != PLAYER_ACTION_CHECK)
181 netMyAction->set_myrelativebet(myPlayer->getMyLastRelativeSet());
182 else
183 netMyAction->set_myrelativebet(0);
184 // Just dump the packet.
185 m_ioService->post(boost::bind(&ClientThread::SendSessionPacket, shared_from_this(), packet));
186 }
187
188 void
SendGameChatMessage(const std::string & msg)189 ClientThread::SendGameChatMessage(const std::string &msg)
190 {
191 // Warning: This function is called in the context of the GUI thread.
192 // Create a network packet containing the chat message.
193 boost::shared_ptr<NetPacket> packet(new NetPacket);
194 packet->GetMsg()->set_messagetype(PokerTHMessage::Type_ChatRequestMessage);
195 ChatRequestMessage *netChat = packet->GetMsg()->mutable_chatrequestmessage();
196 netChat->set_targetgameid(GetGameId());
197 netChat->set_chattext(msg);
198
199 // Just dump the packet.
200 m_ioService->post(boost::bind(&ClientThread::SendSessionPacket, shared_from_this(), packet));
201 }
202
203 void
SendLobbyChatMessage(const std::string & msg)204 ClientThread::SendLobbyChatMessage(const std::string &msg)
205 {
206 // Warning: This function is called in the context of the GUI thread.
207 // Create a network packet containing the chat message.
208 boost::shared_ptr<NetPacket> packet(new NetPacket);
209 packet->GetMsg()->set_messagetype(PokerTHMessage::Type_ChatRequestMessage);
210 ChatRequestMessage *netChat = packet->GetMsg()->mutable_chatrequestmessage();
211 netChat->set_chattext(msg);
212
213 // Just dump the packet.
214 m_ioService->post(boost::bind(&ClientThread::SendSessionPacket, shared_from_this(), packet));
215 }
216
217 void
SendPrivateChatMessage(unsigned targetPlayerId,const std::string & msg)218 ClientThread::SendPrivateChatMessage(unsigned targetPlayerId, const std::string &msg)
219 {
220 // Warning: This function is called in the context of the GUI thread.
221 // Create a network packet containing the chat message.
222 boost::shared_ptr<NetPacket> packet(new NetPacket);
223 packet->GetMsg()->set_messagetype(PokerTHMessage::Type_ChatRequestMessage);
224 ChatRequestMessage *netChat = packet->GetMsg()->mutable_chatrequestmessage();
225 netChat->set_targetplayerid(targetPlayerId);
226 netChat->set_chattext(msg);
227
228 // Just dump the packet.
229 m_ioService->post(boost::bind(&ClientThread::SendSessionPacket, shared_from_this(), packet));
230 }
231
232 void
SendJoinFirstGame(const std::string & password,bool autoLeave)233 ClientThread::SendJoinFirstGame(const std::string &password, bool autoLeave)
234 {
235 // Warning: This function is called in the context of the GUI thread.
236 // Create a network packet to request joining a game.
237 boost::shared_ptr<NetPacket> packet(new NetPacket);
238 packet->GetMsg()->set_messagetype(PokerTHMessage::Type_JoinExistingGameMessage);
239 JoinExistingGameMessage *netJoinGame = packet->GetMsg()->mutable_joinexistinggamemessage();
240 netJoinGame->set_gameid(1);
241 netJoinGame->set_autoleave(autoLeave);
242
243 if (!password.empty()) {
244 netJoinGame->set_password(password);
245 }
246 m_ioService->post(boost::bind(&ClientThread::SendSessionPacket, shared_from_this(), packet));
247 }
248
249 void
SendJoinGame(unsigned gameId,const std::string & password,bool autoLeave)250 ClientThread::SendJoinGame(unsigned gameId, const std::string &password, bool autoLeave)
251 {
252 // Warning: This function is called in the context of the GUI thread.
253 // Create a network packet to request joining a game.
254 boost::shared_ptr<NetPacket> packet(new NetPacket);
255 packet->GetMsg()->set_messagetype(PokerTHMessage::Type_JoinExistingGameMessage);
256 JoinExistingGameMessage *netJoinGame = packet->GetMsg()->mutable_joinexistinggamemessage();
257 netJoinGame->set_gameid(gameId);
258 netJoinGame->set_autoleave(autoLeave);
259
260 if (!password.empty()) {
261 netJoinGame->set_password(password);
262 }
263 m_ioService->post(boost::bind(&ClientThread::SendSessionPacket, shared_from_this(), packet));
264 }
265
266 void
SendRejoinGame(unsigned gameId,bool autoLeave)267 ClientThread::SendRejoinGame(unsigned gameId, bool autoLeave)
268 {
269 // Warning: This function is called in the context of the GUI thread.
270 // Create a network packet to request rejoining a running game.
271 boost::shared_ptr<NetPacket> packet(new NetPacket);
272 packet->GetMsg()->set_messagetype(PokerTHMessage::Type_RejoinExistingGameMessage);
273 RejoinExistingGameMessage *netJoinGame = packet->GetMsg()->mutable_rejoinexistinggamemessage();
274 netJoinGame->set_gameid(gameId);
275 netJoinGame->set_autoleave(autoLeave);
276
277 m_ioService->post(boost::bind(&ClientThread::SendSessionPacket, shared_from_this(), packet));
278 }
279
280 void
SendCreateGame(const GameData & gameData,const std::string & name,const std::string & password,bool autoLeave)281 ClientThread::SendCreateGame(const GameData &gameData, const std::string &name, const std::string &password, bool autoLeave)
282 {
283 // Warning: This function is called in the context of the GUI thread.
284 // Create a network packet to request creating a new game.
285 boost::shared_ptr<NetPacket> packet(new NetPacket);
286 packet->GetMsg()->set_messagetype(PokerTHMessage::Type_JoinNewGameMessage);
287 JoinNewGameMessage *netJoinGame = packet->GetMsg()->mutable_joinnewgamemessage();
288 netJoinGame->set_autoleave(autoLeave);
289 NetGameInfo *gameInfo = netJoinGame->mutable_gameinfo();
290 NetPacket::SetGameData(gameData, *gameInfo);
291 gameInfo->set_gamename(name);
292
293 if (!password.empty()) {
294 netJoinGame->set_password(password);
295 }
296 m_ioService->post(boost::bind(&ClientThread::SendSessionPacket, shared_from_this(), packet));
297 }
298
299 void
SendResetTimeout()300 ClientThread::SendResetTimeout()
301 {
302 boost::shared_ptr<NetPacket> packet(new NetPacket);
303 packet->GetMsg()->set_messagetype(PokerTHMessage::Type_ResetTimeoutMessage);
304 packet->GetMsg()->mutable_resettimeoutmessage();
305 m_ioService->post(boost::bind(&ClientThread::SendSessionPacket, shared_from_this(), packet));
306 }
307
308 void
SendAskKickPlayer(unsigned playerId)309 ClientThread::SendAskKickPlayer(unsigned playerId)
310 {
311 boost::shared_ptr<NetPacket> packet(new NetPacket);
312 packet->GetMsg()->set_messagetype(PokerTHMessage::Type_AskKickPlayerMessage);
313 AskKickPlayerMessage *netAsk = packet->GetMsg()->mutable_askkickplayermessage();
314 netAsk->set_gameid(GetGameId());
315 netAsk->set_playerid(playerId);
316 m_ioService->post(boost::bind(&ClientThread::SendSessionPacket, shared_from_this(), packet));
317 }
318
319 void
SendVoteKick(bool doKick)320 ClientThread::SendVoteKick(bool doKick)
321 {
322 boost::shared_ptr<NetPacket> packet(new NetPacket);
323 packet->GetMsg()->set_messagetype(PokerTHMessage::Type_VoteKickRequestMessage);
324 VoteKickRequestMessage *netVote = packet->GetMsg()->mutable_votekickrequestmessage();
325 netVote->set_gameid(GetGameId());
326 {
327 boost::mutex::scoped_lock lock(m_curPetitionIdMutex);
328 netVote->set_petitionid(m_curPetitionId);
329 }
330 netVote->set_votekick(doKick);
331 m_ioService->post(boost::bind(&ClientThread::SendSessionPacket, shared_from_this(), packet));
332 }
333
334 void
SendShowMyCards()335 ClientThread::SendShowMyCards()
336 {
337 boost::shared_ptr<NetPacket> packet(new NetPacket);
338 packet->GetMsg()->set_messagetype(PokerTHMessage::Type_ShowMyCardsRequestMessage);
339 packet->GetMsg()->mutable_showmycardsrequestmessage();
340 m_ioService->post(boost::bind(&ClientThread::SendSessionPacket, shared_from_this(), packet));
341 }
342
343 void
SendInvitePlayerToCurrentGame(unsigned playerId)344 ClientThread::SendInvitePlayerToCurrentGame(unsigned playerId)
345 {
346 boost::shared_ptr<NetPacket> packet(new NetPacket);
347 packet->GetMsg()->set_messagetype(PokerTHMessage::Type_InvitePlayerToGameMessage);
348 InvitePlayerToGameMessage *netInvite = packet->GetMsg()->mutable_inviteplayertogamemessage();
349 netInvite->set_gameid(GetGameId());
350 netInvite->set_playerid(playerId);
351 m_ioService->post(boost::bind(&ClientThread::SendSessionPacket, shared_from_this(), packet));
352 }
353
354 void
SendRejectGameInvitation(unsigned gameId,DenyGameInvitationReason reason)355 ClientThread::SendRejectGameInvitation(unsigned gameId, DenyGameInvitationReason reason)
356 {
357 boost::shared_ptr<NetPacket> packet(new NetPacket);
358 packet->GetMsg()->set_messagetype(PokerTHMessage::Type_RejectGameInvitationMessage);
359 RejectGameInvitationMessage *netReject = packet->GetMsg()->mutable_rejectgameinvitationmessage();
360 netReject->set_gameid(gameId);
361 netReject->set_myrejectreason(static_cast<RejectGameInvitationMessage::RejectGameInvReason>(reason));
362 m_ioService->post(boost::bind(&ClientThread::SendSessionPacket, shared_from_this(), packet));
363 }
364
365 void
SendReportAvatar(unsigned reportedPlayerId,const std::string & avatarHash)366 ClientThread::SendReportAvatar(unsigned reportedPlayerId, const std::string &avatarHash)
367 {
368 boost::shared_ptr<NetPacket> packet(new NetPacket);
369 packet->GetMsg()->set_messagetype(PokerTHMessage::Type_ReportAvatarMessage);
370 ReportAvatarMessage *netReport = packet->GetMsg()->mutable_reportavatarmessage();
371 netReport->set_reportedplayerid(reportedPlayerId);
372 MD5Buf tmpMD5;
373 if (tmpMD5.FromString(avatarHash) && !tmpMD5.IsZero()) {
374 netReport->set_reportedavatarhash(tmpMD5.GetData(), MD5_DATA_SIZE);
375
376 m_ioService->post(boost::bind(&ClientThread::SendSessionPacket, shared_from_this(), packet));
377 }
378 }
379
380 void
SendReportGameName(unsigned reportedGameId)381 ClientThread::SendReportGameName(unsigned reportedGameId)
382 {
383 boost::shared_ptr<NetPacket> packet(new NetPacket);
384 packet->GetMsg()->set_messagetype(PokerTHMessage::Type_ReportGameMessage);
385 ReportGameMessage *netReport = packet->GetMsg()->mutable_reportgamemessage();
386 netReport->set_reportedgameid(reportedGameId);
387 m_ioService->post(boost::bind(&ClientThread::SendSessionPacket, shared_from_this(), packet));
388 }
389
390 void
SendAdminRemoveGame(unsigned removeGameId)391 ClientThread::SendAdminRemoveGame(unsigned removeGameId)
392 {
393 boost::shared_ptr<NetPacket> packet(new NetPacket);
394 packet->GetMsg()->set_messagetype(PokerTHMessage::Type_AdminRemoveGameMessage);
395 AdminRemoveGameMessage *netRemove = packet->GetMsg()->mutable_adminremovegamemessage();
396 netRemove->set_removegameid(removeGameId);
397 m_ioService->post(boost::bind(&ClientThread::SendSessionPacket, shared_from_this(), packet));
398 }
399
400 void
SendAdminBanPlayer(unsigned playerId)401 ClientThread::SendAdminBanPlayer(unsigned playerId)
402 {
403 boost::shared_ptr<NetPacket> packet(new NetPacket);
404 packet->GetMsg()->set_messagetype(PokerTHMessage::Type_AdminBanPlayerMessage);
405 AdminBanPlayerMessage *netBan = packet->GetMsg()->mutable_adminbanplayermessage();
406 netBan->set_banplayerid(playerId);
407 m_ioService->post(boost::bind(&ClientThread::SendSessionPacket, shared_from_this(), packet));
408 }
409
410 void
StartAsyncRead()411 ClientThread::StartAsyncRead()
412 {
413 GetContext().GetSessionData()->GetReceiveBuffer().StartAsyncRead(GetContext().GetSessionData());
414 }
415
416 void
CloseSession(boost::shared_ptr<SessionData>)417 ClientThread::CloseSession(boost::shared_ptr<SessionData> /*session*/)
418 {
419 throw NetException(__FILE__, __LINE__, ERR_SOCK_CONN_RESET, 0);
420 }
421
422 void
HandlePacket(boost::shared_ptr<SessionData>,boost::shared_ptr<NetPacket> packet)423 ClientThread::HandlePacket(boost::shared_ptr<SessionData> /*session*/, boost::shared_ptr<NetPacket> packet)
424 {
425 GetState().HandlePacket(shared_from_this(), packet);
426 }
427
428 void
SelectServer(unsigned serverId)429 ClientThread::SelectServer(unsigned serverId)
430 {
431 boost::mutex::scoped_lock lock(m_selectServerMutex);
432 m_isServerSelected = true;
433 m_selectedServerId = serverId;
434 }
435
436 void
SetLogin(const std::string & userName,const std::string & password,bool isGuest)437 ClientThread::SetLogin(const std::string &userName, const std::string &password, bool isGuest)
438 {
439 boost::mutex::scoped_lock lock(m_loginDataMutex);
440 m_loginData.userName = userName;
441 m_loginData.password = password;
442 m_loginData.isGuest = isGuest;
443 }
444
445 ServerInfo
GetServerInfo(unsigned serverId) const446 ClientThread::GetServerInfo(unsigned serverId) const
447 {
448 ServerInfo tmpInfo;
449 boost::mutex::scoped_lock lock(m_serverInfoMapMutex);
450 ServerInfoMap::const_iterator pos = m_serverInfoMap.find(serverId);
451 if (pos != m_serverInfoMap.end()) {
452 tmpInfo = pos->second;
453 }
454 return tmpInfo;
455 }
456
457 GameInfo
GetGameInfo(unsigned gameId) const458 ClientThread::GetGameInfo(unsigned gameId) const
459 {
460 GameInfo tmpInfo;
461 boost::mutex::scoped_lock lock(m_gameInfoMapMutex);
462 GameInfoMap::const_iterator pos = m_gameInfoMap.find(gameId);
463 if (pos != m_gameInfoMap.end()) {
464 tmpInfo = pos->second;
465 }
466 return tmpInfo;
467 }
468
469 PlayerInfo
GetPlayerInfo(unsigned playerId) const470 ClientThread::GetPlayerInfo(unsigned playerId) const
471 {
472 PlayerInfo info;
473 if (!GetCachedPlayerInfo(playerId, info)) {
474 ostringstream name;
475 name << "#" << playerId;
476
477 info.playerName = name.str();
478 }
479 return info;
480 }
481
482 bool
GetPlayerIdFromName(const string & playerName,unsigned & playerId) const483 ClientThread::GetPlayerIdFromName(const string &playerName, unsigned &playerId) const
484 {
485 bool retVal = false;
486
487 boost::mutex::scoped_lock lock(m_playerInfoMapMutex);
488 PlayerInfoMap::const_reverse_iterator i = m_playerInfoMap.rbegin();
489 PlayerInfoMap::const_reverse_iterator end = m_playerInfoMap.rend();
490
491 while (i != end) {
492 if (i->second.playerName == playerName) {
493 playerId = i->first;
494 retVal = true;
495 break;
496 }
497 ++i;
498 }
499 return retVal;
500 }
501
502 unsigned
GetGameIdOfPlayer(unsigned playerId) const503 ClientThread::GetGameIdOfPlayer(unsigned playerId) const
504 {
505 unsigned gameId = 0; // Default: no game (invalid id).
506
507 // Iterate through all games to find the player.
508 boost::mutex::scoped_lock lock(m_gameInfoMapMutex);
509 GameInfoMap::const_iterator i = m_gameInfoMap.begin();
510 GameInfoMap::const_iterator i_end = m_gameInfoMap.end();
511 while (i != i_end) {
512 PlayerIdList::const_iterator j = (*i).second.players.begin();
513 PlayerIdList::const_iterator j_end = (*i).second.players.end();
514 while (j != j_end) {
515 if (playerId == *j) {
516 gameId = (*i).first;
517 break;
518 }
519 ++j;
520 }
521 if (gameId)
522 break;
523 ++i;
524 }
525 return gameId;
526 }
527
528 ClientCallback &
GetCallback()529 ClientThread::GetCallback()
530 {
531 return m_gui;
532 }
533
534 GuiInterface &
GetGui()535 ClientThread::GetGui()
536 {
537 return m_gui;
538 }
539
540 boost::shared_ptr<Log>
GetClientLog()541 ClientThread::GetClientLog()
542 {
543 return m_clientLog;
544 }
545
546 AvatarManager &
GetAvatarManager()547 ClientThread::GetAvatarManager()
548 {
549 return m_avatarManager;
550 }
551
552 void
Main()553 ClientThread::Main()
554 {
555 // Main loop.
556 boost::asio::io_service::work ioWork(*m_ioService);
557 try {
558 InitAuthContext();
559 // Start sub-threads.
560 m_avatarDownloader.reset(new DownloaderThread);
561 m_avatarDownloader->Run();
562 SetState(CLIENT_INITIAL_STATE::Instance());
563 RegisterTimers();
564
565 boost::asio::io_service::work ioWork(*m_ioService);
566 m_ioService->run(); // Will only be aborted asynchronously.
567
568 } catch (const PokerTHException &e) {
569 // Delete the cached server list, as it may be outdated.
570 path tmpServerListPath(GetCacheServerListFileName());
571 if (exists(tmpServerListPath)) {
572 remove(tmpServerListPath);
573 }
574 GetCallback().SignalNetClientError(e.GetErrorId(), e.GetOsErrorCode());
575 }
576 // Close the socket.
577 boost::system::error_code ec;
578 GetContext().GetSessionData()->GetAsioSocket()->close(ec);
579 // Set a state which does not do anything.
580 SetState(CLIENT_FINAL_STATE::Instance());
581 // Cancel timers.
582 GetStateTimer().cancel();
583 CancelTimers();
584 // Terminate sub-threads.
585 m_avatarDownloader->SignalTermination();
586 m_avatarDownloader->Join(DOWNLOADER_THREAD_TERMINATE_TIMEOUT);
587
588 ClearAuthContext();
589 }
590
591 void
RegisterTimers()592 ClientThread::RegisterTimers()
593 {
594 m_avatarTimer.expires_from_now(
595 milliseconds(CLIENT_AVATAR_LOOP_MSEC));
596 m_avatarTimer.async_wait(
597 boost::bind(
598 &ClientThread::TimerCheckAvatarDownloads, shared_from_this(), boost::asio::placeholders::error));
599 }
600
601 void
CancelTimers()602 ClientThread::CancelTimers()
603 {
604 m_avatarTimer.cancel();
605 }
606
607 void
InitAuthContext()608 ClientThread::InitAuthContext()
609 {
610 int res = gsasl_init(&m_authContext);
611 if (res != GSASL_OK)
612 throw ClientException(__FILE__, __LINE__, ERR_NET_GSASL_INIT_FAILED, 0);
613
614 if (!gsasl_server_support_p(m_authContext, "SCRAM-SHA-1")) {
615 gsasl_done(m_authContext);
616 throw ClientException(__FILE__, __LINE__, ERR_NET_GSASL_NO_SCRAM, 0);
617 }
618 }
619
620 void
ClearAuthContext()621 ClientThread::ClearAuthContext()
622 {
623 gsasl_done(m_authContext);
624 m_authContext = NULL;
625 }
626
627 void
InitGame()628 ClientThread::InitGame()
629 {
630 // Store current session guid, in case we need to rejoin the game.
631 WriteSessionGuidToFile();
632
633 // EngineFactory erstellen
634 boost::shared_ptr<EngineFactory> factory(new ClientEngineFactory); // LocalEngine erstellen
635
636 MapPlayerDataList();
637 // TODO
638 //if (GetPlayerDataList().size() != (unsigned)GetStartData().numberOfPlayers)
639 // throw ClientException(__FILE__, __LINE__, ERR_NET_INVALID_PLAYER_COUNT, 0);
640 m_startData.numberOfPlayers = (int)GetPlayerDataList().size();
641 m_game.reset(new Game(&m_gui, factory, GetPlayerDataList(), GetGameData(), GetStartData(), m_curGameNum++, m_clientLog.get()));
642 // Initialize Minimum GUI speed.
643 int minimumGuiSpeed = 1;
644 if(GetGameData().delayBetweenHandsSec < 11) {
645 minimumGuiSpeed = 12-GetGameData().delayBetweenHandsSec;
646 }
647 GetGui().initGui(minimumGuiSpeed);
648 // Signal start of game to GUI.
649 GetCallback().SignalNetClientGameStart(m_game);
650 }
651
652 void
SendSessionPacket(boost::shared_ptr<NetPacket> packet)653 ClientThread::SendSessionPacket(boost::shared_ptr<NetPacket> packet)
654 {
655 // Put packets in a buffer until the session is established.
656 if (IsSessionEstablished())
657 GetSender().Send(GetContext().GetSessionData(), packet);
658 else
659 m_outPacketList.push_back(packet);
660 }
661
662 void
SendQueuedPackets()663 ClientThread::SendQueuedPackets()
664 {
665 if (!m_outPacketList.empty()) {
666 NetPacketList::iterator i = m_outPacketList.begin();
667 NetPacketList::iterator end = m_outPacketList.end();
668
669 while (i != end) {
670 GetSender().Send(GetContext().GetSessionData(), *i);
671 ++i;
672 }
673 m_outPacketList.clear();
674 }
675 }
676
677 bool
GetCachedPlayerInfo(unsigned id,PlayerInfo & info) const678 ClientThread::GetCachedPlayerInfo(unsigned id, PlayerInfo &info) const
679 {
680 bool retVal = false;
681
682 boost::mutex::scoped_lock lock(m_playerInfoMapMutex);
683 PlayerInfoMap::const_iterator pos = m_playerInfoMap.find(id);
684 if (pos != m_playerInfoMap.end()) {
685 info = pos->second;
686 retVal = true;
687 }
688 return retVal;
689 }
690
691 void
RequestPlayerInfo(unsigned id,bool requestAvatar)692 ClientThread::RequestPlayerInfo(unsigned id, bool requestAvatar)
693 {
694 list<unsigned> idList;
695 idList.push_back(id);
696 RequestPlayerInfo(idList, requestAvatar);
697 }
698
699 void
RequestPlayerInfo(const list<unsigned> & idList,bool requestAvatar)700 ClientThread::RequestPlayerInfo(const list<unsigned> &idList, bool requestAvatar)
701 {
702 boost::shared_ptr<NetPacket> packet(new NetPacket);
703 packet->GetMsg()->set_messagetype(PokerTHMessage::Type_PlayerInfoRequestMessage);
704 PlayerInfoRequestMessage *netPlayerInfo = packet->GetMsg()->mutable_playerinforequestmessage();
705 BOOST_FOREACH(unsigned playerId, idList) {
706 if (find(m_playerInfoRequestList.begin(), m_playerInfoRequestList.end(), playerId) == m_playerInfoRequestList.end()) {
707 netPlayerInfo->add_playerid(playerId);
708 m_playerInfoRequestList.push_back(playerId);
709 }
710 // Remember that we have to request an avatar.
711 if (requestAvatar) {
712 m_avatarShouldRequestList.push_back(playerId);
713 }
714 }
715 if (netPlayerInfo->playerid_size() > 0) {
716 GetSender().Send(GetContext().GetSessionData(), packet);
717 }
718 }
719
720 void
SetPlayerInfo(unsigned id,const PlayerInfo & info)721 ClientThread::SetPlayerInfo(unsigned id, const PlayerInfo &info)
722 {
723 {
724 boost::mutex::scoped_lock lock(m_playerInfoMapMutex);
725 // Remove previous player entry with different id
726 // for the same player name if it exists.
727 // This can only be one entry, since every time a duplicate
728 // name is added one is removed.
729 // Only erase non computer player entries.
730 if (info.playerName.substr(0, sizeof(SERVER_COMPUTER_PLAYER_NAME) - 1) != SERVER_COMPUTER_PLAYER_NAME) {
731 PlayerInfoMap::iterator i = m_playerInfoMap.begin();
732 PlayerInfoMap::iterator end = m_playerInfoMap.end();
733 while (i != end) {
734 if (i->first != id && i->second.playerName == info.playerName) {
735 m_playerInfoMap.erase(i);
736 break;
737 }
738 ++i;
739 }
740 }
741 m_playerInfoMap[id] = info;
742 }
743
744 // Update player data for current game.
745 boost::shared_ptr<PlayerData> playerData(GetPlayerDataByUniqueId(id));
746 if (playerData) {
747 playerData->SetName(info.playerName);
748 playerData->SetType(info.ptype);
749 if (info.hasAvatar) {
750 string avatarFile;
751 if (GetAvatarManager().GetAvatarFileName(info.avatar, avatarFile)) {
752 playerData->SetAvatarFile(GetQtToolsInterface().stringToUtf8(avatarFile));
753 }
754 }
755 }
756 if (GetGame()) {
757 boost::shared_ptr<PlayerInterface> clientPlayer(GetGame()->getPlayerByUniqueId(id));
758 if (clientPlayer)
759 clientPlayer->setMyName(info.playerName);
760 // Skip avatar here, the game is already running.
761 }
762
763 if (find(m_avatarShouldRequestList.begin(), m_avatarShouldRequestList.end(), id) != m_avatarShouldRequestList.end()) {
764 m_avatarShouldRequestList.remove(id);
765 // Retrieve avatar if needed.
766 RetrieveAvatarIfNeeded(id, info);
767 }
768
769 // Remove it from the request list.
770 m_playerInfoRequestList.remove(id);
771
772 // Notify GUI
773 GetCallback().SignalNetClientPlayerChanged(id, info.playerName);
774
775 }
776
777 void
SetUnknownPlayer(unsigned id)778 ClientThread::SetUnknownPlayer(unsigned id)
779 {
780 // Just remove it from the request list.
781 m_playerInfoRequestList.remove(id);
782 m_avatarShouldRequestList.remove(id);
783 LOG_ERROR("Server reported unknown player id: " << id);
784 }
785
786 void
SetNewGameAdmin(unsigned id)787 ClientThread::SetNewGameAdmin(unsigned id)
788 {
789 // Update player data for current game.
790 boost::shared_ptr<PlayerData> playerData = GetPlayerDataByUniqueId(id);
791 if (playerData.get()) {
792 playerData->SetGameAdmin(true);
793 GetCallback().SignalNetClientNewGameAdmin(id, playerData->GetName());
794 if(m_game) {
795 m_clientLog->logPlayerAction(playerData->GetName(),LOG_ACTION_ADMIN);
796 }
797 }
798 }
799
800 void
RetrieveAvatarIfNeeded(unsigned id,const PlayerInfo & info)801 ClientThread::RetrieveAvatarIfNeeded(unsigned id, const PlayerInfo &info)
802 {
803 if (find(m_avatarHasRequestedList.begin(), m_avatarHasRequestedList.end(), id) == m_avatarHasRequestedList.end()) {
804 if (info.hasAvatar && !info.avatar.IsZero() && !GetAvatarManager().HasAvatar(info.avatar)) {
805 m_avatarHasRequestedList.push_back(id); // Never remove from this list. Only request once.
806
807 // Download from avatar server if applicable.
808 string avatarServerAddress(GetContext().GetAvatarServerAddr());
809 if (!avatarServerAddress.empty() && m_avatarDownloader) {
810 string serverFileName(info.avatar.ToString() + AvatarManager::GetAvatarFileExtension(info.avatarType));
811 m_avatarDownloader->QueueDownload(
812 id, avatarServerAddress + serverFileName, GetContext().GetCacheDir() + TEMP_AVATAR_FILENAME);
813 } else {
814 boost::shared_ptr<NetPacket> packet(new NetPacket);
815 packet->GetMsg()->set_messagetype(PokerTHMessage::Type_AvatarRequestMessage);
816 AvatarRequestMessage *netAvatar = packet->GetMsg()->mutable_avatarrequestmessage();
817 netAvatar->set_requestid(id);
818 netAvatar->set_avatarhash(info.avatar.GetData(), MD5_DATA_SIZE);
819 GetSender().Send(GetContext().GetSessionData(), packet);
820 }
821 }
822 }
823 }
824
825 std::string
GetPlayerName(unsigned id)826 ClientThread::GetPlayerName(unsigned id)
827 {
828 PlayerInfo info;
829 if (!GetCachedPlayerInfo(id, info)) {
830 // Request player info.
831 ostringstream name;
832 name << "#" << id;
833 info.playerName = name.str();
834 RequestPlayerInfo(id);
835 }
836 return info.playerName;
837 }
838
839 void
AddTempAvatarFile(unsigned playerId,unsigned avatarSize,AvatarFileType type)840 ClientThread::AddTempAvatarFile(unsigned playerId, unsigned avatarSize, AvatarFileType type)
841 {
842 boost::shared_ptr<AvatarFile> tmpAvatar(new AvatarFile);
843 tmpAvatar->fileData.reserve(avatarSize);
844 tmpAvatar->fileType = type;
845 tmpAvatar->reportedSize = avatarSize;
846
847 m_tempAvatarMap[playerId] = tmpAvatar;
848 }
849
850 void
StoreInTempAvatarFile(unsigned playerId,const vector<unsigned char> & data)851 ClientThread::StoreInTempAvatarFile(unsigned playerId, const vector<unsigned char> &data)
852 {
853 AvatarFileMap::iterator pos = m_tempAvatarMap.find(playerId);
854 if (pos == m_tempAvatarMap.end())
855 throw ClientException(__FILE__, __LINE__, ERR_NET_INVALID_REQUEST_ID, 0);
856 // We trust the server (concerning size of the data).
857 std::copy(data.begin(), data.end(), back_inserter(pos->second->fileData));
858 }
859
860 void
CompleteTempAvatarFile(unsigned playerId)861 ClientThread::CompleteTempAvatarFile(unsigned playerId)
862 {
863 AvatarFileMap::iterator pos = m_tempAvatarMap.find(playerId);
864 if (pos == m_tempAvatarMap.end())
865 throw ClientException(__FILE__, __LINE__, ERR_NET_INVALID_REQUEST_ID, 0);
866 boost::shared_ptr<AvatarFile> tmpAvatar = pos->second;
867 unsigned avatarSize = (unsigned)tmpAvatar->fileData.size();
868 if (avatarSize != tmpAvatar->reportedSize)
869 LOG_ERROR("Client received invalid avatar file size!");
870 else
871 PassAvatarFileToManager(playerId, tmpAvatar);
872
873 // Free memory.
874 m_tempAvatarMap.erase(pos);
875 }
876
877 void
PassAvatarFileToManager(unsigned playerId,boost::shared_ptr<AvatarFile> AvatarFile)878 ClientThread::PassAvatarFileToManager(unsigned playerId, boost::shared_ptr<AvatarFile> AvatarFile)
879 {
880 PlayerInfo tmpPlayerInfo;
881 if (!GetCachedPlayerInfo(playerId, tmpPlayerInfo))
882 LOG_ERROR("Client received invalid player id!");
883 else {
884 if (AvatarFile->fileType == AVATAR_FILE_TYPE_UNKNOWN)
885 AvatarFile->fileType = tmpPlayerInfo.avatarType;
886 if (!GetAvatarManager().StoreAvatarInCache(tmpPlayerInfo.avatar, AvatarFile->fileType, &AvatarFile->fileData[0], AvatarFile->reportedSize, false))
887 LOG_ERROR("Failed to store avatar in cache directory.");
888
889 // Update player info, but never re-request avatar.
890 SetPlayerInfo(playerId, tmpPlayerInfo);
891
892 string fileName;
893 if (GetAvatarManager().GetAvatarFileName(tmpPlayerInfo.avatar, fileName)) {
894 // Dynamically update avatar in GUI.
895 GetGui().setPlayerAvatar(playerId, GetQtToolsInterface().stringToUtf8(fileName));
896 }
897 }
898 }
899
900 void
SetUnknownAvatar(unsigned playerId)901 ClientThread::SetUnknownAvatar(unsigned playerId)
902 {
903 m_tempAvatarMap.erase(playerId);
904 LOG_ERROR("Server reported unknown avatar for player: " << playerId);
905 }
906
907 void
TimerCheckAvatarDownloads(const boost::system::error_code & ec)908 ClientThread::TimerCheckAvatarDownloads(const boost::system::error_code& ec)
909 {
910 if (!ec) {
911 if (m_avatarDownloader && m_avatarDownloader->HasDownloadResult()) {
912 unsigned playerId;
913 boost::shared_ptr<AvatarFile> tmpAvatar(new AvatarFile);
914 m_avatarDownloader->GetDownloadResult(playerId, tmpAvatar->fileData);
915 tmpAvatar->reportedSize = tmpAvatar->fileData.size();
916 PassAvatarFileToManager(playerId, tmpAvatar);
917 }
918 m_avatarTimer.expires_from_now(
919 milliseconds(CLIENT_AVATAR_LOOP_MSEC));
920 m_avatarTimer.async_wait(
921 boost::bind(
922 &ClientThread::TimerCheckAvatarDownloads, shared_from_this(), boost::asio::placeholders::error));
923 }
924 }
925
926 void
UnsubscribeLobbyMsg()927 ClientThread::UnsubscribeLobbyMsg()
928 {
929 if (GetContext().GetSubscribeLobbyMsg()) {
930 // Send unsubscribe request.
931 boost::shared_ptr<NetPacket> packet(new NetPacket);
932 packet->GetMsg()->set_messagetype(PokerTHMessage::Type_SubscriptionRequestMessage);
933 SubscriptionRequestMessage *netRequest = packet->GetMsg()->mutable_subscriptionrequestmessage();
934 netRequest->set_subscriptionaction(SubscriptionRequestMessage::unsubscribeGameList);
935 GetSender().Send(GetContext().GetSessionData(), packet);
936 GetContext().SetSubscribeLobbyMsg(false);
937 }
938 }
939
940 void
ResubscribeLobbyMsg()941 ClientThread::ResubscribeLobbyMsg()
942 {
943 if (!GetContext().GetSubscribeLobbyMsg()) {
944 // Clear game info map as it is outdated.
945 ClearGameInfoMap();
946 // Send resubscribe request.
947 boost::shared_ptr<NetPacket> packet(new NetPacket);
948 packet->GetMsg()->set_messagetype(PokerTHMessage::Type_SubscriptionRequestMessage);
949 SubscriptionRequestMessage *netRequest = packet->GetMsg()->mutable_subscriptionrequestmessage();
950 netRequest->set_subscriptionaction(SubscriptionRequestMessage::resubscribeGameList);
951 GetSender().Send(GetContext().GetSessionData(), packet);
952 GetContext().SetSubscribeLobbyMsg(true);
953 }
954 }
955
956 const ClientContext &
GetContext() const957 ClientThread::GetContext() const
958 {
959 assert(m_context.get());
960 return *m_context;
961 }
962
963 ClientContext &
GetContext()964 ClientThread::GetContext()
965 {
966 assert(m_context.get());
967 return *m_context;
968 }
969
970 string
GetCacheServerListFileName()971 ClientThread::GetCacheServerListFileName()
972 {
973 string fileName;
974 path tmpServerListPath(GetContext().GetCacheDir());
975 string serverListUrl(GetContext().GetServerListUrl());
976 // Retrieve the file name from the URL.
977 size_t pos = serverListUrl.find_last_of('/');
978 if (!GetContext().GetCacheDir().empty() && !serverListUrl.empty() && pos != string::npos && ++pos < serverListUrl.length()) {
979 tmpServerListPath /= serverListUrl.substr(pos);
980 fileName = tmpServerListPath.directory_string();
981 }
982 return fileName;
983 }
984
985 void
CreateContextSession()986 ClientThread::CreateContextSession()
987 {
988 bool validSocket = false;
989 // TODO sctp
990 try {
991 boost::shared_ptr<tcp::socket> newSock;
992 if (GetContext().GetAddrFamily() == AF_INET6)
993 newSock.reset(new boost::asio::ip::tcp::socket(*m_ioService, tcp::v6()));
994 else
995 newSock.reset(new boost::asio::ip::tcp::socket(*m_ioService, tcp::v4()));
996 #if BOOST_VERSION < 106600
997 boost::asio::socket_base::non_blocking_io command(true);
998 newSock->io_control(command);
999 #else
1000 newSock->non_blocking(true);
1001 #endif
1002 newSock->set_option(tcp::no_delay(true));
1003 newSock->set_option(boost::asio::socket_base::keep_alive(true));
1004
1005 GetContext().SetSessionData(boost::shared_ptr<SessionData>(new SessionData(
1006 newSock,
1007 SESSION_ID_GENERIC,
1008 *this,
1009 *m_ioService)));
1010 GetContext().SetResolver(boost::shared_ptr<boost::asio::ip::tcp::resolver>(
1011 new boost::asio::ip::tcp::resolver(*m_ioService)));
1012 validSocket = true;
1013 } catch (...) {
1014 }
1015 if (!validSocket)
1016 throw ClientException(__FILE__, __LINE__, ERR_SOCK_CREATION_FAILED, 0);
1017 }
1018
1019 ClientState &
GetState()1020 ClientThread::GetState()
1021 {
1022 assert(m_curState);
1023 return *m_curState;
1024 }
1025
1026 void
SetState(ClientState & newState)1027 ClientThread::SetState(ClientState &newState)
1028 {
1029 if (m_curState)
1030 m_curState->Exit(shared_from_this());
1031 m_curState = &newState;
1032 m_curState->Enter(shared_from_this());
1033 }
1034
1035 boost::asio::steady_timer &
GetStateTimer()1036 ClientThread::GetStateTimer()
1037 {
1038 return m_stateTimer;
1039 }
1040
1041 SenderHelper &
GetSender()1042 ClientThread::GetSender()
1043 {
1044 assert(m_senderHelper);
1045 return *m_senderHelper;
1046 }
1047
1048 unsigned
GetGameId() const1049 ClientThread::GetGameId() const
1050 {
1051 boost::mutex::scoped_lock lock(m_curGameIdMutex);
1052 return m_curGameId;
1053 }
1054
1055 void
SetGameId(unsigned id)1056 ClientThread::SetGameId(unsigned id)
1057 {
1058 boost::mutex::scoped_lock lock(m_curGameIdMutex);
1059 m_curGameId = id;
1060 }
1061
1062 Gsasl *
GetAuthContext()1063 ClientThread::GetAuthContext()
1064 {
1065 assert(m_authContext);
1066 return m_authContext;
1067 }
1068
1069 const GameData &
GetGameData() const1070 ClientThread::GetGameData() const
1071 {
1072 return m_gameData;
1073 }
1074
1075 void
SetGameData(const GameData & gameData)1076 ClientThread::SetGameData(const GameData &gameData)
1077 {
1078 m_gameData = gameData;
1079 }
1080
1081 const StartData &
GetStartData() const1082 ClientThread::GetStartData() const
1083 {
1084 return m_startData;
1085 }
1086
1087 void
SetStartData(const StartData & startData)1088 ClientThread::SetStartData(const StartData &startData)
1089 {
1090 m_startData = startData;
1091 }
1092
1093 unsigned
GetGuiPlayerId() const1094 ClientThread::GetGuiPlayerId() const
1095 {
1096 boost::mutex::scoped_lock lock(m_guiPlayerIdMutex);
1097 return m_guiPlayerId;
1098 }
1099
1100 int
GetOrigGuiPlayerNum() const1101 ClientThread::GetOrigGuiPlayerNum() const
1102 {
1103 return m_origGuiPlayerNum;
1104 }
1105
1106 void
SetGuiPlayerId(unsigned guiPlayerId)1107 ClientThread::SetGuiPlayerId(unsigned guiPlayerId)
1108 {
1109 boost::mutex::scoped_lock lock(m_guiPlayerIdMutex);
1110 m_guiPlayerId = guiPlayerId;
1111 }
1112
1113 boost::shared_ptr<Game>
GetGame()1114 ClientThread::GetGame()
1115 {
1116 return m_game;
1117 }
1118
1119 QtToolsInterface &
GetQtToolsInterface()1120 ClientThread::GetQtToolsInterface()
1121 {
1122 assert(myQtToolsInterface.get());
1123 return *myQtToolsInterface;
1124 }
1125
1126 boost::shared_ptr<PlayerData>
CreatePlayerData(unsigned playerId,bool isGameAdmin)1127 ClientThread::CreatePlayerData(unsigned playerId, bool isGameAdmin)
1128 {
1129 boost::shared_ptr<PlayerData> playerData;
1130 PlayerInfo info;
1131 if (GetCachedPlayerInfo(playerId, info)) {
1132 playerData.reset(
1133 new PlayerData(playerId, 0, info.ptype,
1134 info.isGuest ? PLAYER_RIGHTS_GUEST : PLAYER_RIGHTS_NORMAL, isGameAdmin));
1135 playerData->SetName(info.playerName);
1136 if (info.hasAvatar) {
1137 string avatarFile;
1138 if (GetAvatarManager().GetAvatarFileName(info.avatar, avatarFile))
1139 playerData->SetAvatarFile(GetQtToolsInterface().stringToUtf8(avatarFile));
1140 else
1141 RetrieveAvatarIfNeeded(playerId, info);
1142 }
1143 } else {
1144 ostringstream name;
1145 name << "#" << playerId;
1146
1147 // Request player info.
1148 RequestPlayerInfo(playerId, true);
1149 // Use temporary data until the PlayerInfo request is completed.
1150 playerData.reset(
1151 new PlayerData(playerId, 0, PLAYER_TYPE_HUMAN, PLAYER_RIGHTS_NORMAL, isGameAdmin));
1152 playerData->SetName(name.str());
1153 }
1154 return playerData;
1155 }
1156
1157 void
AddPlayerData(boost::shared_ptr<PlayerData> playerData)1158 ClientThread::AddPlayerData(boost::shared_ptr<PlayerData> playerData)
1159 {
1160 if (playerData.get() && !playerData->GetName().empty()) {
1161 m_playerDataList.push_back(playerData);
1162 if (playerData->GetUniqueId() == GetGuiPlayerId())
1163 GetCallback().SignalNetClientSelfJoined(playerData->GetUniqueId(), playerData->GetName(), playerData->IsGameAdmin());
1164 else {
1165 GetCallback().SignalNetClientPlayerJoined(playerData->GetUniqueId(), playerData->GetName(), playerData->IsGameAdmin());
1166 // if(m_game) {
1167 // m_clientLog->logPlayerAction(playerData->GetName(),LOG_ACTION_JOIN);
1168 // }
1169 }
1170 }
1171 }
1172
1173 void
RemovePlayerData(unsigned playerId,int removeReason)1174 ClientThread::RemovePlayerData(unsigned playerId, int removeReason)
1175 {
1176 boost::shared_ptr<PlayerData> tmpData;
1177
1178 PlayerDataList::iterator i = m_playerDataList.begin();
1179 PlayerDataList::iterator end = m_playerDataList.end();
1180 while (i != end) {
1181 if ((*i)->GetUniqueId() == playerId) {
1182 tmpData = *i;
1183 m_playerDataList.erase(i);
1184 break;
1185 }
1186 ++i;
1187 }
1188
1189 if (tmpData.get()) {
1190 // Remove player from gui.
1191 if (GetGame()) {
1192 boost::shared_ptr<PlayerInterface> tmpPlayer(GetGame()->getPlayerByUniqueId(tmpData->GetUniqueId()));
1193 if (tmpPlayer) {
1194 tmpPlayer->setMyStayOnTableStatus(false);
1195 }
1196 }
1197 GetCallback().SignalNetClientPlayerLeft(tmpData->GetUniqueId(), tmpData->GetName(), removeReason);
1198
1199 if(m_game) {
1200 if(removeReason == NTF_NET_REMOVED_KICKED) {
1201 m_clientLog->logPlayerAction(tmpData->GetName(),LOG_ACTION_KICKED);
1202 } else {
1203 m_clientLog->logPlayerAction(tmpData->GetName(),LOG_ACTION_LEFT);
1204 }
1205 }
1206
1207 }
1208 }
1209
1210 void
ClearPlayerDataList()1211 ClientThread::ClearPlayerDataList()
1212 {
1213 m_playerDataList.clear();
1214 }
1215
1216 void
MapPlayerDataList()1217 ClientThread::MapPlayerDataList()
1218 {
1219 // Retrieve the GUI player.
1220 boost::shared_ptr<PlayerData> guiPlayer = GetPlayerDataByUniqueId(GetGuiPlayerId());
1221 assert(guiPlayer.get());
1222 m_origGuiPlayerNum = guiPlayer->GetNumber();
1223
1224 // Create a copy of the player list so that the GUI player
1225 // is player 0. This is mapped because the GUI depends on it.
1226 PlayerDataList mappedList;
1227
1228 PlayerDataList::const_iterator i = m_playerDataList.begin();
1229 PlayerDataList::const_iterator end = m_playerDataList.end();
1230 int numPlayers = GetStartData().numberOfPlayers;
1231
1232 while (i != end) {
1233 boost::shared_ptr<PlayerData> tmpData(new PlayerData(*(*i)));
1234 int numberDiff = numPlayers - m_origGuiPlayerNum;
1235 tmpData->SetNumber((tmpData->GetNumber() + numberDiff) % numPlayers);
1236 mappedList.push_back(tmpData);
1237 ++i;
1238 }
1239
1240 // Sort the list by player number.
1241 mappedList.sort(*boost::lambda::_1 < *boost::lambda::_2);
1242
1243 m_playerDataList = mappedList;
1244 }
1245
1246 const PlayerDataList &
GetPlayerDataList() const1247 ClientThread::GetPlayerDataList() const
1248 {
1249 return m_playerDataList;
1250 }
1251
1252 boost::shared_ptr<PlayerData>
GetPlayerDataByUniqueId(unsigned id)1253 ClientThread::GetPlayerDataByUniqueId(unsigned id)
1254 {
1255 boost::shared_ptr<PlayerData> tmpPlayer;
1256
1257 PlayerDataList::const_iterator i = m_playerDataList.begin();
1258 PlayerDataList::const_iterator end = m_playerDataList.end();
1259
1260 while (i != end) {
1261 if ((*i)->GetUniqueId() == id) {
1262 tmpPlayer = *i;
1263 break;
1264 }
1265 ++i;
1266 }
1267 return tmpPlayer;
1268 }
1269
1270 boost::shared_ptr<PlayerData>
GetPlayerDataByName(const std::string & name)1271 ClientThread::GetPlayerDataByName(const std::string &name)
1272 {
1273 boost::shared_ptr<PlayerData> tmpPlayer;
1274
1275 if (!name.empty()) {
1276 PlayerDataList::const_iterator i = m_playerDataList.begin();
1277 PlayerDataList::const_iterator end = m_playerDataList.end();
1278
1279 while (i != end) {
1280 if ((*i)->GetName() == name) {
1281 tmpPlayer = *i;
1282 break;
1283 }
1284 ++i;
1285 }
1286 }
1287 return tmpPlayer;
1288 }
1289
1290 void
AddServerInfo(unsigned serverId,const ServerInfo & info)1291 ClientThread::AddServerInfo(unsigned serverId, const ServerInfo &info)
1292 {
1293 {
1294 boost::mutex::scoped_lock lock(m_serverInfoMapMutex);
1295 m_serverInfoMap.insert(ServerInfoMap::value_type(serverId, info));
1296 }
1297 GetCallback().SignalNetClientServerListAdd(serverId);
1298 }
1299
1300 void
ClearServerInfoMap()1301 ClientThread::ClearServerInfoMap()
1302 {
1303 {
1304 boost::mutex::scoped_lock lock(m_serverInfoMapMutex);
1305 m_serverInfoMap.clear();
1306 }
1307 GetCallback().SignalNetClientServerListClear();
1308 }
1309
1310 bool
GetSelectedServer(unsigned & serverId) const1311 ClientThread::GetSelectedServer(unsigned &serverId) const
1312 {
1313 bool retVal = false;
1314 boost::mutex::scoped_lock lock(m_selectServerMutex);
1315 if (m_isServerSelected) {
1316 retVal = true;
1317 serverId = m_selectedServerId;
1318 }
1319 return retVal;
1320 }
1321
1322 void
UseServer(unsigned serverId)1323 ClientThread::UseServer(unsigned serverId)
1324 {
1325 ClientContext &context = GetContext();
1326 ServerInfo useInfo(GetServerInfo(serverId));
1327
1328 if (context.GetAddrFamily() == AF_INET6)
1329 context.SetServerAddr(useInfo.ipv6addr);
1330 else
1331 context.SetServerAddr(useInfo.ipv4addr);
1332
1333 context.SetServerPort((unsigned)useInfo.port);
1334 context.SetAvatarServerAddr(useInfo.avatarServerAddr);
1335 }
1336
1337 bool
GetLoginData(LoginData & loginData) const1338 ClientThread::GetLoginData(LoginData &loginData) const
1339 {
1340 bool retVal = false;
1341 boost::mutex::scoped_lock lock(m_loginDataMutex);
1342 if (!m_loginData.userName.empty()) {
1343 loginData = m_loginData;
1344 retVal = true;
1345 }
1346 return retVal;
1347 }
1348
1349 void
AddGameInfo(unsigned gameId,const GameInfo & info)1350 ClientThread::AddGameInfo(unsigned gameId, const GameInfo &info)
1351 {
1352 {
1353 boost::mutex::scoped_lock lock(m_gameInfoMapMutex);
1354 m_gameInfoMap.insert(GameInfoMap::value_type(gameId, info));
1355 }
1356 GetCallback().SignalNetClientGameListNew(gameId);
1357 }
1358
1359 void
UpdateGameInfoMode(unsigned gameId,GameMode mode)1360 ClientThread::UpdateGameInfoMode(unsigned gameId, GameMode mode)
1361 {
1362 bool found = false;
1363 {
1364 boost::mutex::scoped_lock lock(m_gameInfoMapMutex);
1365 GameInfoMap::iterator pos = m_gameInfoMap.find(gameId);
1366 if (pos != m_gameInfoMap.end()) {
1367 found = true;
1368 (*pos).second.mode = mode;
1369 }
1370 }
1371 if (found)
1372 GetCallback().SignalNetClientGameListUpdateMode(gameId, mode);
1373 }
1374
1375 void
UpdateGameInfoAdmin(unsigned gameId,unsigned adminPlayerId)1376 ClientThread::UpdateGameInfoAdmin(unsigned gameId, unsigned adminPlayerId)
1377 {
1378 bool found = false;
1379 {
1380 boost::mutex::scoped_lock lock(m_gameInfoMapMutex);
1381 GameInfoMap::iterator pos = m_gameInfoMap.find(gameId);
1382 if (pos != m_gameInfoMap.end()) {
1383 found = true;
1384 (*pos).second.adminPlayerId = adminPlayerId;
1385 }
1386 }
1387 if (found)
1388 GetCallback().SignalNetClientGameListUpdateAdmin(gameId, adminPlayerId);
1389 }
1390
1391 void
RemoveGameInfo(unsigned gameId)1392 ClientThread::RemoveGameInfo(unsigned gameId)
1393 {
1394 bool found = false;
1395 {
1396 boost::mutex::scoped_lock lock(m_gameInfoMapMutex);
1397 GameInfoMap::iterator pos = m_gameInfoMap.find(gameId);
1398 if (pos != m_gameInfoMap.end()) {
1399 found = true;
1400 m_gameInfoMap.erase(pos);
1401 }
1402 }
1403 if (found)
1404 GetCallback().SignalNetClientGameListRemove(gameId);
1405 }
1406
1407 void
ModifyGameInfoAddPlayer(unsigned gameId,unsigned playerId)1408 ClientThread::ModifyGameInfoAddPlayer(unsigned gameId, unsigned playerId)
1409 {
1410 bool playerAdded = false;
1411 {
1412 boost::mutex::scoped_lock lock(m_gameInfoMapMutex);
1413 GameInfoMap::iterator pos = m_gameInfoMap.find(gameId);
1414 if (pos != m_gameInfoMap.end()) {
1415 pos->second.players.push_back(playerId);
1416 playerAdded = true;
1417 }
1418 }
1419 if (playerAdded)
1420 GetCallback().SignalNetClientGameListPlayerJoined(gameId, playerId);
1421 }
1422
1423 void
ModifyGameInfoRemovePlayer(unsigned gameId,unsigned playerId)1424 ClientThread::ModifyGameInfoRemovePlayer(unsigned gameId, unsigned playerId)
1425 {
1426 bool playerRemoved = false;
1427 {
1428 boost::mutex::scoped_lock lock(m_gameInfoMapMutex);
1429 GameInfoMap::iterator pos = m_gameInfoMap.find(gameId);
1430 if (pos != m_gameInfoMap.end()) {
1431 pos->second.players.remove(playerId);
1432 playerRemoved = true;
1433 }
1434 }
1435 if (playerRemoved)
1436 GetCallback().SignalNetClientGameListPlayerLeft(gameId, playerId);
1437 }
1438
1439 void
ModifyGameInfoAddSpectator(unsigned gameId,unsigned playerId)1440 ClientThread::ModifyGameInfoAddSpectator(unsigned gameId, unsigned playerId)
1441 {
1442 bool playerAdded = false;
1443 {
1444 boost::mutex::scoped_lock lock(m_gameInfoMapMutex);
1445 GameInfoMap::iterator pos = m_gameInfoMap.find(gameId);
1446 if (pos != m_gameInfoMap.end()) {
1447 pos->second.spectators.push_back(playerId);
1448 playerAdded = true;
1449 }
1450 }
1451 if (playerAdded)
1452 GetCallback().SignalNetClientGameListSpectatorJoined(gameId, playerId);
1453 }
1454
1455 void
ModifyGameInfoRemoveSpectator(unsigned gameId,unsigned playerId)1456 ClientThread::ModifyGameInfoRemoveSpectator(unsigned gameId, unsigned playerId)
1457 {
1458 bool playerRemoved = false;
1459 {
1460 boost::mutex::scoped_lock lock(m_gameInfoMapMutex);
1461 GameInfoMap::iterator pos = m_gameInfoMap.find(gameId);
1462 if (pos != m_gameInfoMap.end()) {
1463 pos->second.spectators.remove(playerId);
1464 playerRemoved = true;
1465 }
1466 }
1467 if (playerRemoved)
1468 GetCallback().SignalNetClientGameListSpectatorLeft(gameId, playerId);
1469 }
1470
1471 void
ModifyGameInfoClearSpectatorsDuringGame()1472 ClientThread::ModifyGameInfoClearSpectatorsDuringGame()
1473 {
1474 boost::mutex::scoped_lock lock(m_gameInfoMapMutex);
1475 GameInfoMap::iterator pos = m_gameInfoMap.find(GetGameId());
1476 if (pos != m_gameInfoMap.end()) {
1477 pos->second.spectatorsDuringGame.clear();
1478 }
1479 }
1480
1481 void
ModifyGameInfoAddSpectatorDuringGame(unsigned playerId)1482 ClientThread::ModifyGameInfoAddSpectatorDuringGame(unsigned playerId)
1483 {
1484 bool spectatorAdded = false;
1485 {
1486 boost::mutex::scoped_lock lock(m_gameInfoMapMutex);
1487 GameInfoMap::iterator pos = m_gameInfoMap.find(GetGameId());
1488 if (pos != m_gameInfoMap.end()) {
1489 pos->second.spectatorsDuringGame.push_back(playerId);
1490 spectatorAdded = true;
1491 }
1492 }
1493 if (spectatorAdded)
1494 GetCallback().SignalNetClientSpectatorJoined(playerId, GetPlayerName(playerId));
1495 }
1496
1497 void
ModifyGameInfoRemoveSpectatorDuringGame(unsigned playerId,int removeReason)1498 ClientThread::ModifyGameInfoRemoveSpectatorDuringGame(unsigned playerId, int removeReason)
1499 {
1500 bool spectatorRemoved = false;
1501 {
1502 boost::mutex::scoped_lock lock(m_gameInfoMapMutex);
1503 GameInfoMap::iterator pos = m_gameInfoMap.find(GetGameId());
1504 if (pos != m_gameInfoMap.end()) {
1505 pos->second.spectatorsDuringGame.remove(playerId);
1506 spectatorRemoved = true;
1507 }
1508 }
1509 if (spectatorRemoved)
1510 GetCallback().SignalNetClientSpectatorLeft(playerId, GetPlayerName(playerId), removeReason);
1511 }
1512
1513 void
ClearGameInfoMap()1514 ClientThread::ClearGameInfoMap()
1515 {
1516 boost::mutex::scoped_lock lock(m_gameInfoMapMutex);
1517 m_gameInfoMap.clear();
1518 }
1519
1520 void
StartPetition(unsigned petitionId,unsigned proposingPlayerId,unsigned kickPlayerId,int timeoutSec,int numVotesToKick)1521 ClientThread::StartPetition(unsigned petitionId, unsigned proposingPlayerId, unsigned kickPlayerId, int timeoutSec, int numVotesToKick)
1522 {
1523 {
1524 boost::mutex::scoped_lock lock(m_curPetitionIdMutex);
1525 m_curPetitionId = petitionId;
1526 }
1527 GetGui().startVoteOnKick(kickPlayerId, proposingPlayerId, timeoutSec, numVotesToKick);
1528 if (GetGuiPlayerId() != kickPlayerId
1529 && GetGuiPlayerId() != proposingPlayerId) {
1530 GetGui().changeVoteOnKickButtonsState(true);
1531 }
1532 }
1533
1534 void
UpdatePetition(unsigned petitionId,int,int numVotesInFavourOfKicking,int numVotesToKick)1535 ClientThread::UpdatePetition(unsigned petitionId, int /*numVotesAgainstKicking*/, int numVotesInFavourOfKicking, int numVotesToKick)
1536 {
1537 bool isCurPetition;
1538 {
1539 boost::mutex::scoped_lock lock(m_curPetitionIdMutex);
1540 isCurPetition = m_curPetitionId == petitionId;
1541 }
1542 if (isCurPetition) {
1543 GetGui().refreshVotesMonitor(numVotesInFavourOfKicking, numVotesToKick);
1544 }
1545 }
1546
1547 void
EndPetition(unsigned petitionId)1548 ClientThread::EndPetition(unsigned petitionId)
1549 {
1550 bool isCurPetition;
1551 {
1552 boost::mutex::scoped_lock lock(m_curPetitionIdMutex);
1553 isCurPetition = m_curPetitionId == petitionId;
1554 }
1555 if (isCurPetition)
1556 GetGui().endVoteOnKick();
1557 }
1558
1559 void
UpdateStatData(const ServerStats & stats)1560 ClientThread::UpdateStatData(const ServerStats &stats)
1561 {
1562 boost::mutex::scoped_lock lock(m_curStatsMutex);
1563 if (stats.numberOfPlayersOnServer)
1564 m_curStats.numberOfPlayersOnServer = stats.numberOfPlayersOnServer;
1565
1566 if (stats.totalPlayersEverLoggedIn)
1567 m_curStats.totalPlayersEverLoggedIn = stats.totalPlayersEverLoggedIn;
1568
1569 if (stats.totalGamesEverCreated)
1570 m_curStats.totalGamesEverCreated = stats.totalGamesEverCreated;
1571
1572 GetCallback().SignalNetClientStatsUpdate(m_curStats);
1573 }
1574
1575 void
EndPing()1576 ClientThread::EndPing()
1577 {
1578 boost::mutex::scoped_lock lock(m_pingDataMutex);
1579 if (m_pingData.EndPing()) {
1580 GetCallback().SignalNetClientPingUpdate(m_pingData.MinPing(), m_pingData.AveragePing(), m_pingData.MaxPing());
1581 }
1582 }
1583
1584 ServerStats
GetStatData() const1585 ClientThread::GetStatData() const
1586 {
1587 boost::mutex::scoped_lock lock(m_curStatsMutex);
1588 return m_curStats;
1589 }
1590
1591 bool
IsSessionEstablished() const1592 ClientThread::IsSessionEstablished() const
1593 {
1594 return m_sessionEstablished;
1595 }
1596
1597 void
SetSessionEstablished(bool flag)1598 ClientThread::SetSessionEstablished(bool flag)
1599 {
1600 if (m_sessionEstablished != flag) {
1601 m_sessionEstablished = flag;
1602 if (flag)
1603 SendQueuedPackets();
1604 }
1605 }
1606
1607 bool
IsSynchronized() const1608 ClientThread::IsSynchronized() const
1609 {
1610 return m_playerInfoRequestList.empty();
1611 }
1612
1613 void
ReadSessionGuidFromFile()1614 ClientThread::ReadSessionGuidFromFile()
1615 {
1616 string guidFileName(GetContext().GetCacheDir() + TEMP_GUID_FILENAME);
1617 std::ifstream guidStream(guidFileName.c_str(), ios::in | ios::binary);
1618 if (guidStream.good()) {
1619 std::vector<char> tmpGuid(CLIENT_GUID_SIZE);
1620 guidStream.read(&tmpGuid[0], CLIENT_GUID_SIZE);
1621 GetContext().SetSessionGuid(string(tmpGuid.begin(), tmpGuid.end()));
1622 }
1623 }
1624
1625 void
WriteSessionGuidToFile() const1626 ClientThread::WriteSessionGuidToFile() const
1627 {
1628 string guidFileName(GetContext().GetCacheDir() + TEMP_GUID_FILENAME);
1629 std::ofstream guidStream(guidFileName.c_str(), ios::out | ios::trunc | ios::binary);
1630 if (guidStream.good()) {
1631 guidStream.write(GetContext().GetSessionGuid().c_str(), GetContext().GetSessionGuid().size());
1632 }
1633 }
1634
1635