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

Return-To-The-Roots / s25client / 22254856344

21 Feb 2026 10:02AM UTC coverage: 50.787% (-0.003%) from 50.79%
22254856344

push

github

web-flow
Merge pull request #1888 from Flamefire/duplicate-colors

1 of 30 new or added lines in 2 files covered. (3.33%)

6 existing lines in 2 files now uncovered.

22805 of 44903 relevant lines covered (50.79%)

42423.41 hits per line

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

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

5
#include "GameServer.h"
6
#include "Debug.h"
7
#include "GameMessage.h"
8
#include "GameMessage_GameCommand.h"
9
#include "GameServerPlayer.h"
10
#include "GlobalGameSettings.h"
11
#include "JoinPlayerInfo.h"
12
#include "RTTR_Version.h"
13
#include "RttrConfig.h"
14
#include "Savegame.h"
15
#include "Settings.h"
16
#include "commonDefines.h"
17
#include "files.h"
18
#include "helpers/containerUtils.h"
19
#include "helpers/mathFuncs.h"
20
#include "helpers/random.h"
21
#include "network/CreateServerInfo.h"
22
#include "network/GameMessages.h"
23
#include "random/randomIO.h"
24
#include "gameTypes/LanGameInfo.h"
25
#include "gameTypes/TeamTypes.h"
26
#include "gameData/GameConsts.h"
27
#include "gameData/LanDiscoveryCfg.h"
28
#include "gameData/MaxPlayers.h"
29
#include "gameData/PortraitConsts.h"
30
#include "liblobby/LobbyClient.h"
31
#include "libsiedler2/ArchivItem_Map.h"
32
#include "libsiedler2/ArchivItem_Map_Header.h"
33
#include "libsiedler2/prototypen.h"
34
#include "s25util/SocketSet.h"
35
#include "s25util/colors.h"
36
#include "s25util/utf8.h"
37
#include <boost/container/static_vector.hpp>
38
#include <boost/filesystem.hpp>
39
#include <boost/nowide/convert.hpp>
40
#include <boost/nowide/fstream.hpp>
41
#include <helpers/chronoIO.h>
42
#include <iomanip>
43
#include <iterator>
44
#include <mygettext/mygettext.h>
45

46
struct GameServer::AsyncLog
47
{
48
    uint8_t playerId;
49
    bool done;
50
    AsyncChecksum checksum;
51
    std::string addData;
52
    std::vector<RandomEntry> randEntries;
53
    AsyncLog(uint8_t playerId, AsyncChecksum checksum) : playerId(playerId), done(false), checksum(checksum) {}
×
54
};
55

56
GameServer::ServerConfig::ServerConfig()
×
57
{
58
    Clear();
×
59
}
×
60

61
void GameServer::ServerConfig::Clear()
×
62
{
63
    servertype = ServerType::Local;
×
64
    gamename.clear();
×
65
    password.clear();
×
66
    port = 0;
×
67
    ipv6 = false;
×
68
}
×
69

70
GameServer::CountDown::CountDown() : isActive(false), remainingSecs(0) {}
×
71

72
void GameServer::CountDown::Start(unsigned timeInSec)
×
73
{
74
    isActive = true;
×
75
    remainingSecs = timeInSec;
×
76
    lasttime = SteadyClock::now();
×
77
}
×
78

79
void GameServer::CountDown::Stop()
×
80
{
81
    isActive = false;
×
82
}
×
83

84
bool GameServer::CountDown::Update()
×
85
{
86
    RTTR_Assert(isActive);
×
87
    SteadyClock::time_point curTime = SteadyClock::now();
×
88

89
    // Check if 1s has passed
90
    if(curTime - lasttime < std::chrono::seconds(1))
×
91
        return false;
×
92
    if(remainingSecs == 0)
×
93
    {
94
        Stop();
×
95
        return true;
×
96
    }
97
    // 1s has passed -> Reduce remaining time
98
    lasttime = curTime;
×
99
    remainingSecs--;
×
100
    return true;
×
101
}
102

103
GameServer::GameServer() : skiptogf(0), state(ServerState::Stopped), currentGF(0), lanAnnouncer(LAN_DISCOVERY_CFG) {}
×
104

105
GameServer::~GameServer()
×
106
{
107
    Stop();
×
108
}
×
109

110
///////////////////////////////////////////////////////////////////////////////
111
// Spiel hosten
112
bool GameServer::Start(const CreateServerInfo& csi, const MapDescription& map, const std::string& hostPw)
×
113
{
114
    Stop();
×
115

116
    // Name, Password und Kartenname kopieren
117
    config.gamename = csi.gameName;
×
118
    config.hostPassword = hostPw;
×
119
    config.password = csi.password;
×
120
    config.servertype = csi.type;
×
121
    config.port = csi.port;
×
122
    config.ipv6 = csi.ipv6;
×
123
    mapinfo.type = map.map_type;
×
124
    mapinfo.filepath = map.map_path;
×
125

126
    // Maps, Random-Maps, Savegames - Header laden und relevante Informationen rausschreiben (Map-Titel, Spieleranzahl)
127
    switch(mapinfo.type)
×
128
    {
129
        // Altes S2-Mapformat von BB
130
        case MapType::OldMap:
×
131
        {
132
            libsiedler2::Archiv map;
×
133

134
            // Karteninformationen laden
135
            if(libsiedler2::loader::LoadMAP(mapinfo.filepath, map, true) != 0)
×
136
            {
137
                LOG.write("GameServer::Start: ERROR: Map %1%, couldn't load header!\n") % mapinfo.filepath;
×
138
                return false;
×
139
            }
140
            const libsiedler2::ArchivItem_Map_Header& header =
141
              checkedCast<const libsiedler2::ArchivItem_Map*>(map.get(0))->getHeader();
×
142

143
            playerInfos.resize(header.getNumPlayers());
×
144
            mapinfo.title = s25util::ansiToUTF8(header.getName());
×
145
            ggs_.LoadSettings();
×
146
            currentGF = 0;
×
147
        }
148
        break;
×
149
        // Gespeichertes Spiel
150
        case MapType::Savegame:
×
151
        {
152
            Savegame save;
×
153

154
            if(!save.Load(mapinfo.filepath, SaveGameDataToLoad::HeaderAndSettings))
×
155
                return false;
×
156

157
            // Spieleranzahl
158
            playerInfos.resize(save.GetNumPlayers());
×
159
            mapinfo.title = save.GetMapName();
×
160

161
            for(unsigned i = 0; i < playerInfos.size(); ++i)
×
162
            {
163
                playerInfos[i] = JoinPlayerInfo(save.GetPlayer(i));
×
164
                // If it was a human we make it free, so someone can join
165
                if(playerInfos[i].ps == PlayerState::Occupied)
×
166
                    playerInfos[i].ps = PlayerState::Free;
×
167
            }
168

169
            ggs_ = save.ggs;
×
170
            currentGF = save.start_gf;
×
171
        }
172
        break;
×
173
    }
174

175
    if(playerInfos.empty())
×
176
    {
177
        LOG.write("Map %1% has no players!\n") % mapinfo.filepath;
×
178
        return false;
×
179
    }
NEW
180
    if(playerInfos.size() > MAX_PLAYERS)
×
181
    {
NEW
182
        LOG.write("Map %1% has too many players! (%1% > %2%)\n") % mapinfo.filepath % playerInfos.size() % MAX_PLAYERS;
×
NEW
183
        return false;
×
184
    }
185
    if(!mapinfo.mapData.CompressFromFile(mapinfo.filepath, &mapinfo.mapChecksum))
×
186
        return false;
×
187

188
    if(map.lua_path.has_value() && !bfs::is_regular_file(*map.lua_path))
×
189
        return false;
×
190

191
    bfs::path luaFilePath = map.lua_path.get_value_or(bfs::path(mapinfo.filepath).replace_extension("lua"));
×
192
    if(bfs::is_regular_file(luaFilePath))
×
193
    {
194
        if(!mapinfo.luaData.CompressFromFile(luaFilePath, &mapinfo.luaChecksum))
×
195
            return false;
×
196
        mapinfo.luaFilepath = luaFilePath;
×
197
    } else
198
        RTTR_Assert(mapinfo.luaFilepath.empty() && mapinfo.luaChecksum == 0);
×
199

200
    if(!mapinfo.verifySize())
×
201
    {
202
        LOG.write("Map %1% is to large!\n") % mapinfo.filepath;
×
203
        return false;
×
204
    }
205

206
    // ab in die Konfiguration
207
    state = ServerState::Config;
×
208

209
    // und das socket in listen-modus schicken
210
    if(!serversocket.Listen(config.port, config.ipv6, csi.use_upnp))
×
211
    {
212
        LOG.write("GameServer::Start: ERROR: Listening on port %d failed!\n") % config.port;
×
213
        LOG.writeLastError("Fehler");
×
214
        return false;
×
215
    }
216

217
    if(config.servertype == ServerType::LAN)
×
218
        lanAnnouncer.Start();
×
219
    else if(config.servertype == ServerType::Lobby)
×
220
    {
221
        LOBBYCLIENT.AddServer(config.gamename, mapinfo.title, (!config.password.empty()), config.port);
×
222
        LOBBYCLIENT.AddListener(this);
×
223
    }
224
    AnnounceStatusChange();
×
225

226
    return true;
×
227
}
228

229
unsigned GameServer::GetNumFilledSlots() const
×
230
{
231
    unsigned numFilled = 0;
×
232
    for(const JoinPlayerInfo& player : playerInfos)
×
233
    {
234
        if(player.ps != PlayerState::Free)
×
235
            ++numFilled;
×
236
    }
237
    return numFilled;
×
238
}
239

