From d50e3a18955f6f6717a12f4a724f4ab9153817e6 Mon Sep 17 00:00:00 2001 From: toaster Date: Thu, 13 Apr 2023 20:22:02 +0100 Subject: [PATCH 01/10] Got_Teamchange: Do not repeatedly declare a player has become a spectator if they already were --- src/d_netcmd.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index a470bc9ce..7a3738157 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -3864,7 +3864,7 @@ static void Command_ServerTeamChange_f(void) static void Got_Teamchange(UINT8 **cp, INT32 playernum) { changeteam_union NetPacket; - boolean error = false; + boolean error = false, wasspectator = false; NetPacket.value.l = NetPacket.value.b = READINT16(*cp); if (!G_GametypeHasTeams() && !G_GametypeHasSpectators()) //Make sure you're in the right gametype. @@ -3935,7 +3935,9 @@ static void Got_Teamchange(UINT8 **cp, INT32 playernum) //Safety first! // (not respawning spectators here...) - if (!players[playernum].spectator && gamestate == GS_LEVEL) + wasspectator = (players[playernum].spectator == true); + + if (!wasspectator && gamestate == GS_LEVEL) { if (players[playernum].mo) { @@ -3996,7 +3998,7 @@ static void Got_Teamchange(UINT8 **cp, INT32 playernum) { CONS_Printf(M_GetText("%s switched to the %c%s%c.\n"), player_names[playernum], '\x84', M_GetText("Blue Team"), '\x80'); } - else if (NetPacket.packet.newteam == 0) + else if (NetPacket.packet.newteam == 0 && !wasspectator) HU_AddChatText(va("\x82*%s became a spectator.", player_names[playernum]), false); // "entered the game" text was moved to P_SpectatorJoinGame /*if (G_GametypeHasTeams()) From 15b0141700deeeb1eddcb88f8e875b49a8e4626f Mon Sep 17 00:00:00 2001 From: toaster Date: Thu, 13 Apr 2023 21:17:05 +0100 Subject: [PATCH 02/10] Spectator re-entry timer - On becoming a spectator in a netgame, there is a delay before you can de-spectate. - 30 seconds by default. - This can be changed using the cvar `spectatorreentry`. - ...unless there's only two people left including you, in which case it's three minutes!! - This can be changed using the cvar `duelspectatorreentry`. - If spectatorreentry is set to greater than duelspectatorrentry, the former is used instead. - This timer is wiped on mapload and intermission, so NEW CHALLENGER APPROACHING !! and level changes in general allow people in. - General purpose cleanup of K_CheckSpectateStatus --- src/d_netcmd.c | 35 +++++++++++++++++++++++++ src/d_netcmd.h | 1 + src/d_player.h | 2 ++ src/g_game.c | 6 +++++ src/k_kart.c | 63 +++++++++++++++++++++++++++++++++++---------- src/lua_playerlib.c | 4 +++ src/p_mobj.c | 10 +++---- src/p_saveg.c | 4 +++ 8 files changed, 106 insertions(+), 19 deletions(-) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 7a3738157..4abc18251 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -262,6 +262,10 @@ consvar_t cv_allowteamchange = CVAR_INIT ("allowteamchange", "Yes", CV_NETVAR, C static CV_PossibleValue_t maxplayers_cons_t[] = {{1, "MIN"}, {MAXPLAYERS, "MAX"}, {0, NULL}}; consvar_t cv_maxplayers = CVAR_INIT ("maxplayers", "8", CV_NETVAR, maxplayers_cons_t, NULL); +static CV_PossibleValue_t spectatorreentry_cons_t[] = {{0, "MIN"}, {10*60, "MAX"}, {0, 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_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}}; @@ -788,6 +792,8 @@ void D_RegisterServerCommands(void) CV_RegisterVar(&cv_restrictskinchange); CV_RegisterVar(&cv_allowteamchange); CV_RegisterVar(&cv_maxplayers); + CV_RegisterVar(&cv_spectatorreentry); + CV_RegisterVar(&cv_duelspectatorreentry); CV_RegisterVar(&cv_respawntime); // d_clisrv @@ -1594,6 +1600,35 @@ static void FinalisePlaystateChange(INT32 playernum) // Clear player score and rings if a spectator. if (players[playernum].spectator) { + // To attempt to discourage rage-spectators, we delay any rejoining. + // If you're engaging in a DUEL and quit early, in addition to the + // indignity of losing your PWR, you get a special extra-long delay. + if (netgame) + { + UINT8 pcount = 0; + + if (cv_duelspectatorreentry.value > cv_spectatorreentry.value) + { + UINT8 i; + + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i] || players[i].spectator) + continue; + if (++pcount < 2) + continue; + break; + } + } + + players[playernum].spectatorReentry = + (pcount == 1) + ? (cv_duelspectatorreentry.value * TICRATE) + : (cv_spectatorreentry.value * TICRATE); + + //CONS_Printf("player %u got re-entry of %u\n", playernum, players[playernum].spectatorReentry); + } + if (gametyperules & GTR_POINTLIMIT) // SRB2kart { players[playernum].roundscore = 0; diff --git a/src/d_netcmd.h b/src/d_netcmd.h index 5bfccb5f6..7635296df 100644 --- a/src/d_netcmd.h +++ b/src/d_netcmd.h @@ -75,6 +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; // SRB2kart items extern consvar_t cv_items[NUMKARTRESULTS-1]; diff --git a/src/d_player.h b/src/d_player.h index d0313aaed..b339851f5 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -691,6 +691,8 @@ struct player_t tic_t jointime; // Timer when player joins game to change skin/color + tic_t spectatorReentry; + 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 03346abc3..6a917aee2 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -2476,6 +2476,8 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) tic_t jointime; + tic_t spectatorReentry; + UINT8 splitscreenindex; boolean spectator; boolean bot; @@ -2654,6 +2656,8 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) saveroundconditions = true; } + spectatorReentry = (betweenmaps ? 0 : players[player].spectatorReentry); + if (!betweenmaps) { follower = players[player].follower; @@ -2734,6 +2738,8 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) p->botvars.rubberband = FRACUNIT; p->botvars.controller = UINT16_MAX; + p->spectatorReentry = spectatorReentry; + memcpy(&p->itemRoulette, &itemRoulette, sizeof (p->itemRoulette)); memcpy(&p->respawn, &respawn, sizeof (p->respawn)); diff --git a/src/k_kart.c b/src/k_kart.c index fa8828057..545143438 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -11385,10 +11385,16 @@ void K_CheckSpectateStatus(void) { if (!playeringame[i]) continue; + if (players[i].spectator && (players[i].pflags & PF_WANTSTOJOIN)) players[i].spectatewait++; else players[i].spectatewait = 0; + + if (gamestate != GS_LEVEL) + players[i].spectatorReentry = 0; + else if (players[i].spectatorReentry > 0) + players[i].spectatorReentry--; } // No one's allowed to join @@ -11404,22 +11410,45 @@ void K_CheckSpectateStatus(void) if (!players[i].spectator) { numingame++; - if (cv_maxplayers.value && numingame >= cv_maxplayers.value) // DON'T allow if you've hit the in-game player cap + + // DON'T allow if you've hit the in-game player cap + if (cv_maxplayers.value && numingame >= cv_maxplayers.value) return; - if (gamestate != GS_LEVEL) // Allow if you're not in a level + + // Allow if you're not in a level + if (gamestate != GS_LEVEL) continue; - if (players[i].exiting) // DON'T allow if anyone's exiting + + // DON'T allow if anyone's exiting + if (players[i].exiting) return; - if (numingame < 2 || leveltime < starttime || mapreset) // Allow if the match hasn't started yet + + // Allow if the match hasn't started yet + if (numingame < 2 || leveltime < starttime || mapreset) continue; - if (leveltime > (starttime + 20*TICRATE)) // DON'T allow if the match is 20 seconds in + + // DON'T allow if the match is 20 seconds in + if (leveltime > (starttime + 20*TICRATE)) return; - if ((gametyperules & GTR_CIRCUIT) && players[i].laps >= 2) // DON'T allow if the race is at 2 laps + + // DON'T allow if the race is at 2 laps + if ((gametyperules & GTR_CIRCUIT) && players[i].laps >= 2) return; + continue; } - else if (players[i].bot || !(players[i].pflags & PF_WANTSTOJOIN)) + + if (players[i].bot) + { + // Spectating bots are controlled by other mechanisms. continue; + } + + if (!(players[i].pflags & PF_WANTSTOJOIN)) + { + // This spectator does not want to join. + continue; + } respawnlist[numjoiners++] = i; } @@ -11428,8 +11457,8 @@ void K_CheckSpectateStatus(void) if (!numjoiners) return; - // Organize by spectate wait timer - if (cv_maxplayers.value) + // Organize by spectate wait timer (if there's more than one to sort) + if (cv_maxplayers.value && numjoiners > 1) { UINT8 oldrespawnlist[MAXPLAYERS]; memcpy(oldrespawnlist, respawnlist, numjoiners); @@ -11454,17 +11483,25 @@ void K_CheckSpectateStatus(void) } // Finally, we can de-spectate everyone! - for (i = 0; i < numjoiners; i++) + for (i = 0, j = 0; i < numjoiners; i++) { - if (cv_maxplayers.value && numingame+i >= cv_maxplayers.value) // Hit the in-game player cap while adding people? - break; + // This person has their reentry cooldown active. + if (netgame && players[respawnlist[i]].spectatorReentry > 0 && numingame > 0) + continue; + //CONS_Printf("player %s is joining on tic %d\n", player_names[respawnlist[i]], leveltime); + P_SpectatorJoinGame(&players[respawnlist[i]]); + j++; // j is being used as the number of players added + + // Hit the in-game player cap while adding people? + if (cv_maxplayers.value && numingame+j >= cv_maxplayers.value) + break; } // Reset the match when 2P joins 1P, DUEL mode // Reset the match when 3P joins 1P and 2P, DUEL mode must be disabled - if (!mapreset && gamestate == GS_LEVEL && (numingame < 3 && numingame+i >= 2)) // use previous i value + if (j > 0 && !mapreset && gamestate == GS_LEVEL && (numingame < 3 && numingame+j >= 2)) { S_ChangeMusicInternal("chalng", false); // COME ON mapreset = 3*TICRATE; // Even though only the server uses this for game logic, set for everyone for HUD diff --git a/src/lua_playerlib.c b/src/lua_playerlib.c index 6e93fd974..6ab5946ea 100644 --- a/src/lua_playerlib.c +++ b/src/lua_playerlib.c @@ -500,6 +500,8 @@ static int player_get(lua_State *L) lua_pushboolean(L, plr->bot); else if (fastcmp(field,"jointime")) lua_pushinteger(L, plr->jointime); + else if (fastcmp(field,"spectatorReentry")) + lua_pushinteger(L, plr->spectatorReentry); else if (fastcmp(field,"splitscreenindex")) lua_pushinteger(L, plr->splitscreenindex); #ifdef HWRENDER @@ -880,6 +882,8 @@ static int player_set(lua_State *L) return NOSET; else if (fastcmp(field,"jointime")) return NOSET; + else if (fastcmp(field,"spectatorReentry")) + plr->spectatorReentry = (UINT32)luaL_checkinteger(L, 3); else if (fastcmp(field,"splitscreenindex")) return NOSET; #ifdef HWRENDER diff --git a/src/p_mobj.c b/src/p_mobj.c index a69078b4a..1ee62a5ff 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -11682,12 +11682,7 @@ void P_SpawnPlayer(INT32 playernum) else if (netgame && p->jointime <= 1 && pcount) { p->spectator = true; - -#if 0 - if (pcount == 1 || leveltime < starttime) - p->pflags |= PF_WANTSTOJOIN; - p->jointime = 2; -#endif + p->spectatorReentry = 0; } else if (multiplayer && !netgame) { @@ -11701,6 +11696,7 @@ void P_SpawnPlayer(INT32 playernum) // Spawn as a spectator, // yes even in splitscreen mode p->spectator = true; + if (playernum&1) p->skincolor = skincolor_redteam; else p->skincolor = skincolor_blueteam; @@ -11722,7 +11718,9 @@ void P_SpawnPlayer(INT32 playernum) { // Fix stupid non spectator spectators. if (!p->spectator && !p->ctfteam) + { p->spectator = true; + } // Fix team colors. // This code isn't being done right somewhere else. Oh well. diff --git a/src/p_saveg.c b/src/p_saveg.c index 3a09afd0f..edbb9b768 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -195,6 +195,8 @@ static void P_NetArchivePlayers(savebuffer_t *save) WRITEUINT32(save->p, players[i].jointime); + WRITEUINT32(save->p, players[i].spectatorReentry); + WRITEUINT8(save->p, players[i].splitscreenindex); if (players[i].awayview.mobj) @@ -598,6 +600,8 @@ static void P_NetUnArchivePlayers(savebuffer_t *save) players[i].jointime = READUINT32(save->p); + players[i].spectatorReentry = READUINT32(save->p); + players[i].splitscreenindex = READUINT8(save->p); flags = READUINT16(save->p); From 9e30ffcdb7982d8dfeabbd33db9012b14011fa1b Mon Sep 17 00:00:00 2001 From: toaster Date: Thu, 13 Apr 2023 21:18:05 +0100 Subject: [PATCH 03/10] Got_KickCmd: Do not kick players with the server node from the server (this shut down the server previously !?) --- src/d_clisrv.c | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 3a6841085..c0393ec6a 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -3189,16 +3189,6 @@ static void Got_KickCmd(UINT8 **p, INT32 playernum) pnum = READUINT8(*p); msg = READUINT8(*p); - if (pnum == serverplayer && IsPlayerAdmin(playernum)) - { - CONS_Printf(M_GetText("Server is being shut down remotely. Goodbye!\n")); - - if (server) - COM_BufAddText("quit\n"); - - return; - } - if (msg == KICK_MSG_CUSTOM_BAN || msg == KICK_MSG_CUSTOM_KICK) { READSTRINGN(*p, reason, MAX_REASONLENGTH+1); @@ -3264,6 +3254,12 @@ static void Got_KickCmd(UINT8 **p, INT32 playernum) } } + if (playernode[pnum] == servernode) + { + CONS_Printf(M_GetText("Ignoring kick attempt from %s on node %d (it's the server)\n"), player_names[playernum], servernode); + return; + } + //CONS_Printf("\x82%s ", player_names[pnum]); // Save bans here. Used to be split between here and the actual command, depending on From b69a575923ee39d71bfddb655e92403dbd8cb1d8 Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 30 Apr 2023 14:47:25 +0100 Subject: [PATCH 04/10] K_CheckSpectateStatus: Clean up - Calculate numingame with first loop - Explicitly set both spectatewait and spectatorReentry to 0 when not a spectator - Return sooner for cv_maxplayers cap reaching - Ignore spectatorReentry at an earlier point in the function, instead of complicating the final despectating to use two iteration values --- src/k_kart.c | 53 ++++++++++++++++++++++++++++++++++------------------ 1 file changed, 35 insertions(+), 18 deletions(-) diff --git a/src/k_kart.c b/src/k_kart.c index 545143438..5d836af09 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -11384,23 +11384,45 @@ void K_CheckSpectateStatus(void) for (i = 0; i < MAXPLAYERS; i++) { if (!playeringame[i]) + { continue; + } - if (players[i].spectator && (players[i].pflags & PF_WANTSTOJOIN)) - players[i].spectatewait++; - else + if (!players[i].spectator) + { + numingame++; players[i].spectatewait = 0; + players[i].spectatorReentry = 0; + continue; + } + + if ((players[i].pflags & PF_WANTSTOJOIN)) + { + players[i].spectatewait++; + } + else + { + players[i].spectatewait = 0; + } if (gamestate != GS_LEVEL) + { players[i].spectatorReentry = 0; + } else if (players[i].spectatorReentry > 0) + { players[i].spectatorReentry--; + } } // No one's allowed to join if (!cv_allowteamchange.value) return; + // DON'T allow if you've hit the in-game player cap + if (cv_maxplayers.value && numingame >= cv_maxplayers.value) + return; + // Get the number of players in game, and the players to be de-spectated. for (i = 0; i < MAXPLAYERS; i++) { @@ -11409,12 +11431,6 @@ void K_CheckSpectateStatus(void) if (!players[i].spectator) { - numingame++; - - // DON'T allow if you've hit the in-game player cap - if (cv_maxplayers.value && numingame >= cv_maxplayers.value) - return; - // Allow if you're not in a level if (gamestate != GS_LEVEL) continue; @@ -11450,10 +11466,16 @@ void K_CheckSpectateStatus(void) continue; } + if (netgame && numingame > 0 && players[i].spectatorReentry > 0) + { + // This person has their reentry cooldown active. + continue; + } + respawnlist[numjoiners++] = i; } - // literally zero point in going any further if nobody is joining + // Literally zero point in going any further if nobody is joining. if (!numjoiners) return; @@ -11483,25 +11505,20 @@ void K_CheckSpectateStatus(void) } // Finally, we can de-spectate everyone! - for (i = 0, j = 0; i < numjoiners; i++) + for (i = 0; i < numjoiners; i++) { - // This person has their reentry cooldown active. - if (netgame && players[respawnlist[i]].spectatorReentry > 0 && numingame > 0) - continue; - //CONS_Printf("player %s is joining on tic %d\n", player_names[respawnlist[i]], leveltime); P_SpectatorJoinGame(&players[respawnlist[i]]); - j++; // j is being used as the number of players added // Hit the in-game player cap while adding people? - if (cv_maxplayers.value && numingame+j >= cv_maxplayers.value) + if (cv_maxplayers.value && numingame+i >= cv_maxplayers.value) break; } // Reset the match when 2P joins 1P, DUEL mode // Reset the match when 3P joins 1P and 2P, DUEL mode must be disabled - if (j > 0 && !mapreset && gamestate == GS_LEVEL && (numingame < 3 && numingame+j >= 2)) + if (i > 0 && !mapreset && gamestate == GS_LEVEL && (numingame < 3 && numingame+i >= 2)) { S_ChangeMusicInternal("chalng", false); // COME ON mapreset = 3*TICRATE; // Even though only the server uses this for game logic, set for everyone for HUD From d4dbc1fe3087a9b778efeb585230bba3d77d3a19 Mon Sep 17 00:00:00 2001 From: toaster Date: Thu, 13 Apr 2023 21:31:18 +0100 Subject: [PATCH 05/10] P_Ticker: Seperate out local player device rumble handling into its own inlineable function Clears some space for antigrief handling --- src/p_tick.c | 70 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 41 insertions(+), 29 deletions(-) diff --git a/src/p_tick.c b/src/p_tick.c index 31c1e5c35..97260c727 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -617,6 +617,37 @@ static inline void P_DoTeamStuff(void) } } +static inline void P_DeviceRumbleTick(void) +{ + UINT8 i; + + for (i = 0; i <= splitscreen; i++) + { + player_t *player = &players[g_localplayers[i]]; + UINT16 low = 0; + UINT16 high = 0; + + if (player->mo == NULL) + continue; + + if ((player->mo->eflags & MFE_DAMAGEHITLAG) && player->mo->hitlag) + { + low = high = 65536 / 2; + } + else if (player->sneakertimer > (sneakertime-(TICRATE/2))) + { + low = high = 65536 / (3+player->numsneakers); + } + else if (((player->boostpower < FRACUNIT) || (player->stairjank > 8)) + && P_IsObjectOnGround(player->mo)) + { + low = high = 65536 / 32; + } + + G_PlayerDeviceRumble(i, low, high); + } +} + void P_RunChaseCameras(void) { UINT8 i; @@ -767,37 +798,18 @@ void P_Ticker(boolean run) } } - // Apply rumble to player if local to machine and not in demo playback + { + { + + + + } + } + + // Apply rumble to local players if (!demo.playback) { - for (i = 0; i <= splitscreen; i++) - { - player_t *player = &players[g_localplayers[i]]; - UINT16 low = 0; - UINT16 high = 0; - - if (player->mo == NULL) - continue; - - if ((player->mo->eflags & MFE_DAMAGEHITLAG) && player->mo->hitlag) - { - low = 65536 / 2; - high = 65536 / 2; - } - else if (player->sneakertimer > (sneakertime-(TICRATE/2))) - { - low = 65536 / (3+player->numsneakers); - high = 65536 / (3+player->numsneakers); - } - else if (((player->boostpower < FRACUNIT) || (player->stairjank > 8)) - && P_IsObjectOnGround(player->mo)) - { - low = 65536 / 32; - high = 65536 / 32; - } - - G_PlayerDeviceRumble(i, low, high); - } + P_DeviceRumbleTick(); } if (numFinishingPlayers > 1) From ab68be49e1ca38187e080d1f8369838ceaecfb7c Mon Sep 17 00:00:00 2001 From: toaster Date: Thu, 13 Apr 2023 22:41:39 +0100 Subject: [PATCH 06/10] 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); From 477eb000e53cd0ab7a2272e7b17113f68e0db132 Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 17 Apr 2023 20:00:34 +0100 Subject: [PATCH 07/10] Do not subtract grief strikes for finishing a race normally Prevents scenarios where someone engages in alternating sporting and unsporting behaviour and never experiences lasting consequences --- src/p_user.c | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/p_user.c b/src/p_user.c index dad11f891..13c1f6736 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -1267,12 +1267,6 @@ 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; @@ -3798,12 +3792,6 @@ 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 From d09cfe67041903b79389a03ce55cda580cab5efb Mon Sep 17 00:00:00 2001 From: toaster Date: Thu, 27 Apr 2023 12:26:39 +0100 Subject: [PATCH 08/10] antigrief_cons_t: Increase maximum for parity with the previous entry in the series --- src/d_netcmd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 0507e73bb..dfd86c0ed 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -266,7 +266,7 @@ 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}}; +static CV_PossibleValue_t antigrief_cons_t[] = {{10, "MIN"}, {180, "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); From ee3bf6d117420950f7d31e3483af37ba096ffdab Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 30 Apr 2023 13:36:15 +0100 Subject: [PATCH 09/10] P_CheckRaceGriefing: Fix typo that rendered antigrief ineffective --- src/p_user.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/p_user.c b/src/p_user.c index 13c1f6736..3ec4683f0 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -4629,7 +4629,7 @@ void P_CheckRaceGriefing(player_t *player, boolean dopunishment) player->griefValue--; } - if (dopunishment && player->griefValue > griefMax) + if (dopunishment && player->griefValue >= griefMax) { if (player->griefStrikes < 3) { From 3d9d9ba1e6eabebb110c6dd9fd1dba865b7ec1c3 Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 1 May 2023 17:10:02 +0100 Subject: [PATCH 10/10] P_Ticker: Do not run antigrief detection if there are no waypoints --- src/p_tick.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/p_tick.c b/src/p_tick.c index 074af2c7a..e171ab74a 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -805,7 +805,7 @@ 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 + && (gametyperules & GTR_CIRCUIT) && (leveltime > starttime) && K_GetNumWaypoints()) // The following only detects race griefing { for (i = 0; i < MAXPLAYERS; i++) {