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

Return-To-The-Roots / s25client / 37504881488

06 Oct 2026 05:36PM UTC coverage: 51.524% (+0.04%) from 51.487%
37504881488

push

github

web-flow
Merge pull request #1948 from morganchristiansson/hunter-radius

Hunter: Use GetPointsInRadius() instead of square

19 of 39 new or added lines in 6 files covered. (48.72%)

1 existing line in 1 file now uncovered.

24007 of 46594 relevant lines covered (51.52%)

48129.4 hits per line

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

23.23
/libs/s25main/network/GameClient.cpp
1
// Copyright (C) 2005 - 2026 Settlers Freaks (sf-team at siedler25.org)
2
//
3
// SPDX-License-Identifier: GPL-2.0-or-later
4

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

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

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

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

94
GameClient::~GameClient()
27 ✔
95
{
96
    Stop();
24 ✔
97
}
27 ✔
98

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

114
    RTTR_Assert(aiBattlePlayers_.empty());
15 ✔
115

116
    // Name und Password kopieren
117
    clientconfig.server = server;
15 ✔
118
    clientconfig.password = password;
15 ✔
119

120
    clientconfig.servertyp = servertyp;
15 ✔
121
    clientconfig.port = port;
15 ✔
122
    clientconfig.isHost = host;
15 ✔
123

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

133
    state = ClientState::Connect;
15 ✔
134
    AdvanceState(ConnectState::Initiated);
15 ✔
135

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

139
    return true;
15 ✔
140
}
141

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

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

164
    SocketSet set;
×
165

166
    // erstmal auf Daten überprüfen
167
    set.Clear();
×
168

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

181
    // nun auf Fehler prüfen
182
    set.Clear();
×
183

184
    // zum set hinzufügen
185
    set.Add(mainPlayer.socket);
×
186

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

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

206
    // maximal 10 Pakete verschicken
207
    mainPlayer.sendMsgs(10);
×
208

209
    mainPlayer.executeMsgs(*this);
×
210
}
211

212
/**
213
 *  Stoppt das Spiel
214
 */
215
void GameClient::Stop()
44 ✔
216
{
217
    if(state == ClientState::Stopped)
44 ✔
218
        return;
29 ✔
219

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

225
    if(IsHost())
15 ✔
226
        GAMESERVER.Stop();
×
227

228
    framesinfo.Clear();
15 ✔
229
    clientconfig.Clear();
15 ✔
230
    mapinfo.Clear();
15 ✔
231

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

240
    mainPlayer.closeConnection();
15 ✔
241

242
    // clear jump target
243
    skiptogf = 0;
15 ✔
244

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

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

252
    aiBattlePlayers_.clear();
15 ✔
253
}
254

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

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

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

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

283
    // Start in pause mode
284
    framesinfo.isPaused = true;
×
285

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

290
    // Random-Generator initialisieren
291
    RANDOM.Init(random_init);
×
292
    AI::getRandomGenerator().seed(random_init);
×
293

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

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

317
    state = ClientState::Loading;
×
318

319
    if(ci)
×
320
        ci->CI_GameLoading(game);
×
321

322
    // Get standard settings before they get overwritten
323
    GetPlayer(GetPlayerId()).FillVisualSettings(default_settings);
×
324

325
    GameWorld& gameWorld = game->world_;
×
326
    if(mapinfo.savegame)
×
327
        mapinfo.savegame->sgd.ReadSnapshot(*game, *this);
×
328
    else
329
    {
330
        RTTR_Assert(mapinfo.type != MapType::Savegame);
×
331
        /// Startbündnisse setzen
332
        for(auto& player : gameWorld.getPlayers())
×
333
            player.MakeStartPacts();
×
334

335
        MapLoader loader(gameWorld);
×
336
        if(!loader.Load(mapinfo.filepath)
×
337
           || (!mapinfo.luaFilepath.empty() && !loader.LoadLuaScript(*game, *this, mapinfo.luaFilepath)))
×
338
        {
339
            OnError(ClientError::InvalidMap);
×
340
            return;
×
341
        }
342
        // TODO (Replay): Always use true
343
        const bool fixFish = !GetReplay() || GetReplay()->GetMinorVersion() >= 3;
×
344
        MapLoader::SetupResources(gameWorld, fixFish);
×
345
    }
NEW
346
    if(replayMode && replayinfo)
×
NEW
347
        gameWorld.SetReplayCompatVersion(replayinfo->replay.GetMinorVersion());
×
UNCOV
348
    gameWorld.InitAfterLoad();
×
349

350
    // Update visual settings
351
    ResetVisualSettings();
×
352

353
    if(!replayMode)
×
354
    {
355
        RTTR_Assert(!replayinfo);
×
356
        StartReplayRecording(random_init);
×
357
    }
358

359
    // Daten nach dem Schreiben des Replays ggf wieder löschen
360
    mapinfo.mapData.Clear();
×
361
}
362

363
void GameClient::GameLoaded()
×
364
{
365
    RTTR_Assert(state == ClientState::Loading);
×
366

367
    state = ClientState::Loaded;
×
368

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

391
void GameClient::ExitGame()
×
392
{
393
    RTTR_Assert(state == ClientState::Game || state == ClientState::Loaded || state == ClientState::Loading);
×
394
    game.reset();
×
395
    nwfInfo.reset();
×
396
    // Clear remaining commands
397
    gameCommands_.clear();
×
398
}
×
399

400
unsigned GameClient::GetGFNumber() const
×
401
{
402
    return game->em_->GetCurrentGF();
×
403
}
404

405
/**
406
 *  Ping-Nachricht.
407
 */
408
bool GameClient::OnGameMessage(const GameMessage_Ping& /*msg*/)
×
409
{
410
    mainPlayer.sendMsgAsync(new GameMessage_Pong());
×
411
    return true;
×
412
}
413

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

428
    mainPlayer.playerId = msg.player;
15 ✔
429

430
    // Server-Typ senden
431
    mainPlayer.sendMsgAsync(new GameMessage_Server_Type(clientconfig.servertyp, rttr::version::GetRevision()));
15 ✔
432
    AdvanceState(ConnectState::VerifyServer);
15 ✔
433
    return true;
15 ✔
434
}
435

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

