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

Return-To-The-Roots / s25client / 25609024779

09 May 2026 06:48PM UTC coverage: 50.245% (+0.008%) from 50.237%
25609024779

push

github

Flow86
Document print key screenshot shortcut

23091 of 45957 relevant lines covered (50.24%)

43928.37 hits per line

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

23.28
/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
        gameWorld.SetupResources();
×
341
    }
342
    gameWorld.InitAfterLoad();
×
343

344
    // Update visual settings
345
    ResetVisualSettings();
×
346

347
    if(!replayMode)
×
348
    {
349
        RTTR_Assert(!replayinfo);
×
350
        StartReplayRecording(random_init);
×
351
    }
352

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

357
void GameClient::GameLoaded()
×
358
{
359
    RTTR_Assert(state == ClientState::Loading);
×
360

361
    state = ClientState::Loaded;
×
362

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

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

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

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

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

422
    mainPlayer.playerId = msg.player;
7✔
423

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

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

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

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

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

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

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

487
    if(msg.player >= gameLobby->getNumPlayers())
×
488
        return true;
×
489

490
    JoinPlayerInfo& playerInfo = gameLobby->getPlayer(msg.player);
×
491

492
    playerInfo.name = msg.name;
×
493
    playerInfo.ps = PlayerState::Occupied;
×
494
    playerInfo.ping = 0;
×
495

496
    if(ci)
×
497
        ci->CI_NewPlayer(msg.player);
×
498
    return true;
×
499
}
500

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

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

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

532
    if(msg.player >= gameLobby->getNumPlayers())
×
533
        return true;
×
534

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

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

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

560
    if(msg.player >= gameLobby->getNumPlayers())
×
561
        return true;
×
562

563
    gameLobby->getPlayer(msg.player).nation = msg.nation;
×
564

565
    if(ci)
×
566
        ci->CI_PlayerDataChanged(msg.player);
×
567
    return true;
×
568
}
569

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

578
    if(msg.player >= gameLobby->getNumPlayers())
×
579
        return true;
×
580

581
    gameLobby->getPlayer(msg.player).team = msg.team;
×
582

583
    if(ci)
×
584
        ci->CI_PlayerDataChanged(msg.player);
×
585
    return true;
×
586
}
587

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

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

599
    gameLobby->getPlayer(msg.player).color = msg.color;
×
600

601
    if(ci)
×
602
        ci->CI_PlayerDataChanged(msg.player);
×
603
    return true;
×
604
}
605

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

616
    if(msg.player >= gameLobby->getNumPlayers())
×
617
        return true;
×
618

619
    gameLobby->getPlayer(msg.player).isReady = msg.ready;
×
620

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

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

654
    if(ci)
×
655
        ci->CI_PlayerLeft(msg.player);
×
656
    return true;
×
657
}
658

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

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

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

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

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

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

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

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

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

722
    mainPlayer.sendMsgAsync(new GameMessage_Server_Password(clientconfig.password));
7✔
723

724
    AdvanceState(ConnectState::QueryPw);
7✔
725
    return true;
7✔
726
}
727

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

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

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

746
    AdvanceState(ConnectState::QueryMapInfo);
6✔
747
    return true;
6✔
748
}
749

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

759
    AdvanceState(ConnectState::QueryPlayerList);
4✔
760
    return true;
4✔
761
}
762

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

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

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

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

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

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

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

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

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

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

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

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

866
    // Messenger im Game
867
    if(ci)
×
868
        ci->CI_Async(checksum_list.str());
×
869

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1062
bool GameClient::CreateLobby()
4✔
1063
{
1064
    RTTR_Assert(!gameLobby);
4✔
1065

1066
    unsigned numPlayers;
1067

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

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

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

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

1098
    if(GetPlayerId() >= numPlayers)
4✔
1099
        return false;
×
1100

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

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

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

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

1138
    gameLobby->getSettings() = msg.ggs;
4✔
1139

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

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

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

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

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

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

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

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

1226
    return true;
×
1227
}
1228

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

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

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

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