240
void GameServer::AnnounceStatusChange()
×
241
{
242
    if(config.servertype == ServerType::LAN)
×
243
    {
244
        LanGameInfo info;
×
245
        info.name = config.gamename;
×
246
        info.hasPwd = !config.password.empty();
×
247
        info.map = mapinfo.title;
×
248
        info.curNumPlayers = GetNumFilledSlots();
×
249
        info.maxNumPlayers = playerInfos.size();
×
250
        info.port = config.port;
×
251
        info.isIPv6 = config.ipv6;
×
252
        info.version = rttr::version::GetReadableVersion();
×
253
        info.revision = rttr::version::GetRevision();
×
254
        Serializer ser;
×
255
        info.Serialize(ser);
×
256
        lanAnnouncer.SetPayload(ser.GetData(), ser.GetLength());
×
257
    } else if(config.servertype == ServerType::Lobby)
×
258
    {
259
        if(LOBBYCLIENT.IsIngame())
×
260
            LOBBYCLIENT.UpdateServerNumPlayers(GetNumFilledSlots(), playerInfos.size());
×
261
    }
262
}
×
263

264
void GameServer::LC_Status_Error(const std::string& /*error*/)
×
265
{
266
    // Error during adding of server to lobby -> Stop
267
    Stop();
×
268
}
×
269

270
void GameServer::LC_Created()
×
271
{
272
    // All good -> Don't listen anymore
273
    LOBBYCLIENT.RemoveListener(this);
×
274
    AnnounceStatusChange();
×
275
}
×
276

277
void GameServer::Run()
×
278
{
279
    if(state == ServerState::Stopped)
×
280
        return;
×
281

282
    // auf tote Clients prüfen
283
    ClientWatchDog();
×
284

285
    // auf neue Clients warten
286
    if(state == ServerState::Config)
×
287
        RunStateConfig();
×
288
    else if(state == ServerState::Loading)
×
289
        RunStateLoading();
×
290
    else if(state == ServerState::Game)
×
291
        RunStateGame();
×
292

293
    // post zustellen
294
    FillPlayerQueues();
×
295

296
    // Execute messages
297
    for(GameServerPlayer& player : networkPlayers)
×
298
    {
299
        // Ignore kicked players
300
        if(!player.socket.isValid())
×
301
            continue;
×
302
        player.executeMsgs(*this);
×
303
    }
304
    // Send afterwards as most messages are relayed which should be done as fast as possible
305
    for(GameServerPlayer& player : networkPlayers)
×
306
    {
307
        // Ignore kicked players
308
        if(!player.socket.isValid())
×
309
            continue;
×
310
        player.sendMsgs(10);
×
311
    }
312
    helpers::erase_if(networkPlayers, [](const auto& player) { return !player.socket.isValid(); });
×
313

314
    lanAnnouncer.Run();
×
315
}
316

317
void GameServer::RunStateConfig()
×
318
{
319
    WaitForClients();
×
320
    if(countdown.IsActive() && countdown.Update())
×
321
    {
322
        // nun echt starten
323
        if(!countdown.IsActive())
×
324
        {
325
            if(!StartGame())
×
326
            {
327
                Stop();
×
328
                return;
×
329
            }
330
        } else
331
            SendToAll(GameMessage_Countdown(countdown.GetRemainingSecs()));
×
332
    }
333
}
334

335
void GameServer::RunStateLoading()
×
336
{
337
    if(!nwfInfo.isReady())
×
338
    {
339
        if(SteadyClock::now() - loadStartTime > std::chrono::seconds(LOAD_TIMEOUT))
×
340
        {
341
            for(const NWFPlayerInfo& player : nwfInfo.getPlayerInfos())
×
342
            {
343
                if(player.isLagging)
×
344
                    KickPlayer(player.id, KickReason::PingTimeout, __LINE__);
×
345
            }
346
        }
347
        return;
×
348
    }
349
    LOG.write("SERVER: Game loaded by all players after %1%\n")
×
350
      % helpers::withUnit(std::chrono::duration_cast<std::chrono::seconds>(SteadyClock::now() - loadStartTime));
×
351
    // The first NWF is ready. Server has to set up "missing" commands so every future command is for the correct NWF as
352
    // specified with cmdDelay. We have commands for NWF 0. When clients execute this they will send the commands for
353
    // NWF cmdDelay. So commands for NWF 1..cmdDelay-1 are missing. Do this here and before the NWFDone is sent,
354
    // otherwise we might get them in a wrong order when messages are sent asynchronously
355
    for(unsigned i = 1; i < nwfInfo.getCmdDelay(); i++)
×
356
    {
357
        for(const NWFPlayerInfo& player : nwfInfo.getPlayerInfos())
×
358
        {
359
            GameMessage_GameCommand msg(player.id, nwfInfo.getPlayerCmds(player.id).checksum,
×
360
                                        std::vector<gc::GameCommandPtr>());
×
361
            SendToAll(msg);
×
362
            nwfInfo.addPlayerCmds(player.id, msg.cmds);
×
363
        }
364
    }
365

366
    NWFServerInfo serverInfo = nwfInfo.getServerInfo();
×
367
    // Send cmdDelay NWFDone messages
368
    // First send the OK for NWF 0 which is also the game ready command
369
    // Note: Do not store. It already is in NWFInfo
370
    SendToAll(GameMessage_Server_NWFDone(serverInfo.gf, serverInfo.newGFLen, serverInfo.nextNWF));
×
371
    RTTR_Assert(framesinfo.nwf_length > 0);
×
372
    // Then the remaining OKs for the commands sent above
373
    for(unsigned i = 1; i < nwfInfo.getCmdDelay(); i++)
×
374
    {
375
        serverInfo.gf = serverInfo.nextNWF;
×
376
        serverInfo.nextNWF += framesinfo.nwf_length;
×
377
        SendNWFDone(serverInfo);
×
378
    }
379

380
    // And go!
381
    framesinfo.lastTime = FramesInfo::UsedClock::now();
×
382
    state = ServerState::Game;
×
383
}
384

385
void GameServer::RunStateGame()
×
386
{
387
    if(!framesinfo.isPaused)
×
388
        ExecuteGameFrame();
×
389
}
×
390

391
void GameServer::Stop()
×
392
{
393
    if(state == ServerState::Stopped)
×
394
        return;
×
395

396
    // player verabschieden
397
    playerInfos.clear();
×
398
    networkPlayers.clear();
×
399

400
    // aufräumen
401
    framesinfo.Clear();
×
402
    config.Clear();
×
403
    mapinfo.Clear();
×
404
    countdown.Stop();
×
405

406
    // laden dicht machen
407
    serversocket.Close();
×
408
    // clear jump target
409
    skiptogf = 0;
×
410

411
    // clear async logs
412
    asyncLogs.clear();
×
413

414
    lanAnnouncer.Stop();
×
415

416
    if(LOBBYCLIENT.IsLoggedIn()) // steht die Lobbyverbindung noch?
×
417
        LOBBYCLIENT.DeleteServer();
×
418
    LOBBYCLIENT.RemoveListener(this);
×
419

420
    // status
421
    state = ServerState::Stopped;
×
422
    LOG.write("server state changed to stop\n");
×
423
}
424