451
    for(unsigned i = 0; i < gameLobby->getNumPlayers(); ++i)
16 ✔
452
        gameLobby->getPlayer(i) = msg.playerInfos[i];
12 ✔
453

454
    if(state == ClientState::Connect)
4 ✔
455
        AdvanceState(ConnectState::QuerySettings);
4 ✔
456
    return true;
4 ✔
457
}
458

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

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

485
///////////////////////////////////////////////////////////////////////////////
486
/// player joined
487
/// @param message  Nachricht, welche ausgeführt wird
488
bool GameClient::OnGameMessage(const GameMessage_Player_New& msg)
×
489
{
490
    if(state != ClientState::Config)
×
491
        return true;
×
492

493
    if(msg.player >= gameLobby->getNumPlayers())
×
494
        return true;
×
495

496
    JoinPlayerInfo& playerInfo = gameLobby->getPlayer(msg.player);
×
497

498
    playerInfo.name = msg.name;
×
499
    playerInfo.ps = PlayerState::Occupied;
×
500
    playerInfo.ping = 0;
×
501

502
    if(ci)
×
503
        ci->CI_NewPlayer(msg.player);
×
504
    return true;
×
505
}
506

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

525
    if(ci)
×
526
        ci->CI_PingChanged(msg.player, msg.ping);
×
527
    return true;
×
528
}
529

530
/**
531
 *  Player-Toggle-State-Nachricht.
532
 */
533
bool GameClient::OnGameMessage(const GameMessage_Player_State& msg)
×
534
{
535
    if(state != ClientState::Config)
×
536
        return true;
×
537

538
    if(msg.player >= gameLobby->getNumPlayers())
×
539
        return true;
×
540

541
    JoinPlayerInfo& playerInfo = gameLobby->getPlayer(msg.player);
×
542
    bool wasUsed = playerInfo.isUsed();
×
543
    playerInfo.ps = msg.ps;
×
544
    playerInfo.aiInfo = msg.aiInfo;
×
545

546
    if(ci)
×
547
    {
548
        if(playerInfo.isUsed())
×
549
            ci->CI_NewPlayer(msg.player);
×
550
        else if(wasUsed)
×
551
            ci->CI_PlayerLeft(msg.player);
×
552
        else
553
            ci->CI_PlayerDataChanged(msg.player);
×
554
    }
555
    return true;
×
556
}
557

558
///////////////////////////////////////////////////////////////////////////////
559
/// nation button gedrückt
560
/// @param message  Nachricht, welche ausgeführt wird
561
bool GameClient::OnGameMessage(const GameMessage_Player_Nation& msg)
×
562
{
563
    if(state != ClientState::Config)
×
564
        return true;
×
565

566
    if(msg.player >= gameLobby->getNumPlayers())
×
567
        return true;
×
568

569
    gameLobby->getPlayer(msg.player).nation = msg.nation;
×
570

571
    if(ci)
×
572
        ci->CI_PlayerDataChanged(msg.player);
×
573
    return true;
×
574
}
575

576
///////////////////////////////////////////////////////////////////////////////
577
/// team button gedrückt
578
/// @param message  Nachricht, welche ausgeführt wird
579
bool GameClient::OnGameMessage(const GameMessage_Player_Team& msg)
×
580
{
581
    if(state != ClientState::Config)
×
582
        return true;
×
583

584
    if(msg.player >= gameLobby->getNumPlayers())
×
585
        return true;
×
586

587
    gameLobby->getPlayer(msg.player).team = msg.team;
×
588

589
    if(ci)
×
590
        ci->CI_PlayerDataChanged(msg.player);
×
591
    return true;
×
592
}
593

594
///////////////////////////////////////////////////////////////////////////////
595
/// color button gedrückt
596
/// @param message  Nachricht, welche ausgeführt wird
597
bool GameClient::OnGameMessage(const GameMessage_Player_Color& msg)
×
598
{
599
    if(state != ClientState::Config)
×
600
        return true;
×
601

602
    if(msg.player >= gameLobby->getNumPlayers())
×
603
        return true;
×
604

605
    gameLobby->getPlayer(msg.player).color = msg.color;
×
606

607
    if(ci)
×
608
        ci->CI_PlayerDataChanged(msg.player);
×
609
    return true;
×
610
}
611

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

622
    if(msg.player >= gameLobby->getNumPlayers())
×
623
        return true;
×
624

625
    gameLobby->getPlayer(msg.player).isReady = msg.ready;
×
626

627
    if(ci)
×
628
        ci->CI_ReadyChanged(msg.player, msg.ready);
×
629
    return true;
×
630
}
631

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

660
    if(ci)
×
661
        ci->CI_PlayerLeft(msg.player);
×
662
    return true;
×
663
}
664

