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

Return-To-The-Roots / s25client / 12323492039

13 Dec 2024 09:44PM UTC coverage: 50.144% (-0.02%) from 50.166%
12323492039

Pull #1683

github

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

9 of 40 new or added lines in 8 files covered. (22.5%)

4 existing lines in 1 file now uncovered.

22270 of 44412 relevant lines covered (50.14%)

34410.75 hits per line

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

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

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

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

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

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

105
    RTTR_Assert(aiBattlePlayers_.empty());
3✔
106

107
    // Name und Password kopieren
108
    clientconfig.server = server;
3✔
109
    clientconfig.password = password;
3✔
110

111
    clientconfig.servertyp = servertyp;
3✔
112
    clientconfig.port = port;
3✔
113
    clientconfig.isHost = host;
3✔
114

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

124
    state = ClientState::Connect;
3✔
125
    AdvanceState(ConnectState::Initiated);
3✔
126

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

130
    return true;
3✔
131
}
132

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

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

155
    SocketSet set;
×
156

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

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

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

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

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

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

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

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

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

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

216
    if(IsHost())
3✔
217
        GAMESERVER.Stop();
×
218

219
    framesinfo.Clear();
3✔
220
    clientconfig.Clear();
3✔
221
    mapinfo.Clear();
3✔
222

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

231
    mainPlayer.closeConnection();
3✔
232

233
    // clear jump target
234
    skiptogf = 0;
3✔
235

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

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

243
    aiBattlePlayers_.clear();
3✔
244
}
245

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

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

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

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

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

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

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

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

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

307
    state = ClientState::Loading;
×
308

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

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

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

325
        MapLoader loader(gameWorld);
×
NEW
326
        if((!mapinfo.luaFilepath.empty() && !loader.LoadLuaScript(*game, *this, mapinfo.luaFilepath))
×
NEW
327
           || !loader.Load(mapinfo.filepath)) // Do not reorder: load lua first, load map second.
×
328
                                              // If the map is loaded first and it does not have a player HQ set, it
329
                                              // will not load correctly, even though the HQ may be set using LUA.
330
        {
331
            OnError(ClientError::InvalidMap);
×
332
            return;
×
333
        }
334
        gameWorld.SetupResources();
×
335
    }
336
    gameWorld.InitAfterLoad();
×
337

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

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

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

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

355
    state = ClientState::Loaded;
×
356

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

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

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

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

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

416
    mainPlayer.playerId = msg.player;
3✔
417

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

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

439
    for(unsigned i = 0; i < gameLobby->getNumPlayers(); ++i)
8✔
440
        gameLobby->getPlayer(i) = msg.playerInfos[i];
6✔
441

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

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

459
///////////////////////////////////////////////////////////////////////////////
460
/// player joined
461
/// @param message  Nachricht, welche ausgeführt wird
462
bool GameClient::OnGameMessage(const GameMessage_Player_New& msg)
×
463
{
464
    if(state != ClientState::Config)
×
465
        return true;
×
466

467
    if(msg.player >= gameLobby->getNumPlayers())
×
468
        return true;
×
469

470
    JoinPlayerInfo& playerInfo = gameLobby->getPlayer(msg.player);
×
471

472
    playerInfo.name = msg.name;
×
473
    playerInfo.ps = PlayerState::Occupied;
×
474
    playerInfo.ping = 0;
×
475

476
    if(ci)
×
477
        ci->CI_NewPlayer(msg.player);
×
478
    return true;
×
479
}
480

481
bool GameClient::OnGameMessage(const GameMessage_Player_Ping& msg)
×
482
{
483
    if(state == ClientState::Config)
×
484
    {
485
        if(msg.player >= gameLobby->getNumPlayers())
×
486
            return true;
×
487
        gameLobby->getPlayer(msg.player).ping = msg.ping;
×
488
    } else if(state == ClientState::Loading || state == ClientState::Loaded || state == ClientState::Game)
×
489
    {
490
        if(msg.player >= GetNumPlayers())
×
491
            return true;
×
492
        GetPlayer(msg.player).ping = msg.ping;
×
493
    } else
494
    {
495
        RTTR_Assert(false);
×
496
        return true;
497
    }
498

499
    if(ci)
×
500
        ci->CI_PingChanged(msg.player, msg.ping);
×
501
    return true;
×
502
}
503

504
/**
505
 *  Player-Toggle-State-Nachricht.
506
 */
507
bool GameClient::OnGameMessage(const GameMessage_Player_State& msg)
×
508
{
509
    if(state != ClientState::Config)
×
510
        return true;
×
511

512
    if(msg.player >= gameLobby->getNumPlayers())
×
513
        return true;
×
514

515
    JoinPlayerInfo& playerInfo = gameLobby->getPlayer(msg.player);
×
516
    bool wasUsed = playerInfo.isUsed();
×
517
    playerInfo.ps = msg.ps;
×
518
    playerInfo.aiInfo = msg.aiInfo;
×
519

520
    if(ci)
×
521
    {
522
        if(playerInfo.isUsed())
×
523
            ci->CI_NewPlayer(msg.player);
×
524
        else if(wasUsed)
×
525
            ci->CI_PlayerLeft(msg.player);
×
526
        else
527
            ci->CI_PlayerDataChanged(msg.player);
×
528
    }
529
    return true;
×
530
}
531

532
///////////////////////////////////////////////////////////////////////////////
533
/// nation button gedrückt
534
/// @param message  Nachricht, welche ausgeführt wird
535
bool GameClient::OnGameMessage(const GameMessage_Player_Nation& msg)
×
536
{
537
    if(state != ClientState::Config)
×
538
        return true;
×
539

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

543
    gameLobby->getPlayer(msg.player).nation = msg.nation;
×
544

545
    if(ci)
×
546
        ci->CI_PlayerDataChanged(msg.player);
×
547
    return true;
×
548
}
549

550
///////////////////////////////////////////////////////////////////////////////
551
/// team button gedrückt
552
/// @param message  Nachricht, welche ausgeführt wird
553
bool GameClient::OnGameMessage(const GameMessage_Player_Team& msg)
×
554
{
555
    if(state != ClientState::Config)
×
556
        return true;
×
557

558
    if(msg.player >= gameLobby->getNumPlayers())
×
559
        return true;
×
560

561
    gameLobby->getPlayer(msg.player).team = msg.team;
×
562

563
    if(ci)
×
564
        ci->CI_PlayerDataChanged(msg.player);
×
565
    return true;
×
566
}
567

568
///////////////////////////////////////////////////////////////////////////////
569
/// color button gedrückt
570
/// @param message  Nachricht, welche ausgeführt wird
571
bool GameClient::OnGameMessage(const GameMessage_Player_Color& msg)
×
572
{
573
    if(state != ClientState::Config)
×
574
        return true;
×
575

576
    if(msg.player >= gameLobby->getNumPlayers())
×
577
        return true;
×
578

579
    gameLobby->getPlayer(msg.player).color = msg.color;
×
580

581
    if(ci)
×
582
        ci->CI_PlayerDataChanged(msg.player);
×
583
    return true;
×
584
}
585

586
/**
587
 *  Ready-state eines Spielers hat sich geändert.
588
 *
589
 *  @param[in] message Nachricht, welche ausgeführt wird
590
 */