425
// Check if there are players that have not been assigned a team but only a random team.
426
// Those players are assigned a team now optionally trying to balance the number of players per team.
427
// Returns true iff players have been assigned.
428
bool GameServer::assignPlayersOfRandomTeams(std::vector<JoinPlayerInfo>& playerInfos)
24✔
429
{
430
    static_assert(NUM_TEAMS == 4, "Expected exactly 4 playable teams!");
431
    RTTR_Assert(playerInfos.size() <= MAX_PLAYERS);
24✔
432

433
    using boost::container::static_vector;
434
    using PlayerIndex = unsigned;
435
    using TeamIndex = unsigned;
436
    const auto teamIdxToTeam = [](const TeamIndex teamNum) {
82✔
437
        RTTR_Assert(teamNum < NUM_TEAMS);
82✔
438
        return Team(static_cast<TeamIndex>(Team::Team1) + teamNum);
82✔
439
    };
440

441
    std::array<unsigned, NUM_TEAMS> numPlayersInTeam{};
24✔
442
    struct AssignPlayer
443
    {
444
        PlayerIndex player;
445
        static_vector<TeamIndex, NUM_TEAMS> possibleTeams;
446
        TeamIndex chosenTeam = 0;
447
    };
448

449
    static_vector<AssignPlayer, MAX_PLAYERS> playersToAssign;
×
450
    auto rng = helpers::getRandomGenerator();
24✔
451

452
    bool playerWasAssigned = false;
24✔
453

454
    // Assign (fully) random teams, count players in team and sort into playersToAssign
455
    for(PlayerIndex player = 0; player < playerInfos.size(); ++player)
161✔
456
    {
457
        auto& playerInfo = playerInfos[player];
137✔
458
        if(playerInfo.team == Team::Random)
137✔
459
        {
460
            const TeamIndex randTeam = std::uniform_int_distribution<TeamIndex>{0, NUM_TEAMS - 1u}(rng);
2✔
461
            playerInfo.team = teamIdxToTeam(randTeam);
2✔
462
            playerWasAssigned = true;
2✔
463
        }
464
        switch(playerInfo.team)
137✔
465
        {
466
            case Team::Team1: ++numPlayersInTeam[0]; break;
25✔
467
            case Team::Team2: ++numPlayersInTeam[1]; break;
8✔
468
            case Team::Team3: ++numPlayersInTeam[2]; break;
19✔
469
            case Team::Team4: ++numPlayersInTeam[3]; break;
3✔
470
            case Team::Random1To2: playersToAssign.emplace_back(AssignPlayer{player, {0, 1}}); break;
57✔
471
            case Team::Random1To3: playersToAssign.emplace_back(AssignPlayer{player, {0, 1, 2}}); break;
141✔
472
            case Team::Random1To4: playersToAssign.emplace_back(AssignPlayer{player, {0, 1, 2, 3}}); break;
42✔
473
            case Team::Random: RTTR_Assert(false); break;
×
474
            case Team::None: break;
2✔
475
        }
476
    }
477

478
    // To make the teams as even as possible we start to assign the most constrained players first
479
    std::sort(playersToAssign.begin(), playersToAssign.end(), [](const AssignPlayer& lhs, const AssignPlayer& rhs) {
24✔
480
        return lhs.possibleTeams.size() < rhs.possibleTeams.size();
372✔
481
    });
482

483
    // Put each player into a random team with the currently least amount of players using the possible teams only
484
    for(AssignPlayer& player : playersToAssign)
232✔
485
    {
486
        // Determine the teams with the minima size for the currently possible teams and choose one randomly
487
        unsigned minNextTeamSize = std::numeric_limits<unsigned>::max();
80✔
488
        static_vector<TeamIndex, NUM_TEAMS> teamsForNextPlayer;
×
489
        for(const TeamIndex team : player.possibleTeams)
710✔
490
        {
491
            if(minNextTeamSize > numPlayersInTeam[team])
235✔
492
            {
493
                teamsForNextPlayer.clear();
494
                teamsForNextPlayer.push_back(team);
495
                minNextTeamSize = numPlayersInTeam[team];
126✔
496
            } else if(minNextTeamSize == numPlayersInTeam[team])
109✔
497
                teamsForNextPlayer.push_back(team);
498
        }
499
        player.chosenTeam = helpers::getRandomElement(rng, teamsForNextPlayer);
80✔
500

501
        ++numPlayersInTeam[player.chosenTeam];
80✔
502
        playerWasAssigned = true;
80✔
503
    }
504
    // Now the teams are as even as possible and the uneven team(s) is a random one within the constraints
505
    // To have some more randomness we swap players within their constraints
506
    std::shuffle(playersToAssign.begin(), playersToAssign.end(), rng);
24✔
507
    for(auto it = playersToAssign.begin(); it != playersToAssign.end(); ++it)
184✔
508
    {
509
        // Search for a random player with which we can swap, including ourselfes
510
        // Go only forward to avoid back-swapping
511
        static_vector<decltype(it), MAX_PLAYERS> possibleSwapTargets;
×
512
        for(auto it2 = it; it2 != playersToAssign.end(); ++it2)
504✔
513
        {
514
            if(helpers::contains(it->possibleTeams, it2->chosenTeam)
424✔
515
               && helpers::contains(it2->possibleTeams, it->chosenTeam))
608✔
516
                possibleSwapTargets.push_back(it2);
517
        }
518
        const auto itSwapTarget = helpers::getRandomElement(rng, possibleSwapTargets);
80✔
519
        std::swap(it->chosenTeam, itSwapTarget->chosenTeam);
160✔
520
        playerInfos[it->player].team = teamIdxToTeam(it->chosenTeam);
160✔
521
    }
522

523
    return playerWasAssigned;
48✔
524
}
525

526
bool GameServer::StartGame()
×
527
{
528
    lanAnnouncer.Stop();
×
529

530
    // Finalize the team selection for unassigned players.
531
    if(assignPlayersOfRandomTeams(playerInfos))
×
532
        SendToAll(GameMessage_Player_List(playerInfos));
×
533

534
    // Bei Savegames wird der Startwert von den Clients aus der Datei gelesen!
535
    unsigned random_init;
536
    if(mapinfo.type == MapType::Savegame)
×
537
        random_init = 0;
×
538
    else
539
        random_init = static_cast<unsigned>(std::chrono::high_resolution_clock::now().time_since_epoch().count());
×
540

541
    nwfInfo.init(currentGF, 3);
×
542

543
    // Send start first, then load the rest
544
    SendToAll(GameMessage_Server_Start(random_init, nwfInfo.getNextNWF(), nwfInfo.getCmdDelay()));
×
545
    LOG.writeToFile("SERVER >>> BROADCAST: NMS_SERVER_START(%d)\n") % random_init;
×
546

547
    // Höchsten Ping ermitteln
548
    unsigned highest_ping = 0;
×
549
    for(const JoinPlayerInfo& player : playerInfos)
×
550
    {
551
        if(player.ps == PlayerState::Occupied)
×
552
        {
553
            if(player.ping > highest_ping)
×
554
                highest_ping = player.ping;
×
555
        }
556
    }
557

558
    framesinfo.gfLengthReq = framesinfo.gf_length = SPEED_GF_LENGTHS[ggs_.speed];
×
559

560
    // NetworkFrame-Länge bestimmen, je schlechter (also höher) die Pings, desto länger auch die Framelänge
561
    framesinfo.nwf_length = CalcNWFLenght(FramesInfo::milliseconds32_t(highest_ping));
×
562

563
    LOG.write("SERVER: Using gameframe length of %1%\n") % helpers::withUnit(framesinfo.gf_length);
×
564
    LOG.write("SERVER: Using networkframe length of %1% GFs (%2%)\n") % framesinfo.nwf_length
×
565
      % helpers::withUnit(framesinfo.nwf_length * framesinfo.gf_length);
×
566

567
    for(unsigned id = 0; id < playerInfos.size(); id++)
×
568
    {
569
        if(playerInfos[id].isUsed())
×
570
            nwfInfo.addPlayer(id);
×
571
    }
572

573
    // Add server info so nwfInfo can be ready but do NOT send it yet, as we wait for the player commands before sending
574
    // the done msg
575
    nwfInfo.addServerInfo(NWFServerInfo(currentGF, framesinfo.gf_length / FramesInfo::milliseconds32_t(1),
×
576
                                        currentGF + framesinfo.nwf_length));
×
577

578
    state = ServerState::Loading;
×
579
    loadStartTime = SteadyClock::now();
×
580

581
    return true;
×
582
}
583

584
unsigned GameServer::CalcNWFLenght(FramesInfo::milliseconds32_t minDuration) const
×
585
{
586
    constexpr unsigned maxNumGF = 20;
×
587
    for(unsigned i = 1; i < maxNumGF; ++i)
×
588
    {
589
        if(i * framesinfo.gf_length >= minDuration)
×
590
            return i;
×
591
    }
592
    return maxNumGF;
×
593
}
594

595
void GameServer::SendNWFDone(const NWFServerInfo& info)
×
596
{
597
    nwfInfo.addServerInfo(info);
×
598
    SendToAll(GameMessage_Server_NWFDone(info.gf, info.newGFLen, info.nextNWF));
×
599
}
×
600

601
void GameServer::SendToAll(const GameMessage& msg)
×
602
{
603
    for(GameServerPlayer& player : networkPlayers)
×
604
    {
605
        // ist der Slot Belegt, dann Nachricht senden
606
        if(player.isActive())
×
607
            player.sendMsgAsync(msg.clone());
×
608
    }
609
}
×
610

611
void GameServer::KickPlayer(uint8_t playerId, KickReason cause, uint32_t param)
×
612
{
613
    if(playerId >= playerInfos.size())
×
614
        return;
×
615
    JoinPlayerInfo& playerInfo = playerInfos[playerId];
×
616
    GameServerPlayer* player = GetNetworkPlayer(playerId);
×
617
    if(player)
×
618
        player->closeConnection();
×
619
    // Non-existing or connecting player
620
    if(!playerInfo.isUsed())
×
621
        return;
×
622
    playerInfo.ps = PlayerState::Free;
×
623

624
    SendToAll(GameMessage_Player_Kicked(playerId, cause, param));
×
625

626
    // If we are ingame, replace by KI
627
    if(state == ServerState::Game || state == ServerState::Loading)
×
628
    {
629
        playerInfo.ps = PlayerState::AI;
×
630
        playerInfo.aiInfo = AI::Info(AI::Type::Dummy);
×
631
    } else
632
        CancelCountdown();
×
633

634
    AnnounceStatusChange();
×
635
    LOG.writeToFile("SERVER >>> BROADCAST: NMS_PLAYERKICKED(%d,%d,%d)\n") % unsigned(playerId) % unsigned(cause)
×
636
      % unsigned(param);
×
637
}
638

639
// testet, ob in der Verbindungswarteschlange Clients auf Verbindung warten
640
void GameServer::ClientWatchDog()
×
641
{
642
    SocketSet set;
×
643
    set.Clear();
×
644

645
    // sockets zum set hinzufügen
646
    for(GameServerPlayer& player : networkPlayers)
×
647
        set.Add(player.socket);
×
648

649
    // auf fehler prüfen
650
    if(set.Select(0, 2) > 0)
×
651
    {
652
        for(const GameServerPlayer& player : networkPlayers)
×
653
        {
654
            if(set.InSet(player.socket))
×
655
            {
656
                LOG.write(_("SERVER: Error on socket of player %1%, bye bye!\n")) % player.playerId;
×
657
                KickPlayer(player.playerId, KickReason::ConnectionLost, __LINE__);
×
658
            }
659
        }
660
    }
661

662
    for(GameServerPlayer& player : networkPlayers)
×
663
    {
664
        if(player.hasTimedOut())
×
665
        {
666
            LOG.write(_("SERVER: Reserved slot %1% freed due to timeout\n")) % player.playerId;
×
667
            KickPlayer(player.playerId, KickReason::PingTimeout, __LINE__);
×
668
        } else
669
            player.doPing();
×
670
    }
671
}
×
672

