• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Return-To-The-Roots / s25client / 13471782468

22 Feb 2025 09:46AM UTC coverage: 50.335% (+0.009%) from 50.326%
13471782468

Pull #1744

github

web-flow
Merge f1cb59907 into 541bc230f
Pull Request #1744: Add missing translated strings

0 of 2 new or added lines in 1 file covered. (0.0%)

5 existing lines in 2 files now uncovered.

22372 of 44446 relevant lines covered (50.34%)

36586.69 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

19.8
/libs/s25main/network/GameClient.cpp
1
// Copyright (C) 2005 - 2024 Settlers Freaks (sf-team at siedler25.org)
2
//
3
// SPDX-License-Identifier: GPL-2.0-or-later
4

5
#include "GameClient.h"
6
#include "CreateServerInfo.h"
7
#include "EventManager.h"
8
#include "Game.h"
9
#include "GameEvent.h"
10
#include "GameLobby.h"
11
#include "GameManager.h"
12
#include "GameMessage_GameCommand.h"
13
#include "JoinPlayerInfo.h"
14
#include "Loader.h"
15
#include "NWFInfo.h"
16
#include "PlayerGameCommands.h"
17
#include "RTTR_Version.h"
18
#include "ReplayInfo.h"
19
#include "RttrConfig.h"
20
#include "Savegame.h"
21
#include "SerializedGameData.h"
22
#include "Settings.h"
23
#include "ai/AIPlayer.h"
24
#include "drivers/VideoDriverWrapper.h"
25
#include "factories/AIFactory.h"
26
#include "files.h"
27
#include "helpers/containerUtils.h"
28
#include "helpers/format.hpp"
29
#include "helpers/mathFuncs.h"
30
#include "lua/LuaInterfaceBase.h"
31
#include "network/ClientInterface.h"
32
#include "network/GameMessages.h"
33
#include "network/GameServer.h"
34
#include "ogl/FontStyle.h"
35
#include "ogl/glArchivItem_Bitmap.h"
36
#include "ogl/glFont.h"
37
#include "random/Random.h"
38
#include "random/randomIO.h"
39
#include "world/GameWorld.h"
40
#include "world/GameWorldView.h"
41
#include "world/MapLoader.h"
42
#include "gameTypes/RoadBuildState.h"
43
#include "gameData/GameConsts.h"
44
#include "libsiedler2/ArchivItem_Map.h"
45
#include "libsiedler2/ArchivItem_Map_Header.h"
46
#include "libsiedler2/prototypen.h"
47
#include "s25util/SocketSet.h"
48
#include "s25util/StringConversion.h"
49
#include "s25util/System.h"
50
#include "s25util/fileFuncs.h"
51
#include "s25util/strFuncs.h"
52
#include "s25util/utf8.h"
53
#include <boost/filesystem.hpp>
54
#include <helpers/chronoIO.h>
55
#include <memory>
56

57
namespace {
58
void copyFileIfPathDifferent(const boost::filesystem::path& src_path, const boost::filesystem::path& dst_path)
×
59
{
60
    if(src_path != dst_path)
×
61
    {
62
        boost::system::error_code ignoredEc;
×
63
        constexpr auto overwrite_existing =
×
64
#if BOOST_VERSION >= 107400
65
          boost::filesystem::copy_options::overwrite_existing;
66
#else
67
          boost::filesystem::copy_option::overwrite_if_exists;
68
#endif
69
        copy_file(src_path, dst_path, overwrite_existing, ignoredEc);
×
70
    }
71
}
×
72
} // namespace
73

74
void GameClient::ClientConfig::Clear()
9✔
75
{
76
    server.clear();
9✔
77
    gameName.clear();
9✔
78
    password.clear();
9✔
79
    port = 0;
9✔
80
    isHost = false;
9✔
81
}
9✔
82

83
GameClient::GameClient() : skiptogf(0), mainPlayer(0), state(ClientState::Stopped), ci(nullptr), replayMode(false) {}
6✔
84

85
GameClient::~GameClient()
9✔
86
{
87
    Stop();
6✔
88
}
9✔
89

90
/**
91
 *  Verbindet den Client mit einem Server
92
 *
93
 *  @param server    Hostname des Zielrechners
94
 *  @param password  Passwort des Spieles
95
 *  @param servertyp Servertyp des Spieles (Direct/LAN/usw)
96
 *  @param host      gibt an ob wir selbst der Host sind
97
 *
98
 *  @return true, wenn Client erfolgreich verbunden und gestartet
99
 */
100
bool GameClient::Connect(const std::string& server, const std::string& password, ServerType servertyp,
3✔
101
                         unsigned short port, bool host, bool use_ipv6)
102
{
103
    Stop();
3✔
104

105
    RTTR_Assert(aiBattlePlayers_.empty());
3✔
106

107
    // Name und Password kopieren
108
    clientconfig.server = server;
3✔
109
    clientconfig.password = password;
3✔
110

111
    clientconfig.servertyp = servertyp;
3✔
112
    clientconfig.port = port;
3✔
113
    clientconfig.isHost = host;
3✔
114

115
    // Verbinden
116
    if(!mainPlayer.socket.Connect(server, port, use_ipv6, SETTINGS.proxy))
3✔
117
    {
118
        LOG.write("GameClient::Connect: ERROR: Connect failed!\n");
×
119
        if(host)
×
120
            GAMESERVER.Stop();
×
121
        return false;
×
122
    }
123

124
    state = ClientState::Connect;
3✔
125
    AdvanceState(ConnectState::Initiated);
3✔
126

127
    // Es wird kein Replay abgespielt, sondern dies ist ein richtiges Spiel
128
    replayMode = false;
3✔
129

130
    return true;
3✔
131
}
132

133
bool GameClient::HostGame(const CreateServerInfo& csi, const MapDescription& map)
×
134
{
135
    std::string hostPw = createRandString(20);
×
136
    // Copy the map and lua to the played map folders to avoid having to transmit it from the (local) server
137
    const auto playedMapPath = RTTRCONFIG.ExpandPath(s25::folders::mapsPlayed) / map.map_path.filename();
×
138
    copyFileIfPathDifferent(map.map_path, playedMapPath);
×
139
    if(map.lua_path)
×
140
    {
141
        const auto playedMapLuaPath = RTTRCONFIG.ExpandPath(s25::folders::mapsPlayed) / map.lua_path->filename();
×
142
        copyFileIfPathDifferent(*map.lua_path, playedMapLuaPath);
×
143
    }
144
    return GAMESERVER.Start(csi, map, hostPw) && Connect("localhost", hostPw, csi.type, csi.port, true, csi.ipv6);
×
145
}
146

147
/**
148
 *  Hauptschleife des Clients
149
 */
150
void GameClient::Run()
×
151
{
152
    if(state == ClientState::Stopped)
×
153
        return;
×
154

155
    SocketSet set;
×
156

157
    // erstmal auf Daten überprüfen
158
    set.Clear();
×
159

160
    // zum set hinzufügen
161
    set.Add(mainPlayer.socket);
×
162
    if(set.Select(0, 0) > 0)
×
163
    {
164
        // nachricht empfangen
165
        if(!mainPlayer.receiveMsgs())
×
166
        {
167
            LOG.write("Receiving Message from server failed\n");
×
168
            ServerLost();
×
169
        }
170
    }
171

172
    // nun auf Fehler prüfen
173
    set.Clear();
×
174

175
    // zum set hinzufügen
176
    set.Add(mainPlayer.socket);
×
177

178
    // auf fehler prüfen
179
    if(set.Select(0, 2) > 0)
×
180
    {
181
        if(set.InSet(mainPlayer.socket))
×
182
        {
183
            // Server ist weg
184
            LOG.write("Error on socket to server\n");
×
185
            ServerLost();
×
186
        }
187
    }
188

189
    if(state == ClientState::Loaded)
×
190
    {
191
        // All players ready?
192
        if(nwfInfo->isReady())
×
193
            OnGameStart();
×
194
    } else if(state == ClientState::Game)
×
195
        ExecuteGameFrame();
×
196

197
    // maximal 10 Pakete verschicken
198
    mainPlayer.sendMsgs(10);
×
199

200
    mainPlayer.executeMsgs(*this);
×
201
}
202

203
/**
204
 *  Stoppt das Spiel
205
 */
206
void GameClient::Stop()
11✔
207
{
208
    if(state == ClientState::Stopped)
11✔
209
        return;
8✔
210

211
    if(game)
3✔
212
        ExitGame();
×
213
    else if(state == ClientState::Connect || state == ClientState::Config)
3✔
214
        gameLobby.reset();
3✔
215

216
    if(IsHost())
3✔
217
        GAMESERVER.Stop();
×
218

219
    framesinfo.Clear();
3✔
220
    clientconfig.Clear();
3✔
221
    mapinfo.Clear();
3✔
222

223
    if(replayinfo)
3✔
224
    {
225
        if(replayinfo->replay.IsRecording())
×
226
            replayinfo->replay.StopRecording();
×
227
        replayinfo->replay.Close();
×
228
        replayinfo.reset();
×
229
    }
230

231
    mainPlayer.closeConnection();
3✔
232

233
    // clear jump target
234
    skiptogf = 0;
3✔
235

236
    // Consistency check: No game, no lobby remaining
237
    RTTR_Assert(!game);
3✔
238
    RTTR_Assert(!gameLobby);
3✔
239

240
    state = ClientState::Stopped;
3✔
241
    LOG.write("client state changed to stop\n");
3✔
242

243
    aiBattlePlayers_.clear();
3✔
244
}
245

246
std::shared_ptr<GameLobby> GameClient::GetGameLobby()
1✔
247
{
248
    RTTR_Assert(state != ClientState::Config || gameLobby);
1✔
249
    return gameLobby;
1✔
250
}
251

