From ab68be49e1ca38187e080d1f8369838ceaecfb7c Mon Sep 17 00:00:00 2001 From: toaster Date: Thu, 13 Apr 2023 22:41:39 +0100 Subject: [PATCH] cv_antigrief - Increments a timer on human players who aren't making progress, does it even faster if they're going backwards. - Only applies in: - Netgames - GTR_CIRCUIT after the timer starts - If there's no timelimit, pointlimit, or K_Cooperative (because unproductive behaviour there will be punished by other rules) - The rate at which this changes needs trial and error, but getting the feature functional is more important to start out with. - If this timer reaches cv_antigrief's value in seconds , the player gets a "Grief Strike" - This doesn't happen if: - There's only one active player in the server, so FREE PLAY permits mappers to test what increments/decrements the counter - Turn `debugwaypoints` on to observe this - The cvar is set to 0 - Less than 3 grief strikes is a forced spectate - Anything more is a kick via "automatic grief detection" - Unless your node is the host (or an admin) - Remove grief strike strike for finishing normally # Conflicts: # src/d_clisrv.h --- src/d_clisrv.c | 8 ++++ src/d_clisrv.h | 1 + src/d_netcmd.c | 4 ++ src/d_netcmd.h | 2 +- src/d_player.h | 4 ++ src/g_game.c | 8 ++++ src/k_hud.c | 5 +++ src/k_kart.c | 3 ++ src/lua_playerlib.c | 12 ++++++ src/p_local.h | 1 + src/p_mobj.c | 2 + src/p_saveg.c | 6 +++ src/p_tick.c | 21 +++++++++- src/p_user.c | 93 +++++++++++++++++++++++++++++++++++++++++++++ 14 files changed, 168 insertions(+), 2 deletions(-) diff --git a/src/d_clisrv.c b/src/d_clisrv.c index c0393ec6a..cc3a33689 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -3369,6 +3369,10 @@ static void Got_KickCmd(UINT8 **p, INT32 playernum) HU_AddChatText(va("\x82*%s left the game", player_names[pnum]), false); kickreason = KR_LEAVE; break; + case KICK_MSG_GRIEF: + HU_AddChatText(va("\x82*%s has been kicked (Automatic grief detection)", player_names[pnum]), false); + kickreason = KR_KICK; + break; case KICK_MSG_BANNED: HU_AddChatText(va("\x82*%s has been banned (No reason given)", player_names[pnum]), false); kickreason = KR_BAN; @@ -3414,8 +3418,12 @@ static void Got_KickCmd(UINT8 **p, INT32 playernum) M_StartMessage(M_GetText("Server closed connection\n(Synch failure)\nPress (B)\n"), NULL, MM_NOTHING); else if (msg == KICK_MSG_PING_HIGH) M_StartMessage(M_GetText("Server closed connection\n(Broke delay limit)\nPress (B)\n"), NULL, MM_NOTHING); + else if (msg == KICK_MSG_TIMEOUT) // this one will probably never be seen? + M_StartMessage(M_GetText("Connection timed out\n\nPress (B)\n"), NULL, MM_NOTHING); else if (msg == KICK_MSG_BANNED) M_StartMessage(M_GetText("You have been banned by the server\n\nPress (B)\n"), NULL, MM_NOTHING); + else if (msg == KICK_MSG_CUSTOM_KICK) + M_StartMessage(M_GetText("You have been kicked\n(Automatic grief detection)\nPress (B)\n"), NULL, MM_NOTHING); else if (msg == KICK_MSG_CUSTOM_KICK) M_StartMessage(va(M_GetText("You have been kicked\n(%s)\nPress (B)\n"), reason), NULL, MM_NOTHING); else if (msg == KICK_MSG_CUSTOM_BAN) diff --git a/src/d_clisrv.h b/src/d_clisrv.h index 7bc897d02..07e10f0e2 100644 --- a/src/d_clisrv.h +++ b/src/d_clisrv.h @@ -468,6 +468,7 @@ typedef enum KICK_MSG_CUSTOM_BAN, // Ban message w/ custom reason KICK_MSG_TIMEOUT, // Player's connection timed out KICK_MSG_PING_HIGH, // Player hit the ping limit + KICK_MSG_GRIEF, // Player was detected by antigrief KICK_MSG_CON_FAIL, // Player failed to resync game state KICK_MSG_SIGFAIL, // Player failed signature check KICK_MSG__MAX // Number of unique messages diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 4abc18251..0507e73bb 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -266,6 +266,9 @@ static CV_PossibleValue_t spectatorreentry_cons_t[] = {{0, "MIN"}, {10*60, "MAX" consvar_t cv_spectatorreentry = CVAR_INIT ("spectatorreentry", "30", CV_NETVAR, spectatorreentry_cons_t, NULL); consvar_t cv_duelspectatorreentry = CVAR_INIT ("duelspectatorreentry", "180", CV_NETVAR, spectatorreentry_cons_t, NULL); +static CV_PossibleValue_t antigrief_cons_t[] = {{10, "MIN"}, {120, "MAX"}, {0, "Off"}, {0, NULL}}; +consvar_t cv_antigrief = CVAR_INIT ("antigrief", "30", CV_NETVAR, antigrief_cons_t, NULL); + consvar_t cv_startinglives = CVAR_INIT ("startinglives", "3", CV_NETVAR|CV_CHEAT|CV_NOSHOWHELP, startingliveslimit_cons_t, NULL); static CV_PossibleValue_t respawntime_cons_t[] = {{1, "MIN"}, {30, "MAX"}, {0, "Off"}, {0, NULL}}; @@ -794,6 +797,7 @@ void D_RegisterServerCommands(void) CV_RegisterVar(&cv_maxplayers); CV_RegisterVar(&cv_spectatorreentry); CV_RegisterVar(&cv_duelspectatorreentry); + CV_RegisterVar(&cv_antigrief); CV_RegisterVar(&cv_respawntime); // d_clisrv diff --git a/src/d_netcmd.h b/src/d_netcmd.h index 7635296df..ebc87bbfc 100644 --- a/src/d_netcmd.h +++ b/src/d_netcmd.h @@ -75,7 +75,7 @@ extern consvar_t cv_mute; extern consvar_t cv_pause; extern consvar_t cv_restrictskinchange, cv_allowteamchange, cv_maxplayers, cv_respawntime; -extern consvar_t cv_spectatorreentry, cv_duelspectatorreentry; +extern consvar_t cv_spectatorreentry, cv_duelspectatorreentry, cv_antigrief; // SRB2kart items extern consvar_t cv_items[NUMKARTRESULTS-1]; diff --git a/src/d_player.h b/src/d_player.h index b339851f5..e9f166399 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -504,6 +504,7 @@ struct player_t UINT8 oldposition; // Used for taunting when you pass someone UINT8 positiondelay; // Used for position number, so it can grow when passing UINT32 distancetofinish; + UINT32 distancetofinishprev; waypoint_t *currentwaypoint; waypoint_t *nextwaypoint; respawnvars_t respawn; // Respawn info @@ -693,6 +694,9 @@ struct player_t tic_t spectatorReentry; + UINT32 griefValue; + UINT8 griefStrikes; + UINT8 typing_timer; // Counts down while keystrokes are not emitted UINT8 typing_duration; // How long since resumed timer diff --git a/src/g_game.c b/src/g_game.c index 6a917aee2..91d150155 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -2478,6 +2478,9 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) tic_t spectatorReentry; + UINT32 griefValue; + UINT8 griefStrikes; + UINT8 splitscreenindex; boolean spectator; boolean bot; @@ -2658,6 +2661,9 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) spectatorReentry = (betweenmaps ? 0 : players[player].spectatorReentry); + griefValue = players[player].griefValue; + griefStrikes = players[player].griefStrikes; + if (!betweenmaps) { follower = players[player].follower; @@ -2739,6 +2745,8 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) p->botvars.controller = UINT16_MAX; p->spectatorReentry = spectatorReentry; + p->griefValue = griefValue; + p->griefStrikes = griefStrikes; memcpy(&p->itemRoulette, &itemRoulette, sizeof (p->itemRoulette)); memcpy(&p->respawn, &respawn, sizeof (p->respawn)); diff --git a/src/k_hud.c b/src/k_hud.c index d23c107a4..62c4112af 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -5116,6 +5116,11 @@ static void K_DrawWaypointDebugger(void) if (stplyr != &players[displayplayers[0]]) // only for p1 return; + if (netgame) + { + V_DrawString(8, 146, 0, va("Online griefing: [%u, %u]", stplyr->griefValue/TICRATE, stplyr->griefStrikes)); + } + V_DrawString(8, 156, 0, va("Current Waypoint ID: %d", K_GetWaypointID(stplyr->currentwaypoint))); V_DrawString(8, 166, 0, va("Next Waypoint ID: %d", K_GetWaypointID(stplyr->nextwaypoint))); V_DrawString(8, 176, 0, va("Finishline Distance: %d", stplyr->distancetofinish)); diff --git a/src/k_kart.c b/src/k_kart.c index 5d836af09..c1e29adc2 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -8578,6 +8578,9 @@ void K_UpdateDistanceFromFinishLine(player_t *const player) player->nextwaypoint = nextwaypoint; } + // Update prev value (used for grief prevention code) + player->distancetofinishprev = player->distancetofinish; + // nextwaypoint is now the waypoint that is in front of us if ((player->exiting && !(player->pflags & PF_NOCONTEST)) || player->spectator) { diff --git a/src/lua_playerlib.c b/src/lua_playerlib.c index 6ab5946ea..4dc28ac18 100644 --- a/src/lua_playerlib.c +++ b/src/lua_playerlib.c @@ -225,6 +225,8 @@ static int player_get(lua_State *L) lua_pushinteger(L, plr->positiondelay); else if (fastcmp(field,"distancetofinish")) lua_pushinteger(L, plr->distancetofinish); + else if (fastcmp(field,"distancetofinishprev")) + lua_pushinteger(L, plr->distancetofinishprev); else if (fastcmp(field,"airtime")) lua_pushinteger(L, plr->airtime); else if (fastcmp(field,"flashing")) @@ -502,6 +504,10 @@ static int player_get(lua_State *L) lua_pushinteger(L, plr->jointime); else if (fastcmp(field,"spectatorReentry")) lua_pushinteger(L, plr->spectatorReentry); + else if (fastcmp(field,"griefValue")) + lua_pushinteger(L, plr->griefValue); + else if (fastcmp(field,"griefStrikes")) + lua_pushinteger(L, plr->griefStrikes); else if (fastcmp(field,"splitscreenindex")) lua_pushinteger(L, plr->splitscreenindex); #ifdef HWRENDER @@ -613,6 +619,8 @@ static int player_set(lua_State *L) plr->positiondelay = luaL_checkinteger(L, 3); else if (fastcmp(field,"distancetofinish")) return NOSET; + else if (fastcmp(field,"distancetofinishprev")) + return NOSET; else if (fastcmp(field,"airtime")) plr->airtime = luaL_checkinteger(L, 3); else if (fastcmp(field,"flashing")) @@ -884,6 +892,10 @@ static int player_set(lua_State *L) return NOSET; else if (fastcmp(field,"spectatorReentry")) plr->spectatorReentry = (UINT32)luaL_checkinteger(L, 3); + else if (fastcmp(field,"griefValue")) + plr->griefValue = (UINT32)luaL_checkinteger(L, 3); + else if (fastcmp(field,"griefStrikes")) + plr->griefStrikes = (UINT8)luaL_checkinteger(L, 3); else if (fastcmp(field,"splitscreenindex")) return NOSET; #ifdef HWRENDER diff --git a/src/p_local.h b/src/p_local.h index e44b3bbe3..0c7b3bcb8 100644 --- a/src/p_local.h +++ b/src/p_local.h @@ -210,6 +210,7 @@ void P_PlayerThink(player_t *player); void P_PlayerAfterThink(player_t *player); void P_DoPlayerExit(player_t *player); void P_DoTimeOver(player_t *player); +void P_CheckRaceGriefing(player_t *player, boolean dopunishment); void P_ResetPlayerCheats(void); diff --git a/src/p_mobj.c b/src/p_mobj.c index 1ee62a5ff..d9c502ec1 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -11774,6 +11774,8 @@ void P_SpawnPlayer(INT32 playernum) P_SetScale(mobj, mobj->destscale); P_FlashPal(p, 0, 0); // Resets + p->griefValue = 0; + K_InitStumbleIndicator(p); K_InitSliptideZipIndicator(p); diff --git a/src/p_saveg.c b/src/p_saveg.c index edbb9b768..027174dad 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -196,6 +196,8 @@ static void P_NetArchivePlayers(savebuffer_t *save) WRITEUINT32(save->p, players[i].jointime); WRITEUINT32(save->p, players[i].spectatorReentry); + WRITEUINT32(save->p, players[i].griefValue); + WRITEUINT8(save->p, players[i].griefStrikes); WRITEUINT8(save->p, players[i].splitscreenindex); @@ -274,6 +276,7 @@ static void P_NetArchivePlayers(savebuffer_t *save) WRITEUINT8(save->p, players[i].oldposition); WRITEUINT8(save->p, players[i].positiondelay); WRITEUINT32(save->p, players[i].distancetofinish); + WRITEUINT32(save->p, players[i].distancetofinishprev); WRITEUINT32(save->p, K_GetWaypointHeapIndex(players[i].currentwaypoint)); WRITEUINT32(save->p, K_GetWaypointHeapIndex(players[i].nextwaypoint)); WRITEUINT32(save->p, players[i].airtime); @@ -601,6 +604,8 @@ static void P_NetUnArchivePlayers(savebuffer_t *save) players[i].jointime = READUINT32(save->p); players[i].spectatorReentry = READUINT32(save->p); + players[i].griefValue = READUINT32(save->p); + players[i].griefStrikes = READUINT8(save->p); players[i].splitscreenindex = READUINT8(save->p); @@ -653,6 +658,7 @@ static void P_NetUnArchivePlayers(savebuffer_t *save) players[i].oldposition = READUINT8(save->p); players[i].positiondelay = READUINT8(save->p); players[i].distancetofinish = READUINT32(save->p); + players[i].distancetofinishprev = READUINT32(save->p); players[i].currentwaypoint = (waypoint_t *)(size_t)READUINT32(save->p); players[i].nextwaypoint = (waypoint_t *)(size_t)READUINT32(save->p); players[i].airtime = READUINT32(save->p); diff --git a/src/p_tick.c b/src/p_tick.c index 97260c727..074af2c7a 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -781,7 +781,7 @@ void P_Ticker(boolean run) // Run any "after all the other thinkers" stuff { player_t *finishingPlayers[MAXPLAYERS]; - UINT8 numFinishingPlayers = 0; + UINT8 numingame = 0, numFinishingPlayers = 0; for (i = 0; i < MAXPLAYERS; i++) { @@ -789,6 +789,11 @@ void P_Ticker(boolean run) { P_PlayerAfterThink(&players[i]); + if (players[i].spectator == true) + continue; + + numingame++; + // Check for the number of ties for first place after every player has thunk run for this tic if (players[i].exiting == 1 && players[i].position == 1 && (players[i].pflags & (PF_HITFINISHLINE|PF_NOCONTEST)) == PF_HITFINISHLINE) @@ -798,11 +803,25 @@ void P_Ticker(boolean run) } } + if ((netgame) // Antigrief is supposed to apply? + && !(K_Cooperative() || timelimitintics > 0 || g_pointlimit > 0) // There are rules that will punish a griefing player + && (gametyperules & GTR_CIRCUIT) && (leveltime > starttime)) // The following only detects race griefing { + for (i = 0; i < MAXPLAYERS; i++) { + if (playeringame[i] && players[i].mo && !P_MobjWasRemoved(players[i].mo)) + { + if (players[i].spectator == true) + continue; + if (players[i].exiting || (players[i].pflags & PF_NOCONTEST)) + continue; + if (players[i].bot == true) + continue; + P_CheckRaceGriefing(&players[i], (numingame > 1)); + } } } diff --git a/src/p_user.c b/src/p_user.c index b6aadd97a..dad11f891 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -1267,6 +1267,12 @@ void P_DoPlayerExit(player_t *player) return; } + if (player->griefStrikes > 0) + { + // Remove a strike for finishing a race normally + player->griefStrikes--; + } + if (P_IsLocalPlayer(player) && (!player->spectator && !demo.playback)) { legitimateexit = true; @@ -3792,6 +3798,12 @@ void P_DoTimeOver(player_t *player) return; } + if (player->griefStrikes > 0) + { + // Remove a strike for finishing a race normally + player->griefStrikes--; + } + if (P_IsLocalPlayer(player) && !demo.playback) { legitimateexit = true; // SRB2kart: losing a race is still seeing it through to the end :p @@ -4586,6 +4598,87 @@ void P_PlayerAfterThink(player_t *player) player->mo->pmomz = 0; } +void P_CheckRaceGriefing(player_t *player, boolean dopunishment) +{ + const UINT32 griefMax = cv_antigrief.value * TICRATE; + const UINT8 n = player - players; + + const fixed_t requireDist = (12*player->mo->scale) / FRACUNIT; + INT32 progress = player->distancetofinishprev - player->distancetofinish; + boolean exceptions = ( + player->flashing != 0 + || player->mo->hitlag != 0 + || player->airtime > 3*TICRATE/2 + || (player->justbumped > 0 && player->justbumped < bumptime-1) + ); + + // Don't punish if the cvar is turned off, + // otherwise NOBODY would be able to play! + if (griefMax == 0) + { + dopunishment = false; + } + + if (!exceptions && (progress < requireDist)) + { + // If antigrief is disabled, we don't want the + // player getting into a hole so deep no amount + // of good behaviour could ever make up for it. + if (player->griefValue < griefMax) + { + // Making no progress, start counting against you. + player->griefValue++; + if (progress < -requireDist && player->griefValue < griefMax) + { + // Making NEGATIVE progress? Start counting even harder. + player->griefValue++; + } + } + } + else if (player->griefValue > 0) + { + // Playing normally. + player->griefValue--; + } + + if (dopunishment && player->griefValue > griefMax) + { + if (player->griefStrikes < 3) + { + player->griefStrikes++; + } + + player->griefValue = 0; + + if (server) + { + if (player->griefStrikes == 3 && playernode[n] != servernode +#ifndef DEVELOP + && !IsPlayerAdmin(n) +#endif + ) + { + // Send kick + SendKick(n, KICK_MSG_GRIEF); + } + else + { + // Send spectate + changeteam_union NetPacket; + UINT16 usvalue; + + NetPacket.value.l = NetPacket.value.b = 0; + NetPacket.packet.newteam = 0; + NetPacket.packet.playernum = n; + NetPacket.packet.verification = true; + + usvalue = SHORT(NetPacket.value.l|NetPacket.value.b); + SendNetXCmd(XD_TEAMCHANGE, &usvalue, sizeof(usvalue)); + } + } + } +} + void P_SetPlayerAngle(player_t *player, angle_t angle) { P_ForceLocalAngle(player, angle);