673
void GameServer::ExecuteGameFrame()
×
674
{
675
    RTTR_Assert(state == ServerState::Game);
×
676

677
    FramesInfo::UsedClock::time_point currentTime = FramesInfo::UsedClock::now();
×
678
    FramesInfo::milliseconds32_t passedTime =
679
      std::chrono::duration_cast<FramesInfo::milliseconds32_t>(currentTime - framesinfo.lastTime);
×
680

681
    // prüfen ob GF vergangen
682
    if(passedTime >= framesinfo.gf_length || skiptogf > currentGF)
×
683
    {
684
        // NWF vergangen?
685
        if(currentGF == nwfInfo.getNextNWF())
×
686
        {
687
            if(CheckForLaggingPlayers())
×
688
            {
689
                // Check for kicking every second
690
                static FramesInfo::UsedClock::time_point lastLagKickTime;
691
                if(currentTime - lastLagKickTime >= std::chrono::seconds(1))
×
692
                {
693
                    lastLagKickTime = currentTime;
×
694
                    CheckAndKickLaggingPlayers();
×
695
                }
696
                // Skip the rest
697
                return;
×
698
            } else
699
                ExecuteNWF();
×
700
        }
701
        // Advance GF
702
        ++currentGF;
×
703
        // Normally we set lastTime = curTime (== lastTime + passedTime) where passedTime is ideally 1 GF
704
        // But we might got called late, so we advance the time by 1 GF anyway so in that case we execute the next GF a
705
        // bit earlier. Exception: We lag many GFs behind, then we advance by the full passedTime - 1 GF which means we
706
        // are now only 1 GF behind and execute that on the next call
707
        if(passedTime <= 4 * framesinfo.gf_length)
×
708
            passedTime = framesinfo.gf_length;
×
709
        else
710
            passedTime -= framesinfo.gf_length;
×
711
        framesinfo.lastTime += passedTime;
×
712
    }
713
}
714

715
void GameServer::ExecuteNWF()
×
716
{
717
    // Check for asyncs
718
    if(CheckForAsync())
×
719
    {
720
        // Pause game
721
        RTTR_Assert(!framesinfo.isPaused);
×
722
        SetPaused(true);
×
723

724
        // Notify players
725
        std::vector<unsigned> checksumHashes;
×
726
        checksumHashes.reserve(networkPlayers.size());
×
727
        for(const GameServerPlayer& player : networkPlayers)
×
728
            checksumHashes.push_back(nwfInfo.getPlayerCmds(player.playerId).checksum.getHash());
×
729
        SendToAll(GameMessage_Server_Async(checksumHashes));
×
730

731
        // Request async logs
732
        for(GameServerPlayer& player : networkPlayers)
×
733
        {
734
            asyncLogs.push_back(AsyncLog(player.playerId, nwfInfo.getPlayerCmds(player.playerId).checksum));
×
735
            player.sendMsgAsync(new GameMessage_GetAsyncLog());
×
736
        }
737
    }
738
    const NWFServerInfo serverInfo = nwfInfo.getServerInfo();
×
739
    RTTR_Assert(serverInfo.gf == currentGF);
×
740
    RTTR_Assert(serverInfo.nextNWF > currentGF);
×
741
    // First save old values
742
    unsigned lastNWF = nwfInfo.getLastNWF();
×
743
    FramesInfo::milliseconds32_t oldGFLen = framesinfo.gf_length;
×
744
    nwfInfo.execute(framesinfo);
×
745
    if(oldGFLen != framesinfo.gf_length)
×
746
    {
747
        LOG.write(_("SERVER: At GF %1%: Speed changed from %2% to %3%. NWF %4%\n")) % currentGF
×
748
          % helpers::withUnit(oldGFLen) % helpers::withUnit(framesinfo.gf_length) % framesinfo.nwf_length;
×
749
    }
750
    NWFServerInfo newInfo(lastNWF, framesinfo.gfLengthReq / FramesInfo::milliseconds32_t(1),
×
751
                          lastNWF + framesinfo.nwf_length);
×
752
    if(framesinfo.gfLengthReq != framesinfo.gf_length)
×
753
    {
754
        // Speed will change, adjust nwf length so the time will stay constant
755
        using namespace std::chrono;
756
        using MsDouble = duration<double, std::milli>;
757
        double newNWFLen =
758
          framesinfo.nwf_length * framesinfo.gf_length / duration_cast<MsDouble>(framesinfo.gfLengthReq);
×
759
        newInfo.nextNWF = lastNWF + std::max(1u, helpers::iround<unsigned>(newNWFLen));
×
760
    }
761
    SendNWFDone(newInfo);
×
762
}
×
763

764
bool GameServer::CheckForAsync()
×
765
{
766
    if(networkPlayers.empty())
×
767
        return false;
×
768
    bool isAsync = false;
×
769
    const AsyncChecksum& refChecksum = nwfInfo.getPlayerCmds(networkPlayers.front().playerId).checksum;
×
770
    for(const GameServerPlayer& player : networkPlayers)
×
771
    {
772
        const AsyncChecksum& curChecksum = nwfInfo.getPlayerCmds(player.playerId).checksum;
×
773

774
        // Checksummen nicht gleich?
775
        if(curChecksum != refChecksum)
×
776
        {
777
            LOG.write(_("Async at GF %1% of player %2% vs %3%. Checksums:\n%4%\n%5%\n\n")) % currentGF % player.playerId
×
778
              % networkPlayers.front().playerId % curChecksum % refChecksum;
×
779
            isAsync = true;
×
780
        }
781
    }
782
    return isAsync;
×
783
}
784

785
void GameServer::CheckAndKickLaggingPlayers()
×
786
{
787
    for(const GameServerPlayer& player : networkPlayers)
×
788
    {
789
        const unsigned timeOut = player.getLagTimeOut();
×
790
        if(timeOut == 0)
×
791
            KickPlayer(player.playerId, KickReason::PingTimeout, __LINE__);
×
792
        else if(timeOut <= 30
×
793
                && (timeOut % 5 == 0
×
794
                    || timeOut < 5)) // Notify every 5s if max 30s are remaining, if less than 5s notify every second
×
795
            LOG.write(_("SERVER: Kicking player %1% in %2% seconds\n")) % player.playerId % timeOut;
×
796
    }
797
}
×
798

799
bool GameServer::CheckForLaggingPlayers()
×
800
{
801
    if(nwfInfo.isReady())
×
802
        return false;
×
803
    for(GameServerPlayer& player : networkPlayers)
×
804
    {
805
        if(nwfInfo.getPlayerInfo(player.playerId).isLagging)
×
806
            player.setLagging();
×
807
    }
808
    return true;
×
809
}
810

811
// testet, ob in der Verbindungswarteschlange Clients auf Verbindung warten
812
void GameServer::WaitForClients()
×
813
{
814
    SocketSet set;
×
815

816
    set.Add(serversocket);
×
817
    if(set.Select(0, 0) > 0)
×
818
    {
819
        RTTR_Assert(set.InSet(serversocket));
×
820
        Socket socket = serversocket.Accept();
×
821

822
        // Verbindung annehmen
823
        if(!socket.isValid())
×
824
            return;
×
825

826
        unsigned newPlayerId = GameMessageWithPlayer::NO_PLAYER_ID;
×
827
        // Geeigneten Platz suchen
828
        for(unsigned playerId = 0; playerId < playerInfos.size(); ++playerId)
×
829
        {
830
            if(playerInfos[playerId].ps == PlayerState::Free && !GetNetworkPlayer(playerId))
×
831
            {
832
                networkPlayers.push_back(GameServerPlayer(playerId, socket));
×
833
                newPlayerId = playerId;
×
834
                break;
×
835
            }
836
        }
837

838
        GameMessage_Player_Id msg(newPlayerId);
×
839
        MessageHandler::send(socket, msg);
×
840

841
        // war kein platz mehr frei, wenn ja dann verbindung trennen?
842
        if(newPlayerId == 0xFFFFFFFF)
×
843
            socket.Close();
×
844
    }
845
}
846

847
// füllt die warteschlangen mit "paketen"
848
void GameServer::FillPlayerQueues()
×
849
{
850
    SocketSet set;
×
851
    bool msgReceived = false;
×
852

853
    // erstmal auf Daten überprüfen
854
    do
×
855
    {
856
        // sockets zum set hinzufügen
857
        for(const GameServerPlayer& player : networkPlayers)
×
858
            set.Add(player.socket);
×
859

860
        msgReceived = false;
×
861

862
        // ist eines der Sockets im Set lesbar?
863
        if(set.Select(0, 0) > 0)
×
864
        {
865
            for(GameServerPlayer& player : networkPlayers)
×
866
            {
867
                if(set.InSet(player.socket))
×
868
                {
869
                    // nachricht empfangen
870
                    if(!player.receiveMsgs())
×
871
                    {
872
                        LOG.write(_("SERVER: Receiving Message for player %1% failed, kicking...\n")) % player.playerId;
×
873
                        KickPlayer(player.playerId, KickReason::ConnectionLost, __LINE__);
×
874
                    } else
875
                        msgReceived = true;
×
876
                }
877
            }
878
        }
879
    } while(msgReceived);
880
}
×
881

882
bool GameServer::OnGameMessage(const GameMessage_Pong& msg)
×
883
{
884
    GameServerPlayer* player = GetNetworkPlayer(msg.senderPlayerID);
×
885
    if(player)
×
886
    {
887
        unsigned ping = player->calcPingTime();
×
888
        if(ping == 0u)
×
889
            return true;
×
890
        playerInfos[msg.senderPlayerID].ping = ping;
×
891
        SendToAll(GameMessage_Player_Ping(msg.senderPlayerID, ping));
×
892
    }
893
    return true;
×
894
}
895

