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

Return-To-The-Roots / s25client / 4621997437

pending completion
4621997437

push

github

Flow86
fix path

21886 of 43348 relevant lines covered (50.49%)

31880.72 hits per line

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

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

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

57
void GameClient::ClientConfig::Clear()
9✔
58
{
59
    server.clear();
9✔
60
    gameName.clear();
9✔
61
    password.clear();
9✔
62
    port = 0;
9✔
63
    isHost = false;
9✔
64
}
9✔
65

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

68
GameClient::~GameClient()
9✔
69
{
70
    Stop();
6✔
71
}
9✔
72

73
/**
74
 *  Verbindet den Client mit einem Server
75
 *
76
 *  @param server    Hostname des Zielrechners
77
 *  @param password  Passwort des Spieles
78
 *  @param servertyp Servertyp des Spieles (Direct/LAN/usw)
79
 *  @param host      gibt an ob wir selbst der Host sind
80
 *
81
 *  @return true, wenn Client erfolgreich verbunden und gestartet
82
 */
83
bool GameClient::Connect(const std::string& server, const std::string& password, ServerType servertyp,
3✔
84
                         unsigned short port, bool host, bool use_ipv6)
85
{
86
    Stop();
3✔
87

88
    RTTR_Assert(aiBattlePlayers_.empty());
3✔
89

90
    // Name und Password kopieren
91
    clientconfig.server = server;
3✔
92
    clientconfig.password = password;
3✔
93

94
    clientconfig.servertyp = servertyp;
3✔
95
    clientconfig.port = port;
3✔
96
    clientconfig.isHost = host;
3✔
97

98
    // Verbinden
99
    if(!mainPlayer.socket.Connect(server, port, use_ipv6, SETTINGS.proxy))
3✔
100
    {
101
        LOG.write("GameClient::Connect: ERROR: Connect failed!\n");
×
102
        if(host)
×
103
            GAMESERVER.Stop();
×
104
        return false;
×
105
    }
106

107
    state = ClientState::Connect;
3✔
108
    AdvanceState(ConnectState::Initiated);
3✔
109

110
    // Es wird kein Replay abgespielt, sondern dies ist ein richtiges Spiel
111
    replayMode = false;
3✔
112

113
    return true;
3✔
114
}
115

116
bool GameClient::HostGame(const CreateServerInfo& csi, const boost::filesystem::path& map_path, MapType map_type)
×
117
{
118
    std::string hostPw = createRandString(20);
×
119
    // Copy the map to the played map folders to avoid having to transmit it from the (local) server
120
    const auto playedMapPath = RTTRCONFIG.ExpandPath(s25::folders::mapsPlayed) / map_path.filename();
×
121
    if(playedMapPath != map_path)
×
122
    {
123
        boost::system::error_code ignoredEc;
×
124
        constexpr auto overwrite_existing =
×
125
#if BOOST_VERSION >= 107400
126
          boost::filesystem::copy_options::overwrite_existing;
127
#else
128
          boost::filesystem::copy_option::overwrite_if_exists;
129
#endif
130
        copy_file(map_path, playedMapPath, overwrite_existing, ignoredEc);
×
131
    }
132
    return GAMESERVER.Start(csi, map_path, map_type, hostPw)
×
133
           && Connect("localhost", hostPw, csi.type, csi.port, true, csi.ipv6);
×
134
}
×
135

136
/**
137
 *  Hauptschleife des Clients
138
 */
139
void GameClient::Run()
×
140
{
141
    if(state == ClientState::Stopped)
×
142
        return;
×
143

144
    SocketSet set;
×
145

146
    // erstmal auf Daten überprüfen
147
    set.Clear();
×
148

149
    // zum set hinzufügen
150
    set.Add(mainPlayer.socket);
×
151
    if(set.Select(0, 0) > 0)
×
152
    {
153
        // nachricht empfangen
154
        if(!mainPlayer.receiveMsgs())
×
155
        {
156
            LOG.write("Receiving Message from server failed\n");
×
157
            ServerLost();
×
158
        }
159
    }
160

161
    // nun auf Fehler prüfen
162
    set.Clear();
×
163

164
    // zum set hinzufügen
165
    set.Add(mainPlayer.socket);
×
166

167
    // auf fehler prüfen
168
    if(set.Select(0, 2) > 0)
×
169
    {
170
        if(set.InSet(mainPlayer.socket))
×
171
        {
172
            // Server ist weg
173
            LOG.write("Error on socket to server\n");
×
174
            ServerLost();
×
175
        }
176
    }
177

178
    if(state == ClientState::Loaded)
×
179
    {
180
        // All players ready?
181
        if(nwfInfo->isReady())
×
182
            OnGameStart();
×
183
    } else if(state == ClientState::Game)
×
184
        ExecuteGameFrame();
×
185

186
    // maximal 10 Pakete verschicken
187
    mainPlayer.sendMsgs(10);
×
188

189
    mainPlayer.executeMsgs(*this);
×
190
}
191

192
/**
193
 *  Stoppt das Spiel
194
 */
195
void GameClient::Stop()
11✔
196
{
197
    if(state == ClientState::Stopped)
11✔
198
        return;
8✔
199

200
    if(game)
3✔
201
        ExitGame();
×
202
    else if(state == ClientState::Connect || state == ClientState::Config)
3✔
203
        gameLobby.reset();
3✔
204

205
    if(IsHost())
3✔
206
        GAMESERVER.Stop();
×
207

208
    framesinfo.Clear();
3✔
209
    clientconfig.Clear();
3✔
210
    mapinfo.Clear();
3✔
211

212
    if(replayinfo)
3✔
213
    {
214
        if(replayinfo->replay.IsRecording())
×
215
            replayinfo->replay.StopRecording();
×
216
        replayinfo->replay.Close();
×
217
        replayinfo.reset();
×
218
    }
219

220
    mainPlayer.closeConnection();
3✔
221

222
    // clear jump target
223
    skiptogf = 0;
3✔
224

225
    // Consistency check: No game, no lobby remaining
226
    RTTR_Assert(!game);
3✔
227
    RTTR_Assert(!gameLobby);
3✔
228

229
    state = ClientState::Stopped;
3✔
230
    LOG.write("client state changed to stop\n");
3✔
231

232
    aiBattlePlayers_.clear();
3✔
233
}
234

235
std::shared_ptr<GameLobby> GameClient::GetGameLobby()
1✔
236
{
237
    RTTR_Assert(state != ClientState::Config || gameLobby);
1✔
238
    return gameLobby;
1✔
239
}
240

241
const AIPlayer* GameClient::GetAIPlayer(unsigned id) const
×
242
{
243
    if(!game)
×
244
        return nullptr;
×
245
    return game->GetAIPlayer(id);
×
246
}
247

248
/**
249
 *  Startet ein Spiel oder Replay.
250
 *
251
 *  @param[in] random_init Initialwert des Zufallsgenerators.
252
 */
253
void GameClient::StartGame(const unsigned random_init)
×
254
{
255
    RTTR_Assert(state == ClientState::Config || (state == ClientState::Stopped && replayMode));
×
256

257
    // Mond malen
258
    Position moonPos = VIDEODRIVER.GetMousePos();
×
259
    moonPos.y -= 40;
×
260
    LOADER.GetImageN("resource", 33)->DrawFull(moonPos);
×
261
    VIDEODRIVER.SwapBuffers();
×
262

263
    // Start in pause mode
264
    framesinfo.isPaused = true;
×
265

266
    // Je nach Geschwindigkeit GF-Länge einstellen
267
    framesinfo.gf_length = SPEED_GF_LENGTHS[gameLobby->getSettings().speed];
×
268
    framesinfo.gfLengthReq = framesinfo.gf_length;
×
269

270
    // Random-Generator initialisieren
271
    RANDOM.Init(random_init);
×
272

273
    if(!IsReplayModeOn() && mapinfo.savegame && !mapinfo.savegame->Load(mapinfo.filepath, SaveGameDataToLoad::All))
×
274
    {
275
        OnError(ClientError::InvalidMap);
×
276
        return;
×
277
    }
278

279
    // If we have a savegame, start at its first GF, else at 0
280
    unsigned startGF = (mapinfo.type == MapType::Savegame) ? mapinfo.savegame->start_gf : 0;
×
281
    // Create the game
282
    game =
283
      std::make_shared<Game>(std::move(gameLobby->getSettings()), startGF,
×
284
                             std::vector<PlayerInfo>(gameLobby->getPlayers().begin(), gameLobby->getPlayers().end()));
×
285
    if(!IsReplayModeOn())
×
286
    {
287
        for(unsigned id = 0; id < gameLobby->getNumPlayers(); id++)
×
288
        {
289
            if(gameLobby->getPlayer(id).isUsed())
×
290
                nwfInfo->addPlayer(id);
×
291
        }
292
    }
293
    // Release lobby
294
    gameLobby.reset();
×
295

296
    state = ClientState::Loading;
×
297

298
    if(ci)
×
299
        ci->CI_GameLoading(game);
×
300

301
    // Get standard settings before they get overwritten
302
    GetPlayer(GetPlayerId()).FillVisualSettings(default_settings);
×
303

304
    GameWorld& gameWorld = game->world_;
×
305
    if(mapinfo.savegame)
×
306
        mapinfo.savegame->sgd.ReadSnapshot(*game, *this);
×
307
    else
308
    {
309
        RTTR_Assert(mapinfo.type != MapType::Savegame);
×
310
        /// Startbündnisse setzen
311
        for(unsigned i = 0; i < gameWorld.GetNumPlayers(); ++i)
×
312
            gameWorld.GetPlayer(i).MakeStartPacts();
×
313

314
        MapLoader loader(gameWorld);
×
315
        if(!loader.Load(mapinfo.filepath)
×
316
           || (!mapinfo.luaFilepath.empty() && !loader.LoadLuaScript(*game, *this, mapinfo.luaFilepath)))
×
317
        {
318
            OnError(ClientError::InvalidMap);
×
319
            return;
×
320
        }
321
        gameWorld.SetupResources();
×
322
    }
×
323
    gameWorld.InitAfterLoad();
×
324

325
    // Update visual settings
326
    ResetVisualSettings();
×
327

328
    if(!replayMode)
×
329
    {
330
        RTTR_Assert(!replayinfo);
×
331
        StartReplayRecording(random_init);
×
332
    }
333

334
    // Daten nach dem Schreiben des Replays ggf wieder löschen
335
    mapinfo.mapData.Clear();
×
336
}
337