252
const AIPlayer* GameClient::GetAIPlayer(unsigned id) const
×
253
{
254
    if(!game)
×
255
        return nullptr;
×
256
    return game->GetAIPlayer(id);
×
257
}
258

259
/**
260
 *  Startet ein Spiel oder Replay.
261
 *
262
 *  @param[in] random_init Initialwert des Zufallsgenerators.
263
 */
264
void GameClient::StartGame(const unsigned random_init)
×
265
{
266
    RTTR_Assert(state == ClientState::Config || (state == ClientState::Stopped && replayMode));
×
267

268
    // Mond malen
269
    Position moonPos = VIDEODRIVER.GetMousePos();
×
270
    moonPos.y -= 40;
×
271
    LOADER.GetImageN("resource", 33)->DrawFull(moonPos);
×
272
    VIDEODRIVER.SwapBuffers();
×
273

274
    // Start in pause mode
275
    framesinfo.isPaused = true;
×
276

277
    // Je nach Geschwindigkeit GF-Länge einstellen
278
    framesinfo.gf_length = SPEED_GF_LENGTHS[gameLobby->getSettings().speed];
×
279
    framesinfo.gfLengthReq = framesinfo.gf_length;
×
280

281
    // Random-Generator initialisieren
282
    RANDOM.Init(random_init);
×
283

284
    if(!IsReplayModeOn() && mapinfo.savegame && !mapinfo.savegame->Load(mapinfo.filepath, SaveGameDataToLoad::All))
×
285
    {
286
        OnError(ClientError::InvalidMap);
×
287
        return;
×
288
    }
289

290
    // If we have a savegame, start at its first GF, else at 0
291
    unsigned startGF = (mapinfo.type == MapType::Savegame) ? mapinfo.savegame->start_gf : 0;
×
292
    // Create the game
293
    game =
294
      std::make_shared<Game>(std::move(gameLobby->getSettings()), startGF,
×
295
                             std::vector<PlayerInfo>(gameLobby->getPlayers().begin(), gameLobby->getPlayers().end()));
×
296
    if(!IsReplayModeOn())
×
297
    {
298
        for(unsigned id = 0; id < gameLobby->getNumPlayers(); id++)
×
299
        {
300
            if(gameLobby->getPlayer(id).isUsed())
×
301
                nwfInfo->addPlayer(id);
×
302
        }
303
    }
304
    // Release lobby
305
    gameLobby.reset();
×
306

307
    state = ClientState::Loading;
×
308

309
    if(ci)
×
310
        ci->CI_GameLoading(game);
×
311

312
    // Get standard settings before they get overwritten
313
    GetPlayer(GetPlayerId()).FillVisualSettings(default_settings);
×
314

315
    GameWorld& gameWorld = game->world_;
×
316
    if(mapinfo.savegame)
×
317
        mapinfo.savegame->sgd.ReadSnapshot(*game, *this);
×
318
    else
319
    {
320
        RTTR_Assert(mapinfo.type != MapType::Savegame);
×
321
        /// Startbündnisse setzen
322
        for(unsigned i = 0; i < gameWorld.GetNumPlayers(); ++i)
×
323
            gameWorld.GetPlayer(i).MakeStartPacts();
×
324

325
        MapLoader loader(gameWorld);
×
326
        if(!loader.Load(mapinfo.filepath)
×
327
           || (!mapinfo.luaFilepath.empty() && !loader.LoadLuaScript(*game, *this, mapinfo.luaFilepath)))
×
328
        {
329
            OnError(ClientError::InvalidMap);
×
330
            return;
×
331
        }
332
        gameWorld.SetupResources();
×
333
    }
334
    gameWorld.InitAfterLoad();
×
335

336
    // Update visual settings
337
    ResetVisualSettings();
×
338

339
    if(!replayMode)
×
340
    {
341
        RTTR_Assert(!replayinfo);
×
342
        StartReplayRecording(random_init);
×
343
    }
344

345
    // Daten nach dem Schreiben des Replays ggf wieder löschen
346
    mapinfo.mapData.Clear();
×
347
}
348

349
void GameClient::GameLoaded()
×
350
{
351
    RTTR_Assert(state == ClientState::Loading);
×
352

353
    state = ClientState::Loaded;
×
354

355
    if(replayMode)
×
356
        OnGameStart();
×
357
    else
358
    {
359
        // Notify server that we are ready
360
        if(IsHost())
×
361
        {
362
            for(unsigned id = 0; id < GetNumPlayers(); id++)
×
363
            {
364
                if(GetPlayer(id).ps == PlayerState::AI)
×
365
                {
366
                    game->AddAIPlayer(CreateAIPlayer(id, GetPlayer(id).aiInfo));
×
367
                    SendNothingNC(id);
×
368
                }
369
            }
370
            if(IsAIBattleModeOn())
×
371
                ToggleHumanAIPlayer(aiBattlePlayers_[GetPlayerId()]);
×
372
        }
373
        SendNothingNC();
×
374
    }
375
}
×
376

377
void GameClient::ExitGame()
×
378
{
379
    RTTR_Assert(state == ClientState::Game || state == ClientState::Loaded || state == ClientState::Loading);
×
380
    game.reset();
×
381
    nwfInfo.reset();
×
382
    // Clear remaining commands
383
    gameCommands_.clear();
×
384
}
×
385

386
unsigned GameClient::GetGFNumber() const
×
387
{
388
    return game->em_->GetCurrentGF();
×
389
}
390

391
/**
392
 *  Ping-Nachricht.
393
 */
394
bool GameClient::OnGameMessage(const GameMessage_Ping& /*msg*/)
×
395
{
396
    mainPlayer.sendMsgAsync(new GameMessage_Pong());
×
397
    return true;
×
398
}
399

400
/**
401
 *  Player-ID-Nachricht.
402
 */
403
bool GameClient::OnGameMessage(const GameMessage_Player_Id& msg)
3✔
404
{
405
    if(!VerifyState(ConnectState::Initiated))
3✔
406
        return true;
×
407
    // haben wir eine ungültige ID erhalten? (aka Server-Voll)
408
    if(msg.player == GameMessageWithPlayer::NO_PLAYER_ID)
3✔
409
    {
410
        OnError(ClientError::ServerFull);
×
411
        return true;
×
412
    }
413

414
    mainPlayer.playerId = msg.player;
3✔
415

416
    // Server-Typ senden
417
    mainPlayer.sendMsgAsync(new GameMessage_Server_Type(clientconfig.servertyp, rttr::version::GetRevision()));
3✔
418
    AdvanceState(ConnectState::VerifyServer);
3✔
419
    return true;
3✔
420
}
421

422
/**
423
 *  Player-List-Nachricht.
424
 */
425
bool GameClient::OnGameMessage(const GameMessage_Player_List& msg)
2✔
426
{
427
    if(state != ClientState::Config && !VerifyState(ConnectState::QueryPlayerList))
2✔
428
        return true;
×
429
    RTTR_Assert(gameLobby);
2✔
430
    RTTR_Assert(gameLobby->getNumPlayers() == msg.playerInfos.size());
2✔
431
    if(gameLobby->getNumPlayers() != msg.playerInfos.size())
2✔
432
    {
433
        OnError(ClientError::InvalidMessage);
×
434
        return true;
×
435
    }
436

437
    for(unsigned i = 0; i < gameLobby->getNumPlayers(); ++i)
8✔
438
        gameLobby->getPlayer(i) = msg.playerInfos[i];
6✔
439

440
    if(state == ClientState::Connect)
2✔
441
        AdvanceState(ConnectState::QuerySettings);
2✔
442
    return true;
2✔
443
}
444

445
bool GameClient::OnGameMessage(const GameMessage_Player_Name& msg)
×
446
{
447
    if(state != ClientState::Config)
×
448
        return true;
×
449
    if(msg.player >= gameLobby->getNumPlayers())
×
450
        return true;
×
451
    gameLobby->getPlayer(msg.player).name = msg.playername;
×
452
    if(ci)
×
453
        ci->CI_PlayerDataChanged(msg.player);
×
454
    return true;
×
455
}
456

457
///////////////////////////////////////////////////////////////////////////////
458
/// player joined
459
/// @param message  Nachricht, welche ausgeführt wird
460
bool GameClient::OnGameMessage(const GameMessage_Player_New& msg)
×
461
{
462
    if(state != ClientState::Config)
×
463
        return true;
×
464

465
    if(msg.player >= gameLobby->getNumPlayers())
×
466
        return true;
×
467

468
    JoinPlayerInfo& playerInfo = gameLobby->getPlayer(msg.player);
×
469

470
    playerInfo.name = msg.name;
×
471
    playerInfo.ps = PlayerState::Occupied;
×
472
    playerInfo.ping = 0;
×
473

474
    if(ci)
×
475
        ci->CI_NewPlayer(msg.player);
×
476
    return true;
×
477
}
478

479
bool GameClient::OnGameMessage(const GameMessage_Player_Ping& msg)
×
480
{
481
    if(state == ClientState::Config)
×
482
    {
483
        if(msg.player >= gameLobby->getNumPlayers())
×
484
            return true;
×
485
        gameLobby->getPlayer(msg.player).ping = msg.ping;
×
486
    } else if(state == ClientState::Loading || state == ClientState::Loaded || state == ClientState::Game)
×
487
    {
488
        if(msg.player >= GetNumPlayers())
×
489
            return true;
×
490
        GetPlayer(msg.player).ping = msg.ping;
×
491
    } else
492
    {
493
        RTTR_Assert(false);
×
494
        return true;
495
    }
496

497
    if(ci)
×
498
        ci->CI_PingChanged(msg.player, msg.ping);
×
499
    return true;
×
500
}
501

502
/**
503
 *  Player-Toggle-State-Nachricht.
504
 */
