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

Return-To-The-Roots / s25client / 6903904945

17 Nov 2023 12:22PM UTC coverage: 50.233% (-0.2%) from 50.406%
6903904945

push

github

web-flow
Merge pull request #1640 from ottml/alliance_in_statistics_view

Show alliances in statistic view

0 of 21 new or added lines in 1 file covered. (0.0%)

834 existing lines in 7 files now uncovered.

21889 of 43575 relevant lines covered (50.23%)

31277.27 hits per line

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

19.74
/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
namespace {
UNCOV
58
void copyFileIfPathDifferent(const boost::filesystem::path& src_path, const boost::filesystem::path& dst_path)
×
59
{
UNCOV
60
    if(src_path != dst_path)
×
61
    {
UNCOV
62
        boost::system::error_code ignoredEc;
×
UNCOV
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
UNCOV
69
        copy_file(src_path, dst_path, overwrite_existing, ignoredEc);
×
70
    }
UNCOV
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");
×
UNCOV
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
{
UNCOV
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
UNCOV
137
    const auto playedMapPath = RTTRCONFIG.ExpandPath(s25::folders::mapsPlayed) / map.map_path.filename();
×
UNCOV
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
{
UNCOV
152
    if(state == ClientState::Stopped)
×
UNCOV
153
        return;
×
154

UNCOV
155
    SocketSet set;
×
156

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

160
    // zum set hinzufügen
UNCOV
161
    set.Add(mainPlayer.socket);
×
162
    if(set.Select(0, 0) > 0)
×
163
    {
164
        // nachricht empfangen
165
        if(!mainPlayer.receiveMsgs())
×
166
        {
UNCOV
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
UNCOV
176
    set.Add(mainPlayer.socket);
×
177

178
    // auf fehler prüfen
UNCOV
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");
×
UNCOV
185
            ServerLost();
×
186
        }
187
    }
188

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

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

UNCOV
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✔
UNCOV
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
    {
UNCOV
225
        if(replayinfo->replay.IsRecording())
×
UNCOV
226
            replayinfo->replay.StopRecording();
×
UNCOV
227
        replayinfo->replay.Close();
×
UNCOV
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

UNCOV
252
const AIPlayer* GameClient::GetAIPlayer(unsigned id) const
×
253
{
UNCOV
254
    if(!game)
×
255
        return nullptr;
×
UNCOV
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
{
UNCOV
266
    RTTR_Assert(state == ClientState::Config || (state == ClientState::Stopped && replayMode));
×
267

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

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

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

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

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

290
    // If we have a savegame, start at its first GF, else at 0
UNCOV
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,
×
UNCOV
295
                             std::vector<PlayerInfo>(gameLobby->getPlayers().begin(), gameLobby->getPlayers().end()));
×
296
    if(!IsReplayModeOn())
×
297
    {
298
        for(unsigned id = 0; id < gameLobby->getNumPlayers(); id++)
×
299
        {
UNCOV
300
            if(gameLobby->getPlayer(id).isUsed())
×
UNCOV
301
                nwfInfo->addPlayer(id);
×
302
        }
303
    }
304
    // Release lobby
305
    gameLobby.reset();
×
306

UNCOV
307
    state = ClientState::Loading;
×
308

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

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

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

UNCOV
325
        MapLoader loader(gameWorld);
×
326
        if(!loader.Load(mapinfo.filepath)
×
UNCOV
327
           || (!mapinfo.luaFilepath.empty() && !loader.LoadLuaScript(*game, *this, mapinfo.luaFilepath)))
×
328
        {
UNCOV
329
            OnError(ClientError::InvalidMap);
×
330
            return;
×
331
        }
UNCOV
332
        gameWorld.SetupResources();
×
333
    }
UNCOV
334
    gameWorld.InitAfterLoad();
×
335

336
    // Update visual settings
UNCOV
337
    ResetVisualSettings();
×
338

UNCOV
339
    if(!replayMode)
×
340
    {
UNCOV
341
        RTTR_Assert(!replayinfo);
×
342
        StartReplayRecording(random_init);
×
343
    }
344

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

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

353
    state = ClientState::Loaded;
×
354

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

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

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

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

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

414
    mainPlayer.playerId = msg.player;
3✔
415

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

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

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

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

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

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

465
    if(msg.player >= gameLobby->getNumPlayers())
×
UNCOV
466
        return true;
×
467

468
    JoinPlayerInfo& playerInfo = gameLobby->getPlayer(msg.player);
×
469

470
    playerInfo.name = msg.name;
×
UNCOV
471
    playerInfo.ps = PlayerState::Occupied;
×
472
    playerInfo.ping = 0;
×
473

474
    if(ci)
×
475
        ci->CI_NewPlayer(msg.player);
×
UNCOV
476
    return true;
×
477
}
478

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

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

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

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

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

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

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

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

UNCOV
541
    gameLobby->getPlayer(msg.player).nation = msg.nation;
×
542

543
    if(ci)
×
UNCOV
544
        ci->CI_PlayerDataChanged(msg.player);
×
545
    return true;
×
546
}
547

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

UNCOV
556
    if(msg.player >= gameLobby->getNumPlayers())
×
UNCOV
557
        return true;
×
558

UNCOV
559
    gameLobby->getPlayer(msg.player).team = msg.team;
×
560

561
    if(ci)
×
UNCOV
562
        ci->CI_PlayerDataChanged(msg.player);
×
563
    return true;
×
564
}
565

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

UNCOV
574
    if(msg.player >= gameLobby->getNumPlayers())
×
UNCOV
575
        return true;
×
576

UNCOV
577
    gameLobby->getPlayer(msg.player).color = msg.color;
×
578

UNCOV
579
    if(ci)
×
580
        ci->CI_PlayerDataChanged(msg.player);
×
581
    return true;
×
582
}
583

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

UNCOV
594
    if(msg.player >= gameLobby->getNumPlayers())
×
UNCOV
595
        return true;
×
596

UNCOV
597
    gameLobby->getPlayer(msg.player).isReady = msg.ready;
×
598

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

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

632
    if(ci)
×
633
        ci->CI_PlayerLeft(msg.player);
×
UNCOV
634
    return true;
×
635
}
636

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

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

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

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

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

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

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

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

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