338
void GameClient::GameLoaded()
×
339
{
340
    RTTR_Assert(state == ClientState::Loading);
×
341

342
    state = ClientState::Loaded;
×
343

344
    if(replayMode)
×
345
        OnGameStart();
×
346
    else
347
    {
348
        // Notify server that we are ready
349
        if(IsHost())
×
350
        {
351
            for(unsigned id = 0; id < GetNumPlayers(); id++)
×
352
            {
353
                if(GetPlayer(id).ps == PlayerState::AI)
×
354
                {
355
                    game->AddAIPlayer(CreateAIPlayer(id, GetPlayer(id).aiInfo));
×
356
                    SendNothingNC(id);
×
357
                }
358
            }
359
            if(IsAIBattleModeOn())
×
360
                ToggleHumanAIPlayer(aiBattlePlayers_[GetPlayerId()]);
×
361
        }
362
        SendNothingNC();
×
363
    }
364
}
×
365

366
void GameClient::ExitGame()
×
367
{
368
    RTTR_Assert(state == ClientState::Game || state == ClientState::Loaded || state == ClientState::Loading);
×
369
    game.reset();
×
370
    nwfInfo.reset();
×
371
    // Clear remaining commands
372
    gameCommands_.clear();
×
373
}
×
374

375
unsigned GameClient::GetGFNumber() const
×
376
{
377
    return game->em_->GetCurrentGF();
×
378
}
379

380
/**
381
 *  Ping-Nachricht.
382
 */
383
bool GameClient::OnGameMessage(const GameMessage_Ping& /*msg*/)
×
384
{
385
    mainPlayer.sendMsgAsync(new GameMessage_Pong());
×
386
    return true;
×
387
}
388

389
/**
390
 *  Player-ID-Nachricht.
391
 */
392
bool GameClient::OnGameMessage(const GameMessage_Player_Id& msg)
3✔
393
{
394
    if(!VerifyState(ConnectState::Initiated))
3✔
395
        return true;
×
396
    // haben wir eine ungültige ID erhalten? (aka Server-Voll)
397
    if(msg.player == GameMessageWithPlayer::NO_PLAYER_ID)
3✔
398
    {
399
        OnError(ClientError::ServerFull);
×
400
        return true;
×
401
    }
402

403
    mainPlayer.playerId = msg.player;
3✔
404

405
    // Server-Typ senden
406
    mainPlayer.sendMsgAsync(new GameMessage_Server_Type(clientconfig.servertyp, rttr::version::GetRevision()));
3✔
407
    AdvanceState(ConnectState::VerifyServer);
3✔
408
    return true;
3✔
409
}
410

411
/**
412
 *  Player-List-Nachricht.
413
 */
414
bool GameClient::OnGameMessage(const GameMessage_Player_List& msg)
2✔
415
{
416
    if(state != ClientState::Config && !VerifyState(ConnectState::QueryPlayerList))
2✔
417
        return true;
×
418
    RTTR_Assert(gameLobby);
2✔
419
    RTTR_Assert(gameLobby->getNumPlayers() == msg.playerInfos.size());
2✔
420
    if(gameLobby->getNumPlayers() != msg.playerInfos.size())
2✔
421
    {
422
        OnError(ClientError::InvalidMessage);
×
423
        return true;
×
424
    }
425

426
    for(unsigned i = 0; i < gameLobby->getNumPlayers(); ++i)
8✔
427
        gameLobby->getPlayer(i) = msg.playerInfos[i];
6✔
428

429
    if(state == ClientState::Connect)
2✔
430
        AdvanceState(ConnectState::QuerySettings);
2✔
431
    return true;
2✔
432
}
433

434
bool GameClient::OnGameMessage(const GameMessage_Player_Name& msg)
×
435
{
436
    if(state != ClientState::Config)
×
437
        return true;
×
438
    if(msg.player >= gameLobby->getNumPlayers())
×
439
        return true;
×
440
    gameLobby->getPlayer(msg.player).name = msg.playername;
×
441
    if(ci)
×
442
        ci->CI_PlayerDataChanged(msg.player);
×
443
    return true;
×
444
}
445

446
///////////////////////////////////////////////////////////////////////////////
447
/// player joined
448
/// @param message  Nachricht, welche ausgeführt wird
449
bool GameClient::OnGameMessage(const GameMessage_Player_New& msg)
×
450
{
451
    if(state != ClientState::Config)
×
452
        return true;
×
453

454
    if(msg.player >= gameLobby->getNumPlayers())
×
455
        return true;
×
456

457
    JoinPlayerInfo& playerInfo = gameLobby->getPlayer(msg.player);
×
458

459
    playerInfo.name = msg.name;
×
460
    playerInfo.ps = PlayerState::Occupied;
×
461
    playerInfo.ping = 0;
×
462

463
    if(ci)
×
464
        ci->CI_NewPlayer(msg.player);
×
465
    return true;
×
466
}
467

468
bool GameClient::OnGameMessage(const GameMessage_Player_Ping& msg)
×
469
{
470
    if(state == ClientState::Config)
×
471
    {
472
        if(msg.player >= gameLobby->getNumPlayers())
×
473
            return true;
×
474
        gameLobby->getPlayer(msg.player).ping = msg.ping;
×
475
    } else if(state == ClientState::Loading || state == ClientState::Loaded || state == ClientState::Game)
×
476
    {
477
        if(msg.player >= GetNumPlayers())
×
478
            return true;
×
479
        GetPlayer(msg.player).ping = msg.ping;
×
480
    } else
481
    {
482
        RTTR_Assert(false);
×
483
        return true;
484
    }
485

486
    if(ci)
×
487
        ci->CI_PingChanged(msg.player, msg.ping);
×
488
    return true;
×
489
}
490

491
/**
492
 *  Player-Toggle-State-Nachricht.
493
 */
494
bool GameClient::OnGameMessage(const GameMessage_Player_State& msg)
×
495
{
496
    if(state != ClientState::Config)
×
497
        return true;
×
498

499
    if(msg.player >= gameLobby->getNumPlayers())
×
500
        return true;
×
501

502
    JoinPlayerInfo& playerInfo = gameLobby->getPlayer(msg.player);
×
503
    bool wasUsed = playerInfo.isUsed();
×
504
    playerInfo.ps = msg.ps;
×
505
    playerInfo.aiInfo = msg.aiInfo;
×
506

507
    if(ci)
×
508
    {
509
        if(playerInfo.isUsed())
×
510
            ci->CI_NewPlayer(msg.player);
×
511
        else if(wasUsed)
×
512
            ci->CI_PlayerLeft(msg.player);
×
513
        else
514
            ci->CI_PlayerDataChanged(msg.player);
×
515
    }
516
    return true;
×
517
}
518

519
///////////////////////////////////////////////////////////////////////////////
520
/// nation button gedrückt
521
/// @param message  Nachricht, welche ausgeführt wird
522
bool GameClient::OnGameMessage(const GameMessage_Player_Nation& msg)
×
523
{
524
    if(state != ClientState::Config)
×
525
        return true;
×
526

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

530
    gameLobby->getPlayer(msg.player).nation = msg.nation;
×
531

532
    if(ci)
×
533
        ci->CI_PlayerDataChanged(msg.player);
×
534
    return true;
×
535
}
536

537
///////////////////////////////////////////////////////////////////////////////
538
/// team button gedrückt
539
/// @param message  Nachricht, welche ausgeführt wird
540
bool GameClient::OnGameMessage(const GameMessage_Player_Team& msg)
×
541
{
542
    if(state != ClientState::Config)
×
543
        return true;
×
544

545
    if(msg.player >= gameLobby->getNumPlayers())
×
546
        return true;
×
547

548
    gameLobby->getPlayer(msg.player).team = msg.team;
×
549

550
    if(ci)
×
551
        ci->CI_PlayerDataChanged(msg.player);
×
552
    return true;
×
553
}
554

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

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

566
    gameLobby->getPlayer(msg.player).color = msg.color;
×
567

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

573
/**
574
 *  Ready-state eines Spielers hat sich geändert.
575
 *
576
 *  @param[in] message Nachricht, welche ausgeführt wird
577
 */