505
bool GameClient::OnGameMessage(const GameMessage_Player_State& msg)
×
506
{
507
    if(state != ClientState::Config)
×
508
        return true;
×
509

510
    if(msg.player >= gameLobby->getNumPlayers())
×
511
        return true;
×
512

513
    JoinPlayerInfo& playerInfo = gameLobby->getPlayer(msg.player);
×
514
    bool wasUsed = playerInfo.isUsed();
×
515
    playerInfo.ps = msg.ps;
×
516
    playerInfo.aiInfo = msg.aiInfo;
×
517

518
    if(ci)
×
519
    {
520
        if(playerInfo.isUsed())
×
521
            ci->CI_NewPlayer(msg.player);
×
522
        else if(wasUsed)
×
523
            ci->CI_PlayerLeft(msg.player);
×
524
        else
525
            ci->CI_PlayerDataChanged(msg.player);
×
526
    }
527
    return true;
×
528
}
529

530
///////////////////////////////////////////////////////////////////////////////
531
/// nation button gedrückt
532
/// @param message  Nachricht, welche ausgeführt wird
533
bool GameClient::OnGameMessage(const GameMessage_Player_Nation& msg)
×
534
{
535
    if(state != ClientState::Config)
×
536
        return true;
×
537

538
    if(msg.player >= gameLobby->getNumPlayers())
×
539
        return true;
×
540

541
    gameLobby->getPlayer(msg.player).nation = msg.nation;
×
542

543
    if(ci)
×
544
        ci->CI_PlayerDataChanged(msg.player);
×
545
    return true;
×
546
}
547

548
///////////////////////////////////////////////////////////////////////////////
549
/// team button gedrückt
550
/// @param message  Nachricht, welche ausgeführt wird
551
bool GameClient::OnGameMessage(const GameMessage_Player_Team& msg)
×
552
{
553
    if(state != ClientState::Config)
×
554
        return true;
×
555

556
    if(msg.player >= gameLobby->getNumPlayers())
×
557
        return true;
×
558

559
    gameLobby->getPlayer(msg.player).team = msg.team;
×
560

561
    if(ci)
×
562
        ci->CI_PlayerDataChanged(msg.player);
×
563
    return true;
×
564
}
565

566
///////////////////////////////////////////////////////////////////////////////
567
/// color button gedrückt
568
/// @param message  Nachricht, welche ausgeführt wird
569
bool GameClient::OnGameMessage(const GameMessage_Player_Color& msg)
×
570
{
571
    if(state != ClientState::Config)
×
572
        return true;
×
573

574
    if(msg.player >= gameLobby->getNumPlayers())
×
575
        return true;
×
576

577
    gameLobby->getPlayer(msg.player).color = msg.color;
×
578

579
    if(ci)
×
580
        ci->CI_PlayerDataChanged(msg.player);
×
581
    return true;
×
582
}
583

584
/**
585
 *  Ready-state eines Spielers hat sich geändert.
586
 *
587
 *  @param[in] message Nachricht, welche ausgeführt wird
588
 */
589
bool GameClient::OnGameMessage(const GameMessage_Player_Ready& msg)
×
590
{
591
    if(state != ClientState::Config)
×
592
        return true;
×
593

594
    if(msg.player >= gameLobby->getNumPlayers())
×
595
        return true;
×
596

597
    gameLobby->getPlayer(msg.player).isReady = msg.ready;
×
598

599
    if(ci)
×
600
        ci->CI_ReadyChanged(msg.player, msg.ready);
×
601
    return true;
×
602
}
603

604
///////////////////////////////////////////////////////////////////////////////
605
/// player gekickt
606
/// @param message  Nachricht, welche ausgeführt wird
607
bool GameClient::OnGameMessage(const GameMessage_Player_Kicked& msg)
×
608
{
609
    if(state == ClientState::Config)
×
610
    {
611
        if(msg.player >= gameLobby->getNumPlayers())
×
612
            return true;
×
613
        gameLobby->getPlayer(msg.player).ps = PlayerState::Free;
×
614
    } else if(state == ClientState::Loading || state == ClientState::Loaded || state == ClientState::Game)
×
615
    {
616
        // Im Spiel anzeigen, dass der Spieler das Spiel verlassen hat
617
        GamePlayer& player = GetPlayer(msg.player);
×
618
        if(player.ps != PlayerState::AI)
×
619
        {
620
            player.ps = PlayerState::AI;
×
621
            player.aiInfo = AI::Info(AI::Type::Dummy);
×
622
            // Host has to handle it
623
            if(IsHost())
×
624
            {
625
                game->AddAIPlayer(CreateAIPlayer(msg.player, player.aiInfo));
×
626
                SendNothingNC(msg.player);
×
627
            }
628
        }
×
629
    } else
630
        return true;
×
631

632
    if(ci)
×
633
        ci->CI_PlayerLeft(msg.player);
×
634
    return true;
×
635
}
636

637
bool GameClient::OnGameMessage(const GameMessage_Player_Swap& msg)
×
638
{
639
    LOG.writeToFile("<<< NMS_PLAYER_SWAP(%u, %u)\n") % unsigned(msg.player) % unsigned(msg.player2);
×
640

641
    if(state == ClientState::Config)
×
642
    {
643
        if(msg.player >= gameLobby->getNumPlayers() || msg.player2 >= gameLobby->getNumPlayers())
×
644
            return true;
×
645

646
        // During preparation just swap the players
647
        using std::swap;
648
        swap(gameLobby->getPlayer(msg.player), gameLobby->getPlayer(msg.player2));
×
649
        // Some things cannot be changed in savegames
650
        if(mapinfo.type == MapType::Savegame)
×
651
            gameLobby->getPlayer(msg.player).FixSwappedSaveSlot(gameLobby->getPlayer(msg.player2));
×
652

653
        // Evtl. sind wir betroffen?
654
        if(mainPlayer.playerId == msg.player)
×
655
            mainPlayer.playerId = msg.player2;
×
656
        else if(mainPlayer.playerId == msg.player2)
×
657
            mainPlayer.playerId = msg.player;
×
658

659
        if(ci)
×
660
            ci->CI_PlayersSwapped(msg.player, msg.player2);
×
661
    } else if(state == ClientState::Loading || state == ClientState::Loaded || state == ClientState::Game)
×
662
        ChangePlayerIngame(msg.player, msg.player2);
×
663
    else
664
        return true;
×
665
    mainPlayer.sendMsgAsync(new GameMessage_Player_SwapConfirm(msg.player, msg.player2));
×
666
    return true;
×
667
}
668

669
/**
670
 *  Server-Typ-Nachricht.
671
 */
672
bool GameClient::OnGameMessage(const GameMessage_Server_TypeOK& msg)
3✔
673
{
674
    if(!VerifyState(ConnectState::VerifyServer))
3✔
675
        return true;
×
676

677
    using StatusCode = GameMessage_Server_TypeOK::StatusCode;
678
    switch(msg.err_code)
3✔
679
    {
680
        case StatusCode::Ok: break;
3✔
681

682
        default:
×
683
        case StatusCode::InvalidServerType:
684
        {
685
            OnError(ClientError::InvalidServerType);
×
686
            return true;
×
687
        }
688
        break;
689

690
        case StatusCode::WrongVersion:
×
691
        {
692
            LOG.write(_("Version mismatch. Server version: %1%, your version %2%")) % msg.version
×
693
              % rttr::version::GetRevision();
×
694
            OnError(ClientError::WrongVersion);
×
695
            return true;
×
696
        }
697
        break;
698
    }
699

700
    mainPlayer.sendMsgAsync(new GameMessage_Server_Password(clientconfig.password));
3✔
701

702
    AdvanceState(ConnectState::QueryPw);
3✔
703
    return true;
3✔
704
}
705

706
/**
707
 *  Server-Passwort-Nachricht.
708
 */
709
bool GameClient::OnGameMessage(const GameMessage_Server_Password& msg)
3✔
710
{
711
    if(!VerifyState(ConnectState::QueryPw))
3✔
712
        return true;
×
713

714
    if(msg.password != "true")
3✔
715
    {
716
        OnError(ClientError::WrongPassword);
×
717
        return true;
×
718
    }
719

720
    mainPlayer.sendMsgAsync(new GameMessage_Player_Name(0xFF, SETTINGS.lobby.name));
3✔
721
    mainPlayer.sendMsgAsync(new GameMessage_MapRequest(true));
3✔
722

723
    AdvanceState(ConnectState::QueryMapInfo);
3✔
724
    return true;
3✔
725
}
726

727
/**
728
 *  Server-Name-Nachricht.
729
 */
730
bool GameClient::OnGameMessage(const GameMessage_Server_Name& msg)
2✔
731
{
732
    if(!VerifyState(ConnectState::QueryServerName))
2✔
733
        return true;
×
734
    clientconfig.gameName = msg.name;
2✔
735

736
    AdvanceState(ConnectState::QueryPlayerList);
2✔
737
    return true;
2✔
738
}
739

740
/**
741
 *  Server-Start-Nachricht
742
 */
743
bool GameClient::OnGameMessage(const GameMessage_Server_Start& msg)
×
744
{
745
    if(state != ClientState::Config)
×
746
        return true;
×
747

748
    nwfInfo = std::make_shared<NWFInfo>();
×
749
    nwfInfo->init(msg.firstNwf, msg.cmdDelay);
×
750
    try
751
    {
752
        StartGame(msg.random_init);
×
753
    } catch(SerializedGameData::Error& error)
×
754
    {
755
        LOG.write("Error when loading game: %s\n") % error.what();
×
756
        Stop();
×
757
        GAMEMANAGER.ShowMenu();
×
758
    }
759
    return true;
×
760
}
761

762
/**
763
 *  Server-Chat-Nachricht.
764
 */
