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

Return-To-The-Roots / s25client / 22044569181

15 Feb 2026 10:50PM UTC coverage: 50.34% (-0.5%) from 50.826%
22044569181

Pull #1720

github

web-flow
Merge 4dbe54b70 into 6db06730b
Pull Request #1720: Add leather addon

274 of 1055 new or added lines in 65 files covered. (25.97%)

286 existing lines in 28 files now uncovered.

23017 of 45723 relevant lines covered (50.34%)

43559.46 hits per line

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

22.98
/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()
19✔
83
{
84
    server.clear();
19✔
85
    gameName.clear();
19✔
86
    password.clear();
19✔
87
    port = 0;
19✔
88
    isHost = false;
19✔
89
}
19✔
90

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

93
GameClient::~GameClient()
17✔
94
{
95
    Stop();
14✔
96
}
17✔
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,
5✔
109
                         unsigned short port, bool host, bool use_ipv6)
110
{
111
    Stop();
5✔
112

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

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

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

123
    // Verbinden
124
    if(!mainPlayer.socket.Connect(server, port, use_ipv6, SETTINGS.proxy))
5✔
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;
5✔
133
    AdvanceState(ConnectState::Initiated);
5✔
134

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

138
    return true;
5✔
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()
23✔
215
{
216
    if(state == ClientState::Stopped)
23✔
217
        return;
18✔
218

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

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

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

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

239
    mainPlayer.closeConnection();
5✔
240

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

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

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

251
    aiBattlePlayers_.clear();
5✔
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)
5✔
412
{
413
    if(!VerifyState(ConnectState::Initiated))
5✔
414
        return true;
×
415
    // haben wir eine ungültige ID erhalten? (aka Server-Voll)
416
    if(msg.player == GameMessageWithPlayer::NO_PLAYER_ID)
5✔
417
    {
418
        OnError(ClientError::ServerFull);
×
419
        return true;
×
420
    }
421

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

424
    // Server-Typ senden
425
    mainPlayer.sendMsgAsync(new GameMessage_Server_Type(clientconfig.servertyp, rttr::version::GetRevision()));
5✔
426
    AdvanceState(ConnectState::VerifyServer);
5✔
427
    return true;
5✔
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)
5✔
695
{
696
    if(!VerifyState(ConnectState::VerifyServer))
5✔
697
        return true;
×
698

699
    using StatusCode = GameMessage_Server_TypeOK::StatusCode;
700
    switch(msg.err_code)
5✔
701
    {
702
        case StatusCode::Ok: break;
5✔
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));
5✔
723

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

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

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

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

746
    AdvanceState(ConnectState::QueryMapInfo);
5✔
747
    return true;
5✔
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)
5✔
914
{
915
    if(!VerifyState(ConnectState::QueryMapInfo))
5✔
916
        return true;
×
917

918
    // full path
919
    const std::string portFilename = makePortableFileName(msg.filename);
10✔
920
    if(portFilename.empty())
5✔
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))
5✔
927
    {
928
        OnError(ClientError::InvalidMap);
×
929
        return true;
×
930
    }
931
    mapinfo.filepath = RTTRCONFIG.ExpandPath(s25::folders::mapsPlayed) / portFilename;
10✔
932
    mapinfo.type = msg.mt;
5✔
933

934
    // lua script file path
935
    if(msg.luaLen > 0)
5✔
936
        mapinfo.luaFilepath = bfs::path(mapinfo.filepath).replace_extension("lua");
4✔
937
    else
938
        mapinfo.luaFilepath.clear();
3✔
939

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

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

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

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

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

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

1016
        if(!CreateLobby())
2✔
1017
        {
1018
            OnError(ClientError::MapTransmission);
×
1019
            return true;
×
1020
        }
1021

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

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

1035
void GameClient::OnError(ClientError error)
1✔
1036
{
1037
    if(ci)
1✔
1038
        ci->CI_Error(error);
1✔
1039
    Stop();
1✔
1040
}
1✔
1041

1042
void GameClient::AdvanceState(ConnectState newState)
43✔
1043
{
1044
    connectState = newState;
43✔
1045
    if(ci)
43✔
1046
        ci->CI_NextConnectState(connectState);
43✔
1047
}
43✔
1048

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

1059
bool GameClient::CreateLobby()
4✔
1060
{
1061
    RTTR_Assert(!gameLobby);
4✔
1062

1063
    unsigned numPlayers;
1064

1065
    switch(mapinfo.type)
4✔
1066
    {
1067
        case MapType::OldMap:
4✔
1068
        {
1069
            libsiedler2::Archiv map;
4✔
1070

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

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

1089
            numPlayers = mapinfo.savegame->GetNumPlayers();
×
1090
            mapinfo.title = mapinfo.savegame->GetMapName();
×
1091
            break;
×
1092
        default: return false;
×
1093
    }
1094

1095
    if(GetPlayerId() >= numPlayers)
4✔
1096
        return false;
×
1097

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

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

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

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

1135
    gameLobby->getSettings() = msg.ggs;
4✔
1136

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

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

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

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

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

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

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

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

1223
    return true;
×
1224
}
1225

1226
/**
1227
 *  Pause-Nachricht von Server
1228
 *
1229
 *  @param[in] message Nachricht, welche ausgeführt wird
1230
 */
1231
bool GameClient::OnGameMessage(const GameMessage_Pause& msg)
×
1232
{
1233
    if(state != ClientState::Game)
×
1234
        return true;
×
1235
    if(framesinfo.isPaused == msg.paused)
×
1236
        return true;
×
1237
    framesinfo.isPaused = msg.paused;
×
1238

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

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

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

1260
    // AsyncLog an den Server senden
1261

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

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

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

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

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

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

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

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

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

1340
                    ExecuteNWF();
×
1341

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1550
    replayMode = true;
×
1551

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

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

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

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

1590
    return true;
×
1591
}
1592

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

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

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

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

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

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

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

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

1664
    SetPause(false);
×
1665
    skiptogf = gf;
×
1666

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

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

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

1684
            VIDEODRIVER.SwapBuffers();
×
1685
        }
1686
        ExecuteGameFrame();
×
1687
    }
1688

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

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

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

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

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

1718
    Savegame save;
×
1719

1720
    WritePlayerInfo(save);
×
1721

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

1725
    save.start_gf = GetGFNumber();
×
1726

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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