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

Return-To-The-Roots / s25client / 20538043148

27 Dec 2025 10:46AM UTC coverage: 50.495% (-0.008%) from 50.503%
20538043148

Pull #1849

github

web-flow
Merge 207e98f6b into af2863637
Pull Request #1849: Wrap around from highest to lowest speed with "V" key

4 of 30 new or added lines in 2 files covered. (13.33%)

5 existing lines in 2 files now uncovered.

22559 of 44676 relevant lines covered (50.49%)

36064.69 hits per line

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

20.7
/libs/s25main/network/GameClient.cpp
1
// Copyright (C) 2005 - 2025 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
void copyFileIfPathDifferent(const boost::filesystem::path& src_path, const boost::filesystem::path& dst_path)
×
60
{
61
    if(src_path != dst_path)
×
62
    {
63
        boost::system::error_code ignoredEc;
×
64
        constexpr auto overwrite_existing =
×
65
#if BOOST_VERSION >= 107400
66
          boost::filesystem::copy_options::overwrite_existing;
67
#else
68
          boost::filesystem::copy_option::overwrite_if_exists;
69
#endif
70
        copy_file(src_path, dst_path, overwrite_existing, ignoredEc);
×
71
    }
72
}
×
73
} // namespace
74

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

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

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

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

106
    RTTR_Assert(aiBattlePlayers_.empty());
5✔
107

108
    // Name und Password kopieren
109
    clientconfig.server = server;
5✔
110
    clientconfig.password = password;
5✔
111

112
    clientconfig.servertyp = servertyp;
5✔
113
    clientconfig.port = port;
5✔
114
    clientconfig.isHost = host;
5✔
115

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

125
    state = ClientState::Connect;
5✔
126
    AdvanceState(ConnectState::Initiated);
5✔
127

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

131
    return true;
5✔
132
}
133

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

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

156
    SocketSet set;
×
157

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

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

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

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

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

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

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

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

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

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

217
    if(IsHost())
5✔
218
        GAMESERVER.Stop();
×
219

220
    framesinfo.Clear();
5✔
221
    clientconfig.Clear();
5✔
222
    mapinfo.Clear();
5✔
223

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

232
    mainPlayer.closeConnection();
5✔
233

234
    // clear jump target
235
    skiptogf = 0;
5✔
236

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

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

244
    aiBattlePlayers_.clear();
5✔
245
}
246

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

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

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

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

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

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

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

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

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

308
    state = ClientState::Loading;
×
309

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

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

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

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

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

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

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

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

354
    state = ClientState::Loaded;
×
355

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

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

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

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

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

415
    mainPlayer.playerId = msg.player;
5✔
416

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

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

438
    for(unsigned i = 0; i < gameLobby->getNumPlayers(); ++i)
16✔
439
        gameLobby->getPlayer(i) = msg.playerInfos[i];
12✔
440

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

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

458
bool GameClient::OnGameMessage(const GameMessage_Player_Portrait& msg)
×
459
{
460
    if(state != ClientState::Config)
×
461
        return true;
×
462
    if(msg.player >= gameLobby->getNumPlayers())
×
463
        return true;
×
464
    if(msg.playerPortraitIndex >= Portraits.size())
×
465
        return true;
×
466
    gameLobby->getPlayer(msg.player).portraitIndex = msg.playerPortraitIndex;
×
467
    if(ci)
×
468
        ci->CI_PlayerDataChanged(msg.player);
×
469
    return true;
×
470
}
471

472
///////////////////////////////////////////////////////////////////////////////
473
/// player joined
474
/// @param message  Nachricht, welche ausgeführt wird
475
bool GameClient::OnGameMessage(const GameMessage_Player_New& msg)
×
476
{
477
    if(state != ClientState::Config)
×
478
        return true;
×
479

480
    if(msg.player >= gameLobby->getNumPlayers())
×
481
        return true;
×
482

483
    JoinPlayerInfo& playerInfo = gameLobby->getPlayer(msg.player);
×
484

485
    playerInfo.name = msg.name;
×
486
    playerInfo.ps = PlayerState::Occupied;
×
487
    playerInfo.ping = 0;
×
488

489
    if(ci)
×
490
        ci->CI_NewPlayer(msg.player);
×
491
    return true;
×
492
}
493

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

512
    if(ci)
×
513
        ci->CI_PingChanged(msg.player, msg.ping);
×
514
    return true;
×
515
}
516

517
/**
518
 *  Player-Toggle-State-Nachricht.
519
 */
520
bool GameClient::OnGameMessage(const GameMessage_Player_State& msg)
×
521
{
522
    if(state != ClientState::Config)
×
523
        return true;
×
524

525
    if(msg.player >= gameLobby->getNumPlayers())
×
526
        return true;
×
527

528
    JoinPlayerInfo& playerInfo = gameLobby->getPlayer(msg.player);
×
529
    bool wasUsed = playerInfo.isUsed();
×
530
    playerInfo.ps = msg.ps;
×
531
    playerInfo.aiInfo = msg.aiInfo;
×
532

533
    if(ci)
×
534
    {
535
        if(playerInfo.isUsed())
×
536
            ci->CI_NewPlayer(msg.player);
×
537
        else if(wasUsed)
×
538
            ci->CI_PlayerLeft(msg.player);
×
539
        else
540
            ci->CI_PlayerDataChanged(msg.player);
×
541
    }
542
    return true;
×
543
}
544

545
///////////////////////////////////////////////////////////////////////////////
546
/// nation button gedrückt
547
/// @param message  Nachricht, welche ausgeführt wird
548
bool GameClient::OnGameMessage(const GameMessage_Player_Nation& msg)
×
549
{
550
    if(state != ClientState::Config)
×
551
        return true;
×
552

553
    if(msg.player >= gameLobby->getNumPlayers())
×
554
        return true;
×
555

556
    gameLobby->getPlayer(msg.player).nation = msg.nation;
×
557

558
    if(ci)
×
559
        ci->CI_PlayerDataChanged(msg.player);
×
560
    return true;
×
561
}
562