896
bool GameServer::OnGameMessage(const GameMessage_Server_Type& msg)
×
897
{
898
    if(state != ServerState::Config)
×
899
    {
900
        KickPlayer(msg.senderPlayerID, KickReason::InvalidMsg, __LINE__);
×
901
        return true;
×
902
    }
903

904
    GameServerPlayer* player = GetNetworkPlayer(msg.senderPlayerID);
×
905
    if(!player)
×
906
        return true;
×
907

908
    auto typeok = GameMessage_Server_TypeOK::StatusCode::Ok;
×
909
    if(msg.type != config.servertype)
×
910
        typeok = GameMessage_Server_TypeOK::StatusCode::InvalidServerType;
×
911
    else if(msg.revision != rttr::version::GetRevision())
×
912
        typeok = GameMessage_Server_TypeOK::StatusCode::WrongVersion;
×
913

914
    player->sendMsg(GameMessage_Server_TypeOK(typeok, rttr::version::GetRevision()));
×
915

916
    if(typeok != GameMessage_Server_TypeOK::StatusCode::Ok)
×
917
        KickPlayer(msg.senderPlayerID, KickReason::ConnectionLost, __LINE__);
×
918
    return true;
×
919
}
920

921
bool GameServer::OnGameMessage(const GameMessage_Server_Password& msg)
×
922
{
923
    if(state != ServerState::Config)
×
924
    {
925
        KickPlayer(msg.senderPlayerID, KickReason::InvalidMsg, __LINE__);
×
926
        return true;
×
927
    }
928

929
    GameServerPlayer* player = GetNetworkPlayer(msg.senderPlayerID);
×
930
    if(!player)
×
931
        return true;
×
932

933
    std::string passwordok = (config.password == msg.password ? "true" : "false");
×
934
    if(msg.password == config.hostPassword)
×
935
    {
936
        passwordok = "true";
×
937
        playerInfos[msg.senderPlayerID].isHost = true;
×
938
    } else
939
        playerInfos[msg.senderPlayerID].isHost = false;
×
940

941
    player->sendMsgAsync(new GameMessage_Server_Password(passwordok));
×
942

943
    if(passwordok == "false")
×
944
        KickPlayer(msg.senderPlayerID, KickReason::WrongPassword, __LINE__);
×
945
    return true;
×
946
}
947

948
bool GameServer::OnGameMessage(const GameMessage_Chat& msg)
×
949
{
950
    int playerID = GetTargetPlayer(msg);
×
951
    if(playerID >= 0)
×
952
        SendToAll(GameMessage_Chat(playerID, msg.destination, msg.text));
×
953
    return true;
×
954
}
955

956
bool GameServer::OnGameMessage(const GameMessage_Player_State& msg)
×
957
{
958
    // `Occupied` is set only when a real player went through the join protocol
959
    // See `GameMessage_Map_Checksum` handling
NEW
960
    if(state != ServerState::Config || msg.ps == PlayerState::Occupied)
×
961
    {
962
        KickPlayer(msg.senderPlayerID, KickReason::InvalidMsg, __LINE__);
×
963
        return true;
×
964
    }
965

966
    int playerID = GetTargetPlayer(msg);
×
967
    if(playerID < 0)
×
968
        return true;
×
969
    JoinPlayerInfo& player = playerInfos[playerID];
×
970
    const PlayerState oldPs = player.ps;
×
UNCOV
971
    if(playerID != msg.senderPlayerID)
×
972
    {
973
        // Remove the player currently occupying the target slot, if any
974
        if(GetNetworkPlayer(playerID))
×
975
            KickPlayer(playerID, KickReason::NoCause, __LINE__);
×
976

977
        if(mapinfo.type == MapType::Savegame)
×
978
        {
979
            // For savegames we cannot set anyone on a locked slot as the player does not exist on the map
980
            if(player.ps != PlayerState::Locked)
×
981
            {
982
                // And we don't lock!
983
                player.ps = msg.ps == PlayerState::Locked ? PlayerState::Free : msg.ps;
×
984
                player.aiInfo = msg.aiInfo;
×
985
            }
986
        } else
987
        {
988
            player.ps = msg.ps;
×
989
            player.aiInfo = msg.aiInfo;
×
990
        }
991
        // Free slots on single player games always get occupied by AI
UNCOV
992
        if(player.ps == PlayerState::Free && config.servertype == ServerType::Local)
×
993
        {
994
            player.ps = PlayerState::AI;
×
995
            player.aiInfo = AI::Info(AI::Type::Default);
×
996
        }
997
    }
998
    // Even when nothing changed we send the data because the other players might have expected a change
999

1000
    if(player.ps == PlayerState::AI)
×
1001
    {
1002
        player.SetAIName(playerID);
×
1003
        SendToAll(GameMessage_Player_Name(playerID, player.name));
×
1004
    }
1005
    // If slot is filled, check current color
1006
    if(player.isUsed())
×
1007
    {
1008
        // Ensure a unique color only when initially filling the slot
NEW
1009
        CheckAndSetColor(playerID, player.color, oldPs == PlayerState::Free || oldPs == PlayerState::Locked);
×
1010
    }
UNCOV
1011
    SendToAll(GameMessage_Player_State(playerID, player.ps, player.aiInfo));
×
1012

1013
    if(oldPs != player.ps)
×
1014
        player.isReady = (player.ps == PlayerState::AI);
×
1015
    SendToAll(GameMessage_Player_Ready(playerID, player.isReady));
×
1016
    PlayerDataChanged(playerID);
×
1017
    AnnounceStatusChange();
×
1018
    return true;
×
1019
}
1020

1021
bool GameServer::OnGameMessage(const GameMessage_Player_Name& msg)
×
1022
{
1023
    if(state != ServerState::Config)
×
1024
    {
1025
        KickPlayer(msg.senderPlayerID, KickReason::InvalidMsg, __LINE__);
×
1026
        return true;
×
1027
    }
1028
    int playerID = GetTargetPlayer(msg);
×
1029
    if(playerID < 0)
×
1030
        return true;
×
1031

1032
    LOG.writeToFile("CLIENT%d >>> SERVER: NMS_PLAYER_NAME(%s)\n") % playerID % msg.playername;
×
1033

1034
    playerInfos[playerID].name = msg.playername;
×
1035
    SendToAll(GameMessage_Player_Name(playerID, msg.playername));
×
1036
    PlayerDataChanged(playerID);
×
1037

1038
    return true;
×
1039
}
1040

1041
bool GameServer::OnGameMessage(const GameMessage_Player_Portrait& msg)
×
1042
{
1043
    if(state != ServerState::Config)
×
1044
    {
1045
        KickPlayer(msg.senderPlayerID, KickReason::InvalidMsg, __LINE__);
×
1046
        return true;
×
1047
    }
1048
    int playerID = GetTargetPlayer(msg);
×
1049
    if(playerID < 0)
×
1050
        return true;
×
1051
    if(msg.playerPortraitIndex >= Portraits.size())
×
1052
        return true;
×
1053

1054
    LOG.writeToFile("CLIENT%d >>> SERVER: NMS_PLAYER_PORTRAIT(%u)\n") % playerID % msg.playerPortraitIndex;
×
1055
    playerInfos[playerID].portraitIndex = msg.playerPortraitIndex;
×
1056
    SendToAll(GameMessage_Player_Portrait(playerID, msg.playerPortraitIndex));
×
1057
    PlayerDataChanged(playerID);
×
1058

1059
    return true;
×
1060
}
1061

1062
bool GameServer::OnGameMessage(const GameMessage_Player_Nation& msg)
×
1063
{
1064
    if(state != ServerState::Config)
×
1065
    {
1066
        KickPlayer(msg.senderPlayerID, KickReason::InvalidMsg, __LINE__);
×
1067
        return true;
×
1068
    }
1069
    int playerID = GetTargetPlayer(msg);
×
1070
    if(playerID < 0)
×
1071
        return true;
×
1072

1073
    playerInfos[playerID].nation = msg.nation;
×
1074

1075
    SendToAll(GameMessage_Player_Nation(playerID, msg.nation));
×
1076
    PlayerDataChanged(playerID);
×
1077
    return true;
×
1078
}
1079

1080
bool GameServer::OnGameMessage(const GameMessage_Player_Team& msg)
×
1081
{
1082
    if(state != ServerState::Config)
×
1083
    {
1084
        KickPlayer(msg.senderPlayerID, KickReason::InvalidMsg, __LINE__);
×
1085
        return true;
×
1086
    }
1087
    int playerID = GetTargetPlayer(msg);
×
1088
    if(playerID < 0)
×
1089
        return true;
×
1090

1091
    playerInfos[playerID].team = msg.team;
×
1092

1093
    SendToAll(GameMessage_Player_Team(playerID, msg.team));
×
1094
    PlayerDataChanged(playerID);
×
1095
    return true;
×
1096
}
1097

1098
bool GameServer::OnGameMessage(const GameMessage_Player_Color& msg)
×
1099
{
1100
    if(state != ServerState::Config)
×
1101
    {
1102
        KickPlayer(msg.senderPlayerID, KickReason::InvalidMsg, __LINE__);
×
1103
        return true;
×
1104
    }
1105
    int playerID = GetTargetPlayer(msg);
×
1106
    if(playerID < 0)
×
1107
        return true;
×
1108

NEW
1109
    CheckAndSetColor(playerID, msg.color, false);
×
1110
    PlayerDataChanged(playerID);
×
1111
    return true;
×
1112
}
1113

1114
bool GameServer::OnGameMessage(const GameMessage_Player_Ready& msg)
×
1115
{
1116
    if(state != ServerState::Config)
×
1117
    {
1118
        KickPlayer(msg.senderPlayerID, KickReason::InvalidMsg, __LINE__);
×
1119
        return true;
×
1120
    }
1121
    int playerID = GetTargetPlayer(msg);
×
1122
    if(playerID < 0)
×
1123
        return true;
×
1124

1125
    JoinPlayerInfo& player = playerInfos[playerID];
×
1126

1127
    player.isReady = msg.ready;
×
1128

1129
    // countdown ggf abbrechen
1130
    if(!player.isReady && countdown.IsActive())
×
1131
        CancelCountdown();
×
1132

1133
    SendToAll(GameMessage_Player_Ready(playerID, msg.ready));
×
1134
    return true;
×
1135
}
1136