765
bool GameClient::OnGameMessage(const GameMessage_Chat& msg)
×
766
{
767
    if(msg.destination == ChatDestination::System)
×
768
    {
769
        SystemChat(msg.text, (msg.player < game->world_.GetNumPlayers()) ? msg.player : GetPlayerId());
×
770
        return true;
×
771
    }
772
    if(state == ClientState::Game)
×
773
    {
774
        // Ingame message: Do some checking and logging
775
        if(msg.player >= game->world_.GetNumPlayers())
×
776
            return true;
×
777

778
        /// Mit im Replay aufzeichnen
779
        if(replayinfo && replayinfo->replay.IsRecording())
×
780
            replayinfo->replay.AddChatCommand(GetGFNumber(), msg.player, msg.destination, msg.text);
×
781

782
        const GamePlayer& player = game->world_.GetPlayer(msg.player);
×
783

784
        // Besiegte dürfen nicht mehr heimlich mit Verbüdeten oder Feinden reden
785
        if(player.IsDefeated() && msg.destination != ChatDestination::All)
×
786
            return true;
×
787

788
        const auto isValidRecipient = [&msg, &player](const unsigned playerId) {
×
789
            // Always send to self
790
            if(msg.player == playerId)
×
791
                return true;
×
792
            switch(msg.destination)
×
793
            {
794
                case ChatDestination::System:
×
795
                case ChatDestination::All: return true;
×
796
                case ChatDestination::Allies: return player.IsAlly(playerId);
×
797
                case ChatDestination::Enemies: return !player.IsAlly(playerId);
×
798
            }
799
            return true; // LCOV_EXCL_LINE
800
        };
×
801
        for(AIPlayer& ai : game->aiPlayers_)
×
802
        {
803
            if(isValidRecipient(ai.GetPlayerId()))
×
804
                ai.OnChatMessage(msg.player, msg.destination, msg.text);
×
805
        }
806

807
        if(!isValidRecipient(GetPlayerId()))
×
808
            return true;
×
809
    } else if(state == ClientState::Config)
×
810
    {
811
        // GameLobby message: Just check for valid player
812
        if(msg.player >= gameLobby->getNumPlayers())
×
813
            return true;
×
814
    } else
815
        return true;
×
816

817
    if(ci)
×
818
        ci->CI_Chat(msg.player, msg.destination, msg.text);
×
819
    return true;
×
820
}
821

822
/**
823
 *  Server-Async-Nachricht.
824
 */
825
bool GameClient::OnGameMessage(const GameMessage_Server_Async& msg)
×
826
{
827
    if(state != ClientState::Game)
×
828
        return true;
×
829

830
    // Liste mit Namen und Checksummen erzeugen
831
    std::stringstream checksum_list;
×
832
    for(unsigned i = 0; i < msg.checksums.size(); ++i)
×
833
    {
834
        checksum_list << GetPlayer(i).name << ": " << msg.checksums[i];
×
835
        if(i + 1 < msg.checksums.size())
×
836
            checksum_list << ", ";
×
837
    }
838

839
    // Fehler ausgeben (Konsole)!
840
    LOG.write(_("The Game is not in sync. Checksums of some players don't match."));
×
841
    LOG.write("\n%1%\n") % checksum_list.str();
×
842

843
    // Messenger im Game
844
    if(ci)
×
845
        ci->CI_Async(checksum_list.str());
×
846

847
    std::string fileName = s25util::Time::FormatTime("async_%Y-%m-%d_%H-%i-%s");
×
848
    fileName += "_" + s25util::toStringClassic(GetPlayerId()) + "_";
×
849
    fileName += GetPlayer(GetPlayerId()).name;
×
850

851
    const bfs::path filePathSave = RTTRCONFIG.ExpandPath(s25::folders::save) / makePortableFileName(fileName + ".sav");
×
852
    const bfs::path filePathLog =
853
      RTTRCONFIG.ExpandPath(s25::folders::logs) / makePortableFileName(fileName + "Player.log");
×
854
    saveRandomLog(filePathLog, RANDOM.GetAsyncLog());
×
855
    SaveToFile(filePathSave);
×
856
    LOG.write(_("Async log saved at %1%,\ngame saved at %2%\n")) % filePathLog % filePathSave;
×
857
    return true;
×
858
}
859

860
/**
861
 *  Server-Countdown-Nachricht.
862
 */
863
bool GameClient::OnGameMessage(const GameMessage_Countdown& msg)
×
864
{
865
    if(state != ClientState::Config)
×
866
        return true;
×
867
    if(ci)
×
868
        ci->CI_Countdown(msg.countdown);
×
869
    return true;
×
870
}
871

872
/**
873
 *  Server-Cancel-Countdown-Nachricht.
874
 */
875
bool GameClient::OnGameMessage(const GameMessage_CancelCountdown& msg)
×
876
{
877
    if(state != ClientState::Config)
×
878
        return true;
×
879
    if(ci)
×
880
        ci->CI_CancelCountdown(msg.error);
×
881
    return true;
×
882
}
883

884
/**
885
 *  verarbeitet die MapInfo-Nachricht, in der die gepackte Größe,
886
 *  die normale Größe und Teilanzahl der Karte übertragen wird.
887
 *
888
 *  @param message Nachricht, welche ausgeführt wird
889
 */
890
bool GameClient::OnGameMessage(const GameMessage_Map_Info& msg)
3✔
891
{
892
    if(!VerifyState(ConnectState::QueryMapInfo))
3✔
893
        return true;
×
894

895
    // full path
896
    const std::string portFilename = makePortableFileName(msg.filename);
6✔
897
    if(portFilename.empty())
3✔
898
    {
899
        LOG.write("Invalid filename received!\n");
×
900
        OnError(ClientError::InvalidMap);
×
901
        return true;
×
902
    }
903
    if(!MapInfo::verifySize(msg.mapLen, msg.luaLen, msg.mapCompressedLen, msg.luaCompressedLen))
3✔
904
    {
905
        OnError(ClientError::InvalidMap);
×
906
        return true;
×
907
    }
908
    mapinfo.filepath = RTTRCONFIG.ExpandPath(s25::folders::mapsPlayed) / portFilename;
6✔
909
    mapinfo.type = msg.mt;
3✔
910

911
    // lua script file path
912
    if(msg.luaLen > 0)
3✔
913
        mapinfo.luaFilepath = bfs::path(mapinfo.filepath).replace_extension("lua");
2✔
914
    else
915
        mapinfo.luaFilepath.clear();
2✔
916

917
    // We have the map locally already, so prepare and ask if this is the same as the one on the server
918
    if(bfs::exists(mapinfo.filepath) && (mapinfo.luaFilepath.empty() || bfs::exists(mapinfo.luaFilepath))
3✔
919
       && CreateLobby())
3✔
920
    {
921
        mapinfo.mapData.CompressFromFile(mapinfo.filepath, &mapinfo.mapChecksum);
×
922
        if(mapinfo.mapData.data.size() == msg.mapCompressedLen && mapinfo.mapData.uncompressedLength == msg.mapLen)
×
923
        {
924
            bool ok = true;
×
925
            if(!mapinfo.luaFilepath.empty())
×
926
            {
927
                mapinfo.luaData.CompressFromFile(mapinfo.luaFilepath, &mapinfo.luaChecksum);
×
928
                ok = (mapinfo.luaData.data.size() == msg.luaCompressedLen
×
929
                      && mapinfo.luaData.uncompressedLength == msg.luaLen);
×
930
            }
931

932
            if(ok)
×
933
            {
934
                mainPlayer.sendMsgAsync(new GameMessage_Map_Checksum(mapinfo.mapChecksum, mapinfo.luaChecksum));
×
935
                AdvanceState(ConnectState::VerifyMap);
×
936
                return true;
×
937
            }
938
        }
939
        gameLobby.reset();
×
940
    }
941
    mapinfo.mapData.uncompressedLength = msg.mapLen;
3✔
942
    mapinfo.luaData.uncompressedLength = msg.luaLen;
3✔
943
    mapinfo.mapData.data.resize(msg.mapCompressedLen);
3✔
944
    mapinfo.luaData.data.resize(msg.luaCompressedLen);
3✔
945
    mainPlayer.sendMsgAsync(new GameMessage_MapRequest(false));
3✔
946
    AdvanceState(ConnectState::ReceiveMap);
3✔
947
    return true;
3✔
948
}
949

950
///////////////////////////////////////////////////////////////////////////////
951
/// Kartendaten
952
/// @param message  Nachricht, welche ausgeführt wird
953
bool GameClient::OnGameMessage(const GameMessage_Map_Data& msg)
8✔
954
{
955
    if(!VerifyState(ConnectState::ReceiveMap))
8✔
956
        return true;
×
957

958
    LOG.writeToFile("<<< NMS_MAP_DATA(%u)\n") % msg.data.size();
8✔
959
    std::vector<char>& targetData = (msg.isMapData) ? mapinfo.mapData.data : mapinfo.luaData.data;
8✔
960
    if(msg.data.size() > targetData.size() || msg.offset > targetData.size() - msg.data.size())
8✔
961
    {
962
        OnError(ClientError::MapTransmission);
1✔
963
        return true;
1✔
964
    }
965
    std::copy(msg.data.begin(), msg.data.end(), targetData.begin() + msg.offset);
7✔
966

967
    uint32_t totalSize = mapinfo.mapData.data.size();
7✔
968
    uint32_t receivedSize = msg.offset + msg.data.size();
7✔
969
    if(!mapinfo.luaFilepath.empty())
7✔
970
    {
971
        totalSize += mapinfo.luaData.data.size();
4✔
972
        // Assumes lua data comes after the map data
973
        if(!msg.isMapData)
4✔
974
            receivedSize += mapinfo.mapData.data.size();
2✔
975
    }
976
    if(ci)
7✔
977
        ci->CI_MapPartReceived(receivedSize, totalSize);
7✔
978

979
    if(receivedSize == totalSize)
7✔
980
    {
981
        if(!mapinfo.mapData.DecompressToFile(mapinfo.filepath, &mapinfo.mapChecksum))
2✔
982
        {
983
            OnError(ClientError::MapTransmission);
×
984
            return true;
×
985
        }
986
        if(!mapinfo.luaFilepath.empty() && !mapinfo.luaData.DecompressToFile(mapinfo.luaFilepath, &mapinfo.luaChecksum))
2✔
987
        {
988
            OnError(ClientError::MapTransmission);
×
989
            return true;
×
990
        }
991
        RTTR_Assert(!mapinfo.luaFilepath.empty() || mapinfo.luaChecksum == 0);
2✔
992

993
        if(!CreateLobby())
2✔
994
        {
995
            OnError(ClientError::MapTransmission);
×
996
            return true;
×
997
        }
998

999
        mainPlayer.sendMsgAsync(new GameMessage_Map_Checksum(mapinfo.mapChecksum, mapinfo.luaChecksum));
2✔
1000
        AdvanceState(ConnectState::VerifyMap);
2✔
1001
    }
1002
    return true;
7✔
1003
}
1004