591
bool GameClient::OnGameMessage(const GameMessage_Player_Ready& msg)
×
592
{
593
    if(state != ClientState::Config)
×
594
        return true;
×
595

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

599
    gameLobby->getPlayer(msg.player).isReady = msg.ready;
×
600

601
    if(ci)
×
602
        ci->CI_ReadyChanged(msg.player, msg.ready);
×
603
    return true;
×
604
}
605

606
///////////////////////////////////////////////////////////////////////////////
607
/// player gekickt
608
/// @param message  Nachricht, welche ausgeführt wird
609
bool GameClient::OnGameMessage(const GameMessage_Player_Kicked& msg)
×
610
{
611
    if(state == ClientState::Config)
×
612
    {
613
        if(msg.player >= gameLobby->getNumPlayers())
×
614
            return true;
×
615
        gameLobby->getPlayer(msg.player).ps = PlayerState::Free;
×
616
    } else if(state == ClientState::Loading || state == ClientState::Loaded || state == ClientState::Game)
×
617
    {
618
        // Im Spiel anzeigen, dass der Spieler das Spiel verlassen hat
619
        GamePlayer& player = GetPlayer(msg.player);
×
620
        if(player.ps != PlayerState::AI)
×
621
        {
622
            player.ps = PlayerState::AI;
×
623
            player.aiInfo = AI::Info(AI::Type::Dummy);
×
624
            // Host has to handle it
625
            if(IsHost())
×
626
            {
627
                game->AddAIPlayer(CreateAIPlayer(msg.player, player.aiInfo));
×
628
                SendNothingNC(msg.player);
×
629
            }
630
        }
×
631
    } else
632
        return true;
×
633

634
    if(ci)
×
635
        ci->CI_PlayerLeft(msg.player);
×
636
    return true;
×
637
}
638

639
bool GameClient::OnGameMessage(const GameMessage_Player_Swap& msg)
×
640
{
641
    LOG.writeToFile("<<< NMS_PLAYER_SWAP(%u, %u)\n") % unsigned(msg.player) % unsigned(msg.player2);
×
642

643
    if(state == ClientState::Config)
×
644
    {
645
        if(msg.player >= gameLobby->getNumPlayers() || msg.player2 >= gameLobby->getNumPlayers())
×
646
            return true;
×
647

648
        // During preparation just swap the players
649
        using std::swap;
650
        swap(gameLobby->getPlayer(msg.player), gameLobby->getPlayer(msg.player2));
×
651
        // Some things cannot be changed in savegames
652
        if(mapinfo.type == MapType::Savegame)
×
653
            gameLobby->getPlayer(msg.player).FixSwappedSaveSlot(gameLobby->getPlayer(msg.player2));
×
654

655
        // Evtl. sind wir betroffen?
656
        if(mainPlayer.playerId == msg.player)
×
657
            mainPlayer.playerId = msg.player2;
×
658
        else if(mainPlayer.playerId == msg.player2)
×
659
            mainPlayer.playerId = msg.player;
×
660

661
        if(ci)
×
662
            ci->CI_PlayersSwapped(msg.player, msg.player2);
×
663
    } else if(state == ClientState::Loading || state == ClientState::Loaded || state == ClientState::Game)
×
664
        ChangePlayerIngame(msg.player, msg.player2);
×
665
    else
666
        return true;
×
667
    mainPlayer.sendMsgAsync(new GameMessage_Player_SwapConfirm(msg.player, msg.player2));
×
668
    return true;
×
669
}
670

671
/**
672
 *  Server-Typ-Nachricht.
673
 */
674
bool GameClient::OnGameMessage(const GameMessage_Server_TypeOK& msg)
3✔
675
{
676
    if(!VerifyState(ConnectState::VerifyServer))
3✔
677
        return true;
×
678

679
    using StatusCode = GameMessage_Server_TypeOK::StatusCode;
680
    switch(msg.err_code)
3✔
681
    {
682
        case StatusCode::Ok: break;
3✔
683

684
        default:
×
685
        case StatusCode::InvalidServerType:
686
        {
687
            OnError(ClientError::InvalidServerType);
×
688
            return true;
×
689
        }
690
        break;
691

692
        case StatusCode::WrongVersion:
×
693
        {
694
            LOG.write(_("Version mismatch. Server version: %1%, your version %2%")) % msg.version
×
695
              % rttr::version::GetRevision();
×
696
            OnError(ClientError::WrongVersion);
×
697
            return true;
×
698
        }
699
        break;
700
    }
701

702
    mainPlayer.sendMsgAsync(new GameMessage_Server_Password(clientconfig.password));
3✔
703

704
    AdvanceState(ConnectState::QueryPw);
3✔
705
    return true;
3✔
706
}
707

708
/**
709
 *  Server-Passwort-Nachricht.
710
 */
711
bool GameClient::OnGameMessage(const GameMessage_Server_Password& msg)
3✔
712
{
713
    if(!VerifyState(ConnectState::QueryPw))
3✔
714
        return true;
×
715

716
    if(msg.password != "true")
3✔
717
    {
718
        OnError(ClientError::WrongPassword);
×
719
        return true;
×
720
    }
721

722
    mainPlayer.sendMsgAsync(new GameMessage_Player_Name(0xFF, SETTINGS.lobby.name));
3✔
723
    mainPlayer.sendMsgAsync(new GameMessage_MapRequest(true));
3✔
724

725
    AdvanceState(ConnectState::QueryMapInfo);
3✔
726
    return true;
3✔
727
}
728

729
/**
730
 *  Server-Name-Nachricht.
731
 */
732
bool GameClient::OnGameMessage(const GameMessage_Server_Name& msg)
2✔
733
{
734
    if(!VerifyState(ConnectState::QueryServerName))
2✔
735
        return true;
×
736
    clientconfig.gameName = msg.name;
2✔
737

738
    AdvanceState(ConnectState::QueryPlayerList);
2✔
739
    return true;
2✔
740
}
741

742
/**
743
 *  Server-Start-Nachricht
744
 */
745
bool GameClient::OnGameMessage(const GameMessage_Server_Start& msg)
×
746
{
747
    if(state != ClientState::Config)
×
748
        return true;
×
749

750
    nwfInfo = std::make_shared<NWFInfo>();
×
751
    nwfInfo->init(msg.firstNwf, msg.cmdDelay);
×
752
    try
753
    {
754
        StartGame(msg.random_init);
×
755
    } catch(SerializedGameData::Error& error)
×
756
    {
757
        LOG.write("Error when loading game: %s\n") % error.what();
×
758
        Stop();
×
759
        GAMEMANAGER.ShowMenu();
×
760
    }
761
    return true;
×
762
}
763

764
/**
765
 *  Server-Chat-Nachricht.
766
 */
