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

Return-To-The-Roots / s25client / 21240747033

22 Jan 2026 08:04AM UTC coverage: 50.713% (+0.05%) from 50.663%
21240747033

Pull #1679

github

web-flow
Merge f6d3a397c into 12da8bf44
Pull Request #1679: Add all classic S2 cheats and a few more

91 of 167 new or added lines in 12 files covered. (54.49%)

7 existing lines in 5 files now uncovered.

22786 of 44931 relevant lines covered (50.71%)

41969.11 hits per line

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

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

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

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

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

92
GameClient::~GameClient()
17✔
93
{
94
    Stop();
14✔
95
}
17✔
96

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

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

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

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

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

131
    state = ClientState::Connect;
5✔
132
    AdvanceState(ConnectState::Initiated);
5✔
133

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

137
    return true;
5✔
138
}
139

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

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

162
    SocketSet set;
×
163

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

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

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

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

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

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

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

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

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

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

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

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

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

238
    mainPlayer.closeConnection();
5✔
239

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

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

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

250
    aiBattlePlayers_.clear();
5✔
251
}
252

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

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

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

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

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

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

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

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

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

314
    state = ClientState::Loading;
×
315

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

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

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

332
        MapLoader loader(gameWorld);
×
333
        if(!loader.Load(mapinfo.filepath)
×
334
           || (!mapinfo.luaFilepath.empty() && !loader.LoadLuaScript(*game, *this, mapinfo.luaFilepath)))
×
335
        {
336
            OnError(ClientError::InvalidMap);
×
337
            return;
×
338
        }
339
        gameWorld.SetupResources();
×
340
    }
341
    gameWorld.InitAfterLoad();
×
342

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

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

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

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

360
    state = ClientState::Loaded;
×
361

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

721
    mainPlayer.sendMsgAsync(new GameMessage_Server_Password(clientconfig.password));
5✔
722

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

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

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

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

745
    AdvanceState(ConnectState::QueryMapInfo);
5✔
746
    return true;
5✔
747
}
748

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

917
    // full path
918
    const std::string portFilename = makePortableFileName(msg.filename);
10✔
919
    if(portFilename.empty())
5✔
920
    {
921
        LOG.write("Invalid filename received!\n");
×
922
        OnError(ClientError::InvalidMap);
×
923
        return true;
×
924
    }
925
    if(!MapInfo::verifySize(msg.mapLen, msg.luaLen, msg.mapCompressedLen, msg.luaCompressedLen))
5✔
926
    {
927
        OnError(ClientError::InvalidMap);
×
928
        return true;
×
929
    }
930
    mapinfo.filepath = RTTRCONFIG.ExpandPath(s25::folders::mapsPlayed) / portFilename;
10✔
931
    mapinfo.type = msg.mt;
5✔
932

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1062
    unsigned numPlayers;
1063

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1222
    return true;
×
1223
}
1224

1225
/**
1226
 *  Pause-Nachricht von Server
1227
 *
1228
 *  @param[in] message Nachricht, welche ausgeführt wird
1229
 */
1230
bool GameClient::OnGameMessage(const GameMessage_Pause& msg)
×
1231
{
1232
    if(state != ClientState::Game)
×
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.autosave_interval || replayMode)
×
1396
        return;
×
1397

1398
    // Alle .... GF
1399
    if(GetGFNumber() % SETTINGS.interface.autosave_interval == 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
    replayinfo->next_gf = replayinfo->replay.ReadGF();
×
1562

1563
    return true;
×
1564
}
1565

1566
void GameClient::SetAIBattlePlayers(std::vector<AI::Info> aiInfos)
×
1567
{
1568
    aiBattlePlayers_ = std::move(aiInfos);
×
1569
}
×
1570

1571
unsigned GameClient::GetGlobalAnimation(const unsigned short max, const unsigned char factor_numerator,
1✔
1572
                                        const unsigned char factor_denumerator, const unsigned offset)
1573
{
1574
    // Unit for animations is 630ms (dividable by 2, 3, 5, 6, 7, 10, 15, ...)
1575
    // But this also means: If framerate drops below approx. 15Hz, you won't see
1576
    // every frame of an 8-part animation anymore.
1577
    // An animation runs fully in (factor_numerator / factor_denumerator) multiples of 630ms
1578
    const unsigned unit = 630 /*ms*/ * factor_numerator / factor_denumerator;
1✔
1579
    const unsigned currenttime = std::chrono::duration_cast<FramesInfo::milliseconds32_t>(
1✔
1580
                                   (framesinfo.lastTime + framesinfo.frameTime).time_since_epoch())
1✔
1581
                                   .count();
1✔
1582
    return ((currenttime % unit) * max / unit + offset) % max;
1✔
1583
}
1584

