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

Return-To-The-Roots / s25client / 12323492039

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

Pull #1683

github

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

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

4 existing lines in 1 file now uncovered.

22270 of 44412 relevant lines covered (50.14%)

34410.75 hits per line

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

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

5
#include "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 "liblobby/LobbyClient.h"
30
#include "libsiedler2/ArchivItem_Map.h"
31
#include "libsiedler2/ArchivItem_Map_Header.h"
32
#include "libsiedler2/prototypen.h"
33
#include "s25util/SocketSet.h"
34
#include "s25util/colors.h"
35
#include "s25util/utf8.h"
36
#include <boost/container/static_vector.hpp>
37
#include <boost/filesystem.hpp>
38
#include <boost/nowide/convert.hpp>
39
#include <boost/nowide/fstream.hpp>
40
#include <helpers/chronoIO.h>
41
#include <iomanip>
42
#include <iterator>
43
#include <mygettext/mygettext.h>
44

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

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

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

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

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

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

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

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

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

106
///////////////////////////////////////////////////////////////////////////////
107
//
108
GameServer::~GameServer()
×
109
{
110
    Stop();
×
111
}
×
112

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

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

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

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

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

157
            if(!save.Load(mapinfo.filepath, SaveGameDataToLoad::HeaderAndSettings))
×
158
                return false;
×
159

160
            // Spieleranzahl
161
            playerInfos.resize(save.GetNumPlayers());
×
162
            mapinfo.title = save.GetMapName();
×
163

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

172
            ggs_ = save.ggs;
×
173
            currentGF = save.start_gf;
×
174
        }
175
        break;
×
176
    }
177

178
    if(playerInfos.empty())
×
179
    {
180
        LOG.write("Map %1% has no players!\n") % mapinfo.filepath;
×
181
        return false;
×
182
    }
183

184
    if(!mapinfo.mapData.CompressFromFile(mapinfo.filepath, &mapinfo.mapChecksum))
×
185
        return false;
×
186

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

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

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

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

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

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

225
    return true;
×
226
}
227

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

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

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

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

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

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

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

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

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

315
    lanAnnouncer.Run();
×
316
}
317

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

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

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

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

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

392
///////////////////////////////////////////////////////////////////////////////
393
// stoppt den server
394
void GameServer::Stop()
×
395
{
396
    if(state == ServerState::Stopped)
×
397
        return;
×
398

399
    // player verabschieden
400
    playerInfos.clear();
×
401
    networkPlayers.clear();
×
402

403
    // aufräumen
404
    framesinfo.Clear();
×
405
    config.Clear();
×
406
    mapinfo.Clear();
×
407
    countdown.Stop();
×
408

409
    // laden dicht machen
410
    serversocket.Close();
×
411
    // clear jump target
412
    skiptogf = 0;
×
413

414
    // clear async logs
415
    asyncLogs.clear();
×
416

417
    lanAnnouncer.Stop();
×
418

419
    if(LOBBYCLIENT.IsLoggedIn()) // steht die Lobbyverbindung noch?
×
420
        LOBBYCLIENT.DeleteServer();
×
421
    LOBBYCLIENT.RemoveListener(this);
×
422

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

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

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

444
    std::array<unsigned, NUM_TEAMS> numPlayersInTeam{};
21✔
445
    struct AssignPlayer
446
    {
447
        PlayerIndex player;
448
        static_vector<TeamIndex, NUM_TEAMS> possibleTeams;
449
        TeamIndex chosenTeam = 0;
450
    };
451

452
    static_vector<AssignPlayer, MAX_PLAYERS> playersToAssign;
×
453
    auto rng = helpers::getRandomGenerator();
21✔
454

455
    bool playerWasAssigned = false;
21✔
456

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

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

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

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

526
    return playerWasAssigned;
42✔
527
}
528

NEW
529
void GameServer::SetNumPlayers(unsigned num)
×
530
{
NEW
531
    playerInfos.resize(num);
×
NEW
532
}
×
533

534
/**
535
 *  startet das Spiel.
536
 */
537
bool GameServer::StartGame()
×
538
{
539
    lanAnnouncer.Stop();
×
540

541
    // Finalize the team selection for unassigned players.
542
    if(assignPlayersOfRandomTeams(playerInfos))
×
543
        SendToAll(GameMessage_Player_List(playerInfos));
×
544

545
    // Bei Savegames wird der Startwert von den Clients aus der Datei gelesen!
546
    unsigned random_init;
547
    if(mapinfo.type == MapType::Savegame)
×
548
        random_init = 0;
×
549
    else
550
        random_init = static_cast<unsigned>(std::chrono::high_resolution_clock::now().time_since_epoch().count());
×
551

552
    nwfInfo.init(currentGF, 3);
×
553

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

558
    // Höchsten Ping ermitteln
559
    unsigned highest_ping = 0;
×
560
    for(const JoinPlayerInfo& player : playerInfos)
×
561
    {
562
        if(player.ps == PlayerState::Occupied)
×
563
        {
564
            if(player.ping > highest_ping)
×
565
                highest_ping = player.ping;
×
566
        }
567
    }
568

569
    framesinfo.gfLengthReq = framesinfo.gf_length = SPEED_GF_LENGTHS[ggs_.speed];
×
570

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

574
    LOG.write("SERVER: Using gameframe length of %1%\n") % helpers::withUnit(framesinfo.gf_length);
×
575
    LOG.write("SERVER: Using networkframe length of %1% GFs (%2%)\n") % framesinfo.nwf_length
×
576
      % helpers::withUnit(framesinfo.nwf_length * framesinfo.gf_length);
×
577

578
    for(unsigned id = 0; id < playerInfos.size(); id++)
×
579
    {
580
        if(playerInfos[id].isUsed())
×
581
            nwfInfo.addPlayer(id);
×
582
    }
583

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

589
    state = ServerState::Loading;
×
590
    loadStartTime = SteadyClock::now();
×
591

592
    return true;
×
593
}
594

595
unsigned GameServer::CalcNWFLenght(FramesInfo::milliseconds32_t minDuration) const
×
596
{
597
    constexpr unsigned maxNumGF = 20;
×
598
    for(unsigned i = 1; i < maxNumGF; ++i)
×
599
    {
600
        if(i * framesinfo.gf_length >= minDuration)
×
601
            return i;
×
602
    }
603
    return maxNumGF;
×
604
}
605

606
void GameServer::SendNWFDone(const NWFServerInfo& info)
×
607
{
608
    nwfInfo.addServerInfo(info);
×
609
    SendToAll(GameMessage_Server_NWFDone(info.gf, info.newGFLen, info.nextNWF));
×
610
}
×
611

