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

Return-To-The-Roots / s25client / 27752624950

18 Jun 2026 10:15AM UTC coverage: 50.412% (-0.3%) from 50.749%
27752624950

Pull #1948

github

web-flow
Merge e68942c98 into f299328f2
Pull Request #1948: Hunter: Use GetPointsInRadius() instead of square

15 of 19 new or added lines in 3 files covered. (78.95%)

508 existing lines in 4 files now uncovered.

23186 of 45993 relevant lines covered (50.41%)

42928.47 hits per line

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

23.21
/libs/s25main/network/GameClient.cpp
1
// Copyright (C) 2005 - 2026 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 "LeatherLoader.h"
15
#include "Loader.h"
16
#include "NWFInfo.h"
17
#include "PlayerGameCommands.h"
18
#include "RTTR_Version.h"
19
#include "ReplayInfo.h"
20
#include "RttrConfig.h"
21
#include "Savegame.h"
22
#include "SerializedGameData.h"
23
#include "Settings.h"
24
#include "ai/AIPlayer.h"
25
#include "drivers/VideoDriverWrapper.h"
26
#include "factories/AIFactory.h"
27
#include "files.h"
28
#include "helpers/containerUtils.h"
29
#include "helpers/format.hpp"
30
#include "helpers/mathFuncs.h"
31
#include "lua/LuaInterfaceBase.h"
32
#include "network/ClientInterface.h"
33
#include "network/GameMessages.h"
34
#include "network/GameServer.h"
35
#include "ogl/FontStyle.h"
36
#include "ogl/glArchivItem_Bitmap.h"
37
#include "ogl/glFont.h"
38
#include "random/Random.h"
39
#include "random/randomIO.h"
40
#include "world/GameWorld.h"
41
#include "world/GameWorldView.h"
42
#include "world/MapLoader.h"
43
#include "gameTypes/RoadBuildState.h"
44
#include "gameData/GameConsts.h"
45
#include "gameData/PortraitConsts.h"
46
#include "libsiedler2/ArchivItem_Map.h"
47
#include "libsiedler2/ArchivItem_Map_Header.h"
48
#include "libsiedler2/prototypen.h"
49
#include "s25util/SocketSet.h"
50
#include "s25util/StringConversion.h"
51
#include "s25util/System.h"
52
#include "s25util/fileFuncs.h"
53
#include "s25util/strFuncs.h"
54
#include "s25util/utf8.h"
55
#include <boost/filesystem.hpp>
56
#include <helpers/chronoIO.h>
57
#include <memory>
58

59
namespace {
60
constexpr bool DEBUG_MODE =
61
#ifndef NDEBUG
62
  true;
63
#else
64
  false;
65
#endif
66
void copyFileIfPathDifferent(const boost::filesystem::path& src_path, const boost::filesystem::path& dst_path)
×
67
{
68
    if(src_path != dst_path)
×
69
    {
70
        boost::system::error_code ignoredEc;
×
71
        constexpr auto overwrite_existing =
×
72
#if BOOST_VERSION >= 107400
73
          boost::filesystem::copy_options::overwrite_existing;
74
#else
75
          boost::filesystem::copy_option::overwrite_if_exists;
76
#endif
77
        copy_file(src_path, dst_path, overwrite_existing, ignoredEc);
×
78
    }
79
}
×
80
} // namespace
81

82
void GameClient::ClientConfig::Clear()
23✔
83
{
84
    server.clear();
23✔
85
    gameName.clear();
23✔
86
    password.clear();
23✔
87
    port = 0;
23✔
88
    isHost = false;
23✔
89
}
23✔
90

91
GameClient::GameClient() : skiptogf(0), mainPlayer(0), state(ClientState::Stopped), ci(nullptr), replayMode(false) {}
16✔
92

93
GameClient::~GameClient()
19✔
94
{
95
    Stop();
16✔
96
}
19✔
97

98
/**
99
 *  Verbindet den Client mit einem Server
100
 *
101
 *  @param server    Hostname des Zielrechners
102
 *  @param password  Passwort des Spieles
103
 *  @param servertyp Servertyp des Spieles (Direct/LAN/usw)
104
 *  @param host      gibt an ob wir selbst der Host sind
105
 *
106
 *  @return true, wenn Client erfolgreich verbunden und gestartet
107
 */
108
bool GameClient::Connect(const std::string& server, const std::string& password, ServerType servertyp,
7✔
109
                         unsigned short port, bool host, bool use_ipv6)
110
{
111
    Stop();
7✔
112

113
    RTTR_Assert(aiBattlePlayers_.empty());
7✔
114

115
    // Name und Password kopieren
116
    clientconfig.server = server;
7✔
117
    clientconfig.password = password;
7✔
118

119
    clientconfig.servertyp = servertyp;
7✔
120
    clientconfig.port = port;
7✔
121
    clientconfig.isHost = host;
7✔
122

123
    // Verbinden
124
    if(!mainPlayer.socket.Connect(server, port, use_ipv6, SETTINGS.proxy))
7✔
125
    {
126
        LOG.write("GameClient::Connect: ERROR: Connect failed!\n");
×
127
        if(host)
×
128
            GAMESERVER.Stop();
×
129
        return false;
×
130
    }
131

132
    state = ClientState::Connect;
7✔
133
    AdvanceState(ConnectState::Initiated);
7✔
134

135
    // Es wird kein Replay abgespielt, sondern dies ist ein richtiges Spiel
136
    replayMode = false;
7✔
137

138
    return true;
7✔
139
}
140

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

155
/**
156
 *  Hauptschleife des Clients
157
 */
158
void GameClient::Run()
×
159
{
160
    if(state == ClientState::Stopped)
×
161
        return;
×
162

163
    SocketSet set;
×
164

165
    // erstmal auf Daten überprüfen
166
    set.Clear();
×
167

168
    // zum set hinzufügen
169
    set.Add(mainPlayer.socket);
×
170
    if(set.Select(0, 0) > 0)
×
171
    {
172
        // nachricht empfangen
173
        if(!mainPlayer.receiveMsgs())
×
174
        {
175
            LOG.write("Receiving Message from server failed\n");
×
176
            ServerLost();
×
177
        }
178
    }
179

180
    // nun auf Fehler prüfen
181
    set.Clear();
×
182

183
    // zum set hinzufügen
184
    set.Add(mainPlayer.socket);
×
185

186
    // auf fehler prüfen
187
    if(set.Select(0, 2) > 0)
×
188
    {
189
        if(set.InSet(mainPlayer.socket))
×
190
        {
191
            // Server ist weg
192
            LOG.write("Error on socket to server\n");
×
193
            ServerLost();
×
194
        }
195
    }
196

197
    if(state == ClientState::Loaded)
×
198
    {
199
        // All players ready?
200
        if(nwfInfo->isReady())
×
201
            OnGameStart();
×
202
    } else if(state == ClientState::Game)
×
203
        ExecuteGameFrame();
×
204

205
    // maximal 10 Pakete verschicken
206
    mainPlayer.sendMsgs(10);
×
207

208
    mainPlayer.executeMsgs(*this);
×
209
}
210

211
/**
212
 *  Stoppt das Spiel
213
 */
214
void GameClient::Stop()
28✔
215
{
216
    if(state == ClientState::Stopped)
28✔
217
        return;
21✔
218

219
    if(game)
7✔
220
        ExitGame();
×
221
    else if(state == ClientState::Connect || state == ClientState::Config)
7✔
222
        gameLobby.reset();
7✔
223

224
    if(IsHost())
7✔
225
        GAMESERVER.Stop();
×
226

227
    framesinfo.Clear();
7✔
228
    clientconfig.Clear();
7✔
229
    mapinfo.Clear();
7✔
230

231
    if(replayinfo)
7✔
232
    {
233
        if(replayinfo->replay.IsRecording())
×
234
            replayinfo->replay.StopRecording();
×
235
        replayinfo->replay.Close();
×
236
        replayinfo.reset();
×
237
    }
238

239
    mainPlayer.closeConnection();
7✔
240

241
    // clear jump target
242
    skiptogf = 0;
7✔
243

244
    // Consistency check: No game, no lobby remaining
245
    RTTR_Assert(!game);
7✔
246
    RTTR_Assert(!gameLobby);
7✔
247

248
    state = ClientState::Stopped;
7✔
249
    LOG.write("client state changed to stop\n");
7✔
250

251
    aiBattlePlayers_.clear();
7✔
252
}
253

254
std::shared_ptr<GameLobby> GameClient::GetGameLobby()
1✔
255
{
256
    RTTR_Assert(state != ClientState::Config || gameLobby);
1✔
257
    return gameLobby;
1✔
258
}
259

260
const AIPlayer* GameClient::GetAIPlayer(unsigned id) const
×
261
{
262
    if(!game)
×
263
        return nullptr;
×
264
    return game->GetAIPlayer(id);
×
265
}
266

267
/**
268
 *  Startet ein Spiel oder Replay.
269
 *
270
 *  @param[in] random_init Initialwert des Zufallsgenerators.
271
 */