1137
bool GameServer::OnGameMessage(const GameMessage_MapRequest& msg)
×
1138
{
1139
    if(state != ServerState::Config)
×
1140
    {
1141
        KickPlayer(msg.senderPlayerID, KickReason::InvalidMsg, __LINE__);
×
1142
        return true;
×
1143
    }
1144
    GameServerPlayer* player = GetNetworkPlayer(msg.senderPlayerID);
×
1145
    if(!player)
×
1146
        return true;
×
1147

1148
    if(msg.requestInfo)
×
1149
    {
1150
        player->sendMsgAsync(new GameMessage_Map_Info(mapinfo.filepath.filename().string(), mapinfo.type,
×
1151
                                                      mapinfo.mapData.uncompressedLength, mapinfo.mapData.data.size(),
×
1152
                                                      mapinfo.luaData.uncompressedLength, mapinfo.luaData.data.size()));
×
1153
    } else if(player->isMapSending())
×
1154
    {
1155
        // Don't send again
1156
        KickPlayer(msg.senderPlayerID, KickReason::InvalidMsg, __LINE__);
×
1157
    } else
1158
    {
1159
        // Send map data
1160
        unsigned curPos = 0;
×
1161
        unsigned remainingSize = mapinfo.mapData.data.size();
×
1162
        while(remainingSize)
×
1163
        {
1164
            unsigned chunkSize = std::min(MAP_PART_SIZE, remainingSize);
×
1165

1166
            player->sendMsgAsync(new GameMessage_Map_Data(true, curPos, &mapinfo.mapData.data[curPos], chunkSize));
×
1167
            curPos += chunkSize;
×
1168
            remainingSize -= chunkSize;
×
1169
        }
1170

1171
        // And lua data (if there is any)
1172
        RTTR_Assert(mapinfo.luaFilepath.empty() == mapinfo.luaData.data.empty());
×
1173
        RTTR_Assert(mapinfo.luaData.data.empty() == (mapinfo.luaData.uncompressedLength == 0));
×
1174
        curPos = 0;
×
1175
        remainingSize = mapinfo.luaData.data.size();
×
1176
        while(remainingSize)
×
1177
        {
1178
            unsigned chunkSize = std::min(MAP_PART_SIZE, remainingSize);
×
1179

1180
            player->sendMsgAsync(new GameMessage_Map_Data(false, curPos, &mapinfo.luaData.data[curPos], chunkSize));
×
1181
            curPos += chunkSize;
×
1182
            remainingSize -= chunkSize;
×
1183
        }
1184
        // estimate time. max 60 chunks/s (currently limited by framerate), assume 50 (~25kb/s)
1185
        auto numChunks = (mapinfo.mapData.data.size() + mapinfo.luaData.data.size()) / MAP_PART_SIZE;
×
1186
        player->setMapSending(std::chrono::seconds(numChunks / 50 + 1));
×
1187
    }
1188
    return true;
×
1189
}
1190

1191
bool GameServer::OnGameMessage(const GameMessage_Map_Checksum& msg)
×
1192
{
1193
    if(state != ServerState::Config)
×
1194
    {
1195
        KickPlayer(msg.senderPlayerID, KickReason::InvalidMsg, __LINE__);
×
1196
        return true;
×
1197
    }
1198
    GameServerPlayer* player = GetNetworkPlayer(msg.senderPlayerID);
×
1199
    if(!player)
×
1200
        return true;
×
1201

1202
    bool checksumok = (msg.mapChecksum == mapinfo.mapChecksum && msg.luaChecksum == mapinfo.luaChecksum);
×
1203

1204
    LOG.writeToFile("CLIENT%d >>> SERVER: NMS_MAP_CHECKSUM(%u) expected: %u, ok: %s\n") % unsigned(msg.senderPlayerID)
×
1205
      % msg.mapChecksum % mapinfo.mapChecksum % (checksumok ? "yes" : "no");
×
1206

1207
    // Send response. If map data was not sent yet, the client may retry
1208
    player->sendMsgAsync(new GameMessage_Map_ChecksumOK(checksumok, !player->isMapSending()));
×
1209

1210
    LOG.writeToFile("SERVER >>> CLIENT%d: NMS_MAP_CHECKSUM(%d)\n") % unsigned(msg.senderPlayerID) % checksumok;
×
1211

1212
    if(!checksumok)
×
1213
    {
1214
        if(player->isMapSending())
×
1215
            KickPlayer(msg.senderPlayerID, KickReason::WrongChecksum, __LINE__);
×
1216
    } else
1217
    {
1218
        JoinPlayerInfo& playerInfo = playerInfos[msg.senderPlayerID];
×
1219
        // Used? Then we got this twice or some error happened. Remove him
1220
        if(playerInfo.isUsed())
×
1221
            KickPlayer(msg.senderPlayerID, KickReason::InvalidMsg, __LINE__);
×
1222
        else
1223
        {
1224
            // Inform others about a new player
1225
            SendToAll(GameMessage_Player_New(msg.senderPlayerID, playerInfo.name));
×
1226

1227
            LOG.writeToFile("SERVER >>> BROADCAST: NMS_PLAYER_NEW(%d, %s)\n") % unsigned(msg.senderPlayerID)
×
1228
              % playerInfo.name;
×
1229

1230
            // Mark as used and assign a unique color
1231
            // Do this before sending the player list to avoid sending useless updates
1232
            playerInfo.ps = PlayerState::Occupied;
×
NEW
1233
            CheckAndSetColor(msg.senderPlayerID, playerInfo.color, true);
×
1234

1235
            // Send remaining data and mark as active
1236
            player->sendMsgAsync(new GameMessage_Server_Name(config.gamename));
×
1237
            player->sendMsgAsync(new GameMessage_Player_List(playerInfos));
×
1238
            player->sendMsgAsync(new GameMessage_GGSChange(ggs_));
×
1239
            player->setActive();
×
1240
        }
1241
        AnnounceStatusChange();
×
1242
    }
1243
    return true;
×
1244
}
1245

1246
// speed change message
1247
bool GameServer::OnGameMessage(const GameMessage_Speed& msg)
×
1248
{
1249
    if(state != ServerState::Game)
×
1250
    {
1251
        KickPlayer(msg.senderPlayerID, KickReason::InvalidMsg, __LINE__);
×
1252
        return true;
×
1253
    }
1254
    framesinfo.gfLengthReq = FramesInfo::milliseconds32_t(msg.gf_length);
×
1255
    return true;
×
1256
}
1257

1258
bool GameServer::OnGameMessage(const GameMessage_GameCommand& msg)
×
1259
{
1260
    int targetPlayerId = GetTargetPlayer(msg);
×
1261
    if((state != ServerState::Game && state != ServerState::Loading) || targetPlayerId < 0
×
1262
       || (state == ServerState::Loading && !msg.cmds.gcs.empty()))
×
1263
    {
1264
        KickPlayer(msg.senderPlayerID, KickReason::InvalidMsg, __LINE__);
×
1265
        return true;
×
1266
    }
1267

1268
    if(!nwfInfo.addPlayerCmds(targetPlayerId, msg.cmds))
×
1269
        return true; // Ignore
×
1270
    GameServerPlayer* player = GetNetworkPlayer(targetPlayerId);
×
1271
    if(player)
×
1272
        player->setNotLagging();
×
1273
    SendToAll(GameMessage_GameCommand(targetPlayerId, msg.cmds.checksum, msg.cmds.gcs));
×
1274

1275
    return true;
×
1276
}
1277

1278
bool GameServer::OnGameMessage(const GameMessage_AsyncLog& msg)
×
1279
{
1280
    if(state != ServerState::Game)
×
1281
    {
1282
        KickPlayer(msg.senderPlayerID, KickReason::InvalidMsg, __LINE__);
×
1283
        return true;
×
1284
    }
1285
    bool foundPlayer = false;
×
1286
    for(AsyncLog& log : asyncLogs)
×
1287
    {
1288
        if(log.playerId != msg.senderPlayerID)
×
1289
            continue;
×
1290
        RTTR_Assert(!log.done);
×
1291
        if(log.done)
×
1292
            return true;
×
1293
        foundPlayer = true;
×
1294
        log.addData += msg.addData;
×
1295
        log.randEntries.insert(log.randEntries.end(), msg.entries.begin(), msg.entries.end());
×
1296
        if(msg.last)
×
1297
        {
1298
            LOG.write(_("Received async logs from %1% (%2% entries).\n")) % unsigned(log.playerId)
×
1299
              % log.randEntries.size();
×
1300
            log.done = true;
×
1301
        }
1302
    }
1303
    if(!foundPlayer)
×
1304
    {
1305
        LOG.write(_("Received async log from %1%, but did not expect it!\n")) % unsigned(msg.senderPlayerID);
×
1306
        return true;
×
1307
    }
1308

1309
    // Check if we have all logs
1310
    for(const AsyncLog& log : asyncLogs)
×
1311
    {
1312
        if(!log.done)
×
1313
            return true;
×
1314
    }
1315

1316
    LOG.write(_("Async logs received completely.\n"));
×
1317

1318
    const bfs::path asyncFilePath = SaveAsyncLog();
×
1319
    if(!asyncFilePath.empty())
×
1320
        SendAsyncLog(asyncFilePath);
×
1321

1322
    // Kick all players that have a different checksum from the host
1323
    AsyncChecksum hostChecksum;
×
1324
    for(const AsyncLog& log : asyncLogs)
×
1325
    {
1326
        if(playerInfos.at(log.playerId).isHost)
×
1327
        {
1328
            hostChecksum = log.checksum;
×
1329
            break;
×
1330
        }
1331
    }
1332
    for(const AsyncLog& log : asyncLogs)
×
1333
    {
1334
        if(log.checksum != hostChecksum)
×
1335
            KickPlayer(log.playerId, KickReason::Async, __LINE__);
×
1336
    }
1337
    return true;
×
1338
}
1339

