This commit is contained in:
speedvoltage 2025-04-03 16:07:10 +03:00 committed by GitHub
commit cf1b2551b3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 140 additions and 24 deletions

View file

@ -302,8 +302,22 @@ void Host_Say( edict_t *pEdict, const CCommand &args, bool teamonly )
if ( !(client->IsNetClient()) ) // Not a client ? (should never be true)
continue;
if ( teamonly && g_pGameRules->PlayerCanHearChat( client, pPlayer ) != GR_TEAMMATE )
continue;
if ( g_pGameRules->IsTeamplay() )
{
if ( teamonly && g_pGameRules->PlayerCanHearChat( client, pPlayer ) != GR_TEAMMATE )
continue;
}
else
{
CTeam *sendingTeam = pPlayer ? pPlayer->GetTeam() : NULL; // Could still be NULL at this point
CTeam *receivingTeam = client->GetTeam();
if ( !sendingTeam || !receivingTeam )
continue;
if ( sendingTeam != receivingTeam )
continue;
}
if ( pPlayer && !client->CanHearAndReadChatFrom( pPlayer ) )
continue;
@ -363,6 +377,11 @@ void Host_Say( edict_t *pEdict, const CCommand &args, bool teamonly )
}
}
if ( pPlayer )
{
pPlayer->DecrementPlayerChatBuckets();
}
if ( teamonly )
UTIL_LogPrintf( "\"%s<%i><%s><%s>\" say_team \"%s\"\n", playerName, userid, networkID, playerTeam, p );
else
@ -845,11 +864,19 @@ CON_COMMAND( say, "Display player message" )
CBasePlayer *pPlayer = ToBasePlayer( UTIL_GetCommandClient() );
if ( pPlayer )
{
if ( pPlayer->CanPlayerTalk() )
if ( engine->IsPaused() )
{
Host_Say( pPlayer->edict(), args, 0 );
}
else if ( pPlayer->CanPlayerTalk() )
{
Host_Say( pPlayer->edict(), args, 0 );
pPlayer->NotePlayerTalked();
}
else
{
ClientPrint( pPlayer, HUD_PRINTTALK, "You have sent too many messages! Please wait before sending new messages.\n" );
}
}
// This will result in a "console" say. Ignore anything from
// an index greater than 0 when we don't have a player pointer,
@ -869,11 +896,19 @@ CON_COMMAND( say_team, "Display player message to team" )
CBasePlayer *pPlayer = ToBasePlayer( UTIL_GetCommandClient() );
if (pPlayer)
{
if ( pPlayer->CanPlayerTalk() )
if ( engine->IsPaused() )
{
Host_Say( pPlayer->edict(), args, 1 );
}
else if ( pPlayer->CanPlayerTalk() )
{
Host_Say( pPlayer->edict(), args, 1 );
pPlayer->NotePlayerTalked();
}
else
{
ClientPrint( pPlayer, HUD_PRINTTALK, "You have sent too many messages! Please wait before sending new messages.\n" );
}
}
}

View file

@ -100,6 +100,9 @@ ConVar sv_chat_seconds_per_msg_tier1( "sv_chat_seconds_per_msg_tier1", "3", FCVA
ConVar sv_chat_bucket_size_tier2( "sv_chat_bucket_size_tier2", "30", FCVAR_NONE, "The maximum size of the long term chat msg bucket." );
ConVar sv_chat_seconds_per_msg_tier2( "sv_chat_seconds_per_msg_tier2", "10", FCVAR_NONE, "The number of seconds to accrue an additional long term chat msg." );
ConVar sv_chat_bucket_size_tier1_penalty( "sv_chat_bucket_size_tier1_penalty", "0.25", FCVAR_NONE, "The penalty multiplier that gets added to the regeneration time of this bucket.", true, 0.1, false, NULL );
ConVar sv_chat_bucket_size_tier1_penalty_decay( "sv_chat_bucket_size_tier1_penalty_decay", "30", FCVAR_NONE, "The time in seconds to wait to reduce penalty multiplier one notch.", true, 1.0, false, NULL );
static ConVar sv_maxusrcmdprocessticks( "sv_maxusrcmdprocessticks", "24", FCVAR_NOTIFY, "Maximum number of client-issued usrcmd ticks that can be replayed in packet loss conditions, 0 to allow no restrictions" );
// memdbgon must be the last include file in a .cpp file!!!
@ -614,6 +617,8 @@ CBasePlayer::CBasePlayer( )
m_fLastPlayerTalkAttemptTime = 0.0f;
m_flPlayerTalkAvailableMessagesTier1 = 1.0f;
m_flPlayerTalkAvailableMessagesTier2 = 10.0f;
m_flChatPenaltyMultiplier = 1.0f;
m_flLastPenaltyDecayTime = gpGlobals->curtime;
m_PlayerInfo.SetParent( this );
ResetObserverMode();
@ -8768,37 +8773,90 @@ void CBasePlayer::DeactivateMovementConstraint( )
//-----------------------------------------------------------------------------
// Purpose: Fight chat spam with a two tiered token bucket
//-----------------------------------------------------------------------------
bool CBasePlayer::ArePlayerTalkMessagesAvailable( void )
void CBasePlayer::DecrementPlayerChatBuckets()
{
// How long since we last tried to chat?
m_flPlayerTalkAvailableMessagesTier1 -= 1.0f;
// If the bucket in tier 1 is above 0, decrement by 1
// this avoids really sinking into a deep hole and having
// to wait for an undetermined amout of time
if ( m_flPlayerTalkAvailableMessagesTier1 > 0 )
{
m_flPlayerTalkAvailableMessagesTier2 -= 1.0f;
}
m_flPlayerTalkAvailableMessagesTier1 = MAX( -2.5f, m_flPlayerTalkAvailableMessagesTier1 );
}
bool CBasePlayer::ArePlayerTalkMessagesAvailable()
{
if ( m_flChatPenaltyMultiplier < 1.0f ) // This should never happen, but it is a safety net
m_flChatPenaltyMultiplier = 1.0f;
if ( m_flChatPenaltyMultiplier > 3.0f ) // This should never happen, but it is a safety net
m_flChatPenaltyMultiplier = 3.0f;
float flTimeElapsedSinceLastMsg = gpGlobals->curtime - m_fLastPlayerTalkAttemptTime;
m_fLastPlayerTalkAttemptTime = gpGlobals->curtime;
// The max messages we can have available
// Tier 1 is for short-term spam
float flTotalBucketSizeTier1 = sv_chat_bucket_size_tier1.GetFloat();
// rate at which we gain new messages, this slows if we continue to try to spam messages
float flSecondsPerMessageTier1 = sv_chat_seconds_per_msg_tier1.GetFloat() - MIN( 0.0f, m_flPlayerTalkAvailableMessagesTier1 );
// Apply decay to the penalty if enough time has passed since last chat message
const float flPenaltyDecayInterval = sv_chat_bucket_size_tier1_penalty_decay.GetFloat(); // Every 30 seconds, the penalty decays slightly
if ( gpGlobals->curtime - m_flLastPenaltyDecayTime >= flPenaltyDecayInterval )
{
m_flChatPenaltyMultiplier = MAX( 1.0f, m_flChatPenaltyMultiplier - 0.1f ); // Slowly recover
m_flLastPenaltyDecayTime = gpGlobals->curtime;
}
// Short-term bucket
float flTotalBucketSizeTier1 = sv_chat_bucket_size_tier1.GetFloat();
float flChatPenaltyMulitplier = sv_chat_bucket_size_tier1_penalty.GetFloat();
// Increase regen time by the penalty multiplier
float flBaseSecondsPerMessageTier1 = sv_chat_seconds_per_msg_tier1.GetFloat();
float flSecondsPerMessageTier1 = flBaseSecondsPerMessageTier1 * m_flChatPenaltyMultiplier;
// We'll count partial counts of accruing message throughout, as it'll be more consistent
float flMessagesGainedTier1 = MAX( 0, flTimeElapsedSinceLastMsg / flSecondsPerMessageTier1 );
m_flPlayerTalkAvailableMessagesTier1 = MIN( flTotalBucketSizeTier1, m_flPlayerTalkAvailableMessagesTier1 + flMessagesGainedTier1 );
// But we will allow the counter to go negative, so if you keep trying to spam you have to work your way out of a hole.
m_flPlayerTalkAvailableMessagesTier1 = MAX( -2.5f, MIN( flTotalBucketSizeTier1, m_flPlayerTalkAvailableMessagesTier1 + flMessagesGainedTier1 ) - 1.0f );
// We are going to use a penalty system designed to limit spammers by slowing down the recovery rate of their buckets.
// Basically, if the short term bucket value is insufficient, the message does not get sent and a multiplier penalty is added,
// slowing down their regeneration of the bucket. Their buckets will keep refilling no matter what, but at a slower rate,
// and at each failed message, the bucket fills more slowly. This penalty multiplier will eventually go back down if the player
// stops spamming or does not use the chat for a while.
if ( m_flPlayerTalkAvailableMessagesTier1 <= 1.0f )
{
// Let's not go overboard and apply a max multiplier
// We are setting a hard coded value, because going above 3 would just cause the same original behavior of digging
// a hole and never being to talk ever again due to an insane multiplier and we do not want that.
if ( m_flChatPenaltyMultiplier < 3.0f )
{
m_flChatPenaltyMultiplier += flChatPenaltyMulitplier; // Increase penalty
m_flChatPenaltyMultiplier = clamp( m_flChatPenaltyMultiplier, 1.0f, 3.0f ); // Ensure within bounds
}
// Tier2 is for curbing longer-term consistent spamming
// We'll only allow the long term bucket to accrue if we're not currently in a spammy state
if ( m_flPlayerTalkAvailableMessagesTier1 <= 0.0f )
m_flPlayerTalkAvailableMessagesTier1 = 0.0f; // Never go negative
}
// Long-term bucket
float flMessagesGainedTier2 = 0.0f;
if ( m_flPlayerTalkAvailableMessagesTier1 > 0 )
{
float flTotalBucketSizeTier2 = sv_chat_bucket_size_tier2.GetFloat();
float flSecondsPerMessageTier2 = sv_chat_seconds_per_msg_tier2.GetFloat();
float flMessagesGainedTier2 = MAX( 0, flTimeElapsedSinceLastMsg / flSecondsPerMessageTier2 );
m_flPlayerTalkAvailableMessagesTier2 = MAX( 0, MIN( flTotalBucketSizeTier2, m_flPlayerTalkAvailableMessagesTier2 + flMessagesGainedTier2 ) - 1.0f );
//Msg( "Elapsed : %f2 Gained : %f2 / %f2 \n", flTimeElapsedSinceLastMsg, flMessagesGainedTier1, flMessagesGainedTier2 );
flMessagesGainedTier2 = MAX( 0, flTimeElapsedSinceLastMsg / flSecondsPerMessageTier2 );
m_flPlayerTalkAvailableMessagesTier2 = MIN( flTotalBucketSizeTier2, m_flPlayerTalkAvailableMessagesTier2 + flMessagesGainedTier2 );
}
m_flPlayerTalkAvailableMessagesTier2 = MAX( 0, m_flPlayerTalkAvailableMessagesTier2 );
//Msg( "Remaining Msgs : %f2 / %f2\n", m_flPlayerTalkAvailableMessagesTier1, m_flPlayerTalkAvailableMessagesTier2 );
// Debug messages
DevMsg( "Elapsed: %.2f sec | Gained Tier1: %.2f | Gained Tier2: %.2f\n",
flTimeElapsedSinceLastMsg, flMessagesGainedTier1, flMessagesGainedTier2 );
DevMsg( "Remaining Msgs - Tier1: %.2f / %.2f | Tier2: %.2f / %.2f | Penalty Multiplier: %.2f\n",
m_flPlayerTalkAvailableMessagesTier1, flTotalBucketSizeTier1,
m_flPlayerTalkAvailableMessagesTier2, sv_chat_bucket_size_tier2.GetFloat(),
m_flChatPenaltyMultiplier );
return m_flPlayerTalkAvailableMessagesTier1 > 1.0f && m_flPlayerTalkAvailableMessagesTier2 > 1.0f;
}
@ -8808,7 +8866,9 @@ bool CBasePlayer::ArePlayerTalkMessagesAvailable( void )
//-----------------------------------------------------------------------------
bool CBasePlayer::CanPlayerTalk()
{
const float talk_interval = 0.66; // min time between say commands from a client
// min time between say commands from a client,
// has no effect when the game is paused because time is frozen
const float talk_interval = 0.66;
bool bRateLimitAllowed = LastTimePlayerTalked() + talk_interval < gpGlobals->curtime;

View file

@ -777,6 +777,7 @@ public:
void NotePlayerTalked() { m_fLastPlayerTalkTime = gpGlobals->curtime; }
float LastTimePlayerTalked() const { return m_fLastPlayerTalkTime; }
bool ArePlayerTalkMessagesAvailable();
void DecrementPlayerChatBuckets();
void DisableButtons( int nButtons );
void EnableButtons( int nButtons );
@ -839,6 +840,10 @@ private:
int DetermineSimulationTicks( void );
void AdjustPlayerTimeBase( int simulation_ticks );
// Player spamming way too much, add a penalty
float m_flChatPenaltyMultiplier;
float m_flLastPenaltyDecayTime;
public:
// How long since this player last interacted with something the game considers an objective/target/goal

View file

@ -1249,9 +1249,10 @@ const char *CHL2MPRules::GetChatFormat( bool bTeamOnly, CBasePlayer *pPlayer )
}
const char *pszFormat = NULL;
bool bIsDead = ( pPlayer->m_lifeState != LIFE_ALIVE );
// team only
if ( bTeamOnly == TRUE )
if ( bTeamOnly )
{
if ( pPlayer->GetTeamNumber() == TEAM_SPECTATOR )
{
@ -1266,7 +1267,14 @@ const char *CHL2MPRules::GetChatFormat( bool bTeamOnly, CBasePlayer *pPlayer )
}
else
{
pszFormat = "HL2MP_Chat_Team";
if ( bIsDead )
{
pszFormat = "HL2MP_Chat_Team_Dead";
}
else
{
pszFormat = "HL2MP_Chat_Team";
}
}
}
}
@ -1275,7 +1283,14 @@ const char *CHL2MPRules::GetChatFormat( bool bTeamOnly, CBasePlayer *pPlayer )
{
if ( pPlayer->GetTeamNumber() != TEAM_SPECTATOR )
{
pszFormat = "HL2MP_Chat_All";
if ( bIsDead )
{
pszFormat = "HL2MP_Chat_AllDead";
}
else
{
pszFormat = "HL2MP_Chat_All";
}
}
else
{
@ -1286,4 +1301,5 @@ const char *CHL2MPRules::GetChatFormat( bool bTeamOnly, CBasePlayer *pPlayer )
return pszFormat;
}
#endif