Skip to content

Commit

Permalink
Add host auto kick on sustained desync
Browse files Browse the repository at this point in the history
Defaults to kicking if a player remains in desync status for 10 seconds
  • Loading branch information
past-due committed Oct 28, 2024
1 parent cc6a1f3 commit f8ec4d8
Show file tree
Hide file tree
Showing 9 changed files with 126 additions and 6 deletions.
1 change: 1 addition & 0 deletions lib/netplay/netplay.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1933,6 +1933,7 @@ static bool swapPlayerIndexes(uint32_t playerIndexA, uint32_t playerIndexB)
// Swap certain ingame player-associated entries
std::swap(ingame.PingTimes[playerIndexA], ingame.PingTimes[playerIndexB]);
std::swap(ingame.LagCounter[playerIndexA], ingame.LagCounter[playerIndexB]);
std::swap(ingame.DesyncCounter[playerIndexA], ingame.DesyncCounter[playerIndexB]);
std::swap(ingame.VerifiedIdentity[playerIndexA], ingame.VerifiedIdentity[playerIndexB]);
std::swap(ingame.JoiningInProgress[playerIndexA], ingame.JoiningInProgress[playerIndexB]);
std::swap(ingame.PendingDisconnect[playerIndexA], ingame.PendingDisconnect[playerIndexB]);
Expand Down
2 changes: 2 additions & 0 deletions src/configuration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -582,6 +582,7 @@ bool loadConfig()
pie_EnableFog(false);
}
war_setAutoLagKickSeconds(iniGetInteger("hostAutoLagKickSeconds", war_getAutoLagKickSeconds()).value());
war_setAutoDesyncKickSeconds(iniGetInteger("hostAutoDesyncKickSeconds", war_getAutoDesyncKickSeconds()).value());
war_setDisableReplayRecording(iniGetBool("disableReplayRecord", war_getDisableReplayRecording()).value());
war_setMaxReplaysSaved(iniGetInteger("maxReplaysSaved", war_getMaxReplaysSaved()).value());
war_setOldLogsLimit(iniGetInteger("oldLogsLimit", war_getOldLogsLimit()).value());
Expand Down Expand Up @@ -773,6 +774,7 @@ bool saveConfig()
iniSetBool("autosaveEnabled", autosaveEnabled);
iniSetBool("fog", pie_GetFogEnabled());
iniSetInteger("hostAutoLagKickSeconds", war_getAutoLagKickSeconds());
iniSetInteger("hostAutoDesyncKickSeconds", war_getAutoDesyncKickSeconds());
iniSetBool("disableReplayRecord", war_getDisableReplayRecording());
iniSetInteger("maxReplaysSaved", war_getMaxReplaysSaved());
iniSetInteger("oldLogsLimit", war_getOldLogsLimit());
Expand Down
1 change: 1 addition & 0 deletions src/multiint.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6329,6 +6329,7 @@ bool WzMultiplayerOptionsTitleUI::startHost()
ingame.PingTimes[i] = 0;
ingame.VerifiedIdentity[i] = false;
ingame.LagCounter[i] = 0;
ingame.DesyncCounter[i] = 0;
ingame.lastSentPlayerDataCheck2[i].reset();
ingame.muteChat[i] = false;
}
Expand Down
3 changes: 3 additions & 0 deletions src/multijoin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ void clearPlayer(UDWORD player, bool quietly)
debug(LOG_NET, "R.I.P. %s (%u). quietly is %s", getPlayerName(player), player, quietly ? "true" : "false");

ingame.LagCounter[player] = 0;
ingame.DesyncCounter[player] = 0;
ingame.JoiningInProgress[player] = false; // if they never joined, reset the flag
ingame.DataIntegrity[player] = false;
ingame.hostChatPermissions[player] = false;
Expand Down Expand Up @@ -332,6 +333,7 @@ void handlePlayerLeftInGame(UDWORD player)
debug(LOG_NET, "R.I.P. %s (%u).", getPlayerName(player), player);

ingame.LagCounter[player] = 0;
ingame.DesyncCounter[player] = 0;
ingame.JoiningInProgress[player] = false; // if they never joined, reset the flag
ingame.PendingDisconnect[player] = false;
ingame.DataIntegrity[player] = false;
Expand Down Expand Up @@ -671,6 +673,7 @@ void setupNewPlayer(UDWORD player)

ingame.PingTimes[player] = 0; // Reset ping time
ingame.LagCounter[player] = 0;
ingame.DesyncCounter[player] = 0;
ingame.VerifiedIdentity[player] = false;
ingame.JoiningInProgress[player] = true; // Note that player is now joining
ingame.PendingDisconnect[player] = false;
Expand Down
1 change: 1 addition & 0 deletions src/multiopt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -724,6 +724,7 @@ bool multiGameShutdown()
ingame.startTime = std::chrono::steady_clock::time_point();
ingame.endTime = nullopt;
ingame.lastLagCheck = std::chrono::steady_clock::time_point();
ingame.lastDesyncCheck = std::chrono::steady_clock::time_point();
ingame.lastPlayerDataCheck2 = std::chrono::steady_clock::time_point();
NetPlay.isHost = false;
bMultiPlayer = false; // Back to single player mode
Expand Down
104 changes: 98 additions & 6 deletions src/multiplay.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -116,13 +116,13 @@ static bool sendDataCheck2();
void startMultiplayerGame();

// ////////////////////////////////////////////////////////////////////////////
// Auto Lag Kick Handling
// Auto Bad Connection Kick Handling

#define LAG_INITIAL_LOAD_GRACEPERIOD 60
#define LAG_CHECK_INTERVAL 1000
const std::chrono::milliseconds LagCheckInterval(LAG_CHECK_INTERVAL);

void autoLagKickRoutine()
void autoLagKickRoutine(std::chrono::steady_clock::time_point now)
{
if (!bMultiPlayer || !NetPlay.bComms || !NetPlay.isHost)
{
Expand All @@ -135,7 +135,6 @@ void autoLagKickRoutine()
return;
}

const std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now();
if (std::chrono::duration_cast<std::chrono::milliseconds>(now - ingame.lastLagCheck) < LagCheckInterval)
{
return;
Expand Down Expand Up @@ -225,12 +224,103 @@ void autoLagKickRoutine()
ingame.LagCounter[i] = 0;
}
else if (ingame.LagCounter[i] >= (LagAutoKickSeconds - 3)) {
std::string msg = astringf("Auto-kicking player %" PRIu32 " (\"%s\") in %u seconds.", i, getPlayerName(i), (LagAutoKickSeconds - ingame.LagCounter[i]));
std::string msg = astringf("Auto-kicking player %" PRIu32 " (\"%s\") in %u seconds. (lag)", i, getPlayerName(i), (LagAutoKickSeconds - ingame.LagCounter[i]));
debug(LOG_INFO, "%s", msg.c_str());
sendInGameSystemMessage(msg.c_str());
}
else if (ingame.LagCounter[i] % 15 == 0) { // every 15 seconds
std::string msg = astringf("Auto-kicking player %" PRIu32 " (\"%s\") in %u seconds.", i, getPlayerName(i), (LagAutoKickSeconds - ingame.LagCounter[i]));
std::string msg = astringf("Auto-kicking player %" PRIu32 " (\"%s\") in %u seconds. (lag)", i, getPlayerName(i), (LagAutoKickSeconds - ingame.LagCounter[i]));
debug(LOG_INFO, "%s", msg.c_str());
sendInGameSystemMessage(msg.c_str());
}
}
}

void autoLagKickRoutine()
{
if (!bMultiPlayer || !NetPlay.bComms || !NetPlay.isHost)
{
return;
}

const std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now();
autoLagKickRoutine(now);
}

#define DESYNC_CHECK_INTERVAL 1000
const std::chrono::milliseconds DesyncCheckInterval(DESYNC_CHECK_INTERVAL);