578
bool GameClient::OnGameMessage(const GameMessage_Player_Ready& msg)
×
579
{
580
    if(state != ClientState::Config)
×
581
        return true;
×
582

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

586
    gameLobby->getPlayer(msg.player).isReady = msg.ready;
×
587

588
    if(ci)
×
589
        ci->CI_ReadyChanged(msg.player, msg.ready);
×
590
    return true;
×
591
}
592

593
///////////////////////////////////////////////////////////////////////////////
594
/// player gekickt
595
/// @param message  Nachricht, welche ausgeführt wird
596
bool GameClient::OnGameMessage(const GameMessage_Player_Kicked& msg)
×
597
{
598
    if(state == ClientState::Config)
×
599
    {
600
        if(msg.player >= gameLobby->getNumPlayers())
×
601
            return true;
×
602
        gameLobby->getPlayer(msg.player).ps = PlayerState::Free;
×
603
    } else if(state == ClientState::Loading || state == ClientState::Loaded || state == ClientState::Game)
×
604
    {
605
        // Im Spiel anzeigen, dass der Spieler das Spiel verlassen hat
606
        GamePlayer& player = GetPlayer(msg.player);
×
607
        if(player.ps != PlayerState::AI)
×
608
        {
609
            player.ps = PlayerState::AI;
×
610
            player.aiInfo = AI::Info(AI::Type::Dummy);
×
611
            // Host has to handle it
612
            if(IsHost())
×
613
            {
614
                game->AddAIPlayer(CreateAIPlayer(msg.player, player.aiInfo));
×
615
                SendNothingNC(msg.player);
×
616
            }
617
        }
618
    } else
×
619
        return true;
×
620

621
    if(ci)
×
622
        ci->CI_PlayerLeft(msg.player);
×
623
    return true;
×
624
}
625

626
bool GameClient::OnGameMessage(const GameMessage_Player_Swap& msg)
×
627
{
628
    LOG.writeToFile("<<< NMS_PLAYER_SWAP(%u, %u)\n") % unsigned(msg.player) % unsigned(msg.player2);
×
629

630
    if(state == ClientState::Config)
×
631
    {
632
        if(msg.player >= gameLobby->getNumPlayers() || msg.player2 >= gameLobby->getNumPlayers())
×
633
            return true;
×
634

635
        // During preparation just swap the players
636
        using std::swap;
637
        swap(gameLobby->getPlayer(msg.player), gameLobby->getPlayer(msg.player2));
×
638
        // Some things cannot be changed in savegames
639
        if(mapinfo.type == MapType::Savegame)
×
640
            gameLobby->getPlayer(msg.player).FixSwappedSaveSlot(gameLobby->getPlayer(msg.player2));
×
641

642
        // Evtl. sind wir betroffen?
643
        if(mainPlayer.playerId == msg.player)
×
644
            mainPlayer.playerId = msg.player2;
×
645
        else if(mainPlayer.playerId == msg.player2)
×
646
            mainPlayer.playerId = msg.player;
×
647

648
        if(ci)
×
649
            ci->CI_PlayersSwapped(msg.player, msg.player2);
×
650
    } else if(state == ClientState::Loading || state == ClientState::Loaded || state == ClientState::Game)
×
651
        ChangePlayerIngame(msg.player, msg.player2);
×
652
    else
653
        return true;
×
654
    mainPlayer.sendMsgAsync(new GameMessage_Player_SwapConfirm(msg.player, msg.player2));
×
655
    return true;
×
656
}
657

658
/**
659
 *  Server-Typ-Nachricht.
660
 */
661
bool GameClient::OnGameMessage(const GameMessage_Server_TypeOK& msg)
3✔
662
{
663
    if(!VerifyState(ConnectState::VerifyServer))
3✔
664
        return true;
×
665

666
    using StatusCode = GameMessage_Server_TypeOK::StatusCode;
667
    switch(msg.err_code)
3✔
668
    {
669
        case StatusCode::Ok: break;
3✔
670

671
        default:
×
672
        case StatusCode::InvalidServerType:
673
        {
674
            OnError(ClientError::InvalidServerType);
×
675
            return true;
×
676
        }
677
        break;
678

679
        case StatusCode::WrongVersion:
×
680
        {
681
            LOG.write(_("Version mismatch. Server version: %1%, your version %2%")) % msg.version
×
682
              % rttr::version::GetRevision();
×
683
            OnError(ClientError::WrongVersion);
×
684
            return true;
×
685
        }
686
        break;
687
    }
688

689
    mainPlayer.sendMsgAsync(new GameMessage_Server_Password(clientconfig.password));
3✔
690

691
    AdvanceState(ConnectState::QueryPw);
3✔
692
    return true;
3✔
693
}
694

695
/**
696
 *  Server-Passwort-Nachricht.
697
 */
698
bool GameClient::OnGameMessage(const GameMessage_Server_Password& msg)
3✔
699
{
700
    if(!VerifyState(ConnectState::QueryPw))
3✔
701
        return true;
×
702

703
    if(msg.password != "true")
3✔
704
    {
705
        OnError(ClientError::WrongPassword);
×
706
        return true;
×
707
    }
708

709
    mainPlayer.sendMsgAsync(new GameMessage_Player_Name(0xFF, SETTINGS.lobby.name));
3✔
710
    mainPlayer.sendMsgAsync(new GameMessage_MapRequest(true));
3✔
711

712
    AdvanceState(ConnectState::QueryMapInfo);
3✔
713
    return true;
3✔
714
}
715

716
/**
717
 *  Server-Name-Nachricht.
718
 */
719
bool GameClient::OnGameMessage(const GameMessage_Server_Name& msg)
2✔
720
{
721
    if(!VerifyState(ConnectState::QueryServerName))
2✔
722
        return true;
×
723
    clientconfig.gameName = msg.name;
2✔
724

725
    AdvanceState(ConnectState::QueryPlayerList);
2✔
726
    return true;
2✔
727
}
728

729
/**
730
 *  Server-Start-Nachricht
731
 */
732
bool GameClient::OnGameMessage(const GameMessage_Server_Start& msg)
×
733
{
734
    if(state != ClientState::Config)
×
735
        return true;
×
736

737
    nwfInfo = std::make_shared<NWFInfo>();
×
738
    nwfInfo->init(msg.firstNwf, msg.cmdDelay);
×
739
    try
740
    {
741
        StartGame(msg.random_init);
×
742
    } catch(SerializedGameData::Error& error)
×
743
    {
744
        LOG.write("Error when loading game: %s\n") % error.what();
×
745
        Stop();
×
746
        GAMEMANAGER.ShowMenu();
×
747
    }
×
748
    return true;
×
749
}
750

751
/**
752
 *  Server-Chat-Nachricht.
753
 */
754
bool GameClient::OnGameMessage(const GameMessage_Chat& msg)
×
755
{
756
    if(msg.destination == ChatDestination::System)
×
757
    {
758
        SystemChat(msg.text, (msg.player < game->world_.GetNumPlayers()) ? msg.player : GetPlayerId());
×
759
        return true;
×
760
    }
761
    if(state == ClientState::Game)
×
762
    {
763
        // Ingame message: Do some checking and logging
764
        if(msg.player >= game->world_.GetNumPlayers())
×
765
            return true;
×
766

767
        /// Mit im Replay aufzeichnen
768
        if(replayinfo && replayinfo->replay.IsRecording())
×
769
            replayinfo->replay.AddChatCommand(GetGFNumber(), msg.player, msg.destination, msg.text);
×
770

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

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

777
        const auto isValidRecipient = [&msg, &player](const unsigned playerId) {
×
778
            // Always send to self
779
            if(msg.player == playerId)
×
780
                return true;
×
781
            switch(msg.destination)
×
782
            {
783
                case ChatDestination::System:
×
784
                case ChatDestination::All: return true;
×
785
                case ChatDestination::Allies: return player.IsAlly(playerId);
×
786
                case ChatDestination::Enemies: return !player.IsAlly(playerId);
×
787
            }
788
            return true; // LCOV_EXCL_LINE
789
        };
×
790
        for(AIPlayer& ai : game->aiPlayers_)
×
791
        {
792
            if(isValidRecipient(ai.GetPlayerId()))
×
793
                ai.OnChatMessage(msg.player, msg.destination, msg.text);
×
794
        }
795

796
        if(!isValidRecipient(GetPlayerId()))
×
797
            return true;
×
798
    } else if(state == ClientState::Config)
×
799
    {
800
        // GameLobby message: Just check for valid player
801
        if(msg.player >= gameLobby->getNumPlayers())
×
802
            return true;
×
803
    } else
804
        return true;
×
805

806
    if(ci)
×
807
        ci->CI_Chat(msg.player, msg.destination, msg.text);
×
808
    return true;
×
809
}
810

811
/**
812
 *  Server-Async-Nachricht.
813
 */