767
bool GameClient::OnGameMessage(const GameMessage_Chat& msg)
×
768
{
769
    if(msg.destination == ChatDestination::System)
×
770
    {
771
        SystemChat(msg.text, (msg.player < game->world_.GetNumPlayers()) ? msg.player : GetPlayerId());
×
772
        return true;
×
773
    }
774
    if(state == ClientState::Game)
×
775
    {
776
        // Ingame message: Do some checking and logging
777
        if(msg.player >= game->world_.GetNumPlayers())
×
778
            return true;
×
779

780
        /// Mit im Replay aufzeichnen
781
        if(replayinfo && replayinfo->replay.IsRecording())
×
782
            replayinfo->replay.AddChatCommand(GetGFNumber(), msg.player, msg.destination, msg.text);
×
783

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

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

790
        const auto isValidRecipient = [&msg, &player](const unsigned playerId) {
×
791
            // Always send to self
792
            if(msg.player == playerId)
×
793
                return true;
×
794
            switch(msg.destination)
×
795
            {
796
                case ChatDestination::System:
×
797
                case ChatDestination::All: return true;
×
798
                case ChatDestination::Allies: return player.IsAlly(playerId);
×
799
                case ChatDestination::Enemies: return !player.IsAlly(playerId);
×
800
            }
801
            return true; // LCOV_EXCL_LINE
802
        };
×
803
        for(AIPlayer& ai : game->aiPlayers_)
×
804
        {
805
            if(isValidRecipient(ai.GetPlayerId()))
×
806
                ai.OnChatMessage(msg.player, msg.destination, msg.text);
×
807
        }
808

809
        if(!isValidRecipient(GetPlayerId()))
×
810
            return true;
×
811
    } else if(state == ClientState::Config)
×
812
    {
813
        // GameLobby message: Just check for valid player
814
        if(msg.player >= gameLobby->getNumPlayers())
×
815
            return true;
×
816
    } else
817
        return true;
×
818

819
    if(ci)
×
820
        ci->CI_Chat(msg.player, msg.destination, msg.text);
×
821
    return true;
×
822
}
823

824
/**
825
 *  Server-Async-Nachricht.
826
 */
827
bool GameClient::OnGameMessage(const GameMessage_Server_Async& msg)
×
828
{
829
    if(state != ClientState::Game)
×
830
        return true;
×
831

832
    // Liste mit Namen und Checksummen erzeugen
833
    std::stringstream checksum_list;
×
834
    for(unsigned i = 0; i < msg.checksums.size(); ++i)
×
835
    {
836
        checksum_list << GetPlayer(i).name << ": " << msg.checksums[i];
×
837
        if(i + 1 < msg.checksums.size())
×
838
            checksum_list << ", ";
×
839
    }
840

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

845
    // Messenger im Game
846
    if(ci)
×
847
        ci->CI_Async(checksum_list.str());
×
848

849
    std::string fileName = s25util::Time::FormatTime("async_%Y-%m-%d_%H-%i-%s");
×
850
    fileName += "_" + s25util::toStringClassic(GetPlayerId()) + "_";
×
851
    fileName += GetPlayer(GetPlayerId()).name;
×
852

853
    const bfs::path filePathSave = RTTRCONFIG.ExpandPath(s25::folders::save) / makePortableFileName(fileName + ".sav");
×
854
    const bfs::path filePathLog =
855
      RTTRCONFIG.ExpandPath(s25::folders::logs) / makePortableFileName(fileName + "Player.log");
×
856
    saveRandomLog(filePathLog, RANDOM.GetAsyncLog());
×
857
    SaveToFile(filePathSave);
×
858
    LOG.write(_("Async log saved at %1%,\ngame saved at %2%\n")) % filePathLog % filePathSave;
×
859
    return true;
×
860
}
861

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

874
/**
875
 *  Server-Cancel-Countdown-Nachricht.
876
 */
877
bool GameClient::OnGameMessage(const GameMessage_CancelCountdown& msg)
×
878
{
879
    if(state != ClientState::Config)
×
880
        return true;
×
881
    if(ci)
×
882
        ci->CI_CancelCountdown(msg.error);
×
883
    return true;
×
884
}
885

886
/**
887
 *  verarbeitet die MapInfo-Nachricht, in der die gepackte Größe,
888
 *  die normale Größe und Teilanzahl der Karte übertragen wird.
889
 *
890
 *  @param message Nachricht, welche ausgeführt wird
891
 */
892
bool GameClient::OnGameMessage(const GameMessage_Map_Info& msg)
3✔
893
{
894
    if(!VerifyState(ConnectState::QueryMapInfo))
3✔
895
        return true;
×
896

897
    // full path
898
    const std::string portFilename = makePortableFileName(msg.filename);
6✔
899
    if(portFilename.empty())
3✔
900
    {
901
        LOG.write("Invalid filename received!\n");
×
902
        OnError(ClientError::InvalidMap);
×
903
        return true;
×
904
    }
905
    if(!MapInfo::verifySize(msg.mapLen, msg.luaLen, msg.mapCompressedLen, msg.luaCompressedLen))
3✔
906
    {
907
        OnError(ClientError::InvalidMap);
×
908
        return true;
×
909
    }
910
    mapinfo.filepath = RTTRCONFIG.ExpandPath(s25::folders::mapsPlayed) / portFilename;
6✔
911
    mapinfo.type = msg.mt;
3✔
912

913
    // lua script file path
914
    if(msg.luaLen > 0)
3✔
915
        mapinfo.luaFilepath = bfs::path(mapinfo.filepath).replace_extension("lua");
2✔
916
    else
917
        mapinfo.luaFilepath.clear();
2✔
918

919
    // We have the map locally already, so prepare and ask if this is the same as the one on the server
920
    if(bfs::exists(mapinfo.filepath) && (mapinfo.luaFilepath.empty() || bfs::exists(mapinfo.luaFilepath))
3✔
921
       && CreateLobby())
3✔
922
    {
923
        mapinfo.mapData.CompressFromFile(mapinfo.filepath, &mapinfo.mapChecksum);
×
924
        if(mapinfo.mapData.data.size() == msg.mapCompressedLen && mapinfo.mapData.uncompressedLength == msg.mapLen)
×
925
        {
926
            bool ok = true;
×
927
            if(!mapinfo.luaFilepath.empty())
×
928
            {
929
                mapinfo.luaData.CompressFromFile(mapinfo.luaFilepath, &mapinfo.luaChecksum);
×
930
                ok = (mapinfo.luaData.data.size() == msg.luaCompressedLen
×
931
                      && mapinfo.luaData.uncompressedLength == msg.luaLen);
×
932
            }
933

934
            if(ok)
×
935
            {
936
                mainPlayer.sendMsgAsync(new GameMessage_Map_Checksum(mapinfo.mapChecksum, mapinfo.luaChecksum));
×
937
                AdvanceState(ConnectState::VerifyMap);
×
938
                return true;
×
939
            }
940
        }
941
        gameLobby.reset();
×
942
    }
943
    mapinfo.mapData.uncompressedLength = msg.mapLen;
3✔
944
    mapinfo.luaData.uncompressedLength = msg.luaLen;
3✔
945
    mapinfo.mapData.data.resize(msg.mapCompressedLen);
3✔
946
    mapinfo.luaData.data.resize(msg.luaCompressedLen);
3✔
947
    mainPlayer.sendMsgAsync(new GameMessage_MapRequest(false));
3✔
948
    AdvanceState(ConnectState::ReceiveMap);
3✔
949
    return true;
3✔
950
}
951