272
void GameClient::StartGame(const unsigned random_init)
×
273
{
274
    RTTR_Assert(state == ClientState::Config || (state == ClientState::Stopped && replayMode));
×
275

276
    // Mond malen
277
    Position moonPos = VIDEODRIVER.GetMousePos();
×
278
    moonPos.y -= 40;
×
279
    LOADER.GetImageN("resource", 33)->DrawFull(moonPos);
×
280
    VIDEODRIVER.SwapBuffers();
×
281

282
    // Start in pause mode
283
    framesinfo.isPaused = true;
×
284

285
    // Je nach Geschwindigkeit GF-Länge einstellen
286
    framesinfo.gf_length = SPEED_GF_LENGTHS[gameLobby->getSettings().speed];
×
287
    framesinfo.gfLengthReq = framesinfo.gf_length;
×
288

289
    // Random-Generator initialisieren
290
    RANDOM.Init(random_init);
×
291

292
    if(!IsReplayModeOn() && mapinfo.savegame && !mapinfo.savegame->Load(mapinfo.filepath, SaveGameDataToLoad::All))
×
293
    {
294
        OnError(ClientError::InvalidMap);
×
295
        return;
×
296
    }
297

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

315
    state = ClientState::Loading;
×
316

317
    if(ci)
×
318
        ci->CI_GameLoading(game);
×
319

320
    // Get standard settings before they get overwritten
321
    GetPlayer(GetPlayerId()).FillVisualSettings(default_settings);
×
322

323
    GameWorld& gameWorld = game->world_;
×
324
    if(mapinfo.savegame)
×
325
        mapinfo.savegame->sgd.ReadSnapshot(*game, *this);
×
326
    else
327
    {
328
        RTTR_Assert(mapinfo.type != MapType::Savegame);
×
329
        /// Startbündnisse setzen
330
        for(unsigned i = 0; i < gameWorld.GetNumPlayers(); ++i)
×
331
            gameWorld.GetPlayer(i).MakeStartPacts();
×
332

333
        MapLoader loader(gameWorld);
×
334
        if(!loader.Load(mapinfo.filepath)
×
335
           || (!mapinfo.luaFilepath.empty() && !loader.LoadLuaScript(*game, *this, mapinfo.luaFilepath)))
×
336
        {
337
            OnError(ClientError::InvalidMap);
×
338
            return;
×
339
        }
340
        // TODO (Replay): Always use true
341
        const bool fixFish = !GetReplay() || GetReplay()->GetMinorVersion() >= 3;
×
342
        MapLoader::SetupResources(gameWorld, fixFish);
×
343
    }
344
    gameWorld.InitAfterLoad();
×
345

346
    // Update visual settings
UNCOV
347
    ResetVisualSettings();
×
348

349
    if(!replayMode)
×
350
    {
351
        RTTR_Assert(!replayinfo);
×
UNCOV
352
        StartReplayRecording(random_init);
×
353
    }
354

355
    // Daten nach dem Schreiben des Replays ggf wieder löschen
UNCOV
356
    mapinfo.mapData.Clear();
×
357
}
358

UNCOV
359
void GameClient::GameLoaded()
×
360
{
361
    RTTR_Assert(state == ClientState::Loading);
×
362

363
    state = ClientState::Loaded;
×
364

365
    if(replayMode)
×
UNCOV
366
        OnGameStart();
×
367
    else
368
    {
369
        // Notify server that we are ready
UNCOV
370
        if(IsHost())
×
371
        {
372
            for(unsigned id = 0; id < GetNumPlayers(); id++)
×
373
            {
374
                if(GetPlayer(id).ps == PlayerState::AI)
×
375
                {
376
                    game->AddAIPlayer(CreateAIPlayer(id, GetPlayer(id).aiInfo));
×
UNCOV
377
                    SendNothingNC(id);
×
378
                }
379
            }
UNCOV
380
            if(IsAIBattleModeOn())
×
UNCOV
381
                ToggleHumanAIPlayer(aiBattlePlayers_[GetPlayerId()]);
×
382
        }
383
        SendNothingNC();
×
384
    }
385
}
×
386

387
void GameClient::ExitGame()
×
388
{
389
    RTTR_Assert(state == ClientState::Game || state == ClientState::Loaded || state == ClientState::Loading);
×
UNCOV
390
    game.reset();
×
391
    nwfInfo.reset();
×
392
    // Clear remaining commands
393
    gameCommands_.clear();
×
UNCOV
394
}
×
395

396
unsigned GameClient::GetGFNumber() const
×
397
{
398
    return game->em_->GetCurrentGF();
×
399
}
400

401
/**
402
 *  Ping-Nachricht.
403
 */
UNCOV
404
bool GameClient::OnGameMessage(const GameMessage_Ping& /*msg*/)
×
405
{
406
    mainPlayer.sendMsgAsync(new GameMessage_Pong());
×
UNCOV
407
    return true;
×
408
}
409

410
/**
411
 *  Player-ID-Nachricht.
412
 */
413
bool GameClient::OnGameMessage(const GameMessage_Player_Id& msg)
7✔
414
{
415
    if(!VerifyState(ConnectState::Initiated))
7✔
UNCOV
416
        return true;
×
417
    // haben wir eine ungültige ID erhalten? (aka Server-Voll)
418
    if(msg.player == GameMessageWithPlayer::NO_PLAYER_ID)
7✔
419
    {
UNCOV
420
        OnError(ClientError::ServerFull);
×
UNCOV
421
        return true;
×
422
    }
423

424
    mainPlayer.playerId = msg.player;
7✔
425

426
    // Server-Typ senden
427
    mainPlayer.sendMsgAsync(new GameMessage_Server_Type(clientconfig.servertyp, rttr::version::GetRevision()));
7✔
428
    AdvanceState(ConnectState::VerifyServer);
7✔
429
    return true;
7✔
430
}
431

432
/**
433
 *  Player-List-Nachricht.
434
 */
435
bool GameClient::OnGameMessage(const GameMessage_Player_List& msg)
4✔
436
{
437
    if(state != ClientState::Config && !VerifyState(ConnectState::QueryPlayerList))
4✔
UNCOV
438
        return true;
×
439
    RTTR_Assert(gameLobby);
4✔
440
    RTTR_Assert(gameLobby->getNumPlayers() == msg.playerInfos.size());
4✔
441
    if(gameLobby->getNumPlayers() != msg.playerInfos.size())
4✔
442
    {
UNCOV
443
        OnError(ClientError::InvalidMessage);
×
UNCOV
444
        return true;
×
445
    }
446

447
    for(unsigned i = 0; i < gameLobby->getNumPlayers(); ++i)
16✔
448
        gameLobby->getPlayer(i) = msg.playerInfos[i];
12✔
449

450
    if(state == ClientState::Connect)
4✔
451
        AdvanceState(ConnectState::QuerySettings);
4✔
452
    return true;
4✔
453
}
454

UNCOV
455
bool GameClient::OnGameMessage(const GameMessage_Player_Name& msg)
×
456
{
457
    if(state != ClientState::Config)
×
UNCOV
458
        return true;
×
459
    if(msg.player >= gameLobby->getNumPlayers())
×
460
        return true;
×
461
    gameLobby->getPlayer(msg.player).name = msg.playername;
×
462
    if(ci)
×
463
        ci->CI_PlayerDataChanged(msg.player);
×
464
    return true;
×
465
}
466

UNCOV
467
bool GameClient::OnGameMessage(const GameMessage_Player_Portrait& msg)
×
468
{
469
    if(state != ClientState::Config)
×
UNCOV
470
        return true;
×
471
    if(msg.player >= gameLobby->getNumPlayers())
×
472
        return true;
×
473
    if(msg.playerPortraitIndex >= Portraits.size())
×
474
        return true;
×
475
    gameLobby->getPlayer(msg.player).portraitIndex = msg.playerPortraitIndex;
×
476
    if(ci)
×
477
        ci->CI_PlayerDataChanged(msg.player);
×
478
    return true;
×
479
}
480

481
///////////////////////////////////////////////////////////////////////////////
482
/// player joined
483
/// @param message  Nachricht, welche ausgeführt wird
UNCOV
484
bool GameClient::OnGameMessage(const GameMessage_Player_New& msg)
×
485
{
486
    if(state != ClientState::Config)
×
UNCOV
487
        return true;
×
488

489
    if(msg.player >= gameLobby->getNumPlayers())
×
UNCOV
490
        return true;
×
491

492
    JoinPlayerInfo& playerInfo = gameLobby->getPlayer(msg.player);
×
493

494
    playerInfo.name = msg.name;
×
UNCOV
495
    playerInfo.ps = PlayerState::Occupied;
×
496
    playerInfo.ping = 0;
×
497

498
    if(ci)
×
UNCOV
499
        ci->CI_NewPlayer(msg.player);
×
500
    return true;
×
501
}
502

UNCOV
503
bool GameClient::OnGameMessage(const GameMessage_Player_Ping& msg)
×
504
{
505
    if(state == ClientState::Config)
×
506
    {
507
        if(msg.player >= gameLobby->getNumPlayers())
×
UNCOV
508
            return true;
×
509
        gameLobby->getPlayer(msg.player).ping = msg.ping;
×
510
    } else if(state == ClientState::Loading || state == ClientState::Loaded || state == ClientState::Game)
×
511
    {
512
        if(msg.player >= GetNumPlayers())
×
UNCOV
513
            return true;
×
514
        GetPlayer(msg.player).ping = msg.ping;
×
515
    } else
516
    {
UNCOV
517
        RTTR_Assert(false);
×
518
        return true;
519
    }
520

UNCOV
521
    if(ci)
×
UNCOV
522
        ci->CI_PingChanged(msg.player, msg.ping);
×
523
    return true;
×
524
}
525

526
/**
527
 *  Player-Toggle-State-Nachricht.
528
 */
UNCOV
529
bool GameClient::OnGameMessage(const GameMessage_Player_State& msg)
×
530
{
531
    if(state != ClientState::Config)
×
UNCOV
532
        return true;
×
533

534
    if(msg.player >= gameLobby->getNumPlayers())
×
UNCOV
535
        return true;
×
536

537
    JoinPlayerInfo& playerInfo = gameLobby->getPlayer(msg.player);
×
UNCOV
538
    bool wasUsed = playerInfo.isUsed();
×
539
    playerInfo.ps = msg.ps;
×
540
    playerInfo.aiInfo = msg.aiInfo;
×
541

542
    if(ci)
×
543
    {
544
        if(playerInfo.isUsed())
×
UNCOV
545
            ci->CI_NewPlayer(msg.player);
×
546
        else if(wasUsed)
×
547
            ci->CI_PlayerLeft(msg.player);
×
548
        else
549
            ci->CI_PlayerDataChanged(msg.player);
×
550
    }
551
    return true;
×
552
}
553

554
///////////////////////////////////////////////////////////////////////////////
555
/// nation button gedrückt
556
/// @param message  Nachricht, welche ausgeführt wird
UNCOV
557
bool GameClient::OnGameMessage(const GameMessage_Player_Nation& msg)
×
558
{
559
    if(state != ClientState::Config)
×
UNCOV
560
        return true;
×
561

562
    if(msg.player >= gameLobby->getNumPlayers())
×
UNCOV
563
        return true;
×
564

565
    gameLobby->getPlayer(msg.player).nation = msg.nation;
×
566

567
    if(ci)
×
UNCOV
568
        ci->CI_PlayerDataChanged(msg.player);
×
569
    return true;
×
570
}
571