1340
bool GameServer::OnGameMessage(const GameMessage_RemoveLua& msg)
×
1341
{
1342
    if(state != ServerState::Config || !IsHost(msg.senderPlayerID))
×
1343
    {
1344
        KickPlayer(msg.senderPlayerID, KickReason::InvalidMsg, __LINE__);
×
1345
        return true;
×
1346
    }
1347
    mapinfo.luaFilepath.clear();
×
1348
    mapinfo.luaData.Clear();
×
1349
    mapinfo.luaChecksum = 0;
×
1350
    SendToAll(msg);
×
1351
    CancelCountdown();
×
1352
    return true;
×
1353
}
1354

1355
bool GameServer::OnGameMessage(const GameMessage_Countdown& msg)
×
1356
{
1357
    if(state != ServerState::Config || !IsHost(msg.senderPlayerID))
×
1358
    {
1359
        KickPlayer(msg.senderPlayerID, KickReason::InvalidMsg, __LINE__);
×
1360
        return true;
×
1361
    }
1362

1363
    NetworkPlayer* nwPlayer = GetNetworkPlayer(msg.senderPlayerID);
×
1364
    if(!nwPlayer || countdown.IsActive())
×
1365
        return true;
×
1366

1367
    if(!ArePlayersReady())
×
1368
        nwPlayer->sendMsgAsync(new GameMessage_CancelCountdown(true));
×
1369
    else
1370
    {
1371
        // Just to make sure update all player infos
1372
        SendToAll(GameMessage_Player_List(playerInfos));
×
1373
        // Start countdown (except its single player)
1374
        if(networkPlayers.size() > 1)
×
1375
        {
1376
            countdown.Start(msg.countdown);
×
1377
            SendToAll(GameMessage_Countdown(countdown.GetRemainingSecs()));
×
1378
            LOG.writeToFile("SERVER >>> Countdown started(%d)\n") % countdown.GetRemainingSecs();
×
1379
        } else if(!StartGame())
×
1380
            Stop();
×
1381
    }
1382

1383
    return true;
×
1384
}
1385

1386
bool GameServer::OnGameMessage(const GameMessage_CancelCountdown& msg)
×
1387
{
1388
    if(state != ServerState::Config || !IsHost(msg.senderPlayerID))
×
1389
    {
1390
        KickPlayer(msg.senderPlayerID, KickReason::InvalidMsg, __LINE__);
×
1391
        return true;
×
1392
    }
1393

1394
    CancelCountdown();
×
1395
    return true;
×
1396
}
1397

1398
bool GameServer::OnGameMessage(const GameMessage_Pause& msg)
×
1399
{
1400
    if(IsHost(msg.senderPlayerID))
×
1401
        SetPaused(msg.paused);
×
1402
    return true;
×
1403
}
1404

1405
bool GameServer::OnGameMessage(const GameMessage_SkipToGF& msg)
×
1406
{
1407
    if(IsHost(msg.senderPlayerID))
×
1408
    {
1409
        skiptogf = msg.targetGF;
×
1410
        SendToAll(msg);
×
1411
    }
1412
    return true;
×
1413
}
1414

1415
bool GameServer::OnGameMessage(const GameMessage_GGSChange& msg)
×
1416
{
1417
    if(state != ServerState::Config || !IsHost(msg.senderPlayerID))
×
1418
    {
1419
        KickPlayer(msg.senderPlayerID, KickReason::InvalidMsg, __LINE__);
×
1420
        return true;
×
1421
    }
1422
    ggs_ = msg.ggs;
×
1423
    SendToAll(msg);
×
1424
    CancelCountdown();
×
1425
    return true;
×
1426
}
1427

1428
void GameServer::CancelCountdown()
×
1429
{
1430
    if(!countdown.IsActive())
×
1431
        return;
×
1432
    // Countdown-Stop allen mitteilen
1433
    countdown.Stop();
×
1434
    SendToAll(GameMessage_CancelCountdown());
×
1435
    LOG.writeToFile("SERVER >>> BROADCAST: NMS_CANCELCOUNTDOWN\n");
×
1436
}
1437

1438
bool GameServer::ArePlayersReady() const
×
1439
{
1440
    // All slots filled and human players are ready
1441
    for(const JoinPlayerInfo& player : playerInfos)
×
1442
    {
UNCOV
1443
        if(player.ps == PlayerState::Free || (player.isHuman() && !player.isReady))
×
1444
            return false;
×
1445
    }
1446

1447
    // Ensure unique colors except for scripted maps where duplicate colors can be intentional
NEW
1448
    if(!mapinfo.luaFilepath.empty())
×
1449
    {
NEW
1450
        std::set<unsigned> takenColors;
×
NEW
1451
        for(const JoinPlayerInfo& player : playerInfos)
×
1452
        {
NEW
1453
            if(player.isUsed())
×
1454
            {
NEW
1455
                if(helpers::contains(takenColors, player.color))
×
NEW
1456
                    return false;
×
NEW
1457
                takenColors.insert(player.color);
×
1458
            }
1459
        }
1460
    }
1461
    return true;
×
1462
}
1463

1464
void GameServer::PlayerDataChanged(unsigned playerIdx)
×
1465
{
1466
    CancelCountdown();
×
1467
    JoinPlayerInfo& player = GetJoinPlayer(playerIdx);
×
1468
    if(player.ps != PlayerState::AI && player.isReady)
×
1469
    {
1470
        player.isReady = false;
×
1471
        SendToAll(GameMessage_Player_Ready(playerIdx, false));
×
1472
    }
1473
}
×
1474

1475
bfs::path GameServer::SaveAsyncLog()
×
1476
{
1477
    // Get the highest common counter number and start from there (remove all others)
1478
    unsigned maxCtr = 0;
×
1479
    for(const AsyncLog& log : asyncLogs)
×
1480
    {
1481
        if(!log.randEntries.empty() && log.randEntries[0].counter > maxCtr)
×
1482
            maxCtr = log.randEntries[0].counter;
×
1483
    }
1484
    // Number of entries = max(asyncLogs[0..n].randEntries.size())
1485
    unsigned numEntries = 0;
×
1486
    for(AsyncLog& log : asyncLogs)
×
1487
    {
1488
        auto it = helpers::find_if(log.randEntries, [maxCtr](const auto& e) { return e.counter == maxCtr; });
×
1489
        log.randEntries.erase(log.randEntries.begin(), it);
×
1490
        if(numEntries < log.randEntries.size())
×
1491
            numEntries = log.randEntries.size();
×
1492
    }
1493
    // No entries :(
1494
    if(numEntries == 0 || asyncLogs.size() < 2u)
×
1495
        return "";
×
1496

1497
    // count identical lines
1498
    unsigned numIdentical = 0;
×
1499
    for(unsigned i = 0; i < numEntries; i++)
×
1500
    {
1501
        bool isIdentical = true;
×
1502
        if(i >= asyncLogs[0].randEntries.size())
×
1503
            break;
×
1504
        const RandomEntry& refEntry = asyncLogs[0].randEntries[i];
×
1505
        for(const AsyncLog& log : asyncLogs)
×
1506
        {
1507
            if(i >= log.randEntries.size())
×
1508
            {
1509
                isIdentical = false;
×
1510
                break;
×
1511
            }
1512
            const RandomEntry& curEntry = log.randEntries[i];
×
1513
            if(curEntry.maxExcl != refEntry.maxExcl || curEntry.rngState != refEntry.rngState
×
1514
               || curEntry.objId != refEntry.objId)
×
1515
            {
1516
                isIdentical = false;
×
1517
                break;
×
1518
            }
1519
        }
1520
        if(isIdentical)
×
1521
            ++numIdentical;
×
1522
        else
1523
            break;
×
1524
    }
1525

1526
    LOG.write(_("There are %1% identical async log entries.\n")) % numIdentical;
×
1527

1528
    bfs::path filePath =
1529
      RTTRCONFIG.ExpandPath(s25::folders::logs) / (s25util::Time::FormatTime("async_%Y-%m-%d_%H-%i-%s") + "Server.log");
×
1530

1531
    // open async log
1532
    bnw::ofstream file(filePath);
×
1533

1534
    if(file)
×
1535
    {
1536
        file << "Map: " << mapinfo.title << std::endl;
×
1537
        file << std::setfill(' ');
×
1538
        for(const AsyncLog& log : asyncLogs)
×
1539
        {
1540
            const JoinPlayerInfo& plInfo = playerInfos.at(log.playerId);
×
1541
            file << "Player " << std::setw(2) << unsigned(log.playerId) << (plInfo.isHost ? '#' : ' ') << "\t\""
×
1542
                 << plInfo.name << '"' << std::endl;
×
1543
            file << "System info: " << log.addData << std::endl;
×
1544
            file << "\tChecksum: " << std::setw(0) << log.checksum << std::endl;
×
1545
        }
1546
        for(const AsyncLog& log : asyncLogs)
×
1547
            file << "Checksum " << std::setw(2) << unsigned(log.playerId) << std::setw(0) << ": " << log.checksum
×
1548
                 << std::endl;
×
1549

1550
        // print identical lines, they help in tracing the bug
1551
        for(unsigned i = 0; i < numIdentical; i++)
×
1552
            file << "[ I ]: " << asyncLogs[0].randEntries[i] << "\n";
×
1553
        for(unsigned i = numIdentical; i < numEntries; i++)
×
1554
        {
1555
            for(const AsyncLog& log : asyncLogs)
×
1556
            {
1557
                if(i < log.randEntries.size())
×
1558
                    file << "[C" << std::setw(2) << unsigned(log.playerId) << std::setw(0)
×
1559
                         << "]: " << log.randEntries[i] << '\n';
×
1560
            }
1561
        }
1562

1563
        LOG.write(_("Async log saved at %1%\n")) % filePath;
×
1564
        return filePath;
×
1565
    } else
1566
    {
1567
        LOG.write(_("Failed to save async log at %1%\n")) % filePath;
×
1568
        return "";
×
1569
    }
1570
}
1571

