LCOV - code coverage report
Current view: top level - source/network - NetClient.cpp (source / functions) Hit Total Coverage
Test: 0 A.D. test coverage report Lines: 0 493 0.0 %
Date: 2021-09-24 14:46:47 Functions: 0 58 0.0 %

          Line data    Source code
       1             : /* Copyright (C) 2021 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/external_libraries/libsdl.h"
      29             : #include "lib/sysdep/sysdep.h"
      30             : #include "lobby/IXmppClient.h"
      31             : #include "ps/CConsole.h"
      32             : #include "ps/CLogger.h"
      33             : #include "ps/Compress.h"
      34             : #include "ps/CStr.h"
      35             : #include "ps/Game.h"
      36             : #include "ps/Hashing.h"
      37             : #include "ps/Loader.h"
      38             : #include "ps/Profile.h"
      39             : #include "ps/Threading.h"
      40             : #include "scriptinterface/ScriptInterface.h"
      41             : #include "scriptinterface/JSON.h"
      42             : #include "simulation2/Simulation2.h"
      43             : #include "network/StunClient.h"
      44             : 
      45             : /**
      46             :  * Once ping goes above turn length * command delay,
      47             :  * the game will start 'freezing' for other clients while we catch up.
      48             :  * Since commands are sent client -> server -> client, divide by 2.
      49             :  * (duplicated in NetServer.cpp to avoid having to fetch the constants in a header file)
      50             :  */
      51             : constexpr u32 NETWORK_BAD_PING = DEFAULT_TURN_LENGTH * COMMAND_DELAY_MP / 2;
      52             : 
      53             : CNetClient *g_NetClient = NULL;
      54             : 
      55             : /**
      56             :  * Async task for receiving the initial game state when rejoining an
      57             :  * in-progress network game.
      58             :  */
      59             : class CNetFileReceiveTask_ClientRejoin : public CNetFileReceiveTask
      60             : {
      61             :     NONCOPYABLE(CNetFileReceiveTask_ClientRejoin);
      62             : public:
      63           0 :     CNetFileReceiveTask_ClientRejoin(CNetClient& client, const CStr& initAttribs)
      64           0 :         : m_Client(client), m_InitAttributes(initAttribs)
      65             :     {
      66           0 :     }
      67             : 
      68           0 :     virtual void OnComplete()
      69             :     {
      70             :         // We've received the game state from the server
      71             : 
      72             :         // Save it so we can use it after the map has finished loading
      73           0 :         m_Client.m_JoinSyncBuffer = m_Buffer;
      74             : 
      75             :         // Pretend the server told us to start the game
      76           0 :         CGameStartMessage start;
      77           0 :         start.m_InitAttributes = m_InitAttributes;
      78           0 :         m_Client.HandleMessage(&start);
      79           0 :     }
      80             : 
      81             : private:
      82             :     CNetClient& m_Client;
      83             :     CStr m_InitAttributes;
      84             : };
      85             : 
      86           0 : CNetClient::CNetClient(CGame* game) :
      87             :     m_Session(NULL),
      88             :     m_UserName(L"anonymous"),
      89             :     m_HostID((u32)-1), m_ClientTurnManager(NULL), m_Game(game),
      90             :     m_LastConnectionCheck(0),
      91             :     m_ServerAddress(),
      92             :     m_ServerPort(0),
      93           0 :     m_Rejoin(false)
      94             : {
      95           0 :     m_Game->SetTurnManager(NULL); // delete the old local turn manager so we don't accidentally use it
      96             : 
      97           0 :     void* context = this;
      98             : 
      99           0 :     JS_AddExtraGCRootsTracer(GetScriptInterface().GetGeneralJSContext(), CNetClient::Trace, this);
     100             : 
     101             :     // Set up transitions for session
     102           0 :     AddTransition(NCS_UNCONNECTED, (uint)NMT_CONNECT_COMPLETE, NCS_CONNECT, (void*)&OnConnect, context);
     103             : 
     104           0 :     AddTransition(NCS_CONNECT, (uint)NMT_SERVER_HANDSHAKE, NCS_HANDSHAKE, (void*)&OnHandshake, context);
     105             : 
     106           0 :     AddTransition(NCS_HANDSHAKE, (uint)NMT_SERVER_HANDSHAKE_RESPONSE, NCS_AUTHENTICATE, (void*)&OnHandshakeResponse, context);
     107             : 
     108           0 :     AddTransition(NCS_AUTHENTICATE, (uint)NMT_AUTHENTICATE, NCS_AUTHENTICATE, (void*)&OnAuthenticateRequest, context);
     109           0 :     AddTransition(NCS_AUTHENTICATE, (uint)NMT_AUTHENTICATE_RESULT, NCS_PREGAME, (void*)&OnAuthenticate, context);
     110             : 
     111           0 :     AddTransition(NCS_PREGAME, (uint)NMT_CHAT, NCS_PREGAME, (void*)&OnChat, context);
     112           0 :     AddTransition(NCS_PREGAME, (uint)NMT_READY, NCS_PREGAME, (void*)&OnReady, context);
     113           0 :     AddTransition(NCS_PREGAME, (uint)NMT_GAME_SETUP, NCS_PREGAME, (void*)&OnGameSetup, context);
     114           0 :     AddTransition(NCS_PREGAME, (uint)NMT_PLAYER_ASSIGNMENT, NCS_PREGAME, (void*)&OnPlayerAssignment, context);
     115           0 :     AddTransition(NCS_PREGAME, (uint)NMT_KICKED, NCS_PREGAME, (void*)&OnKicked, context);
     116           0 :     AddTransition(NCS_PREGAME, (uint)NMT_CLIENT_TIMEOUT, NCS_PREGAME, (void*)&OnClientTimeout, context);
     117           0 :     AddTransition(NCS_PREGAME, (uint)NMT_CLIENT_PERFORMANCE, NCS_PREGAME, (void*)&OnClientPerformance, context);
     118           0 :     AddTransition(NCS_PREGAME, (uint)NMT_GAME_START, NCS_LOADING, (void*)&OnGameStart, context);
     119           0 :     AddTransition(NCS_PREGAME, (uint)NMT_JOIN_SYNC_START, NCS_JOIN_SYNCING, (void*)&OnJoinSyncStart, context);
     120             : 
     121           0 :     AddTransition(NCS_JOIN_SYNCING, (uint)NMT_CHAT, NCS_JOIN_SYNCING, (void*)&OnChat, context);
     122           0 :     AddTransition(NCS_JOIN_SYNCING, (uint)NMT_GAME_SETUP, NCS_JOIN_SYNCING, (void*)&OnGameSetup, context);
     123           0 :     AddTransition(NCS_JOIN_SYNCING, (uint)NMT_PLAYER_ASSIGNMENT, NCS_JOIN_SYNCING, (void*)&OnPlayerAssignment, context);
     124           0 :     AddTransition(NCS_JOIN_SYNCING, (uint)NMT_KICKED, NCS_JOIN_SYNCING, (void*)&OnKicked, context);
     125           0 :     AddTransition(NCS_JOIN_SYNCING, (uint)NMT_CLIENT_TIMEOUT, NCS_JOIN_SYNCING, (void*)&OnClientTimeout, context);
     126           0 :     AddTransition(NCS_JOIN_SYNCING, (uint)NMT_CLIENT_PERFORMANCE, NCS_JOIN_SYNCING, (void*)&OnClientPerformance, context);
     127           0 :     AddTransition(NCS_JOIN_SYNCING, (uint)NMT_GAME_START, NCS_JOIN_SYNCING, (void*)&OnGameStart, context);
     128           0 :     AddTransition(NCS_JOIN_SYNCING, (uint)NMT_SIMULATION_COMMAND, NCS_JOIN_SYNCING, (void*)&OnInGame, context);
     129           0 :     AddTransition(NCS_JOIN_SYNCING, (uint)NMT_END_COMMAND_BATCH, NCS_JOIN_SYNCING, (void*)&OnJoinSyncEndCommandBatch, context);
     130           0 :     AddTransition(NCS_JOIN_SYNCING, (uint)NMT_LOADED_GAME, NCS_INGAME, (void*)&OnLoadedGame, context);
     131             : 
     132           0 :     AddTransition(NCS_LOADING, (uint)NMT_CHAT, NCS_LOADING, (void*)&OnChat, context);
     133           0 :     AddTransition(NCS_LOADING, (uint)NMT_GAME_SETUP, NCS_LOADING, (void*)&OnGameSetup, context);
     134           0 :     AddTransition(NCS_LOADING, (uint)NMT_PLAYER_ASSIGNMENT, NCS_LOADING, (void*)&OnPlayerAssignment, context);
     135           0 :     AddTransition(NCS_LOADING, (uint)NMT_KICKED, NCS_LOADING, (void*)&OnKicked, context);
     136           0 :     AddTransition(NCS_LOADING, (uint)NMT_CLIENT_TIMEOUT, NCS_LOADING, (void*)&OnClientTimeout, context);
     137           0 :     AddTransition(NCS_LOADING, (uint)NMT_CLIENT_PERFORMANCE, NCS_LOADING, (void*)&OnClientPerformance, context);
     138           0 :     AddTransition(NCS_LOADING, (uint)NMT_CLIENTS_LOADING, NCS_LOADING, (void*)&OnClientsLoading, context);
     139           0 :     AddTransition(NCS_LOADING, (uint)NMT_LOADED_GAME, NCS_INGAME, (void*)&OnLoadedGame, context);
     140             : 
     141           0 :     AddTransition(NCS_INGAME, (uint)NMT_REJOINED, NCS_INGAME, (void*)&OnRejoined, context);
     142           0 :     AddTransition(NCS_INGAME, (uint)NMT_KICKED, NCS_INGAME, (void*)&OnKicked, context);
     143           0 :     AddTransition(NCS_INGAME, (uint)NMT_CLIENT_TIMEOUT, NCS_INGAME, (void*)&OnClientTimeout, context);
     144           0 :     AddTransition(NCS_INGAME, (uint)NMT_CLIENT_PERFORMANCE, NCS_INGAME, (void*)&OnClientPerformance, context);
     145           0 :     AddTransition(NCS_INGAME, (uint)NMT_CLIENTS_LOADING, NCS_INGAME, (void*)&OnClientsLoading, context);
     146           0 :     AddTransition(NCS_INGAME, (uint)NMT_CLIENT_PAUSED, NCS_INGAME, (void*)&OnClientPaused, context);
     147           0 :     AddTransition(NCS_INGAME, (uint)NMT_CHAT, NCS_INGAME, (void*)&OnChat, context);
     148           0 :     AddTransition(NCS_INGAME, (uint)NMT_GAME_SETUP, NCS_INGAME, (void*)&OnGameSetup, context);
     149           0 :     AddTransition(NCS_INGAME, (uint)NMT_PLAYER_ASSIGNMENT, NCS_INGAME, (void*)&OnPlayerAssignment, context);
     150           0 :     AddTransition(NCS_INGAME, (uint)NMT_SIMULATION_COMMAND, NCS_INGAME, (void*)&OnInGame, context);
     151           0 :     AddTransition(NCS_INGAME, (uint)NMT_SYNC_ERROR, NCS_INGAME, (void*)&OnInGame, context);
     152           0 :     AddTransition(NCS_INGAME, (uint)NMT_END_COMMAND_BATCH, NCS_INGAME, (void*)&OnInGame, context);
     153             : 
     154             :     // Set first state
     155           0 :     SetFirstState(NCS_UNCONNECTED);
     156           0 : }
     157             : 
     158           0 : CNetClient::~CNetClient()
     159             : {
     160             :     // Try to flush messages before dying (probably fails).
     161           0 :     if (m_ClientTurnManager)
     162           0 :         m_ClientTurnManager->OnDestroyConnection();
     163             : 
     164           0 :     DestroyConnection();
     165           0 :     JS_RemoveExtraGCRootsTracer(GetScriptInterface().GetGeneralJSContext(), CNetClient::Trace, this);
     166           0 : }
     167           0 : 
     168             : void CNetClient::TraceMember(JSTracer *trc)
     169             : {
     170             :     for (JS::Heap<JS::Value>& guiMessage : m_GuiMessageQueue)
     171             :         JS::TraceEdge(trc, &guiMessage, "m_GuiMessageQueue");
     172             : }
     173             : 
     174             : void CNetClient::SetUserName(const CStrW& username)
     175           0 : {
     176           0 :     ENSURE(!m_Session); // must be called before we start the connection
     177             : 
     178             :     m_UserName = username;
     179           0 : }
     180           0 : 
     181             : void CNetClient::SetHostJID(const CStr& jid)
     182           0 : {
     183           0 :     m_HostJID = jid;
     184           0 : }
     185             : 
     186           0 : void CNetClient::SetGamePassword(const CStr& hashedPassword)
     187             : {
     188           0 :     // Hash on top with the user's name, to make sure not all
     189           0 :     // hashing data is in control of the host.
     190           0 :     m_Password = HashCryptographically(hashedPassword, m_UserName.ToUTF8());
     191             : }
     192           0 : 
     193             : void CNetClient::SetControllerSecret(const std::string& secret)
     194           0 : {
     195             :     m_ControllerSecret = secret;
     196           0 : }
     197           0 : 
     198             : 
     199           0 : bool CNetClient::SetupConnection(ENetHost* enetClient)
     200             : {
     201           0 :     CNetClientSession* session = new CNetClientSession(*this);
     202           0 :     bool ok = session->Connect(m_ServerAddress, m_ServerPort, enetClient);
     203             :     SetAndOwnSession(session);
     204           0 :     m_PollingThread = std::thread(Threading::HandleExceptions<CNetClientSession::RunNetLoop>::Wrapper, m_Session);
     205             :     return ok;
     206             : }
     207             : 
     208           0 : void CNetClient::SetupConnectionViaLobby()
     209           0 : {
     210             :     g_XmppClient->SendIqGetConnectionData(m_HostJID, m_Password, m_UserName.ToUTF8(), false);
     211           0 : }
     212             : 
     213           0 : void CNetClient::SetupServerData(CStr address, u16 port, bool stun)
     214           0 : {
     215             :     ENSURE(!m_Session);
     216             : 
     217           0 :     m_ServerAddress = address;
     218             :     m_ServerPort = port;
     219           0 :     m_UseSTUN = stun;
     220           0 : }
     221           0 : 
     222           0 : void CNetClient::HandleGetServerDataFailed(const CStr& error)
     223           0 : {
     224             :     if (m_Session)
     225             :         return;
     226           0 : 
     227             :     PushGuiMessage(
     228           0 :         "type", "serverdata",
     229           0 :         "status", "failed",
     230             :         "reason", error
     231           0 :     );
     232             : }
     233           0 : 
     234             : bool CNetClient::TryToConnect(const CStr& hostJID, bool localNetwork)
     235           0 : {
     236           0 :     if (m_Session)
     237           0 :         return false;
     238           0 : 
     239             :     if (m_ServerAddress.empty())
     240           0 :     {
     241             :         PushGuiMessage(
     242           0 :             "type", "netstatus",
     243             :             "status", "disconnected",
     244             :             "reason", static_cast<i32>(NDR_SERVER_REFUSED));
     245           0 :         return false;
     246             :     }
     247             : 
     248             :     ENetAddress hostAddr{ ENET_HOST_ANY, ENET_PORT_ANY };
     249             :     ENetHost* enetClient = enet_host_create(&hostAddr, 1, 1, 0, 0);
     250             : 
     251             :     if (!enetClient)
     252           0 :     {
     253             :         PushGuiMessage(
     254           0 :             "type", "netstatus",
     255             :             "status", "disconnected",
     256             :             "reason", static_cast<i32>(NDR_STUN_PORT_FAILED));
     257           0 :         return false;
     258             :     }
     259           0 : 
     260             :     CStr ip;
     261             :     u16 port = 0;
     262           0 :     if (g_XmppClient && m_UseSTUN)
     263           0 :     {
     264             :         if (!StunClient::FindPublicIP(*enetClient, ip, port))
     265             :         {
     266           0 :             PushGuiMessage(
     267           0 :                 "type", "netstatus",
     268             :                 "status", "disconnected",
     269           0 :                 "reason", static_cast<i32>(NDR_STUN_ENDPOINT_FAILED));
     270             :             return false;
     271           0 :         }
     272             : 
     273             :         // If the host is on the same network, we risk failing to connect
     274           0 :         // on routers that don't support NAT hairpinning/NAT loopback.
     275           0 :         // To work around that, send again a connection data request, but for internal IP this time.
     276             :         if (ip == m_ServerAddress)
     277             :         {
     278           0 :             g_XmppClient->SendIqGetConnectionData(m_HostJID, m_Password, m_UserName.ToUTF8(), true);
     279           0 :             // Return true anyways - we're on a success path here.
     280           0 :             return true;
     281             :         }
     282           0 :     }
     283             :     else if (g_XmppClient && localNetwork)
     284           0 :     {
     285             :         // We may need to punch a hole through the local firewall, so fetch our local IP.
     286             :         // NB: we'll ignore failures here, and hope that the firewall will be open to connection
     287           0 :         // if we fail to fetch the local IP (which is unlikely anyways).
     288           0 :         if (!StunClient::FindLocalIP(ip))
     289             :             ip = "";
     290             :         // Check if we're hosting on localhost, and if so, explicitly use that
     291             :         // (this circumvents, at least, the 'block all incoming connections' setting
     292             :         // on the MacOS firewall).
     293             :         if (ip == m_ServerAddress)
     294           0 :         {
     295             :             m_ServerAddress = "127.0.0.1";
     296           0 :             ip = "";
     297             :         }
     298           0 :         port = enetClient->address.port;
     299             :     }
     300             : 
     301           0 :     LOGMESSAGE("NetClient: connecting to server at %s:%i", m_ServerAddress, m_ServerPort);
     302             : 
     303             :     if (!ip.empty())
     304             :     {
     305             :         // UDP hole-punching
     306           0 :         // Step 0: send a message, via XMPP, to the server with our external IP & port.
     307           0 :         g_XmppClient->SendStunEndpointToHost(ip, port, hostJID);
     308             : 
     309             :         // Step 1b: Wait some time - we need the host to receive the stun endpoint and start punching a hole themselves before
     310             :         // we try to establish the connection below.
     311           0 :         SDL_Delay(1000);
     312             : 
     313           0 :         // Step 2: Send a message ourselves to the server so that the NAT, if any, routes incoming trafic correctly.
     314           0 :         // TODO: verify if this step is necessary, since we'll try and connect anyways below.
     315             :         StunClient::SendHolePunchingMessages(*enetClient, m_ServerAddress, m_ServerPort);
     316           0 :     }
     317             : 
     318             :     if (!g_NetClient->SetupConnection(enetClient))
     319           0 :     {
     320             :         PushGuiMessage(
     321           0 :             "type", "netstatus",
     322             :             "status", "disconnected",
     323             :             "reason", static_cast<i32>(NDR_UNKNOWN));
     324             :         return false;
     325           0 :     }
     326             : 
     327             :     return true;
     328             : }
     329           0 : 
     330             : 
     331             : void CNetClient::SetAndOwnSession(CNetClientSession* session)
     332             : {
     333           0 :     delete m_Session;
     334             :     m_Session = session;
     335             : }
     336           0 : 
     337             : void CNetClient::DestroyConnection()
     338           0 : {
     339             :     if (m_Session)
     340             :         m_Session->Shutdown();
     341           0 : 
     342           0 :     if (m_PollingThread.joinable())
     343             :         // Use detach() over join() because we don't want to wait for the session
     344             :         // (which may be polling or trying to send messages).
     345             :         m_PollingThread.detach();
     346             : 
     347             :     // The polling thread will cleanup the session on its own,
     348             :     // mark it as nullptr here so we know we're done using it.
     349           0 :     m_Session = nullptr;
     350             : }
     351           0 : 
     352           0 : void CNetClient::Poll()
     353           0 : {
     354             :     if (!m_Session)
     355           0 :         return;
     356             : 
     357           0 :     PROFILE3("NetClient::poll");
     358           0 : 
     359             :     CheckServerConnection();
     360           0 :     m_Session->ProcessPolledMessages();
     361             : }
     362             : 
     363           0 : void CNetClient::CheckServerConnection()
     364             : {
     365             :     // Trigger local warnings if the connection to the server is bad.
     366             :     // At most once per second.
     367           0 :     std::time_t now = std::time(nullptr);
     368           0 :     if (now <= m_LastConnectionCheck)
     369             :         return;
     370           0 : 
     371             :     m_LastConnectionCheck = now;
     372           0 : 
     373           0 :     // Report if we are losing the connection to the server
     374             :     u32 lastReceived = m_Session->GetLastReceivedTime();
     375           0 :     if (lastReceived > NETWORK_WARNING_TIMEOUT)
     376             :     {
     377           0 :         PushGuiMessage(
     378           0 :             "type", "netwarn",
     379             :             "warntype", "server-timeout",
     380             :             "lastReceivedTime", lastReceived);
     381           0 :         return;
     382             :     }
     383             : 
     384             :     // Report if we have a bad ping to the server.
     385           0 :     u32 meanRTT = m_Session->GetMeanRTT();
     386           0 :     if (meanRTT > NETWORK_BAD_PING)
     387           0 :     {
     388             :         PushGuiMessage(
     389           0 :             "type", "netwarn",
     390             :             "warntype", "server-latency",
     391             :             "meanRTT", meanRTT);
     392           0 :     }
     393           0 : }
     394             : 
     395           0 : void CNetClient::GuiPoll(JS::MutableHandleValue ret)
     396             : {
     397             :     if (m_GuiMessageQueue.empty())
     398             :     {
     399           0 :         ret.setUndefined();
     400             :         return;
     401             :     }
     402             : 
     403           0 :     ret.set(m_GuiMessageQueue.front());
     404           0 :     m_GuiMessageQueue.pop_front();
     405             : }
     406           0 : 
     407             : std::string CNetClient::TestReadGuiMessages()
     408             : {
     409             :     ScriptRequest rq(GetScriptInterface());
     410             : 
     411             :     std::string r;
     412             :     JS::RootedValue msg(rq.cx);
     413           0 :     while (true)
     414             :     {
     415           0 :         GuiPoll(&msg);
     416             :         if (msg.isUndefined())
     417           0 :             break;
     418           0 :         r += Script::ToString(rq, &msg) + "\n";
     419             :     }
     420             :     return r;
     421           0 : }
     422           0 : 
     423             : const ScriptInterface& CNetClient::GetScriptInterface()
     424             : {
     425           0 :     return m_Game->GetSimulation2()->GetScriptInterface();
     426             : }
     427           0 : 
     428             : void CNetClient::PostPlayerAssignmentsToScript()
     429           0 : {
     430           0 :     ScriptRequest rq(GetScriptInterface());
     431           0 : 
     432             :     JS::RootedValue newAssignments(rq.cx);
     433           0 :     Script::CreateObject(rq, &newAssignments);
     434           0 : 
     435             :     for (const std::pair<const CStr, PlayerAssignment>& p : m_PlayerAssignments)
     436           0 :     {
     437             :         JS::RootedValue assignment(rq.cx);
     438           0 : 
     439             :         Script::CreateObject(
     440             :             rq,
     441           0 :             &assignment,
     442             :             "name", p.second.m_Name,
     443           0 :             "player", p.second.m_PlayerID,
     444             :             "status", p.second.m_Status);
     445             : 
     446           0 :         Script::SetProperty(rq, newAssignments, p.first.c_str(), assignment);
     447             :     }
     448           0 : 
     449             :     PushGuiMessage(
     450           0 :         "type", "players",
     451           0 :         "newAssignments", newAssignments);
     452             : }
     453           0 : 
     454             : bool CNetClient::SendMessage(const CNetMessage* message)
     455           0 : {
     456             :     if (!m_Session)
     457           0 :         return false;
     458             : 
     459             :     return m_Session->SendMessage(message);
     460           0 : }
     461           0 : 
     462           0 : void CNetClient::HandleConnect()
     463             : {
     464           0 :     Update((uint)NMT_CONNECT_COMPLETE, NULL);
     465             : }
     466             : 
     467           0 : void CNetClient::HandleDisconnect(u32 reason)
     468             : {
     469             :     PushGuiMessage(
     470           0 :         "type", "netstatus",
     471             :         "status", "disconnected",
     472           0 :         "reason", reason);
     473             : 
     474           0 :     DestroyConnection();
     475             : 
     476             :     // Update the state immediately to UNCONNECTED (don't bother with FSM transitions since
     477           0 :     // we'd need one for every single state, and we don't need to use per-state actions)
     478             :     SetCurrState(NCS_UNCONNECTED);
     479             : }
     480           0 : 
     481             : void CNetClient::SendGameSetupMessage(JS::MutableHandleValue attrs, const ScriptInterface& scriptInterface)
     482           0 : {
     483           0 :     CGameSetupMessage gameSetup(scriptInterface);
     484             :     gameSetup.m_Data = attrs;
     485           0 :     SendMessage(&gameSetup);
     486             : }
     487           0 : 
     488             : void CNetClient::SendAssignPlayerMessage(const int playerID, const CStr& guid)
     489             : {
     490             :     CAssignPlayerMessage assignPlayer;
     491             :     assignPlayer.m_PlayerID = playerID;
     492           0 :     assignPlayer.m_GUID = guid;
     493             :     SendMessage(&assignPlayer);
     494             : }
     495             : 
     496           0 : void CNetClient::SendChatMessage(const std::wstring& text)
     497           0 : {
     498             :     CChatMessage chat;
     499           0 :     chat.m_Message = text;
     500             :     SendMessage(&chat);
     501           0 : }
     502           0 : 
     503           0 : void CNetClient::SendReadyMessage(const int status)
     504           0 : {
     505             :     CReadyMessage readyStatus;
     506           0 :     readyStatus.m_Status = status;
     507             :     SendMessage(&readyStatus);
     508           0 : }
     509           0 : 
     510           0 : void CNetClient::SendClearAllReadyMessage()
     511           0 : {
     512           0 :     CClearAllReadyMessage clearAllReady;
     513             :     SendMessage(&clearAllReady);
     514           0 : }
     515             : 
     516           0 : void CNetClient::SendStartGameMessage(const CStr& initAttribs)
     517           0 : {
     518           0 :     CGameStartMessage gameStart;
     519           0 :     gameStart.m_InitAttributes = initAttribs;
     520             :     SendMessage(&gameStart);
     521           0 : }
     522             : 
     523           0 : void CNetClient::SendRejoinedMessage()
     524           0 : {
     525           0 :     CRejoinedMessage rejoinedMessage;
     526           0 :     SendMessage(&rejoinedMessage);
     527             : }
     528           0 : 
     529             : void CNetClient::SendKickPlayerMessage(const CStrW& playerName, bool ban)
     530           0 : {
     531           0 :     CKickedMessage kickPlayer;
     532           0 :     kickPlayer.m_Name = playerName;
     533             :     kickPlayer.m_Ban = ban;
     534           0 :     SendMessage(&kickPlayer);
     535             : }
     536           0 : 
     537           0 : void CNetClient::SendPausedMessage(bool pause)
     538           0 : {
     539           0 :     CClientPausedMessage pausedMessage;
     540             :     pausedMessage.m_Pause = pause;
     541           0 :     SendMessage(&pausedMessage);
     542             : }
     543           0 : 
     544           0 : bool CNetClient::HandleMessage(CNetMessage* message)
     545           0 : {
     546             :     // Handle non-FSM messages first
     547           0 : 
     548             :     Status status = m_Session->GetFileTransferer().HandleMessageReceive(*message);
     549           0 :     if (status == INFO::OK)
     550           0 :         return true;
     551           0 :     if (status != INFO::SKIPPED)
     552           0 :         return false;
     553           0 : 
     554             :     if (message->GetType() == NMT_FILE_TRANSFER_REQUEST)
     555           0 :     {
     556             :         CFileTransferRequestMessage* reqMessage = static_cast<CFileTransferRequestMessage*>(message);
     557           0 : 
     558           0 :         // TODO: we should support different transfer request types, instead of assuming
     559           0 :         // it's always requesting the simulation state
     560           0 : 
     561             :         std::stringstream stream;
     562           0 : 
     563             :         LOGMESSAGERENDER("Serializing game at turn %u for rejoining player", m_ClientTurnManager->GetCurrentTurn());
     564             :         u32 turn = to_le32(m_ClientTurnManager->GetCurrentTurn());
     565             :         stream.write((char*)&turn, sizeof(turn));
     566           0 : 
     567           0 :         bool ok = m_Game->GetSimulation2()->SerializeState(stream);
     568             :         ENSURE(ok);
     569           0 : 
     570             :         // Compress the content with zlib to save bandwidth
     571             :         // (TODO: if this is still too large, compressing with e.g. LZMA works much better)
     572           0 :         std::string compressed;
     573             :         CompressZLib(stream.str(), compressed, true);
     574           0 : 
     575             :         m_Session->GetFileTransferer().StartResponse(reqMessage->m_RequestID, compressed);
     576             : 
     577             :         return true;
     578             :     }
     579           0 : 
     580             :     // Update FSM
     581           0 :     bool ok = Update(message->GetType(), message);
     582           0 :     if (!ok)
     583           0 :         LOGERROR("Net client: Error running FSM update (type=%d state=%d)", (int)message->GetType(), (int)GetCurrState());
     584             :     return ok;
     585           0 : }
     586           0 : 
     587             : void CNetClient::LoadFinished()
     588             : {
     589             :     if (!m_JoinSyncBuffer.empty())
     590           0 :     {
     591           0 :         // We're rejoining a game, and just finished loading the initial map,
     592             :         // so deserialize the saved game state now
     593           0 : 
     594             :         std::string state;
     595           0 :         DecompressZLib(m_JoinSyncBuffer, state, true);
     596             : 
     597             :         std::stringstream stream(state);
     598             : 
     599           0 :         u32 turn;
     600           0 :         stream.read((char*)&turn, sizeof(turn));
     601           0 :         turn = to_le32(turn);
     602             : 
     603             :         LOGMESSAGE("Rejoining client deserializing state at turn %u\n", turn);
     604             : 
     605           0 :         bool ok = m_Game->GetSimulation2()->DeserializeState(stream);
     606             :         ENSURE(ok);
     607           0 : 
     608             :         m_ClientTurnManager->ResetState(turn, turn);
     609             : 
     610             :         PushGuiMessage(
     611             :             "type", "netstatus",
     612           0 :             "status", "join_syncing");
     613           0 :     }
     614             :     else
     615           0 :     {
     616             :         // Connecting at the start of a game, so we'll wait for other players to finish loading
     617           0 :         PushGuiMessage(
     618           0 :             "type", "netstatus",
     619           0 :             "status", "waiting_for_players");
     620             :     }
     621           0 : 
     622             :     CLoadedGameMessage loaded;
     623           0 :     loaded.m_CurrentTurn = m_ClientTurnManager->GetCurrentTurn();
     624           0 :     SendMessage(&loaded);
     625             : }
     626           0 : 
     627             : void CNetClient::SendAuthenticateMessage()
     628           0 : {
     629             :     CAuthenticateMessage authenticate;
     630             :     authenticate.m_Name = m_UserName;
     631             :     authenticate.m_Password = m_Password;
     632             :     authenticate.m_ControllerSecret = m_ControllerSecret;
     633             :     SendMessage(&authenticate);
     634             : }
     635           0 : 
     636             : bool CNetClient::OnConnect(void* context, CFsmEvent* event)
     637             : {
     638             :     ENSURE(event->GetType() == (uint)NMT_CONNECT_COMPLETE);
     639             : 
     640           0 :     CNetClient* client = static_cast<CNetClient*>(context);
     641           0 : 
     642           0 :     client->PushGuiMessage(
     643           0 :         "type", "netstatus",
     644             :         "status", "connected");
     645           0 : 
     646             :     return true;
     647           0 : }
     648           0 : 
     649           0 : bool CNetClient::OnHandshake(void* context, CFsmEvent* event)
     650           0 : {
     651           0 :     ENSURE(event->GetType() == (uint)NMT_SERVER_HANDSHAKE);
     652           0 : 
     653             :     CNetClient* client = static_cast<CNetClient*>(context);
     654           0 : 
     655             :     CCliHandshakeMessage handshake;
     656           0 :     handshake.m_MagicResponse = PS_PROTOCOL_MAGIC_RESPONSE;
     657             :     handshake.m_ProtocolVersion = PS_PROTOCOL_VERSION;
     658           0 :     handshake.m_SoftwareVersion = PS_PROTOCOL_VERSION;
     659             :     client->SendMessage(&handshake);
     660           0 : 
     661             :     return true;
     662             : }
     663             : 
     664           0 : bool CNetClient::OnHandshakeResponse(void* context, CFsmEvent* event)
     665             : {
     666             :     ENSURE(event->GetType() == (uint)NMT_SERVER_HANDSHAKE_RESPONSE);
     667           0 : 
     668             :     CNetClient* client = static_cast<CNetClient*>(context);
     669           0 :     CSrvHandshakeResponseMessage* message = static_cast<CSrvHandshakeResponseMessage*>(event->GetParamRef());
     670             : 
     671           0 :     client->m_GUID = message->m_GUID;
     672             : 
     673           0 :     if (message->m_Flags & PS_NETWORK_FLAG_REQUIRE_LOBBYAUTH)
     674           0 :     {
     675           0 :         if (g_XmppClient && !client->m_HostJID.empty())
     676           0 :             g_XmppClient->SendIqLobbyAuth(client->m_HostJID, client->m_GUID);
     677           0 :         else
     678             :         {
     679           0 :             client->PushGuiMessage(
     680             :                 "type", "netstatus",
     681             :                 "status", "disconnected",
     682           0 :                 "reason", static_cast<i32>(NDR_LOBBY_AUTH_FAILED));
     683             : 
     684           0 :             LOGMESSAGE("Net client: Couldn't send lobby auth xmpp message");
     685             :         }
     686           0 :         return true;
     687           0 :     }
     688             : 
     689           0 :     client->SendAuthenticateMessage();
     690             :     return true;
     691           0 : }
     692             : 
     693           0 : bool CNetClient::OnAuthenticateRequest(void* context, CFsmEvent* event)
     694           0 : {
     695             :     ENSURE(event->GetType() == (uint)NMT_AUTHENTICATE);
     696             : 
     697           0 :     CNetClient* client = static_cast<CNetClient*>(context);
     698             :     client->SendAuthenticateMessage();
     699             :     return true;
     700           0 : }
     701             : 
     702           0 : bool CNetClient::OnAuthenticate(void* context, CFsmEvent* event)
     703             : {
     704           0 :     ENSURE(event->GetType() == (uint)NMT_AUTHENTICATE_RESULT);
     705             : 
     706             :     CNetClient* client = static_cast<CNetClient*>(context);
     707           0 :     CAuthenticateResultMessage* message = static_cast<CAuthenticateResultMessage*>(event->GetParamRef());
     708           0 : 
     709             :     LOGMESSAGE("Net: Authentication result: host=%u, %s", message->m_HostID, utf8_from_wstring(message->m_Message));
     710             : 
     711           0 :     client->m_HostID = message->m_HostID;
     712             :     client->m_Rejoin = message->m_Code == ARC_OK_REJOINING;
     713           0 :     client->m_IsController = message->m_IsController;
     714             : 
     715           0 :     client->PushGuiMessage(
     716           0 :         "type", "netstatus",
     717           0 :         "status", "authenticated",
     718             :         "rejoining", client->m_Rejoin);
     719             : 
     720           0 :     return true;
     721             : }
     722           0 : 
     723             : bool CNetClient::OnChat(void* context, CFsmEvent* event)
     724           0 : {
     725           0 :     ENSURE(event->GetType() == (uint)NMT_CHAT);
     726             : 
     727           0 :     CNetClient* client = static_cast<CNetClient*>(context);
     728             :     CChatMessage* message = static_cast<CChatMessage*>(event->GetParamRef());
     729           0 : 
     730           0 :     client->PushGuiMessage(
     731           0 :         "type", "chat",
     732             :         "guid", message->m_GUID,
     733           0 :         "text", message->m_Message);
     734             : 
     735             :     return true;
     736           0 : }
     737             : 
     738           0 : bool CNetClient::OnReady(void* context, CFsmEvent* event)
     739             : {
     740             :     ENSURE(event->GetType() == (uint)NMT_READY);
     741           0 : 
     742             :     CNetClient* client = static_cast<CNetClient*>(context);
     743           0 :     CReadyMessage* message = static_cast<CReadyMessage*>(event->GetParamRef());
     744             : 
     745           0 :     client->PushGuiMessage(
     746           0 :         "type", "ready",
     747             :         "guid", message->m_GUID,
     748           0 :         "status", message->m_Status);
     749             : 
     750           0 :     return true;
     751           0 : }
     752             : 
     753           0 : bool CNetClient::OnGameSetup(void* context, CFsmEvent* event)
     754             : {
     755             :     ENSURE(event->GetType() == (uint)NMT_GAME_SETUP);
     756           0 : 
     757             :     CNetClient* client = static_cast<CNetClient*>(context);
     758           0 :     CGameSetupMessage* message = static_cast<CGameSetupMessage*>(event->GetParamRef());
     759             : 
     760           0 :     client->PushGuiMessage(
     761           0 :         "type", "gamesetup",
     762             :         "data", message->m_Data);
     763           0 : 
     764             :     return true;
     765           0 : }
     766           0 : 
     767             : bool CNetClient::OnPlayerAssignment(void* context, CFsmEvent* event)
     768           0 : {
     769             :     ENSURE(event->GetType() == (uint)NMT_PLAYER_ASSIGNMENT);
     770             : 
     771           0 :     CNetClient* client = static_cast<CNetClient*>(context);
     772             :     CPlayerAssignmentMessage* message = static_cast<CPlayerAssignmentMessage*>(event->GetParamRef());
     773           0 : 
     774             :     // Unpack the message
     775           0 :     PlayerAssignmentMap newPlayerAssignments;
     776           0 :     for (size_t i = 0; i < message->m_Hosts.size(); ++i)
     777             :     {
     778           0 :         PlayerAssignment assignment;
     779             :         assignment.m_Enabled = true;
     780           0 :         assignment.m_Name = message->m_Hosts[i].m_Name;
     781             :         assignment.m_PlayerID = message->m_Hosts[i].m_PlayerID;
     782           0 :         assignment.m_Status = message->m_Hosts[i].m_Status;
     783             :         newPlayerAssignments[message->m_Hosts[i].m_GUID] = assignment;
     784             :     }
     785           0 : 
     786             :     client->m_PlayerAssignments.swap(newPlayerAssignments);
     787           0 : 
     788             :     client->PostPlayerAssignmentsToScript();
     789           0 : 
     790           0 :     return true;
     791             : }
     792             : 
     793           0 : // This is called either when the host clicks the StartGame button or
     794           0 : // if this client rejoins and finishes the download of the simstate.
     795             : bool CNetClient::OnGameStart(void* context, CFsmEvent* event)
     796           0 : {
     797           0 :     ENSURE(event->GetType() == (uint)NMT_GAME_START);
     798           0 : 
     799           0 :     CNetClient* client = static_cast<CNetClient*>(context);
     800           0 :     CGameStartMessage* message = static_cast<CGameStartMessage*>(event->GetParamRef());
     801           0 : 
     802             :     // Find the player assigned to our GUID
     803             :     int player = -1;
     804           0 :     if (client->m_PlayerAssignments.find(client->m_GUID) != client->m_PlayerAssignments.end())
     805             :         player = client->m_PlayerAssignments[client->m_GUID].m_PlayerID;
     806           0 : 
     807             :     client->m_ClientTurnManager = new CNetClientTurnManager(
     808           0 :             *client->m_Game->GetSimulation2(), *client, client->m_HostID, client->m_Game->GetReplayLogger());
     809             : 
     810             :     // Parse init attributes.
     811             :     const ScriptInterface& scriptInterface = client->m_Game->GetSimulation2()->GetScriptInterface();
     812             :     ScriptRequest rq(scriptInterface);
     813           0 :     JS::RootedValue initAttribs(rq.cx);
     814             :     Script::ParseJSON(rq, message->m_InitAttributes, &initAttribs);
     815           0 : 
     816             :     client->m_Game->SetPlayerID(player);
     817           0 :     client->m_Game->StartGame(&initAttribs, "");
     818           0 : 
     819             :     client->PushGuiMessage("type", "start",
     820             :                            "initAttributes", initAttribs);
     821           0 : 
     822           0 :     return true;
     823           0 : }
     824             : 
     825           0 : bool CNetClient::OnJoinSyncStart(void* context, CFsmEvent* event)
     826           0 : {
     827             :     ENSURE(event->GetType() == (uint)NMT_JOIN_SYNC_START);
     828             : 
     829           0 :     CNetClient* client = static_cast<CNetClient*>(context);
     830           0 : 
     831           0 :     CJoinSyncStartMessage* joinSyncStartMessage = (CJoinSyncStartMessage*)event->GetParamRef();
     832           0 : 
     833             :     // The server wants us to start downloading the game state from it, so do so
     834           0 :     client->m_Session->GetFileTransferer().StartTask(
     835           0 :         std::shared_ptr<CNetFileReceiveTask>(new CNetFileReceiveTask_ClientRejoin(*client, joinSyncStartMessage->m_InitAttributes))
     836             :     );
     837           0 : 
     838             :     return true;
     839             : }
     840           0 : 
     841             : bool CNetClient::OnJoinSyncEndCommandBatch(void* context, CFsmEvent* event)
     842             : {
     843           0 :     ENSURE(event->GetType() == (uint)NMT_END_COMMAND_BATCH);
     844             : 
     845           0 :     CNetClient* client = static_cast<CNetClient*>(context);
     846             : 
     847           0 :     CEndCommandBatchMessage* endMessage = (CEndCommandBatchMessage*)event->GetParamRef();
     848             : 
     849           0 :     client->m_ClientTurnManager->FinishedAllCommands(endMessage->m_Turn, endMessage->m_TurnLength);
     850             : 
     851             :     // Execute all the received commands for the latest turn
     852           0 :     client->m_ClientTurnManager->UpdateFastForward();
     853           0 : 
     854             :     return true;
     855             : }
     856           0 : 
     857             : bool CNetClient::OnRejoined(void* context, CFsmEvent* event)
     858             : {
     859           0 :     ENSURE(event->GetType() == (uint)NMT_REJOINED);
     860             : 
     861           0 :     CNetClient* client = static_cast<CNetClient*>(context);
     862             :     CRejoinedMessage* message = static_cast<CRejoinedMessage*>(event->GetParamRef());
     863           0 : 
     864             :     client->PushGuiMessage(
     865           0 :         "type", "rejoined",
     866             :         "guid", message->m_GUID);
     867           0 : 
     868             :     return true;
     869             : }
     870           0 : 
     871             : bool CNetClient::OnKicked(void *context, CFsmEvent* event)
     872           0 : {
     873             :     ENSURE(event->GetType() == (uint)NMT_KICKED);
     874             : 
     875           0 :     CNetClient* client = static_cast<CNetClient*>(context);
     876             :     CKickedMessage* message = static_cast<CKickedMessage*>(event->GetParamRef());
     877           0 : 
     878             :     client->PushGuiMessage(
     879           0 :         "username", message->m_Name,
     880           0 :         "type", "kicked",
     881             :         "banned", message->m_Ban != 0);
     882           0 : 
     883             :     return true;
     884           0 : }
     885             : 
     886           0 : bool CNetClient::OnClientTimeout(void *context, CFsmEvent* event)
     887             : {
     888             :     // Report the timeout of some other client
     889           0 : 
     890             :     ENSURE(event->GetType() == (uint)NMT_CLIENT_TIMEOUT);
     891           0 : 
     892             :     CNetClient* client = static_cast<CNetClient*>(context);
     893           0 :     CClientTimeoutMessage* message = static_cast<CClientTimeoutMessage*>(event->GetParamRef());
     894           0 : 
     895             :     client->PushGuiMessage(
     896           0 :         "type", "netwarn",
     897           0 :         "warntype", "client-timeout",
     898             :         "guid", message->m_GUID,
     899           0 :         "lastReceivedTime", message->m_LastReceivedTime);
     900             : 
     901           0 :     return true;
     902             : }
     903             : 
     904           0 : bool CNetClient::OnClientPerformance(void *context, CFsmEvent* event)
     905             : {
     906             :     // Performance statistics for one or multiple clients
     907             : 
     908           0 :     ENSURE(event->GetType() == (uint)NMT_CLIENT_PERFORMANCE);
     909             : 
     910           0 :     CNetClient* client = static_cast<CNetClient*>(context);
     911           0 :     CClientPerformanceMessage* message = static_cast<CClientPerformanceMessage*>(event->GetParamRef());
     912             : 
     913           0 :     // Display warnings for other clients with bad ping
     914             :     for (size_t i = 0; i < message->m_Clients.size(); ++i)
     915             :     {
     916           0 :         if (message->m_Clients[i].m_MeanRTT < NETWORK_BAD_PING || message->m_Clients[i].m_GUID == client->m_GUID)
     917           0 :             continue;
     918             : 
     919           0 :         client->PushGuiMessage(
     920             :             "type", "netwarn",
     921             :             "warntype", "client-latency",
     922           0 :             "guid", message->m_Clients[i].m_GUID,
     923             :             "meanRTT", message->m_Clients[i].m_MeanRTT);
     924             :     }
     925             : 
     926           0 :     return true;
     927             : }
     928           0 : 
     929           0 : bool CNetClient::OnClientsLoading(void *context, CFsmEvent *event)
     930             : {
     931             :     ENSURE(event->GetType() == (uint)NMT_CLIENTS_LOADING);
     932           0 : 
     933             :     CNetClient* client = static_cast<CNetClient*>(context);
     934           0 :     CClientsLoadingMessage* message = static_cast<CClientsLoadingMessage*>(event->GetParamRef());
     935             : 
     936             :     std::vector<CStr> guids;
     937           0 :     guids.reserve(message->m_Clients.size());
     938             :     for (const CClientsLoadingMessage::S_m_Clients& mClient : message->m_Clients)
     939             :         guids.push_back(mClient.m_GUID);
     940           0 : 
     941           0 :     client->PushGuiMessage(
     942             :         "type", "clients-loading",
     943             :         "guids", guids);
     944           0 :     return true;
     945             : }
     946             : 
     947           0 : bool CNetClient::OnClientPaused(void *context, CFsmEvent *event)
     948             : {
     949           0 :     ENSURE(event->GetType() == (uint)NMT_CLIENT_PAUSED);
     950             : 
     951           0 :     CNetClient* client = static_cast<CNetClient*>(context);
     952           0 :     CClientPausedMessage* message = static_cast<CClientPausedMessage*>(event->GetParamRef());
     953             : 
     954           0 :     client->PushGuiMessage(
     955           0 :         "type", "paused",
     956           0 :         "pause", message->m_Pause != 0,
     957           0 :         "guid", message->m_GUID);
     958             : 
     959           0 :     return true;
     960             : }
     961             : 
     962           0 : bool CNetClient::OnLoadedGame(void* context, CFsmEvent* event)
     963             : {
     964             :     ENSURE(event->GetType() == (uint)NMT_LOADED_GAME);
     965           0 : 
     966             :     CNetClient* client = static_cast<CNetClient*>(context);
     967           0 : 
     968             :     // All players have loaded the game - start running the turn manager
     969           0 :     // so that the game begins
     970           0 :     client->m_Game->SetTurnManager(client->m_ClientTurnManager);
     971             : 
     972           0 :     client->PushGuiMessage(
     973             :         "type", "netstatus",
     974           0 :         "status", "active");
     975           0 : 
     976             :     // If we have rejoined an in progress game, send the rejoined message to the server.
     977           0 :     if (client->m_Rejoin)
     978             :         client->SendRejoinedMessage();
     979             : 
     980           0 :     return true;
     981             : }
     982           0 : 
     983             : bool CNetClient::OnInGame(void *context, CFsmEvent* event)
     984           0 : {
     985             :     // TODO: should split each of these cases into a separate method
     986             : 
     987             :     CNetClient* client = static_cast<CNetClient*>(context);
     988           0 :     CNetMessage* message = static_cast<CNetMessage*>(event->GetParamRef());
     989             : 
     990           0 :     if (message)
     991             :     {
     992             :         if (message->GetType() == NMT_SIMULATION_COMMAND)
     993             :         {
     994             :             CSimulationMessage* simMessage = static_cast<CSimulationMessage*> (message);
     995           0 :             client->m_ClientTurnManager->OnSimulationMessage(simMessage);
     996           0 :         }
     997             :         else if (message->GetType() == NMT_SYNC_ERROR)
     998           0 :         {
     999             :             CSyncErrorMessage* syncMessage = static_cast<CSyncErrorMessage*> (message);
    1000             :             client->m_ClientTurnManager->OnSyncError(syncMessage->m_Turn, syncMessage->m_HashExpected, syncMessage->m_PlayerNames);
    1001           0 :         }
    1002             :         else if (message->GetType() == NMT_END_COMMAND_BATCH)
    1003             :         {
    1004             :             CEndCommandBatchMessage* endMessage = static_cast<CEndCommandBatchMessage*> (message);
    1005           0 :             client->m_ClientTurnManager->FinishedAllCommands(endMessage->m_Turn, endMessage->m_TurnLength);
    1006           0 :         }
    1007             :     }
    1008           0 : 
    1009             :     return true;
    1010           0 : }

Generated by: LCOV version 1.13