700
    mainPlayer.sendMsgAsync(new GameMessage_Server_Password(clientconfig.password));
3✔
701

702
    AdvanceState(ConnectState::QueryPw);
3✔
703
    return true;
3✔
704
}
705

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

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

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

723
    AdvanceState(ConnectState::QueryMapInfo);
3✔
724
    return true;
3✔
725
}
726

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

736
    AdvanceState(ConnectState::QueryPlayerList);
2✔
737
    return true;
2✔
738
}
739

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

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

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

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

UNCOV
782
        const GamePlayer& player = game->world_.GetPlayer(msg.player);
×
783

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1036
bool GameClient::CreateLobby()
2✔
1037
{
1038
    RTTR_Assert(!gameLobby);
2✔
1039

1040
    unsigned numPlayers;
1041

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

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

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

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

1072
    if(GetPlayerId() >= numPlayers)
2✔
UNCOV
1073
        return false;
×
1074

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

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

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

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

1112
    gameLobby->getSettings() = msg.ggs;
2✔
1113

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

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

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

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

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

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

UNCOV
1179
    FramesInfo::milliseconds32_t maxSpeed(replayMode ? 1000 : 70);
×
1180

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

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

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

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

1208
    return true;
×
1209
}
1210

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

UNCOV
1224
    LOG.writeToFile("<<< NMS_NFC_PAUSE(%1%)\n") % msg.paused;
×
1225

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

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

1245
    // AsyncLog an den Server senden
1246

1247
    std::vector<RandomEntry> async_log = RANDOM.GetAsyncLog();
×
1248

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

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

1262
    mainPlayer.sendMsgAsync(new GameMessage_AsyncLog(part, true));
×
UNCOV
1263
    return true;
×
1264
}
1265

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

1273
    FramesInfo::UsedClock::time_point currentTime = FramesInfo::UsedClock::now();
×
1274

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

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

UNCOV
1323
                    RTTR_Assert(nwfInfo->getServerInfo().gf == curGF);
×
1324

UNCOV
1325
                    ExecuteNWF();
×
1326

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

1337
                NextGF(isNWF);
×
1338
                RTTR_Assert(curGF <= nwfInfo->getNextNWF());
×
UNCOV
1339
                HandleAutosave();
×
1340

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

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

1367
        RTTR_Assert(framesinfo.gf_length > DurationType::zero());
×
UNCOV
1368
        const auto maxFrameTime = framesinfo.gf_length - DurationType(1);
×
1369

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

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

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

UNCOV
1393
        SaveToFile(RTTRCONFIG.ExpandPath(s25::folders::save) / filename);
×
1394
    }
1395
}
1396

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1536
    replayMode = true;
×
UNCOV
1537
    replayinfo->async = 0;
×
UNCOV
1538
    replayinfo->end = false;
×
1539

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

UNCOV
1550
    replayinfo->replay.ReadGF(&replayinfo->next_gf);
×
1551

UNCOV
1552
    return true;
×
1553
}
1554

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

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

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

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

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

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

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

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

UNCOV
1626
    SetPause(false);
×
1627
    skiptogf = gf;
×
1628

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

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

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

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

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

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

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

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

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

UNCOV
1680
    Savegame save;
×
1681

UNCOV
1682
    WritePlayerInfo(save);
×
1683

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

1687
    save.start_gf = GetGFNumber();
×
1688

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

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

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

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

1734
void GameClient::ToggleReplayFOW()
×
1735
{
1736
    if(replayinfo)
×
UNCOV
1737
        replayinfo->all_visible = !replayinfo->all_visible;
×
UNCOV
1738
}
×
1739

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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