1 /* Copyright (C) 2018 Wildfire Games.
2 * This file is part of 0 A.D.
3 *
4 * 0 A.D. is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * 0 A.D. is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18 #include "precompiled.h"
19
20 #include "NetClient.h"
21
22 #include "NetClientTurnManager.h"
23 #include "NetMessage.h"
24 #include "NetSession.h"
25
26 #include "lib/byte_order.h"
27 #include "lib/external_libraries/enet.h"
28 #include "lib/sysdep/sysdep.h"
29 #include "lobby/IXmppClient.h"
30 #include "ps/CConsole.h"
31 #include "ps/CLogger.h"
32 #include "ps/Compress.h"
33 #include "ps/CStr.h"
34 #include "ps/Game.h"
35 #include "ps/Loader.h"
36 #include "scriptinterface/ScriptInterface.h"
37 #include "simulation2/Simulation2.h"
38
39 CNetClient *g_NetClient = NULL;
40
41 /**
42 * Async task for receiving the initial game state when rejoining an
43 * in-progress network game.
44 */
45 class CNetFileReceiveTask_ClientRejoin : public CNetFileReceiveTask
46 {
47 NONCOPYABLE(CNetFileReceiveTask_ClientRejoin);
48 public:
CNetFileReceiveTask_ClientRejoin(CNetClient & client)49 CNetFileReceiveTask_ClientRejoin(CNetClient& client)
50 : m_Client(client)
51 {
52 }
53
OnComplete()54 virtual void OnComplete()
55 {
56 // We've received the game state from the server
57
58 // Save it so we can use it after the map has finished loading
59 m_Client.m_JoinSyncBuffer = m_Buffer;
60
61 // Pretend the server told us to start the game
62 CGameStartMessage start;
63 m_Client.HandleMessage(&start);
64 }
65
66 private:
67 CNetClient& m_Client;
68 };
69
CNetClient(CGame * game,bool isLocalClient)70 CNetClient::CNetClient(CGame* game, bool isLocalClient) :
71 m_Session(NULL),
72 m_UserName(L"anonymous"),
73 m_HostID((u32)-1), m_ClientTurnManager(NULL), m_Game(game),
74 m_GameAttributes(game->GetSimulation2()->GetScriptInterface().GetContext()),
75 m_IsLocalClient(isLocalClient),
76 m_LastConnectionCheck(0),
77 m_Rejoin(false)
78 {
79 m_Game->SetTurnManager(NULL); // delete the old local turn manager so we don't accidentally use it
80
81 void* context = this;
82
83 JS_AddExtraGCRootsTracer(GetScriptInterface().GetJSRuntime(), CNetClient::Trace, this);
84
85 // Set up transitions for session
86 AddTransition(NCS_UNCONNECTED, (uint)NMT_CONNECT_COMPLETE, NCS_CONNECT, (void*)&OnConnect, context);
87
88 AddTransition(NCS_CONNECT, (uint)NMT_SERVER_HANDSHAKE, NCS_HANDSHAKE, (void*)&OnHandshake, context);
89
90 AddTransition(NCS_HANDSHAKE, (uint)NMT_SERVER_HANDSHAKE_RESPONSE, NCS_AUTHENTICATE, (void*)&OnHandshakeResponse, context);
91
92 AddTransition(NCS_AUTHENTICATE, (uint)NMT_AUTHENTICATE, NCS_AUTHENTICATE, (void*)&OnAuthenticateRequest, context);
93 AddTransition(NCS_AUTHENTICATE, (uint)NMT_AUTHENTICATE_RESULT, NCS_INITIAL_GAMESETUP, (void*)&OnAuthenticate, context);
94
95 AddTransition(NCS_INITIAL_GAMESETUP, (uint)NMT_GAME_SETUP, NCS_PREGAME, (void*)&OnGameSetup, context);
96
97 AddTransition(NCS_PREGAME, (uint)NMT_CHAT, NCS_PREGAME, (void*)&OnChat, context);
98 AddTransition(NCS_PREGAME, (uint)NMT_READY, NCS_PREGAME, (void*)&OnReady, context);
99 AddTransition(NCS_PREGAME, (uint)NMT_GAME_SETUP, NCS_PREGAME, (void*)&OnGameSetup, context);
100 AddTransition(NCS_PREGAME, (uint)NMT_PLAYER_ASSIGNMENT, NCS_PREGAME, (void*)&OnPlayerAssignment, context);
101 AddTransition(NCS_PREGAME, (uint)NMT_KICKED, NCS_PREGAME, (void*)&OnKicked, context);
102 AddTransition(NCS_PREGAME, (uint)NMT_CLIENT_TIMEOUT, NCS_PREGAME, (void*)&OnClientTimeout, context);
103 AddTransition(NCS_PREGAME, (uint)NMT_CLIENT_PERFORMANCE, NCS_PREGAME, (void*)&OnClientPerformance, context);
104 AddTransition(NCS_PREGAME, (uint)NMT_GAME_START, NCS_LOADING, (void*)&OnGameStart, context);
105 AddTransition(NCS_PREGAME, (uint)NMT_JOIN_SYNC_START, NCS_JOIN_SYNCING, (void*)&OnJoinSyncStart, context);
106
107 AddTransition(NCS_JOIN_SYNCING, (uint)NMT_CHAT, NCS_JOIN_SYNCING, (void*)&OnChat, context);
108 AddTransition(NCS_JOIN_SYNCING, (uint)NMT_GAME_SETUP, NCS_JOIN_SYNCING, (void*)&OnGameSetup, context);
109 AddTransition(NCS_JOIN_SYNCING, (uint)NMT_PLAYER_ASSIGNMENT, NCS_JOIN_SYNCING, (void*)&OnPlayerAssignment, context);
110 AddTransition(NCS_JOIN_SYNCING, (uint)NMT_KICKED, NCS_JOIN_SYNCING, (void*)&OnKicked, context);
111 AddTransition(NCS_JOIN_SYNCING, (uint)NMT_CLIENT_TIMEOUT, NCS_JOIN_SYNCING, (void*)&OnClientTimeout, context);
112 AddTransition(NCS_JOIN_SYNCING, (uint)NMT_CLIENT_PERFORMANCE, NCS_JOIN_SYNCING, (void*)&OnClientPerformance, context);
113 AddTransition(NCS_JOIN_SYNCING, (uint)NMT_GAME_START, NCS_JOIN_SYNCING, (void*)&OnGameStart, context);
114 AddTransition(NCS_JOIN_SYNCING, (uint)NMT_SIMULATION_COMMAND, NCS_JOIN_SYNCING, (void*)&OnInGame, context);
115 AddTransition(NCS_JOIN_SYNCING, (uint)NMT_END_COMMAND_BATCH, NCS_JOIN_SYNCING, (void*)&OnJoinSyncEndCommandBatch, context);
116 AddTransition(NCS_JOIN_SYNCING, (uint)NMT_LOADED_GAME, NCS_INGAME, (void*)&OnLoadedGame, context);
117
118 AddTransition(NCS_LOADING, (uint)NMT_CHAT, NCS_LOADING, (void*)&OnChat, context);
119 AddTransition(NCS_LOADING, (uint)NMT_GAME_SETUP, NCS_LOADING, (void*)&OnGameSetup, context);
120 AddTransition(NCS_LOADING, (uint)NMT_PLAYER_ASSIGNMENT, NCS_LOADING, (void*)&OnPlayerAssignment, context);
121 AddTransition(NCS_LOADING, (uint)NMT_KICKED, NCS_LOADING, (void*)&OnKicked, context);
122 AddTransition(NCS_LOADING, (uint)NMT_CLIENT_TIMEOUT, NCS_LOADING, (void*)&OnClientTimeout, context);
123 AddTransition(NCS_LOADING, (uint)NMT_CLIENT_PERFORMANCE, NCS_LOADING, (void*)&OnClientPerformance, context);
124 AddTransition(NCS_LOADING, (uint)NMT_CLIENTS_LOADING, NCS_LOADING, (void*)&OnClientsLoading, context);
125 AddTransition(NCS_LOADING, (uint)NMT_LOADED_GAME, NCS_INGAME, (void*)&OnLoadedGame, context);
126
127 AddTransition(NCS_INGAME, (uint)NMT_REJOINED, NCS_INGAME, (void*)&OnRejoined, context);
128 AddTransition(NCS_INGAME, (uint)NMT_KICKED, NCS_INGAME, (void*)&OnKicked, context);
129 AddTransition(NCS_INGAME, (uint)NMT_CLIENT_TIMEOUT, NCS_INGAME, (void*)&OnClientTimeout, context);
130 AddTransition(NCS_INGAME, (uint)NMT_CLIENT_PERFORMANCE, NCS_INGAME, (void*)&OnClientPerformance, context);
131 AddTransition(NCS_INGAME, (uint)NMT_CLIENTS_LOADING, NCS_INGAME, (void*)&OnClientsLoading, context);
132 AddTransition(NCS_INGAME, (uint)NMT_CLIENT_PAUSED, NCS_INGAME, (void*)&OnClientPaused, context);
133 AddTransition(NCS_INGAME, (uint)NMT_CHAT, NCS_INGAME, (void*)&OnChat, context);
134 AddTransition(NCS_INGAME, (uint)NMT_GAME_SETUP, NCS_INGAME, (void*)&OnGameSetup, context);
135 AddTransition(NCS_INGAME, (uint)NMT_PLAYER_ASSIGNMENT, NCS_INGAME, (void*)&OnPlayerAssignment, context);
136 AddTransition(NCS_INGAME, (uint)NMT_SIMULATION_COMMAND, NCS_INGAME, (void*)&OnInGame, context);
137 AddTransition(NCS_INGAME, (uint)NMT_SYNC_ERROR, NCS_INGAME, (void*)&OnInGame, context);
138 AddTransition(NCS_INGAME, (uint)NMT_END_COMMAND_BATCH, NCS_INGAME, (void*)&OnInGame, context);
139
140 // Set first state
141 SetFirstState(NCS_UNCONNECTED);
142 }
143
~CNetClient()144 CNetClient::~CNetClient()
145 {
146 DestroyConnection();
147 JS_RemoveExtraGCRootsTracer(GetScriptInterface().GetJSRuntime(), CNetClient::Trace, this);
148 }
149
TraceMember(JSTracer * trc)150 void CNetClient::TraceMember(JSTracer *trc)
151 {
152 for (JS::Heap<JS::Value>& guiMessage : m_GuiMessageQueue)
153 JS_CallValueTracer(trc, &guiMessage, "m_GuiMessageQueue");
154 }
155
SetUserName(const CStrW & username)156 void CNetClient::SetUserName(const CStrW& username)
157 {
158 ENSURE(!m_Session); // must be called before we start the connection
159
160 m_UserName = username;
161 }
162
SetHostingPlayerName(const CStr & hostingPlayerName)163 void CNetClient::SetHostingPlayerName(const CStr& hostingPlayerName)
164 {
165 m_HostingPlayerName = hostingPlayerName;
166 }
167
SetupConnection(const CStr & server,const u16 port,ENetHost * enetClient)168 bool CNetClient::SetupConnection(const CStr& server, const u16 port, ENetHost* enetClient)
169 {
170 CNetClientSession* session = new CNetClientSession(*this);
171 bool ok = session->Connect(server, port, m_IsLocalClient, enetClient);
172 SetAndOwnSession(session);
173 return ok;
174 }
175
SetAndOwnSession(CNetClientSession * session)176 void CNetClient::SetAndOwnSession(CNetClientSession* session)
177 {
178 delete m_Session;
179 m_Session = session;
180 }
181
DestroyConnection()182 void CNetClient::DestroyConnection()
183 {
184 // Attempt to send network messages from the current frame before connection is destroyed.
185 if (m_ClientTurnManager)
186 {
187 m_ClientTurnManager->OnDestroyConnection();
188 Flush();
189 }
190 SAFE_DELETE(m_Session);
191 }
192
Poll()193 void CNetClient::Poll()
194 {
195 if (!m_Session)
196 return;
197
198 CheckServerConnection();
199 m_Session->Poll();
200 }
201
CheckServerConnection()202 void CNetClient::CheckServerConnection()
203 {
204 // Trigger local warnings if the connection to the server is bad.
205 // At most once per second.
206 std::time_t now = std::time(nullptr);
207 if (now <= m_LastConnectionCheck)
208 return;
209
210 m_LastConnectionCheck = now;
211
212 JSContext* cx = GetScriptInterface().GetContext();
213 JSAutoRequest rq(cx);
214
215 // Report if we are losing the connection to the server
216 u32 lastReceived = m_Session->GetLastReceivedTime();
217 if (lastReceived > NETWORK_WARNING_TIMEOUT)
218 {
219 JS::RootedValue msg(cx);
220 GetScriptInterface().Eval("({ 'type':'netwarn', 'warntype': 'server-timeout' })", &msg);
221 GetScriptInterface().SetProperty(msg, "lastReceivedTime", lastReceived);
222 PushGuiMessage(msg);
223 return;
224 }
225
226 // Report if we have a bad ping to the server
227 u32 meanRTT = m_Session->GetMeanRTT();
228 if (meanRTT > DEFAULT_TURN_LENGTH_MP)
229 {
230 JS::RootedValue msg(cx);
231 GetScriptInterface().Eval("({ 'type':'netwarn', 'warntype': 'server-latency' })", &msg);
232 GetScriptInterface().SetProperty(msg, "meanRTT", meanRTT);
233 PushGuiMessage(msg);
234 }
235 }
236
Flush()237 void CNetClient::Flush()
238 {
239 if (m_Session)
240 m_Session->Flush();
241 }
242
GuiPoll(JS::MutableHandleValue ret)243 void CNetClient::GuiPoll(JS::MutableHandleValue ret)
244 {
245 if (m_GuiMessageQueue.empty())
246 {
247 ret.setUndefined();
248 return;
249 }
250
251 ret.set(m_GuiMessageQueue.front());
252 m_GuiMessageQueue.pop_front();
253 }
254
PushGuiMessage(const JS::HandleValue message)255 void CNetClient::PushGuiMessage(const JS::HandleValue message)
256 {
257 ENSURE(!message.isUndefined());
258
259 m_GuiMessageQueue.push_back(JS::Heap<JS::Value>(message));
260 }
261
TestReadGuiMessages()262 std::string CNetClient::TestReadGuiMessages()
263 {
264 JSContext* cx = GetScriptInterface().GetContext();
265 JSAutoRequest rq(cx);
266
267 std::string r;
268 JS::RootedValue msg(cx);
269 while (true)
270 {
271 GuiPoll(&msg);
272 if (msg.isUndefined())
273 break;
274 r += GetScriptInterface().ToString(&msg) + "\n";
275 }
276 return r;
277 }
278
GetScriptInterface()279 const ScriptInterface& CNetClient::GetScriptInterface()
280 {
281 return m_Game->GetSimulation2()->GetScriptInterface();
282 }
283
PostPlayerAssignmentsToScript()284 void CNetClient::PostPlayerAssignmentsToScript()
285 {
286 JSContext* cx = GetScriptInterface().GetContext();
287 JSAutoRequest rq(cx);
288
289 JS::RootedValue msg(cx);
290 GetScriptInterface().Eval("({'type':'players', 'newAssignments':{}})", &msg);
291
292 JS::RootedValue newAssignments(cx);
293 GetScriptInterface().GetProperty(msg, "newAssignments", &newAssignments);
294
295 for (const std::pair<CStr, PlayerAssignment>& p : m_PlayerAssignments)
296 {
297 JS::RootedValue assignment(cx);
298 GetScriptInterface().Eval("({})", &assignment);
299 GetScriptInterface().SetProperty(assignment, "name", CStrW(p.second.m_Name), false);
300 GetScriptInterface().SetProperty(assignment, "player", p.second.m_PlayerID, false);
301 GetScriptInterface().SetProperty(assignment, "status", p.second.m_Status, false);
302 GetScriptInterface().SetProperty(newAssignments, p.first.c_str(), assignment, false);
303 }
304
305 PushGuiMessage(msg);
306 }
307
SendMessage(const CNetMessage * message)308 bool CNetClient::SendMessage(const CNetMessage* message)
309 {
310 if (!m_Session)
311 return false;
312
313 return m_Session->SendMessage(message);
314 }
315
HandleConnect()316 void CNetClient::HandleConnect()
317 {
318 Update((uint)NMT_CONNECT_COMPLETE, NULL);
319 }
320
HandleDisconnect(u32 reason)321 void CNetClient::HandleDisconnect(u32 reason)
322 {
323 JSContext* cx = GetScriptInterface().GetContext();
324 JSAutoRequest rq(cx);
325
326 JS::RootedValue msg(cx);
327 GetScriptInterface().Eval("({'type':'netstatus','status':'disconnected'})", &msg);
328 GetScriptInterface().SetProperty(msg, "reason", (int)reason, false);
329 PushGuiMessage(msg);
330
331 SAFE_DELETE(m_Session);
332
333 // Update the state immediately to UNCONNECTED (don't bother with FSM transitions since
334 // we'd need one for every single state, and we don't need to use per-state actions)
335 SetCurrState(NCS_UNCONNECTED);
336 }
337
SendGameSetupMessage(JS::MutableHandleValue attrs,const ScriptInterface & scriptInterface)338 void CNetClient::SendGameSetupMessage(JS::MutableHandleValue attrs, const ScriptInterface& scriptInterface)
339 {
340 CGameSetupMessage gameSetup(scriptInterface);
341 gameSetup.m_Data = attrs;
342 SendMessage(&gameSetup);
343 }
344
SendAssignPlayerMessage(const int playerID,const CStr & guid)345 void CNetClient::SendAssignPlayerMessage(const int playerID, const CStr& guid)
346 {
347 CAssignPlayerMessage assignPlayer;
348 assignPlayer.m_PlayerID = playerID;
349 assignPlayer.m_GUID = guid;
350 SendMessage(&assignPlayer);
351 }
352
SendChatMessage(const std::wstring & text)353 void CNetClient::SendChatMessage(const std::wstring& text)
354 {
355 CChatMessage chat;
356 chat.m_Message = text;
357 SendMessage(&chat);
358 }
359
SendReadyMessage(const int status)360 void CNetClient::SendReadyMessage(const int status)
361 {
362 CReadyMessage readyStatus;
363 readyStatus.m_Status = status;
364 SendMessage(&readyStatus);
365 }
366
SendClearAllReadyMessage()367 void CNetClient::SendClearAllReadyMessage()
368 {
369 CClearAllReadyMessage clearAllReady;
370 SendMessage(&clearAllReady);
371 }
372
SendStartGameMessage()373 void CNetClient::SendStartGameMessage()
374 {
375 CGameStartMessage gameStart;
376 SendMessage(&gameStart);
377 }
378
SendRejoinedMessage()379 void CNetClient::SendRejoinedMessage()
380 {
381 CRejoinedMessage rejoinedMessage;
382 SendMessage(&rejoinedMessage);
383 }
384
SendKickPlayerMessage(const CStrW & playerName,bool ban)385 void CNetClient::SendKickPlayerMessage(const CStrW& playerName, bool ban)
386 {
387 CKickedMessage kickPlayer;
388 kickPlayer.m_Name = playerName;
389 kickPlayer.m_Ban = ban;
390 SendMessage(&kickPlayer);
391 }
392
SendPausedMessage(bool pause)393 void CNetClient::SendPausedMessage(bool pause)
394 {
395 CClientPausedMessage pausedMessage;
396 pausedMessage.m_Pause = pause;
397 SendMessage(&pausedMessage);
398 }
399
HandleMessage(CNetMessage * message)400 bool CNetClient::HandleMessage(CNetMessage* message)
401 {
402 // Handle non-FSM messages first
403
404 Status status = m_Session->GetFileTransferer().HandleMessageReceive(message);
405 if (status == INFO::OK)
406 return true;
407 if (status != INFO::SKIPPED)
408 return false;
409
410 if (message->GetType() == NMT_FILE_TRANSFER_REQUEST)
411 {
412 CFileTransferRequestMessage* reqMessage = (CFileTransferRequestMessage*)message;
413
414 // TODO: we should support different transfer request types, instead of assuming
415 // it's always requesting the simulation state
416
417 std::stringstream stream;
418
419 LOGMESSAGERENDER("Serializing game at turn %u for rejoining player", m_ClientTurnManager->GetCurrentTurn());
420 u32 turn = to_le32(m_ClientTurnManager->GetCurrentTurn());
421 stream.write((char*)&turn, sizeof(turn));
422
423 bool ok = m_Game->GetSimulation2()->SerializeState(stream);
424 ENSURE(ok);
425
426 // Compress the content with zlib to save bandwidth
427 // (TODO: if this is still too large, compressing with e.g. LZMA works much better)
428 std::string compressed;
429 CompressZLib(stream.str(), compressed, true);
430
431 m_Session->GetFileTransferer().StartResponse(reqMessage->m_RequestID, compressed);
432
433 return true;
434 }
435
436 // Update FSM
437 bool ok = Update(message->GetType(), message);
438 if (!ok)
439 LOGERROR("Net client: Error running FSM update (type=%d state=%d)", (int)message->GetType(), (int)GetCurrState());
440 return ok;
441 }
442
LoadFinished()443 void CNetClient::LoadFinished()
444 {
445 JSContext* cx = GetScriptInterface().GetContext();
446 JSAutoRequest rq(cx);
447
448 if (!m_JoinSyncBuffer.empty())
449 {
450 // We're rejoining a game, and just finished loading the initial map,
451 // so deserialize the saved game state now
452
453 std::string state;
454 DecompressZLib(m_JoinSyncBuffer, state, true);
455
456 std::stringstream stream(state);
457
458 u32 turn;
459 stream.read((char*)&turn, sizeof(turn));
460 turn = to_le32(turn);
461
462 LOGMESSAGE("Rejoining client deserializing state at turn %u\n", turn);
463
464 bool ok = m_Game->GetSimulation2()->DeserializeState(stream);
465 ENSURE(ok);
466
467 m_ClientTurnManager->ResetState(turn, turn);
468
469 JS::RootedValue msg(cx);
470 GetScriptInterface().Eval("({'type':'netstatus','status':'join_syncing'})", &msg);
471 PushGuiMessage(msg);
472 }
473 else
474 {
475 // Connecting at the start of a game, so we'll wait for other players to finish loading
476 JS::RootedValue msg(cx);
477 GetScriptInterface().Eval("({'type':'netstatus','status':'waiting_for_players'})", &msg);
478 PushGuiMessage(msg);
479 }
480
481 CLoadedGameMessage loaded;
482 loaded.m_CurrentTurn = m_ClientTurnManager->GetCurrentTurn();
483 SendMessage(&loaded);
484 }
485
SendAuthenticateMessage()486 void CNetClient::SendAuthenticateMessage()
487 {
488 CAuthenticateMessage authenticate;
489 authenticate.m_Name = m_UserName;
490 authenticate.m_Password = L""; // TODO
491 authenticate.m_IsLocalClient = m_IsLocalClient;
492 SendMessage(&authenticate);
493 }
494
OnConnect(void * context,CFsmEvent * event)495 bool CNetClient::OnConnect(void* context, CFsmEvent* event)
496 {
497 ENSURE(event->GetType() == (uint)NMT_CONNECT_COMPLETE);
498
499 CNetClient* client = (CNetClient*)context;
500
501 JSContext* cx = client->GetScriptInterface().GetContext();
502 JSAutoRequest rq(cx);
503
504 JS::RootedValue msg(cx);
505 client->GetScriptInterface().Eval("({'type':'netstatus','status':'connected'})", &msg);
506 client->PushGuiMessage(msg);
507
508 return true;
509 }
510
OnHandshake(void * context,CFsmEvent * event)511 bool CNetClient::OnHandshake(void* context, CFsmEvent* event)
512 {
513 ENSURE(event->GetType() == (uint)NMT_SERVER_HANDSHAKE);
514
515 CNetClient* client = (CNetClient*)context;
516
517 CCliHandshakeMessage handshake;
518 handshake.m_MagicResponse = PS_PROTOCOL_MAGIC_RESPONSE;
519 handshake.m_ProtocolVersion = PS_PROTOCOL_VERSION;
520 handshake.m_SoftwareVersion = PS_PROTOCOL_VERSION;
521 client->SendMessage(&handshake);
522
523 return true;
524 }
525
OnHandshakeResponse(void * context,CFsmEvent * event)526 bool CNetClient::OnHandshakeResponse(void* context, CFsmEvent* event)
527 {
528 ENSURE(event->GetType() == (uint)NMT_SERVER_HANDSHAKE_RESPONSE);
529
530 CNetClient* client = (CNetClient*)context;
531
532 CSrvHandshakeResponseMessage* message = (CSrvHandshakeResponseMessage*)event->GetParamRef();
533 client->m_GUID = message->m_GUID;
534
535 if (message->m_Flags & PS_NETWORK_FLAG_REQUIRE_LOBBYAUTH)
536 {
537 if (g_XmppClient && !client->m_HostingPlayerName.empty())
538 g_XmppClient->SendIqLobbyAuth(client->m_HostingPlayerName, client->m_GUID);
539 else
540 {
541 JSContext* cx = client->GetScriptInterface().GetContext();
542 JSAutoRequest rq(cx);
543
544 JS::RootedValue msg(cx);
545 client->GetScriptInterface().Eval("({'type':'netstatus','status':'disconnected'})", &msg);
546 client->GetScriptInterface().SetProperty(msg, "reason", (int)NDR_LOBBY_AUTH_FAILED, false);
547 client->PushGuiMessage(msg);
548 LOGMESSAGE("Net client: Couldn't send lobby auth xmpp message");
549 }
550 return true;
551 }
552
553 client->SendAuthenticateMessage();
554 return true;
555 }
556
OnAuthenticateRequest(void * context,CFsmEvent * event)557 bool CNetClient::OnAuthenticateRequest(void* context, CFsmEvent* event)
558 {
559 ENSURE(event->GetType() == (uint)NMT_AUTHENTICATE);
560
561 CNetClient* client = (CNetClient*)context;
562 client->SendAuthenticateMessage();
563 return true;
564 }
565
OnAuthenticate(void * context,CFsmEvent * event)566 bool CNetClient::OnAuthenticate(void* context, CFsmEvent* event)
567 {
568 ENSURE(event->GetType() == (uint)NMT_AUTHENTICATE_RESULT);
569
570 CNetClient* client = (CNetClient*)context;
571
572 JSContext* cx = client->GetScriptInterface().GetContext();
573 JSAutoRequest rq(cx);
574
575 CAuthenticateResultMessage* message = (CAuthenticateResultMessage*)event->GetParamRef();
576
577 LOGMESSAGE("Net: Authentication result: host=%u, %s", message->m_HostID, utf8_from_wstring(message->m_Message));
578
579 client->m_HostID = message->m_HostID;
580 client->m_Rejoin = message->m_Code == ARC_OK_REJOINING;
581
582 JS::RootedValue msg(cx);
583 client->GetScriptInterface().Eval("({'type':'netstatus','status':'authenticated'})", &msg);
584 client->GetScriptInterface().SetProperty(msg, "rejoining", client->m_Rejoin);
585 client->PushGuiMessage(msg);
586
587 return true;
588 }
589
OnChat(void * context,CFsmEvent * event)590 bool CNetClient::OnChat(void* context, CFsmEvent* event)
591 {
592 ENSURE(event->GetType() == (uint)NMT_CHAT);
593
594 CNetClient* client = (CNetClient*)context;
595 JSContext* cx = client->GetScriptInterface().GetContext();
596 JSAutoRequest rq(cx);
597
598 CChatMessage* message = (CChatMessage*)event->GetParamRef();
599
600 JS::RootedValue msg(cx);
601 client->GetScriptInterface().Eval("({'type':'chat'})", &msg);
602 client->GetScriptInterface().SetProperty(msg, "guid", std::string(message->m_GUID), false);
603 client->GetScriptInterface().SetProperty(msg, "text", std::wstring(message->m_Message), false);
604 client->PushGuiMessage(msg);
605
606 return true;
607 }
608
OnReady(void * context,CFsmEvent * event)609 bool CNetClient::OnReady(void* context, CFsmEvent* event)
610 {
611 ENSURE(event->GetType() == (uint)NMT_READY);
612
613 CNetClient* client = (CNetClient*)context;
614 JSContext* cx = client->GetScriptInterface().GetContext();
615 JSAutoRequest rq(cx);
616
617 CReadyMessage* message = (CReadyMessage*)event->GetParamRef();
618
619 JS::RootedValue msg(cx);
620 client->GetScriptInterface().Eval("({'type':'ready'})", &msg);
621 client->GetScriptInterface().SetProperty(msg, "guid", std::string(message->m_GUID), false);
622 client->GetScriptInterface().SetProperty(msg, "status", int (message->m_Status), false);
623 client->PushGuiMessage(msg);
624
625 return true;
626 }
627
OnGameSetup(void * context,CFsmEvent * event)628 bool CNetClient::OnGameSetup(void* context, CFsmEvent* event)
629 {
630 ENSURE(event->GetType() == (uint)NMT_GAME_SETUP);
631
632 CNetClient* client = (CNetClient*)context;
633 JSContext* cx = client->GetScriptInterface().GetContext();
634 JSAutoRequest rq(cx);
635
636 CGameSetupMessage* message = (CGameSetupMessage*)event->GetParamRef();
637
638 client->m_GameAttributes = message->m_Data;
639
640 JS::RootedValue msg(cx);
641 client->GetScriptInterface().Eval("({'type':'gamesetup'})", &msg);
642 client->GetScriptInterface().SetProperty(msg, "data", message->m_Data, false);
643 client->PushGuiMessage(msg);
644
645 return true;
646 }
647
OnPlayerAssignment(void * context,CFsmEvent * event)648 bool CNetClient::OnPlayerAssignment(void* context, CFsmEvent* event)
649 {
650 ENSURE(event->GetType() == (uint)NMT_PLAYER_ASSIGNMENT);
651
652 CNetClient* client = (CNetClient*)context;
653
654 CPlayerAssignmentMessage* message = (CPlayerAssignmentMessage*)event->GetParamRef();
655
656 // Unpack the message
657 PlayerAssignmentMap newPlayerAssignments;
658 for (size_t i = 0; i < message->m_Hosts.size(); ++i)
659 {
660 PlayerAssignment assignment;
661 assignment.m_Enabled = true;
662 assignment.m_Name = message->m_Hosts[i].m_Name;
663 assignment.m_PlayerID = message->m_Hosts[i].m_PlayerID;
664 assignment.m_Status = message->m_Hosts[i].m_Status;
665 newPlayerAssignments[message->m_Hosts[i].m_GUID] = assignment;
666 }
667
668 client->m_PlayerAssignments.swap(newPlayerAssignments);
669
670 client->PostPlayerAssignmentsToScript();
671
672 return true;
673 }
674
675 // This is called either when the host clicks the StartGame button or
676 // if this client rejoins and finishes the download of the simstate.
OnGameStart(void * context,CFsmEvent * event)677 bool CNetClient::OnGameStart(void* context, CFsmEvent* event)
678 {
679 ENSURE(event->GetType() == (uint)NMT_GAME_START);
680
681 CNetClient* client = (CNetClient*)context;
682 JSContext* cx = client->GetScriptInterface().GetContext();
683 JSAutoRequest rq(cx);
684
685 client->m_Session->SetLongTimeout(true);
686
687 // Find the player assigned to our GUID
688 int player = -1;
689 if (client->m_PlayerAssignments.find(client->m_GUID) != client->m_PlayerAssignments.end())
690 player = client->m_PlayerAssignments[client->m_GUID].m_PlayerID;
691
692 client->m_ClientTurnManager = new CNetClientTurnManager(
693 *client->m_Game->GetSimulation2(), *client, client->m_HostID, client->m_Game->GetReplayLogger());
694
695 client->m_Game->SetPlayerID(player);
696 client->m_Game->StartGame(&client->m_GameAttributes, "");
697
698 JS::RootedValue msg(cx);
699 client->GetScriptInterface().Eval("({'type':'start'})", &msg);
700 client->PushGuiMessage(msg);
701
702 return true;
703 }
704
OnJoinSyncStart(void * context,CFsmEvent * event)705 bool CNetClient::OnJoinSyncStart(void* context, CFsmEvent* event)
706 {
707 ENSURE(event->GetType() == (uint)NMT_JOIN_SYNC_START);
708
709 CNetClient* client = (CNetClient*)context;
710
711 // The server wants us to start downloading the game state from it, so do so
712 client->m_Session->GetFileTransferer().StartTask(
713 shared_ptr<CNetFileReceiveTask>(new CNetFileReceiveTask_ClientRejoin(*client))
714 );
715
716 return true;
717 }
718
OnJoinSyncEndCommandBatch(void * context,CFsmEvent * event)719 bool CNetClient::OnJoinSyncEndCommandBatch(void* context, CFsmEvent* event)
720 {
721 ENSURE(event->GetType() == (uint)NMT_END_COMMAND_BATCH);
722
723 CNetClient* client = (CNetClient*)context;
724
725 CEndCommandBatchMessage* endMessage = (CEndCommandBatchMessage*)event->GetParamRef();
726
727 client->m_ClientTurnManager->FinishedAllCommands(endMessage->m_Turn, endMessage->m_TurnLength);
728
729 // Execute all the received commands for the latest turn
730 client->m_ClientTurnManager->UpdateFastForward();
731
732 return true;
733 }
734
OnRejoined(void * context,CFsmEvent * event)735 bool CNetClient::OnRejoined(void* context, CFsmEvent* event)
736 {
737 ENSURE(event->GetType() == (uint)NMT_REJOINED);
738
739 CNetClient* client = (CNetClient*)context;
740 JSContext* cx = client->GetScriptInterface().GetContext();
741 JSAutoRequest rq(cx);
742
743 CRejoinedMessage* message = (CRejoinedMessage*)event->GetParamRef();
744 JS::RootedValue msg(cx);
745 client->GetScriptInterface().Eval("({'type':'rejoined'})", &msg);
746 client->GetScriptInterface().SetProperty(msg, "guid", std::string(message->m_GUID), false);
747 client->PushGuiMessage(msg);
748
749 return true;
750 }
751
OnKicked(void * context,CFsmEvent * event)752 bool CNetClient::OnKicked(void *context, CFsmEvent* event)
753 {
754 ENSURE(event->GetType() == (uint)NMT_KICKED);
755
756 CNetClient* client = (CNetClient*)context;
757 JSContext* cx = client->GetScriptInterface().GetContext();
758 JSAutoRequest rq(cx);
759
760 CKickedMessage* message = (CKickedMessage*)event->GetParamRef();
761 JS::RootedValue msg(cx);
762
763 client->GetScriptInterface().Eval("({})", &msg);
764 client->GetScriptInterface().SetProperty(msg, "username", message->m_Name);
765 client->GetScriptInterface().SetProperty(msg, "type", CStr("kicked"));
766 client->GetScriptInterface().SetProperty(msg, "banned", message->m_Ban != 0);
767 client->PushGuiMessage(msg);
768
769 return true;
770 }
771
OnClientTimeout(void * context,CFsmEvent * event)772 bool CNetClient::OnClientTimeout(void *context, CFsmEvent* event)
773 {
774 // Report the timeout of some other client
775
776 ENSURE(event->GetType() == (uint)NMT_CLIENT_TIMEOUT);
777
778 CNetClient* client = (CNetClient*)context;
779 JSContext* cx = client->GetScriptInterface().GetContext();
780 JSAutoRequest rq(cx);
781
782 CClientTimeoutMessage* message = (CClientTimeoutMessage*)event->GetParamRef();
783 JS::RootedValue msg(cx);
784
785 client->GetScriptInterface().Eval("({ 'type':'netwarn', 'warntype': 'client-timeout' })", &msg);
786 client->GetScriptInterface().SetProperty(msg, "guid", std::string(message->m_GUID));
787 client->GetScriptInterface().SetProperty(msg, "lastReceivedTime", message->m_LastReceivedTime);
788 client->PushGuiMessage(msg);
789
790 return true;
791 }
792
OnClientPerformance(void * context,CFsmEvent * event)793 bool CNetClient::OnClientPerformance(void *context, CFsmEvent* event)
794 {
795 // Performance statistics for one or multiple clients
796
797 ENSURE(event->GetType() == (uint)NMT_CLIENT_PERFORMANCE);
798
799 CNetClient* client = (CNetClient*)context;
800 JSContext* cx = client->GetScriptInterface().GetContext();
801 JSAutoRequest rq(cx);
802
803 CClientPerformanceMessage* message = (CClientPerformanceMessage*)event->GetParamRef();
804
805 // Display warnings for other clients with bad ping
806 for (size_t i = 0; i < message->m_Clients.size(); ++i)
807 {
808 if (message->m_Clients[i].m_MeanRTT < DEFAULT_TURN_LENGTH_MP || message->m_Clients[i].m_GUID == client->m_GUID)
809 continue;
810
811 JS::RootedValue msg(cx);
812 client->GetScriptInterface().Eval("({ 'type':'netwarn', 'warntype': 'client-latency' })", &msg);
813 client->GetScriptInterface().SetProperty(msg, "guid", message->m_Clients[i].m_GUID);
814 client->GetScriptInterface().SetProperty(msg, "meanRTT", message->m_Clients[i].m_MeanRTT);
815 client->PushGuiMessage(msg);
816 }
817
818 return true;
819 }
820
OnClientsLoading(void * context,CFsmEvent * event)821 bool CNetClient::OnClientsLoading(void *context, CFsmEvent *event)
822 {
823 ENSURE(event->GetType() == (uint)NMT_CLIENTS_LOADING);
824
825 CClientsLoadingMessage* message = (CClientsLoadingMessage*)event->GetParamRef();
826
827 CNetClient* client = (CNetClient*)context;
828 JSContext* cx = client->GetScriptInterface().GetContext();
829 JSAutoRequest rq(cx);
830
831 bool finished = true;
832 std::vector<CStr> guids;
833 guids.reserve(message->m_Clients.size());
834 for (const CClientsLoadingMessage::S_m_Clients& mClient : message->m_Clients)
835 {
836 if (client->m_GUID == mClient.m_GUID)
837 finished = false;
838
839 guids.push_back(mClient.m_GUID);
840 }
841
842 // Disable the timeout here after processing the enet message, so as to ensure that the connection isn't currently
843 // timing out (as it is when just leaving the loading screen in LoadFinished).
844 if (finished)
845 client->m_Session->SetLongTimeout(false);
846
847 JS::RootedValue msg(cx);
848 client->GetScriptInterface().Eval("({ 'type':'clients-loading' })", &msg);
849 client->GetScriptInterface().SetProperty(msg, "guids", guids);
850 client->PushGuiMessage(msg);
851 return true;
852 }
853
OnClientPaused(void * context,CFsmEvent * event)854 bool CNetClient::OnClientPaused(void *context, CFsmEvent *event)
855 {
856 ENSURE(event->GetType() == (uint)NMT_CLIENT_PAUSED);
857
858 CNetClient* client = (CNetClient*)context;
859 JSContext* cx = client->GetScriptInterface().GetContext();
860 JSAutoRequest rq(cx);
861
862 CClientPausedMessage* message = (CClientPausedMessage*)event->GetParamRef();
863
864 JS::RootedValue msg(cx);
865 client->GetScriptInterface().Eval("({ 'type':'paused' })", &msg);
866 client->GetScriptInterface().SetProperty(msg, "pause", message->m_Pause != 0);
867 client->GetScriptInterface().SetProperty(msg, "guid", message->m_GUID);
868 client->PushGuiMessage(msg);
869
870 return true;
871 }
872
OnLoadedGame(void * context,CFsmEvent * event)873 bool CNetClient::OnLoadedGame(void* context, CFsmEvent* event)
874 {
875 ENSURE(event->GetType() == (uint)NMT_LOADED_GAME);
876
877 CNetClient* client = (CNetClient*)context;
878 JSContext* cx = client->GetScriptInterface().GetContext();
879 JSAutoRequest rq(cx);
880
881 // All players have loaded the game - start running the turn manager
882 // so that the game begins
883 client->m_Game->SetTurnManager(client->m_ClientTurnManager);
884
885 JS::RootedValue msg(cx);
886 client->GetScriptInterface().Eval("({'type':'netstatus','status':'active'})", &msg);
887 client->PushGuiMessage(msg);
888
889 // If we have rejoined an in progress game, send the rejoined message to the server.
890 if (client->m_Rejoin)
891 client->SendRejoinedMessage();
892
893 // The last client to leave the loading screen didn't receive the CClientsLoadingMessage, so disable here.
894 client->m_Session->SetLongTimeout(false);
895
896 return true;
897 }
898
OnInGame(void * context,CFsmEvent * event)899 bool CNetClient::OnInGame(void *context, CFsmEvent* event)
900 {
901 // TODO: should split each of these cases into a separate method
902
903 CNetClient* client = (CNetClient*)context;
904
905 CNetMessage* message = (CNetMessage*)event->GetParamRef();
906 if (message)
907 {
908 if (message->GetType() == NMT_SIMULATION_COMMAND)
909 {
910 CSimulationMessage* simMessage = static_cast<CSimulationMessage*> (message);
911 client->m_ClientTurnManager->OnSimulationMessage(simMessage);
912 }
913 else if (message->GetType() == NMT_SYNC_ERROR)
914 {
915 CSyncErrorMessage* syncMessage = static_cast<CSyncErrorMessage*> (message);
916 client->m_ClientTurnManager->OnSyncError(syncMessage->m_Turn, syncMessage->m_HashExpected, syncMessage->m_PlayerNames);
917 }
918 else if (message->GetType() == NMT_END_COMMAND_BATCH)
919 {
920 CEndCommandBatchMessage* endMessage = static_cast<CEndCommandBatchMessage*> (message);
921 client->m_ClientTurnManager->FinishedAllCommands(endMessage->m_Turn, endMessage->m_TurnLength);
922 }
923 }
924
925 return true;
926 }
927