1585
unsigned GameClient::Interpolate(unsigned max_val, const GameEvent* ev)
×
1586
{
1587
    RTTR_Assert(ev);
×
1588
    // TODO: Move to some animation system that is part of game
1589
    FramesInfo::milliseconds32_t elapsedTime;
1590
    if(state == ClientState::Game)
×
1591
        elapsedTime = (GetGFNumber() - ev->startGF) * framesinfo.gf_length + framesinfo.frameTime;
×
1592
    else
1593
        elapsedTime = FramesInfo::milliseconds32_t::zero();
×
1594
    FramesInfo::milliseconds32_t duration = ev->length * framesinfo.gf_length;
×
1595
    return helpers::interpolate(0u, max_val, elapsedTime, duration);
×
1596
}
1597

1598
int GameClient::Interpolate(int x1, int x2, const GameEvent* ev)
×
1599
{
1600
    RTTR_Assert(ev);
×
1601
    FramesInfo::milliseconds32_t elapsedTime;
1602
    if(state == ClientState::Game)
×
1603
        elapsedTime = (GetGFNumber() - ev->startGF) * framesinfo.gf_length + framesinfo.frameTime;
×
1604
    else
1605
        elapsedTime = FramesInfo::milliseconds32_t::zero();
×
1606
    FramesInfo::milliseconds32_t duration = ev->length * framesinfo.gf_length;
×
1607
    return helpers::interpolate(x1, x2, elapsedTime, duration);
×
1608
}
1609

1610
void GameClient::ServerLost()
×
1611
{
1612
    OnError(ClientError::ConnectionLost);
×
1613
    // Stop game
1614
    framesinfo.isPaused = true;
×
1615
}
×
1616

1617
/**
1618
 *  überspringt eine bestimmte Anzahl von Gameframes.
1619
 *
1620
 *  @param[in] dest_gf Zielgameframe
1621
 */
1622
void GameClient::SkipGF(unsigned gf, GameWorldView& gwv)
×
1623
{
1624
    if(gf <= GetGFNumber())
×
1625
        return;
×
1626

1627
    unsigned start_ticks = VIDEODRIVER.GetTickCount();
×
1628

1629
    if(!replayMode)
×
1630
    {
1631
        // unpause before skipping
1632
        SetPause(false);
×
1633
        mainPlayer.sendMsgAsync(new GameMessage_SkipToGF(gf));
×
1634
        return;
×
1635
    }
1636

1637
    SetPause(false);
×
1638
    skiptogf = gf;
×
1639

1640
    // GFs überspringen
1641
    for(unsigned i = GetGFNumber(); i < skiptogf; ++i)
×
1642
    {
1643
        if(i % 1000 == 0)
×
1644
        {
1645
            RoadBuildState road;
×
1646
            road.mode = RoadBuildMode::Disabled;
×
1647

1648
            // spiel aktualisieren
1649
            gwv.Draw(road, MapPoint::Invalid(), false);
×
1650

1651
            // text oben noch hinschreiben
1652
            boost::format nwfString(_("current GF: %u - still fast forwarding: %d GFs left (%d %%)"));
×
1653
            nwfString % GetGFNumber() % (gf - i) % (i * 100 / gf);
×
1654
            LargeFont->Draw(DrawPoint(VIDEODRIVER.GetRenderSize() / 2u), nwfString.str(), FontStyle::CENTER,
×
1655
                            COLOR_YELLOW);
1656

1657
            VIDEODRIVER.SwapBuffers();
×
1658
        }
1659
        ExecuteGameFrame();
×
1660
    }
1661

1662
    // Spiel pausieren & text ausgabe wie lang das jetzt gedauert hat
1663
    unsigned ticks = VIDEODRIVER.GetTickCount() - start_ticks;
×
1664
    boost::format text(_("Jump finished (%1$.3g seconds)."));
×
1665
    text % (ticks / 1000.0);
×
1666
    SystemChat(text.str());
×
1667
    SetPause(true);
×
1668
}
1669