563
///////////////////////////////////////////////////////////////////////////////
564
/// team button gedrückt
565
/// @param message  Nachricht, welche ausgeführt wird
566
bool GameClient::OnGameMessage(const GameMessage_Player_Team& msg)
×
567
{
568
    if(state != ClientState::Config)
×
569
        return true;
×
570

571
    if(msg.player >= gameLobby->getNumPlayers())
×
572
        return true;
×
573

574
    gameLobby->getPlayer(msg.player).team = msg.team;
×
575

576
    if(ci)
×
577
        ci->CI_PlayerDataChanged(msg.player);
×
578
    return true;
×
579
}
580

581
///////////////////////////////////////////////////////////////////////////////
582
/// color button gedrückt
583
/// @param message  Nachricht, welche ausgeführt wird
584
bool GameClient::OnGameMessage(const GameMessage_Player_Color& msg)
×
585
{
586
    if(state != ClientState::Config)
×
587
        return true;
×
588

589
    if(msg.player >= gameLobby->getNumPlayers())
×
590
        return true;
×
591

592
    gameLobby->getPlayer(msg.player).color = msg.color;
×
593

594
    if(ci)
×
595
        ci->CI_PlayerDataChanged(msg.player);
×
596
    return true;
×
597
}
598

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

609
    if(msg.player >= gameLobby->getNumPlayers())
×
610
        return true;
×
611

612
    gameLobby->getPlayer(msg.player).isReady = msg.ready;
×
613

614
    if(ci)
×
615
        ci->CI_ReadyChanged(msg.player, msg.ready);
×
616
    return true;
×
617
}
618

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

647
    if(ci)
×
648
        ci->CI_PlayerLeft(msg.player);
×
649
    return true;
×
650
}
651

652
bool GameClient::OnGameMessage(const GameMessage_Player_Swap& msg)
×
653
{
654
    LOG.writeToFile("<<< NMS_PLAYER_SWAP(%u, %u)\n") % unsigned(msg.player) % unsigned(msg.player2);
×
655

656
    if(state == ClientState::Config)
×
657
    {
658
        if(msg.player >= gameLobby->getNumPlayers() || msg.player2 >= gameLobby->getNumPlayers())
×
659
            return true;
×
660

661
        // During preparation just swap the players
662
        using std::swap;
663
        swap(gameLobby->getPlayer(msg.player), gameLobby->getPlayer(msg.player2));
×
664
        // Some things cannot be changed in savegames
665
        if(mapinfo.type == MapType::Savegame)
×
666
            gameLobby->getPlayer(msg.player).FixSwappedSaveSlot(gameLobby->getPlayer(msg.player2));
×
667

668
        // Evtl. sind wir betroffen?
669
        if(mainPlayer.playerId == msg.player)
×
670
            mainPlayer.playerId = msg.player2;
×
671
        else if(mainPlayer.playerId == msg.player2)
×
672
            mainPlayer.playerId = msg.player;
×
673

674
        if(ci)
×
675
            ci->CI_PlayersSwapped(msg.player, msg.player2);
×
676
    } else if(state == ClientState::Loading || state == ClientState::Loaded || state == ClientState::Game)
×
677
        ChangePlayerIngame(msg.player, msg.player2);
×
678
    else
679
        return true;
×
680
    mainPlayer.sendMsgAsync(new GameMessage_Player_SwapConfirm(msg.player, msg.player2));
×
681
    return true;
×
682
}
683

684
/**
685
 *  Server-Typ-Nachricht.
686
 */
687
bool GameClient::OnGameMessage(const GameMessage_Server_TypeOK& msg)
5✔
688
{
689
    if(!VerifyState(ConnectState::VerifyServer))
5✔
690
        return true;
×
691

692
    using StatusCode = GameMessage_Server_TypeOK::StatusCode;
693
    switch(msg.err_code)
5✔
694
    {
695
        case StatusCode::Ok: break;
5✔
696

697
        default:
×
698
        case StatusCode::InvalidServerType:
699
        {
700
            OnError(ClientError::InvalidServerType);
×
701
            return true;
×
702
        }
703
        break;
704

705
        case StatusCode::WrongVersion:
×
706
        {
707
            LOG.write(_("Version mismatch. Server version: %1%, your version %2%")) % msg.version
×
708
              % rttr::version::GetRevision();
×
709
            OnError(ClientError::WrongVersion);
×
710
            return true;
×
711
        }
712
        break;
713
    }
714

715
    mainPlayer.sendMsgAsync(new GameMessage_Server_Password(clientconfig.password));
5✔
716

717
    AdvanceState(ConnectState::QueryPw);
5✔
718
    return true;
5✔
719
}
720

721
/**
722
 *  Server-Passwort-Nachricht.
723
 */
724
bool GameClient::OnGameMessage(const GameMessage_Server_Password& msg)
5✔
725
{
726
    if(!VerifyState(ConnectState::QueryPw))
5✔
727
        return true;
×
728

729
    if(msg.password != "true")
5✔
730
    {
731
        OnError(ClientError::WrongPassword);
×
732
        return true;
×
733
    }
734

735
    mainPlayer.sendMsgAsync(new GameMessage_Player_Name(0xFF, SETTINGS.lobby.name));
5✔
736
    mainPlayer.sendMsgAsync(new GameMessage_Player_Portrait(0xFF, SETTINGS.lobby.portraitIndex));
5✔
737
    mainPlayer.sendMsgAsync(new GameMessage_MapRequest(true));
5✔
738

739
    AdvanceState(ConnectState::QueryMapInfo);
5✔
740
    return true;
5✔
741
}
742

743
/**
744
 *  Server-Name-Nachricht.
745
 */
746
bool GameClient::OnGameMessage(const GameMessage_Server_Name& msg)
4✔
747
{
748
    if(!VerifyState(ConnectState::QueryServerName))
4✔
749
        return true;
×
750
    clientconfig.gameName = msg.name;
4✔
751

752
    AdvanceState(ConnectState::QueryPlayerList);
4✔
753
    return true;
4✔
754
}
755

756
/**
757
 *  Server-Start-Nachricht
758
 */
759
bool GameClient::OnGameMessage(const GameMessage_Server_Start& msg)
×
760
{
761
    if(state != ClientState::Config)
×
762
        return true;
×
763

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

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

794
        /// Mit im Replay aufzeichnen
795
        if(replayinfo && replayinfo->replay.IsRecording())
×
796
            replayinfo->replay.AddChatCommand(GetGFNumber(), msg.player, msg.destination, msg.text);
×
797

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

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

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

823
        if(!isValidRecipient(GetPlayerId()))
×
824
            return true;
×
825
    } else if(state == ClientState::Config)