952
///////////////////////////////////////////////////////////////////////////////
953
/// Kartendaten
954
/// @param message  Nachricht, welche ausgeführt wird
955
bool GameClient::OnGameMessage(const GameMessage_Map_Data& msg)
8✔
956
{
957
    if(!VerifyState(ConnectState::ReceiveMap))
8✔
958
        return true;
×
959

960
    LOG.writeToFile("<<< NMS_MAP_DATA(%u)\n") % msg.data.size();
8✔
961
    std::vector<char>& targetData = (msg.isMapData) ? mapinfo.mapData.data : mapinfo.luaData.data;
8✔
962
    if(msg.data.size() > targetData.size() || msg.offset > targetData.size() - msg.data.size())
8✔
963
    {
964
        OnError(ClientError::MapTransmission);
1✔
965
        return true;
1✔
966
    }
967
    std::copy(msg.data.begin(), msg.data.end(), targetData.begin() + msg.offset);
7✔
968

969
    uint32_t totalSize = mapinfo.mapData.data.size();
7✔
970
    uint32_t receivedSize = msg.offset + msg.data.size();
7✔
971
    if(!mapinfo.luaFilepath.empty())
7✔
972
    {
973
        totalSize += mapinfo.luaData.data.size();
4✔
974
        // Assumes lua data comes after the map data
975
        if(!msg.isMapData)
4✔
976
            receivedSize += mapinfo.mapData.data.size();
2✔
977
    }
978
    if(ci)
7✔
979
        ci->CI_MapPartReceived(receivedSize, totalSize);
7✔
980

981
    if(receivedSize == totalSize)
7✔
982
    {
983
        if(!mapinfo.mapData.DecompressToFile(mapinfo.filepath, &mapinfo.mapChecksum))
2✔
984
        {
985
            OnError(ClientError::MapTransmission);
×
986
            return true;
×
987
        }
988
        if(!mapinfo.luaFilepath.empty() && !mapinfo.luaData.DecompressToFile(mapinfo.luaFilepath, &mapinfo.luaChecksum))
2✔
989
        {
990
            OnError(ClientError::MapTransmission);
×
991
            return true;
×
992
        }
993
        RTTR_Assert(!mapinfo.luaFilepath.empty() || mapinfo.luaChecksum == 0);
2✔
994

995
        if(!CreateLobby())
2✔
996
        {
997
            OnError(ClientError::MapTransmission);
×
998
            return true;
×
999
        }
1000

1001
        mainPlayer.sendMsgAsync(new GameMessage_Map_Checksum(mapinfo.mapChecksum, mapinfo.luaChecksum));
2✔
1002
        AdvanceState(ConnectState::VerifyMap);
2✔
1003
    }
1004
    return true;
7✔
1005
}
1006

1007
bool GameClient::OnGameMessage(const GameMessage_SkipToGF& msg)
×
1008
{
1009
    skiptogf = msg.targetGF;
×
1010
    LOG.write("Jumping from GF %1% to GF %2%\n") % GetGFNumber() % skiptogf;
×
1011
    return true;
×
1012
}
1013

1014
void GameClient::OnError(ClientError error)
1✔
1015
{
1016
    if(ci)
1✔
1017
        ci->CI_Error(error);
1✔
1018
    Stop();
1✔
1019
}
1✔
1020

1021
void GameClient::AdvanceState(ConnectState newState)
25✔
1022
{
1023
    connectState = newState;
25✔
1024
    if(ci)
25✔
1025
        ci->CI_NextConnectState(connectState);
25✔
1026
}
25✔
1027

1028
bool GameClient::VerifyState(ConnectState expectedState)
28✔
1029
{
1030
    if(state != ClientState::Connect || connectState != expectedState)
28✔
1031
    {
1032
        OnError(ClientError::InvalidMessage);
×
1033
        return false;
×
1034
    }
1035
    return true;
28✔
1036
}
1037

1038
bool GameClient::CreateLobby()
2✔
1039
{
1040
    RTTR_Assert(!gameLobby);
2✔
1041

1042
    unsigned numPlayers;
1043

1044
    switch(mapinfo.type)
2✔
1045
    {
1046
        case MapType::OldMap:
2✔
1047
        {
1048
            libsiedler2::Archiv map;
2✔
1049

1050
            // Karteninformationen laden
1051
            if(libsiedler2::loader::LoadMAP(mapinfo.filepath, map, true) != 0)
2✔
1052
            {
1053
                LOG.write("GameClient::OnMapData: ERROR: Map %1%, couldn't load header!\n") % mapinfo.filepath;
×
1054
                return false;
×
1055
            }
1056

1057
            const libsiedler2::ArchivItem_Map_Header& header =
1058
              checkedCast<const libsiedler2::ArchivItem_Map*>(map.get(0))->getHeader();
2✔
1059
            numPlayers = header.getNumPlayers();
2✔
1060
            mapinfo.title = s25util::ansiToUTF8(header.getName());
2✔
1061
        }
1062
        break;
2✔
1063
        case MapType::Savegame:
×
1064
            mapinfo.savegame = std::make_unique<Savegame>();
×
1065
            if(!mapinfo.savegame->Load(mapinfo.filepath, SaveGameDataToLoad::HeaderAndSettings))
×
1066
                return false;
×
1067

1068
            numPlayers = mapinfo.savegame->GetNumPlayers();
×
1069
            mapinfo.title = mapinfo.savegame->GetMapName();
×
1070
            break;
×
1071
        default: return false;
×
1072
    }
1073

1074
    if(GetPlayerId() >= numPlayers)
2✔
1075
        return false;
×
1076

1077
    gameLobby = std::make_shared<GameLobby>(mapinfo.type == MapType::Savegame, IsHost(), numPlayers);
2✔
1078
    return true;
2✔
1079
}
1080

1081
///////////////////////////////////////////////////////////////////////////////
1082
/// map-checksum
1083
/// @param message  Nachricht, welche ausgeführt wird
1084
bool GameClient::OnGameMessage(const GameMessage_Map_ChecksumOK& msg)
2✔
1085
{
1086
    if(!VerifyState(ConnectState::VerifyMap))
2✔
1087
        return true;
×
1088
    LOG.writeToFile("<<< NMS_MAP_CHECKSUM(%d)\n") % (msg.correct ? 1 : 0);
2✔
1089

1090
    if(msg.correct)
2✔
1091
        AdvanceState(ConnectState::QueryServerName);
2✔
1092
    else
1093
    {
1094
        gameLobby.reset();
×
1095
        if(msg.retryAllowed)
×
1096
        {
1097
            mainPlayer.sendMsgAsync(new GameMessage_MapRequest(false));
×
1098
            AdvanceState(ConnectState::ReceiveMap);
×
1099
        } else
1100
            OnError(ClientError::MapTransmission);
×
1101
    }
1102
    return true;
2✔
1103
}
1104

1105
///////////////////////////////////////////////////////////////////////////////
1106
/// server typ
1107
/// @param message  Nachricht, welche ausgeführt wird
1108
bool GameClient::OnGameMessage(const GameMessage_GGSChange& msg)
2✔
1109
{
1110
    if(state != ClientState::Config && !VerifyState(ConnectState::QuerySettings))
2✔
1111
        return true;
×
1112
    LOG.writeToFile("<<< NMS_GGS_CHANGE\n");
2✔
1113

1114
    gameLobby->getSettings() = msg.ggs;
2✔
1115

1116
    if(state == ClientState::Connect)
2✔
1117
    {
1118
        state = ClientState::Config;
2✔
1119
        AdvanceState(ConnectState::Finished);
2✔
1120
    } else if(ci)
×
1121
        ci->CI_GGSChanged(msg.ggs);
×
1122
    return true;
2✔
1123
}
1124