1670
void GameClient::SystemChat(const std::string& text)
×
1671
{
1672
    SystemChat(text, GetPlayerId());
×
1673
}
×
1674

1675
void GameClient::SystemChat(const std::string& text, unsigned char fromPlayerIdx)
×
1676
{
1677
    if(ci)
×
1678
        ci->CI_Chat(fromPlayerIdx, ChatDestination::System, text);
×
1679
}
×
1680

1681
bool GameClient::SaveToFile(const boost::filesystem::path& filepath)
×
1682
{
1683
    mainPlayer.sendMsg(GameMessage_Chat(GetPlayerId(), ChatDestination::System, _("Saving game...")));
×
1684

1685
    // Mond malen
1686
    Position moonPos = VIDEODRIVER.GetMousePos();
×
1687
    moonPos.y -= 40;
×
1688
    LOADER.GetImageN("resource", 33)->DrawFull(moonPos);
×
1689
    VIDEODRIVER.SwapBuffers();
×
1690

1691
    Savegame save;
×
1692

1693
    WritePlayerInfo(save);
×
1694

1695
    // GGS-Daten
1696
    save.ggs = game->ggs_;
×
1697

1698
    save.start_gf = GetGFNumber();
×
1699

1700
    // Enable/Disable debugging of savegames
1701
    save.sgd.debugMode = SETTINGS.global.debugMode;
×
1702

1703
    try
1704
    {
1705
        // Spiel serialisieren
1706
        save.sgd.MakeSnapshot(*game);
×
1707
        // Und alles speichern
1708
        return save.Save(filepath, mapinfo.title);
×
1709
    } catch(const std::exception& e)
×
1710
    {
1711
        SystemChat(std::string(_("Error during saving: ")) + e.what());
×
1712
        return false;
×
1713
    }
1714
}
1715

1716
void GameClient::ResetVisualSettings()
×
1717
{
1718
    GetPlayer(GetPlayerId()).FillVisualSettings(visual_settings);
×
1719
}
×
1720

1721
void GameClient::SetPause(bool pause)
×
1722
{
1723
    if(state == ClientState::Stopped)
×
1724
    {
1725
        // We can never continue from pause if stopped as the reason for stopping might be that the game was finished
1726
        // However we allow to pause even when stopped so we can pause after we received the stop notification
1727
        if(!pause)
×
1728
            return;
×
1729
        framesinfo.isPaused = true;
×
1730
        framesinfo.frameTime = FramesInfo::milliseconds32_t::zero();
×
1731
    } else if(replayMode)
×
1732
    {
1733
        framesinfo.isPaused = pause;
×
1734
        framesinfo.frameTime = FramesInfo::milliseconds32_t::zero();
×
1735
    } else if(IsHost())
×
1736
    {
1737
        // Pause instantly
1738
        auto* msg = new GameMessage_Pause(pause);
×
1739
        if(pause)
×
1740
            OnGameMessage(*msg);
×
1741
        mainPlayer.sendMsgAsync(msg);
×
1742
    }
1743
}
1744

1745
void GameClient::SetReplayFOW(const bool hideFOW)
×
1746
{
1747
    if(replayinfo)
×
1748
        replayinfo->all_visible = hideFOW;
×
1749
}
×
1750

1751
bool GameClient::IsReplayFOWDisabled() const
×
1752
{
1753
    return replayMode && replayinfo->all_visible;
×
1754
}
1755

1756
unsigned GameClient::GetLastReplayGF() const
×
1757
{
1758
    return replayinfo ? replayinfo->replay.GetLastGF() : 0u;
×
1759
}
1760

1761
bool GameClient::AddGC(gc::GameCommandPtr gc)
×
1762
{
1763
    // Nicht in der Pause oder wenn er besiegt wurde
1764
    if(framesinfo.isPaused || GetPlayer(GetPlayerId()).IsDefeated() || IsReplayModeOn())
×
1765
        return false;
×
1766

1767
    gameCommands_.push_back(gc);
×
1768
    return true;
×
1769
}
1770

1771
unsigned GameClient::GetNumPlayers() const
×
1772
{
1773
    RTTR_Assert(state == ClientState::Loading || state == ClientState::Loaded || state == ClientState::Game);
×
1774
    return game->world_.GetNumPlayers();
×
1775
}
1776

