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

Return-To-The-Roots / s25client / 26253516618

21 May 2026 09:14PM UTC coverage: 50.373% (+0.08%) from 50.29%
26253516618

Pull #1925

github

web-flow
Merge 70b841fe8 into 64b706b67
Pull Request #1925: Ignore isolated fish resources for fisheries

36 of 63 new or added lines in 4 files covered. (57.14%)

5 existing lines in 2 files now uncovered.

23185 of 46027 relevant lines covered (50.37%)

44209.73 hits per line

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

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

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

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

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

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

93
GameClient::~GameClient()
19✔
94
{
95
    Stop();
16✔
96
}
19✔
97

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

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

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

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

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

132
    state = ClientState::Connect;
7✔
133
    AdvanceState(ConnectState::Initiated);
7✔
134

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

138
    return true;
7✔
139
}
140

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

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

163
    SocketSet set;
×
164

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

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

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

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

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

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

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

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

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

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

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

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

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

239
    mainPlayer.closeConnection();
7✔
240

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

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

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

251
    aiBattlePlayers_.clear();
7✔
252
}
253

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

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

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

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

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

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

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

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

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

315
    state = ClientState::Loading;
×
316

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

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

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

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

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

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

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

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

363
    state = ClientState::Loaded;
×
364

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

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

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

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

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

424
    mainPlayer.playerId = msg.player;
7✔
425

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

724
    mainPlayer.sendMsgAsync(new GameMessage_Server_Password(clientconfig.password));
7✔
725

726
    AdvanceState(ConnectState::QueryPw);
7✔
727
    return true;
7✔
728
}
729

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

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

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

748
    AdvanceState(ConnectState::QueryMapInfo);
6✔
749
    return true;
6✔
750
}
751

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

920
    // full path
921
    const std::string portFilename = makePortableFileName(msg.filename);
12✔
922
    if(portFilename.empty())
6✔
923
    {
924
        LOG.write("Invalid filename received!\n");
×
925
        OnError(ClientError::InvalidMap);
×
926
        return true;
×
927
    }
928
    if(!MapInfo::verifySize(msg.mapLen, msg.luaLen, msg.mapCompressedLen, msg.luaCompressedLen))
6✔
929
    {
930
        OnError(ClientError::InvalidMap);
×
931
        return true;
×
932
    }
933
    const auto targetPath =
934
      RTTRCONFIG.ExpandPath((msg.mt == MapType::Savegame) ? s25::folders::save : s25::folders::mapsPlayed);
18✔
935
    bfs::create_directories(targetPath);
6✔
936
    mapinfo.filepath = targetPath / portFilename;
12✔
937
    mapinfo.type = msg.mt;
6✔
938

939
    // lua script file path
940
    if(msg.luaLen > 0)
6✔
941
        mapinfo.luaFilepath = bfs::path(mapinfo.filepath).replace_extension("lua");
4✔
942
    else
943
        mapinfo.luaFilepath.clear();
4✔
944

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

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

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

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

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

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

1021
        if(!CreateLobby())
2✔
1022
        {
1023
            OnError(ClientError::MapTransmission);
×
1024
            return true;
×
1025
        }
1026

1027
        mainPlayer.sendMsgAsync(new GameMessage_Map_Checksum(mapinfo.mapChecksum, mapinfo.luaChecksum));
2✔
1028
        AdvanceState(ConnectState::VerifyMap);
2✔
1029
    }
1030
    return true;
7✔
1031
}
1032

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

1040
void GameClient::OnError(ClientError error)
2✔
1041
{
1042
    if(ci)
2✔
1043
        ci->CI_Error(error);
2✔
1044
    Stop();
2✔
1045
}
2✔
1046

1047
void GameClient::AdvanceState(ConnectState newState)
51✔
1048
{
1049
    connectState = newState;
51✔
1050
    if(ci)
51✔
1051
        ci->CI_NextConnectState(connectState);
46✔
1052
}
51✔
1053

1054
bool GameClient::VerifyState(ConnectState expectedState)
51✔
1055
{
1056
    if(state != ClientState::Connect || connectState != expectedState)
51✔
1057
    {
1058
        OnError(ClientError::InvalidMessage);
×
1059
        return false;
×
1060
    }
1061
    return true;
51✔
1062
}
1063