665
bool GameClient::OnGameMessage(const GameMessage_Player_Swap& msg)
×
666
{
667
    LOG.writeToFile("<<< NMS_PLAYER_SWAP(%u, %u)\n") % unsigned(msg.player) % unsigned(msg.player2);
×
668

669
    if(state == ClientState::Config)
×
670
    {
671
        if(msg.player >= gameLobby->getNumPlayers() || msg.player2 >= gameLobby->getNumPlayers())
×
672
            return true;
×
673

674
        // During preparation just swap the players
675
        using std::swap;
676
        swap(gameLobby->getPlayer(msg.player), gameLobby->getPlayer(msg.player2));
×
677
        // Some things cannot be changed in savegames
678
        if(mapinfo.type == MapType::Savegame)
×
679
            gameLobby->getPlayer(msg.player).FixSwappedSaveSlot(gameLobby->getPlayer(msg.player2));
×
680

681
        // Evtl. sind wir betroffen?
682
        if(mainPlayer.playerId == msg.player)
×
683
            mainPlayer.playerId = msg.player2;
×
684
        else if(mainPlayer.playerId == msg.player2)
×
685
            mainPlayer.playerId = msg.player;
×
686

687
        if(ci)
×
688
            ci->CI_PlayersSwapped(msg.player, msg.player2);
×
689
    } else if(state == ClientState::Loading || state == ClientState::Loaded || state == ClientState::Game)
×
690
        ChangePlayerIngame(msg.player, msg.player2);
×
691
    else
692
        return true;
×
693
    mainPlayer.sendMsgAsync(new GameMessage_Player_SwapConfirm(msg.player, msg.player2));
×
694
    return true;
×
695
}
696

697
/**
698
 *  Server-Typ-Nachricht.
699
 */
700
bool GameClient::OnGameMessage(const GameMessage_Server_TypeOK& msg)
15 ✔
701
{
702
    if(!VerifyState(ConnectState::VerifyServer))
15 ✔
703
        return true;
×
704

705
    using StatusCode = GameMessage_Server_TypeOK::StatusCode;
706
    switch(msg.err_code)
15 ✔
707
    {
708
        case StatusCode::Ok: break;
15 ✔
709

710
        default:
×
711
        case StatusCode::InvalidServerType:
712
        {
713
            OnError(ClientError::InvalidServerType);
×
714
            return true;
×
715
        }
716
        break;
717

718
        case StatusCode::WrongVersion:
×
719
        {
720
            LOG.write(_("Version mismatch. Server version: %1%, your version %2%")) % msg.version
×
721
              % rttr::version::GetRevision();
×
722
            OnError(ClientError::WrongVersion);
×
723
            return true;
×
724
        }
725
        break;
726
    }
727

728
    mainPlayer.sendMsgAsync(new GameMessage_Server_Password(clientconfig.password));
15 ✔
729

730
    AdvanceState(ConnectState::QueryPw);
15 ✔
731
    return true;
15 ✔
732
}
733

734
/**
735
 *  Server-Passwort-Nachricht.
736
 */
737
bool GameClient::OnGameMessage(const GameMessage_Server_Password& msg)
15 ✔
738
{
739
    if(!VerifyState(ConnectState::QueryPw))
15 ✔
740
        return true;
×
741

742
    if(msg.password != "true")
15 ✔
743
    {
744
        OnError(ClientError::WrongPassword);
1 ✔
745
        return true;
1 ✔
746
    }
747

748
    mainPlayer.sendMsgAsync(new GameMessage_Player_Name(0xFF, SETTINGS.lobby.name));
14 ✔
749
    mainPlayer.sendMsgAsync(new GameMessage_Player_Portrait(0xFF, SETTINGS.lobby.portraitIndex));
14 ✔
750
    mainPlayer.sendMsgAsync(new GameMessage_MapRequest(true));
14 ✔
751

752
    AdvanceState(ConnectState::QueryMapInfo);
14 ✔
753
    return true;
14 ✔
754
}
755

756
/**
757
 *  Server-Name-Nachricht.
758
 */
759
bool GameClient::OnGameMessage(const GameMessage_Server_Name& msg)
4 ✔
760
{
761
    if(!VerifyState(ConnectState::QueryServerName))
4 ✔
762
        return true;
×
763
    clientconfig.gameName = msg.name;
4 ✔
764

765
    AdvanceState(ConnectState::QueryPlayerList);
4 ✔
766
    return true;
4 ✔
767
}
768

769
/**
770
 *  Server-Start-Nachricht
771
 */
772
bool GameClient::OnGameMessage(const GameMessage_Server_Start& msg)
×
773
{
774
    if(state != ClientState::Config)
×
775
        return true;
×
776

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

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

807
        /// Mit im Replay aufzeichnen
808
        if(replayinfo && replayinfo->replay.IsRecording())
×
809
            replayinfo->replay.AddChatCommand(GetGFNumber(), msg.player, msg.destination, msg.text);
×
810

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

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

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

836
        if(!isValidRecipient(GetPlayerId()))
×
837
            return true;
×
838
    } else if(state == ClientState::Config)
×
839
    {
840
        // GameLobby message: Just check for valid player
841
        if(msg.player >= gameLobby->getNumPlayers())
×
842
            return true;
×
843
    } else
844
        return true;
×
845

846
    if(ci)
×
847
        ci->CI_Chat(msg.player, msg.destination, msg.text);
×
848
    return true;
×
849
}
850

851
/**
852
 *  Server-Async-Nachricht.
853
 */