1125
bool GameClient::OnGameMessage(const GameMessage_RemoveLua&)
×
1126
{
1127
    if(state != ClientState::Connect && state != ClientState::Config)
×
1128
        return true;
×
1129
    mapinfo.luaFilepath.clear();
×
1130
    mapinfo.luaData.Clear();
×
1131
    mapinfo.luaChecksum = 0;
×
1132
    return true;
×
1133
}
1134

1135
///////////////////////////////////////////////////////////////////////////////
1136
/// NFC Antwort vom Server
1137
/// @param message  Nachricht, welche ausgeführt wird
1138
bool GameClient::OnGameMessage(const GameMessage_GameCommand& msg)
×
1139
{
1140
    if(nwfInfo)
×
1141
    {
1142
        if(!nwfInfo->addPlayerCmds(msg.player, msg.cmds))
×
1143
        {
1144
            LOG.write("Could not add gamecommands for player %1%. He might be cheating!\n") % unsigned(msg.player);
×
1145
            RTTR_Assert(false);
×
1146
        }
1147
    }
1148
    return true;
×
1149
}
1150

1151
void GameClient::IncreaseSpeed()
×
1152
{
1153
    const bool debugMode =
×
1154
#ifndef NDEBUG
1155
      true;
1156
#else
1157
      false;
1158
#endif
1159
    if(framesinfo.gfLengthReq > FramesInfo::milliseconds32_t(10))
×
1160
        framesinfo.gfLengthReq -= FramesInfo::milliseconds32_t(10);
×
1161
    else if((replayMode || debugMode) && framesinfo.gfLengthReq == FramesInfo::milliseconds32_t(10))
×
1162
        framesinfo.gfLengthReq = FramesInfo::milliseconds32_t(1);
×
1163
    else
1164
        framesinfo.gfLengthReq = FramesInfo::milliseconds32_t(70);
×
1165

1166
    if(replayMode)
×
1167
        framesinfo.gf_length = framesinfo.gfLengthReq;
×
1168
    else
1169
        mainPlayer.sendMsgAsync(new GameMessage_Speed(framesinfo.gfLengthReq.count()));
×
1170
}
×
1171

1172
void GameClient::DecreaseSpeed()
×
1173
{
1174
    const bool debugMode =
×
1175
#ifndef NDEBUG
1176
      true;
1177
#else
1178
      false;
1179
#endif
1180

1181
    FramesInfo::milliseconds32_t maxSpeed(replayMode ? 1000 : 70);
×
1182

1183
    if(framesinfo.gfLengthReq == maxSpeed)
×
1184
        framesinfo.gfLengthReq = FramesInfo::milliseconds32_t(replayMode || debugMode ? 1 : 10);
×
1185
    else if(framesinfo.gfLengthReq == FramesInfo::milliseconds32_t(1))
×
1186
        framesinfo.gfLengthReq = FramesInfo::milliseconds32_t(10);
×
1187
    else
1188
        framesinfo.gfLengthReq += FramesInfo::milliseconds32_t(10);
×
1189

1190
    if(replayMode)
×
1191
        framesinfo.gf_length = framesinfo.gfLengthReq;
×
1192
    else
1193
        mainPlayer.sendMsgAsync(new GameMessage_Speed(framesinfo.gfLengthReq.count()));
×
1194
}
×
1195

1196
///////////////////////////////////////////////////////////////////////////////
1197
/// NFC Done vom Server
1198
/// @param message  Nachricht, welche ausgeführt wird
1199
bool GameClient::OnGameMessage(const GameMessage_Server_NWFDone& msg)
×
1200
{
1201
    if(!nwfInfo)
×
1202
        return true;
×
1203

1204
    if(!nwfInfo->addServerInfo(NWFServerInfo(msg.gf, msg.gf_length, msg.nextNWF)))
×
1205
    {
1206
        RTTR_Assert(false);
×
1207
        LOG.write("Failed to add server info. Invalid server?\n");
1208
    }
1209

1210
    return true;
×
1211
}
1212

1213
/**
1214
 *  Pause-Nachricht von Server
1215
 *
1216
 *  @param[in] message Nachricht, welche ausgeführt wird
1217
 */
1218
bool GameClient::OnGameMessage(const GameMessage_Pause& msg)
×
1219
{
1220
    if(state != ClientState::Game)
×
1221
        return true;
×
1222
    if(framesinfo.isPaused == msg.paused)
×
1223
        return true;
×
1224
    framesinfo.isPaused = msg.paused;
×
1225

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

1228
    if(msg.paused)
×
1229
        ci->CI_GamePaused();
×
1230
    else
1231
        ci->CI_GameResumed();
×
1232
    return true;
×
1233
}
1234

1235
/**
1236
 *  NFC GetAsyncLog von Server
1237
 *
1238
 *  @param[in] message Nachricht, welche ausgeführt wird
1239
 */
1240
bool GameClient::OnGameMessage(const GameMessage_GetAsyncLog& /*msg*/)
×
1241
{
1242
    if(state != ClientState::Game)
×
1243
        return true;
×
1244
    std::string systemInfo = System::getCompilerName() + " @ " + System::getOSName();
×
1245
    mainPlayer.sendMsgAsync(new GameMessage_AsyncLog(systemInfo));
×
1246

1247
    // AsyncLog an den Server senden
1248

1249
    std::vector<RandomEntry> async_log = RANDOM.GetAsyncLog();
×
1250

1251
    // stückeln...
1252
    std::vector<RandomEntry> part;
×
1253
    for(auto& it : async_log)
×
1254
    {
1255
        part.push_back(it);
×
1256

1257
        if(part.size() == 10)
×
1258
        {
1259
            mainPlayer.sendMsgAsync(new GameMessage_AsyncLog(part, false));
×
1260
            part.clear();
×
1261
        }
1262
    }
1263

1264
    mainPlayer.sendMsgAsync(new GameMessage_AsyncLog(part, true));
×
1265
    return true;
×
1266
}
1267

1268
///////////////////////////////////////////////////////////////////////////////
1269
/// testet ob ein Netwerkframe abgelaufen ist und führt dann ggf die Befehle aus
1270
void GameClient::ExecuteGameFrame()
×
1271
{
1272
    if(framesinfo.isPaused)
×
1273
        return; // Pause
×
1274

1275
    FramesInfo::UsedClock::time_point currentTime = FramesInfo::UsedClock::now();
×
1276

1277
    if(framesinfo.forcePauseLen.count())
×
1278
    {
1279
        if(currentTime - framesinfo.forcePauseStart > framesinfo.forcePauseLen)
×
1280
            framesinfo.forcePauseLen = FramesInfo::milliseconds32_t::zero();
×
1281
        else
1282
            return; // Pause
×
1283
    }
1284

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

1325
                    RTTR_Assert(nwfInfo->getServerInfo().gf == curGF);
×
1326

1327
                    ExecuteNWF();
×
1328

1329
                    FramesInfo::milliseconds32_t oldGFLen = framesinfo.gf_length;
×
1330
                    nwfInfo->execute(framesinfo);
×
1331
                    if(oldGFLen != framesinfo.gf_length)
×
1332
                    {
1333
                        LOG.write("Client: Speed changed at %1% from %2% to %3% (NWF: %4%)\n") % curGF
×
1334
                          % helpers::withUnit(oldGFLen) % helpers::withUnit(framesinfo.gf_length)
×
1335
                          % framesinfo.nwf_length;
×
1336
                    }
1337
                }
