Flood protection does not compute chat frequency correctly when game is paused

[deleted to protect IPs]

3 Likes

I didn’t get a chance to see the video (but yeah, if you use namelog or have any other kinds of sensitive information in a video, it is a good idea to not publicly post such videos :wink: ), based on the discussion I had with @MachineMedic when he made this post, I renamed the topic to be a bit more descriptive about the issue :wink: . Basically what happens is if flood protection kicks in from rapid use of chat and/or commands on the development server, the time that you are not allowed to use any chat/commands is extended without limit each time you attempt such an action while still flood limited.

What I’ll do, when I get back to my computer with the CODEZ™ later today, is cap the delay at 5 seconds. So if you are still flooding it still would extend, but the moment you stop it would end 5 seconds later. Also I’ll make sure to flag admin levels 3 and 4 to bypass the flood protection, because admins shouldn’t be automuted from using admin commands when addressing griefers :wink: . I’ll post another reply once I have applied those fixes.

@dGr8LookinSparky

You still don’t understand. I’ve said it three times. The flood protection on your server stops functioning entirely once the game is paused, because your flood protection subroutine records chat times based on an internal timescale-locked clock instead of local machine time.

When the game is paused, the flood-protect subroutine sees ALL chats and commands as happening concurrently at the exact same moment, because no time has passed in the game world.

The issue is not that the flood timeout rises too high (though capping it wouldn’t hurt), it’s that the system is malfunctioning due to a design flaw.

https://www.youtube.com/watch?v=ZpZaMCspQcE&feature=youtu.be

If you still don’t understand, then start a singleplayer game with

/devmap atcs
Once the game is running, use

/timescale 0.1
to set the game time to be equal to 1/10th of local machine time. Then, close the console. Watch how slowly it closes. This is because the client-side function that draws the console uses game-world time as it’s reference. The same thing is happening with your flood protection.

The obvious solution is to re-code the flood protection system to rely on real-world time (meaning the local machine clock) instead of virtual game time.

2 Likes

Ah I see, before when I was triggering the flood protection I wasn’t in pause. Turns out this bug was probably in the trem repo since 2011 when the pause command was added. The way that pause command works is at the beginning of the function G_RunFrame() pause prevents level.time from being reset to the current time provided by the tremded, and then returns from that function preventing that frame from running. However, how much time elapsed while the game is paused is tracked by level.pausedTime (to auto unpause after 2 minutes).

Here is the beginning of the G_RunFrame() function:

/*
================
G_RunFrame

Advances the non-player objects in the world
================
*/
void G_RunFrame( int levelTime )
{
  int        i;
  gentity_t  *ent;
  int        msec;
  static int ptime3000 = 0;

  // if we are waiting for the level to restart, do nothing
  if( level.restarted )
    return;

  if( level.pausedTime )
  {
    msec = levelTime - level.time - level.pausedTime;
    level.pausedTime = levelTime - level.time;

    ptime3000 += msec;
    while( ptime3000 > 3000 )
    {
      ptime3000 -= 3000;
      trap_SendServerCommand( -1, "cp \"The game has been paused. Please wait.\"" );

      if( level.pausedTime >= 110000  && level.pausedTime <= 119000 )
        trap_SendServerCommand( -1, va( "print \"Server: Game will auto-unpause in %d seconds\n\"",
          (int) ( (float) ( 120000 - level.pausedTime ) / 1000.0f ) ) );
    }

    // Prevents clients from getting lagged-out messages
    for( i = 0; i < level.maxclients; i++ )
    {
      if( level.clients[ i ].pers.connected == CON_CONNECTED )
        level.clients[ i ].ps.commandTime = levelTime;
    }

    if( level.pausedTime > 120000 )
    {
      trap_SendServerCommand( -1, "print \"Server: The game has been unpaused automatically (2 minute max)\n\"" );
      trap_SendServerCommand( -1, "cp \"The game has been unpaused!\"" );
      level.pausedTime = 0;
    }

    return;
  }

  level.framenum++;
  level.previousTime = level.time;
  level.time = levelTime;
  msec = level.time - level.previousTime;

take note that level.time is set equal to levelTime (the current time provided from the tremded) only after the level.pausedTime check fails.

So for any timer check that depends solely on the value of level.time would act as if it is in the same point of time. I fixed this issue in the flood protection by adding level.pausedTime to level.time. I have also capped the flood protection duration to be equal to the value of the cvar g_floodMaxDemerits (which is 5 seconds in length by default).

Here is the current flood protection function after those fixes:

/*
==================
G_FloodLimited

Determine whether a user is flood limited, and adjust their flood demerits
Print them a warning message if they are over the limit
Return is time in msec until the user can speak again
==================
*/
int G_FloodLimited( gentity_t *ent )
{
  int deltatime, ms;

  if( g_floodMinTime.integer <= 0 )
    return 0;

  // handles !ent
  if( G_admin_permission( ent, ADMF_NOCENSORFLOOD ) )
    return 0;

  deltatime = level.time + level.pausedTime - ent->client->pers.floodTime;

  ent->client->pers.floodDemerits += g_floodMinTime.integer - deltatime;
  if( ent->client->pers.floodDemerits < 0 )
    ent->client->pers.floodDemerits = 0;
  else if( ent->client->pers.floodDemerits > 2 * g_floodMaxDemerits.integer )
  {
    // cap the flood protection duration at the value of g_floodMaxDemerits
    ent->client->pers.floodDemerits = 2 * g_floodMaxDemerits.integer;
  }
  ent->client->pers.floodTime = level.time + level.pausedTime;

  ms = ent->client->pers.floodDemerits - g_floodMaxDemerits.integer;
  if( ms <= 0 )
    return 0;
  trap_SendServerCommand( ent - g_entities, va( "print \"You are flooding: "
                          "please wait %d second%s before trying again\n",
                          ( ms + 999 ) / 1000, ( ms > 1000 ) ? "s" : "" ) );
  return ms;
}

@MachineMedic , those fixes are now on the test7341 server, I’ll wait a bit before flagging level 3 and 4 admins with the ability to bypass the flood protection, so that you have a chance to put these fixes to the test.

1 Like