854
bool GameClient::OnGameMessage(const GameMessage_Server_Async& msg)
×
855
{
856
    if(state != ClientState::Game)
×
857
        return true;
×
858

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

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

872
    // Messenger im Game
873
    if(ci)
×
874
        ci->CI_Async(checksum_list.str());
×
875

876
    std::string fileName = s25util::Time::FormatTime("async_%Y-%m-%d_%H-%i-%s");
×
877
    fileName += "_" + s25util::toStringClassic(GetPlayerId()) + "_";
×
878
    fileName += GetPlayer(GetPlayerId()).name;
×
879

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

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

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

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

924
    // For local savegame loads the filename comes from our own server and is already a valid
925
    // filesystem name — sanitizing it (spaces -> underscores) would create a renamed duplicate
926
    // alongside the original save file.
927
    const std::string portFilename = (clientconfig.servertyp == ServerType::Local && msg.mt == MapType::Savegame) ?
14 ✔
928
                                       msg.filename :
1 ✔
929
                                       makePortableFileName(msg.filename);
28 ✔
930
    if(portFilename.empty())
14 ✔
931
    {
932
        LOG.write("Invalid filename received!\n");
×
933
        OnError(ClientError::InvalidMap);
×
934
        return true;
×
935
    }
936
    if(!MapInfo::verifySize(msg.mapLen, msg.luaLen, msg.mapCompressedLen, msg.luaCompressedLen))
14 ✔
937
    {
938
        OnError(ClientError::InvalidMap);
×
939
        return true;
×
940
    }
941
    const auto targetPath =
942
      RTTRCONFIG.ExpandPath((msg.mt == MapType::Savegame) ? s25::folders::save : s25::folders::mapsPlayed);
42 ✔
943
    bfs::create_directories(targetPath);
14 ✔
944
    mapinfo.filepath = targetPath / portFilename;
28 ✔
945
    mapinfo.type = msg.mt;
14 ✔
946

947
    // lua script file path
948
    if(msg.luaLen > 0)
14 ✔
949
        mapinfo.luaFilepath = bfs::path(mapinfo.filepath).replace_extension("lua");
4 ✔
950
    else
951
        mapinfo.luaFilepath.clear();
12 ✔
952

953
    // We have the map locally already, so prepare and ask if this is the same as the one on the server
954
    if(bfs::exists(mapinfo.filepath) && (mapinfo.luaFilepath.empty() || bfs::exists(mapinfo.luaFilepath))
16 ✔
955
       && CreateLobby())
16 ✔
956
    {
957
        mapinfo.mapData.CompressFromFile(mapinfo.filepath, &mapinfo.mapChecksum);
2 ✔
958
        if(mapinfo.mapData.data.size() == msg.mapCompressedLen && mapinfo.mapData.uncompressedLength == msg.mapLen)
2 ✔
959
        {
960
            bool ok = true;
2 ✔
961
            if(!mapinfo.luaFilepath.empty())
2 ✔
962
            {
963
                mapinfo.luaData.CompressFromFile(mapinfo.luaFilepath, &mapinfo.luaChecksum);
1 ✔
964
                ok = (mapinfo.luaData.data.size() == msg.luaCompressedLen
2 ✔
965
                      && mapinfo.luaData.uncompressedLength == msg.luaLen);
1 ✔
966
            }
967

968
            if(ok)
2 ✔
969
            {
970
                mainPlayer.sendMsgAsync(new GameMessage_Map_Checksum(mapinfo.mapChecksum, mapinfo.luaChecksum));
2 ✔
971
                AdvanceState(ConnectState::VerifyMap);
2 ✔
972
                return true;
2 ✔
973
            }
974
        }
975
        gameLobby.reset();
×
976
    }
977
    mapinfo.mapData.uncompressedLength = msg.mapLen;
12 ✔
978
    mapinfo.luaData.uncompressedLength = msg.luaLen;
12 ✔
979
    mapinfo.mapData.data.resize(msg.mapCompressedLen);
12 ✔
980
    mapinfo.luaData.data.resize(msg.luaCompressedLen);
12 ✔
981
    mainPlayer.sendMsgAsync(new GameMessage_MapRequest(false));
12 ✔
982
    AdvanceState(ConnectState::ReceiveMap);
12 ✔
983
    return true;
12 ✔
984
}
985

986
///////////////////////////////////////////////////////////////////////////////
987
/// Kartendaten
988
/// @param message  Nachricht, welche ausgeführt wird
989
bool GameClient::OnGameMessage(const GameMessage_Map_Data& msg)
8 ✔
990
{
991
    if(!VerifyState(ConnectState::ReceiveMap))
8 ✔
992
        return true;
×
993

994
    LOG.writeToFile("<<< NMS_MAP_DATA(%u)\n") % msg.data.size();
8 ✔
995
    std::vector<char>& targetData = (msg.isMapData) ? mapinfo.mapData.data : mapinfo.luaData.data;
8 ✔
996
    if(msg.data.size() > targetData.size() || msg.offset > targetData.size() - msg.data.size())
8 ✔
997
    {
998
        OnError(ClientError::MapTransmission);
1 ✔
999
        return true;
1 ✔
1000
    }
1001
    std::copy(msg.data.begin(), msg.data.end(), targetData.begin() + msg.offset);
7 ✔
1002

1003
    uint32_t totalSize = mapinfo.mapData.data.size();
7 ✔
1004
    uint32_t receivedSize = msg.offset + msg.data.size();
7 ✔
1005
    if(!mapinfo.luaFilepath.empty())
7 ✔
1006
    {
1007
        totalSize += mapinfo.luaData.data.size();
4 ✔
1008
        // Assumes lua data comes after the map data
1009
        if(!msg.isMapData)
4 ✔
1010
            receivedSize += mapinfo.mapData.data.size();
2 ✔
1011
    }
1012
    if(ci)
7 ✔
1013
        ci->CI_MapPartReceived(receivedSize, totalSize);
7 ✔
1014

1015
    if(receivedSize == totalSize)
7 ✔
1016
    {
1017
        if(!mapinfo.mapData.DecompressToFile(mapinfo.filepath, &mapinfo.mapChecksum))
2 ✔
1018
        {
1019
            OnError(ClientError::MapTransmission);
×
1020
            return true;
×
1021
        }
1022
        if(!mapinfo.luaFilepath.empty() && !mapinfo.luaData.DecompressToFile(mapinfo.luaFilepath, &mapinfo.luaChecksum))
2 ✔
1023
        {
1024
            OnError(ClientError::MapTransmission);
×
1025
            return true;
×
1026
        }
1027
        RTTR_Assert(!mapinfo.luaFilepath.empty() || mapinfo.luaChecksum == 0);
2 ✔
1028

1029
        if(!CreateLobby())
2 ✔
1030
        {
1031
            OnError(ClientError::MapTransmission);
×
1032
            return true;
×
1033
        }
1034

1035
        mainPlayer.sendMsgAsync(new GameMessage_Map_Checksum(mapinfo.mapChecksum, mapinfo.luaChecksum));
2 ✔
1036
        AdvanceState(ConnectState::VerifyMap);
2 ✔
1037
    }
1038
    return true;
7 ✔
1039
}
1040