1338

1339
                NextGF(isNWF);
×
1340
                RTTR_Assert(curGF <= nwfInfo->getNextNWF());
×
1341
                HandleAutosave();
×
1342

1343
                // GF-Ende im Replay aktualisieren
1344
                if(replayinfo && replayinfo->replay.IsRecording())
×
1345
                    replayinfo->replay.UpdateLastGF(curGF);
×
1346
            }
1347

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

1369
        RTTR_Assert(framesinfo.gf_length > DurationType::zero());
×
1370
        const auto maxFrameTime = framesinfo.gf_length - DurationType(1);
×
1371

1372
        if(framesinfo.frameTime > maxLackFrames * framesinfo.gf_length)
×
1373
            framesinfo.lastTime += framesinfo.frameTime - maxFrameTime; // Skip simulation time until caught up
×
1374
        framesinfo.frameTime = maxFrameTime;
×
1375
    }
1376
    // This is assumed by drawing code for interpolation
1377
    RTTR_Assert(framesinfo.frameTime < framesinfo.gf_length);
×
1378
}
1379

1380
void GameClient::HandleAutosave()
×
1381
{
1382
    // If inactive or during replay -> no autosave
1383
    if(!SETTINGS.interface.autosave_interval || replayMode)
×
1384
        return;
×
1385

1386
    // Alle .... GF
1387
    if(GetGFNumber() % SETTINGS.interface.autosave_interval == 0)
×
1388
    {
1389
        std::string filename;
×
1390
        if(mapinfo.title.empty())
×
1391
            filename = std::string(_("Auto-Save")) + ".sav";
×
1392
        else
1393
            filename = mapinfo.title + " (" + _("Auto-Save") + ").sav";
×
1394

1395
        SaveToFile(RTTRCONFIG.ExpandPath(s25::folders::save) / filename);
×
1396
    }
1397
}
1398

1399
/// Führt notwendige Dinge für nächsten GF aus
1400
void GameClient::NextGF(bool wasNWF)
×
1401
{
1402
    for(AIPlayer& ai : game->aiPlayers_)
×
1403
        ai.RunGF(GetGFNumber(), wasNWF);
×
1404
    game->RunGF();
×
1405
}
×
1406

1407
void GameClient::ExecuteAllGCs(uint8_t playerId, const PlayerGameCommands& gcs)
×
1408
{
1409
    for(const gc::GameCommandPtr& gc : gcs.gcs)
×
1410
        gc->Execute(game->world_, playerId);
×
1411
}
×
1412

1413
void GameClient::SendNothingNC(uint8_t player)
×
1414
{
1415
    mainPlayer.sendMsgAsync(
×
1416
      new GameMessage_GameCommand(player, AsyncChecksum::create(*game), std::vector<gc::GameCommandPtr>()));
×
1417
}
×
1418

1419
void GameClient::WritePlayerInfo(SavedFile& file)
×
1420
{
1421
    RTTR_Assert(state == ClientState::Loading || state == ClientState::Loaded || state == ClientState::Game);
×
1422
    // Spielerdaten
1423
    for(unsigned i = 0; i < GetNumPlayers(); ++i)
×
1424
        file.AddPlayer(GetPlayer(i));
×
1425
}
×
1426

1427
void GameClient::OnGameStart()
5✔
1428
{
1429
    if(state == ClientState::Loaded)
5✔
1430
    {
1431
        GAMEMANAGER.ResetAverageGFPS();
×
1432
        framesinfo.lastTime = FramesInfo::UsedClock::now();
×
1433
        state = ClientState::Game;
×
1434
        if(ci)
×
1435
            ci->CI_GameStarted();
×
1436
    } else if(state == ClientState::Game && !game->IsStarted())
5✔
1437
    {
1438
        framesinfo.isPaused = replayMode;
×
1439
        game->Start(!!mapinfo.savegame);
×
1440
    }
1441
}
5✔
1442

1443
void GameClient::StartReplayRecording(const unsigned random_init)
×
1444
{
1445
    replayinfo = std::make_unique<ReplayInfo>();
×
1446
    replayinfo->filename = s25util::Time::FormatTime("%Y-%m-%d_%H-%i-%s") + ".rpl";
×
1447

1448
    WritePlayerInfo(replayinfo->replay);
×
1449
    replayinfo->replay.ggs = game->ggs_;
×
1450

1451
    if(!replayinfo->replay.StartRecording(RTTRCONFIG.ExpandPath(s25::folders::replays) / replayinfo->filename, mapinfo,
×
1452
                                          random_init))
1453
    {
1454
        LOG.write(_("Replayfile couldn't be opened. No replay will be recorded\n"));
×
1455
        replayinfo.reset();
×
1456
    }
1457
}
×
1458

1459
bool GameClient::StartReplay(const boost::filesystem::path& path)
×
1460
{
1461
    RTTR_Assert(state == ClientState::Stopped);
×
1462
    mapinfo.Clear();
×
1463
    replayinfo = std::make_unique<ReplayInfo>();
×
1464

1465
    if(!replayinfo->replay.LoadHeader(path) || !replayinfo->replay.LoadGameData(mapinfo)) //-V807
×
1466
    {
1467
        LOG.write(_("Invalid Replay %1%! Reason: %2%\n")) % path
×
1468
          % (replayinfo->replay.GetLastErrorMsg().empty() ? _("Unknown") : replayinfo->replay.GetLastErrorMsg());
×
1469
        OnError(ClientError::InvalidMap);
×
1470
        replayinfo.reset();
×
1471
        return false;
×
1472
    }
1473
    replayinfo->filename = path.filename();
×
1474

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

1477
    for(unsigned i = 0; i < replayinfo->replay.GetNumPlayers(); ++i)
×
1478
        gameLobby->getPlayer(i) = JoinPlayerInfo(replayinfo->replay.GetPlayer(i));
×
1479

1480
    bool playerFound = false;
×
1481
    // Find a player to spectate from
1482
    // First find a human player
1483
    for(unsigned char i = 0; i < gameLobby->getNumPlayers(); ++i)
×
1484
    {
1485
        if(gameLobby->getPlayer(i).ps == PlayerState::Occupied)
×
1486
        {
1487
            mainPlayer.playerId = i;
×
1488
            playerFound = true;
×
1489
            break;
×
1490
        }
1491
    }
1492
    if(!playerFound)
×
1493
    {
1494
        // If no human found, take the first AI
1495
        for(unsigned char i = 0; i < gameLobby->getNumPlayers(); ++i)
×
1496
        {
1497
            if(gameLobby->getPlayer(i).ps == PlayerState::AI)
×
1498
            {
1499
                mainPlayer.playerId = i;
×
1500
                break;
×
1501
            }
1502
        }
1503
    }
1504

1505
    // GGS-Daten
1506
    gameLobby->getSettings() = replayinfo->replay.ggs;
×
1507

1508
    switch(mapinfo.type)
×
1509
    {
1510
        default: break;
×
1511
        case MapType::OldMap:
×
1512
        {
1513
            // Richtigen Pfad zur Map erstellen
1514
            bfs::path mapFilePath = RTTRCONFIG.ExpandPath(s25::folders::mapsPlayed) / mapinfo.filepath.filename();
×
1515
            mapinfo.filepath = mapFilePath;
×
1516
            if(!mapinfo.mapData.DecompressToFile(mapinfo.filepath))
×
1517
            {
1518
                LOG.write(_("Error decompressing map file"));
×
1519
                OnError(ClientError::MapTransmission);
×
1520
                return false;
×
1521
            }
1522
            if(mapinfo.luaData.uncompressedLength)
×
1523
            {
1524
                mapinfo.luaFilepath = mapFilePath.replace_extension("lua");
×
1525
                if(!mapinfo.luaData.DecompressToFile(mapinfo.luaFilepath))
×
1526
                {
1527
                    LOG.write(_("Error decompressing lua file"));
×
1528
                    OnError(ClientError::MapTransmission);
×
1529
                    return false;
×
1530
                }
1531
            }
1532
        }
1533
        break;
×
1534
        case MapType::Savegame: break;
×
1535
    }
1536

1537
    replayMode = true;
×
1538

1539
    try
1540
    {
1541
        StartGame(replayinfo->replay.getSeed());
×
1542
    } catch(SerializedGameData::Error& error)
×
1543
    {
1544
        LOG.write(_("Error when loading game from replay: %s\n")) % error.what();
×
1545
        OnError(ClientError::InvalidMap);
×
1546
        return false;
×
1547
    }
1548

1549
    replayinfo->next_gf = replayinfo->replay.ReadGF();
×
1550

1551
    return true;
×
1552
}
1553

1554
void GameClient::SetAIBattlePlayers(std::vector<AI::Info> aiInfos)
×
1555
{
1556
    aiBattlePlayers_ = std::move(aiInfos);
×
1557
}
×
1558

1559
unsigned GameClient::GetGlobalAnimation(const unsigned short max, const unsigned char factor_numerator,
1✔
1560
                                        const unsigned char factor_denumerator, const unsigned offset)
1561
{
1562
    // Unit for animations is 630ms (dividable by 2, 3, 5, 6, 7, 10, 15, ...)
1563
    // But this also means: If framerate drops below approx. 15Hz, you won't see
1564
    // every frame of an 8-part animation anymore.
1565
    // An animation runs fully in (factor_numerator / factor_denumerator) multiples of 630ms
1566
    const unsigned unit = 630 /*ms*/ * factor_numerator / factor_denumerator;
1✔
1567
    const unsigned currenttime = std::chrono::duration_cast<FramesInfo::milliseconds32_t>(
1✔
1568
                                   (framesinfo.lastTime + framesinfo.frameTime).time_since_epoch())
1✔
1569
                                   .count();
1✔
1570
    return ((currenttime % unit) * max / unit + offset) % max;
1✔
1571
}
1572

1573
unsigned GameClient::Interpolate(unsigned max_val, const GameEvent* ev)
×
1574
{
1575
    RTTR_Assert(ev);
×
1576
    // TODO: Move to some animation system that is part of game
1577
    FramesInfo::milliseconds32_t elapsedTime;
1578
    if(state == ClientState::Game)
×
1579
        elapsedTime = (GetGFNumber() - ev->startGF) * framesinfo.gf_length + framesinfo.frameTime;
×
1580
    else
1581
        elapsedTime = FramesInfo::milliseconds32_t::zero();
×
1582
    FramesInfo::milliseconds32_t duration = ev->length * framesinfo.gf_length;
×
1583
    return helpers::interpolate(0u, max_val, elapsedTime, duration);
×
1584
}
1585

1586
int GameClient::Interpolate(int x1, int x2, const GameEvent* ev)
×
1587
{
1588
    RTTR_Assert(ev);
×
1589
    FramesInfo::milliseconds32_t elapsedTime;
1590
    if(state == ClientState::Game)
×
1591
        elapsedTime = (GetGFNumber() - ev->startGF) * framesinfo.gf_length + framesinfo.frameTime;
×
1592
    else
1593
        elapsedTime = FramesInfo::milliseconds32_t::zero();
×
1594
    FramesInfo::milliseconds32_t duration = ev->length * framesinfo.gf_length;
×
1595
    return helpers::interpolate(x1, x2, elapsedTime, duration);
×
1596
}
1597

1598
void GameClient::ServerLost()
×
1599
{
1600
    OnError(ClientError::ConnectionLost);
×
1601
    // Stop game
1602
    framesinfo.isPaused = true;
×
1603
}
×
1604

1605
/**
1606
 *  überspringt eine bestimmte Anzahl von Gameframes.
1607
 *
1608
 *  @param[in] dest_gf Zielgameframe
1609
 */
1610
void GameClient::SkipGF(unsigned gf, GameWorldView& gwv)
×
1611
{
1612
    if(gf <= GetGFNumber())
×
1613
        return;
×
1614

1615
    unsigned start_ticks = VIDEODRIVER.GetTickCount();
×
1616

1617
    if(!replayMode)
×
1618
    {
1619
        // unpause before skipping
1620
        SetPause(false);
×
1621
        mainPlayer.sendMsgAsync(new GameMessage_SkipToGF(gf));
×
1622
        return;
×
1623
    }
1624

1625
    SetPause(false);
×
1626
    skiptogf = gf;
×
1627

1628
    // GFs überspringen
1629
    for(unsigned i = GetGFNumber(); i < skiptogf; ++i)
×
1630
    {
1631
        if(i % 1000 == 0)
×
1632
        {
1633
            RoadBuildState road;
×
1634
            road.mode = RoadBuildMode::Disabled;
×
1635

1636
            // spiel aktualisieren
1637
            gwv.Draw(road, MapPoint::Invalid(), false);
×
1638

1639
            // text oben noch hinschreiben
1640
            boost::format nwfString(_("current GF: %u - still fast forwarding: %d GFs left (%d %%)"));
×
1641
            nwfString % GetGFNumber() % (gf - i) % (i * 100 / gf);
×
1642
            LargeFont->Draw(DrawPoint(VIDEODRIVER.GetRenderSize() / 2u), nwfString.str(), FontStyle::CENTER,
×
1643
                            COLOR_YELLOW);
1644

1645
            VIDEODRIVER.SwapBuffers();
×
1646
        }
1647
        ExecuteGameFrame();
×
1648
    }
1649

1650
    // Spiel pausieren & text ausgabe wie lang das jetzt gedauert hat
1651
    unsigned ticks = VIDEODRIVER.GetTickCount() - start_ticks;
×
1652
    boost::format text(_("Jump finished (%1$.3g seconds)."));
×
1653
    text % (ticks / 1000.0);
×
1654
    SystemChat(text.str());
×
1655
    SetPause(true);
×
1656
}
1657

1658
void GameClient::SystemChat(const std::string& text)
×
1659
{
1660
    SystemChat(text, GetPlayerId());
×
1661
}
×
1662

1663
void GameClient::SystemChat(const std::string& text, unsigned char fromPlayerIdx)
×
1664
{
1665
    if(ci)
×
1666
        ci->CI_Chat(fromPlayerIdx, ChatDestination::System, text);
×
1667
}
×
1668

1669
bool GameClient::SaveToFile(const boost::filesystem::path& filepath)
×
1670
{
1671
    mainPlayer.sendMsg(GameMessage_Chat(GetPlayerId(), ChatDestination::System, "Saving game..."));
×
1672

1673
    // Mond malen
1674
    Position moonPos = VIDEODRIVER.GetMousePos();
×
1675
    moonPos.y -= 40;
×
1676
    LOADER.GetImageN("resource", 33)->DrawFull(moonPos);
×
1677
    VIDEODRIVER.SwapBuffers();
×
1678

1679
    Savegame save;
×
1680

1681
    WritePlayerInfo(save);
×
1682

1683
    // GGS-Daten
1684
    save.ggs = game->ggs_;
×
1685

1686
    save.start_gf = GetGFNumber();
×
1687

1688
    // Enable/Disable debugging of savegames
1689
    save.sgd.debugMode = SETTINGS.global.debugMode;
×
1690

1691
    try
1692
    {
1693
        // Spiel serialisieren
1694
        save.sgd.MakeSnapshot(*game);
×
1695
        // Und alles speichern
1696
        return save.Save(filepath, mapinfo.title);
×
1697
    } catch(std::exception& e)
×
1698
    {
1699
        SystemChat(std::string("Error during saving: ") + e.what());
×
1700
        return false;
×
1701
    }
1702
}
1703

1704
void GameClient::ResetVisualSettings()
×
1705
{
1706
    GetPlayer(GetPlayerId()).FillVisualSettings(visual_settings);
×
1707
}
×
1708

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

1733
void GameClient::SetReplayFOW(const bool hideFOW)
×
1734
{
1735
    if(replayinfo)
×
1736
        replayinfo->all_visible = hideFOW;
×
1737
}
×
1738

1739
bool GameClient::IsReplayFOWDisabled() const
×
1740
{
1741
    return replayMode && replayinfo->all_visible;
×
1742
}
1743

1744
unsigned GameClient::GetLastReplayGF() const
×
1745
{
1746
    return replayinfo ? replayinfo->replay.GetLastGF() : 0u;
×
1747
}
1748

1749
bool GameClient::AddGC(gc::GameCommandPtr gc)
×
1750
{
1751
    // Nicht in der Pause oder wenn er besiegt wurde
1752
    if(framesinfo.isPaused || GetPlayer(GetPlayerId()).IsDefeated() || IsReplayModeOn())
×
1753
        return false;
×
1754

1755
    gameCommands_.push_back(gc);
×
1756
    return true;
×
1757
}
1758

1759
unsigned GameClient::GetNumPlayers() const
×
1760
{
1761
    RTTR_Assert(state == ClientState::Loading || state == ClientState::Loaded || state == ClientState::Game);
×
1762
    return game->world_.GetNumPlayers();
×
1763
}
1764

1765
GamePlayer& GameClient::GetPlayer(const unsigned id)
×
1766
{
1767
    RTTR_Assert(state == ClientState::Loading || state == ClientState::Loaded || state == ClientState::Game);
×
1768
    RTTR_Assert(id < GetNumPlayers());
×
1769
    return game->world_.GetPlayer(id);
×
1770
}
1771

1772
std::unique_ptr<AIPlayer> GameClient::CreateAIPlayer(unsigned playerId, const AI::Info& aiInfo)
×
1773
{
1774
    return AIFactory::Create(aiInfo, playerId, game->world_);
×
1775
}
1776

1777
/// Wandelt eine GF-Angabe in eine Zeitangabe um (HH:MM:SS oder MM:SS wenn Stunden = 0)
1778
std::string GameClient::FormatGFTime(const unsigned gf) const
5✔
1779
{
1780
    using seconds = std::chrono::duration<uint32_t, std::chrono::seconds::period>;
1781
    using hours = std::chrono::duration<uint32_t, std::chrono::hours::period>;
1782
    using minutes = std::chrono::duration<uint32_t, std::chrono::minutes::period>;
1783
    using std::chrono::duration_cast;
1784

1785
    // In Sekunden umrechnen
1786
    seconds numSeconds = duration_cast<seconds>(gf * SPEED_GF_LENGTHS[referenceSpeed]);
5✔
1787

1788
    // Angaben rausfiltern
1789
    hours numHours = duration_cast<hours>(numSeconds);
5✔
1790
    numSeconds -= numHours;
5✔
1791
    minutes numMinutes = duration_cast<minutes>(numSeconds);
5✔
1792
    numSeconds -= numMinutes;
5✔
1793

1794
    // ganze Stunden mit dabei? Dann entsprechend anderes format, ansonsten ignorieren wir die einfach
1795
    if(numHours.count())
5✔
1796
        return helpers::format("%u:%02u:%02u", numHours.count(), numMinutes.count(), numSeconds.count());
×
1797
    else
1798
        return helpers::format("%02u:%02u", numMinutes.count(), numSeconds.count());
10✔
1799
}
1800

1801
const boost::filesystem::path& GameClient::GetReplayFilename() const
×
1802
{
1803
    static boost::filesystem::path emptyString;
×
1804
    return replayinfo ? replayinfo->filename : emptyString;
×
1805
}
1806

1807
Replay* GameClient::GetReplay()
×
1808
{
1809
    return replayinfo ? &replayinfo->replay : nullptr;
×
1810
}
1811

1812
std::shared_ptr<const NWFInfo> GameClient::GetNWFInfo() const
×
1813
{
1814
    return nwfInfo;
×
1815
}
1816

1817
/// Is tournament mode activated (0 if not)? Returns the durations of the tournament mode in gf otherwise
1818
unsigned GameClient::GetTournamentModeDuration() const
×
1819
{
1820
    using namespace std::chrono;
1821
    if(game && rttr::enum_cast(game->ggs_.objective) >= rttr::enum_cast(GameObjective::Tournament1)
×
1822
       && static_cast<unsigned>(rttr::enum_cast(game->ggs_.objective))
×
1823
            < rttr::enum_cast(GameObjective::Tournament1) + NUM_TOURNAMENT_MODES)
×
1824
    {
1825
        const auto turnamentMode = rttr::enum_cast(game->ggs_.objective) - rttr::enum_cast(GameObjective::Tournament1);
×
1826
        return minutes(TOURNAMENT_MODES_DURATION[turnamentMode]) / SPEED_GF_LENGTHS[referenceSpeed];
×
1827
    } else
1828
        return 0;
×
1829
}
1830

1831
void GameClient::ToggleHumanAIPlayer(const AI::Info& aiInfo)
×
1832
{
1833
    RTTR_Assert(!IsReplayModeOn());
×
1834
    auto it = helpers::find_if(game->aiPlayers_,
×
1835
                               [id = this->GetPlayerId()](const auto& player) { return player.GetPlayerId() == id; });
×
1836
    if(it != game->aiPlayers_.end())
×
1837
        game->aiPlayers_.erase(it);
×
1838
    else
1839
        game->AddAIPlayer(CreateAIPlayer(GetPlayerId(), aiInfo));
×
1840
}
×
1841

1842
void GameClient::RequestSwapToPlayer(const unsigned char newId)
×
1843
{
1844
    if(state != ClientState::Game)
×
1845
        return;
×
1846
    GamePlayer& player = GetPlayer(newId);
×
1847
    if(player.ps == PlayerState::AI && player.aiInfo.type == AI::Type::Dummy)
×
1848
        mainPlayer.sendMsgAsync(new GameMessage_Player_Swap(0xFF, newId));
×
1849
}
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