void autoDesyncKickRoutine(std::chrono::steady_clock::time_point now)
{
if (!bMultiPlayer || !NetPlay.bComms || !NetPlay.isHost)
{
return;
}

int DesyncAutoKickSeconds = war_getAutoDesyncKickSeconds();
if (DesyncAutoKickSeconds <= 0)
{
return;
}

if (std::chrono::duration_cast<std::chrono::milliseconds>(now - ingame.lastDesyncCheck) < DesyncCheckInterval)
{
return;
}

if (ingame.endTime.has_value())
{
// game ended - skip desync check / kick
return;
}

ingame.lastDesyncCheck = now;
uint32_t playerCheckLimit = MAX_PLAYERS;
for (uint32_t i = 0; i < playerCheckLimit; ++i)
{
if (!isHumanPlayer(i))
{
continue;
}
if (i == NetPlay.hostPlayer)
{
continue;
}
if (i > MAX_PLAYERS && !gtimeShouldWaitForPlayer(i))
{
continue;
}

bool isDesynced = NETcheckPlayerConnectionStatus(CONNECTIONSTATUS_DESYNC, i);

if (!isDesynced)
{
ingame.DesyncCounter[i] = 0;
continue;
}

if (ingame.PendingDisconnect[i])
{
// player already technically left, but we're still in the "pre-game" phase so the GAME_PLAYER_LEFT hasn't been processed yet
continue;
}

ingame.DesyncCounter[i]++;
if (ingame.DesyncCounter[i] >= DesyncAutoKickSeconds) {
std::string msg = astringf("Auto-kicking player %" PRIu32 " (\"%s\") because of desync. (Timeout: %u seconds)", i, getPlayerName(i), DesyncAutoKickSeconds);
debug(LOG_INFO, "%s", msg.c_str());
sendInGameSystemMessage(msg.c_str());
wz_command_interface_output("WZEVENT: desync-kick: %u %s\n", i, NetPlay.players[i].IPtextAddress);
kickPlayer(i, "Your game simulation deviated too far from the host - desync.", ERROR_CONNECTION, false);
ingame.DesyncCounter[i] = 0;
}
else if (ingame.DesyncCounter[i] >= (DesyncAutoKickSeconds - 3)) {
std::string msg = astringf("Auto-kicking player %" PRIu32 " (\"%s\") in %u seconds. (desync)", i, getPlayerName(i), (DesyncAutoKickSeconds - ingame.DesyncCounter[i]));
debug(LOG_INFO, "%s", msg.c_str());
sendInGameSystemMessage(msg.c_str());
}
else if (ingame.DesyncCounter[i] % 2 == 0) { // every 2 seconds
std::string msg = astringf("Auto-kicking player %" PRIu32 " (\"%s\") in %u seconds. (desync)", i, getPlayerName(i), (DesyncAutoKickSeconds - ingame.DesyncCounter[i]));
debug(LOG_INFO, "%s", msg.c_str());
sendInGameSystemMessage(msg.c_str());
}
Expand Down Expand Up @@ -414,7 +504,9 @@ bool multiPlayerLoop()

if (NetPlay.isHost)
{
autoLagKickRoutine();
const std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now();
autoLagKickRoutine(now);
autoDesyncKickRoutine(now);
processPendingKickVotes();
}

Expand Down
2 changes: 2 additions & 0 deletions src/multiplay.h
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ struct MULTIPLAYERINGAME
{
UDWORD PingTimes[MAX_CONNECTED_PLAYERS]; // store for pings.
int LagCounter[MAX_CONNECTED_PLAYERS];
int DesyncCounter[MAX_CONNECTED_PLAYERS];
bool VerifiedIdentity[MAX_CONNECTED_PLAYERS]; // if the multistats identity has been verified.
bool localOptionsReceived; // used to show if we have game options yet..
bool localJoiningInProgress; // used before we know our player number.
Expand All @@ -120,6 +121,7 @@ struct MULTIPLAYERINGAME
std::chrono::steady_clock::time_point startTime;
optional<std::chrono::steady_clock::time_point> endTime;
std::chrono::steady_clock::time_point lastLagCheck;
std::chrono::steady_clock::time_point lastDesyncCheck;
optional<std::chrono::steady_clock::time_point> lastSentPlayerDataCheck2[MAX_CONNECTED_PLAYERS] = {};
std::chrono::steady_clock::time_point lastPlayerDataCheck2;
bool muteChat[MAX_CONNECTED_PLAYERS] = {false}; // the local client-set mute status for this player
Expand Down
16 changes: 16 additions & 0 deletions src/warzoneconfig.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ struct WARZONE_GLOBALS
JS_BACKEND jsBackend = (JS_BACKEND)0;
bool autoAdjustDisplayScale = true;
int autoLagKickSeconds = 60;
int autoDesyncKickSeconds = 10;
bool disableReplayRecording = false;
int maxReplaysSaved = MAX_REPLAY_FILES;
int oldLogsLimit = MAX_OLD_LOGS;
Expand Down Expand Up @@ -486,6 +487,21 @@ void war_setAutoLagKickSeconds(int seconds)
warGlobs.autoLagKickSeconds = seconds;
}

int war_getAutoDesyncKickSeconds()
{
return warGlobs.autoDesyncKickSeconds;
}

void war_setAutoDesyncKickSeconds(int seconds)
{
seconds = std::max(seconds, 0);
if (seconds > 0)
{
seconds = std::max(seconds, 10);
}
warGlobs.autoDesyncKickSeconds = seconds;
}

bool war_getDisableReplayRecording()
{
return warGlobs.disableReplayRecording;
Expand Down
2 changes: 2 additions & 0 deletions src/warzoneconfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,8 @@ bool war_getAutoAdjustDisplayScale();
void war_setAutoAdjustDisplayScale(bool autoAdjustDisplayScale);
int war_getAutoLagKickSeconds();
void war_setAutoLagKickSeconds(int seconds);
int war_getAutoDesyncKickSeconds();
void war_setAutoDesyncKickSeconds(int seconds);
bool war_getDisableReplayRecording();
void war_setDisableReplayRecording(bool disable);
int war_getMaxReplaysSaved();
Expand Down

0 comments on commit f8ec4d8

Please sign in to comment.