1005
bool GameClient::OnGameMessage(const GameMessage_SkipToGF& msg)
×
1006
{
1007
    skiptogf = msg.targetGF;
×
1008
    LOG.write("Jumping from GF %1% to GF %2%\n") % GetGFNumber() % skiptogf;
×
1009
    return true;
×
1010
}
1011

1012
void GameClient::OnError(ClientError error)
1✔
1013
{
1014
    if(ci)
1✔
1015
        ci->CI_Error(error);
1✔
1016
    Stop();
1✔
1017
}
1✔
1018

1019
void GameClient::AdvanceState(ConnectState newState)
25✔
1020
{
1021
    connectState = newState;
25✔
1022
    if(ci)
25✔
1023
        ci->CI_NextConnectState(connectState);
25✔
1024
}
25✔
1025

1026
bool GameClient::VerifyState(ConnectState expectedState)
28✔
1027
{
1028
    if(state != ClientState::Connect || connectState != expectedState)
28✔
1029
    {
1030
        OnError(ClientError::InvalidMessage);
×
1031
        return false;
×
1032
    }
1033
    return true;
28✔
1034
}
1035

1036
bool GameClient::CreateLobby()
2✔
1037
{
1038
    RTTR_Assert(!gameLobby);
2✔
1039

1040
    unsigned numPlayers;
1041

1042
    switch(mapinfo.type)
2✔
1043
    {
1044
        case MapType::OldMap:
2✔
1045
        {
1046
            libsiedler2::Archiv map;
2✔
1047

1048
            // Karteninformationen laden
1049
            if(libsiedler2::loader::LoadMAP(mapinfo.filepath, map, true) != 0)
2✔
1050
            {
1051
                LOG.write("GameClient::OnMapData: ERROR: Map %1%, couldn't load header!\n") % mapinfo.filepath;
×
1052
                return false;
×
1053
            }
1054

1055
            const libsiedler2::ArchivItem_Map_Header& header =
1056
              checkedCast<const libsiedler2::ArchivItem_Map*>(map.get(0))->getHeader();
2✔
1057
            numPlayers = header.getNumPlayers();
2✔
1058
            mapinfo.title = s25util::ansiToUTF8(header.getName());
2✔
1059
        }
1060
        break;
2✔
1061
        case MapType::Savegame:
×
1062
            mapinfo.savegame = std::make_unique<Savegame>();
×
1063
            if(!mapinfo.savegame->Load(mapinfo.filepath, SaveGameDataToLoad::HeaderAndSettings))
×
1064
                return false;
×
1065

1066
            numPlayers = mapinfo.savegame->GetNumPlayers();
×
1067
            mapinfo.title = mapinfo.savegame->GetMapName();
×
1068
            break;
×
1069
        default: return false;
×
1070
    }
1071

1072
    if(GetPlayerId() >= numPlayers)
2✔
1073
        return false;
×
1074

1075
    gameLobby = std::make_shared<GameLobby>(mapinfo.type == MapType::Savegame, IsHost(), numPlayers);
2✔
1076
    return true;
2✔
1077
}
1078

1079
///////////////////////////////////////////////////////////////////////////////
1080
/// map-checksum
1081
/// @param message  Nachricht, welche ausgeführt wird
1082
bool GameClient::OnGameMessage(const GameMessage_Map_ChecksumOK& msg)
2✔
1083
{
1084
    if(!VerifyState(ConnectState::VerifyMap))
2✔
1085
        return true;
×
1086
    LOG.writeToFile("<<< NMS_MAP_CHECKSUM(%d)\n") % (msg.correct ? 1 : 0);
2✔
1087

1088
    if(msg.correct)
2✔
1089
        AdvanceState(ConnectState::QueryServerName);
2✔
1090
    else
1091
    {
1092
        gameLobby.reset();
×
1093
        if(msg.retryAllowed)
×
1094
        {
1095
            mainPlayer.sendMsgAsync(new GameMessage_MapRequest(false));
×
1096
            AdvanceState(ConnectState::ReceiveMap);
×
1097
        } else
1098
            OnError(ClientError::MapTransmission);
×
1099
    }
1100
    return true;
2✔
1101
}
1102

1103
///////////////////////////////////////////////////////////////////////////////
1104
/// server typ
1105
/// @param message  Nachricht, welche ausgeführt wird
1106
bool GameClient::OnGameMessage(const GameMessage_GGSChange& msg)
2✔
1107
{
1108
    if(state != ClientState::Config && !VerifyState(ConnectState::QuerySettings))
2✔
1109
        return true;
×
1110
    LOG.writeToFile("<<< NMS_GGS_CHANGE\n");
2✔
1111

1112
    gameLobby->getSettings() = msg.ggs;
2✔
1113

1114
    if(state == ClientState::Connect)
2✔
1115
    {
1116
        state = ClientState::Config;
2✔
1117
        AdvanceState(ConnectState::Finished);
2✔
1118
    } else if(ci)
×
1119
        ci->CI_GGSChanged(msg.ggs);
×
1120
    return true;
2✔
1121
}
1122

1123
bool GameClient::OnGameMessage(const GameMessage_RemoveLua&)
×
1124
{
1125
    if(state != ClientState::Connect && state != ClientState::Config)
×
1126
        return true;
×
1127
    mapinfo.luaFilepath.clear();
×
1128
    mapinfo.luaData.Clear();
×
1129
    mapinfo.luaChecksum = 0;
×
1130
    return true;
×
1131
}
1132

1133
///////////////////////////////////////////////////////////////////////////////
1134
/// NFC Antwort vom Server
1135
/// @param message  Nachricht, welche ausgeführt wird
1136
bool GameClient::OnGameMessage(const GameMessage_GameCommand& msg)
×
1137
{
1138
    if(nwfInfo)
×
1139
    {
1140
        if(!nwfInfo->addPlayerCmds(msg.player, msg.cmds))
×
1141
        {
1142
            LOG.write("Could not add gamecommands for player %1%. He might be cheating!\n") % unsigned(msg.player);
×
1143
            RTTR_Assert(false);
×
1144
        }
1145
    }
1146
    return true;
×
1147
}
1148

1149
void GameClient::IncreaseSpeed()
×
1150
{
1151
    const bool debugMode =
×
1152
#ifndef NDEBUG
1153
      true;
1154
#else
1155
      false;
1156
#endif
1157
    if(framesinfo.gfLengthReq > FramesInfo::milliseconds32_t(10))
×
1158
        framesinfo.gfLengthReq -= FramesInfo::milliseconds32_t(10);
×
1159
    else if((replayMode || debugMode) && framesinfo.gfLengthReq == FramesInfo::milliseconds32_t(10))
×
1160
        framesinfo.gfLengthReq = FramesInfo::milliseconds32_t(1);
×
1161
    else
1162
        framesinfo.gfLengthReq = FramesInfo::milliseconds32_t(70);
×
1163

1164
    if(replayMode)
×
1165
        framesinfo.gf_length = framesinfo.gfLengthReq;
×
1166
    else
1167
        mainPlayer.sendMsgAsync(new GameMessage_Speed(framesinfo.gfLengthReq.count()));
×
1168
}
×
1169

1170
void GameClient::DecreaseSpeed()
×
1171
{
1172
    const bool debugMode =
×
1173
#ifndef NDEBUG
1174
      true;
1175
#else
1176
      false;
1177
#endif
1178

1179
    FramesInfo::milliseconds32_t maxSpeed(replayMode ? 1000 : 70);
×
1180

1181
    if(framesinfo.gfLengthReq == maxSpeed)
×
1182
        framesinfo.gfLengthReq = FramesInfo::milliseconds32_t(replayMode || debugMode ? 1 : 10);
×
1183
    else if(framesinfo.gfLengthReq == FramesInfo::milliseconds32_t(1))
×
1184
        framesinfo.gfLengthReq = FramesInfo::milliseconds32_t(10);
×
1185
    else
1186
        framesinfo.gfLengthReq += FramesInfo::milliseconds32_t(10);
×
1187

1188
    if(replayMode)
×
1189
        framesinfo.gf_length = framesinfo.gfLengthReq;
×
1190
    else
1191
        mainPlayer.sendMsgAsync(new GameMessage_Speed(framesinfo.gfLengthReq.count()));
×
1192
}
×
1193

1194
///////////////////////////////////////////////////////////////////////////////
1195
/// NFC Done vom Server
1196
/// @param message  Nachricht, welche ausgeführt wird
1197
bool GameClient::OnGameMessage(const GameMessage_Server_NWFDone& msg)
×
1198
{
1199
    if(!nwfInfo)
×
1200
        return true;
×
1201

1202
    if(!nwfInfo->addServerInfo(NWFServerInfo(msg.gf, msg.gf_length, msg.nextNWF)))
×
1203
    {
1204
        RTTR_Assert(false);
×
1205
        LOG.write("Failed to add server info. Invalid server?\n");
1206
    }
1207

1208
    return true;
×
1209
}
1210