1572
void GameServer::SendAsyncLog(const bfs::path& asyncLogFilePath)
×
1573
{
1574
    if(SETTINGS.global.submit_debug_data == 1
×
1575
#ifdef _WIN32
1576
       || (MessageBoxW(nullptr,
1577
                       boost::nowide::widen(_("The game clients are out of sync. Would you like to send debug "
1578
                                              "information to RttR to help us avoiding this in "
1579
                                              "the future? Thank you very much!"))
1580
                         .c_str(),
1581
                       boost::nowide::widen(_("Error")).c_str(),
1582
                       MB_YESNO | MB_ICONERROR | MB_TASKMODAL | MB_SETFOREGROUND)
1583
           == IDYES)
1584
#endif
1585
    )
1586
    {
1587
        DebugInfo di;
×
1588
        LOG.write(_("Sending async logs %1%.\n")) % (di.SendAsyncLog(asyncLogFilePath) ? "succeeded" : "failed");
×
1589

1590
        di.SendReplay();
×
1591
    }
1592
}
×
1593

NEW
1594
void GameServer::CheckAndSetColor(unsigned playerIdx, unsigned newColor, bool ensureUnique)
×
1595
{
1596
    RTTR_Assert(playerIdx < playerInfos.size());
×
1597
    // If either of those fails we may not find a valid color!
1598
    static_assert(PLAYER_COLORS.size() >= MAX_PLAYERS, "Not enough player colors defined!");
NEW
1599
    RTTR_Assert(playerInfos.size() <= PLAYER_COLORS.size());
×
1600

1601
    JoinPlayerInfo& player = playerInfos[playerIdx];
×
1602
    RTTR_Assert(player.isUsed()); // Should only set colors for taken spots
×
1603

NEW
1604
    if(ensureUnique)
×
1605
    {
1606
        // Get colors used by other players
NEW
1607
        std::set<unsigned> takenColors;
×
NEW
1608
        for(unsigned p = 0; p < playerInfos.size(); ++p)
×
1609
        {
1610
            // Skip self
NEW
1611
            if(p == playerIdx)
×
NEW
1612
                continue;
×
1613

NEW
1614
            JoinPlayerInfo& otherPlayer = playerInfos[p];
×
NEW
1615
            if(otherPlayer.isUsed())
×
NEW
1616
                takenColors.insert(otherPlayer.color);
×
1617
        }
1618
        // Look for a unique color
NEW
1619
        int newColorIdx = JoinPlayerInfo::GetColorIdx(newColor);
×
NEW
1620
        while(helpers::contains(takenColors, newColor))
×
NEW
1621
            newColor = PLAYER_COLORS[(++newColorIdx) % PLAYER_COLORS.size()];
×
1622
    }
1623

UNCOV
1624
    if(player.color == newColor)
×
1625
        return;
×
1626

1627
    player.color = newColor;
×
1628

1629
    SendToAll(GameMessage_Player_Color(playerIdx, player.color));
×
1630
    LOG.writeToFile("SERVER >>> BROADCAST: NMS_PLAYER_TOGGLECOLOR(%d, %d)\n") % playerIdx % player.color;
×
1631
}
1632

1633
bool GameServer::OnGameMessage(const GameMessage_Player_Swap& msg)
×
1634
{
1635
    if(state != ServerState::Game && state != ServerState::Config)
×
1636
        return true;
×
1637
    int targetPlayer = GetTargetPlayer(msg);
×
1638
    if(targetPlayer < 0)
×
1639
        return true;
×
1640
    auto player1 = static_cast<uint8_t>(targetPlayer);
×
1641
    if(player1 == msg.player2 || msg.player2 >= playerInfos.size())
×
1642
        return true;
×
1643

1644
    SwapPlayer(player1, msg.player2);
×
1645
    return true;
×
1646
}
1647

1648
bool GameServer::OnGameMessage(const GameMessage_Player_SwapConfirm& msg)
×
1649
{
1650
    GameServerPlayer* player = GetNetworkPlayer(msg.senderPlayerID);
×
1651
    if(!player)
×
1652
        return true;
×
1653
    for(auto it = player->getPendingSwaps().begin(); it != player->getPendingSwaps().end(); ++it)
×
1654
    {
1655
        if(it->first == msg.player && it->second == msg.player2)
×
1656
        {
1657
            player->getPendingSwaps().erase(it);
×
1658
            break;
×
1659
        }
1660
    }
1661
    return true;
×
1662
}
1663

1664
void GameServer::SwapPlayer(const uint8_t player1, const uint8_t player2)
×
1665
{
1666
    // TODO: Swapping the player messes up the ids because our IDs are indizes. Usually this works because we use the
1667
    // sender player ID for messages received by the server and set the right ID for messages sent by the server.
1668
    // However (currently only) the host may send messages for another player. Those will not get the adjusted ID till
1669
    // he gets the swap message. So there is a short time, where the messages may be executed for the wrong player.
1670
    // Idea: Use actual IDs for players in messages (unique)
1671
    if(state == ServerState::Config)
×
1672
    {
1673
        // Swap player during match-making
1674
        // Swap everything
1675
        using std::swap;
1676
        swap(playerInfos[player1], playerInfos[player2]);
×
1677
        // In savegames some things cannot be changed
1678
        if(mapinfo.type == MapType::Savegame)
×
1679
            playerInfos[player1].FixSwappedSaveSlot(playerInfos[player2]);
×
1680
    } else if(state == ServerState::Game)
×
1681
    {
1682
        // Ingame we can only switch to a KI
1683
        if(playerInfos[player1].ps != PlayerState::Occupied || playerInfos[player2].ps != PlayerState::AI)
×
1684
            return;
×
1685

1686
        LOG.write("GameServer::ChangePlayer %i - %i \n") % unsigned(player1) % unsigned(player2);
×
1687
        using std::swap;
1688
        swap(playerInfos[player2].ps, playerInfos[player1].ps);
×
1689
        swap(playerInfos[player2].aiInfo, playerInfos[player1].aiInfo);
×
1690
        swap(playerInfos[player2].isHost, playerInfos[player1].isHost);
×
1691
    }
1692
    // Change ids of network players (if any). Get both first!
1693
    GameServerPlayer* newPlayer = GetNetworkPlayer(player2);
×
1694
    GameServerPlayer* oldPlayer = GetNetworkPlayer(player1);
×
1695
    if(newPlayer)
×
1696
        newPlayer->playerId = player1;
×
1697
    if(oldPlayer)
×
1698
        oldPlayer->playerId = player2;
×
1699
    SendToAll(GameMessage_Player_Swap(player1, player2));
×
1700
    const auto pSwap = std::make_pair(player1, player2);
×
1701
    for(GameServerPlayer& player : networkPlayers)
×
1702
    {
1703
        if(!player.isActive())
×
1704
            continue;
×
1705
        player.getPendingSwaps().push_back(pSwap);
×
1706
    }
1707
}
1708

1709
GameServerPlayer* GameServer::GetNetworkPlayer(unsigned playerId)
×
1710
{
1711
    for(GameServerPlayer& player : networkPlayers)
×
1712
    {
1713
        if(player.playerId == playerId)
×
1714
            return &player;
×
1715
    }
1716
    return nullptr;
×
1717
}
1718

1719
void GameServer::SetPaused(bool paused)
×
1720
{
1721
    if(framesinfo.isPaused == paused)
×
1722
        return;
×
1723
    framesinfo.isPaused = paused;
×
1724
    SendToAll(GameMessage_Pause(framesinfo.isPaused));
×
1725
    for(GameServerPlayer& player : networkPlayers)
×
1726
        player.setNotLagging();
×
1727
}
1728

1729
JoinPlayerInfo& GameServer::GetJoinPlayer(unsigned playerIdx)
×
1730
{
1731
    return playerInfos.at(playerIdx);
×
1732
}
1733

1734
bool GameServer::IsHost(unsigned playerIdx) const
×
1735
{
1736
    return playerIdx < playerInfos.size() && playerInfos[playerIdx].isHost;
×
1737
}
1738

1739
int GameServer::GetTargetPlayer(const GameMessageWithPlayer& msg)
×
1740
{
1741
    if(msg.player != 0xFF)
×
1742
    {
1743
        if(msg.player < playerInfos.size() && (msg.player == msg.senderPlayerID || IsHost(msg.senderPlayerID)))
×
1744
        {
1745
            GameServerPlayer* networkPlayer = GetNetworkPlayer(msg.senderPlayerID);
×
1746
            if(networkPlayer->isActive())
×
1747
                return msg.player;
×
1748
            unsigned result = msg.player;
×
1749
            // Apply pending swaps
1750
            for(auto& pSwap : networkPlayer->getPendingSwaps()) //-V522
×
1751
            {
1752
                if(pSwap.first == result)
×
1753
                    result = pSwap.second;
×
1754
                else if(pSwap.second == result)
×
1755
                    result = pSwap.first;
×
1756
            }
1757
            return result;
×
1758
        }
1759
    } else if(msg.senderPlayerID < playerInfos.size())
×
1760
        return msg.senderPlayerID;
×
1761
    return -1;
×
1762
}
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