1259
    // AsyncLog an den Server senden
1260

1261
    std::vector<RandomEntry> async_log = RANDOM.GetAsyncLog();
×
1262

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

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

1276
    mainPlayer.sendMsgAsync(new GameMessage_AsyncLog(part, true));
×
1277
    return true;
×
1278
}
1279

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

1287
    FramesInfo::UsedClock::time_point currentTime = FramesInfo::UsedClock::now();
×
1288

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

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

1337
                    RTTR_Assert(nwfInfo->getServerInfo().gf == curGF);
×
1338

1339
                    ExecuteNWF();
×
1340

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

1351
                NextGF(isNWF);
×
1352
                RTTR_Assert(curGF <= nwfInfo->getNextNWF());
×
1353
                HandleAutosave();
×
1354

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

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

1381
        RTTR_Assert(framesinfo.gf_length > DurationType::zero());
×
1382
        const auto maxFrameTime = framesinfo.gf_length - DurationType(1);
×
1383

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

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

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

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

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

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

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

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

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

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

1460
    WritePlayerInfo(replayinfo->replay);
×
1461
    replayinfo->replay.ggs = game->ggs_;
×
1462

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

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

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

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

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

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

1517
    // GGS-Daten
1518
    gameLobby->getSettings() = replayinfo->replay.ggs;
×
1519

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

1549
    replayMode = true;
×
1550

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

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

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

1585
    replayinfo->next_gf = replayinfo->replay.ReadGF();
×
1586

1587
    return true;
×
1588
}
1589

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

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

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

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

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

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

1651
    unsigned start_ticks = VIDEODRIVER.GetTickCount();
×
1652

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

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

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

1674
            // spiel aktualisieren
1675
            gwv.Draw(road, MapPoint::Invalid(), false);
×
1676

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

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

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

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

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

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

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

1719
    Savegame save;
×
1720

1721
    WritePlayerInfo(save);
×
1722

1723
    // GGS-Daten
1724
    save.ggs = game->ggs_;
×
1725

1726
    save.start_gf = GetGFNumber();
×
1727

1728
    // Enable/Disable debugging of savegames
1729
    save.sgd.debugMode = SETTINGS.global.debugMode;
×
1730

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

1744
void GameClient::ResetVisualSettings()
×
1745
{
1746
    GetPlayer(GetPlayerId()).FillVisualSettings(visual_settings);
×
1747
}
×
1748

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

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

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

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

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

1795
    gameCommands_.push_back(gc);
×
1796
    return true;
×
1797
}
1798

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

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

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

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

1825
    seconds numSeconds = duration_cast<seconds>(gfs_to_duration(gf));
5✔
1826

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

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

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

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

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

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

1869
void GameClient::ToggleHumanAIPlayer(const AI::Info& aiInfo)
×
1870
{
1871
    RTTR_Assert(!IsReplayModeOn());
×
1872
    auto it = helpers::find_if(game->aiPlayers_,
×
1873
                               [id = this->GetPlayerId()](const auto& player) { return player.GetPlayerId() == id; });
×
1874
    if(it != game->aiPlayers_.end())
×
1875
        game->aiPlayers_.erase(it);
×
1876
    else
1877
        game->AddAIPlayer(CreateAIPlayer(GetPlayerId(), aiInfo));
×
1878
}
×
1879

1880
void GameClient::RequestSwapToPlayer(const unsigned char newId)
×
1881
{
1882
    if(state != ClientState::Game)
×
1883
        return;
×
1884
    GamePlayer& player = GetPlayer(newId);
×
1885
    if(player.ps == PlayerState::AI && player.aiInfo.type == AI::Type::Dummy)
×
1886
        mainPlayer.sendMsgAsync(new GameMessage_Player_Swap(0xFF, newId));
×
1887
}
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