1041
bool GameClient::OnGameMessage(const GameMessage_SkipToGF& msg)
×
1042
{
1043
    skiptogf = msg.targetGF;
×
1044
    LOG.write("Jumping from GF %1% to GF %2%\n") % GetGFNumber() % skiptogf;
×
1045
    return true;
×
1046
}
1047

1048
void GameClient::OnError(ClientError error)
2 ✔
1049
{
1050
    if(ci)
2 ✔
1051
        ci->CI_Error(error);
2 ✔
1052
    Stop();
2 ✔
1053
}
2 ✔
1054

1055
void GameClient::AdvanceState(ConnectState newState)
91 ✔
1056
{
1057
    connectState = newState;
91 ✔
1058
    if(ci)
91 ✔
1059
        ci->CI_NextConnectState(connectState);
46 ✔
1060
}
91 ✔
1061

1062
bool GameClient::VerifyState(ConnectState expectedState)
83 ✔
1063
{
1064
    if(state != ClientState::Connect || connectState != expectedState)
83 ✔
1065
    {
1066
        OnError(ClientError::InvalidMessage);
×
1067
        return false;
×
1068
    }
1069
    return true;
83 ✔
1070
}
1071

1072
bool GameClient::CreateLobby()
4 ✔
1073
{
1074
    RTTR_Assert(!gameLobby);
4 ✔
1075

1076
    unsigned numPlayers;
1077

1078
    switch(mapinfo.type)
4 ✔
1079
    {
1080
        case MapType::OldMap:
4 ✔
1081
        {
1082
            libsiedler2::Archiv map;
4 ✔
1083

1084
            // Karteninformationen laden
1085
            if(libsiedler2::loader::LoadMAP(mapinfo.filepath, map, true) != 0)
4 ✔
1086
            {
1087
                LOG.write("GameClient::OnMapData: ERROR: Map %1%, couldn't load header!\n") % mapinfo.filepath;
×
1088
                return false;
×
1089
            }
1090

1091
            const libsiedler2::ArchivItem_Map_Header& header =
1092
              checkedCast<const libsiedler2::ArchivItem_Map*>(map.get(0))->getHeader();
4 ✔
1093
            numPlayers = header.getNumPlayers();
4 ✔
1094
            mapinfo.title = s25util::ansiToUTF8(header.getName());
4 ✔
1095
        }
1096
        break;
4 ✔
1097
        case MapType::Savegame:
×
1098
            mapinfo.savegame = std::make_unique<Savegame>();
×
1099
            if(!mapinfo.savegame->Load(mapinfo.filepath, SaveGameDataToLoad::HeaderAndSettings))
×
1100
                return false;
×
1101

1102
            numPlayers = mapinfo.savegame->GetNumPlayers();
×
1103
            mapinfo.title = mapinfo.savegame->GetMapName();
×
1104
            break;
×
1105
        default: return false;
×
1106
    }
1107

1108
    if(GetPlayerId() >= numPlayers)
4 ✔
1109
        return false;
×
1110

1111
    gameLobby = std::make_shared<GameLobby>(mapinfo.type == MapType::Savegame, IsHost(), numPlayers);
4 ✔
1112
    return true;
4 ✔
1113
}
1114

1115
///////////////////////////////////////////////////////////////////////////////
1116
/// map-checksum
1117
/// @param message  Nachricht, welche ausgeführt wird
1118
bool GameClient::OnGameMessage(const GameMessage_Map_ChecksumOK& msg)
4 ✔
1119
{
1120
    if(!VerifyState(ConnectState::VerifyMap))
4 ✔
1121
        return true;
×
1122
    LOG.writeToFile("<<< NMS_MAP_CHECKSUM(%d)\n") % (msg.correct ? 1 : 0);
4 ✔
1123

1124
    if(msg.correct)
4 ✔
1125
        AdvanceState(ConnectState::QueryServerName);
4 ✔
1126
    else
1127
    {
1128
        gameLobby.reset();
×
1129
        if(msg.retryAllowed)
×
1130
        {
1131
            mainPlayer.sendMsgAsync(new GameMessage_MapRequest(false));
×
1132
            AdvanceState(ConnectState::ReceiveMap);
×
1133
        } else
1134
            OnError(ClientError::MapTransmission);
×
1135
    }
1136
    return true;
4 ✔
1137
}
1138