1777
GamePlayer& GameClient::GetPlayer(const unsigned id)
×
1778
{
1779
    RTTR_Assert(state == ClientState::Loading || state == ClientState::Loaded || state == ClientState::Game);
×
1780
    RTTR_Assert(id < GetNumPlayers());
×
1781
    return game->world_.GetPlayer(id);
×
1782
}
1783

1784
std::unique_ptr<AIPlayer> GameClient::CreateAIPlayer(unsigned playerId, const AI::Info& aiInfo)
×
1785
{
1786
    return AIFactory::Create(aiInfo, playerId, game->world_);
×
1787
}
1788

1789
/// Wandelt eine GF-Angabe in eine Zeitangabe um (HH:MM:SS oder MM:SS wenn Stunden = 0)
1790
std::string GameClient::FormatGFTime(const unsigned gf) const
5✔
1791
{
1792
    using seconds = std::chrono::duration<uint32_t, std::chrono::seconds::period>;
1793
    using hours = std::chrono::duration<uint32_t, std::chrono::hours::period>;
1794
    using minutes = std::chrono::duration<uint32_t, std::chrono::minutes::period>;
1795
    using std::chrono::duration_cast;
1796

1797
    seconds numSeconds = duration_cast<seconds>(gfs_to_duration(gf));
5✔
1798

1799
    hours numHours = duration_cast<hours>(numSeconds);
5✔
1800
    numSeconds -= numHours;
5✔
1801
    minutes numMinutes = duration_cast<minutes>(numSeconds);
5✔
1802
    numSeconds -= numMinutes;
5✔
1803

1804
    // Use hour format only if we have hours
1805
    if(numHours.count())
5✔
1806
        return helpers::format("%u:%02u:%02u", numHours.count(), numMinutes.count(), numSeconds.count());
×
1807
    else
1808
        return helpers::format("%02u:%02u", numMinutes.count(), numSeconds.count());
10✔
1809
}
1810

1811
const boost::filesystem::path& GameClient::GetReplayFilename() const
×
1812
{
1813
    static boost::filesystem::path emptyString;
×
1814
    return replayinfo ? replayinfo->filename : emptyString;
×
1815
}
1816

1817
Replay* GameClient::GetReplay()
×
1818
{
1819
    return replayinfo ? &replayinfo->replay : nullptr;
×
1820
}
1821

1822
std::shared_ptr<const NWFInfo> GameClient::GetNWFInfo() const
×
1823
{
1824
    return nwfInfo;
×
1825
}
1826

1827
/// Is tournament mode activated (0 if not)? Returns the durations of the tournament mode in gf otherwise
1828
unsigned GameClient::GetTournamentModeDuration() const
×
1829
{
1830
    using namespace std::chrono;
1831
    if(game && rttr::enum_cast(game->ggs_.objective) >= rttr::enum_cast(GameObjective::Tournament1)
×
1832
       && static_cast<unsigned>(rttr::enum_cast(game->ggs_.objective))
×
1833
            < rttr::enum_cast(GameObjective::Tournament1) + NUM_TOURNAMENT_MODES)
×
1834
    {
1835
        const auto turnamentMode = rttr::enum_cast(game->ggs_.objective) - rttr::enum_cast(GameObjective::Tournament1);
×
1836
        return duration_to_gfs(TOURNAMENT_MODES_DURATION[turnamentMode]);
×
1837
    } else
1838
        return 0;
×
1839
}
1840

1841
void GameClient::ToggleHumanAIPlayer(const AI::Info& aiInfo)
×
1842
{
1843
    RTTR_Assert(!IsReplayModeOn());
×
1844
    auto it = helpers::find_if(game->aiPlayers_,
×
1845
                               [id = this->GetPlayerId()](const auto& player) { return player.GetPlayerId() == id; });
×
1846
    if(it != game->aiPlayers_.end())
×
1847
        game->aiPlayers_.erase(it);
×
1848
    else
1849
        game->AddAIPlayer(CreateAIPlayer(GetPlayerId(), aiInfo));
×
1850
}
×
1851

1852
void GameClient::RequestSwapToPlayer(const unsigned char newId)
×
1853
{
1854
    if(state != ClientState::Game)
×
1855
        return;
×
1856
    GamePlayer& player = GetPlayer(newId);
×
1857
    if(player.ps == PlayerState::AI && player.aiInfo.type == AI::Type::Dummy)
×
1858
        mainPlayer.sendMsgAsync(new GameMessage_Player_Swap(0xFF, newId));
×
1859
}
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