1211
/**
1212
 *  Pause-Nachricht von Server
1213
 *
1214
 *  @param[in] message Nachricht, welche ausgeführt wird
1215
 */
1216
bool GameClient::OnGameMessage(const GameMessage_Pause& msg)
×
1217
{
1218
    if(state != ClientState::Game)
×
1219
        return true;
×
1220
    if(framesinfo.isPaused == msg.paused)
×
1221
        return true;
×
1222
    framesinfo.isPaused = msg.paused;
×
1223

1224
    LOG.writeToFile("<<< NMS_NFC_PAUSE(%1%)\n") % msg.paused;
×
1225

1226
    if(msg.paused)
×
1227
        ci->CI_GamePaused();
×
1228
    else
1229
        ci->CI_GameResumed();
×
1230
    return true;
×
1231
}
1232

1233
/**
1234
 *  NFC GetAsyncLog von Server
1235
 *
1236
 *  @param[in] message Nachricht, welche ausgeführt wird
1237
 */
1238
bool GameClient::OnGameMessage(const GameMessage_GetAsyncLog& /*msg*/)
×
1239
{
1240
    if(state != ClientState::Game)
×
1241
        return true;
×
1242
    std::string systemInfo = System::getCompilerName() + " @ " + System::getOSName();
×
1243
    mainPlayer.sendMsgAsync(new GameMessage_AsyncLog(systemInfo));
×
1244

1245
    // AsyncLog an den Server senden
1246

1247
    std::vector<RandomEntry> async_log = RANDOM.GetAsyncLog();
×
1248

1249
    // stückeln...
1250
    std::vector<RandomEntry> part;
×
1251
    for(auto& it : async_log)
×
1252
    {
1253
        part.push_back(it);
×
1254

1255
        if(part.size() == 10)
×
1256
        {
1257
            mainPlayer.sendMsgAsync(new GameMessage_AsyncLog(part, false));
×
1258
            part.clear();
×
1259
        }
1260
    }
1261

1262
    mainPlayer.sendMsgAsync(new GameMessage_AsyncLog(part, true));
×
1263
    return true;
×
1264
}
1265

1266
///////////////////////////////////////////////////////////////////////////////
1267
/// testet ob ein Netwerkframe abgelaufen ist und führt dann ggf die Befehle aus
1268
void GameClient::ExecuteGameFrame()
×
1269
{
1270
    if(framesinfo.isPaused)
×
1271
        return; // Pause
×
1272

1273
    FramesInfo::UsedClock::time_point currentTime = FramesInfo::UsedClock::now();
×
1274

1275
    if(framesinfo.forcePauseLen.count())
×
1276
    {
1277
        if(currentTime - framesinfo.forcePauseStart > framesinfo.forcePauseLen)
×
1278
            framesinfo.forcePauseLen = FramesInfo::milliseconds32_t::zero();
×
1279
        else
1280
            return; // Pause
×
1281
    }
1282

1283
    const unsigned curGF = GetGFNumber();
×
1284
    const bool isSkipping = skiptogf > curGF;
×
1285
    // Is it time for the next GF? If we are skipping, it is always time for the next GF
1286
    if(isSkipping || (currentTime - framesinfo.lastTime) >= framesinfo.gf_length)
×
1287
    {
1288
        try
1289
        {
1290
            if(isSkipping)
×
1291
            {
1292
                // We are always in realtime
1293
                framesinfo.lastTime = currentTime;
×
1294
            } else
1295
            {
1296
                // Advance simulation time (lastTime) by 1 GF
1297
                framesinfo.lastTime += framesinfo.gf_length;
×
1298
            }
1299
            if(replayMode)
×
1300
            {
1301
                // In replay mode we have all commands in the file -> Execute them
1302
                ExecuteGameFrame_Replay();
×
1303
            } else
1304
            {
1305
                RTTR_Assert(curGF <= nwfInfo->getNextNWF());
×
1306
                bool isNWF = (curGF == nwfInfo->getNextNWF());
×
1307
                // Is it time for a NWF, handle that first
1308
                if(isNWF)
×
1309
                {
1310
                    // If a player is lagging (we did not got his commands) "pause" the game by skipping the rest of
1311
                    // this function
1312
                    // -> Don't execute GF, don't autosave etc.
1313
                    if(!nwfInfo->isReady())
×
1314
                    {
1315
                        // If a player is a few GFs behind, he will never catch up and always lag
1316
                        // Hence, pause up to 4 GFs randomly before trying again to execute this NWF
1317
                        // Do not reset frameTime or lastTime as this will mess up interpolation for drawing
1318
                        framesinfo.forcePauseStart = currentTime;
×
1319
                        framesinfo.forcePauseLen = (rand() * 4 * framesinfo.gf_length) / RAND_MAX;
×
1320
                        return;
×
1321
                    }
1322

1323
                    RTTR_Assert(nwfInfo->getServerInfo().gf == curGF);
×
1324

1325
                    ExecuteNWF();
×
1326

1327
                    FramesInfo::milliseconds32_t oldGFLen = framesinfo.gf_length;
×
1328
                    nwfInfo->execute(framesinfo);
×
1329
                    if(oldGFLen != framesinfo.gf_length)
×
1330
                    {
1331
                        LOG.write("Client: Speed changed at %1% from %2% to %3% (NWF: %4%)\n") % curGF
×
1332
                          % helpers::withUnit(oldGFLen) % helpers::withUnit(framesinfo.gf_length)
×
1333
                          % framesinfo.nwf_length;
×
1334
                    }
1335
                }
1336

1337
                NextGF(isNWF);
×
1338
                RTTR_Assert(curGF <= nwfInfo->getNextNWF());
×
1339
                HandleAutosave();
×
1340

1341
                // GF-Ende im Replay aktualisieren
1342
                if(replayinfo && replayinfo->replay.IsRecording())
×
1343
                    replayinfo->replay.UpdateLastGF(curGF);
×
1344
            }
1345

1346
        } catch(LuaExecutionError& e)
×
1347
        {
1348
            SystemChat((boost::format(_("Error during execution of lua script: %1\nGame stopped!")) % e.what()).str());
×
1349
            OnError(ClientError::InvalidMap);
×
1350
        }
1351
        if(skiptogf == GetGFNumber())
×
1352
            skiptogf = 0;
×
1353
    }
1354
    framesinfo.frameTime = std::chrono::duration_cast<FramesInfo::milliseconds32_t>(currentTime - framesinfo.lastTime);
×
1355
    // Check remaining time until next GF
1356
    if(framesinfo.frameTime >= framesinfo.gf_length)
×
1357
    {
1358
        // This can happen, if we don't call this method in intervalls less than gf_length or gf_length has changed
1359
        // TODO: Run multiple GFs per call.
1360
        // For now just make sure it is less than gf_length by skipping some simulation time,
1361
        // until we are only a bit less than 1 GF behind
1362
        // However we allow the simulation to lack behind for a few frames, so if there was a single spike we can still
1363
        // catch up in the next visual frames
1364
        using DurationType = decltype(framesinfo.gf_length);
1365
        constexpr auto maxLackFrames = 5;
×
1366

1367
        RTTR_Assert(framesinfo.gf_length > DurationType::zero());
×
1368
        const auto maxFrameTime = framesinfo.gf_length - DurationType(1);
×
1369

1370
        if(framesinfo.frameTime > maxLackFrames * framesinfo.gf_length)
×
1371
            framesinfo.lastTime += framesinfo.frameTime - maxFrameTime; // Skip simulation time until caught up
×
1372
        framesinfo.frameTime = maxFrameTime;
×
1373
    }
1374
    // This is assumed by drawing code for interpolation
1375
    RTTR_Assert(framesinfo.frameTime < framesinfo.gf_length);
×
1376
}
1377

1378
void GameClient::HandleAutosave()
×
1379
{
1380
    // If inactive or during replay -> no autosave
1381
    if(!SETTINGS.interface.autosave_interval || replayMode)
×
1382
        return;
×
1383

1384
    // Alle .... GF
1385
    if(GetGFNumber() % SETTINGS.interface.autosave_interval == 0)
×
1386
    {
1387
        std::string filename;
×
1388
        if(mapinfo.title.empty())
×
1389
            filename = std::string(_("Auto-Save")) + ".sav";
×
1390
        else
1391
            filename = mapinfo.title + " (" + _("Auto-Save") + ").sav";
×
1392

1393
        SaveToFile(RTTRCONFIG.ExpandPath(s25::folders::save) / filename);
×
1394
    }
1395
}
1396

1397
/// Führt notwendige Dinge für nächsten GF aus
1398
void GameClient::NextGF(bool wasNWF)
×
1399
{
1400
    for(AIPlayer& ai : game->aiPlayers_)
×
1401
        ai.RunGF(GetGFNumber(), wasNWF);
×
1402
    game->RunGF();
×
1403
}
×
1404

1405
void GameClient::ExecuteAllGCs(uint8_t playerId, const PlayerGameCommands& gcs)
×
1406
{
1407
    for(const gc::GameCommandPtr& gc : gcs.gcs)
×
1408
        gc->Execute(game->world_, playerId);
×
1409
}
×
1410

1411
void GameClient::SendNothingNC(uint8_t player)
×
1412
{
1413
    mainPlayer.sendMsgAsync(
×
1414
      new GameMessage_GameCommand(player, AsyncChecksum::create(*game), std::vector<gc::GameCommandPtr>()));
×
1415
}
×
1416

1417
void GameClient::WritePlayerInfo(SavedFile& file)
×
1418
{
1419
    RTTR_Assert(state == ClientState::Loading || state == ClientState::Loaded || state == ClientState::Game);
×
1420
    // Spielerdaten
1421
    for(unsigned i = 0; i < GetNumPlayers(); ++i)
×
1422
        file.AddPlayer(GetPlayer(i));
×
1423
}
×
1424