612
/**
613
 *  Nachricht an Alle
614
 */
615
void GameServer::SendToAll(const GameMessage& msg)
×
616
{
617
    for(GameServerPlayer& player : networkPlayers)
×
618
    {
619
        // ist der Slot Belegt, dann Nachricht senden
620
        if(player.isActive())
×
621
            player.sendMsgAsync(msg.clone());
×
622
    }
623
}
×
624

625
void GameServer::KickPlayer(uint8_t playerId, KickReason cause, uint32_t param)
×
626
{
627
    if(playerId >= playerInfos.size())
×
628
        return;
×
629
    JoinPlayerInfo& playerInfo = playerInfos[playerId];
×
630
    GameServerPlayer* player = GetNetworkPlayer(playerId);
×
631
    if(player)
×
632
        player->closeConnection();
×
633
    // Non-existing or connecting player
634
    if(!playerInfo.isUsed())
×
635
        return;
×
636
    playerInfo.ps = PlayerState::Free;
×
637

638
    SendToAll(GameMessage_Player_Kicked(playerId, cause, param));
×
639

640
    // If we are ingame, replace by KI
641
    if(state == ServerState::Game || state == ServerState::Loading)
×
642
    {
643
        playerInfo.ps = PlayerState::AI;
×
644
        playerInfo.aiInfo = AI::Info(AI::Type::Dummy);
×
645
    } else
646
        CancelCountdown();
×
647

648
    AnnounceStatusChange();
×
649
    LOG.writeToFile("SERVER >>> BROADCAST: NMS_PLAYERKICKED(%d,%d,%d)\n") % unsigned(playerId) % unsigned(cause)
×
650
      % unsigned(param);
×
651
}
652

653
///////////////////////////////////////////////////////////////////////////////
654
// testet, ob in der Verbindungswarteschlange Clients auf Verbindung warten
655
void GameServer::ClientWatchDog()
×
656
{
657
    SocketSet set;
×
658
    set.Clear();
×
659

660
    // sockets zum set hinzufügen
661
    for(GameServerPlayer& player : networkPlayers)
×
662
        set.Add(player.socket);
×
663

664
    // auf fehler prüfen
665
    if(set.Select(0, 2) > 0)
×
666
    {
667
        for(const GameServerPlayer& player : networkPlayers)
×
668
        {
669
            if(set.InSet(player.socket))
×
670
            {
671
                LOG.write(_("SERVER: Error on socket of player %1%, bye bye!\n")) % player.playerId;
×
672
                KickPlayer(player.playerId, KickReason::ConnectionLost, __LINE__);
×
673
            }
674
        }
675
    }
676

677
    for(GameServerPlayer& player : networkPlayers)
×
678
    {
679
        if(player.hasTimedOut())
×
680
        {
681
            LOG.write(_("SERVER: Reserved slot %1% freed due to timeout\n")) % player.playerId;
×
682
            KickPlayer(player.playerId, KickReason::PingTimeout, __LINE__);
×
683
        } else
684
            player.doPing();
×
685
    }
686
}
×
687

688
void GameServer::ExecuteGameFrame()
×
689
{
690
    RTTR_Assert(state == ServerState::Game);
×
691

692
    FramesInfo::UsedClock::time_point currentTime = FramesInfo::UsedClock::now();
×
693
    FramesInfo::milliseconds32_t passedTime =
694
      std::chrono::duration_cast<FramesInfo::milliseconds32_t>(currentTime - framesinfo.lastTime);
×
695

696
    // prüfen ob GF vergangen
697
    if(passedTime >= framesinfo.gf_length || skiptogf > currentGF)
×
698
    {
699
        // NWF vergangen?
700
        if(currentGF == nwfInfo.getNextNWF())
×
701
        {
702
            if(CheckForLaggingPlayers())
×
703
            {
704
                // Check for kicking every second
705
                static FramesInfo::UsedClock::time_point lastLagKickTime;
706
                if(currentTime - lastLagKickTime >= std::chrono::seconds(1))
×
707
                {
708
                    lastLagKickTime = currentTime;
×
709
                    CheckAndKickLaggingPlayers();
×
710
                }
711
                // Skip the rest
712
                return;
×
713
            } else
714
                ExecuteNWF();
×
715
        }
716
        // Advance GF
717
        ++currentGF;
×
718
        // Normally we set lastTime = curTime (== lastTime + passedTime) where passedTime is ideally 1 GF
719
        // 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
720
        // bit earlier. Exception: We lag many GFs behind, then we advance by the full passedTime - 1 GF which means we
721
        // are now only 1 GF behind and execute that on the next call
722
        if(passedTime <= 4 * framesinfo.gf_length)
×
723
            passedTime = framesinfo.gf_length;
×
724
        else
725
            passedTime -= framesinfo.gf_length;
×
726
        framesinfo.lastTime += passedTime;
×
727
    }
728
}
729

730
void GameServer::ExecuteNWF()
×
731
{
732
    // Check for asyncs
733
    if(CheckForAsync())
×
734
    {
735
        // Pause game
736
        RTTR_Assert(!framesinfo.isPaused);
×
737
        SetPaused(true);
×
738

739
        // Notify players
740
        std::vector<unsigned> checksumHashes;
×
741
        for(const GameServerPlayer& player : networkPlayers)
×
742
            checksumHashes.push_back(nwfInfo.getPlayerCmds(player.playerId).checksum.getHash());
×
743
        SendToAll(GameMessage_Server_Async(checksumHashes));
×
744

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

778
bool GameServer::CheckForAsync()
×
779
{
780
    if(networkPlayers.empty())
×
781
        return false;
×
782
    bool isAsync = false;
×
783
    const AsyncChecksum& refChecksum = nwfInfo.getPlayerCmds(networkPlayers.front().playerId).checksum;
×
784
    for(const GameServerPlayer& player : networkPlayers)
×
785
    {
786
        const AsyncChecksum& curChecksum = nwfInfo.getPlayerCmds(player.playerId).checksum;
×
787

788
        // Checksummen nicht gleich?
789
        if(curChecksum != refChecksum)
×
790
        {
791
            LOG.write(_("Async at GF %1% of player %2% vs %3%. Checksums:\n%4%\n%5%\n\n")) % currentGF % player.playerId
×
792
              % networkPlayers.front().playerId % curChecksum % refChecksum;
×
793
            isAsync = true;
×
794
        }
795
    }
796
    return isAsync;
×
797
}
798

799
void GameServer::CheckAndKickLaggingPlayers()
×
800
{
801
    for(const GameServerPlayer& player : networkPlayers)
×
802
    {
803
        const unsigned timeOut = player.getLagTimeOut();
×
804
        if(timeOut == 0)
×
805
            KickPlayer(player.playerId, KickReason::PingTimeout, __LINE__);
×
806
        else if(timeOut <= 30
×
807
                && (timeOut % 5 == 0
×
808
                    || timeOut < 5)) // Notify every 5s if max 30s are remaining, if less than 5s notify every second
×
809
            LOG.write(_("SERVER: Kicking player %1% in %2% seconds\n")) % player.playerId % timeOut;
×
810
    }
811
}
×
812

813
bool GameServer::CheckForLaggingPlayers()
×
814
{
815
    if(nwfInfo.isReady())
×
816
        return false;
×
817
    for(GameServerPlayer& player : networkPlayers)
×
818
    {
819
        if(nwfInfo.getPlayerInfo(player.playerId).isLagging)
×
820
            player.setLagging();
×
821
    }
822
    return true;
×
823
}
824

825
///////////////////////////////////////////////////////////////////////////////
826
// testet, ob in der Verbindungswarteschlange Clients auf Verbindung warten
827
void GameServer::WaitForClients()
×
828
{
829
    SocketSet set;
×
830

831
    set.Add(serversocket);
×
832
    if(set.Select(0, 0) > 0)
×
833
    {
834
        RTTR_Assert(set.InSet(serversocket));
×
835
        Socket socket = serversocket.Accept();
×
836

837
        // Verbindung annehmen
838
        if(!socket.isValid())
×
839
            return;
×
840

841
        unsigned newPlayerId = GameMessageWithPlayer::NO_PLAYER_ID;
×
842
        // Geeigneten Platz suchen
843
        for(unsigned playerId = 0; playerId < playerInfos.size(); ++playerId)
×
844
        {
845
            if(playerInfos[playerId].ps == PlayerState::Free && !GetNetworkPlayer(playerId))
×
846
            {
847
                networkPlayers.push_back(GameServerPlayer(playerId, socket));
×
848
                newPlayerId = playerId;
×
849
                break;
×
850
            }
851
        }
852

853
        GameMessage_Player_Id msg(newPlayerId);
×
854
        MessageHandler::send(socket, msg);
×
855

856
        // war kein platz mehr frei, wenn ja dann verbindung trennen?
857
        if(newPlayerId == 0xFFFFFFFF)
×
858
            socket.Close();
×
859
    }
860
}
861

862
///////////////////////////////////////////////////////////////////////////////
863
// füllt die warteschlangen mit "paketen"
864
void GameServer::FillPlayerQueues()
×
865
{
866
    SocketSet set;
×
867
    bool msgReceived = false;
×
868

869
    // erstmal auf Daten überprüfen
870
    do
×
871
    {
872
        // sockets zum set hinzufügen
873
        for(const GameServerPlayer& player : networkPlayers)
×
874
            set.Add(player.socket);
×
875

876
        msgReceived = false;
×
877

878
        // ist eines der Sockets im Set lesbar?
879
        if(set.Select(0, 0) > 0)
×
880
        {
881
            for(GameServerPlayer& player : networkPlayers)
×
882
            {
883
                if(set.InSet(player.socket))
×
884
                {
885
                    // nachricht empfangen
886
                    if(!player.receiveMsgs())
×
887
                    {
888
                        LOG.write(_("SERVER: Receiving Message for player %1% failed, kicking...\n")) % player.playerId;
×
889
                        KickPlayer(player.playerId, KickReason::ConnectionLost, __LINE__);
×
890
                    } else
891
                        msgReceived = true;
×
892
                }
893
            }
894
        }
895
    } while(msgReceived);
896
}
×
897

898
///////////////////////////////////////////////////////////////////////////////
899
// pongnachricht
900
bool GameServer::OnGameMessage(const GameMessage_Pong& msg)
×
901
{
902
    GameServerPlayer* player = GetNetworkPlayer(msg.senderPlayerID);
×
903
    if(player)
×
904
    {
905
        unsigned ping = player->calcPingTime();
×
906
        if(ping == 0u)
×
907
            return true;
×
908
        playerInfos[msg.senderPlayerID].ping = ping;
×
909
        SendToAll(GameMessage_Player_Ping(msg.senderPlayerID, ping));
×
910
    }
911
    return true;
×
912
}
913

914
///////////////////////////////////////////////////////////////////////////////
915
// servertype
916
bool GameServer::OnGameMessage(const GameMessage_Server_Type& msg)
×
917
{
918
    if(state != ServerState::Config)
×
919
    {
920
        KickPlayer(msg.senderPlayerID, KickReason::InvalidMsg, __LINE__);
×
921
        return true;
×
922
    }
923

924
    GameServerPlayer* player = GetNetworkPlayer(msg.senderPlayerID);
×
925
    if(!player)
×
926
        return true;
×
927

928
    auto typeok = GameMessage_Server_TypeOK::StatusCode::Ok;
×
929
    if(msg.type != config.servertype)
×
930
        typeok = GameMessage_Server_TypeOK::StatusCode::InvalidServerType;
×
931
    else if(msg.revision != rttr::version::GetRevision())
×
932
        typeok = GameMessage_Server_TypeOK::StatusCode::WrongVersion;
×
933

934
    player->sendMsg(GameMessage_Server_TypeOK(typeok, rttr::version::GetRevision()));
×
935

936
    if(typeok != GameMessage_Server_TypeOK::StatusCode::Ok)
×
937
        KickPlayer(msg.senderPlayerID, KickReason::ConnectionLost, __LINE__);
×
938
    return true;
×
939
}
940

941
/**
942
 *  Server-Passwort-Nachricht
943
 */
944
bool GameServer::OnGameMessage(const GameMessage_Server_Password& msg)
×
945
{
946
    if(state != ServerState::Config)
×
947
    {
948
        KickPlayer(msg.senderPlayerID, KickReason::InvalidMsg, __LINE__);
×
949
        return true;
×
950
    }
951

952
    GameServerPlayer* player = GetNetworkPlayer(msg.senderPlayerID);
×
953
    if(!player)
×
954
        return true;
×
955

956
    std::string passwordok = (config.password == msg.password ? "true" : "false");
×
957
    if(msg.password == config.hostPassword)
×
958
    {
959
        passwordok = "true";
×
960
        playerInfos[msg.senderPlayerID].isHost = true;
×
961
    } else
962
        playerInfos[msg.senderPlayerID].isHost = false;
×
963

964
    player->sendMsgAsync(new GameMessage_Server_Password(passwordok));
×
965

966
    if(passwordok == "false")
×
967
        KickPlayer(msg.senderPlayerID, KickReason::WrongPassword, __LINE__);
×
968
    return true;
×
969
}
970

971
/**
972
 *  Chat-Nachricht.
973
 */
974
bool GameServer::OnGameMessage(const GameMessage_Chat& msg)
×
975
{
976
    int playerID = GetTargetPlayer(msg);
×
977
    if(playerID >= 0)
×
978
        SendToAll(GameMessage_Chat(playerID, msg.destination, msg.text));
×
979
    return true;
×
980
}
981

982
bool GameServer::OnGameMessage(const GameMessage_Player_State& msg)
×
983
{
984
    if(state != ServerState::Config)
×
985
    {
986
        KickPlayer(msg.senderPlayerID, KickReason::InvalidMsg, __LINE__);
×
987
        return true;
×
988
    }
989
    // Can't do this. Have to have a joined player
990
    if(msg.ps == PlayerState::Occupied)
×
991
        return true;
×
992

993
    int playerID = GetTargetPlayer(msg);
×
994
    if(playerID < 0)
×
995
        return true;
×
996
    JoinPlayerInfo& player = playerInfos[playerID];
×
997
    const PlayerState oldPs = player.ps;
×
998
    // Can't change self
999
    if(playerID != msg.senderPlayerID)
×
1000
    {
1001
        // oh ein spieler, weg mit ihm!
1002
        if(GetNetworkPlayer(playerID))
×
1003
            KickPlayer(playerID, KickReason::NoCause, __LINE__);
×
1004

1005
        if(mapinfo.type == MapType::Savegame)
×
1006
        {
1007
            // For savegames we cannot set anyone on a locked slot as the player does not exist on the map
1008
            if(player.ps != PlayerState::Locked)
×
1009
            {
1010
                // And we don't lock!
1011
                player.ps = msg.ps == PlayerState::Locked ? PlayerState::Free : msg.ps;
×
1012
                player.aiInfo = msg.aiInfo;
×
1013
            }
1014
        } else
1015
        {
1016
            player.ps = msg.ps;
×
1017
            player.aiInfo = msg.aiInfo;
×
1018
        }
1019
        if(player.ps == PlayerState::Free && config.servertype == ServerType::Local)
×
1020
        {
1021
            player.ps = PlayerState::AI;
×
1022
            player.aiInfo = AI::Info(AI::Type::Default);
×
1023
        }
1024
    }
1025
    // Even when nothing changed we send the data because the other players might have expected a change
1026

1027
    if(player.ps == PlayerState::AI)
×
1028
    {
1029
        player.SetAIName(playerID);
×
1030
        SendToAll(GameMessage_Player_Name(playerID, player.name));
×
1031
    }
1032
    // If slot is filled, check current color
1033
    if(player.isUsed())
×
1034
        CheckAndSetColor(playerID, player.color);
×
1035
    SendToAll(GameMessage_Player_State(playerID, player.ps, player.aiInfo));
×
1036

1037
    if(oldPs != player.ps)
×
1038
        player.isReady = (player.ps == PlayerState::AI);
×
1039
    SendToAll(GameMessage_Player_Ready(playerID, player.isReady));
×
1040
    PlayerDataChanged(playerID);
×
1041
    AnnounceStatusChange();
×
1042
    return true;
×
1043
}
1044

1045
///////////////////////////////////////////////////////////////////////////////
1046
// Spielername
1047
bool GameServer::OnGameMessage(const GameMessage_Player_Name& msg)
×
1048
{
1049
    if(state != ServerState::Config)
×
1050
    {
1051
        KickPlayer(msg.senderPlayerID, KickReason::InvalidMsg, __LINE__);
×
1052
        return true;
×
1053
    }
1054
    int playerID = GetTargetPlayer(msg);
×
1055
    if(playerID < 0)
×
1056
        return true;
×
1057

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

1060
    playerInfos[playerID].name = msg.playername;
×
1061
    SendToAll(GameMessage_Player_Name(playerID, msg.playername));
×
1062
    PlayerDataChanged(playerID);
×
1063

1064
    return true;
×
1065
}
1066

1067
///////////////////////////////////////////////////////////////////////////////
1068
// Nation weiterwechseln
1069
bool GameServer::OnGameMessage(const GameMessage_Player_Nation& msg)
×
1070
{
1071
    if(state != ServerState::Config)
×
1072
    {
1073
        KickPlayer(msg.senderPlayerID, KickReason::InvalidMsg, __LINE__);
×
1074
        return true;
×
1075
    }
1076
    int playerID = GetTargetPlayer(msg);
×
1077
    if(playerID < 0)
×
1078
        return true;
×
1079

1080
    playerInfos[playerID].nation = msg.nation;
×
1081

1082
    SendToAll(GameMessage_Player_Nation(playerID, msg.nation));
×
1083
    PlayerDataChanged(playerID);
×
1084
    return true;
×
1085
}
1086

1087
///////////////////////////////////////////////////////////////////////////////
1088
// Team weiterwechseln
1089
bool GameServer::OnGameMessage(const GameMessage_Player_Team& msg)
×
1090
{
1091
    if(state != ServerState::Config)
×
1092
    {
1093
        KickPlayer(msg.senderPlayerID, KickReason::InvalidMsg, __LINE__);
×
1094
        return true;
×
1095
    }
1096
    int playerID = GetTargetPlayer(msg);
×
1097
    if(playerID < 0)
×
1098
        return true;
×
1099

1100
    playerInfos[playerID].team = msg.team;
×
1101

1102
    SendToAll(GameMessage_Player_Team(playerID, msg.team));
×
1103
    PlayerDataChanged(playerID);
×
1104
    return true;
×
1105
}
1106

1107
///////////////////////////////////////////////////////////////////////////////
1108
// Farbe weiterwechseln
1109
bool GameServer::OnGameMessage(const GameMessage_Player_Color& msg)
×
1110
{
1111
    if(state != ServerState::Config)
×
1112
    {
1113
        KickPlayer(msg.senderPlayerID, KickReason::InvalidMsg, __LINE__);
×
1114
        return true;
×
1115
    }
1116
    int playerID = GetTargetPlayer(msg);
×
1117
    if(playerID < 0)
×
1118
        return true;
×
1119

1120
    CheckAndSetColor(playerID, msg.color);
×
1121
    PlayerDataChanged(playerID);
×
1122
    return true;
×
1123
}
1124

1125
/**
1126
 *  Spielerstatus wechseln
1127
 */
1128
bool GameServer::OnGameMessage(const GameMessage_Player_Ready& msg)
×
1129
{
1130
    if(state != ServerState::Config)
×
1131
    {
1132
        KickPlayer(msg.senderPlayerID, KickReason::InvalidMsg, __LINE__);
×
1133
        return true;
×
1134
    }
1135
    int playerID = GetTargetPlayer(msg);
×
1136
    if(playerID < 0)
×
1137
        return true;
×
1138

1139
    JoinPlayerInfo& player = playerInfos[playerID];
×
1140

1141
    player.isReady = msg.ready;
×
1142

1143
    // countdown ggf abbrechen
1144
    if(!player.isReady && countdown.IsActive())
×
1145
        CancelCountdown();
×
1146

1147
    SendToAll(GameMessage_Player_Ready(playerID, msg.ready));
×
1148
    return true;
×
1149
}
1150

1151
bool GameServer::OnGameMessage(const GameMessage_MapRequest& msg)
×
1152
{
1153
    if(state != ServerState::Config)
×
1154
    {
1155
        KickPlayer(msg.senderPlayerID, KickReason::InvalidMsg, __LINE__);
×
1156
        return true;
×
1157
    }
1158
    GameServerPlayer* player = GetNetworkPlayer(msg.senderPlayerID);
×
1159
    if(!player)
×
1160
        return true;
×
1161

1162
    if(msg.requestInfo)
×
1163
    {
1164
        player->sendMsgAsync(new GameMessage_Map_Info(mapinfo.filepath.filename().string(), mapinfo.type,
×
1165
                                                      mapinfo.mapData.uncompressedLength, mapinfo.mapData.data.size(),
×
1166
                                                      mapinfo.luaData.uncompressedLength, mapinfo.luaData.data.size()));
×
1167
    } else if(player->isMapSending())
×
1168
    {
1169
        // Don't send again
1170
        KickPlayer(msg.senderPlayerID, KickReason::InvalidMsg, __LINE__);
×
1171
    } else
1172
    {
1173
        // Send map data
1174
        unsigned curPos = 0;
×
1175
        unsigned remainingSize = mapinfo.mapData.data.size();
×
1176
        while(remainingSize)
×
1177
        {
1178
            unsigned chunkSize = std::min(MAP_PART_SIZE, remainingSize);
×
1179

1180
            player->sendMsgAsync(new GameMessage_Map_Data(true, curPos, &mapinfo.mapData.data[curPos], chunkSize));
×
1181
            curPos += chunkSize;
×
1182
            remainingSize -= chunkSize;
×
1183
        }
1184

1185
        // And lua data (if there is any)
1186
        RTTR_Assert(mapinfo.luaFilepath.empty() == mapinfo.luaData.data.empty());
×
1187
        RTTR_Assert(mapinfo.luaData.data.empty() == (mapinfo.luaData.uncompressedLength == 0));
×
1188
        curPos = 0;
×
1189
        remainingSize = mapinfo.luaData.data.size();
×
1190
        while(remainingSize)
×
1191
        {
1192
            unsigned chunkSize = std::min(MAP_PART_SIZE, remainingSize);
×
1193

1194
            player->sendMsgAsync(new GameMessage_Map_Data(false, curPos, &mapinfo.luaData.data[curPos], chunkSize));
×
1195
            curPos += chunkSize;
×
1196
            remainingSize -= chunkSize;
×
1197
        }
1198
        // estimate time. max 60 chunks/s (currently limited by framerate), assume 50 (~25kb/s)
1199
        auto numChunks = (mapinfo.mapData.data.size() + mapinfo.luaData.data.size()) / MAP_PART_SIZE;
×
1200
        player->setMapSending(std::chrono::seconds(numChunks / 50 + 1));
×
1201
    }
1202
    return true;
×
1203
}
1204

1205
bool GameServer::OnGameMessage(const GameMessage_Map_Checksum& msg)
×
1206
{
1207
    if(state != ServerState::Config)
×
1208
    {
1209
        KickPlayer(msg.senderPlayerID, KickReason::InvalidMsg, __LINE__);
×
1210
        return true;
×
1211
    }
1212
    GameServerPlayer* player = GetNetworkPlayer(msg.senderPlayerID);
×
1213
    if(!player)
×
1214
        return true;
×
1215

1216
    bool checksumok = (msg.mapChecksum == mapinfo.mapChecksum && msg.luaChecksum == mapinfo.luaChecksum);
×
1217

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

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

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

1226
    if(!checksumok)
×
1227
    {
1228
        if(player->isMapSending())
×
1229
            KickPlayer(msg.senderPlayerID, KickReason::WrongChecksum, __LINE__);
×
1230
    } else
1231
    {
1232
        JoinPlayerInfo& playerInfo = playerInfos[msg.senderPlayerID];
×
1233
        // Used? Then we got this twice or some error happened. Remove him
1234
        if(playerInfo.isUsed())
×
1235
            KickPlayer(msg.senderPlayerID, KickReason::InvalidMsg, __LINE__);
×
1236
        else
1237
        {
1238
            // Inform others about a new player
1239
            SendToAll(GameMessage_Player_New(msg.senderPlayerID, playerInfo.name));
×
1240

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

1244
            // Mark as used and assign a unique color
1245
            // Do this before sending the player list to avoid sending useless updates
1246
            playerInfo.ps = PlayerState::Occupied;
×
1247
            CheckAndSetColor(msg.senderPlayerID, playerInfo.color);
×
1248

1249
            // Send remaining data and mark as active
1250
            player->sendMsgAsync(new GameMessage_Server_Name(config.gamename));
×
1251
            player->sendMsgAsync(new GameMessage_Player_List(playerInfos));
×
1252
            player->sendMsgAsync(new GameMessage_GGSChange(ggs_));
×
1253
            player->setActive();
×
1254
        }
1255
        AnnounceStatusChange();
×
1256
    }
1257
    return true;
×
1258
}
1259

1260
// speed change message
1261
bool GameServer::OnGameMessage(const GameMessage_Speed& msg)
×
1262
{
1263
    if(state != ServerState::Game)
×
1264
    {
1265
        KickPlayer(msg.senderPlayerID, KickReason::InvalidMsg, __LINE__);
×
1266
        return true;
×
1267
    }
1268
    framesinfo.gfLengthReq = FramesInfo::milliseconds32_t(msg.gf_length);
×
1269
    return true;
×
1270
}
1271

1272
bool GameServer::OnGameMessage(const GameMessage_GameCommand& msg)
×
1273
{
1274
    int targetPlayerId = GetTargetPlayer(msg);
×
1275
    if((state != ServerState::Game && state != ServerState::Loading) || targetPlayerId < 0
×
1276
       || (state == ServerState::Loading && !msg.cmds.gcs.empty()))
×
1277
    {
1278
        KickPlayer(msg.senderPlayerID, KickReason::InvalidMsg, __LINE__);
×
1279
        return true;
×
1280
    }
1281

1282
    if(!nwfInfo.addPlayerCmds(targetPlayerId, msg.cmds))
×
1283
        return true; // Ignore
×
1284
    GameServerPlayer* player = GetNetworkPlayer(targetPlayerId);
×
1285
    if(player)
×
1286
        player->setNotLagging();
×
1287
    SendToAll(GameMessage_GameCommand(targetPlayerId, msg.cmds.checksum, msg.cmds.gcs));
×
1288

1289
    return true;
×
1290
}
1291

1292
bool GameServer::OnGameMessage(const GameMessage_AsyncLog& msg)
×
1293
{
1294
    if(state != ServerState::Game)
×
1295
    {
1296
        KickPlayer(msg.senderPlayerID, KickReason::InvalidMsg, __LINE__);
×
1297
        return true;
×
1298
    }
1299
    bool foundPlayer = false;
×
1300
    for(AsyncLog& log : asyncLogs)
×
1301
    {
1302
        if(log.playerId != msg.senderPlayerID)
×
1303
            continue;
×
1304
        RTTR_Assert(!log.done);
×
1305
        if(log.done)
×
1306
            return true;
×
1307
        foundPlayer = true;
×
1308
        log.addData += msg.addData;
×
1309
        log.randEntries.insert(log.randEntries.end(), msg.entries.begin(), msg.entries.end());
×
1310
        if(msg.last)
×
1311
        {
1312
            LOG.write(_("Received async logs from %1% (%2% entries).\n")) % unsigned(log.playerId)
×
1313
              % log.randEntries.size();
×
1314
            log.done = true;
×
1315
        }
1316
    }
1317
    if(!foundPlayer)
×
1318
    {
1319
        LOG.write(_("Received async log from %1%, but did not expect it!\n")) % unsigned(msg.senderPlayerID);
×
1320
        return true;
×
1321
    }
1322

1323
    // Check if we have all logs
1324
    for(const AsyncLog& log : asyncLogs)
×
1325
    {
1326
        if(!log.done)
×
1327
            return true;
×
1328
    }
1329

1330
    LOG.write(_("Async logs received completely.\n"));
×
1331

1332
    const bfs::path asyncFilePath = SaveAsyncLog();
×
1333
    if(!asyncFilePath.empty())
×
1334
        SendAsyncLog(asyncFilePath);
×
1335

1336
    // Kick all players that have a different checksum from the host
1337
    AsyncChecksum hostChecksum;
×
1338
    for(const AsyncLog& log : asyncLogs)
×
1339
    {
1340
        if(playerInfos.at(log.playerId).isHost)
×
1341
        {
1342
            hostChecksum = log.checksum;
×
1343
            break;
×
1344
        }
1345
    }
1346
    for(const AsyncLog& log : asyncLogs)
×
1347
    {
1348
        if(log.checksum != hostChecksum)
×
1349
            KickPlayer(log.playerId, KickReason::Async, __LINE__);
×
1350
    }
1351
    return true;
×
1352
}
1353

1354
bool GameServer::OnGameMessage(const GameMessage_RemoveLua& msg)
×
1355
{
1356
    if(state != ServerState::Config || !IsHost(msg.senderPlayerID))
×
1357
    {
1358
        KickPlayer(msg.senderPlayerID, KickReason::InvalidMsg, __LINE__);
×
1359
        return true;
×
1360
    }
1361
    mapinfo.luaFilepath.clear();
×
1362
    mapinfo.luaData.Clear();
×
1363
    mapinfo.luaChecksum = 0;
×
1364
    SendToAll(msg);
×
1365
    CancelCountdown();
×
1366
    return true;
×
1367
}
1368

1369
bool GameServer::OnGameMessage(const GameMessage_Countdown& msg)
×
1370
{
1371
    if(state != ServerState::Config || !IsHost(msg.senderPlayerID))
×
1372
    {
1373
        KickPlayer(msg.senderPlayerID, KickReason::InvalidMsg, __LINE__);
×
1374
        return true;
×
1375
    }
1376

1377
    NetworkPlayer* nwPlayer = GetNetworkPlayer(msg.senderPlayerID);
×
1378
    if(!nwPlayer || countdown.IsActive())
×
1379
        return true;
×
1380

1381
    if(!ArePlayersReady())
×
1382
        nwPlayer->sendMsgAsync(new GameMessage_CancelCountdown(true));
×
1383
    else
1384
    {
1385
        // Just to make sure update all player infos
1386
        SendToAll(GameMessage_Player_List(playerInfos));
×
1387
        // Start countdown (except its single player)
1388
        if(networkPlayers.size() > 1)
×
1389
        {
1390
            countdown.Start(msg.countdown);
×
1391
            SendToAll(GameMessage_Countdown(countdown.GetRemainingSecs()));
×
1392
            LOG.writeToFile("SERVER >>> Countdown started(%d)\n") % countdown.GetRemainingSecs();
×
1393
        } else if(!StartGame())
×
1394
            Stop();
×
1395
    }
1396

1397
    return true;
×
1398
}
1399

1400
bool GameServer::OnGameMessage(const GameMessage_CancelCountdown& msg)
×
1401
{
1402
    if(state != ServerState::Config || !IsHost(msg.senderPlayerID))
×
1403
    {
1404
        KickPlayer(msg.senderPlayerID, KickReason::InvalidMsg, __LINE__);
×
1405
        return true;
×
1406
    }
1407

1408
    CancelCountdown();
×
1409
    return true;
×
1410
}
1411

1412
bool GameServer::OnGameMessage(const GameMessage_Pause& msg)
×
1413
{
1414
    if(IsHost(msg.senderPlayerID))
×
1415
        SetPaused(msg.paused);
×
1416
    return true;
×
1417
}
1418

1419
bool GameServer::OnGameMessage(const GameMessage_SkipToGF& msg)
×
1420
{
1421
    if(IsHost(msg.senderPlayerID))
×
1422
    {
1423
        skiptogf = msg.targetGF;
×
1424
        SendToAll(msg);
×
1425
    }
1426
    return true;
×
1427
}
1428

1429
bool GameServer::OnGameMessage(const GameMessage_GGSChange& msg)
×
1430
{
1431
    if(state != ServerState::Config || !IsHost(msg.senderPlayerID))
×
1432
    {
1433
        KickPlayer(msg.senderPlayerID, KickReason::InvalidMsg, __LINE__);
×
1434
        return true;
×
1435
    }
1436
    ggs_ = msg.ggs;
×
1437
    SendToAll(msg);
×
1438
    CancelCountdown();
×
1439
    return true;
×
1440
}
1441

1442
void GameServer::CancelCountdown()
×
1443
{
1444
    if(!countdown.IsActive())
×
1445
        return;
×
1446
    // Countdown-Stop allen mitteilen
1447
    countdown.Stop();
×
1448
    SendToAll(GameMessage_CancelCountdown());
×
1449
    LOG.writeToFile("SERVER >>> BROADCAST: NMS_CANCELCOUNTDOWN\n");
×
1450
}
1451

1452
bool GameServer::ArePlayersReady() const
×
1453
{
1454
    // Alle Spieler da?
1455
    for(const JoinPlayerInfo& player : playerInfos)
×
1456
    {
1457
        // noch nicht alle spieler da -> feierabend!
1458
        if(player.ps == PlayerState::Free || (player.isHuman() && !player.isReady))
×
1459
            return false;
×
1460
    }
1461

1462
    std::set<unsigned> takenColors;
×
1463

1464
    // Check all players have different colors
1465
    for(const JoinPlayerInfo& player : playerInfos)
×
1466
    {
1467
        if(player.isUsed())
×
1468
        {
1469
            if(helpers::contains(takenColors, player.color))
×
1470
                return false;
×
1471
            takenColors.insert(player.color);
×
1472
        }
1473
    }
1474
    return true;
×
1475
}
1476

1477
void GameServer::PlayerDataChanged(unsigned playerIdx)
×
1478
{
1479
    CancelCountdown();
×
1480
    JoinPlayerInfo& player = GetJoinPlayer(playerIdx);
×
1481
    if(player.ps != PlayerState::AI && player.isReady)
×
1482
    {
1483
        player.isReady = false;
×
1484
        SendToAll(GameMessage_Player_Ready(playerIdx, false));
×
1485
    }
1486
}
×
1487

1488
bfs::path GameServer::SaveAsyncLog()
×
1489
{
1490
    // Get the highest common counter number and start from there (remove all others)
1491
    unsigned maxCtr = 0;
×
1492
    for(const AsyncLog& log : asyncLogs)
×
1493
    {
1494
        if(!log.randEntries.empty() && log.randEntries[0].counter > maxCtr)
×
1495
            maxCtr = log.randEntries[0].counter;
×
1496
    }
1497
    // Number of entries = max(asyncLogs[0..n].randEntries.size())
1498
    unsigned numEntries = 0;
×
1499
    for(AsyncLog& log : asyncLogs)
×
1500
    {
1501
        auto it = helpers::find_if(log.randEntries, [maxCtr](const auto& e) { return e.counter == maxCtr; });
×
1502
        log.randEntries.erase(log.randEntries.begin(), it);
×
1503
        if(numEntries < log.randEntries.size())
×
1504
            numEntries = log.randEntries.size();
×
1505
    }
1506
    // No entries :(
1507
    if(numEntries == 0 || asyncLogs.size() < 2u)
×
1508
        return "";
×
1509

1510
    // count identical lines
1511
    unsigned numIdentical = 0;
×
1512
    for(unsigned i = 0; i < numEntries; i++)
×
1513
    {
1514
        bool isIdentical = true;
×
1515
        if(i >= asyncLogs[0].randEntries.size())
×
1516
            break;
×
1517
        const RandomEntry& refEntry = asyncLogs[0].randEntries[i];
×
1518
        for(const AsyncLog& log : asyncLogs)
×
1519
        {
1520
            if(i >= log.randEntries.size())
×
1521
            {
1522
                isIdentical = false;
×
1523
                break;
×
1524
            }
1525
            const RandomEntry& curEntry = log.randEntries[i];
×
1526
            if(curEntry.maxExcl != refEntry.maxExcl || curEntry.rngState != refEntry.rngState
×
1527
               || curEntry.objId != refEntry.objId)
×
1528
            {
1529
                isIdentical = false;
×
1530
                break;
×
1531
            }
1532
        }
1533
        if(isIdentical)
×
1534
            ++numIdentical;
×
1535
        else
1536
            break;
×
1537
    }
1538

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

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

1544
    // open async log
1545
    bnw::ofstream file(filePath);
×
1546

1547
    if(file)
×
1548
    {
1549
        file << "Map: " << mapinfo.title << std::endl;
×
1550
        file << std::setfill(' ');
×
1551
        for(const AsyncLog& log : asyncLogs)
×
1552
        {
1553
            const JoinPlayerInfo& plInfo = playerInfos.at(log.playerId);
×
1554
            file << "Player " << std::setw(2) << unsigned(log.playerId) << (plInfo.isHost ? '#' : ' ') << "\t\""
×
1555
                 << plInfo.name << '"' << std::endl;
×
1556
            file << "System info: " << log.addData << std::endl;
×
1557
            file << "\tChecksum: " << std::setw(0) << log.checksum << std::endl;
×
1558
        }
1559
        for(const AsyncLog& log : asyncLogs)
×
1560
            file << "Checksum " << std::setw(2) << unsigned(log.playerId) << std::setw(0) << ": " << log.checksum
×
1561
                 << std::endl;
×
1562

1563
        // print identical lines, they help in tracing the bug
1564
        for(unsigned i = 0; i < numIdentical; i++)
×
1565
            file << "[ I ]: " << asyncLogs[0].randEntries[i] << "\n";
×
1566
        for(unsigned i = numIdentical; i < numEntries; i++)
×
1567
        {
1568
            for(const AsyncLog& log : asyncLogs)
×
1569
            {
1570
                if(i < log.randEntries.size())
×
1571
                    file << "[C" << std::setw(2) << unsigned(log.playerId) << std::setw(0)
×
1572
                         << "]: " << log.randEntries[i] << '\n';
×
1573
            }
1574
        }
1575

1576
        LOG.write(_("Async log saved at %1%\n")) % filePath;
×
1577
        return filePath;
×
1578
    } else
1579
    {
1580
        LOG.write(_("Failed to save async log at %1%\n")) % filePath;
×
1581
        return "";
×
1582
    }
1583
}
1584

1585
void GameServer::SendAsyncLog(const bfs::path& asyncLogFilePath)
×
1586
{
1587
    if(SETTINGS.global.submit_debug_data == 1
×
1588
#ifdef _WIN32
1589
       || (MessageBoxW(nullptr,
1590
                       boost::nowide::widen(_("The game clients are out of sync. Would you like to send debug "
1591
                                              "information to RttR to help us avoiding this in "
1592
                                              "the future? Thank you very much!"))
1593
                         .c_str(),
1594
                       boost::nowide::widen(_("Error")).c_str(),
1595
                       MB_YESNO | MB_ICONERROR | MB_TASKMODAL | MB_SETFOREGROUND)
1596
           == IDYES)
1597
#endif
1598
    )
1599
    {
1600
        DebugInfo di;
×
1601
        LOG.write(_("Sending async logs %1%.\n")) % (di.SendAsyncLog(asyncLogFilePath) ? "succeeded" : "failed");
×
1602

1603
        di.SendReplay();
×
1604
    }
1605
}
×
1606

1607
void GameServer::CheckAndSetColor(unsigned playerIdx, unsigned newColor)
×
1608
{
1609
    RTTR_Assert(playerIdx < playerInfos.size());
×
1610
    RTTR_Assert(playerInfos.size() <= PLAYER_COLORS.size()); // Else we may not find a valid color!
×
1611

1612
    JoinPlayerInfo& player = playerInfos[playerIdx];
×
1613
    RTTR_Assert(player.isUsed()); // Should only set colors for taken spots
×
1614

1615
    // Get colors used by other players
1616
    std::set<unsigned> takenColors;
×
1617
    for(unsigned p = 0; p < playerInfos.size(); ++p)
×
1618
    {
1619
        // Skip self
1620
        if(p == playerIdx)
×
1621
            continue;
×
1622

1623
        JoinPlayerInfo& otherPlayer = playerInfos[p];
×
1624
        if(otherPlayer.isUsed())
×
1625
            takenColors.insert(otherPlayer.color);
×
1626
    }
1627

1628
    // Look for a unique color
1629
    int newColorIdx = JoinPlayerInfo::GetColorIdx(newColor);
×
1630
    while(helpers::contains(takenColors, newColor))
×
1631
        newColor = PLAYER_COLORS[(++newColorIdx) % PLAYER_COLORS.size()];
×
1632

1633
    if(player.color == newColor)
×
1634
        return;
×
1635

1636
    player.color = newColor;
×
1637

1638
    SendToAll(GameMessage_Player_Color(playerIdx, player.color));
×
1639
    LOG.writeToFile("SERVER >>> BROADCAST: NMS_PLAYER_TOGGLECOLOR(%d, %d)\n") % playerIdx % player.color;
×
1640
}
1641

1642
bool GameServer::OnGameMessage(const GameMessage_Player_Swap& msg)
×
1643
{
1644
    if(state != ServerState::Game && state != ServerState::Config)
×
1645
        return true;
×
1646
    int targetPlayer = GetTargetPlayer(msg);
×
1647
    if(targetPlayer < 0)
×
1648
        return true;
×
1649
    auto player1 = static_cast<uint8_t>(targetPlayer);
×
1650
    if(player1 == msg.player2 || msg.player2 >= playerInfos.size())
×
1651
        return true;
×
1652

1653
    SwapPlayer(player1, msg.player2);
×
1654
    return true;
×
1655
}
1656

1657
bool GameServer::OnGameMessage(const GameMessage_Player_SwapConfirm& msg)
×
1658
{
1659
    GameServerPlayer* player = GetNetworkPlayer(msg.senderPlayerID);
×
1660
    if(!player)
×
1661
        return true;
×
1662
    for(auto it = player->getPendingSwaps().begin(); it != player->getPendingSwaps().end(); ++it)
×
1663
    {
1664
        if(it->first == msg.player && it->second == msg.player2)
×
1665
        {
1666
            player->getPendingSwaps().erase(it);
×
1667
            break;
×
1668
        }
1669
    }
1670
    return true;
×
1671
}
1672

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

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

1718
GameServerPlayer* GameServer::GetNetworkPlayer(unsigned playerId)
×
1719
{
1720
    for(GameServerPlayer& player : networkPlayers)
×
1721
    {
1722
        if(player.playerId == playerId)
×
1723
            return &player;
×
1724
    }
1725
    return nullptr;
×
1726
}
1727

1728
void GameServer::SetPaused(bool paused)
×
1729
{
1730
    if(framesinfo.isPaused == paused)
×
1731
        return;
×
1732
    framesinfo.isPaused = paused;
×
1733
    SendToAll(GameMessage_Pause(framesinfo.isPaused));
×
1734
    for(GameServerPlayer& player : networkPlayers)
×
1735
        player.setNotLagging();
×
1736
}
1737

1738
JoinPlayerInfo& GameServer::GetJoinPlayer(unsigned playerIdx)
×
1739
{
1740
    return playerInfos.at(playerIdx);
×
1741
}
1742

1743
bool GameServer::IsHost(unsigned playerIdx) const
×
1744
{
1745
    return playerIdx < playerInfos.size() && playerInfos[playerIdx].isHost;
×
1746
}
1747

1748
int GameServer::GetTargetPlayer(const GameMessageWithPlayer& msg)
×
1749
{
1750
    if(msg.player != 0xFF)
×
1751
    {
1752
        if(msg.player < playerInfos.size() && (msg.player == msg.senderPlayerID || IsHost(msg.senderPlayerID)))
×
1753
        {
1754
            GameServerPlayer* networkPlayer = GetNetworkPlayer(msg.senderPlayerID);
×
1755
            if(networkPlayer->isActive())
×
1756
                return msg.player;
×
1757
            unsigned result = msg.player;
×
1758
            // Apply pending swaps
1759
            for(auto& pSwap : networkPlayer->getPendingSwaps()) //-V522
×
1760
            {
1761
                if(pSwap.first == result)
×
1762
                    result = pSwap.second;
×
1763
                else if(pSwap.second == result)
×
1764
                    result = pSwap.first;
×
1765
            }
1766
            return result;
×
1767
        }
1768
    } else if(msg.senderPlayerID < playerInfos.size())
×
1769
        return msg.senderPlayerID;
×
1770
    return -1;
×
1771
}
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