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

Return-To-The-Roots / s25client / 21600910629

02 Feb 2026 05:42PM UTC coverage: 50.721% (-0.03%) from 50.754%
21600910629

Pull #1683

github

web-flow
Merge b0763ef2d into dc0ce04f6
Pull Request #1683: Lua: Allow setting number of players, and placing HQs. Then fix the mission on map "The snake"

1 of 29 new or added lines in 7 files covered. (3.45%)

5 existing lines in 2 files now uncovered.

22796 of 44944 relevant lines covered (50.72%)

41333.25 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
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);
×
NEW
333
        if((!mapinfo.luaFilepath.empty() && !loader.LoadLuaScript(*game, *this, mapinfo.luaFilepath))
×
NEW
334
           || !loader.Load(mapinfo.filepath)) // Do not reorder: load lua first, load map second.
×
335
                                              // If the map is loaded first and it does not have a player HQ set, it
336
                                              // will not load correctly, even though the HQ may be set using LUA.
337
        {
338
            OnError(ClientError::InvalidMap);
×
339
            return;
×
340
        }
341
        gameWorld.SetupResources();
×
342
    }
343
    gameWorld.InitAfterLoad();
×
344

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

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

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

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

362
    state = ClientState::Loaded;
×
363

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

723
    mainPlayer.sendMsgAsync(new GameMessage_Server_Password(clientconfig.password));
5✔
724

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

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

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

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

747
    AdvanceState(ConnectState::QueryMapInfo);
5✔
748
    return true;
5✔
749
}
750

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1064
    unsigned numPlayers;
1065

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1224
    return true;
×
1225
}
1226

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

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

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

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

1261
    // AsyncLog an den Server senden
1262

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

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

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

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

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

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

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

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

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

1341
                    ExecuteNWF();
×
1342

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1551
    replayMode = true;
×
1552

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

1563
    replayinfo->next_gf = replayinfo->replay.ReadGF();
×
1564

1565
    return true;
×
1566
}
1567

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

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

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

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

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

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

1629
    unsigned start_ticks = VIDEODRIVER.GetTickCount();
×
1630

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

1639
    SetPause(false);
×
1640
    skiptogf = gf;
×
1641

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

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

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

1659
            VIDEODRIVER.SwapBuffers();
×
1660
        }
1661
        ExecuteGameFrame();
×
1662
    }
1663

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

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

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

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

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

1693
    Savegame save;
×
1694

1695
    WritePlayerInfo(save);
×
1696

1697
    // GGS-Daten
1698
    save.ggs = game->ggs_;
×
1699

1700
    save.start_gf = GetGFNumber();
×
1701

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

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

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

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

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

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

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

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

1769
    gameCommands_.push_back(gc);
×
1770
    return true;
×
1771
}
1772

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

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

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

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

1799
    seconds numSeconds = duration_cast<seconds>(gfs_to_duration(gf));
5✔
1800

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

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

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

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

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

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

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

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