1139
///////////////////////////////////////////////////////////////////////////////
1140
/// server typ
1141
/// @param message  Nachricht, welche ausgeführt wird
1142
bool GameClient::OnGameMessage(const GameMessage_GGSChange& msg)
4 ✔
1143
{
1144
    if(state != ClientState::Config && !VerifyState(ConnectState::QuerySettings))
4 ✔
1145
        return true;
×
1146
    LOG.writeToFile("<<< NMS_GGS_CHANGE\n");
4 ✔
1147

1148
    gameLobby->getSettings() = msg.ggs;
4 ✔
1149

1150
    if(state == ClientState::Connect)
4 ✔
1151
    {
1152
        state = ClientState::Config;
4 ✔
1153
        AdvanceState(ConnectState::Finished);
4 ✔
1154
    } else if(ci)
×
1155
        ci->CI_GGSChanged(msg.ggs);
×
1156
    return true;
4 ✔
1157
}
1158

1159
bool GameClient::OnGameMessage(const GameMessage_RemoveLua&)
×
1160
{
1161
    if(state != ClientState::Connect && state != ClientState::Config)
×
1162
        return true;
×
1163
    mapinfo.luaFilepath.clear();
×
1164
    mapinfo.luaData.Clear();
×
1165
    mapinfo.luaChecksum = 0;
×
1166
    return true;
×
1167
}
1168

1169
///////////////////////////////////////////////////////////////////////////////
1170
/// NFC Antwort vom Server
1171
/// @param message  Nachricht, welche ausgeführt wird
1172
bool GameClient::OnGameMessage(const GameMessage_GameCommand& msg)
×
1173
{
1174
    if(nwfInfo)
×
1175
    {
1176
        if(!nwfInfo->addPlayerCmds(msg.player, msg.cmds))
×
1177
        {
1178
            LOG.write("Could not add gamecommands for player %1%. He might be cheating!\n") % unsigned(msg.player);
×
1179
            RTTR_Assert(false);
×
1180
        }
1181
    }
1182
    return true;
×
1183
}
1184

1185
void GameClient::IncreaseSpeed(const bool wraparound)
12 ✔
1186
{
1187
    const auto curSpeed = framesinfo.gfLengthReq;
12 ✔
1188
    // Note: Higher speed = lower gf_length value
1189
    // Go from debug speed directly back to min speed, else in fixed steps
1190
    static_assert(MIN_SPEED_DEBUG > MIN_SPEED);
1191
    if(framesinfo.gfLengthReq > MIN_SPEED)
12 ✔
1192
        SetNewSpeed(MIN_SPEED); // NOLINT(bugprone-branch-clone)
2 ✔
1193
    else
1194
        SetNewSpeed((framesinfo.gfLengthReq - 1ms) / SPEED_STEP * SPEED_STEP);
10 ✔
1195
    // If unchanged we capped at max speed, so wrap around if requested
1196
    if(wraparound && framesinfo.gfLengthReq == curSpeed)
12 ✔
1197
        SetNewSpeed(MIN_SPEED);
1 ✔
1198
}
12 ✔
1199

1200
void GameClient::DecreaseSpeed()
11 ✔
1201
{
1202
    if((DEBUG_MODE || replayMode) && framesinfo.gfLengthReq >= MIN_SPEED)
11 ✔
1203
        SetNewSpeed(MIN_SPEED_DEBUG);
2 ✔
1204
    else
1205
        SetNewSpeed(framesinfo.gfLengthReq / SPEED_STEP * SPEED_STEP + SPEED_STEP);
9 ✔
1206
}
11 ✔
1207

1208
void GameClient::SetNewSpeed(std::chrono::milliseconds gfLength)
38 ✔
1209
{
1210
    static_assert(MIN_SPEED >= SPEED_GF_LENGTHS[GameSpeed::VeryFast], "Not all speeds reachable");
1211
    static_assert(MAX_SPEED <= SPEED_GF_LENGTHS[GameSpeed::VerySlow], "Not all speeds reachable");
1212
    const auto minSpeed = (replayMode || DEBUG_MODE) ? MIN_SPEED_DEBUG : MIN_SPEED;
38 ✔
1213
    const auto maxSpeed = (replayMode || DEBUG_MODE) ? MAX_SPEED_DEBUG : MAX_SPEED;
38 ✔
1214
    const auto oldSpeed = framesinfo.gfLengthReq;
38 ✔
1215
    framesinfo.gfLengthReq = helpers::clamp<decltype(gfLength)>(gfLength, maxSpeed, minSpeed);
38 ✔
1216
    if(replayMode)
38 ✔
1217
        framesinfo.gf_length = framesinfo.gfLengthReq;
×
1218
    else if(framesinfo.gfLengthReq != oldSpeed)
38 ✔
1219
        mainPlayer.sendMsgAsync(new GameMessage_Speed(framesinfo.gfLengthReq));
35 ✔
1220
}
38 ✔
1221

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

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

1236
    return true;
×
1237
}
1238