×
826
    {
827
        // GameLobby message: Just check for valid player
828
        if(msg.player >= gameLobby->getNumPlayers())
×
829
            return true;
×
830
    } else
831
        return true;
×
832

833
    if(ci)
×
834
        ci->CI_Chat(msg.player, msg.destination, msg.text);
×
835
    return true;
×
836
}
837

838
/**
839
 *  Server-Async-Nachricht.
840
 */
841
bool GameClient::OnGameMessage(const GameMessage_Server_Async& msg)
×
842
{
843
    if(state != ClientState::Game)
×
844
        return true;
×
845

846
    // Liste mit Namen und Checksummen erzeugen
847
    std::stringstream checksum_list;
×
848
    for(unsigned i = 0; i < msg.checksums.size(); ++i)
×
849
    {
850
        checksum_list << GetPlayer(i).name << ": " << msg.checksums[i];
×
851
        if(i + 1 < msg.checksums.size())
×
852
            checksum_list << ", ";
×
853
    }
854

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

859
    // Messenger im Game
860
    if(ci)
×
861
        ci->CI_Async(checksum_list.str());
×
862

863
    std::string fileName = s25util::Time::FormatTime("async_%Y-%m-%d_%H-%i-%s");
×
864
    fileName += "_" + s25util::toStringClassic(GetPlayerId()) + "_";
×
865
    fileName += GetPlayer(GetPlayerId()).name;
×
866

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

876
/**
877
 *  Server-Countdown-Nachricht.
878
 */
879
bool GameClient::OnGameMessage(const GameMessage_Countdown& msg)
×
880
{
881
    if(state != ClientState::Config)
×
882
        return true;
×
883
    if(ci)
×
884
        ci->CI_Countdown(msg.countdown);
×
885
    return true;
×
886
}
887

888
/**
889
 *  Server-Cancel-Countdown-Nachricht.
890
 */
891
bool GameClient::OnGameMessage(const GameMessage_CancelCountdown& msg)
×
892
{
893
    if(state != ClientState::Config)
×
894
        return true;
×
895
    if(ci)
×
896
        ci->CI_CancelCountdown(msg.error);
×
897
    return true;
×
898
}
899

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

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

927
    // lua script file path
928
    if(msg.luaLen > 0)
5✔
929
        mapinfo.luaFilepath = bfs::path(mapinfo.filepath).replace_extension("lua");
4✔
930
    else
931
        mapinfo.luaFilepath.clear();
3✔
932

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

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

966
///////////////////////////////////////////////////////////////////////////////
967
/// Kartendaten
968
/// @param message  Nachricht, welche ausgeführt wird
969
bool GameClient::OnGameMessage(const GameMessage_Map_Data& msg)
8✔
970
{
971
    if(!VerifyState(ConnectState::ReceiveMap))
8✔
972
        return true;
×
973

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

983
    uint32_t totalSize = mapinfo.mapData.data.size();
7✔
984
    uint32_t receivedSize = msg.offset + msg.data.size();
7✔
985
    if(!mapinfo.luaFilepath.empty())
7✔
986
    {
987
        totalSize += mapinfo.luaData.data.size();
4✔
988
        // Assumes lua data comes after the map data
989
        if(!msg.isMapData)
4✔
990
            receivedSize += mapinfo.mapData.data.size();
2✔
991
    }
992
    if(ci)
7✔
993
        ci->CI_MapPartReceived(receivedSize, totalSize);
7✔
994

995
    if(receivedSize == totalSize)
7✔
996
    {
997
        if(!mapinfo.mapData.DecompressToFile(mapinfo.filepath, &mapinfo.mapChecksum))
2✔
998
        {
999
            OnError(ClientError::MapTransmission);
×
1000
            return true;
×
1001
        }
1002
        if(!mapinfo.luaFilepath.empty() && !mapinfo.luaData.DecompressToFile(mapinfo.luaFilepath, &mapinfo.luaChecksum))
2✔
1003
        {
1004
            OnError(ClientError::MapTransmission);
×
1005
            return true;
×
1006
        }
1007
        RTTR_Assert(!mapinfo.luaFilepath.empty() || mapinfo.luaChecksum == 0);
2✔
1008

1009
        if(!CreateLobby())
2✔
1010
        {
1011
            OnError(ClientError::MapTransmission);
×
1012
            return true;
×
1013
        }
1014

1015
        mainPlayer.sendMsgAsync(new GameMessage_Map_Checksum(mapinfo.mapChecksum, mapinfo.luaChecksum));
2✔
1016
        AdvanceState(ConnectState::VerifyMap);
2✔
1017
    }
1018
    return true;
7✔
1019
}
1020

1021
bool GameClient::OnGameMessage(const GameMessage_SkipToGF& msg)
×
1022
{
1023
    skiptogf = msg.targetGF;
×
1024
    LOG.write("Jumping from GF %1% to GF %2%\n") % GetGFNumber() % skiptogf;
×
1025
    return true;
×
1026
}
1027

1028
void GameClient::OnError(ClientError error)
1✔
1029
{
1030
    if(ci)
1✔
1031
        ci->CI_Error(error);
1✔
1032
    Stop();
1✔
1033
}
1✔
1034

1035
void GameClient::AdvanceState(ConnectState newState)
43✔
1036
{
1037
    connectState = newState;
43✔
1038
    if(ci)
43✔
1039
        ci->CI_NextConnectState(connectState);
43✔
1040
}
43✔
1041

1042
bool GameClient::VerifyState(ConnectState expectedState)
44✔
1043
{
1044
    if(state != ClientState::Connect || connectState != expectedState)
44✔
1045
    {
1046
        OnError(ClientError::InvalidMessage);
×
1047
        return false;
×
1048
    }
1049
    return true;
44✔
1050
}
1051

1052
bool GameClient::CreateLobby()
4✔
1053
{
1054
    RTTR_Assert(!gameLobby);
4✔
1055

1056
    unsigned numPlayers;
1057

1058
    switch(mapinfo.type)
4✔
1059
    {
1060
        case MapType::OldMap:
4✔
1061
        {
1062
            libsiedler2::Archiv map;
4✔
1063

1064
            // Karteninformationen laden
1065
            if(libsiedler2::loader::LoadMAP(mapinfo.filepath, map, true) != 0)
4✔
1066
            {
1067
                LOG.write("GameClient::OnMapData: ERROR: Map %1%, couldn't load header!\n") % mapinfo.filepath;
×
1068
                return false;
×
1069
            }
1070

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

1082
            numPlayers = mapinfo.savegame->GetNumPlayers();
×
1083
            mapinfo.title = mapinfo.savegame->GetMapName();
×
1084
            break;
×
1085
        default: return false;
×
1086
    }
1087

1088
    if(GetPlayerId() >= numPlayers)
4✔
1089
        return false;
×
1090

1091
    gameLobby = std::make_shared<GameLobby>(mapinfo.type == MapType::Savegame, IsHost(), numPlayers);
4✔
1092
    return true;
4✔
1093
}
1094

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

1104
    if(msg.correct)
4✔
1105
        AdvanceState(ConnectState::QueryServerName);
4✔
1106
    else
1107
    {
1108
        gameLobby.reset();
×
1109
        if(msg.retryAllowed)
×
1110
        {
1111
            mainPlayer.sendMsgAsync(new GameMessage_MapRequest(false));
×
1112
            AdvanceState(ConnectState::ReceiveMap);
×
1113
        } else
1114
            OnError(ClientError::MapTransmission);
×
1115
    }
1116
    return true;
4✔
1117
}
1118

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

1128
    gameLobby->getSettings() = msg.ggs;
4✔
1129

1130
    if(state == ClientState::Connect)
4✔
1131
    {
1132
        state = ClientState::Config;
4✔
1133
        AdvanceState(ConnectState::Finished);
4✔
1134
    } else if(ci)
×
1135
        ci->CI_GGSChanged(msg.ggs);
×
1136
    return true;
4✔
1137
}
1138