572
///////////////////////////////////////////////////////////////////////////////
573
/// team button gedrückt
574
/// @param message  Nachricht, welche ausgeführt wird
UNCOV
575
bool GameClient::OnGameMessage(const GameMessage_Player_Team& msg)
×
576
{
577
    if(state != ClientState::Config)
×
UNCOV
578
        return true;
×
579

580
    if(msg.player >= gameLobby->getNumPlayers())
×
UNCOV
581
        return true;
×
582

583
    gameLobby->getPlayer(msg.player).team = msg.team;
×
584

585
    if(ci)
×
UNCOV
586
        ci->CI_PlayerDataChanged(msg.player);
×
587
    return true;
×
588
}
589

590
///////////////////////////////////////////////////////////////////////////////
591
/// color button gedrückt
592
/// @param message  Nachricht, welche ausgeführt wird
UNCOV
593
bool GameClient::OnGameMessage(const GameMessage_Player_Color& msg)
×
594
{
595
    if(state != ClientState::Config)
×
UNCOV
596
        return true;
×
597

598
    if(msg.player >= gameLobby->getNumPlayers())
×
UNCOV
599
        return true;
×
600

601
    gameLobby->getPlayer(msg.player).color = msg.color;
×
602

603
    if(ci)
×
UNCOV
604
        ci->CI_PlayerDataChanged(msg.player);
×
605
    return true;
×
606
}
607

608
/**
609
 *  Ready-state eines Spielers hat sich geändert.
610
 *
611
 *  @param[in] message Nachricht, welche ausgeführt wird
612
 */
UNCOV
613
bool GameClient::OnGameMessage(const GameMessage_Player_Ready& msg)
×
614
{
615
    if(state != ClientState::Config)
×
UNCOV
616
        return true;
×
617

618
    if(msg.player >= gameLobby->getNumPlayers())
×
UNCOV
619
        return true;
×
620

621
    gameLobby->getPlayer(msg.player).isReady = msg.ready;
×
622

623
    if(ci)
×
UNCOV
624
        ci->CI_ReadyChanged(msg.player, msg.ready);
×
625
    return true;
×
626
}
627

628
///////////////////////////////////////////////////////////////////////////////
629
/// player gekickt
630
/// @param message  Nachricht, welche ausgeführt wird
UNCOV
631
bool GameClient::OnGameMessage(const GameMessage_Player_Kicked& msg)
×
632
{
633
    if(state == ClientState::Config)
×
634
    {
635
        if(msg.player >= gameLobby->getNumPlayers())
×
UNCOV
636
            return true;
×
637
        gameLobby->getPlayer(msg.player).ps = PlayerState::Free;
×
638
    } else if(state == ClientState::Loading || state == ClientState::Loaded || state == ClientState::Game)
×
639
    {
640
        // Im Spiel anzeigen, dass der Spieler das Spiel verlassen hat
UNCOV
641
        GamePlayer& player = GetPlayer(msg.player);
×
UNCOV
642
        if(player.ps != PlayerState::AI)
×
643
        {
644
            player.ps = PlayerState::AI;
×
UNCOV
645
            player.aiInfo = AI::Info(AI::Type::Dummy);
×
646
            // Host has to handle it
647
            if(IsHost())
×
648
            {
649
                game->AddAIPlayer(CreateAIPlayer(msg.player, player.aiInfo));
×
UNCOV
650
                SendNothingNC(msg.player);
×
651
            }
652
        }
×
653
    } else
654
        return true;
×
655

656
    if(ci)
×
UNCOV
657
        ci->CI_PlayerLeft(msg.player);
×
658
    return true;
×
659
}
660

UNCOV
661
bool GameClient::OnGameMessage(const GameMessage_Player_Swap& msg)
×
662
{
663
    LOG.writeToFile("<<< NMS_PLAYER_SWAP(%u, %u)\n") % unsigned(msg.player) % unsigned(msg.player2);
×
664

665
    if(state == ClientState::Config)
×
666
    {
667
        if(msg.player >= gameLobby->getNumPlayers() || msg.player2 >= gameLobby->getNumPlayers())
×
UNCOV
668
            return true;
×
669

670
        // During preparation just swap the players
671
        using std::swap;
UNCOV
672
        swap(gameLobby->getPlayer(msg.player), gameLobby->getPlayer(msg.player2));
×
673
        // Some things cannot be changed in savegames
674
        if(mapinfo.type == MapType::Savegame)
×
UNCOV
675
            gameLobby->getPlayer(msg.player).FixSwappedSaveSlot(gameLobby->getPlayer(msg.player2));
×
676

677
        // Evtl. sind wir betroffen?
UNCOV
678
        if(mainPlayer.playerId == msg.player)
×
UNCOV
679
            mainPlayer.playerId = msg.player2;
×
680
        else if(mainPlayer.playerId == msg.player2)
×
681
            mainPlayer.playerId = msg.player;
×
682

683
        if(ci)
×
UNCOV
684
            ci->CI_PlayersSwapped(msg.player, msg.player2);
×
685
    } else if(state == ClientState::Loading || state == ClientState::Loaded || state == ClientState::Game)
×
686
        ChangePlayerIngame(msg.player, msg.player2);
×
687
    else
688
        return true;
×
UNCOV
689
    mainPlayer.sendMsgAsync(new GameMessage_Player_SwapConfirm(msg.player, msg.player2));
×
690
    return true;
×
691
}
692

693
/**
694
 *  Server-Typ-Nachricht.
695
 */
696
bool GameClient::OnGameMessage(const GameMessage_Server_TypeOK& msg)
7✔
697
{
698
    if(!VerifyState(ConnectState::VerifyServer))
7✔
UNCOV
699
        return true;
×
700

701
    using StatusCode = GameMessage_Server_TypeOK::StatusCode;
702
    switch(msg.err_code)
7✔
703
    {
704
        case StatusCode::Ok: break;
7✔
705

UNCOV
706
        default:
×
707
        case StatusCode::InvalidServerType:
708
        {
UNCOV
709
            OnError(ClientError::InvalidServerType);
×
UNCOV
710
            return true;
×
711
        }
712
        break;
713

UNCOV
714
        case StatusCode::WrongVersion:
×
715
        {
716
            LOG.write(_("Version mismatch. Server version: %1%, your version %2%")) % msg.version
×
UNCOV
717
              % rttr::version::GetRevision();
×
718
            OnError(ClientError::WrongVersion);
×
719
            return true;
×
720
        }
721
        break;
722
    }
723

724
    mainPlayer.sendMsgAsync(new GameMessage_Server_Password(clientconfig.password));
7✔
725

726
    AdvanceState(ConnectState::QueryPw);
7✔
727
    return true;
7✔
728
}
729

730
/**
731
 *  Server-Passwort-Nachricht.
732
 */
733
bool GameClient::OnGameMessage(const GameMessage_Server_Password& msg)
7✔
734
{
735
    if(!VerifyState(ConnectState::QueryPw))
7✔
UNCOV
736
        return true;
×
737

738
    if(msg.password != "true")
7✔
739
    {
740
        OnError(ClientError::WrongPassword);
1✔
741
        return true;
1✔
742
    }
743

744
    mainPlayer.sendMsgAsync(new GameMessage_Player_Name(0xFF, SETTINGS.lobby.name));
6✔
745
    mainPlayer.sendMsgAsync(new GameMessage_Player_Portrait(0xFF, SETTINGS.lobby.portraitIndex));
6✔
746
    mainPlayer.sendMsgAsync(new GameMessage_MapRequest(true));
6✔
747

748
    AdvanceState(ConnectState::QueryMapInfo);
6✔
749
    return true;
6✔
750
}
751

752
/**
753
 *  Server-Name-Nachricht.
754
 */
755
bool GameClient::OnGameMessage(const GameMessage_Server_Name& msg)
4✔
756
{
757
    if(!VerifyState(ConnectState::QueryServerName))
4✔
UNCOV
758
        return true;
×
759
    clientconfig.gameName = msg.name;
4✔
760

761
    AdvanceState(ConnectState::QueryPlayerList);
4✔
762
    return true;
4✔
763
}
764

765
/**
766
 *  Server-Start-Nachricht
767
 */
UNCOV
768
bool GameClient::OnGameMessage(const GameMessage_Server_Start& msg)
×
769
{
770
    if(state != ClientState::Config)
×
UNCOV
771
        return true;
×
772

773
    nwfInfo = std::make_shared<NWFInfo>();
×
UNCOV
774
    nwfInfo->init(msg.firstNwf, msg.cmdDelay);
×
775
    try
776
    {
UNCOV
777
        StartGame(msg.random_init);
×
UNCOV
778
    } catch(const SerializedGameData::Error& error)
×
779
    {
780
        LOG.write("Error when loading game: %s\n") % error.what();
×
UNCOV
781
        Stop();
×
782
        GAMEMANAGER.ShowMenu();
×
783
    }
784
    return true;
×
785
}
786

787
/**
788
 *  Server-Chat-Nachricht.
789
 */
UNCOV
790
bool GameClient::OnGameMessage(const GameMessage_Chat& msg)
×
791
{
792
    if(msg.destination == ChatDestination::System)
×
793
    {
794
        SystemChat(msg.text, (msg.player < game->world_.GetNumPlayers()) ? msg.player : GetPlayerId());
×
UNCOV
795
        return true;
×
796
    }
797
    if(state == ClientState::Game)
×
798
    {
799
        // Ingame message: Do some checking and logging
UNCOV
800
        if(msg.player >= game->world_.GetNumPlayers())
×
UNCOV
801
            return true;
×
802

803
        /// Mit im Replay aufzeichnen
UNCOV
804
        if(replayinfo && replayinfo->replay.IsRecording())
×
UNCOV
805
            replayinfo->replay.AddChatCommand(GetGFNumber(), msg.player, msg.destination, msg.text);
×
806

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

809
        // Besiegte dürfen nicht mehr heimlich mit Verbüdeten oder Feinden reden
UNCOV
810
        if(player.IsDefeated() && msg.destination != ChatDestination::All)
×
UNCOV
811
            return true;
×
812

813
        const auto isValidRecipient = [&msg, &player](const unsigned playerId) {
×
814
            // Always send to self
815
            if(msg.player == playerId)
×
UNCOV
816
                return true;
×
817
            switch(msg.destination)
×
818
            {
819
                case ChatDestination::System:
×
UNCOV
820
                case ChatDestination::All: return true;
×
821
                case ChatDestination::Allies: return player.IsAlly(playerId);
×
822
                case ChatDestination::Enemies: return !player.IsAlly(playerId);
×
823
            }
824
            return true; // LCOV_EXCL_LINE
UNCOV
825
        };
×
UNCOV
826
        for(AIPlayer& ai : game->aiPlayers_)
×
827
        {
828
            if(isValidRecipient(ai.GetPlayerId()))
×
UNCOV
829
                ai.OnChatMessage(msg.player, msg.destination, msg.text);
×
830
        }
831

UNCOV
832
        if(!isValidRecipient(GetPlayerId()))
×
UNCOV
833
            return true;
×
834
    } else if(state == ClientState::Config)
×
835
    {
836
        // GameLobby message: Just check for valid player
UNCOV
837
        if(msg.player >= gameLobby->getNumPlayers())
×
UNCOV
838
            return true;
×
839
    } else
840
        return true;
×
841

842
    if(ci)
×
UNCOV
843
        ci->CI_Chat(msg.player, msg.destination, msg.text);
×
844
    return true;
×
845
}
846

847
/**
848
 *  Server-Async-Nachricht.
849
 */
UNCOV
850
bool GameClient::OnGameMessage(const GameMessage_Server_Async& msg)
×
851
{
852
    if(state != ClientState::Game)
×
UNCOV
853
        return true;
×
854

855
    // Liste mit Namen und Checksummen erzeugen
UNCOV
856
    std::stringstream checksum_list;
×
UNCOV
857
    for(unsigned i = 0; i < msg.checksums.size(); ++i)
×
858
    {
859
        checksum_list << GetPlayer(i).name << ": " << msg.checksums[i];
×
UNCOV
860
        if(i + 1 < msg.checksums.size())
×
861
            checksum_list << ", ";
×
862
    }
863

864
    // Fehler ausgeben (Konsole)!
UNCOV
865
    LOG.write(_("The Game is not in sync. Checksums of some players don't match."));
×
UNCOV
866
    LOG.write("\n%1%\n") % checksum_list.str();
×
867

868
    // Messenger im Game
UNCOV
869
    if(ci)
×
UNCOV
870
        ci->CI_Async(checksum_list.str());
×
871

872
    std::string fileName = s25util::Time::FormatTime("async_%Y-%m-%d_%H-%i-%s");
×
UNCOV
873
    fileName += "_" + s25util::toStringClassic(GetPlayerId()) + "_";
×
874
    fileName += GetPlayer(GetPlayerId()).name;
×
875

876
    const bfs::path filePathSave = RTTRCONFIG.ExpandPath(s25::folders::save) / makePortableFileName(fileName + ".sav");
×
877
    const bfs::path filePathLog =
878
      RTTRCONFIG.ExpandPath(s25::folders::logs) / makePortableFileName(fileName + "Player.log");
×
UNCOV
879
    saveRandomLog(filePathLog, RANDOM.GetAsyncLog());
×
880
    SaveToFile(filePathSave);
×
881
    LOG.write(_("Async log saved at %1%,\ngame saved at %2%\n")) % filePathLog % filePathSave;
×
882
    return true;
×
883
}
884

885
/**
886
 *  Server-Countdown-Nachricht.
887
 */
UNCOV
888
bool GameClient::OnGameMessage(const GameMessage_Countdown& msg)
×
889
{
890
    if(state != ClientState::Config)
×
UNCOV
891
        return true;
×
892
    if(ci)
×
893
        ci->CI_Countdown(msg.countdown);
×
894
    return true;
×
895
}
896

897
/**
898
 *  Server-Cancel-Countdown-Nachricht.
899
 */
UNCOV
900
bool GameClient::OnGameMessage(const GameMessage_CancelCountdown& msg)
×
901
{
902
    if(state != ClientState::Config)
×
UNCOV
903
        return true;
×
904
    if(ci)
×
905
        ci->CI_CancelCountdown(msg.error);
×
906
    return true;
×
907
}
908

909
/**
910
 *  verarbeitet die MapInfo-Nachricht, in der die gepackte Größe,
911
 *  die normale Größe und Teilanzahl der Karte übertragen wird.
912
 *
913
 *  @param message Nachricht, welche ausgeführt wird
914
 */
915
bool GameClient::OnGameMessage(const GameMessage_Map_Info& msg)
6✔
916
{
917
    if(!VerifyState(ConnectState::QueryMapInfo))
6✔
UNCOV
918
        return true;
×
919

920
    // full path
921
    const std::string portFilename = makePortableFileName(msg.filename);
12✔
922
    if(portFilename.empty())
6✔
923
    {
UNCOV
924
        LOG.write("Invalid filename received!\n");
×
UNCOV
925
        OnError(ClientError::InvalidMap);
×
926
        return true;
×
927
    }
928
    if(!MapInfo::verifySize(msg.mapLen, msg.luaLen, msg.mapCompressedLen, msg.luaCompressedLen))
6✔
929
    {
UNCOV
930
        OnError(ClientError::InvalidMap);
×
UNCOV
931
        return true;
×
932
    }
933
    const auto targetPath =
934
      RTTRCONFIG.ExpandPath((msg.mt == MapType::Savegame) ? s25::folders::save : s25::folders::mapsPlayed);
18✔
935
    bfs::create_directories(targetPath);
6✔
936
    mapinfo.filepath = targetPath / portFilename;
12✔
937
    mapinfo.type = msg.mt;
6✔
938

939
    // lua script file path
940
    if(msg.luaLen > 0)
6✔
941
        mapinfo.luaFilepath = bfs::path(mapinfo.filepath).replace_extension("lua");
4✔
942
    else
943
        mapinfo.luaFilepath.clear();
4✔
944

945
    // We have the map locally already, so prepare and ask if this is the same as the one on the server
946
    if(bfs::exists(mapinfo.filepath) && (mapinfo.luaFilepath.empty() || bfs::exists(mapinfo.luaFilepath))
8✔
947
       && CreateLobby())
8✔
948
    {
949
        mapinfo.mapData.CompressFromFile(mapinfo.filepath, &mapinfo.mapChecksum);
2✔
950
        if(mapinfo.mapData.data.size() == msg.mapCompressedLen && mapinfo.mapData.uncompressedLength == msg.mapLen)
2✔
951
        {
952
            bool ok = true;
2✔
953
            if(!mapinfo.luaFilepath.empty())
2✔
954
            {
955
                mapinfo.luaData.CompressFromFile(mapinfo.luaFilepath, &mapinfo.luaChecksum);
1✔
956
                ok = (mapinfo.luaData.data.size() == msg.luaCompressedLen
2✔
957
                      && mapinfo.luaData.uncompressedLength == msg.luaLen);
1✔
958
            }
959

960
            if(ok)
2✔
961
            {
962
                mainPlayer.sendMsgAsync(new GameMessage_Map_Checksum(mapinfo.mapChecksum, mapinfo.luaChecksum));
2✔
963
                AdvanceState(ConnectState::VerifyMap);
2✔
964
                return true;
2✔
965
            }
966
        }
UNCOV
967
        gameLobby.reset();
×
968
    }
969
    mapinfo.mapData.uncompressedLength = msg.mapLen;
4✔
970
    mapinfo.luaData.uncompressedLength = msg.luaLen;
4✔
971
    mapinfo.mapData.data.resize(msg.mapCompressedLen);
4✔
972
    mapinfo.luaData.data.resize(msg.luaCompressedLen);
4✔
973
    mainPlayer.sendMsgAsync(new GameMessage_MapRequest(false));
4✔
974
    AdvanceState(ConnectState::ReceiveMap);
4✔
975
    return true;
4✔
976
}
977

978
///////////////////////////////////////////////////////////////////////////////
979
/// Kartendaten
980
/// @param message  Nachricht, welche ausgeführt wird
981
bool GameClient::OnGameMessage(const GameMessage_Map_Data& msg)
8✔
982
{
983
    if(!VerifyState(ConnectState::ReceiveMap))
8✔
UNCOV
984
        return true;
×
985

986
    LOG.writeToFile("<<< NMS_MAP_DATA(%u)\n") % msg.data.size();
8✔
987
    std::vector<char>& targetData = (msg.isMapData) ? mapinfo.mapData.data : mapinfo.luaData.data;
8✔
988
    if(msg.data.size() > targetData.size() || msg.offset > targetData.size() - msg.data.size())
8✔
989
    {
990
        OnError(ClientError::MapTransmission);
1✔
991
        return true;
1✔
992
    }
993
    std::copy(msg.data.begin(), msg.data.end(), targetData.begin() + msg.offset);
7✔
994

995
    uint32_t totalSize = mapinfo.mapData.data.size();
7✔
996
    uint32_t receivedSize = msg.offset + msg.data.size();
7✔
997
    if(!mapinfo.luaFilepath.empty())
7✔
998
    {
999
        totalSize += mapinfo.luaData.data.size();
4✔
1000
        // Assumes lua data comes after the map data
1001
        if(!msg.isMapData)
4✔
1002
            receivedSize += mapinfo.mapData.data.size();
2✔
1003
    }
1004
    if(ci)
7✔
1005
        ci->CI_MapPartReceived(receivedSize, totalSize);
7✔
1006

1007
    if(receivedSize == totalSize)
7✔
1008
    {
1009
        if(!mapinfo.mapData.DecompressToFile(mapinfo.filepath, &mapinfo.mapChecksum))
2✔
1010
        {
UNCOV
1011
            OnError(ClientError::MapTransmission);
×
UNCOV
1012
            return true;
×
1013
        }
1014
        if(!mapinfo.luaFilepath.empty() && !mapinfo.luaData.DecompressToFile(mapinfo.luaFilepath, &mapinfo.luaChecksum))
2✔
1015
        {
UNCOV
1016
            OnError(ClientError::MapTransmission);
×
UNCOV
1017
            return true;
×
1018
        }
1019
        RTTR_Assert(!mapinfo.luaFilepath.empty() || mapinfo.luaChecksum == 0);
2✔
1020

1021
        if(!CreateLobby())
2✔
1022
        {
UNCOV
1023
            OnError(ClientError::MapTransmission);
×
UNCOV
1024
            return true;
×
1025
        }
1026

1027
        mainPlayer.sendMsgAsync(new GameMessage_Map_Checksum(mapinfo.mapChecksum, mapinfo.luaChecksum));
2✔
1028
        AdvanceState(ConnectState::VerifyMap);
2✔
1029
    }
1030
    return true;
7✔
1031
}
1032

UNCOV
1033
bool GameClient::OnGameMessage(const GameMessage_SkipToGF& msg)
×
1034
{
1035
    skiptogf = msg.targetGF;
×
UNCOV
1036
    LOG.write("Jumping from GF %1% to GF %2%\n") % GetGFNumber() % skiptogf;
×
1037
    return true;
×
1038
}
1039

1040
void GameClient::OnError(ClientError error)
2✔
1041
{
1042
    if(ci)
2✔
1043
        ci->CI_Error(error);
2✔
1044
    Stop();
2✔
1045
}
2✔
1046

1047
void GameClient::AdvanceState(ConnectState newState)
51✔
1048
{
1049
    connectState = newState;
51✔
1050
    if(ci)
51✔
1051
        ci->CI_NextConnectState(connectState);
46✔
1052
}
51✔
1053

1054
bool GameClient::VerifyState(ConnectState expectedState)
51✔
1055
{
1056
    if(state != ClientState::Connect || connectState != expectedState)
51✔
1057
    {
UNCOV
1058
        OnError(ClientError::InvalidMessage);
×
UNCOV
1059
        return false;
×
1060
    }
1061
    return true;
51✔
1062
}
1063

1064
bool GameClient::CreateLobby()
4✔
1065
{
1066
    RTTR_Assert(!gameLobby);
4✔
1067

1068
    unsigned numPlayers;
1069

1070
    switch(mapinfo.type)
4✔
1071
    {
1072
        case MapType::OldMap:
4✔
1073
        {
1074
            libsiedler2::Archiv map;
4✔
1075

1076
            // Karteninformationen laden
1077
            if(libsiedler2::loader::LoadMAP(mapinfo.filepath, map, true) != 0)
4✔
1078
            {
UNCOV
1079
                LOG.write("GameClient::OnMapData: ERROR: Map %1%, couldn't load header!\n") % mapinfo.filepath;
×
UNCOV
1080
                return false;
×
1081
            }
1082

1083
            const libsiedler2::ArchivItem_Map_Header& header =
1084
              checkedCast<const libsiedler2::ArchivItem_Map*>(map.get(0))->getHeader();
4✔
1085
            numPlayers = header.getNumPlayers();
4✔
1086
            mapinfo.title = s25util::ansiToUTF8(header.getName());
4✔
1087
        }
1088
        break;
4✔
UNCOV
1089
        case MapType::Savegame:
×
UNCOV
1090
            mapinfo.savegame = std::make_unique<Savegame>();
×
1091
            if(!mapinfo.savegame->Load(mapinfo.filepath, SaveGameDataToLoad::HeaderAndSettings))
×
1092
                return false;
×
1093

1094
            numPlayers = mapinfo.savegame->GetNumPlayers();
×
UNCOV
1095
            mapinfo.title = mapinfo.savegame->GetMapName();
×
1096
            break;
×
1097
        default: return false;
×
1098
    }
1099

1100
    if(GetPlayerId() >= numPlayers)
4✔
UNCOV
1101
        return false;
×
1102

1103
    gameLobby = std::make_shared<GameLobby>(mapinfo.type == MapType::Savegame, IsHost(), numPlayers);
4✔
1104
    return true;
4✔
1105
}
1106

1107
///////////////////////////////////////////////////////////////////////////////
1108
/// map-checksum
1109
/// @param message  Nachricht, welche ausgeführt wird
1110
bool GameClient::OnGameMessage(const GameMessage_Map_ChecksumOK& msg)
4✔
1111
{
1112
    if(!VerifyState(ConnectState::VerifyMap))
4✔
UNCOV
1113
        return true;
×
1114
    LOG.writeToFile("<<< NMS_MAP_CHECKSUM(%d)\n") % (msg.correct ? 1 : 0);
4✔
1115

1116
    if(msg.correct)
4✔
1117
        AdvanceState(ConnectState::QueryServerName);
4✔
1118
    else
1119
    {
UNCOV
1120
        gameLobby.reset();
×
UNCOV
1121
        if(msg.retryAllowed)
×
1122
        {
1123
            mainPlayer.sendMsgAsync(new GameMessage_MapRequest(false));
×
UNCOV
1124
            AdvanceState(ConnectState::ReceiveMap);
×
1125
        } else
1126
            OnError(ClientError::MapTransmission);
×
1127
    }
1128
    return true;
4✔
1129
}
1130

1131
///////////////////////////////////////////////////////////////////////////////
1132
/// server typ
1133
/// @param message  Nachricht, welche ausgeführt wird
1134
bool GameClient::OnGameMessage(const GameMessage_GGSChange& msg)
4✔
1135
{
1136
    if(state != ClientState::Config && !VerifyState(ConnectState::QuerySettings))
4✔
UNCOV
1137
        return true;
×
1138
    LOG.writeToFile("<<< NMS_GGS_CHANGE\n");
4✔
1139

1140
    gameLobby->getSettings() = msg.ggs;
4✔
1141

1142
    if(state == ClientState::Connect)
4✔
1143
    {
1144
        state = ClientState::Config;
4✔
1145
        AdvanceState(ConnectState::Finished);
4✔
UNCOV
1146
    } else if(ci)
×
UNCOV
1147
        ci->CI_GGSChanged(msg.ggs);
×
1148
    return true;
4✔
1149
}
1150

UNCOV
1151
bool GameClient::OnGameMessage(const GameMessage_RemoveLua&)
×
1152
{
1153
    if(state != ClientState::Connect && state != ClientState::Config)
×
UNCOV
1154
        return true;
×
1155
    mapinfo.luaFilepath.clear();
×
1156
    mapinfo.luaData.Clear();
×
1157
    mapinfo.luaChecksum = 0;
×
1158
    return true;
×
1159
}
1160

1161
///////////////////////////////////////////////////////////////////////////////
1162
/// NFC Antwort vom Server
1163
/// @param message  Nachricht, welche ausgeführt wird
UNCOV
1164
bool GameClient::OnGameMessage(const GameMessage_GameCommand& msg)
×
1165
{
1166
    if(nwfInfo)
×
1167
    {
1168
        if(!nwfInfo->addPlayerCmds(msg.player, msg.cmds))
×
1169
        {
1170
            LOG.write("Could not add gamecommands for player %1%. He might be cheating!\n") % unsigned(msg.player);
×
UNCOV
1171
            RTTR_Assert(false);
×
1172
        }
1173
    }
UNCOV
1174
    return true;
×
1175
}
1176

1177
void GameClient::IncreaseSpeed(const bool wraparound)
12✔
1178
{
1179
    const auto curSpeed = framesinfo.gfLengthReq;
12✔
1180
    // Note: Higher speed = lower gf_length value
1181
    // Go from debug speed directly back to min speed, else in fixed steps
1182
    static_assert(MIN_SPEED_DEBUG > MIN_SPEED);
1183
    if(framesinfo.gfLengthReq > MIN_SPEED)
12✔
1184
        SetNewSpeed(MIN_SPEED); // NOLINT(bugprone-branch-clone)
2✔
1185
    else
1186
        SetNewSpeed((framesinfo.gfLengthReq - 1ms) / SPEED_STEP * SPEED_STEP);
10✔
1187
    // If unchanged we capped at max speed, so wrap around if requested
1188
    if(wraparound && framesinfo.gfLengthReq == curSpeed)
12✔
1189
        SetNewSpeed(MIN_SPEED);
1✔
1190
}
12✔
1191

1192
void GameClient::DecreaseSpeed()
11✔
1193
{
1194
    if((DEBUG_MODE || replayMode) && framesinfo.gfLengthReq >= MIN_SPEED)
11✔
1195
        SetNewSpeed(MIN_SPEED_DEBUG);
2✔
1196
    else
1197
        SetNewSpeed(framesinfo.gfLengthReq / SPEED_STEP * SPEED_STEP + SPEED_STEP);
9✔
1198
}
11✔
1199

1200
void GameClient::SetNewSpeed(FramesInfo::milliseconds32_t gfLength)
38✔
1201
{
1202
    static_assert(MIN_SPEED >= SPEED_GF_LENGTHS[GameSpeed::VeryFast], "Not all speeds reachable");
1203
    static_assert(MAX_SPEED <= SPEED_GF_LENGTHS[GameSpeed::VerySlow], "Not all speeds reachable");
1204
    const auto minSpeed = (replayMode || DEBUG_MODE) ? MIN_SPEED_DEBUG : MIN_SPEED;
38✔
1205
    const auto maxSpeed = (replayMode || DEBUG_MODE) ? MAX_SPEED_DEBUG : MAX_SPEED;
38✔
1206
    const auto oldSpeed = framesinfo.gfLengthReq;
38✔
1207
    framesinfo.gfLengthReq = helpers::clamp<decltype(gfLength)>(gfLength, maxSpeed, minSpeed);
38✔
1208
    if(replayMode)
38✔
UNCOV
1209
        framesinfo.gf_length = framesinfo.gfLengthReq;
×
1210
    else if(framesinfo.gfLengthReq != oldSpeed)
38✔
1211
        mainPlayer.sendMsgAsync(new GameMessage_Speed(framesinfo.gfLengthReq));
35✔
1212
}
38✔
1213

1214
///////////////////////////////////////////////////////////////////////////////
1215
/// NFC Done vom Server
1216
/// @param message  Nachricht, welche ausgeführt wird
UNCOV
1217
bool GameClient::OnGameMessage(const GameMessage_Server_NWFDone& msg)
×
1218
{
1219
    if(!nwfInfo)
×
UNCOV
1220
        return true;
×
1221

1222
    if(!nwfInfo->addServerInfo(NWFServerInfo(msg.gf, msg.gf_length, msg.nextNWF)))
×
1223
    {
1224
        RTTR_Assert(false);
×
1225
        LOG.write("Failed to add server info. Invalid server?\n");
1226
    }
1227

UNCOV
1228
    return true;
×
1229
}
1230

UNCOV
1231
bool GameClient::OnGameMessage(const GameMessage_Pause& msg)
×
1232
{
1233
    // Ignore recorded pause messages in replay mode
UNCOV
1234
    if(state != ClientState::Game || replayMode)
×
UNCOV
1235
        return true;
×
1236
    if(framesinfo.isPaused == msg.paused)
×
1237
        return true;
×
1238
    framesinfo.isPaused = msg.paused;
×
1239

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

1242
    if(msg.paused)
×
UNCOV
1243
        ci->CI_GamePaused();
×
1244
    else
1245
        ci->CI_GameResumed();
×
UNCOV
1246
    return true;
×
1247
}
1248

1249
/**
1250
 *  NFC GetAsyncLog von Server
1251
 *
1252
 *  @param[in] message Nachricht, welche ausgeführt wird
1253
 */
UNCOV
1254
bool GameClient::OnGameMessage(const GameMessage_GetAsyncLog& /*msg*/)
×
1255
{
1256
    if(state != ClientState::Game)
×
UNCOV
1257
        return true;
×
1258
    std::string systemInfo = System::getCompilerName() + " @ " + System::getOSName();
×
1259
    mainPlayer.sendMsgAsync(new GameMessage_AsyncLog(systemInfo));
×
1260

1261
    // AsyncLog an den Server senden
1262

UNCOV
1263
    std::vector<RandomEntry> async_log = RANDOM.GetAsyncLog();
×
1264

1265
    // stückeln...
UNCOV
1266
    std::vector<RandomEntry> part;
×
UNCOV
1267
    for(auto& it : async_log)
×
1268
    {
1269
        part.push_back(it);
×
1270

1271
        if(part.size() == 10)
×
1272
        {
1273
            mainPlayer.sendMsgAsync(new GameMessage_AsyncLog(part, false));
×
UNCOV
1274
            part.clear();
×
1275
        }
1276
    }
1277

UNCOV
1278
    mainPlayer.sendMsgAsync(new GameMessage_AsyncLog(part, true));
×
UNCOV
1279
    return true;
×
1280
}
1281

1282
///////////////////////////////////////////////////////////////////////////////
1283
/// testet ob ein Netwerkframe abgelaufen ist und führt dann ggf die Befehle aus
UNCOV
1284
void GameClient::ExecuteGameFrame()
×
1285
{
1286
    if(framesinfo.isPaused)
×
UNCOV
1287
        return; // Pause
×
1288

1289
    FramesInfo::UsedClock::time_point currentTime = FramesInfo::UsedClock::now();
×
1290

1291
    if(framesinfo.forcePauseLen.count())
×
1292
    {
1293
        if(currentTime - framesinfo.forcePauseStart > framesinfo.forcePauseLen)
×
UNCOV
1294
            framesinfo.forcePauseLen = FramesInfo::milliseconds32_t::zero();
×
1295
        else
1296
            return; // Pause
×
1297
    }
1298

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

UNCOV
1339
                    RTTR_Assert(nwfInfo->getServerInfo().gf == curGF);
×
1340

1341
                    ExecuteNWF();
×
1342

1343
                    FramesInfo::milliseconds32_t oldGFLen = framesinfo.gf_length;
×
UNCOV
1344
                    nwfInfo->execute(framesinfo);
×
1345
                    if(oldGFLen != framesinfo.gf_length)
×
1346
                    {
1347
                        LOG.write("Client: Speed changed at %1% from %2% to %3% (NWF: %4%)\n") % curGF
×
UNCOV
1348
                          % helpers::withUnit(oldGFLen) % helpers::withUnit(framesinfo.gf_length)
×
1349
                          % framesinfo.nwf_length;
×
1350
                    }
1351
                }
1352

UNCOV
1353
                NextGF(isNWF);
×
UNCOV
1354
                RTTR_Assert(curGF <= nwfInfo->getNextNWF());
×
1355
                HandleAutosave();
×
1356

1357
                // GF-Ende im Replay aktualisieren
UNCOV
1358
                if(replayinfo && replayinfo->replay.IsRecording())
×
UNCOV
1359
                    replayinfo->replay.UpdateLastGF(curGF);
×
1360
            }
1361

UNCOV
1362
        } catch(const LuaExecutionError& e)
×
1363
        {
1364
            SystemChat((boost::format(_("Error during execution of lua script: %1\nGame stopped!")) % e.what()).str());
×
UNCOV
1365
            OnError(ClientError::InvalidMap);
×
1366
        }
1367
        if(skiptogf == GetGFNumber())
×
UNCOV
1368
            skiptogf = 0;
×
1369
    }
1370
    framesinfo.frameTime = std::chrono::duration_cast<FramesInfo::milliseconds32_t>(currentTime - framesinfo.lastTime);
×
1371
    // Check remaining time until next GF
1372
    if(framesinfo.frameTime >= framesinfo.gf_length)
×
1373
    {
1374
        // This can happen, if we don't call this method in intervals less than gf_length or gf_length has changed
1375
        // TODO: Run multiple GFs per call.
1376
        // For now just make sure it is less than gf_length by skipping some simulation time,
1377
        // until we are only a bit less than 1 GF behind
1378
        // However we allow the simulation to lack behind for a few frames, so if there was a single spike we can still
1379
        // catch up in the next visual frames
1380
        using DurationType = decltype(framesinfo.gf_length);
UNCOV
1381
        constexpr auto maxLackFrames = 5;
×
1382

1383
        RTTR_Assert(framesinfo.gf_length > DurationType::zero());
×
UNCOV
1384
        const auto maxFrameTime = framesinfo.gf_length - DurationType(1);
×
1385

1386
        if(framesinfo.frameTime > maxLackFrames * framesinfo.gf_length)
×
UNCOV
1387
            framesinfo.lastTime += framesinfo.frameTime - maxFrameTime; // Skip simulation time until caught up
×
1388
        framesinfo.frameTime = maxFrameTime;
×
1389
    }
1390
    // This is assumed by drawing code for interpolation
UNCOV
1391
    RTTR_Assert(framesinfo.frameTime < framesinfo.gf_length);
×
1392
}
1393

UNCOV
1394
void GameClient::HandleAutosave()
×
1395
{
1396
    // If inactive or during replay -> no autosave
UNCOV
1397
    if(!SETTINGS.interface.autosaveInterval || replayMode)
×
UNCOV
1398
        return;
×
1399

1400
    // Alle .... GF
UNCOV
1401
    if(GetGFNumber() % SETTINGS.interface.autosaveInterval == 0)
×
1402
    {
1403
        std::string filename;
×
UNCOV
1404
        if(mapinfo.title.empty())
×
1405
            filename = std::string(_("Auto-Save")) + ".sav";
×
1406
        else
1407
            filename = mapinfo.title + " (" + _("Auto-Save") + ").sav";
×
1408

1409
        SaveToFile(RTTRCONFIG.ExpandPath(s25::folders::save) / filename);
×
1410
    }
1411
}
1412

1413
/// Führt notwendige Dinge für nächsten GF aus
UNCOV
1414
void GameClient::NextGF(bool wasNWF)
×
1415
{
1416
    for(AIPlayer& ai : game->aiPlayers_)
×
UNCOV
1417
        ai.RunGF(GetGFNumber(), wasNWF);
×
1418
    game->RunGF();
×
1419
}
×
1420

1421
void GameClient::ExecuteAllGCs(uint8_t playerId, const PlayerGameCommands& gcs)
×
1422
{
1423
    for(const gc::GameCommandPtr& gc : gcs.gcs)
×
UNCOV
1424
        gc->Execute(game->world_, playerId);
×
1425
}
×
1426

1427
void GameClient::SendNothingNC(uint8_t player)
×
1428
{
1429
    mainPlayer.sendMsgAsync(
×
UNCOV
1430
      new GameMessage_GameCommand(player, AsyncChecksum::create(*game), std::vector<gc::GameCommandPtr>()));
×
1431
}
×
1432

1433
void GameClient::WritePlayerInfo(SavedFile& file)
×
1434
{
1435
    RTTR_Assert(state == ClientState::Loading || state == ClientState::Loaded || state == ClientState::Game);
×
1436
    // Spielerdaten
1437
    for(unsigned i = 0; i < GetNumPlayers(); ++i)
×
UNCOV
1438
        file.AddPlayer(GetPlayer(i));
×
1439
}
×
1440

1441
void GameClient::OnGameStart()
6✔
1442
{
1443
    if(state == ClientState::Loaded)
6✔
1444
    {
UNCOV
1445
        GAMEMANAGER.ResetAverageGFPS();
×
UNCOV
1446
        framesinfo.lastTime = FramesInfo::UsedClock::now();
×
1447
        state = ClientState::Game;
×
1448
        if(ci)
×
1449
            ci->CI_GameStarted();
×
1450
    } else if(state == ClientState::Game && !game->IsStarted())
6✔
1451
    {
UNCOV
1452
        framesinfo.isPaused = replayMode;
×
UNCOV
1453
        game->Start(!!mapinfo.savegame);
×
1454
    }
1455
}
6✔
1456

UNCOV
1457
void GameClient::StartReplayRecording(const unsigned random_init)
×
1458
{
1459
    replayinfo = std::make_unique<ReplayInfo>();
×
UNCOV
1460
    replayinfo->filename = s25util::Time::FormatTime("%Y-%m-%d_%H-%i-%s") + ".rpl";
×
1461

1462
    WritePlayerInfo(replayinfo->replay);
×
UNCOV
1463
    replayinfo->replay.ggs = game->ggs_;
×
1464

1465
    if(!replayinfo->replay.StartRecording(RTTRCONFIG.ExpandPath(s25::folders::replays) / replayinfo->filename, mapinfo,
×
1466
                                          random_init))
1467
    {
UNCOV
1468
        LOG.write(_("Replayfile couldn't be opened. No replay will be recorded\n"));
×
UNCOV
1469
        replayinfo.reset();
×
1470
    }
1471
}
×
1472

1473
bool GameClient::StartReplay(const boost::filesystem::path& path)
×
1474
{
1475
    RTTR_Assert(state == ClientState::Stopped);
×
UNCOV
1476
    mapinfo.Clear();
×
1477
    replayinfo = std::make_unique<ReplayInfo>();
×
1478

1479
    if(!replayinfo->replay.LoadHeader(path) || !replayinfo->replay.LoadGameData(mapinfo)) //-V807
×
1480
    {
1481
        LOG.write(_("Invalid Replay %1%! Reason: %2%\n")) % path
×
UNCOV
1482
          % (replayinfo->replay.GetLastErrorMsg().empty() ? _("Unknown") : replayinfo->replay.GetLastErrorMsg());
×
1483
        OnError(ClientError::InvalidMap);
×
1484
        replayinfo.reset();
×
1485
        return false;
×
1486
    }
1487
    replayinfo->filename = path.filename();
×
1488

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

1491
    for(unsigned i = 0; i < replayinfo->replay.GetNumPlayers(); ++i)
×
UNCOV
1492
        gameLobby->getPlayer(i) = JoinPlayerInfo(replayinfo->replay.GetPlayer(i));
×
1493

1494
    bool playerFound = false;
×
1495
    // Find a player to spectate from
1496
    // First find a human player
UNCOV
1497
    for(unsigned char i = 0; i < gameLobby->getNumPlayers(); ++i)
×
1498
    {
1499
        if(gameLobby->getPlayer(i).ps == PlayerState::Occupied)
×
1500
        {
1501
            mainPlayer.playerId = i;
×
UNCOV
1502
            playerFound = true;
×
1503
            break;
×
1504
        }
1505
    }
UNCOV
1506
    if(!playerFound)
×
1507
    {
1508
        // If no human found, take the first AI
UNCOV
1509
        for(unsigned char i = 0; i < gameLobby->getNumPlayers(); ++i)
×
1510
        {
1511
            if(gameLobby->getPlayer(i).ps == PlayerState::AI)
×
1512
            {
1513
                mainPlayer.playerId = i;
×
UNCOV
1514
                break;
×
1515
            }
1516
        }
1517
    }
1518

1519
    // GGS-Daten
UNCOV
1520
    gameLobby->getSettings() = replayinfo->replay.ggs;
×
1521

1522
    switch(mapinfo.type)
×
1523
    {
1524
        default: break;
×
UNCOV
1525
        case MapType::OldMap:
×
1526
        {
1527
            // Richtigen Pfad zur Map erstellen
UNCOV
1528
            bfs::path mapFilePath = RTTRCONFIG.ExpandPath(s25::folders::mapsPlayed) / mapinfo.filepath.filename();
×
UNCOV
1529
            mapinfo.filepath = mapFilePath;
×
1530
            if(!mapinfo.mapData.DecompressToFile(mapinfo.filepath))
×
1531
            {
1532
                LOG.write(_("Error decompressing map file"));
×
UNCOV
1533
                OnError(ClientError::MapTransmission);
×
1534
                return false;
×
1535
            }
1536
            if(mapinfo.luaData.uncompressedLength)
×
1537
            {
1538
                mapinfo.luaFilepath = mapFilePath.replace_extension("lua");
×
UNCOV
1539
                if(!mapinfo.luaData.DecompressToFile(mapinfo.luaFilepath))
×
1540
                {
1541
                    LOG.write(_("Error decompressing lua file"));
×
UNCOV
1542
                    OnError(ClientError::MapTransmission);
×
1543
                    return false;
×
1544
                }
1545
            }
1546
        }
UNCOV
1547
        break;
×
UNCOV
1548
        case MapType::Savegame: break;
×
1549
    }
1550

UNCOV
1551
    replayMode = true;
×
1552

1553
    try
1554
    {
UNCOV
1555
        StartGame(replayinfo->replay.getSeed());
×
UNCOV
1556
    } catch(const SerializedGameData::Error& error)
×
1557
    {
1558
        LOG.write(_("Error when loading game from replay: %s\n")) % error.what();
×
UNCOV
1559
        OnError(ClientError::InvalidMap);
×
1560
        return false;
×
1561
    }
1562

1563
    /*
1564
      We have to to this if we have a old replay starting from scratch not containing a savegame. If a savegame is
1565
      contained in the replay the compatibility code in GamePlayer deserialization function takes care of handling this.
1566
      If we have a replay starting from scratch in the constructor of the gameplayer the standard distributions are
1567
      loaded. These contain also the new leather addon buildings. When the distribution is recomputed these buildings
1568
      are added to the possible goals for wares. This leads to the problem that we have more buildings then before in
1569
      the list. So it happens for example for wood that the ware is deliverd to a different goal and then the replay
1570
      gets out of sync.
1571
    */
UNCOV
1572
    if(!mapinfo.savegame && replayinfo->replay.GetMinorVersion() < 2)
×
1573
    {
1574
        auto newDistributions = default_settings.distribution;
×
UNCOV
1575
        unsigned idx = 0;
×
1576
        for(const DistributionMapping& mapping : distributionMap)
×
1577
        {
1578
            if(leatheraddon::isLeatherAddonBuildingType(std::get<1>(mapping)))
×
UNCOV
1579
                newDistributions[idx] = 0;
×
1580
            idx++;
×
1581
        }
1582

UNCOV
1583
        for(unsigned i = 0; i < game->world_.GetNumPlayers(); i++)
×
UNCOV
1584
            game->world_.GetPlayer(i).ChangeDistribution(newDistributions);
×
1585
    }
1586

UNCOV
1587
    replayinfo->next_gf = replayinfo->replay.ReadGF();
×
1588

1589
    return true;
×
1590
}
1591

UNCOV
1592
void GameClient::SetAIBattlePlayers(std::vector<AI::Info> aiInfos)
×
1593
{
1594
    aiBattlePlayers_ = std::move(aiInfos);
×
UNCOV
1595
}
×
1596

1597
unsigned GameClient::GetGlobalAnimation(const unsigned short max, const unsigned char factor_numerator,
1✔
1598
                                        const unsigned char factor_denumerator, const unsigned offset)
1599
{
1600
    // Unit for animations is 630ms (dividable by 2, 3, 5, 6, 7, 10, 15, ...)
1601
    // But this also means: If framerate drops below approx. 15Hz, you won't see
1602
    // every frame of an 8-part animation anymore.
1603
    // An animation runs fully in (factor_numerator / factor_denumerator) multiples of 630ms
1604
    const unsigned unit = 630 /*ms*/ * factor_numerator / factor_denumerator;
1✔
1605
    const unsigned currenttime = std::chrono::duration_cast<FramesInfo::milliseconds32_t>(
1✔
1606
                                   (framesinfo.lastTime + framesinfo.frameTime).time_since_epoch())
1✔
1607
                                   .count();
1✔
1608
    return ((currenttime % unit) * max / unit + offset) % max;
1✔
1609
}
1610

UNCOV
1611
unsigned GameClient::Interpolate(unsigned max_val, const GameEvent* ev)
×
1612
{
1613
    RTTR_Assert(ev);
×
1614
    // TODO: Move to some animation system that is part of game
1615
    FramesInfo::milliseconds32_t elapsedTime;
UNCOV
1616
    if(state == ClientState::Game)
×
UNCOV
1617
        elapsedTime = (GetGFNumber() - ev->startGF) * framesinfo.gf_length + framesinfo.frameTime;
×
1618
    else
1619
        elapsedTime = FramesInfo::milliseconds32_t::zero();
×
UNCOV
1620
    FramesInfo::milliseconds32_t duration = ev->length * framesinfo.gf_length;
×
1621
    return helpers::interpolate(0u, max_val, elapsedTime, duration);
×
1622
}
1623

UNCOV
1624
int GameClient::Interpolate(int x1, int x2, const GameEvent* ev)
×
1625
{
1626
    RTTR_Assert(ev);
×
1627
    FramesInfo::milliseconds32_t elapsedTime;
1628
    if(state == ClientState::Game)
×
UNCOV
1629
        elapsedTime = (GetGFNumber() - ev->startGF) * framesinfo.gf_length + framesinfo.frameTime;
×
1630
    else
1631
        elapsedTime = FramesInfo::milliseconds32_t::zero();
×
UNCOV
1632
    FramesInfo::milliseconds32_t duration = ev->length * framesinfo.gf_length;
×
1633
    return helpers::interpolate(x1, x2, elapsedTime, duration);
×
1634
}
1635

UNCOV
1636
void GameClient::ServerLost()
×
1637
{
1638
    OnError(ClientError::ConnectionLost);
×
1639
    // Stop game
1640
    framesinfo.isPaused = true;
×
UNCOV
1641
}
×
1642

1643
/**
1644
 *  überspringt eine bestimmte Anzahl von Gameframes.
1645
 *
1646
 *  @param[in] dest_gf Zielgameframe
1647
 */
UNCOV
1648
void GameClient::SkipGF(unsigned gf, GameWorldView& gwv)
×
1649
{
1650
    if(gf <= GetGFNumber())
×
UNCOV
1651
        return;
×
1652

1653
    unsigned start_ticks = VIDEODRIVER.GetTickCount();
×
1654

1655
    if(!replayMode)
×
1656
    {
1657
        // unpause before skipping
UNCOV
1658
        SetPause(false);
×
UNCOV
1659
        mainPlayer.sendMsgAsync(new GameMessage_SkipToGF(gf));
×
1660
        return;
×
1661
    }
1662

UNCOV
1663
    SetPause(false);
×
UNCOV
1664
    if(GetGFNumber() < GetLastReplayGF())
×
1665
        gf = std::min(gf, GetLastReplayGF() + 1u);
×
1666
    skiptogf = gf;
×
1667

1668
    // GFs überspringen
UNCOV
1669
    for(unsigned i = GetGFNumber(); i < skiptogf; ++i)
×
1670
    {
1671
        if(i % 1000 == 0)
×
1672
        {
1673
            RoadBuildState road;
×
UNCOV
1674
            road.mode = RoadBuildMode::Disabled;
×
1675

1676
            // spiel aktualisieren
UNCOV
1677
            gwv.Draw(road, MapPoint::Invalid(), false);
×
1678

1679
            // text oben noch hinschreiben
UNCOV
1680
            boost::format nwfString(_("current GF: %u - still fast forwarding: %d GFs left (%d %%)"));
×
UNCOV
1681
            nwfString % GetGFNumber() % (gf - i) % (i * 100 / gf);
×
1682
            LargeFont->Draw(DrawPoint(VIDEODRIVER.GetRenderSize() / 2u), nwfString.str(), FontStyle::CENTER,
×
1683
                            COLOR_YELLOW);
1684

UNCOV
1685
            VIDEODRIVER.SwapBuffers();
×
1686
        }
1687
        ExecuteGameFrame();
×
1688
    }
1689
    // Either we force-stopped skipping (skiptogf set to 0) or we reached the target gf
UNCOV
1690
    RTTR_Assert(skiptogf == 0u || GetGFNumber() == gf);
×
1691

1692
    // Spiel pausieren & text ausgabe wie lang das jetzt gedauert hat
UNCOV
1693
    unsigned ticks = VIDEODRIVER.GetTickCount() - start_ticks;
×
UNCOV
1694
    boost::format text(_("Jump finished (%1$.3g seconds)."));
×
1695
    text % (ticks / 1000.0);
×
1696
    SystemChat(text.str());
×
1697
    SetPause(true);
×
1698
}
1699

UNCOV
1700
void GameClient::SystemChat(const std::string& text)
×
1701
{
1702
    SystemChat(text, GetPlayerId());
×
UNCOV
1703
}
×
1704

1705
void GameClient::SystemChat(const std::string& text, unsigned char fromPlayerIdx)
×
1706
{
1707
    if(ci)
×
UNCOV
1708
        ci->CI_Chat(fromPlayerIdx, ChatDestination::System, text);
×
1709
}
×
1710

1711
bool GameClient::SaveToFile(const boost::filesystem::path& filepath)
×
1712
{
1713
    mainPlayer.sendMsg(GameMessage_Chat(GetPlayerId(), ChatDestination::System, _("Saving game...")));
×
1714

1715
    // Mond malen
UNCOV
1716
    Position moonPos = VIDEODRIVER.GetMousePos();
×
UNCOV
1717
    moonPos.y -= 40;
×
1718
    LOADER.GetImageN("resource", 33)->DrawFull(moonPos);
×
1719
    VIDEODRIVER.SwapBuffers();
×
1720

1721
    Savegame save;
×
1722

1723
    WritePlayerInfo(save);
×
1724

1725
    // GGS-Daten
UNCOV
1726
    save.ggs = game->ggs_;
×
1727

1728
    save.start_gf = GetGFNumber();
×
1729

1730
    // Enable/Disable debugging of savegames
UNCOV
1731
    save.sgd.debugMode = SETTINGS.global.debugMode;
×
1732

1733
    try
1734
    {
1735
        // Spiel serialisieren
UNCOV
1736
        save.sgd.MakeSnapshot(*game);
×
1737
        // Und alles speichern
1738
        return save.Save(filepath, mapinfo.title);
×
UNCOV
1739
    } catch(const std::exception& e)
×
1740
    {
1741
        SystemChat(std::string(_("Error during saving: ")) + e.what());
×
UNCOV
1742
        return false;
×
1743
    }
1744
}
1745

UNCOV
1746
void GameClient::ResetVisualSettings()
×
1747
{
1748
    GetPlayer(GetPlayerId()).FillVisualSettings(visual_settings);
×
UNCOV
1749
}
×
1750

1751
void GameClient::SetPause(bool pause)
×
1752
{
1753
    if(state == ClientState::Stopped)
×
1754
    {
1755
        // We can never continue from pause if stopped as the reason for stopping might be that the game was finished
1756
        // However we allow to pause even when stopped so we can pause after we received the stop notification
UNCOV
1757
        if(!pause)
×
UNCOV
1758
            return;
×
1759
        framesinfo.isPaused = true;
×
1760
        framesinfo.frameTime = FramesInfo::milliseconds32_t::zero();
×
1761
    } else if(replayMode)
×
1762
    {
1763
        // Pause instantly
UNCOV
1764
        framesinfo.isPaused = pause;
×
UNCOV
1765
        framesinfo.frameTime = FramesInfo::milliseconds32_t::zero();
×
1766
    } else if(IsHost())
×
1767
    {
1768
        auto* msg = new GameMessage_Pause(pause);
×
UNCOV
1769
        if(pause)
×
1770
            OnGameMessage(*msg);
×
1771
        mainPlayer.sendMsgAsync(msg);
×
1772
    }
1773
}
1774

UNCOV
1775
void GameClient::SetReplayFOW(const bool hideFOW)
×
1776
{
1777
    if(replayinfo)
×
UNCOV
1778
        replayinfo->all_visible = hideFOW;
×
1779
}
×
1780

1781
bool GameClient::IsReplayFOWDisabled() const
×
1782
{
1783
    return replayMode && replayinfo->all_visible;
×
1784
}
1785

UNCOV
1786
unsigned GameClient::GetLastReplayGF() const
×
1787
{
1788
    return replayinfo ? replayinfo->replay.GetLastGF() : 0u;
×
1789
}
1790

UNCOV
1791
bool GameClient::AddGC(gc::GameCommandPtr gc)
×
1792
{
1793
    // Nicht in der Pause oder wenn er besiegt wurde
UNCOV
1794
    if(framesinfo.isPaused || GetPlayer(GetPlayerId()).IsDefeated() || IsReplayModeOn())
×
UNCOV
1795
        return false;
×
1796

1797
    gameCommands_.push_back(gc);
×
UNCOV
1798
    return true;
×
1799
}
1800

UNCOV
1801
unsigned GameClient::GetNumPlayers() const
×
1802
{
1803
    RTTR_Assert(state == ClientState::Loading || state == ClientState::Loaded || state == ClientState::Game);
×
UNCOV
1804
    return game->world_.GetNumPlayers();
×
1805
}
1806

UNCOV
1807
GamePlayer& GameClient::GetPlayer(const unsigned id)
×
1808
{
1809
    RTTR_Assert(state == ClientState::Loading || state == ClientState::Loaded || state == ClientState::Game);
×
UNCOV
1810
    RTTR_Assert(id < GetNumPlayers());
×
1811
    return game->world_.GetPlayer(id);
×
1812
}
1813

UNCOV
1814
std::unique_ptr<AIPlayer> GameClient::CreateAIPlayer(unsigned playerId, const AI::Info& aiInfo)
×
1815
{
1816
    return AIFactory::Create(aiInfo, playerId, game->world_);
×
1817
}
1818

1819
/// Wandelt eine GF-Angabe in eine Zeitangabe um (HH:MM:SS oder MM:SS wenn Stunden = 0)
1820
std::string GameClient::FormatGFTime(const unsigned gf) const
5✔
1821
{
1822
    using seconds = std::chrono::duration<uint32_t, std::chrono::seconds::period>;
1823
    using hours = std::chrono::duration<uint32_t, std::chrono::hours::period>;
1824
    using minutes = std::chrono::duration<uint32_t, std::chrono::minutes::period>;
1825
    using std::chrono::duration_cast;
1826

1827
    seconds numSeconds = duration_cast<seconds>(gfs_to_duration(gf));
5✔
1828

1829
    hours numHours = duration_cast<hours>(numSeconds);
5✔
1830
    numSeconds -= numHours;
5✔
1831
    minutes numMinutes = duration_cast<minutes>(numSeconds);
5✔
1832
    numSeconds -= numMinutes;
5✔
1833

1834
    // Use hour format only if we have hours
1835
    if(numHours.count())
5✔
UNCOV
1836
        return helpers::format("%u:%02u:%02u", numHours.count(), numMinutes.count(), numSeconds.count());
×
1837
    else
1838
        return helpers::format("%02u:%02u", numMinutes.count(), numSeconds.count());
10✔
1839
}
1840

UNCOV
1841
const boost::filesystem::path& GameClient::GetReplayFilename() const
×
1842
{
1843
    static boost::filesystem::path emptyString;
×
UNCOV
1844
    return replayinfo ? replayinfo->filename : emptyString;
×
1845
}
1846

UNCOV
1847
Replay* GameClient::GetReplay()
×
1848
{
1849
    return replayinfo ? &replayinfo->replay : nullptr;
×
1850
}
1851

UNCOV
1852
std::shared_ptr<const NWFInfo> GameClient::GetNWFInfo() const
×
1853
{
1854
    return nwfInfo;
×
1855
}
1856

1857
/// Is tournament mode activated (0 if not)? Returns the durations of the tournament mode in gf otherwise
UNCOV
1858
unsigned GameClient::GetTournamentModeDuration() const
×
1859
{
1860
    using namespace std::chrono;
UNCOV
1861
    if(game && rttr::enum_cast(game->ggs_.objective) >= rttr::enum_cast(GameObjective::Tournament1)
×
UNCOV
1862
       && static_cast<unsigned>(rttr::enum_cast(game->ggs_.objective))
×
1863
            < rttr::enum_cast(GameObjective::Tournament1) + NUM_TOURNAMENT_MODES)
×
1864
    {
1865
        const auto turnamentMode = rttr::enum_cast(game->ggs_.objective) - rttr::enum_cast(GameObjective::Tournament1);
×
UNCOV
1866
        return duration_to_gfs(TOURNAMENT_MODES_DURATION[turnamentMode]);
×
1867
    } else
1868
        return 0;
×
1869
}
1870

UNCOV
1871
void GameClient::ToggleHumanAIPlayer(const AI::Info& aiInfo)
×
1872
{
1873
    RTTR_Assert(!IsReplayModeOn());
×
UNCOV
1874
    auto it = helpers::find_if(game->aiPlayers_,
×
1875
                               [id = this->GetPlayerId()](const auto& player) { return player.GetPlayerId() == id; });
×
1876
    if(it != game->aiPlayers_.end())
×
1877
    {
1878
        game->aiPlayers_.erase(it);
×
UNCOV
1879
        SystemChat(_("Disabled AI for current player"));
×
1880
    } else
1881
    {
UNCOV
1882
        game->AddAIPlayer(CreateAIPlayer(GetPlayerId(), aiInfo));
×
UNCOV
1883
        SystemChat(_("Enabled AI for current player"));
×
1884
    }
1885
}
×
1886

1887
void GameClient::RequestSwapToPlayer(const unsigned char newId)
×
1888
{
1889
    if(state != ClientState::Game)
×
UNCOV
1890
        return;
×
1891
    GamePlayer& player = GetPlayer(newId);
×
1892
    if(player.ps == PlayerState::AI && player.aiInfo.type == AI::Type::Dummy)
×
1893
        mainPlayer.sendMsgAsync(new GameMessage_Player_Swap(0xFF, newId));
×
1894
}
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