814
bool GameClient::OnGameMessage(const GameMessage_Server_Async& msg)
×
815
{
816
    if(state != ClientState::Game)
×
817
        return true;
×
818

819
    // Liste mit Namen und Checksummen erzeugen
820
    std::stringstream checksum_list;
×
821
    for(unsigned i = 0; i < msg.checksums.size(); ++i)
×
822
    {
823
        checksum_list << GetPlayer(i).name << ": " << msg.checksums[i];
×
824
        if(i + 1 < msg.checksums.size())
×
825
            checksum_list << ", ";
×
826
    }
827

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

832
    // Messenger im Game
833
    if(ci)
×
834
        ci->CI_Async(checksum_list.str());
×
835

836
    std::string fileName = s25util::Time::FormatTime("async_%Y-%m-%d_%H-%i-%s");
×
837
    fileName += "_" + s25util::toStringClassic(GetPlayerId()) + "_";
×
838
    fileName += GetPlayer(GetPlayerId()).name;
×
839

840
    const bfs::path filePathSave = RTTRCONFIG.ExpandPath(s25::folders::save) / makePortableFileName(fileName + ".sav");
×
841
    const bfs::path filePathLog =
842
      RTTRCONFIG.ExpandPath(s25::folders::logs) / makePortableFileName(fileName + "Player.log");
×
843
    saveRandomLog(filePathLog, RANDOM.GetAsyncLog());
×
844
    SaveToFile(filePathSave);
×
845
    LOG.write(_("Async log saved at %1%,\ngame saved at %2%\n")) % filePathLog % filePathSave;
×
846
    return true;
×
847
}
×
848

849
/**
850
 *  Server-Countdown-Nachricht.
851
 */
852
bool GameClient::OnGameMessage(const GameMessage_Countdown& msg)
×
853
{
854
    if(state != ClientState::Config)
×
855
        return true;
×
856
    if(ci)
×
857
        ci->CI_Countdown(msg.countdown);
×
858
    return true;
×
859
}
860

861
/**
862
 *  Server-Cancel-Countdown-Nachricht.
863
 */
864
bool GameClient::OnGameMessage(const GameMessage_CancelCountdown& msg)
×
865
{
866
    if(state != ClientState::Config)
×
867
        return true;
×
868
    if(ci)
×
869
        ci->CI_CancelCountdown(msg.error);
×
870
    return true;
×
871
}
872

873
/**
874
 *  verarbeitet die MapInfo-Nachricht, in der die gepackte Größe,
875
 *  die normale Größe und Teilanzahl der Karte übertragen wird.
876
 *
877
 *  @param message Nachricht, welche ausgeführt wird
878
 */
879
bool GameClient::OnGameMessage(const GameMessage_Map_Info& msg)
3✔
880
{
881
    if(!VerifyState(ConnectState::QueryMapInfo))
3✔
882
        return true;
×
883

884
    // full path
885
    const std::string portFilename = makePortableFileName(msg.filename);
3✔
886
    if(portFilename.empty())
3✔
887
    {
888
        LOG.write("Invalid filename received!\n");
×
889
        OnError(ClientError::InvalidMap);
×
890
        return true;
×
891
    }
892
    if(!MapInfo::verifySize(msg.mapLen, msg.luaLen, msg.mapCompressedLen, msg.luaCompressedLen))
3✔
893
    {
894
        OnError(ClientError::InvalidMap);
×
895
        return true;
×
896
    }
897
    mapinfo.filepath = RTTRCONFIG.ExpandPath(s25::folders::mapsPlayed) / portFilename;
3✔
898
    mapinfo.type = msg.mt;
3✔
899

900
    // lua script file path
901
    if(msg.luaLen > 0)
3✔
902
        mapinfo.luaFilepath = bfs::path(mapinfo.filepath).replace_extension("lua");
1✔
903
    else
904
        mapinfo.luaFilepath.clear();
2✔
905

906
    // We have the map locally already, so prepare and ask if this is the same as the one on the server
907
    if(bfs::exists(mapinfo.filepath) && (mapinfo.luaFilepath.empty() || bfs::exists(mapinfo.luaFilepath))
3✔
908
       && CreateLobby())
3✔
909
    {
910
        mapinfo.mapData.CompressFromFile(mapinfo.filepath, &mapinfo.mapChecksum);
×
911
        if(mapinfo.mapData.data.size() == msg.mapCompressedLen && mapinfo.mapData.uncompressedLength == msg.mapLen)
×
912
        {
913
            bool ok = true;
×
914
            if(!mapinfo.luaFilepath.empty())
×
915
            {
916
                mapinfo.luaData.CompressFromFile(mapinfo.luaFilepath, &mapinfo.luaChecksum);
×
917
                ok = (mapinfo.luaData.data.size() == msg.luaCompressedLen
×
918
                      && mapinfo.luaData.uncompressedLength == msg.luaLen);
×
919
            }
920

921
            if(ok)
×
922
            {
923
                mainPlayer.sendMsgAsync(new GameMessage_Map_Checksum(mapinfo.mapChecksum, mapinfo.luaChecksum));
×
924
                AdvanceState(ConnectState::VerifyMap);
×
925
                return true;
×
926
            }
927
        }
928
        gameLobby.reset();
×
929
    }
930
    mapinfo.mapData.uncompressedLength = msg.mapLen;
3✔
931
    mapinfo.luaData.uncompressedLength = msg.luaLen;
3✔
932
    mapinfo.mapData.data.resize(msg.mapCompressedLen);
3✔
933
    mapinfo.luaData.data.resize(msg.luaCompressedLen);
3✔
934
    mainPlayer.sendMsgAsync(new GameMessage_MapRequest(false));
3✔
935
    AdvanceState(ConnectState::ReceiveMap);
3✔
936
    return true;
3✔
937
}
3✔
938

939
///////////////////////////////////////////////////////////////////////////////
940
/// Kartendaten
941
/// @param message  Nachricht, welche ausgeführt wird
942
bool GameClient::OnGameMessage(const GameMessage_Map_Data& msg)
8✔
943
{
944
    if(!VerifyState(ConnectState::ReceiveMap))
8✔
945
        return true;
×
946

947
    LOG.writeToFile("<<< NMS_MAP_DATA(%u)\n") % msg.data.size();
8✔
948
    std::vector<char>& targetData = (msg.isMapData) ? mapinfo.mapData.data : mapinfo.luaData.data;
8✔
949
    if(msg.data.size() > targetData.size() || msg.offset > targetData.size() - msg.data.size())
8✔
950
    {
951
        OnError(ClientError::MapTransmission);
1✔
952
        return true;
1✔
953
    }
954
    std::copy(msg.data.begin(), msg.data.end(), targetData.begin() + msg.offset);
7✔
955

956
    uint32_t totalSize = mapinfo.mapData.data.size();
7✔
957
    uint32_t receivedSize = msg.offset + msg.data.size();
7✔
958
    if(!mapinfo.luaFilepath.empty())
7✔
959
    {
960
        totalSize += mapinfo.luaData.data.size();
4✔
961
        // Assumes lua data comes after the map data
962
        if(!msg.isMapData)
4✔
963
            receivedSize += mapinfo.mapData.data.size();
2✔
964
    }
965
    if(ci)
7✔
966
        ci->CI_MapPartReceived(receivedSize, totalSize);
7✔
967

968
    if(receivedSize == totalSize)
7✔
969
    {
970
        if(!mapinfo.mapData.DecompressToFile(mapinfo.filepath, &mapinfo.mapChecksum))
2✔
971
        {
972
            OnError(ClientError::MapTransmission);
×
973
            return true;
×
974
        }
975
        if(!mapinfo.luaFilepath.empty() && !mapinfo.luaData.DecompressToFile(mapinfo.luaFilepath, &mapinfo.luaChecksum))
2✔
976
        {
977
            OnError(ClientError::MapTransmission);
×
978
            return true;
×
979
        }
980
        RTTR_Assert(!mapinfo.luaFilepath.empty() || mapinfo.luaChecksum == 0);
2✔
981

982
        if(!CreateLobby())
2✔
983
        {
984
            OnError(ClientError::MapTransmission);
×
985
            return true;
×
986
        }
987

988
        mainPlayer.sendMsgAsync(new GameMessage_Map_Checksum(mapinfo.mapChecksum, mapinfo.luaChecksum));
2✔
989
        AdvanceState(ConnectState::VerifyMap);
2✔
990
    }
991
    return true;
7✔
992
}
993

994
bool GameClient::OnGameMessage(const GameMessage_SkipToGF& msg)
×
995
{
996
    skiptogf = msg.targetGF;
×
997
    LOG.write("Jumping from GF %1% to GF %2%\n") % GetGFNumber() % skiptogf;
×
998
    return true;
×
999
}
1000

1001
void GameClient::OnError(ClientError error)
1✔
1002
{
1003
    if(ci)
1✔
1004
        ci->CI_Error(error);
1✔
1005
    Stop();
1✔
1006
}
1✔
1007

1008
void GameClient::AdvanceState(ConnectState newState)
25✔
1009
{
1010
    connectState = newState;
25✔
1011
    if(ci)
25✔
1012
        ci->CI_NextConnectState(connectState);
25✔
1013
}
25✔
1014

1015
bool GameClient::VerifyState(ConnectState expectedState)
28✔
1016
{
1017
    if(state != ClientState::Connect || connectState != expectedState)
28✔
1018
    {
1019
        OnError(ClientError::InvalidMessage);
×
1020
        return false;
×
1021
    }
1022
    return true;
28✔
1023
}
1024

1025
bool GameClient::CreateLobby()
2✔
1026
{
1027
    RTTR_Assert(!gameLobby);
2✔
1028

1029
    unsigned numPlayers;
1030

1031
    switch(mapinfo.type)
2✔
1032
    {
1033
        case MapType::OldMap:
2✔
1034
        {
1035
            libsiedler2::Archiv map;
2✔
1036

1037
            // Karteninformationen laden
1038
            if(libsiedler2::loader::LoadMAP(mapinfo.filepath, map, true) != 0)
2✔
1039
            {
1040
                LOG.write("GameClient::OnMapData: ERROR: Map %1%, couldn't load header!\n") % mapinfo.filepath;
×
1041
                return false;
×
1042
            }
1043

1044
            const libsiedler2::ArchivItem_Map_Header& header =
1045
              checkedCast<const libsiedler2::ArchivItem_Map*>(map.get(0))->getHeader();
2✔
1046
            numPlayers = header.getNumPlayers();
2✔
1047
            mapinfo.title = s25util::ansiToUTF8(header.getName());
2✔
1048
        }
2✔
1049
        break;
2✔
1050
        case MapType::Savegame:
×
1051
            mapinfo.savegame = std::make_unique<Savegame>();
×
1052
            if(!mapinfo.savegame->Load(mapinfo.filepath, SaveGameDataToLoad::HeaderAndSettings))
×
1053
                return false;
×
1054

1055
            numPlayers = mapinfo.savegame->GetNumPlayers();
×
1056
            mapinfo.title = mapinfo.savegame->GetMapName();
×
1057
            break;
×
1058
        default: return false;
×
1059
    }
1060

1061
    if(GetPlayerId() >= numPlayers)
2✔
1062
        return false;
×
1063

1064
    gameLobby = std::make_shared<GameLobby>(mapinfo.type == MapType::Savegame, IsHost(), numPlayers);
2✔
1065
    return true;
2✔
1066
}
1067

1068
///////////////////////////////////////////////////////////////////////////////
1069
/// map-checksum
1070
/// @param message  Nachricht, welche ausgeführt wird
1071
bool GameClient::OnGameMessage(const GameMessage_Map_ChecksumOK& msg)
2✔
1072
{
1073
    if(!VerifyState(ConnectState::VerifyMap))
2✔
1074
        return true;
×
1075
    LOG.writeToFile("<<< NMS_MAP_CHECKSUM(%d)\n") % (msg.correct ? 1 : 0);
2✔
1076

1077
    if(msg.correct)
2✔
1078
        AdvanceState(ConnectState::QueryServerName);
2✔
1079
    else
1080
    {
1081
        gameLobby.reset();
×
1082
        if(msg.retryAllowed)
×
1083
        {
1084
            mainPlayer.sendMsgAsync(new GameMessage_MapRequest(false));
×
1085
            AdvanceState(ConnectState::ReceiveMap);
×
1086
        } else
1087
            OnError(ClientError::MapTransmission);
×
1088
    }
1089
    return true;
2✔
1090
}
1091

1092
///////////////////////////////////////////////////////////////////////////////
1093
/// server typ
1094
/// @param message  Nachricht, welche ausgeführt wird
1095
bool GameClient::OnGameMessage(const GameMessage_GGSChange& msg)
2✔
1096
{
1097
    if(state != ClientState::Config && !VerifyState(ConnectState::QuerySettings))
2✔
1098
        return true;
×
1099
    LOG.writeToFile("<<< NMS_GGS_CHANGE\n");
2✔
1100

1101
    gameLobby->getSettings() = msg.ggs;
2✔
1102

1103
    if(state == ClientState::Connect)
2✔
1104
    {
1105
        state = ClientState::Config;
2✔
1106
        AdvanceState(ConnectState::Finished);
2✔
1107
    } else if(ci)
×
1108
        ci->CI_GGSChanged(msg.ggs);
×
1109
    return true;
2✔
1110
}
1111

1112
bool GameClient::OnGameMessage(const GameMessage_RemoveLua&)
×
1113
{
1114
    if(state != ClientState::Connect && state != ClientState::Config)
×
1115
        return true;
×
1116
    mapinfo.luaFilepath.clear();
×
1117
    mapinfo.luaData.Clear();
×
1118
    mapinfo.luaChecksum = 0;
×
1119
    return true;
×
1120
}
1121

1122
///////////////////////////////////////////////////////////////////////////////
1123
/// NFC Antwort vom Server
1124
/// @param message  Nachricht, welche ausgeführt wird
1125
bool GameClient::OnGameMessage(const GameMessage_GameCommand& msg)
×
1126
{
1127
    if(nwfInfo)
×
1128
    {
1129
        if(!nwfInfo->addPlayerCmds(msg.player, msg.cmds))
×
1130
        {
1131
            LOG.write("Could not add gamecommands for player %1%. He might be cheating!\n") % unsigned(msg.player);
×
1132
            RTTR_Assert(false);
×
1133
        }
1134
    }
1135
    return true;
×
1136
}
1137

1138
void GameClient::IncreaseSpeed()
×
1139
{
1140
    const bool debugMode =
×
1141
#ifndef NDEBUG
1142
      true;
1143
#else
1144
      false;
1145
#endif
1146
    if(framesinfo.gfLengthReq > FramesInfo::milliseconds32_t(10))
×
1147
        framesinfo.gfLengthReq -= FramesInfo::milliseconds32_t(10);
×
1148
    else if((replayMode || debugMode) && framesinfo.gfLengthReq == FramesInfo::milliseconds32_t(10))
×
1149
        framesinfo.gfLengthReq = FramesInfo::milliseconds32_t(1);
×
1150
    else
1151
        framesinfo.gfLengthReq = FramesInfo::milliseconds32_t(70);
×
1152

1153
    if(replayMode)
×
1154
        framesinfo.gf_length = framesinfo.gfLengthReq;
×
1155
    else
1156
        mainPlayer.sendMsgAsync(new GameMessage_Speed(framesinfo.gfLengthReq.count()));
×
1157
}
×
1158

1159
void GameClient::DecreaseSpeed()
×
1160
{
1161
    const bool debugMode =
×
1162
#ifndef NDEBUG
1163
      true;
1164
#else
1165
      false;
1166
#endif
1167

1168
    FramesInfo::milliseconds32_t maxSpeed(replayMode ? 1000 : 70);
×
1169

1170
    if(framesinfo.gfLengthReq == maxSpeed)
×
1171
        framesinfo.gfLengthReq = FramesInfo::milliseconds32_t(replayMode || debugMode ? 1 : 10);
×
1172
    else if(framesinfo.gfLengthReq == FramesInfo::milliseconds32_t(1))
×
1173
        framesinfo.gfLengthReq = FramesInfo::milliseconds32_t(10);
×
1174
    else
1175
        framesinfo.gfLengthReq += FramesInfo::milliseconds32_t(10);
×
1176

1177
    if(replayMode)
×
1178
        framesinfo.gf_length = framesinfo.gfLengthReq;
×
1179
    else
1180
        mainPlayer.sendMsgAsync(new GameMessage_Speed(framesinfo.gfLengthReq.count()));
×
1181
}
×
1182

1183
///////////////////////////////////////////////////////////////////////////////
1184
/// NFC Done vom Server
1185
/// @param message  Nachricht, welche ausgeführt wird
1186
bool GameClient::OnGameMessage(const GameMessage_Server_NWFDone& msg)
×
1187
{
1188
    if(!nwfInfo)
×
1189
        return true;
×
1190

1191
    if(!nwfInfo->addServerInfo(NWFServerInfo(msg.gf, msg.gf_length, msg.nextNWF)))
×
1192
    {
1193
        RTTR_Assert(false);
×
1194
        LOG.write("Failed to add server info. Invalid server?\n");
1195
    }
1196

1197
    return true;
×
1198
}
1199

1200
/**
1201
 *  Pause-Nachricht von Server
1202
 *
1203
 *  @param[in] message Nachricht, welche ausgeführt wird
1204
 */
1205
bool GameClient::OnGameMessage(const GameMessage_Pause& msg)
×
1206
{
1207
    if(state != ClientState::Game)
×
1208
        return true;
×
1209
    if(framesinfo.isPaused == msg.paused)
×
1210
        return true;
×
1211
    framesinfo.isPaused = msg.paused;
×
1212

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

1215
    if(msg.paused)
×
1216
        ci->CI_GamePaused();
×
1217
    else
1218
        ci->CI_GameResumed();
×
1219
    return true;
×
1220
}
1221

1222
/**
1223
 *  NFC GetAsyncLog von Server
1224
 *
1225
 *  @param[in] message Nachricht, welche ausgeführt wird
1226
 */
1227
bool GameClient::OnGameMessage(const GameMessage_GetAsyncLog& /*msg*/)
×
1228
{
1229
    if(state != ClientState::Game)
×
1230
        return true;
×
1231
    std::string systemInfo = System::getCompilerName() + " @ " + System::getOSName();
×
1232
    mainPlayer.sendMsgAsync(new GameMessage_AsyncLog(systemInfo));
×
1233

1234
    // AsyncLog an den Server senden
1235

1236
    std::vector<RandomEntry> async_log = RANDOM.GetAsyncLog();
×
1237

1238
    // stückeln...
1239
    std::vector<RandomEntry> part;
×
1240
    for(auto& it : async_log)
×
1241
    {
1242
        part.push_back(it);
×
1243

1244
        if(part.size() == 10)
×
1245
        {
1246
            mainPlayer.sendMsgAsync(new GameMessage_AsyncLog(part, false));
×
1247
            part.clear();
×
1248
        }
1249
    }
1250

1251
    mainPlayer.sendMsgAsync(new GameMessage_AsyncLog(part, true));
×
1252
    return true;
×
1253
}
×
1254

