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
This commit is contained in:
toaster 2023-04-13 22:41:39 +01:00
parent d4dbc1fe30
commit ab68be49e1
14 changed files with 168 additions and 2 deletions

View file

@ -3369,6 +3369,10 @@ static void Got_KickCmd(UINT8 **p, INT32 playernum)
HU_AddChatText(va("\x82*%s left the game", player_names[pnum]), false); HU_AddChatText(va("\x82*%s left the game", player_names[pnum]), false);
kickreason = KR_LEAVE; kickreason = KR_LEAVE;
break; 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: case KICK_MSG_BANNED:
HU_AddChatText(va("\x82*%s has been banned (No reason given)", player_names[pnum]), false); HU_AddChatText(va("\x82*%s has been banned (No reason given)", player_names[pnum]), false);
kickreason = KR_BAN; 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); M_StartMessage(M_GetText("Server closed connection\n(Synch failure)\nPress (B)\n"), NULL, MM_NOTHING);
else if (msg == KICK_MSG_PING_HIGH) else if (msg == KICK_MSG_PING_HIGH)
M_StartMessage(M_GetText("Server closed connection\n(Broke delay limit)\nPress (B)\n"), NULL, MM_NOTHING); 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) else if (msg == KICK_MSG_BANNED)
M_StartMessage(M_GetText("You have been banned by the server\n\nPress (B)\n"), NULL, MM_NOTHING); 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) 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); 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) else if (msg == KICK_MSG_CUSTOM_BAN)

View file

@ -468,6 +468,7 @@ typedef enum
KICK_MSG_CUSTOM_BAN, // Ban message w/ custom reason KICK_MSG_CUSTOM_BAN, // Ban message w/ custom reason
KICK_MSG_TIMEOUT, // Player's connection timed out KICK_MSG_TIMEOUT, // Player's connection timed out
KICK_MSG_PING_HIGH, // Player hit the ping limit 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_CON_FAIL, // Player failed to resync game state
KICK_MSG_SIGFAIL, // Player failed signature check KICK_MSG_SIGFAIL, // Player failed signature check
KICK_MSG__MAX // Number of unique messages KICK_MSG__MAX // Number of unique messages

View file

@ -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_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); 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); 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}}; 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_maxplayers);
CV_RegisterVar(&cv_spectatorreentry); CV_RegisterVar(&cv_spectatorreentry);
CV_RegisterVar(&cv_duelspectatorreentry); CV_RegisterVar(&cv_duelspectatorreentry);
CV_RegisterVar(&cv_antigrief);
CV_RegisterVar(&cv_respawntime); CV_RegisterVar(&cv_respawntime);
// d_clisrv // d_clisrv

View file

@ -75,7 +75,7 @@ extern consvar_t cv_mute;
extern consvar_t cv_pause; extern consvar_t cv_pause;
extern consvar_t cv_restrictskinchange, cv_allowteamchange, cv_maxplayers, cv_respawntime; 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 // SRB2kart items
extern consvar_t cv_items[NUMKARTRESULTS-1]; extern consvar_t cv_items[NUMKARTRESULTS-1];

View file

@ -504,6 +504,7 @@ struct player_t
UINT8 oldposition; // Used for taunting when you pass someone UINT8 oldposition; // Used for taunting when you pass someone
UINT8 positiondelay; // Used for position number, so it can grow when passing UINT8 positiondelay; // Used for position number, so it can grow when passing
UINT32 distancetofinish; UINT32 distancetofinish;
UINT32 distancetofinishprev;
waypoint_t *currentwaypoint; waypoint_t *currentwaypoint;
waypoint_t *nextwaypoint; waypoint_t *nextwaypoint;
respawnvars_t respawn; // Respawn info respawnvars_t respawn; // Respawn info
@ -693,6 +694,9 @@ struct player_t
tic_t spectatorReentry; tic_t spectatorReentry;
UINT32 griefValue;
UINT8 griefStrikes;
UINT8 typing_timer; // Counts down while keystrokes are not emitted UINT8 typing_timer; // Counts down while keystrokes are not emitted
UINT8 typing_duration; // How long since resumed timer UINT8 typing_duration; // How long since resumed timer

View file

@ -2478,6 +2478,9 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
tic_t spectatorReentry; tic_t spectatorReentry;
UINT32 griefValue;
UINT8 griefStrikes;
UINT8 splitscreenindex; UINT8 splitscreenindex;
boolean spectator; boolean spectator;
boolean bot; boolean bot;
@ -2658,6 +2661,9 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
spectatorReentry = (betweenmaps ? 0 : players[player].spectatorReentry); spectatorReentry = (betweenmaps ? 0 : players[player].spectatorReentry);
griefValue = players[player].griefValue;
griefStrikes = players[player].griefStrikes;
if (!betweenmaps) if (!betweenmaps)
{ {
follower = players[player].follower; follower = players[player].follower;
@ -2739,6 +2745,8 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
p->botvars.controller = UINT16_MAX; p->botvars.controller = UINT16_MAX;
p->spectatorReentry = spectatorReentry; p->spectatorReentry = spectatorReentry;
p->griefValue = griefValue;
p->griefStrikes = griefStrikes;
memcpy(&p->itemRoulette, &itemRoulette, sizeof (p->itemRoulette)); memcpy(&p->itemRoulette, &itemRoulette, sizeof (p->itemRoulette));
memcpy(&p->respawn, &respawn, sizeof (p->respawn)); memcpy(&p->respawn, &respawn, sizeof (p->respawn));

View file

@ -5116,6 +5116,11 @@ static void K_DrawWaypointDebugger(void)
if (stplyr != &players[displayplayers[0]]) // only for p1 if (stplyr != &players[displayplayers[0]]) // only for p1
return; 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, 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, 166, 0, va("Next Waypoint ID: %d", K_GetWaypointID(stplyr->nextwaypoint)));
V_DrawString(8, 176, 0, va("Finishline Distance: %d", stplyr->distancetofinish)); V_DrawString(8, 176, 0, va("Finishline Distance: %d", stplyr->distancetofinish));

View file

@ -8578,6 +8578,9 @@ void K_UpdateDistanceFromFinishLine(player_t *const player)
player->nextwaypoint = nextwaypoint; 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 // nextwaypoint is now the waypoint that is in front of us
if ((player->exiting && !(player->pflags & PF_NOCONTEST)) || player->spectator) if ((player->exiting && !(player->pflags & PF_NOCONTEST)) || player->spectator)
{ {

View file

@ -225,6 +225,8 @@ static int player_get(lua_State *L)
lua_pushinteger(L, plr->positiondelay); lua_pushinteger(L, plr->positiondelay);
else if (fastcmp(field,"distancetofinish")) else if (fastcmp(field,"distancetofinish"))
lua_pushinteger(L, plr->distancetofinish); lua_pushinteger(L, plr->distancetofinish);
else if (fastcmp(field,"distancetofinishprev"))
lua_pushinteger(L, plr->distancetofinishprev);
else if (fastcmp(field,"airtime")) else if (fastcmp(field,"airtime"))
lua_pushinteger(L, plr->airtime); lua_pushinteger(L, plr->airtime);
else if (fastcmp(field,"flashing")) else if (fastcmp(field,"flashing"))
@ -502,6 +504,10 @@ static int player_get(lua_State *L)
lua_pushinteger(L, plr->jointime); lua_pushinteger(L, plr->jointime);
else if (fastcmp(field,"spectatorReentry")) else if (fastcmp(field,"spectatorReentry"))
lua_pushinteger(L, plr->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")) else if (fastcmp(field,"splitscreenindex"))
lua_pushinteger(L, plr->splitscreenindex); lua_pushinteger(L, plr->splitscreenindex);
#ifdef HWRENDER #ifdef HWRENDER
@ -613,6 +619,8 @@ static int player_set(lua_State *L)
plr->positiondelay = luaL_checkinteger(L, 3); plr->positiondelay = luaL_checkinteger(L, 3);
else if (fastcmp(field,"distancetofinish")) else if (fastcmp(field,"distancetofinish"))
return NOSET; return NOSET;
else if (fastcmp(field,"distancetofinishprev"))
return NOSET;
else if (fastcmp(field,"airtime")) else if (fastcmp(field,"airtime"))
plr->airtime = luaL_checkinteger(L, 3); plr->airtime = luaL_checkinteger(L, 3);
else if (fastcmp(field,"flashing")) else if (fastcmp(field,"flashing"))
@ -884,6 +892,10 @@ static int player_set(lua_State *L)
return NOSET; return NOSET;
else if (fastcmp(field,"spectatorReentry")) else if (fastcmp(field,"spectatorReentry"))
plr->spectatorReentry = (UINT32)luaL_checkinteger(L, 3); 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")) else if (fastcmp(field,"splitscreenindex"))
return NOSET; return NOSET;
#ifdef HWRENDER #ifdef HWRENDER

View file

@ -210,6 +210,7 @@ void P_PlayerThink(player_t *player);
void P_PlayerAfterThink(player_t *player); void P_PlayerAfterThink(player_t *player);
void P_DoPlayerExit(player_t *player); void P_DoPlayerExit(player_t *player);
void P_DoTimeOver(player_t *player); void P_DoTimeOver(player_t *player);
void P_CheckRaceGriefing(player_t *player, boolean dopunishment);
void P_ResetPlayerCheats(void); void P_ResetPlayerCheats(void);

View file

@ -11774,6 +11774,8 @@ void P_SpawnPlayer(INT32 playernum)
P_SetScale(mobj, mobj->destscale); P_SetScale(mobj, mobj->destscale);
P_FlashPal(p, 0, 0); // Resets P_FlashPal(p, 0, 0); // Resets
p->griefValue = 0;
K_InitStumbleIndicator(p); K_InitStumbleIndicator(p);
K_InitSliptideZipIndicator(p); K_InitSliptideZipIndicator(p);

View file

@ -196,6 +196,8 @@ static void P_NetArchivePlayers(savebuffer_t *save)
WRITEUINT32(save->p, players[i].jointime); WRITEUINT32(save->p, players[i].jointime);
WRITEUINT32(save->p, players[i].spectatorReentry); WRITEUINT32(save->p, players[i].spectatorReentry);
WRITEUINT32(save->p, players[i].griefValue);
WRITEUINT8(save->p, players[i].griefStrikes);
WRITEUINT8(save->p, players[i].splitscreenindex); 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].oldposition);
WRITEUINT8(save->p, players[i].positiondelay); WRITEUINT8(save->p, players[i].positiondelay);
WRITEUINT32(save->p, players[i].distancetofinish); 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].currentwaypoint));
WRITEUINT32(save->p, K_GetWaypointHeapIndex(players[i].nextwaypoint)); WRITEUINT32(save->p, K_GetWaypointHeapIndex(players[i].nextwaypoint));
WRITEUINT32(save->p, players[i].airtime); 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].jointime = READUINT32(save->p);
players[i].spectatorReentry = 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); 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].oldposition = READUINT8(save->p);
players[i].positiondelay = READUINT8(save->p); players[i].positiondelay = READUINT8(save->p);
players[i].distancetofinish = READUINT32(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].currentwaypoint = (waypoint_t *)(size_t)READUINT32(save->p);
players[i].nextwaypoint = (waypoint_t *)(size_t)READUINT32(save->p); players[i].nextwaypoint = (waypoint_t *)(size_t)READUINT32(save->p);
players[i].airtime = READUINT32(save->p); players[i].airtime = READUINT32(save->p);

View file

@ -781,7 +781,7 @@ void P_Ticker(boolean run)
// Run any "after all the other thinkers" stuff // Run any "after all the other thinkers" stuff
{ {
player_t *finishingPlayers[MAXPLAYERS]; player_t *finishingPlayers[MAXPLAYERS];
UINT8 numFinishingPlayers = 0; UINT8 numingame = 0, numFinishingPlayers = 0;
for (i = 0; i < MAXPLAYERS; i++) for (i = 0; i < MAXPLAYERS; i++)
{ {
@ -789,6 +789,11 @@ void P_Ticker(boolean run)
{ {
P_PlayerAfterThink(&players[i]); 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 // 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 && if (players[i].exiting == 1 && players[i].position == 1 &&
(players[i].pflags & (PF_HITFINISHLINE|PF_NOCONTEST)) == PF_HITFINISHLINE) (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));
}
} }
} }

View file

@ -1267,6 +1267,12 @@ void P_DoPlayerExit(player_t *player)
return; return;
} }
if (player->griefStrikes > 0)
{
// Remove a strike for finishing a race normally
player->griefStrikes--;
}
if (P_IsLocalPlayer(player) && (!player->spectator && !demo.playback)) if (P_IsLocalPlayer(player) && (!player->spectator && !demo.playback))
{ {
legitimateexit = true; legitimateexit = true;
@ -3792,6 +3798,12 @@ void P_DoTimeOver(player_t *player)
return; return;
} }
if (player->griefStrikes > 0)
{
// Remove a strike for finishing a race normally
player->griefStrikes--;
}
if (P_IsLocalPlayer(player) && !demo.playback) if (P_IsLocalPlayer(player) && !demo.playback)
{ {
legitimateexit = true; // SRB2kart: losing a race is still seeing it through to the end :p 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; 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) void P_SetPlayerAngle(player_t *player, angle_t angle)
{ {
P_ForceLocalAngle(player, angle); P_ForceLocalAngle(player, angle);