1139
bool GameClient::OnGameMessage(const GameMessage_RemoveLua&)
×
1140
{
1141
    if(state != ClientState::Connect && state != ClientState::Config)
×
1142
        return true;
×
1143
    mapinfo.luaFilepath.clear();
×
1144
    mapinfo.luaData.Clear();
×
1145
    mapinfo.luaChecksum = 0;
×
1146
    return true;
×
1147
}
1148

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

NEW
1165
void GameClient::IncreaseSpeed(const bool wraparound)
×
1166
{
1167
    static_assert(MIN_SPEED >= SPEED_GF_LENGTHS[GameSpeed::VeryFast], "Not all speeds reachable");
1168
    const bool debugMode =
×
1169
#ifndef NDEBUG
1170
      true;
1171
#else
1172
      false;
1173
#endif
1174
    const auto oldSpeed = framesinfo.gfLengthReq;
×
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_DEBUG)
×
NEW
1179
        framesinfo.gfLengthReq = MIN_SPEED; // NOLINT(bugprone-branch-clone)
×
1180
    else if(framesinfo.gfLengthReq >= MAX_SPEED + SPEED_STEP)
×
1181
        framesinfo.gfLengthReq -= SPEED_STEP;
×
NEW
1182
    else if((replayMode || debugMode) && framesinfo.gfLengthReq > MAX_SPEED_DEBUG) // 1 more step in debug/replay mode
×
1183
        framesinfo.gfLengthReq = MAX_SPEED_DEBUG;
×
NEW
1184
    else if(wraparound) // Highest speed, wrap around to slowest if requested
×
NEW
1185
        framesinfo.gfLengthReq = MIN_SPEED;
×
1186

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

1193
void GameClient::DecreaseSpeed()
×
1194
{
1195
    static_assert(MAX_SPEED <= SPEED_GF_LENGTHS[GameSpeed::VerySlow], "Not all speeds reachable");
1196
    const bool debugMode =
×
1197
#ifndef NDEBUG
1198
      true;
1199
#else
1200
      false;
1201
#endif
1202

1203
    const auto oldSpeed = framesinfo.gfLengthReq;
×
1204

1205
    // Go from debug speed directly back to max speed, else in fixed steps
1206
    static_assert(MAX_SPEED_DEBUG < MAX_SPEED);
1207
    if(framesinfo.gfLengthReq == MAX_SPEED_DEBUG)
×
1208
        framesinfo.gfLengthReq = MAX_SPEED;
×
1209
    else if(framesinfo.gfLengthReq + SPEED_STEP <= MIN_SPEED)
×
1210
        framesinfo.gfLengthReq += SPEED_STEP;
×
1211
    else
1212
        framesinfo.gfLengthReq = (replayMode || debugMode) ? MIN_SPEED_DEBUG : MIN_SPEED;
×
1213

1214
    if(replayMode)
×
1215
        framesinfo.gf_length = framesinfo.gfLengthReq;
×
1216
    else if(framesinfo.gfLengthReq != oldSpeed)
×
1217
        mainPlayer.sendMsgAsync(new GameMessage_Speed(framesinfo.gfLengthReq));
×
1218
}
×
1219

1220
///////////////////////////////////////////////////////////////////////////////
1221
/// NFC Done vom Server
1222
/// @param message  Nachricht, welche ausgeführt wird
1223
bool GameClient::OnGameMessage(const GameMessage_Server_NWFDone& msg)
×
1224
{
1225
    if(!nwfInfo)
×
1226
        return true;
×
1227

1228
    if(!nwfInfo->addServerInfo(NWFServerInfo(msg.gf, msg.gf_length, msg.nextNWF)))
×
1229
    {
1230
        RTTR_Assert(false);
×
1231
        LOG.write("Failed to add server info. Invalid server?\n");
1232
    }
1233

1234
    return true;
×
1235
}
1236

1237
/**
1238
 *  Pause-Nachricht von Server
1239
 *
1240
 *  @param[in] message Nachricht, welche ausgeführt wird
1241
 */
1242
bool GameClient::OnGameMessage(const GameMessage_Pause& msg)
×
1243
{
1244
    if(state != ClientState::Game)
×
1245
        return true;
×
1246
    if(framesinfo.isPaused == msg.paused)
×
1247
        return true;
×
1248
    framesinfo.isPaused = msg.paused;
×
1249

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

1252
    if(msg.paused)
×
1253
        ci->CI_GamePaused();
×
1254
    else
1255
        ci->CI_GameResumed();
×
1256
    return true;
×
1257
}
1258

1259
/**
1260
 *  NFC GetAsyncLog von Server
1261
 *
1262
 *  @param[in] message Nachricht, welche ausgeführt wird
1263
 */
1264
bool GameClient::OnGameMessage(const GameMessage_GetAsyncLog& /*msg*/)
×
1265
{
1266
    if(state != ClientState::Game)
×
1267
        return true;
×
1268
    std::string systemInfo = System::getCompilerName() + " @ " + System::getOSName();
×
1269
    mainPlayer.sendMsgAsync(new GameMessage_AsyncLog(systemInfo));
×
1270

1271
    // AsyncLog an den Server senden
1272

1273
    std::vector<RandomEntry> async_log = RANDOM.GetAsyncLog();
×
1274

1275
    // stückeln...
1276
    std::vector<RandomEntry> part;
×
1277
    for(auto& it : async_log)
×
1278
    {
1279
        part.push_back(it);
×
1280

1281
        if(part.size() == 10)
×
1282
        {
1283
            mainPlayer.sendMsgAsync(new GameMessage_AsyncLog(part, false));
×
1284
            part.clear();
×
1285
        }
1286
    }
1287

1288
    mainPlayer.sendMsgAsync(new GameMessage_AsyncLog(part, true));
×
1289
    return true;
×
1290
}
1291

1292
///////////////////////////////////////////////////////////////////////////////
1293
/// testet ob ein Netwerkframe abgelaufen ist und führt dann ggf die Befehle aus
1294
void GameClient::ExecuteGameFrame()
×
1295
{
1296
    if(framesinfo.isPaused)
×
1297
        return; // Pause
×
1298

1299
    FramesInfo::UsedClock::time_point currentTime = FramesInfo::UsedClock::now();
×
1300

1301
    if(framesinfo.forcePauseLen.count())
×
1302
    {
1303
        if(currentTime - framesinfo.forcePauseStart > framesinfo.forcePauseLen)
×
1304
            framesinfo.forcePauseLen = FramesInfo::milliseconds32_t::zero();
×
1305
        else
1306
            return; // Pause
×
1307
    }
1308

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

1349
                    RTTR_Assert(nwfInfo->getServerInfo().gf == curGF);
×
1350

1351
                    ExecuteNWF();
×
1352

1353
                    FramesInfo::milliseconds32_t oldGFLen = framesinfo.gf_length;
×
1354
                    nwfInfo->execute(framesinfo);
×
1355
                    if(oldGFLen != framesinfo.gf_length)
×
1356
                    {
1357
                        LOG.write("Client: Speed changed at %1% from %2% to %3% (NWF: %4%)\n") % curGF
×
1358
                          % helpers::withUnit(oldGFLen) % helpers::withUnit(framesinfo.gf_length)
×
1359
                          % framesinfo.nwf_length;
×
1360
                    }
1361
                }
1362

1363
                NextGF(isNWF);
×
1364
                RTTR_Assert(curGF <= nwfInfo->getNextNWF());
×
1365
                HandleAutosave();
×
1366

1367
                // GF-Ende im Replay aktualisieren
1368
                if(replayinfo && replayinfo->replay.IsRecording())
×
1369
                    replayinfo->replay.UpdateLastGF(curGF);
×
1370
            }
1371

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

1393
        RTTR_Assert(framesinfo.gf_length > DurationType::zero());
×
1394
        const auto maxFrameTime = framesinfo.gf_length - DurationType(1);
×
1395

1396
        if(framesinfo.frameTime > maxLackFrames * framesinfo.gf_length)
×
1397
            framesinfo.lastTime += framesinfo.frameTime - maxFrameTime; // Skip simulation time until caught up
×
1398
        framesinfo.frameTime = maxFrameTime;
×
1399
    }
1400
    // This is assumed by drawing code for interpolation
1401
    RTTR_Assert(framesinfo.frameTime < framesinfo.gf_length);
×
1402
}
1403

1404
void GameClient::HandleAutosave()
×
1405
{
1406
    // If inactive or during replay -> no autosave
1407
    if(!SETTINGS.interface.autosave_interval || replayMode)
×
1408
        return;
×
1409

1410
    // Alle .... GF
1411
    if(GetGFNumber() % SETTINGS.interface.autosave_interval == 0)
×
1412
    {
1413
        std::string filename;
×
1414
        if(mapinfo.title.empty())
×
1415
            filename = std::string(_("Auto-Save")) + ".sav";
×
1416
        else
1417
            filename = mapinfo.title + " (" + _("Auto-Save") + ").sav";
×
1418

1419
        SaveToFile(RTTRCONFIG.ExpandPath(s25::folders::save) / filename);
×
1420
    }
1421
}
1422

1423
/// Führt notwendige Dinge für nächsten GF aus
1424
void GameClient::NextGF(bool wasNWF)
×
1425
{
1426
    for(AIPlayer& ai : game->aiPlayers_)
×
1427
        ai.RunGF(GetGFNumber(), wasNWF);
×
1428
    game->RunGF();
×
1429
}
×
1430

1431
void GameClient::ExecuteAllGCs(uint8_t playerId, const PlayerGameCommands& gcs)
×
1432
{
1433
    for(const gc::GameCommandPtr& gc : gcs.gcs)
×
1434
        gc->Execute(game->world_, playerId);
×
1435
}
×
1436

1437
void GameClient::SendNothingNC(uint8_t player)
×
1438
{
1439
    mainPlayer.sendMsgAsync(
×
1440
      new GameMessage_GameCommand(player, AsyncChecksum::create(*game), std::vector<gc::GameCommandPtr>()));
×
1441
}
×
1442

1443
void GameClient::WritePlayerInfo(SavedFile& file)
×
1444
{
1445
    RTTR_Assert(state == ClientState::Loading || state == ClientState::Loaded || state == ClientState::Game);
×
1446
    // Spielerdaten
1447
    for(unsigned i = 0; i < GetNumPlayers(); ++i)
×
1448
        file.AddPlayer(GetPlayer(i));
×
1449
}
×
1450

1451
void GameClient::OnGameStart()
6✔
1452
{
1453
    if(state == ClientState::Loaded)
6✔
1454
    {
1455
        GAMEMANAGER.ResetAverageGFPS();
×
1456
        framesinfo.lastTime = FramesInfo::UsedClock::now();
×
1457
        state = ClientState::Game;
×
1458
        if(ci)
×
1459
            ci->CI_GameStarted();
×
1460
    } else if(state == ClientState::Game && !game->IsStarted())
6✔
1461
    {
1462
        framesinfo.isPaused = replayMode;
×
1463
        game->Start(!!mapinfo.savegame);
×
1464
    }
1465
}
6✔
1466

1467
void GameClient::StartReplayRecording(const unsigned random_init)
×
1468
{
1469
    replayinfo = std::make_unique<ReplayInfo>();
×
1470
    replayinfo->filename = s25util::Time::FormatTime("%Y-%m-%d_%H-%i-%s") + ".rpl";
×
1471

1472
    WritePlayerInfo(replayinfo->replay);
×
1473
    replayinfo->replay.ggs = game->ggs_;
×
1474

1475
    if(!replayinfo->replay.StartRecording(RTTRCONFIG.ExpandPath(s25::folders::replays) / replayinfo->filename, mapinfo,
×
1476
                                          random_init))
1477
    {
1478
        LOG.write(_("Replayfile couldn't be opened. No replay will be recorded\n"));
×
1479
        replayinfo.reset();
×
1480
    }
1481
}
×
1482

1483
bool GameClient::StartReplay(const boost::filesystem::path& path)
×
1484
{
1485
    RTTR_Assert(state == ClientState::Stopped);
×
1486
    mapinfo.Clear();
×
1487
    replayinfo = std::make_unique<ReplayInfo>();
×
1488

1489
    if(!replayinfo->replay.LoadHeader(path) || !replayinfo->replay.LoadGameData(mapinfo)) //-V807
×
1490
    {
1491
        LOG.write(_("Invalid Replay %1%! Reason: %2%\n")) % path
×
1492
          % (replayinfo->replay.GetLastErrorMsg().empty() ? _("Unknown") : replayinfo->replay.GetLastErrorMsg());
×
1493
        OnError(ClientError::InvalidMap);
×
1494
        replayinfo.reset();
×
1495
        return false;
×
1496
    }
1497
    replayinfo->filename = path.filename();
×
1498

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

1501
    for(unsigned i = 0; i < replayinfo->replay.GetNumPlayers(); ++i)
×
1502
        gameLobby->getPlayer(i) = JoinPlayerInfo(replayinfo->replay.GetPlayer(i));
×
1503

1504
    bool playerFound = false;
×
1505
    // Find a player to spectate from
1506
    // First find a human player
1507
    for(unsigned char i = 0; i < gameLobby->getNumPlayers(); ++i)
×
1508
    {
1509
        if(gameLobby->getPlayer(i).ps == PlayerState::Occupied)
×
1510
        {
1511
            mainPlayer.playerId = i;
×
1512
            playerFound = true;
×
1513
            break;
×
1514
        }
1515
    }
1516
    if(!playerFound)
×
1517
    {
1518
        // If no human found, take the first AI
1519
        for(unsigned char i = 0; i < gameLobby->getNumPlayers(); ++i)
×
1520
        {
1521
            if(gameLobby->getPlayer(i).ps == PlayerState::AI)
×
1522
            {
1523
                mainPlayer.playerId = i;
×
1524
                break;
×
1525
            }
1526
        }
1527
    }
1528

1529
    // GGS-Daten
1530
    gameLobby->getSettings() = replayinfo->replay.ggs;
×
1531

1532
    switch(mapinfo.type)
×
1533
    {
1534
        default: break;
×
1535
        case MapType::OldMap:
×
1536
        {
1537
            // Richtigen Pfad zur Map erstellen
1538
            bfs::path mapFilePath = RTTRCONFIG.ExpandPath(s25::folders::mapsPlayed) / mapinfo.filepath.filename();
×
1539
            mapinfo.filepath = mapFilePath;
×
1540
            if(!mapinfo.mapData.DecompressToFile(mapinfo.filepath))
×
1541
            {
1542
                LOG.write(_("Error decompressing map file"));
×
1543
                OnError(ClientError::MapTransmission);
×
1544
                return false;
×
1545
            }
1546
            if(mapinfo.luaData.uncompressedLength)
×
1547
            {
1548
                mapinfo.luaFilepath = mapFilePath.replace_extension("lua");
×
1549
                if(!mapinfo.luaData.DecompressToFile(mapinfo.luaFilepath))
×
1550
                {
1551
                    LOG.write(_("Error decompressing lua file"));
×
1552
                    OnError(ClientError::MapTransmission);
×
1553
                    return false;
×
1554
                }
1555
            }
1556
        }
1557
        break;
×
1558
        case MapType::Savegame: break;
×
1559
    }
1560

1561
    replayMode = true;
×
1562

1563
    try
1564
    {
1565
        StartGame(replayinfo->replay.getSeed());
×
1566
    } catch(const SerializedGameData::Error& error)
×
1567
    {
1568
        LOG.write(_("Error when loading game from replay: %s\n")) % error.what();
×
1569
        OnError(ClientError::InvalidMap);
×
1570
        return false;
×
1571
    }
1572

1573
    replayinfo->next_gf = replayinfo->replay.ReadGF();
×
1574

1575
    return true;
×
1576
}
1577

1578
void GameClient::SetAIBattlePlayers(std::vector<AI::Info> aiInfos)
×
1579
{
1580
    aiBattlePlayers_ = std::move(aiInfos);
×
1581
}
×
1582

1583
unsigned GameClient::GetGlobalAnimation(const unsigned short max, const unsigned char factor_numerator,
1✔
1584
                                        const unsigned char factor_denumerator, const unsigned offset)
1585
{
1586
    // Unit for animations is 630ms (dividable by 2, 3, 5, 6, 7, 10, 15, ...)
1587
    // But this also means: If framerate drops below approx. 15Hz, you won't see
1588
    // every frame of an 8-part animation anymore.
1589
    // An animation runs fully in (factor_numerator / factor_denumerator) multiples of 630ms
1590
    const unsigned unit = 630 /*ms*/ * factor_numerator / factor_denumerator;
1✔
1591
    const unsigned currenttime = std::chrono::duration_cast<FramesInfo::milliseconds32_t>(
1✔
1592
                                   (framesinfo.lastTime + framesinfo.frameTime).time_since_epoch())
1✔
1593
                                   .count();
1✔
1594
    return ((currenttime % unit) * max / unit + offset) % max;
1✔
1595
}
1596

1597
unsigned GameClient::Interpolate(unsigned max_val, const GameEvent* ev)
×
1598
{
1599
    RTTR_Assert(ev);
×
1600
    // TODO: Move to some animation system that is part of game
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(0u, max_val, elapsedTime, duration);
×
1608
}
1609

1610
int GameClient::Interpolate(int x1, int x2, const GameEvent* ev)
×
1611
{
1612
    RTTR_Assert(ev);
×
1613
    FramesInfo::milliseconds32_t elapsedTime;
1614
    if(state == ClientState::Game)
×
1615
        elapsedTime = (GetGFNumber() - ev->startGF) * framesinfo.gf_length + framesinfo.frameTime;
×
1616
    else
1617
        elapsedTime = FramesInfo::milliseconds32_t::zero();
×
1618
    FramesInfo::milliseconds32_t duration = ev->length * framesinfo.gf_length;
×
1619
    return helpers::interpolate(x1, x2, elapsedTime, duration);
×
1620
}
1621

1622
void GameClient::ServerLost()
×
1623
{
1624
    OnError(ClientError::ConnectionLost);
×
1625
    // Stop game
1626
    framesinfo.isPaused = true;
×
1627
}
×
1628

1629
/**
1630
 *  überspringt eine bestimmte Anzahl von Gameframes.
1631
 *
1632
 *  @param[in] dest_gf Zielgameframe
1633
 */
1634
void GameClient::SkipGF(unsigned gf, GameWorldView& gwv)
×
1635
{
1636
    if(gf <= GetGFNumber())
×
1637
        return;
×
1638

1639
    unsigned start_ticks = VIDEODRIVER.GetTickCount();
×
1640

1641
    if(!replayMode)
×
1642
    {
1643
        // unpause before skipping
1644
        SetPause(false);
×
1645
        mainPlayer.sendMsgAsync(new GameMessage_SkipToGF(gf));
×
1646
        return;
×
1647
    }
1648

1649
    SetPause(false);
×
1650
    skiptogf = gf;
×
1651

1652
    // GFs überspringen
1653
    for(unsigned i = GetGFNumber(); i < skiptogf; ++i)
×
1654
    {
1655
        if(i % 1000 == 0)
×
1656
        {
1657
            RoadBuildState road;
×
1658
            road.mode = RoadBuildMode::Disabled;
×
1659

1660
            // spiel aktualisieren
1661
            gwv.Draw(road, MapPoint::Invalid(), false);
×
1662

1663
            // text oben noch hinschreiben
1664
            boost::format nwfString(_("current GF: %u - still fast forwarding: %d GFs left (%d %%)"));
×
1665
            nwfString % GetGFNumber() % (gf - i) % (i * 100 / gf);
×
1666
            LargeFont->Draw(DrawPoint(VIDEODRIVER.GetRenderSize() / 2u), nwfString.str(), FontStyle::CENTER,
×
1667
                            COLOR_YELLOW);
1668

1669
            VIDEODRIVER.SwapBuffers();
×
1670
        }
1671
        ExecuteGameFrame();
×
1672
    }
1673

1674
    // Spiel pausieren & text ausgabe wie lang das jetzt gedauert hat
1675
    unsigned ticks = VIDEODRIVER.GetTickCount() - start_ticks;
×
1676
    boost::format text(_("Jump finished (%1$.3g seconds)."));
×
1677
    text % (ticks / 1000.0);
×
1678
    SystemChat(text.str());
×
1679
    SetPause(true);
×
1680
}
1681

1682
void GameClient::SystemChat(const std::string& text)
×
1683
{
1684
    SystemChat(text, GetPlayerId());
×
1685
}
×
1686

1687
void GameClient::SystemChat(const std::string& text, unsigned char fromPlayerIdx)
×
1688
{
1689
    if(ci)
×
1690
        ci->CI_Chat(fromPlayerIdx, ChatDestination::System, text);
×
1691
}
×
1692

1693
bool GameClient::SaveToFile(const boost::filesystem::path& filepath)
×
1694
{
1695
    mainPlayer.sendMsg(GameMessage_Chat(GetPlayerId(), ChatDestination::System, _("Saving game...")));
×
1696

1697
    // Mond malen
1698
    Position moonPos = VIDEODRIVER.GetMousePos();
×
1699
    moonPos.y -= 40;
×
1700
    LOADER.GetImageN("resource", 33)->DrawFull(moonPos);
×
1701
    VIDEODRIVER.SwapBuffers();
×
1702

1703
    Savegame save;
×
1704

1705
    WritePlayerInfo(save);
×
1706

1707
    // GGS-Daten
1708
    save.ggs = game->ggs_;
×
1709

1710
    save.start_gf = GetGFNumber();
×
1711

1712
    // Enable/Disable debugging of savegames
1713
    save.sgd.debugMode = SETTINGS.global.debugMode;
×
1714

1715
    try
1716
    {
1717
        // Spiel serialisieren
1718
        save.sgd.MakeSnapshot(*game);
×
1719
        // Und alles speichern
1720
        return save.Save(filepath, mapinfo.title);
×
1721
    } catch(const std::exception& e)
×
1722
    {
1723
        SystemChat(std::string(_("Error during saving: ")) + e.what());
×
1724
        return false;
×
1725
    }
1726
}
1727

1728
void GameClient::ResetVisualSettings()
×
1729
{
1730
    GetPlayer(GetPlayerId()).FillVisualSettings(visual_settings);
×
1731
}
×
1732