1255
///////////////////////////////////////////////////////////////////////////////
1256
/// testet ob ein Netwerkframe abgelaufen ist und führt dann ggf die Befehle aus
1257
void GameClient::ExecuteGameFrame()
×
1258
{
1259
    if(framesinfo.isPaused)
×
1260
        return; // Pause
×
1261

1262
    FramesInfo::UsedClock::time_point currentTime = FramesInfo::UsedClock::now();
×
1263

1264
    if(framesinfo.forcePauseLen.count())
×
1265
    {
1266
        if(currentTime - framesinfo.forcePauseStart > framesinfo.forcePauseLen)
×
1267
            framesinfo.forcePauseLen = FramesInfo::milliseconds32_t::zero();
×
1268
        else
1269
            return; // Pause
×
1270
    }
1271

1272
    const unsigned curGF = GetGFNumber();
×
1273
    const bool isSkipping = skiptogf > curGF;
×
1274
    // Is it time for the next GF? If we are skipping, it is always time for the next GF
1275
    if(isSkipping || (currentTime - framesinfo.lastTime) >= framesinfo.gf_length)
×
1276
    {
1277
        try
1278
        {
1279
            if(isSkipping)
×
1280
            {
1281
                // We are always in realtime
1282
                framesinfo.lastTime = currentTime;
×
1283
            } else
1284
            {
1285
                // Advance simulation time (lastTime) by 1 GF
1286
                framesinfo.lastTime += framesinfo.gf_length;
×
1287
            }
1288
            if(replayMode)
×
1289
            {
1290
                // In replay mode we have all commands in the file -> Execute them
1291
                ExecuteGameFrame_Replay();
×
1292
            } else
1293
            {
1294
                RTTR_Assert(curGF <= nwfInfo->getNextNWF());
×
1295
                bool isNWF = (curGF == nwfInfo->getNextNWF());
×
1296
                // Is it time for a NWF, handle that first
1297
                if(isNWF)
×
1298
                {
1299
                    // If a player is lagging (we did not got his commands) "pause" the game by skipping the rest of
1300
                    // this function
1301
                    // -> Don't execute GF, don't autosave etc.
1302
                    if(!nwfInfo->isReady())
×
1303
                    {
1304
                        // If a player is a few GFs behind, he will never catch up and always lag
1305
                        // Hence, pause up to 4 GFs randomly before trying again to execute this NWF
1306
                        // Do not reset frameTime or lastTime as this will mess up interpolation for drawing
1307
                        framesinfo.forcePauseStart = currentTime;
×
1308
                        framesinfo.forcePauseLen = (rand() * 4 * framesinfo.gf_length) / RAND_MAX;
×
1309
                        return;
×
1310
                    }
1311

1312
                    RTTR_Assert(nwfInfo->getServerInfo().gf == curGF);
×
1313

1314
                    ExecuteNWF();
×
1315

1316
                    FramesInfo::milliseconds32_t oldGFLen = framesinfo.gf_length;
×
1317
                    nwfInfo->execute(framesinfo);
×
1318
                    if(oldGFLen != framesinfo.gf_length)
×
1319
                    {
1320
                        LOG.write("Client: Speed changed at %1% from %2% to %3% (NWF: %4%)\n") % curGF
×
1321
                          % helpers::withUnit(oldGFLen) % helpers::withUnit(framesinfo.gf_length)
×
1322
                          % framesinfo.nwf_length;
×
1323
                    }
1324
                }
1325

1326
                NextGF(isNWF);
×
1327
                RTTR_Assert(curGF <= nwfInfo->getNextNWF());
×
1328
                HandleAutosave();
×
1329

1330
                // GF-Ende im Replay aktualisieren
1331
                if(replayinfo && replayinfo->replay.IsRecording())
×
1332
                    replayinfo->replay.UpdateLastGF(curGF);
×
1333
            }
1334

1335
        } catch(LuaExecutionError& e)
×
1336
        {
1337
            SystemChat((boost::format(_("Error during execution of lua script: %1\nGame stopped!")) % e.what()).str());
×
1338
            OnError(ClientError::InvalidMap);
×
1339
        }
×
1340
        if(skiptogf == GetGFNumber())
×
1341
            skiptogf = 0;
×
1342
    }
1343
    framesinfo.frameTime = std::chrono::duration_cast<FramesInfo::milliseconds32_t>(currentTime - framesinfo.lastTime);
×
1344
    // Check remaining time until next GF
1345
    if(framesinfo.frameTime >= framesinfo.gf_length)
×
1346
    {
1347
        // This can happen, if we don't call this method in intervalls less than gf_length or gf_length has changed
1348
        // TODO: Run multiple GFs per call.
1349
        // For now just make sure it is less than gf_length by skipping some simulation time,
1350
        // until we are only a bit less than 1 GF behind
1351
        // However we allow the simulation to lack behind for a few frames, so if there was a single spike we can still
1352
        // catch up in the next visual frames
1353
        using DurationType = decltype(framesinfo.gf_length);
1354
        constexpr auto maxLackFrames = 5;
×
1355

1356
        RTTR_Assert(framesinfo.gf_length > DurationType::zero());
×
1357
        const auto maxFrameTime = framesinfo.gf_length - DurationType(1);
×
1358

1359
        if(framesinfo.frameTime > maxLackFrames * framesinfo.gf_length)
×
1360
            framesinfo.lastTime += framesinfo.frameTime - maxFrameTime; // Skip simulation time until caught up
×
1361
        framesinfo.frameTime = maxFrameTime;
×
1362
    }
1363
    // This is assumed by drawing code for interpolation
1364
    RTTR_Assert(framesinfo.frameTime < framesinfo.gf_length);
×
1365
}
1366

1367
void GameClient::HandleAutosave()
×
1368
{
1369
    // If inactive or during replay -> no autosave
1370
    if(!SETTINGS.interface.autosave_interval || replayMode)
×
1371
        return;
×
1372

1373
    // Alle .... GF
1374
    if(GetGFNumber() % SETTINGS.interface.autosave_interval == 0)
×
1375
    {
1376
        std::string filename;
×
1377
        if(mapinfo.title.empty())
×
1378
            filename = std::string(_("Auto-Save")) + ".sav";
×
1379
        else
1380
            filename = mapinfo.title + " (" + _("Auto-Save") + ").sav";
×
1381

1382
        SaveToFile(RTTRCONFIG.ExpandPath(s25::folders::save) / filename);
×
1383
    }
×
1384
}
1385

1386
/// Führt notwendige Dinge für nächsten GF aus
1387
void GameClient::NextGF(bool wasNWF)
×
1388
{
1389
    for(AIPlayer& ai : game->aiPlayers_)
×
1390
        ai.RunGF(GetGFNumber(), wasNWF);
×
1391
    game->RunGF();
×
1392
}
×
1393

1394
void GameClient::ExecuteAllGCs(uint8_t playerId, const PlayerGameCommands& gcs)
×
1395
{
1396
    for(const gc::GameCommandPtr& gc : gcs.gcs)
×
1397
        gc->Execute(game->world_, playerId);
×
1398
}
×
1399

1400
void GameClient::SendNothingNC(uint8_t player)
×
1401
{
1402
    mainPlayer.sendMsgAsync(
×
1403
      new GameMessage_GameCommand(player, AsyncChecksum::create(*game), std::vector<gc::GameCommandPtr>()));
×
1404
}
×
1405

1406
void GameClient::WritePlayerInfo(SavedFile& file)
×
1407
{
1408
    RTTR_Assert(state == ClientState::Loading || state == ClientState::Loaded || state == ClientState::Game);
×
1409
    // Spielerdaten
1410
    for(unsigned i = 0; i < GetNumPlayers(); ++i)
×
1411
        file.AddPlayer(GetPlayer(i));
×
1412
}
×
1413

1414
void GameClient::OnGameStart()
5✔
1415
{
1416
    if(state == ClientState::Loaded)
5✔
1417
    {
1418
        GAMEMANAGER.ResetAverageGFPS();
×
1419
        framesinfo.lastTime = FramesInfo::UsedClock::now();
×
1420
        state = ClientState::Game;
×
1421
        if(ci)
×
1422
            ci->CI_GameStarted();
×
1423
    } else if(state == ClientState::Game && !game->IsStarted())
5✔
1424
    {
1425
        framesinfo.isPaused = replayMode;
×
1426
        game->Start(!!mapinfo.savegame);
×
1427
    }
1428
}
5✔
1429

1430
void GameClient::StartReplayRecording(const unsigned random_init)
×
1431
{
1432
    replayinfo = std::make_unique<ReplayInfo>();
×
1433
    replayinfo->filename = s25util::Time::FormatTime("%Y-%m-%d_%H-%i-%s") + ".rpl";
×
1434
    replayinfo->replay.random_init = random_init;
×
1435

1436
    WritePlayerInfo(replayinfo->replay);
×
1437
    replayinfo->replay.ggs = game->ggs_;
×
1438

1439
    // Datei speichern
1440
    if(!replayinfo->replay.StartRecording(RTTRCONFIG.ExpandPath(s25::folders::replays) / replayinfo->filename, mapinfo))
×
1441
    {
1442
        LOG.write(_("Replayfile couldn't be opened. No replay will be recorded\n"));
×
1443
        replayinfo.reset();
×
1444
    }
1445
}
×
1446

1447
bool GameClient::StartReplay(const boost::filesystem::path& path)
×
1448
{
1449
    RTTR_Assert(state == ClientState::Stopped);
×
1450
    mapinfo.Clear();
×
1451
    replayinfo = std::make_unique<ReplayInfo>();
×
1452

1453
    if(!replayinfo->replay.LoadHeader(path) || !replayinfo->replay.LoadGameData(mapinfo)) //-V807
×
1454
    {
1455
        LOG.write(_("Invalid Replay %1%! Reason: %2%\n")) % path
×
1456
          % (replayinfo->replay.GetLastErrorMsg().empty() ? _("Unknown") : replayinfo->replay.GetLastErrorMsg());
×
1457
        OnError(ClientError::InvalidMap);
×
1458
        replayinfo.reset();
×
1459
        return false;
×
1460
    }
1461
    replayinfo->filename = path.filename();
×
1462

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

1465
    for(unsigned i = 0; i < replayinfo->replay.GetNumPlayers(); ++i)
×
1466
        gameLobby->getPlayer(i) = JoinPlayerInfo(replayinfo->replay.GetPlayer(i));
×
1467

1468
    bool playerFound = false;
×
1469
    // Find a player to spectate from
1470
    // First find a human player
1471
    for(unsigned char i = 0; i < gameLobby->getNumPlayers(); ++i)
×
1472
    {
1473
        if(gameLobby->getPlayer(i).ps == PlayerState::Occupied)
×
1474
        {
1475
            mainPlayer.playerId = i;
×
1476
            playerFound = true;
×
1477
            break;
×
1478
        }
1479
    }
1480
    if(!playerFound)
×
1481
    {
1482
        // If no human found, take the first AI
1483
        for(unsigned char i = 0; i < gameLobby->getNumPlayers(); ++i)
×
1484
        {
1485
            if(gameLobby->getPlayer(i).ps == PlayerState::AI)
×
1486
            {
1487
                mainPlayer.playerId = i;
×
1488
                break;
×
1489
            }
1490
        }
1491
    }
1492

1493
    // GGS-Daten
1494
    gameLobby->getSettings() = replayinfo->replay.ggs;
×
1495

1496
    switch(mapinfo.type)
×
1497
    {
1498
        default: break;
×
1499
        case MapType::OldMap:
×
1500
        {
1501
            // Richtigen Pfad zur Map erstellen
1502
            bfs::path mapFilePath = RTTRCONFIG.ExpandPath(s25::folders::mapsPlayed) / mapinfo.filepath.filename();
×
1503
            mapinfo.filepath = mapFilePath;
×
1504
            if(!mapinfo.mapData.DecompressToFile(mapinfo.filepath))
×
1505
            {
1506
                LOG.write(_("Error decompressing map file"));
×
1507
                OnError(ClientError::MapTransmission);
×
1508
                return false;
×
1509
            }
1510
            if(mapinfo.luaData.uncompressedLength)
×
1511
            {
1512
                mapinfo.luaFilepath = mapFilePath.replace_extension("lua");
×
1513
                if(!mapinfo.luaData.DecompressToFile(mapinfo.luaFilepath))
×
1514
                {
1515
                    LOG.write(_("Error decompressing lua file"));
×
1516
                    OnError(ClientError::MapTransmission);
×
1517
                    return false;
×
1518
                }
1519
            }
1520
        }
×
1521
        break;
×
1522
        case MapType::Savegame: break;
×
1523
    }
1524

1525
    replayMode = true;
×
1526
    replayinfo->async = 0;
×
1527
    replayinfo->end = false;
×
1528

1529
    try
1530
    {
1531
        StartGame(replayinfo->replay.random_init);
×
1532
    } catch(SerializedGameData::Error& error)
×
1533
    {
1534
        LOG.write(_("Error when loading game from replay: %s\n")) % error.what();
×
1535
        OnError(ClientError::InvalidMap);
×
1536
        return false;
×
1537
    }
×
1538

1539
    replayinfo->replay.ReadGF(&replayinfo->next_gf);
×
1540

1541
    return true;
×
1542
}
1543

1544
void GameClient::SetAIBattlePlayers(std::vector<AI::Info> aiInfos)
×
1545
{
1546
    aiBattlePlayers_ = std::move(aiInfos);
×
1547
}
×
1548

1549
unsigned GameClient::GetGlobalAnimation(const unsigned short max, const unsigned char factor_numerator,
×
1550
                                        const unsigned char factor_denumerator, const unsigned offset)
1551
{
1552
    // Unit for animations is 630ms (dividable by 2, 3, 5, 6, 7, 10, 15, ...)
1553
    // But this also means: If framerate drops below approx. 15Hz, you won't see
1554
    // every frame of an 8-part animation anymore.
1555
    // An animation runs fully in (factor_numerator / factor_denumerator) multiples of 630ms
1556
    const unsigned unit = 630 /*ms*/ * factor_numerator / factor_denumerator;
×
1557
    const unsigned currenttime = std::chrono::duration_cast<FramesInfo::milliseconds32_t>(
×
1558
                                   (framesinfo.lastTime + framesinfo.frameTime).time_since_epoch())
×
1559
                                   .count();
×
1560
    return ((currenttime % unit) * max / unit + offset) % max;
×
1561
}
1562

1563
unsigned GameClient::Interpolate(unsigned max_val, const GameEvent* ev)
×
1564
{
1565
    RTTR_Assert(ev);
×
1566
    // TODO: Move to some animation system that is part of game
1567
    FramesInfo::milliseconds32_t elapsedTime;
1568
    if(state == ClientState::Game)
×
1569
        elapsedTime = (GetGFNumber() - ev->startGF) * framesinfo.gf_length + framesinfo.frameTime;
×
1570
    else
1571
        elapsedTime = FramesInfo::milliseconds32_t::zero();
×
1572
    FramesInfo::milliseconds32_t duration = ev->length * framesinfo.gf_length;
×
1573
    return helpers::interpolate(0u, max_val, elapsedTime, duration);
×
1574
}
1575

1576
int GameClient::Interpolate(int x1, int x2, const GameEvent* ev)
×
1577
{
1578
    RTTR_Assert(ev);
×
1579
    FramesInfo::milliseconds32_t elapsedTime;
1580
    if(state == ClientState::Game)
×
1581
        elapsedTime = (GetGFNumber() - ev->startGF) * framesinfo.gf_length + framesinfo.frameTime;
×
1582
    else
1583
        elapsedTime = FramesInfo::milliseconds32_t::zero();
×
1584
    FramesInfo::milliseconds32_t duration = ev->length * framesinfo.gf_length;
×
1585
    return helpers::interpolate(x1, x2, elapsedTime, duration);
×
1586
}
1587

1588
void GameClient::ServerLost()
×
1589
{
1590
    OnError(ClientError::ConnectionLost);
×
1591
    // Stop game
1592
    framesinfo.isPaused = true;
×
1593
}
×
1594

1595
/**
1596
 *  überspringt eine bestimmte Anzahl von Gameframes.
1597
 *
1598
 *  @param[in] dest_gf Zielgameframe
1599
 */
1600
void GameClient::SkipGF(unsigned gf, GameWorldView& gwv)
×
1601
{
1602
    if(gf <= GetGFNumber())
×
1603
        return;
×
1604

1605
    unsigned start_ticks = VIDEODRIVER.GetTickCount();
×
1606

1607
    if(!replayMode)
×
1608
    {
1609
        // unpause before skipping
1610
        SetPause(false);
×
1611
        mainPlayer.sendMsgAsync(new GameMessage_SkipToGF(gf));
×
1612
        return;
×
1613
    }
1614

1615
    SetPause(false);
×
1616
    skiptogf = gf;
×
1617

1618
    // GFs überspringen
1619
    for(unsigned i = GetGFNumber(); i < skiptogf; ++i)
×
1620
    {
1621
        if(i % 1000 == 0)
×
1622
        {
1623
            RoadBuildState road;
×
1624
            road.mode = RoadBuildMode::Disabled;
×
1625

1626
            // spiel aktualisieren
1627
            gwv.Draw(road, MapPoint::Invalid(), false);
×
1628

1629
            // text oben noch hinschreiben
1630
            boost::format nwfString(_("current GF: %u - still fast forwarding: %d GFs left (%d %%)"));
×
1631
            nwfString % GetGFNumber() % (gf - i) % (i * 100 / gf);
×
1632
            LargeFont->Draw(DrawPoint(VIDEODRIVER.GetRenderSize() / 2u), nwfString.str(), FontStyle::CENTER,
×
1633
                            COLOR_YELLOW);
1634

1635
            VIDEODRIVER.SwapBuffers();
×
1636
        }
×
1637
        ExecuteGameFrame();
×
1638
    }
1639

1640
    // Spiel pausieren & text ausgabe wie lang das jetzt gedauert hat
1641
    unsigned ticks = VIDEODRIVER.GetTickCount() - start_ticks;
×
1642
    boost::format text(_("Jump finished (%1$.3g seconds)."));
×
1643
    text % (ticks / 1000.0);
×
1644
    SystemChat(text.str());
×
1645
    SetPause(true);
×
1646
}
×
1647

1648
void GameClient::SystemChat(const std::string& text)
×
1649
{
1650
    SystemChat(text, GetPlayerId());
×
1651
}
×
1652

1653
void GameClient::SystemChat(const std::string& text, unsigned char fromPlayerIdx)
×
1654
{
1655
    if(ci)
×
1656
        ci->CI_Chat(fromPlayerIdx, ChatDestination::System, text);
×
1657
}
×
1658

1659
bool GameClient::SaveToFile(const boost::filesystem::path& filepath)
×
1660
{
1661
    mainPlayer.sendMsg(GameMessage_Chat(GetPlayerId(), ChatDestination::System, "Saving game..."));
×
1662

1663
    // Mond malen
1664
    Position moonPos = VIDEODRIVER.GetMousePos();
×
1665
    moonPos.y -= 40;
×
1666
    LOADER.GetImageN("resource", 33)->DrawFull(moonPos);
×
1667
    VIDEODRIVER.SwapBuffers();
×
1668

1669
    Savegame save;
×
1670

1671
    WritePlayerInfo(save);
×
1672

1673
    // GGS-Daten
1674
    save.ggs = game->ggs_;
×
1675

1676
    save.start_gf = GetGFNumber();
×
1677

1678
    // Enable/Disable debugging of savegames
1679
    save.sgd.debugMode = SETTINGS.global.debugMode;
×
1680

1681
    try
1682
    {
1683
        // Spiel serialisieren
1684
        save.sgd.MakeSnapshot(*game);
×
1685
        // Und alles speichern
1686
        return save.Save(filepath, mapinfo.title);
×
1687
    } catch(std::exception& e)
×
1688
    {
1689
        SystemChat(std::string("Error during saving: ") + e.what());
×
1690
        return false;
×
1691
    }
×
1692
}
×
1693

1694
void GameClient::ResetVisualSettings()
×
1695
{
1696
    GetPlayer(GetPlayerId()).FillVisualSettings(visual_settings);
×
1697
}
×
1698

1699
void GameClient::SetPause(bool pause)
×
1700
{
1701
    if(state == ClientState::Stopped)
×
1702
    {
1703
        // We can never continue from pause if stopped as the reason for stopping might be that the game was finished
1704
        // However we allow to pause even when stopped so we can pause after we received the stop notification
1705
        if(!pause)
×
1706
            return;
×
1707
        framesinfo.isPaused = true;
×
1708
        framesinfo.frameTime = FramesInfo::milliseconds32_t::zero();
×
1709
    } else if(replayMode)
×
1710
    {
1711
        framesinfo.isPaused = pause;
×
1712
        framesinfo.frameTime = FramesInfo::milliseconds32_t::zero();
×
1713
    } else if(IsHost())
×
1714
    {
1715
        // Pause instantly
1716
        auto* msg = new GameMessage_Pause(pause);
×
1717
        if(pause)
×
1718
            OnGameMessage(*msg);
×
1719
        mainPlayer.sendMsgAsync(msg);
×
1720
    }
1721
}
1722

1723
void GameClient::ToggleReplayFOW()
×
1724
{
1725
    if(replayinfo)
×
1726
        replayinfo->all_visible = !replayinfo->all_visible;
×
1727
}
×
1728

1729
bool GameClient::IsReplayFOWDisabled() const
×
1730
{
1731
    return replayMode && replayinfo->all_visible;
×
1732
}
1733

1734
unsigned GameClient::GetLastReplayGF() const
×
1735
{
1736
    return replayinfo ? replayinfo->replay.GetLastGF() : 0u;
×
1737
}
1738

1739
bool GameClient::AddGC(gc::GameCommandPtr gc)
×
1740
{
1741
    // Nicht in der Pause oder wenn er besiegt wurde
1742
    if(framesinfo.isPaused || GetPlayer(GetPlayerId()).IsDefeated() || IsReplayModeOn())
×
1743
        return false;
×
1744

1745
    gameCommands_.push_back(gc);
×
1746
    return true;
×
1747
}
1748

1749
unsigned GameClient::GetNumPlayers() const
×
1750
{
1751
    RTTR_Assert(state == ClientState::Loading || state == ClientState::Loaded || state == ClientState::Game);
×
1752
    return game->world_.GetNumPlayers();
×
1753
}
1754

1755
GamePlayer& GameClient::GetPlayer(const unsigned id)
×
1756
{
1757
    RTTR_Assert(state == ClientState::Loading || state == ClientState::Loaded || state == ClientState::Game);
×
1758
    RTTR_Assert(id < GetNumPlayers());
×
1759
    return game->world_.GetPlayer(id);
×
1760
}
1761

1762
std::unique_ptr<AIPlayer> GameClient::CreateAIPlayer(unsigned playerId, const AI::Info& aiInfo)
×
1763
{
1764
    return AIFactory::Create(aiInfo, playerId, game->world_);
×
1765
}
1766

1767
/// Wandelt eine GF-Angabe in eine Zeitangabe um (HH:MM:SS oder MM:SS wenn Stunden = 0)
1768
std::string GameClient::FormatGFTime(const unsigned gf) const
3✔
1769
{
1770
    using seconds = std::chrono::duration<uint32_t, std::chrono::seconds::period>;
1771
    using hours = std::chrono::duration<uint32_t, std::chrono::hours::period>;
1772
    using minutes = std::chrono::duration<uint32_t, std::chrono::minutes::period>;
1773
    using std::chrono::duration_cast;
1774

1775
    // In Sekunden umrechnen
1776
    seconds numSeconds = duration_cast<seconds>(gf * SPEED_GF_LENGTHS[referenceSpeed]);
3✔
1777

1778
    // Angaben rausfiltern
1779
    hours numHours = duration_cast<hours>(numSeconds);
3✔
1780
    numSeconds -= numHours;
3✔
1781
    minutes numMinutes = duration_cast<minutes>(numSeconds);
3✔
1782
    numSeconds -= numMinutes;
3✔
1783

1784
    // ganze Stunden mit dabei? Dann entsprechend anderes format, ansonsten ignorieren wir die einfach
1785
    if(numHours.count())
3✔
1786
        return helpers::format("%u:%02u:%02u", numHours.count(), numMinutes.count(), numSeconds.count());
×
1787
    else
1788
        return helpers::format("%02u:%02u", numMinutes.count(), numSeconds.count());
6✔
1789
}
1790

1791
const boost::filesystem::path& GameClient::GetReplayFilename() const
×
1792
{
1793
    static boost::filesystem::path emptyString;
×
1794
    return replayinfo ? replayinfo->filename : emptyString;
×
1795
}
1796

1797
Replay* GameClient::GetReplay()
×
1798
{
1799
    return replayinfo ? &replayinfo->replay : nullptr;
×
1800
}
1801

1802
std::shared_ptr<const NWFInfo> GameClient::GetNWFInfo() const
×
1803
{
1804
    return nwfInfo;
×
1805
}
1806

1807
/// Is tournament mode activated (0 if not)? Returns the durations of the tournament mode in gf otherwise
1808
unsigned GameClient::GetTournamentModeDuration() const
×
1809
{
1810
    using namespace std::chrono;
1811
    if(game && rttr::enum_cast(game->ggs_.objective) >= rttr::enum_cast(GameObjective::Tournament1)
×
1812
       && static_cast<unsigned>(rttr::enum_cast(game->ggs_.objective))
×
1813
            < rttr::enum_cast(GameObjective::Tournament1) + NUM_TOURNAMENT_MODES)
×
1814
    {
1815
        const auto turnamentMode = rttr::enum_cast(game->ggs_.objective) - rttr::enum_cast(GameObjective::Tournament1);
×
1816
        return minutes(TOURNAMENT_MODES_DURATION[turnamentMode]) / SPEED_GF_LENGTHS[referenceSpeed];
×
1817
    } else
1818
        return 0;
×
1819
}
1820

1821
void GameClient::ToggleHumanAIPlayer(const AI::Info& aiInfo)
×
1822
{
1823
    RTTR_Assert(!IsReplayModeOn());
×
1824
    auto it = helpers::find_if(game->aiPlayers_,
×
1825
                               [id = this->GetPlayerId()](const auto& player) { return player.GetPlayerId() == id; });
×
1826
    if(it != game->aiPlayers_.end())
×
1827
        game->aiPlayers_.erase(it);
×
1828
    else
1829
        game->AddAIPlayer(CreateAIPlayer(GetPlayerId(), aiInfo));
×
1830
}
×
1831

1832
void GameClient::RequestSwapToPlayer(const unsigned char newId)
×
1833
{
1834
    if(state != ClientState::Game)
×
1835
        return;
×
1836
    GamePlayer& player = GetPlayer(newId);
×
1837
    if(player.ps == PlayerState::AI && player.aiInfo.type == AI::Type::Dummy)
×
1838
        mainPlayer.sendMsgAsync(new GameMessage_Player_Swap(0xFF, newId));
×
1839
}
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