1425
void GameClient::OnGameStart()
5✔
1426
{
1427
    if(state == ClientState::Loaded)
5✔
1428
    {
1429
        GAMEMANAGER.ResetAverageGFPS();
×
1430
        framesinfo.lastTime = FramesInfo::UsedClock::now();
×
1431
        state = ClientState::Game;
×
1432
        if(ci)
×
1433
            ci->CI_GameStarted();
×
1434
    } else if(state == ClientState::Game && !game->IsStarted())
5✔
1435
    {
1436
        framesinfo.isPaused = replayMode;
×
1437
        game->Start(!!mapinfo.savegame);
×
1438
    }
1439
}
5✔
1440

1441
void GameClient::StartReplayRecording(const unsigned random_init)
×
1442
{
1443
    replayinfo = std::make_unique<ReplayInfo>();
×
1444
    replayinfo->filename = s25util::Time::FormatTime("%Y-%m-%d_%H-%i-%s") + ".rpl";
×
1445

1446
    WritePlayerInfo(replayinfo->replay);
×
1447
    replayinfo->replay.ggs = game->ggs_;
×
1448

1449
    if(!replayinfo->replay.StartRecording(RTTRCONFIG.ExpandPath(s25::folders::replays) / replayinfo->filename, mapinfo,
×
1450
                                          random_init))
1451
    {
1452
        LOG.write(_("Replayfile couldn't be opened. No replay will be recorded\n"));
×
1453
        replayinfo.reset();
×
1454
    }
1455
}
×
1456

1457
bool GameClient::StartReplay(const boost::filesystem::path& path)
×
1458
{
1459
    RTTR_Assert(state == ClientState::Stopped);
×
1460
    mapinfo.Clear();
×
1461
    replayinfo = std::make_unique<ReplayInfo>();
×
1462

1463
    if(!replayinfo->replay.LoadHeader(path) || !replayinfo->replay.LoadGameData(mapinfo)) //-V807
×
1464
    {
1465
        LOG.write(_("Invalid Replay %1%! Reason: %2%\n")) % path
×
1466
          % (replayinfo->replay.GetLastErrorMsg().empty() ? _("Unknown") : replayinfo->replay.GetLastErrorMsg());
×
1467
        OnError(ClientError::InvalidMap);
×
1468
        replayinfo.reset();
×
1469
        return false;
×
1470
    }
1471
    replayinfo->filename = path.filename();
×
1472

1473
    gameLobby = std::make_shared<GameLobby>(true, true, replayinfo->replay.GetNumPlayers());
×
1474

1475
    for(unsigned i = 0; i < replayinfo->replay.GetNumPlayers(); ++i)
×
1476
        gameLobby->getPlayer(i) = JoinPlayerInfo(replayinfo->replay.GetPlayer(i));
×
1477

1478
    bool playerFound = false;
×
1479
    // Find a player to spectate from
1480
    // First find a human player
1481
    for(unsigned char i = 0; i < gameLobby->getNumPlayers(); ++i)
×
1482
    {
1483
        if(gameLobby->getPlayer(i).ps == PlayerState::Occupied)
×
1484
        {
1485
            mainPlayer.playerId = i;
×
1486
            playerFound = true;
×
1487
            break;
×
1488
        }
1489
    }
1490
    if(!playerFound)
×
1491
    {
1492
        // If no human found, take the first AI
1493
        for(unsigned char i = 0; i < gameLobby->getNumPlayers(); ++i)
×
1494
        {
1495
            if(gameLobby->getPlayer(i).ps == PlayerState::AI)
×
1496
            {
1497
                mainPlayer.playerId = i;
×
1498
                break;
×
1499
            }
1500
        }
1501
    }
1502

1503
    // GGS-Daten
1504
    gameLobby->getSettings() = replayinfo->replay.ggs;
×
1505

1506
    switch(mapinfo.type)
×
1507
    {
1508
        default: break;
×
1509
        case MapType::OldMap:
×
1510
        {
1511
            // Richtigen Pfad zur Map erstellen
1512
            bfs::path mapFilePath = RTTRCONFIG.ExpandPath(s25::folders::mapsPlayed) / mapinfo.filepath.filename();
×
1513
            mapinfo.filepath = mapFilePath;
×
1514
            if(!mapinfo.mapData.DecompressToFile(mapinfo.filepath))
×
1515
            {
1516
                LOG.write(_("Error decompressing map file"));
×
1517
                OnError(ClientError::MapTransmission);
×
1518
                return false;
×
1519
            }
1520
            if(mapinfo.luaData.uncompressedLength)
×
1521
            {
1522
                mapinfo.luaFilepath = mapFilePath.replace_extension("lua");
×
1523
                if(!mapinfo.luaData.DecompressToFile(mapinfo.luaFilepath))
×
1524
                {
1525
                    LOG.write(_("Error decompressing lua file"));
×
1526
                    OnError(ClientError::MapTransmission);
×
1527
                    return false;
×
1528
                }
1529
            }
1530
        }
1531
        break;
×
1532
        case MapType::Savegame: break;
×
1533
    }
1534

1535
    replayMode = true;
×
1536

1537
    try
1538
    {
1539
        StartGame(replayinfo->replay.getSeed());
×
1540
    } catch(SerializedGameData::Error& error)
×
1541
    {
1542
        LOG.write(_("Error when loading game from replay: %s\n")) % error.what();
×
1543
        OnError(ClientError::InvalidMap);
×
1544
        return false;
×
1545
    }
1546

1547
    replayinfo->next_gf = replayinfo->replay.ReadGF();
×
1548

1549
    return true;
×
1550
}
1551

1552
void GameClient::SetAIBattlePlayers(std::vector<AI::Info> aiInfos)
×
1553
{
1554
    aiBattlePlayers_ = std::move(aiInfos);
×
1555
}
×
1556

1557
unsigned GameClient::GetGlobalAnimation(const unsigned short max, const unsigned char factor_numerator,
1✔
1558
                                        const unsigned char factor_denumerator, const unsigned offset)
1559
{
1560
    // Unit for animations is 630ms (dividable by 2, 3, 5, 6, 7, 10, 15, ...)
1561
    // But this also means: If framerate drops below approx. 15Hz, you won't see
1562
    // every frame of an 8-part animation anymore.
1563
    // An animation runs fully in (factor_numerator / factor_denumerator) multiples of 630ms
1564
    const unsigned unit = 630 /*ms*/ * factor_numerator / factor_denumerator;
1✔
1565
    const unsigned currenttime = std::chrono::duration_cast<FramesInfo::milliseconds32_t>(
1✔
1566
                                   (framesinfo.lastTime + framesinfo.frameTime).time_since_epoch())
1✔
1567
                                   .count();
1✔
1568
    return ((currenttime % unit) * max / unit + offset) % max;
1✔
1569
}
1570

1571
unsigned GameClient::Interpolate(unsigned max_val, const GameEvent* ev)
×
1572
{
1573
    RTTR_Assert(ev);
×
1574
    // TODO: Move to some animation system that is part of game
1575
    FramesInfo::milliseconds32_t elapsedTime;
1576
    if(state == ClientState::Game)
×
1577
        elapsedTime = (GetGFNumber() - ev->startGF) * framesinfo.gf_length + framesinfo.frameTime;
×
1578
    else
1579
        elapsedTime = FramesInfo::milliseconds32_t::zero();
×
1580
    FramesInfo::milliseconds32_t duration = ev->length * framesinfo.gf_length;
×
1581
    return helpers::interpolate(0u, max_val, elapsedTime, duration);
×
1582
}
1583

1584
int GameClient::Interpolate(int x1, int x2, const GameEvent* ev)
×
1585
{
1586
    RTTR_Assert(ev);
×
1587
    FramesInfo::milliseconds32_t elapsedTime;
1588
    if(state == ClientState::Game)
×
1589
        elapsedTime = (GetGFNumber() - ev->startGF) * framesinfo.gf_length + framesinfo.frameTime;
×
1590
    else
1591
        elapsedTime = FramesInfo::milliseconds32_t::zero();
×
1592
    FramesInfo::milliseconds32_t duration = ev->length * framesinfo.gf_length;
×
1593
    return helpers::interpolate(x1, x2, elapsedTime, duration);
×
1594
}
1595

1596
void GameClient::ServerLost()
×
1597
{
1598
    OnError(ClientError::ConnectionLost);
×
1599
    // Stop game
1600
    framesinfo.isPaused = true;
×
1601
}
×
1602

1603
/**
1604
 *  überspringt eine bestimmte Anzahl von Gameframes.
1605
 *
1606
 *  @param[in] dest_gf Zielgameframe
1607
 */
1608
void GameClient::SkipGF(unsigned gf, GameWorldView& gwv)
×
1609
{
1610
    if(gf <= GetGFNumber())
×
1611
        return;
×
1612

1613
    unsigned start_ticks = VIDEODRIVER.GetTickCount();
×
1614

1615
    if(!replayMode)
×
1616
    {
1617
        // unpause before skipping
1618
        SetPause(false);
×
1619
        mainPlayer.sendMsgAsync(new GameMessage_SkipToGF(gf));
×
1620
        return;
×
1621
    }
1622

1623
    SetPause(false);
×
1624
    skiptogf = gf;
×
1625

1626
    // GFs überspringen
1627
    for(unsigned i = GetGFNumber(); i < skiptogf; ++i)
×
1628
    {
1629
        if(i % 1000 == 0)
×
1630
        {
1631
            RoadBuildState road;
×
1632
            road.mode = RoadBuildMode::Disabled;
×
1633

1634
            // spiel aktualisieren
1635
            gwv.Draw(road, MapPoint::Invalid(), false);
×
1636

1637
            // text oben noch hinschreiben
1638
            boost::format nwfString(_("current GF: %u - still fast forwarding: %d GFs left (%d %%)"));
×
1639
            nwfString % GetGFNumber() % (gf - i) % (i * 100 / gf);
×
1640
            LargeFont->Draw(DrawPoint(VIDEODRIVER.GetRenderSize() / 2u), nwfString.str(), FontStyle::CENTER,
×
1641
                            COLOR_YELLOW);
1642

1643
            VIDEODRIVER.SwapBuffers();
×
1644
        }
1645
        ExecuteGameFrame();
×
1646
    }
1647

1648
    // Spiel pausieren & text ausgabe wie lang das jetzt gedauert hat
1649
    unsigned ticks = VIDEODRIVER.GetTickCount() - start_ticks;
×
1650
    boost::format text(_("Jump finished (%1$.3g seconds)."));
×
1651
    text % (ticks / 1000.0);
×
1652
    SystemChat(text.str());
×
1653
    SetPause(true);
×
1654
}
1655