1733
void GameClient::SetPause(bool pause)
×
1734
{
1735
    if(state == ClientState::Stopped)
×
1736
    {
1737
        // We can never continue from pause if stopped as the reason for stopping might be that the game was finished
1738
        // However we allow to pause even when stopped so we can pause after we received the stop notification
1739
        if(!pause)
×
1740
            return;
×
1741
        framesinfo.isPaused = true;
×
1742
        framesinfo.frameTime = FramesInfo::milliseconds32_t::zero();
×
1743
    } else if(replayMode)
×
1744
    {
1745
        framesinfo.isPaused = pause;
×
1746
        framesinfo.frameTime = FramesInfo::milliseconds32_t::zero();
×
1747
    } else if(IsHost())
×
1748
    {
1749
        // Pause instantly
1750
        auto* msg = new GameMessage_Pause(pause);
×
1751
        if(pause)
×
1752
            OnGameMessage(*msg);
×
1753
        mainPlayer.sendMsgAsync(msg);
×
1754
    }
1755
}
1756

1757
void GameClient::SetReplayFOW(const bool hideFOW)
×
1758
{
1759
    if(replayinfo)
×
1760
        replayinfo->all_visible = hideFOW;
×
1761
}
×
1762

1763
bool GameClient::IsReplayFOWDisabled() const
×
1764
{
1765
    return replayMode && replayinfo->all_visible;
×
1766
}
1767

1768
unsigned GameClient::GetLastReplayGF() const
×
1769
{
1770
    return replayinfo ? replayinfo->replay.GetLastGF() : 0u;
×
1771
}
1772

1773
bool GameClient::AddGC(gc::GameCommandPtr gc)
×
1774
{
1775
    // Nicht in der Pause oder wenn er besiegt wurde
1776
    if(framesinfo.isPaused || GetPlayer(GetPlayerId()).IsDefeated() || IsReplayModeOn())
×
1777
        return false;
×
1778

1779
    gameCommands_.push_back(gc);
×
1780
    return true;
×
1781
}
1782

1783
unsigned GameClient::GetNumPlayers() const
×
1784
{
1785
    RTTR_Assert(state == ClientState::Loading || state == ClientState::Loaded || state == ClientState::Game);
×
1786
    return game->world_.GetNumPlayers();
×
1787
}
1788

1789
GamePlayer& GameClient::GetPlayer(const unsigned id)
×
1790
{
1791
    RTTR_Assert(state == ClientState::Loading || state == ClientState::Loaded || state == ClientState::Game);
×
1792
    RTTR_Assert(id < GetNumPlayers());
×
1793
    return game->world_.GetPlayer(id);
×
1794
}
1795

1796
std::unique_ptr<AIPlayer> GameClient::CreateAIPlayer(unsigned playerId, const AI::Info& aiInfo)
×
1797
{
1798
    return AIFactory::Create(aiInfo, playerId, game->world_);
×
1799
}
1800

1801
/// Wandelt eine GF-Angabe in eine Zeitangabe um (HH:MM:SS oder MM:SS wenn Stunden = 0)
1802
std::string GameClient::FormatGFTime(const unsigned gf) const
5✔
1803
{
1804
    using seconds = std::chrono::duration<uint32_t, std::chrono::seconds::period>;
1805
    using hours = std::chrono::duration<uint32_t, std::chrono::hours::period>;
1806
    using minutes = std::chrono::duration<uint32_t, std::chrono::minutes::period>;
1807
    using std::chrono::duration_cast;
1808

1809
    seconds numSeconds = duration_cast<seconds>(gfs_to_duration(gf));
5✔
1810

1811
    hours numHours = duration_cast<hours>(numSeconds);
5✔
1812
    numSeconds -= numHours;
5✔
1813
    minutes numMinutes = duration_cast<minutes>(numSeconds);
5✔
1814
    numSeconds -= numMinutes;
5✔
1815

1816
    // Use hour format only if we have hours
1817
    if(numHours.count())
5✔
1818
        return helpers::format("%u:%02u:%02u", numHours.count(), numMinutes.count(), numSeconds.count());
×
1819
    else
1820
        return helpers::format("%02u:%02u", numMinutes.count(), numSeconds.count());
10✔
1821
}
1822

1823
const boost::filesystem::path& GameClient::GetReplayFilename() const
×
1824
{
1825
    static boost::filesystem::path emptyString;
×
1826
    return replayinfo ? replayinfo->filename : emptyString;
×
1827
}
1828

1829
Replay* GameClient::GetReplay()
×
1830
{
1831
    return replayinfo ? &replayinfo->replay : nullptr;
×
1832
}
1833

1834
std::shared_ptr<const NWFInfo> GameClient::GetNWFInfo() const
×
1835
{
1836
    return nwfInfo;
×
1837
}
1838

1839
/// Is tournament mode activated (0 if not)? Returns the durations of the tournament mode in gf otherwise
1840
unsigned GameClient::GetTournamentModeDuration() const
×
1841
{
1842
    using namespace std::chrono;
1843
    if(game && rttr::enum_cast(game->ggs_.objective) >= rttr::enum_cast(GameObjective::Tournament1)
×
1844
       && static_cast<unsigned>(rttr::enum_cast(game->ggs_.objective))
×
1845
            < rttr::enum_cast(GameObjective::Tournament1) + NUM_TOURNAMENT_MODES)
×
1846
    {
1847
        const auto turnamentMode = rttr::enum_cast(game->ggs_.objective) - rttr::enum_cast(GameObjective::Tournament1);
×
1848
        return duration_to_gfs(TOURNAMENT_MODES_DURATION[turnamentMode]);
×
1849
    } else
1850
        return 0;
×
1851
}
1852

1853
void GameClient::ToggleHumanAIPlayer(const AI::Info& aiInfo)
×
1854
{
1855
    RTTR_Assert(!IsReplayModeOn());
×
1856
    auto it = helpers::find_if(game->aiPlayers_,
×
1857
                               [id = this->GetPlayerId()](const auto& player) { return player.GetPlayerId() == id; });
×
1858
    if(it != game->aiPlayers_.end())
×
1859
        game->aiPlayers_.erase(it);
×
1860
    else
1861
        game->AddAIPlayer(CreateAIPlayer(GetPlayerId(), aiInfo));
×
1862
}
×
1863

1864
void GameClient::RequestSwapToPlayer(const unsigned char newId)
×
1865
{
1866
    if(state != ClientState::Game)
×
1867
        return;
×
1868
    GamePlayer& player = GetPlayer(newId);
×
1869
    if(player.ps == PlayerState::AI && player.aiInfo.type == AI::Type::Dummy)
×
1870
        mainPlayer.sendMsgAsync(new GameMessage_Player_Swap(0xFF, newId));
×
1871
}
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