1239
bool GameClient::OnGameMessage(const GameMessage_Pause& msg)
×
1240
{
1241
    // Ignore recorded pause messages in replay mode
1242
    if(state != ClientState::Game || replayMode)
×
1243
        return true;
×
1244
    if(framesinfo.isPaused == msg.paused)
×
1245
        return true;
×
1246
    framesinfo.isPaused = msg.paused;
×
1247

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

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

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

1269
    // AsyncLog an den Server senden
1270

1271
    std::vector<RandomEntry> async_log = RANDOM.GetAsyncLog();
×
1272

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

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

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

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

1297
    FramesInfo::UsedClock::time_point currentTime = FramesInfo::UsedClock::now();
×
1298

1299
    if(framesinfo.forcePauseLen.count())
×
1300
    {
1301
        if(currentTime - framesinfo.forcePauseStart > framesinfo.forcePauseLen)
×
1302
            framesinfo.forcePauseLen = framesinfo.forcePauseLen.zero();
×
1303
        else
1304
            return; // Pause
×
1305
    }
1306

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

1347
                    RTTR_Assert(nwfInfo->getServerInfo().gf == curGF);
×
1348

1349
                    ExecuteNWF();
×
1350

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1560
    replayMode = true;
×
1561

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

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

1592
        for(auto& player : game->world_.getPlayers())
×
1593
            player.ChangeDistribution(newDistributions);
×
1594
    }
1595

1596
    replayinfo->next_gf = replayinfo->replay.ReadGF();
×
1597

1598
    return true;
×
1599
}
1600

1601
void GameClient::SetAIBattlePlayers(std::vector<AI::Info> aiInfos)
×
1602
{
1603
    aiBattlePlayers_ = std::move(aiInfos);
×
1604
}
×
1605

1606
unsigned GameClient::GetGlobalAnimation(const unsigned short max, const unsigned char factor_numerator,
1 ✔
1607
                                        const unsigned char factor_denumerator, const unsigned offset)
1608
{
1609
    // Unit for animations is 630ms (dividable by 2, 3, 5, 6, 7, 10, 15, ...)
1610
    // But this also means: If framerate drops below approx. 15Hz, you won't see
1611
    // every frame of an 8-part animation anymore.
1612
    // An animation runs fully in (factor_numerator / factor_denumerator) multiples of 630ms
1613
    const unsigned unit = 630 /*ms*/ * factor_numerator / factor_denumerator;
1 ✔
1614
    const auto currenttime =
1615
      static_cast<unsigned>((framesinfo.lastTime + framesinfo.frameTime).time_since_epoch() / 1ms);
1 ✔
1616
    return ((currenttime % unit) * max / unit + offset) % max;
1 ✔
1617
}
1618

1619
template<typename T>
1620
T GameClient::do_interpolate(const T x1, const T x2, const GameEvent& ev) const
×
1621
{
1622
    // TODO: Move to some animation system that is part of game
1623
    std::chrono::milliseconds elapsedTime{};
×
1624
    if(state == ClientState::Game)
×
1625
        elapsedTime = (GetGFNumber() - ev.startGF) * framesinfo.gf_length + framesinfo.frameTime;
×
1626
    const auto duration = ev.length * framesinfo.gf_length;
×
1627
    return helpers::interpolate(x1, x2, elapsedTime, duration);
×
1628
}
1629

1630
unsigned GameClient::Interpolate(const unsigned maxVal, const GameEvent* ev) const
×
1631
{
1632
    return do_interpolate(0u, maxVal, assertNonNull(ev));
×
1633
}
1634

1635
int GameClient::Interpolate(const int x1, const int x2, const GameEvent* ev) const
×
1636
{
1637
    return do_interpolate(x1, x2, assertNonNull(ev));
×
1638
}
1639

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

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

1657
    unsigned start_ticks = VIDEODRIVER.GetTickCount();
×
1658

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

1667
    SetPause(false);
×
1668
    if(GetGFNumber() < GetLastReplayGF())
×
1669
        gf = std::min(gf, GetLastReplayGF() + 1u);
×
1670
    skiptogf = gf;
×
1671

1672
    // GFs überspringen
1673
    for(unsigned i = GetGFNumber(); i < skiptogf; ++i)
×
1674
    {
1675
        if(i % 1000 == 0)
×
1676
        {
1677
            RoadBuildState road;
×
1678
            road.mode = RoadBuildMode::Disabled;
×
1679

1680
            // spiel aktualisieren
1681
            gwv.Draw(road, MapPoint::Invalid(), false);
×
1682

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

1689
            VIDEODRIVER.SwapBuffers();
×
1690
        }
1691
        ExecuteGameFrame();
×
1692
    }
1693
    // Either we force-stopped skipping (skiptogf set to 0) or we reached the target gf
1694
    RTTR_Assert(skiptogf == 0u || GetGFNumber() == gf);
×
1695

1696
    // Spiel pausieren & text ausgabe wie lang das jetzt gedauert hat
1697
    unsigned ticks = VIDEODRIVER.GetTickCount() - start_ticks;
×
1698
    boost::format text(_("Jump finished (%1$.3g seconds)."));
×
1699
    text % (ticks / 1000.0);
×
1700
    SystemChat(text.str());
×
1701
    SetPause(true);
×
1702
}
1703

1704
void GameClient::SystemChat(const std::string& text)
×
1705
{
1706
    SystemChat(text, GetPlayerId());
×
1707
}
×
1708

1709
void GameClient::SystemChat(const std::string& text, unsigned char fromPlayerIdx)
×
1710
{
1711
    if(ci)
×
1712
        ci->CI_Chat(fromPlayerIdx, ChatDestination::System, text);
×
1713
}
×
1714