1656
void GameClient::SystemChat(const std::string& text)
×
1657
{
1658
    SystemChat(text, GetPlayerId());
×
1659
}
×
1660

1661
void GameClient::SystemChat(const std::string& text, unsigned char fromPlayerIdx)
×
1662
{
1663
    if(ci)
×
1664
        ci->CI_Chat(fromPlayerIdx, ChatDestination::System, text);
×
1665
}
×
1666

1667
bool GameClient::SaveToFile(const boost::filesystem::path& filepath)
×
1668
{
NEW
1669
    mainPlayer.sendMsg(GameMessage_Chat(GetPlayerId(), ChatDestination::System, _("Saving game...")));
×
1670

1671
    // Mond malen
1672
    Position moonPos = VIDEODRIVER.GetMousePos();
×
1673
    moonPos.y -= 40;
×
1674
    LOADER.GetImageN("resource", 33)->DrawFull(moonPos);
×
1675
    VIDEODRIVER.SwapBuffers();
×
1676

1677
    Savegame save;
×
1678

1679
    WritePlayerInfo(save);
×
1680

1681
    // GGS-Daten
1682
    save.ggs = game->ggs_;
×
1683

1684
    save.start_gf = GetGFNumber();
×
1685

1686
    // Enable/Disable debugging of savegames
1687
    save.sgd.debugMode = SETTINGS.global.debugMode;
×
1688

1689
    try
1690
    {
1691
        // Spiel serialisieren
1692
        save.sgd.MakeSnapshot(*game);
×
1693
        // Und alles speichern
1694
        return save.Save(filepath, mapinfo.title);
×
1695
    } catch(std::exception& e)
×
1696
    {
NEW
1697
        SystemChat(std::string(_("Error during saving: ")) + e.what());
×
1698
        return false;
×
1699
    }
1700
}
1701

1702
void GameClient::ResetVisualSettings()
×
1703
{
1704
    GetPlayer(GetPlayerId()).FillVisualSettings(visual_settings);
×
1705
}
×
1706

1707
void GameClient::SetPause(bool pause)
×
1708
{
1709
    if(state == ClientState::Stopped)
×
1710
    {
1711
        // We can never continue from pause if stopped as the reason for stopping might be that the game was finished
1712
        // However we allow to pause even when stopped so we can pause after we received the stop notification
1713
        if(!pause)
×
1714
            return;
×
1715
        framesinfo.isPaused = true;
×
1716
        framesinfo.frameTime = FramesInfo::milliseconds32_t::zero();
×
1717
    } else if(replayMode)
×
1718
    {
1719
        framesinfo.isPaused = pause;
×
1720
        framesinfo.frameTime = FramesInfo::milliseconds32_t::zero();
×
1721
    } else if(IsHost())
×
1722
    {
1723
        // Pause instantly
1724
        auto* msg = new GameMessage_Pause(pause);
×
1725
        if(pause)
×
1726
            OnGameMessage(*msg);
×
1727
        mainPlayer.sendMsgAsync(msg);
×
1728
    }
1729
}
1730

1731
void GameClient::SetReplayFOW(const bool hideFOW)
×
1732
{
1733
    if(replayinfo)
×
1734
        replayinfo->all_visible = hideFOW;
×
1735
}
×
1736

1737
bool GameClient::IsReplayFOWDisabled() const
×
1738
{
1739
    return replayMode && replayinfo->all_visible;
×
1740
}
1741

1742
unsigned GameClient::GetLastReplayGF() const
×
1743
{
1744
    return replayinfo ? replayinfo->replay.GetLastGF() : 0u;
×
1745
}
1746

1747
bool GameClient::AddGC(gc::GameCommandPtr gc)
×
1748
{
1749
    // Nicht in der Pause oder wenn er besiegt wurde
1750
    if(framesinfo.isPaused || GetPlayer(GetPlayerId()).IsDefeated() || IsReplayModeOn())
×
1751
        return false;
×
1752

1753
    gameCommands_.push_back(gc);
×
1754
    return true;
×
1755
}
1756

1757
unsigned GameClient::GetNumPlayers() const
×
1758
{
1759
    RTTR_Assert(state == ClientState::Loading || state == ClientState::Loaded || state == ClientState::Game);
×
1760
    return game->world_.GetNumPlayers();
×
1761
}
1762

1763
GamePlayer& GameClient::GetPlayer(const unsigned id)
×
1764
{
1765
    RTTR_Assert(state == ClientState::Loading || state == ClientState::Loaded || state == ClientState::Game);
×
1766
    RTTR_Assert(id < GetNumPlayers());
×
1767
    return game->world_.GetPlayer(id);
×
1768
}
1769

1770
std::unique_ptr<AIPlayer> GameClient::CreateAIPlayer(unsigned playerId, const AI::Info& aiInfo)
×
1771
{
1772
    return AIFactory::Create(aiInfo, playerId, game->world_);
×
1773
}
1774

1775
/// Wandelt eine GF-Angabe in eine Zeitangabe um (HH:MM:SS oder MM:SS wenn Stunden = 0)
1776
std::string GameClient::FormatGFTime(const unsigned gf) const
5✔
1777
{
1778
    using seconds = std::chrono::duration<uint32_t, std::chrono::seconds::period>;
1779
    using hours = std::chrono::duration<uint32_t, std::chrono::hours::period>;
1780
    using minutes = std::chrono::duration<uint32_t, std::chrono::minutes::period>;
1781
    using std::chrono::duration_cast;
1782

1783
    // In Sekunden umrechnen
1784
    seconds numSeconds = duration_cast<seconds>(gf * SPEED_GF_LENGTHS[referenceSpeed]);
5✔
1785

1786
    // Angaben rausfiltern
1787
    hours numHours = duration_cast<hours>(numSeconds);
5✔
1788
    numSeconds -= numHours;
5✔
1789
    minutes numMinutes = duration_cast<minutes>(numSeconds);
5✔
1790
    numSeconds -= numMinutes;
5✔
1791

1792
    // ganze Stunden mit dabei? Dann entsprechend anderes format, ansonsten ignorieren wir die einfach
1793
    if(numHours.count())
5✔
1794
        return helpers::format("%u:%02u:%02u", numHours.count(), numMinutes.count(), numSeconds.count());
×
1795
    else
1796
        return helpers::format("%02u:%02u", numMinutes.count(), numSeconds.count());
10✔
1797
}
1798

1799
const boost::filesystem::path& GameClient::GetReplayFilename() const
×
1800
{
1801
    static boost::filesystem::path emptyString;
×
1802
    return replayinfo ? replayinfo->filename : emptyString;
×
1803
}
1804

1805
Replay* GameClient::GetReplay()
×
1806
{
1807
    return replayinfo ? &replayinfo->replay : nullptr;
×
1808
}
1809

1810
std::shared_ptr<const NWFInfo> GameClient::GetNWFInfo() const
×
1811
{
1812
    return nwfInfo;
×
1813
}
1814

1815
/// Is tournament mode activated (0 if not)? Returns the durations of the tournament mode in gf otherwise
1816
unsigned GameClient::GetTournamentModeDuration() const
×
1817
{
1818
    using namespace std::chrono;
1819
    if(game && rttr::enum_cast(game->ggs_.objective) >= rttr::enum_cast(GameObjective::Tournament1)
×
1820
       && static_cast<unsigned>(rttr::enum_cast(game->ggs_.objective))
×
1821
            < rttr::enum_cast(GameObjective::Tournament1) + NUM_TOURNAMENT_MODES)
×
1822
    {
1823
        const auto turnamentMode = rttr::enum_cast(game->ggs_.objective) - rttr::enum_cast(GameObjective::Tournament1);
×
1824
        return minutes(TOURNAMENT_MODES_DURATION[turnamentMode]) / SPEED_GF_LENGTHS[referenceSpeed];
×
1825
    } else
1826
        return 0;
×
1827
}
1828

1829
void GameClient::ToggleHumanAIPlayer(const AI::Info& aiInfo)
×
1830
{
1831
    RTTR_Assert(!IsReplayModeOn());
×
1832
    auto it = helpers::find_if(game->aiPlayers_,
×
1833
                               [id = this->GetPlayerId()](const auto& player) { return player.GetPlayerId() == id; });
×
1834
    if(it != game->aiPlayers_.end())
×
1835
        game->aiPlayers_.erase(it);
×
1836
    else
1837
        game->AddAIPlayer(CreateAIPlayer(GetPlayerId(), aiInfo));
×
1838
}
×
1839

1840
void GameClient::RequestSwapToPlayer(const unsigned char newId)
×
1841
{
1842
    if(state != ClientState::Game)
×
1843
        return;
×
1844
    GamePlayer& player = GetPlayer(newId);
×
1845
    if(player.ps == PlayerState::AI && player.aiInfo.type == AI::Type::Dummy)
×
1846
        mainPlayer.sendMsgAsync(new GameMessage_Player_Swap(0xFF, newId));
×
1847
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc