You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
350 lines
11 KiB
350 lines
11 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
// $NoKeywords: $ |
|
//============================================================================= |
|
|
|
#include "cbase.h" |
|
#include "mp_shareddefs.h" |
|
#include "basemultiplayerplayer.h" |
|
|
|
// Minimum interval between rate-limited commands that players can run. |
|
#define COMMAND_MAX_RATE 0.3 |
|
|
|
CBaseMultiplayerPlayer::CBaseMultiplayerPlayer() |
|
{ |
|
m_iCurrentConcept = MP_CONCEPT_NONE; |
|
m_flLastForcedChangeTeamTime = -1; |
|
m_iBalanceScore = 0; |
|
m_flConnectionTime = gpGlobals->curtime; |
|
|
|
// per life achievement counters |
|
m_pAchievementKV = new KeyValues( "achievement_counts" ); |
|
|
|
m_flAreaCaptureScoreAccumulator = 0.0f; |
|
} |
|
|
|
CBaseMultiplayerPlayer::~CBaseMultiplayerPlayer() |
|
{ |
|
m_pAchievementKV->deleteThis(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
CAI_Expresser *CBaseMultiplayerPlayer::CreateExpresser( void ) |
|
{ |
|
m_pExpresser = new CMultiplayer_Expresser(this); |
|
if ( !m_pExpresser) |
|
return NULL; |
|
|
|
m_pExpresser->Connect(this); |
|
return m_pExpresser; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseMultiplayerPlayer::PostConstructor( const char *szClassname ) |
|
{ |
|
BaseClass::PostConstructor( szClassname ); |
|
CreateExpresser(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseMultiplayerPlayer::ModifyOrAppendCriteria( AI_CriteriaSet& criteriaSet ) |
|
{ |
|
BaseClass::ModifyOrAppendCriteria( criteriaSet ); |
|
|
|
ModifyOrAppendPlayerCriteria( criteriaSet ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool CBaseMultiplayerPlayer::SpeakIfAllowed( AIConcept_t concept, const char *modifiers, char *pszOutResponseChosen, size_t bufsize, IRecipientFilter *filter ) |
|
{ |
|
if ( !IsAlive() ) |
|
return false; |
|
|
|
//if ( IsAllowedToSpeak( concept, bRespondingToPlayer ) ) |
|
return Speak( concept, modifiers, pszOutResponseChosen, bufsize, filter ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
IResponseSystem *CBaseMultiplayerPlayer::GetResponseSystem() |
|
{ |
|
return BaseClass::GetResponseSystem(); |
|
// NOTE: This is where you would hook your custom responses. |
|
// return <*>GameRules()->m_ResponseRules[iIndex].m_ResponseSystems[m_iCurrentConcept]; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Doesn't actually speak the concept. Just finds a response in the system. You then have to play it yourself. |
|
//----------------------------------------------------------------------------- |
|
bool CBaseMultiplayerPlayer::SpeakConcept( AI_Response &response, int iConcept ) |
|
{ |
|
// Save the current concept. |
|
m_iCurrentConcept = iConcept; |
|
return SpeakFindResponse( response, g_pszMPConcepts[iConcept] ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool CBaseMultiplayerPlayer::SpeakConceptIfAllowed( int iConcept, const char *modifiers, char *pszOutResponseChosen, size_t bufsize, IRecipientFilter *filter ) |
|
{ |
|
// Save the current concept. |
|
m_iCurrentConcept = iConcept; |
|
return SpeakIfAllowed( g_pszMPConcepts[iConcept], modifiers, pszOutResponseChosen, bufsize, filter ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool CBaseMultiplayerPlayer::CanHearAndReadChatFrom( CBasePlayer *pPlayer ) |
|
{ |
|
// can always hear the console unless we're ignoring all chat |
|
if ( !pPlayer ) |
|
return m_iIgnoreGlobalChat != CHAT_IGNORE_ALL; |
|
|
|
// check if we're ignoring all chat |
|
if ( m_iIgnoreGlobalChat == CHAT_IGNORE_ALL ) |
|
return false; |
|
|
|
// check if we're ignoring all but teammates |
|
if ( m_iIgnoreGlobalChat == CHAT_IGNORE_TEAM && g_pGameRules->PlayerRelationship( this, pPlayer ) != GR_TEAMMATE ) |
|
return false; |
|
|
|
// can't hear dead players if we're alive |
|
if ( pPlayer->m_lifeState != LIFE_ALIVE && m_lifeState == LIFE_ALIVE ) |
|
return false; |
|
|
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool CBaseMultiplayerPlayer::ShouldRunRateLimitedCommand( const CCommand &args ) |
|
{ |
|
return ShouldRunRateLimitedCommand( args[0] ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool CBaseMultiplayerPlayer::ShouldRunRateLimitedCommand( const char *pszCommand ) |
|
{ |
|
const char *pcmd = pszCommand; |
|
|
|
int i = m_RateLimitLastCommandTimes.Find( pcmd ); |
|
if ( i == m_RateLimitLastCommandTimes.InvalidIndex() ) |
|
{ |
|
m_RateLimitLastCommandTimes.Insert( pcmd, gpGlobals->curtime ); |
|
return true; |
|
} |
|
else if ( (gpGlobals->curtime - m_RateLimitLastCommandTimes[i]) < COMMAND_MAX_RATE ) |
|
{ |
|
// Too fast. |
|
return false; |
|
} |
|
else |
|
{ |
|
m_RateLimitLastCommandTimes[i] = gpGlobals->curtime; |
|
return true; |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool CBaseMultiplayerPlayer::ClientCommand( const CCommand &args ) |
|
{ |
|
const char *pcmd = args[0]; |
|
|
|
if ( FStrEq( pcmd, "ignoremsg" ) ) |
|
{ |
|
if ( ShouldRunRateLimitedCommand( args ) ) |
|
{ |
|
m_iIgnoreGlobalChat = (m_iIgnoreGlobalChat + 1) % 3; |
|
switch( m_iIgnoreGlobalChat ) |
|
{ |
|
case CHAT_IGNORE_NONE: |
|
ClientPrint( this, HUD_PRINTTALK, "#Accept_All_Messages" ); |
|
break; |
|
case CHAT_IGNORE_ALL: |
|
ClientPrint( this, HUD_PRINTTALK, "#Ignore_Broadcast_Messages" ); |
|
break; |
|
case CHAT_IGNORE_TEAM: |
|
ClientPrint( this, HUD_PRINTTALK, "#Ignore_Broadcast_Team_Messages" ); |
|
break; |
|
default: |
|
break; |
|
} |
|
} |
|
return true; |
|
} |
|
|
|
return BaseClass::ClientCommand( args ); |
|
} |
|
|
|
bool CBaseMultiplayerPlayer::ShouldShowVoiceSubtitleToEnemy( void ) |
|
{ |
|
return false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// calculate a score for this player. higher is more likely to be switched |
|
//----------------------------------------------------------------------------- |
|
int CBaseMultiplayerPlayer::CalculateTeamBalanceScore( void ) |
|
{ |
|
// base score is 0 - ( seconds on server ) |
|
float flTimeConnected = gpGlobals->curtime - m_flConnectionTime; |
|
int iScore = 0 - (int)flTimeConnected; |
|
|
|
// if we were switched recently, score us way down |
|
float flLastSwitchedTime = GetLastForcedChangeTeamTime(); |
|
if ( flLastSwitchedTime > 0 && ( gpGlobals->curtime - flLastSwitchedTime ) < 300 ) |
|
{ |
|
iScore -= 10000; |
|
} |
|
return iScore; |
|
} |
|
|
|
void CBaseMultiplayerPlayer::Spawn( void ) |
|
{ |
|
ResetPerLifeCounters(); |
|
|
|
StopScoringEscortPoints(); |
|
|
|
BaseClass::Spawn(); |
|
} |
|
|
|
void CBaseMultiplayerPlayer::AwardAchievement( int iAchievement, int iCount ) |
|
{ |
|
Assert( iAchievement >= 0 && iAchievement < 0xFFFF ); // must fit in short |
|
|
|
CSingleUserRecipientFilter filter( this ); |
|
|
|
UserMessageBegin( filter, "AchievementEvent" ); |
|
WRITE_SHORT( iAchievement ); |
|
WRITE_SHORT( iCount ); |
|
MessageEnd(); |
|
} |
|
|
|
#ifdef _DEBUG |
|
|
|
#include "utlbuffer.h" |
|
|
|
void DumpAchievementCounters( const CCommand &args ) |
|
{ |
|
int iPlayerIndex = 1; |
|
|
|
if ( args.ArgC() >= 2 ) |
|
{ |
|
iPlayerIndex = atoi( args[1] ); |
|
} |
|
|
|
CBaseMultiplayerPlayer *pPlayer = ToBaseMultiplayerPlayer( UTIL_PlayerByIndex( iPlayerIndex ) ); |
|
if ( pPlayer && pPlayer->GetPerLifeCounterKeys() ) |
|
{ |
|
CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER ); |
|
pPlayer->GetPerLifeCounterKeys()->RecursiveSaveToFile( buf, 0 ); |
|
|
|
char szBuf[1024]; |
|
|
|
// probably not the best way to print out a CUtlBuffer |
|
int pos = 0; |
|
while ( buf.PeekStringLength() ) |
|
{ |
|
szBuf[pos] = buf.GetChar(); |
|
pos++; |
|
} |
|
szBuf[pos] = '\0'; |
|
|
|
Msg( "%s\n", szBuf ); |
|
} |
|
} |
|
ConCommand dump_achievement_counters( "dump_achievement_counters", DumpAchievementCounters, "Spew the per-life achievement counters for multiplayer players", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY ); |
|
|
|
#endif // _DEBUG |
|
|
|
int CBaseMultiplayerPlayer::GetPerLifeCounterKV( const char *name ) |
|
{ |
|
return m_pAchievementKV->GetInt( name, 0 ); |
|
} |
|
|
|
void CBaseMultiplayerPlayer::SetPerLifeCounterKV( const char *name, int value ) |
|
{ |
|
m_pAchievementKV->SetInt( name, value ); |
|
} |
|
|
|
void CBaseMultiplayerPlayer::ResetPerLifeCounters( void ) |
|
{ |
|
m_pAchievementKV->Clear(); |
|
} |
|
|
|
|
|
ConVar tf_escort_score_rate( "tf_escort_score_rate", "1", FCVAR_CHEAT, "Score for escorting the train, in points per second" ); |
|
|
|
#define ESCORT_SCORE_CONTEXT "AreaScoreContext" |
|
#define ESCORT_SCORE_INTERVAL 0.1 |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: think to accumulate and award points for escorting the train |
|
//----------------------------------------------------------------------------- |
|
void CBaseMultiplayerPlayer::EscortScoringThink( void ) |
|
{ |
|
m_flAreaCaptureScoreAccumulator += ESCORT_SCORE_INTERVAL; |
|
|
|
if ( m_flCapPointScoreRate > 0 ) |
|
{ |
|
float flTimeForOnePoint = 1.0f / m_flCapPointScoreRate; |
|
|
|
int iPoints = 0; |
|
|
|
while ( m_flAreaCaptureScoreAccumulator >= flTimeForOnePoint ) |
|
{ |
|
m_flAreaCaptureScoreAccumulator -= flTimeForOnePoint; |
|
iPoints++; |
|
} |
|
|
|
if ( iPoints > 0 ) |
|
{ |
|
IGameEvent *event = gameeventmanager->CreateEvent( "player_escort_score" ); |
|
if ( event ) |
|
{ |
|
event->SetInt( "player", entindex() ); |
|
event->SetInt( "points", iPoints ); |
|
gameeventmanager->FireEvent( event, true /* only to server */ ); |
|
} |
|
} |
|
} |
|
|
|
SetContextThink( &CBaseMultiplayerPlayer::EscortScoringThink, gpGlobals->curtime + ESCORT_SCORE_INTERVAL, ESCORT_SCORE_CONTEXT ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: We're escorting the train, start giving points |
|
//----------------------------------------------------------------------------- |
|
void CBaseMultiplayerPlayer::StartScoringEscortPoints( float flRate ) |
|
{ |
|
Assert( flRate > 0.0f ); |
|
m_flCapPointScoreRate = flRate; |
|
SetContextThink( &CBaseMultiplayerPlayer::EscortScoringThink, gpGlobals->curtime + ESCORT_SCORE_INTERVAL, ESCORT_SCORE_CONTEXT ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Stopped escorting the train |
|
//----------------------------------------------------------------------------- |
|
void CBaseMultiplayerPlayer::StopScoringEscortPoints( void ) |
|
{ |
|
SetContextThink( NULL, 0, ESCORT_SCORE_CONTEXT ); |
|
} |
|
|
|
|