1064
bool GameClient::CreateLobby()
4✔
1065
{
1066
    RTTR_Assert(!gameLobby);
4✔
1067

1068
    unsigned numPlayers;
1069

1070
    switch(mapinfo.type)
4✔
1071
    {
1072
        case MapType::OldMap:
4✔
1073
        {
1074
            libsiedler2::Archiv map;
4✔
1075

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

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

1094
            numPlayers = mapinfo.savegame->GetNumPlayers();
×
1095
            mapinfo.title = mapinfo.savegame->GetMapName();
×
1096
            break;
×
1097
        default: return false;
×
1098
    }
1099

1100
    if(GetPlayerId() >= numPlayers)
4✔
1101
        return false;
×
1102

1103
    gameLobby = std::make_shared<GameLobby>(mapinfo.type == MapType::Savegame, IsHost(), numPlayers);
4✔
1104
    return true;
4✔
1105
}
1106

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

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

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

1140
    gameLobby->getSettings() = msg.ggs;
4✔
1141

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

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

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

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

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

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

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

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

1228
    return true;
×
1229
}
1230

1231
bool GameClient::OnGameMessage(const GameMessage_Pause& msg)
×
1232
{
1233
    // Ignore recorded pause messages in replay mode
1234
    if(state != ClientState::Game || replayMode)
×
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.autosaveInterval || replayMode)
×
1398
        return;
×
1399

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

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

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

1589
    return true;
×
1590
}
1591

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

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

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

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

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

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

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

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

1663
    SetPause(false);
×
1664
    if(GetGFNumber() < GetLastReplayGF())
×
1665
        gf = std::min(gf, GetLastReplayGF() + 1u);
×
1666
    skiptogf = gf;
×
1667

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

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

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

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

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

1700
void GameClient::SystemChat(const std::string& text)
×
1701
{
1702
    SystemChat(text, GetPlayerId());
×
1703
}
×
1704

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

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

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

1721
    Savegame save;
×
1722

1723
    WritePlayerInfo(save);
×
1724

1725
    // GGS-Daten
1726
    save.ggs = game->ggs_;
×
1727

1728
    save.start_gf = GetGFNumber();
×
1729

1730
    // Enable/Disable debugging of savegames
1731
    save.sgd.debugMode = SETTINGS.global.debugMode;
×
1732

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

1746
void GameClient::ResetVisualSettings()
×
1747
{
1748
    GetPlayer(GetPlayerId()).FillVisualSettings(visual_settings);
×
1749
}
×
1750

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

1775
void GameClient::SetReplayFOW(const bool hideFOW)
×
1776
{
1777
    if(replayinfo)
×
1778
        replayinfo->all_visible = hideFOW;
×
1779
}
×
1780

1781
bool GameClient::IsReplayFOWDisabled() const
×
1782
{
1783
    return replayMode && replayinfo->all_visible;
×
1784
}
1785

1786
unsigned GameClient::GetLastReplayGF() const
×
1787
{
1788
    return replayinfo ? replayinfo->replay.GetLastGF() : 0u;
×
1789
}
1790

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

1797
    gameCommands_.push_back(gc);
×
1798
    return true;
×
1799
}
1800

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

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

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

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

1827
    seconds numSeconds = duration_cast<seconds>(gfs_to_duration(gf));
5✔
1828

1829
    hours numHours = duration_cast<hours>(numSeconds);
5✔
1830
    numSeconds -= numHours;
5✔
1831
    minutes numMinutes = duration_cast<minutes>(numSeconds);
5✔
1832
    numSeconds -= numMinutes;
5✔
1833

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

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

1847
Replay* GameClient::GetReplay()
×
1848
{
1849
    return replayinfo ? &replayinfo->replay : nullptr;
×
1850
}
1851

1852
std::shared_ptr<const NWFInfo> GameClient::GetNWFInfo() const
×
1853
{
1854
    return nwfInfo;
×
1855
}
1856

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

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

1887
void GameClient::RequestSwapToPlayer(const unsigned char newId)
×
1888
{
1889
    if(state != ClientState::Game)
×
1890
        return;
×
1891
    GamePlayer& player = GetPlayer(newId);
×
1892
    if(player.ps == PlayerState::AI && player.aiInfo.type == AI::Type::Dummy)
×
1893
        mainPlayer.sendMsgAsync(new GameMessage_Player_Swap(0xFF, newId));
×
1894
}
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