1715
bool GameClient::SaveToFile(const boost::filesystem::path& filepath)
×
1716
{
1717
    mainPlayer.sendMsg(GameMessage_Chat(GetPlayerId(), ChatDestination::System, _("Saving game...")));
×
1718

1719
    // Mond malen
1720
    Position moonPos = VIDEODRIVER.GetMousePos();
×
1721
    moonPos.y -= 40;
×
1722
    LOADER.GetImageN("resource", 33)->DrawFull(moonPos);
×
1723
    VIDEODRIVER.SwapBuffers();
×
1724

1725
    Savegame save;
×
1726

1727
    WritePlayerInfo(save);
×
1728

1729
    // GGS-Daten
1730
    save.ggs = game->ggs_;
×
1731

1732
    save.start_gf = GetGFNumber();
×
1733

1734
    // Enable/Disable debugging of savegames
1735
    save.sgd.debugMode = SETTINGS.global.debugMode;
×
1736

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

1750
void GameClient::ResetVisualSettings()
×
1751
{
1752
    GetPlayer(GetPlayerId()).FillVisualSettings(visual_settings);
×
1753
}
×
1754

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

1779
void GameClient::SetReplayFOW(const bool hideFOW)
×
1780
{
1781
    if(replayinfo)
×
1782
        replayinfo->all_visible = hideFOW;
×
1783
}
×
1784

1785
bool GameClient::IsReplayFOWDisabled() const
×
1786
{
1787
    return replayMode && replayinfo->all_visible;
×
1788
}
1789

1790
unsigned GameClient::GetLastReplayGF() const
×
1791
{
1792
    return replayinfo ? replayinfo->replay.GetLastGF() : 0u;
×
1793
}
1794

1795
bool GameClient::AddGC(gc::GameCommandPtr gc)
×
1796
{
1797
    // Nicht in der Pause oder wenn er besiegt wurde
1798
    if(framesinfo.isPaused || GetPlayer(GetPlayerId()).IsDefeated() || IsReplayModeOn())
×
1799
        return false;
×
1800

1801
    gameCommands_.push_back(gc);
×
1802
    return true;
×
1803
}
1804

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

1811
GamePlayer& GameClient::GetPlayer(const unsigned id)
×
1812
{
1813
    RTTR_Assert(state == ClientState::Loading || state == ClientState::Loaded || state == ClientState::Game);
×
1814
    RTTR_Assert(id < GetNumPlayers());
×
1815
    return game->world_.GetPlayer(id);
×
1816
}
1817

1818
std::unique_ptr<AIPlayer> GameClient::CreateAIPlayer(unsigned playerId, const AI::Info& aiInfo)
×
1819
{
1820
    return AIFactory::Create(aiInfo, playerId, game->world_);
×
1821
}
1822

1823
/// Wandelt eine GF-Angabe in eine Zeitangabe um (HH:MM:SS oder MM:SS wenn Stunden = 0)
1824
std::string GameClient::FormatGFTime(const unsigned gf) const
5 ✔
1825
{
1826
    using seconds = std::chrono::duration<uint32_t, std::chrono::seconds::period>;
1827
    using hours = std::chrono::duration<uint32_t, std::chrono::hours::period>;
1828
    using minutes = std::chrono::duration<uint32_t, std::chrono::minutes::period>;
1829
    using std::chrono::duration_cast;
1830

1831
    seconds numSeconds = duration_cast<seconds>(gfs_to_duration(gf));
5 ✔
1832

1833
    hours numHours = duration_cast<hours>(numSeconds);
5 ✔
1834
    numSeconds -= numHours;
5 ✔
1835
    minutes numMinutes = duration_cast<minutes>(numSeconds);
5 ✔
1836
    numSeconds -= numMinutes;
5 ✔
1837

1838
    // Use hour format only if we have hours
1839
    if(numHours.count())
5 ✔
1840
        return helpers::format("%u:%02u:%02u", numHours.count(), numMinutes.count(), numSeconds.count());
×
1841
    else
1842
        return helpers::format("%02u:%02u", numMinutes.count(), numSeconds.count());
10 ✔
1843
}
1844

1845
const boost::filesystem::path& GameClient::GetReplayFilename() const
×
1846
{
1847
    static boost::filesystem::path emptyString;
×
1848
    return replayinfo ? replayinfo->filename : emptyString;
×
1849
}
1850

1851
Replay* GameClient::GetReplay()
×
1852
{
1853
    return replayinfo ? &replayinfo->replay : nullptr;
×
1854
}
1855

1856
std::shared_ptr<const NWFInfo> GameClient::GetNWFInfo() const
×
1857
{
1858
    return nwfInfo;
×
1859
}
1860

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

1875
void GameClient::ToggleHumanAIPlayer(const AI::Info& aiInfo)
×
1876
{
1877
    RTTR_Assert(!IsReplayModeOn());
×
1878
    auto it = helpers::find_if(game->aiPlayers_,
×
1879
                               [id = this->GetPlayerId()](const auto& player) { return player.GetPlayerId() == id; });
×
1880
    if(it != game->aiPlayers_.end())
×
1881
    {
1882
        game->aiPlayers_.erase(it);
×
1883
        SystemChat(_("Disabled AI for current player"));
×
1884
    } else
1885
    {
1886
        game->AddAIPlayer(CreateAIPlayer(GetPlayerId(), aiInfo));
×
1887
        SystemChat(_("Enabled AI for current player"));
×
1888
    }
1889
}
×
1890

1891
void GameClient::RequestSwapToPlayer(const unsigned char newId)
×
1892
{
1893
    if(state != ClientState::Game)
×
1894
        return;
×
1895
    GamePlayer& player = GetPlayer(newId);
×
1896
    if(player.ps == PlayerState::AI && player.aiInfo.type == AI::Type::Dummy)
×
1897
        mainPlayer.sendMsgAsync(new GameMessage_Player_Swap(0xFF, newId));
×
1898
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc