//========= Copyright 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================//
#include "cbase.h"
#include "gamerules.h"
#include "ammodef.h"
#include "tier0/vprof.h"
#include "KeyValues.h"
#ifdef CLIENT_DLL
#else
#include "player.h"
#include "teamplay_gamerules.h"
#include "game.h"
#include "entitylist.h"
#include "basecombatweapon.h"
#include "voice_gamemgr.h"
#include "globalstate.h"
#include "player_resource.h"
#endif
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
ConVar g_Language( "g_Language", "0", FCVAR_REPLICATED );
static CViewVectors g_DefaultViewVectors(
Vector( 0, 0, 64 ),
Vector(-16, -16, 0 ),
Vector( 16, 16, 72 ),
Vector(-16, -16, 0 ),
Vector( 16, 16, 36 ),
Vector( 0, 0, 28 ),
Vector(-10, -10, -10 ),
Vector( 10, 10, 10 ),
Vector( 0, 0, 14 )
);
// ------------------------------------------------------------------------------------ //
// CGameRulesProxy implementation.
// ------------------------------------------------------------------------------------ //
CGameRulesProxy *CGameRulesProxy::s_pGameRulesProxy = NULL;
IMPLEMENT_NETWORKCLASS_ALIASED( GameRulesProxy, DT_GameRulesProxy )
// Don't send any of the CBaseEntity stuff..
BEGIN_NETWORK_TABLE_NOBASE( CGameRulesProxy, DT_GameRulesProxy )
END_NETWORK_TABLE()
CGameRulesProxy::CGameRulesProxy()
{
Assert( !s_pGameRulesProxy );
s_pGameRulesProxy = this;
}
CGameRulesProxy::~CGameRulesProxy()
{
Assert( s_pGameRulesProxy );
s_pGameRulesProxy = NULL;
}
int CGameRulesProxy::UpdateTransmitState()
{
#ifndef CLIENT_DLL
// ALWAYS transmit to all clients.
return SetTransmitState( FL_EDICT_ALWAYS );
#else
return 0;
#endif
}
void CGameRulesProxy::NotifyNetworkStateChanged()
{
if ( s_pGameRulesProxy )
s_pGameRulesProxy->NetworkStateChanged();
}
ConVar old_radius_damage( "old_radiusdamage", "0.0", FCVAR_REPLICATED );
#ifdef CLIENT_DLL //{
CGameRules::CGameRules() : CAutoGameSystemPerFrame( "CGameRules" )
{
Assert( !g_pGameRules );
g_pGameRules = this;
}
#else //}{
// In tf_gamerules.cpp or hl_gamerules.cpp.
extern IVoiceGameMgrHelper *g_pVoiceGameMgrHelper;
CGameRules* g_pGameRules = NULL;
extern bool g_fGameOver;
//-----------------------------------------------------------------------------
// constructor, destructor
//-----------------------------------------------------------------------------
CGameRules::CGameRules() : CAutoGameSystemPerFrame( "CGameRules" )
{
Assert( !g_pGameRules );
g_pGameRules = this;
GetVoiceGameMgr()->Init( g_pVoiceGameMgrHelper, gpGlobals->maxClients );
ClearMultiDamage();
}
//-----------------------------------------------------------------------------
// Purpose: Return true if the specified player can carry any more of the ammo type
//-----------------------------------------------------------------------------
bool CGameRules::CanHaveAmmo( CBaseCombatCharacter *pPlayer, int iAmmoIndex )
{
if ( iAmmoIndex > -1 )
{
// Get the max carrying capacity for this ammo
int iMaxCarry = GetAmmoDef()->MaxCarry( iAmmoIndex );
// Does the player have room for more of this type of ammo?
if ( pPlayer->GetAmmoCount( iAmmoIndex ) < iMaxCarry )
return true;
}
return false;
}
//-----------------------------------------------------------------------------
// Purpose: Return true if the specified player can carry any more of the ammo type
//-----------------------------------------------------------------------------
bool CGameRules::CanHaveAmmo( CBaseCombatCharacter *pPlayer, const char *szName )
{
return CanHaveAmmo( pPlayer, GetAmmoDef()->Index(szName) );
}
//=========================================================
//=========================================================
CBaseEntity *CGameRules::GetPlayerSpawnSpot( CBasePlayer *pPlayer )
{
CBaseEntity *pSpawnSpot = pPlayer->EntSelectSpawnPoint();
Assert( pSpawnSpot );
pPlayer->SetLocalOrigin( pSpawnSpot->GetAbsOrigin() + Vector(0,0,1) );
pPlayer->SetAbsVelocity( vec3_origin );
pPlayer->SetLocalAngles( pSpawnSpot->GetLocalAngles() );
pPlayer->m_Local.m_vecPunchAngle = vec3_angle;
pPlayer->m_Local.m_vecPunchAngleVel = vec3_angle;
pPlayer->SnapEyeAngles( pSpawnSpot->GetLocalAngles() );
return pSpawnSpot;
}
// checks if the spot is clear of players
bool CGameRules::IsSpawnPointValid( CBaseEntity *pSpot, CBasePlayer *pPlayer )
{
CBaseEntity *ent = NULL;
if ( !pSpot->IsTriggered( pPlayer ) )
{
return false;
}
for ( CEntitySphereQuery sphere( pSpot->GetAbsOrigin(), 128 ); (ent = sphere.GetCurrentEntity()) != NULL; sphere.NextEntity() )
{
// if ent is a client, don't spawn on 'em
if ( ent->IsPlayer() && ent != pPlayer )
return false;
}
return true;
}
//=========================================================
//=========================================================
bool CGameRules::CanHavePlayerItem( CBasePlayer *pPlayer, CBaseCombatWeapon *pWeapon )
{
/*
if ( pWeapon->m_pszAmmo1 )
{
if ( !CanHaveAmmo( pPlayer, pWeapon->m_iPrimaryAmmoType ) )
{
// we can't carry anymore ammo for this gun. We can only
// have the gun if we aren't already carrying one of this type
if ( pPlayer->Weapon_OwnsThisType( pWeapon ) )
{
return FALSE;
}
}
}
else
{
// weapon doesn't use ammo, don't take another if you already have it.
if ( pPlayer->Weapon_OwnsThisType( pWeapon ) )
{
return FALSE;
}
}
*/
// note: will fall through to here if GetItemInfo doesn't fill the struct!
return TRUE;
}
//=========================================================
// load the SkillData struct with the proper values based on the skill level.
//=========================================================
void CGameRules::RefreshSkillData ( bool forceUpdate )
{
#ifndef CLIENT_DLL
if ( !forceUpdate )
{
if ( GlobalEntity_IsInTable( "skill.cfg" ) )
return;
}
GlobalEntity_Add( "skill.cfg", STRING(gpGlobals->mapname), GLOBAL_ON );
char szExec[256];
ConVar const *skill = cvar->FindVar( "skill" );
SetSkillLevel( skill ? skill->GetInt() : 1 );
#ifdef HL2_DLL
// HL2 current only uses one skill config file that represents MEDIUM skill level and
// synthesizes EASY and HARD. (sjb)
Q_snprintf( szExec,sizeof(szExec), "exec skill_manifest.cfg\n" );
engine->ServerCommand( szExec );
engine->ServerExecute();
#else
Q_snprintf( szExec,sizeof(szExec), "exec skill%d.cfg\n", GetSkillLevel() );
engine->ServerCommand( szExec );
engine->ServerExecute();
#endif // HL2_DLL
#endif // CLIENT_DLL
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool IsExplosionTraceBlocked( trace_t *ptr )
{
if( ptr->DidHitWorld() )
return true;
if( ptr->m_pEnt == NULL )
return false;
if( ptr->m_pEnt->GetMoveType() == MOVETYPE_PUSH )
{
// All doors are push, but not all things that push are doors. This
// narrows the search before we start to do classname compares.
if( FClassnameIs(ptr->m_pEnt, "prop_door_rotating") ||
FClassnameIs(ptr->m_pEnt, "func_door") ||
FClassnameIs(ptr->m_pEnt, "func_door_rotating") )
return true;
}
return false;
}
//-----------------------------------------------------------------------------
// Default implementation of radius damage
//-----------------------------------------------------------------------------
#define ROBUST_RADIUS_PROBE_DIST 16.0f // If a solid surface blocks the explosion, this is how far to creep along the surface looking for another way to the target
void CGameRules::RadiusDamage( const CTakeDamageInfo &info, const Vector &vecSrcIn, float flRadius, int iClassIgnore, CBaseEntity *pEntityIgnore )
{
const int MASK_RADIUS_DAMAGE = MASK_SHOT&(~CONTENTS_HITBOX);
CBaseEntity *pEntity = NULL;
trace_t tr;
float flAdjustedDamage, falloff;
Vector vecSpot;
Vector vecSrc = vecSrcIn;
if ( flRadius )
falloff = info.GetDamage() / flRadius;
else
falloff = 1.0;
int bInWater = (UTIL_PointContents ( vecSrc ) & MASK_WATER) ? true : false;
#ifdef HL2_DLL
if( bInWater )
{
// Only muffle the explosion if deeper than 2 feet in water.
if( !(UTIL_PointContents(vecSrc + Vector(0, 0, 24)) & MASK_WATER) )
{
bInWater = false;
}
}
#endif // HL2_DLL
vecSrc.z += 1;// in case grenade is lying on the ground
float flHalfRadiusSqr = Square( flRadius / 2.0f );
// iterate on all entities in the vicinity.
for ( CEntitySphereQuery sphere( vecSrc, flRadius ); (pEntity = sphere.GetCurrentEntity()) != NULL; sphere.NextEntity() )
{
// This value is used to scale damage when the explosion is blocked by some other object.
float flBlockedDamagePercent = 0.0f;
if ( pEntity == pEntityIgnore )
continue;
if ( pEntity->m_takedamage == DAMAGE_NO )
continue;
// UNDONE: this should check a damage mask, not an ignore
if ( iClassIgnore != CLASS_NONE && pEntity->Classify() == iClassIgnore )
{// houndeyes don't hurt other houndeyes with their attack
continue;
}
// blast's don't tavel into or out of water
if (bInWater && pEntity->GetWaterLevel() == 0)
continue;
if (!bInWater && pEntity->GetWaterLevel() == 3)
continue;
// Check that the explosion can 'see' this entity.
vecSpot = pEntity->BodyTarget( vecSrc, false );
UTIL_TraceLine( vecSrc, vecSpot, MASK_RADIUS_DAMAGE, info.GetInflictor(), COLLISION_GROUP_NONE, &tr );
if( old_radius_damage.GetBool() )
{
if ( tr.fraction != 1.0 && tr.m_pEnt != pEntity )
continue;
}
else
{
if ( tr.fraction != 1.0 )
{
if ( IsExplosionTraceBlocked(&tr) )
{
if( ShouldUseRobustRadiusDamage( pEntity ) )
{
if( vecSpot.DistToSqr( vecSrc ) > flHalfRadiusSqr )
{
// Only use robust model on a target within one-half of the explosion's radius.
continue;
}
Vector vecToTarget = vecSpot - tr.endpos;
VectorNormalize( vecToTarget );
// We're going to deflect the blast along the surface that
// interrupted a trace from explosion to this target.
Vector vecUp, vecDeflect;
CrossProduct( vecToTarget, tr.plane.normal, vecUp );
CrossProduct( tr.plane.normal, vecUp, vecDeflect );
VectorNormalize( vecDeflect );
// Trace along the surface that intercepted the blast...
UTIL_TraceLine( tr.endpos, tr.endpos + vecDeflect * ROBUST_RADIUS_PROBE_DIST, MASK_RADIUS_DAMAGE, info.GetInflictor(), COLLISION_GROUP_NONE, &tr );
//NDebugOverlay::Line( tr.startpos, tr.endpos, 255, 255, 0, false, 10 );
// ...to see if there's a nearby edge that the explosion would 'spill over' if the blast were fully simulated.
UTIL_TraceLine( tr.endpos, vecSpot, MASK_RADIUS_DAMAGE, info.GetInflictor(), COLLISION_GROUP_NONE, &tr );
//NDebugOverlay::Line( tr.startpos, tr.endpos, 255, 0, 0, false, 10 );
if( tr.fraction != 1.0 && tr.DidHitWorld() )
{
// Still can't reach the target.
continue;
}
// else fall through
}
else
{
continue;
}
}
// UNDONE: Probably shouldn't let children block parents either? Or maybe those guys should set their owner if they want this behavior?
if( tr.m_pEnt && tr.m_pEnt != pEntity && tr.m_pEnt->GetOwnerEntity() != pEntity )
{
// Some entity was hit by the trace, meaning the explosion does not have clear
// line of sight to the entity that it's trying to hurt. If the world is also
// blocking, we do no damage.
CBaseEntity *pBlockingEntity = tr.m_pEnt;
//Msg( "%s may be blocked by %s...", pEntity->GetClassname(), pBlockingEntity->GetClassname() );
UTIL_TraceLine( vecSrc, vecSpot, CONTENTS_SOLID, info.GetInflictor(), COLLISION_GROUP_NONE, &tr );
if( tr.fraction != 1.0 )
{
continue;
}
// Now, if the interposing object is physics, block some explosion force based on its mass.
if( pBlockingEntity->VPhysicsGetObject() )
{
const float MASS_ABSORB_ALL_DAMAGE = 350.0f;
float flMass = pBlockingEntity->VPhysicsGetObject()->GetMass();
float scale = flMass / MASS_ABSORB_ALL_DAMAGE;
// Absorbed all the damage.
if( scale >= 1.0f )
{
continue;
}
ASSERT( scale > 0.0f );
flBlockedDamagePercent = scale;
//Msg("Object (%s) weighing %fkg blocked %f percent of explosion damage\n", pBlockingEntity->GetClassname(), flMass, scale * 100.0f);
}
else
{
// Some object that's not the world and not physics. Generically block 25% damage
flBlockedDamagePercent = 0.25f;
}
}
}
}
// decrease damage for an ent that's farther from the bomb.
flAdjustedDamage = ( vecSrc - tr.endpos ).Length() * falloff;
flAdjustedDamage = info.GetDamage() - flAdjustedDamage;
if ( flAdjustedDamage <= 0 )
continue;
// the explosion can 'see' this entity, so hurt them!
if (tr.startsolid)
{
// if we're stuck inside them, fixup the position and distance
tr.endpos = vecSrc;
tr.fraction = 0.0;
}
CTakeDamageInfo adjustedInfo = info;
//Msg("%s: Blocked damage: %f percent (in:%f out:%f)\n", pEntity->GetClassname(), flBlockedDamagePercent * 100, flAdjustedDamage, flAdjustedDamage - (flAdjustedDamage * flBlockedDamagePercent) );
adjustedInfo.SetDamage( flAdjustedDamage - (flAdjustedDamage * flBlockedDamagePercent) );
// Now make a consideration for skill level!
if( info.GetAttacker() && info.GetAttacker()->IsPlayer() && pEntity->IsNPC() )
{
// An explosion set off by the player is harming an NPC. Adjust damage accordingly.
adjustedInfo.AdjustPlayerDamageInflictedForSkillLevel();
}
Vector dir = vecSpot - vecSrc;
VectorNormalize( dir );
// If we don't have a damage force, manufacture one
if ( adjustedInfo.GetDamagePosition() == vec3_origin || adjustedInfo.GetDamageForce() == vec3_origin )
{
CalculateExplosiveDamageForce( &adjustedInfo, dir, vecSrc );
}
else
{
// Assume the force passed in is the maximum force. Decay it based on falloff.
float flForce = adjustedInfo.GetDamageForce().Length() * falloff;
adjustedInfo.SetDamageForce( dir * flForce );
adjustedInfo.SetDamagePosition( vecSrc );
}
if ( tr.fraction != 1.0 && pEntity == tr.m_pEnt )
{
ClearMultiDamage( );
pEntity->DispatchTraceAttack( adjustedInfo, dir, &tr );
ApplyMultiDamage();
}
else
{
pEntity->TakeDamage( adjustedInfo );
}
// Now hit all triggers along the way that respond to damage...
pEntity->TraceAttackToTriggers( adjustedInfo, vecSrc, tr.endpos, dir );
}
}
bool CGameRules::ClientCommand( const char *pcmd, CBaseEntity *pEdict )
{
if( pEdict->IsPlayer() )
{
if( GetVoiceGameMgr()->ClientCommand( static_cast<CBasePlayer*>(pEdict), pcmd ) )
return true;
}
return false;
}
void CGameRules::FrameUpdatePostEntityThink()
{
VPROF( "CGameRules::FrameUpdatePostEntityThink" );
Think();
}
// Hook into the convar from the engine
ConVar skill( "skill", "0" );
void CGameRules::Think()
{
GetVoiceGameMgr()->Update( gpGlobals->frametime );
SetSkillLevel( skill.GetInt() );
}
//-----------------------------------------------------------------------------
// Purpose: Called at the end of GameFrame (i.e. after all game logic has run this frame)
//-----------------------------------------------------------------------------
void CGameRules::EndGameFrame( void )
{
// If you hit this assert, it means something called AddMultiDamage() and didn't ApplyMultiDamage().
// The g_MultiDamage.m_hAttacker & g_MultiDamage.m_hInflictor should give help you figure out the culprit.
Assert( g_MultiDamage.IsClear() );
if ( !g_MultiDamage.IsClear() )
{
Warning("Unapplied multidamage left in the system:\nTarget: %s\nInflictor: %s\nAttacker: %s\nDamage: %.2f\n",
g_MultiDamage.GetTarget()->GetDebugName(),
g_MultiDamage.GetInflictor()->GetDebugName(),
g_MultiDamage.GetAttacker()->GetDebugName(),
g_MultiDamage.GetDamage() );
ApplyMultiDamage();
}
}
//-----------------------------------------------------------------------------
// trace line rules
//-----------------------------------------------------------------------------
float CGameRules::WeaponTraceEntity( CBaseEntity *pEntity, const Vector &vecStart, const Vector &vecEnd,
unsigned int mask, trace_t *ptr )
{
UTIL_TraceEntity( pEntity, vecStart, vecEnd, mask, ptr );
return 1.0f;
}
void CGameRules::CreateStandardEntities()
{
g_pPlayerResource = (CPlayerResource*)CBaseEntity::Create( "player_manager", vec3_origin, vec3_angle );
g_pPlayerResource->AddEFlags( EFL_KEEP_ON_RECREATE_ENTITIES );
}
#endif //} !CLIENT_DLL
// ----------------------------------------------------------------------------- //
// Shared CGameRules implementation.
// ----------------------------------------------------------------------------- //
CGameRules::~CGameRules()
{
Assert( g_pGameRules == this );
g_pGameRules = NULL;
}
bool CGameRules::SwitchToNextBestWeapon( CBaseCombatCharacter *pPlayer, CBaseCombatWeapon *pCurrentWeapon )
{
return false;
}
CBaseCombatWeapon *CGameRules::GetNextBestWeapon( CBaseCombatCharacter *pPlayer, CBaseCombatWeapon *pCurrentWeapon )
{
return NULL;
}
bool CGameRules::ShouldCollide( int collisionGroup0, int collisionGroup1 )
{
if ( collisionGroup0 > collisionGroup1 )
{
// swap so that lowest is always first
swap(collisionGroup0,collisionGroup1);
}
#ifndef HL2MP
if ( (collisionGroup0 == COLLISION_GROUP_PLAYER || collisionGroup0 == COLLISION_GROUP_PLAYER_MOVEMENT) &&
collisionGroup1 == COLLISION_GROUP_PUSHAWAY )
{
return false;
}
#endif
if ( collisionGroup0 == COLLISION_GROUP_DEBRIS && collisionGroup1 == COLLISION_GROUP_PUSHAWAY )
{
// let debris and multiplayer objects collide
return true;
}
// --------------------------------------------------------------------------
// NOTE: All of this code assumes the collision groups have been sorted!!!!
// NOTE: Don't change their order without rewriting this code !!!
// --------------------------------------------------------------------------
// Don't bother if either is in a vehicle...
if (( collisionGroup0 == COLLISION_GROUP_IN_VEHICLE ) || ( collisionGroup1 == COLLISION_GROUP_IN_VEHICLE ))
return false;
if ( ( collisionGroup1 == COLLISION_GROUP_DOOR_BLOCKER ) && ( collisionGroup0 != COLLISION_GROUP_NPC ) )
return false;
if ( ( collisionGroup0 == COLLISION_GROUP_PLAYER ) && ( collisionGroup1 == COLLISION_GROUP_PASSABLE_DOOR ) )
return false;
if ( collisionGroup0 == COLLISION_GROUP_DEBRIS || collisionGroup0 == COLLISION_GROUP_DEBRIS_TRIGGER )
{
// put exceptions here, right now this will only collide with COLLISION_GROUP_NONE
return false;
}
// Dissolving guys only collide with COLLISION_GROUP_NONE
if ( (collisionGroup0 == COLLISION_GROUP_DISSOLVING) || (collisionGroup1 == COLLISION_GROUP_DISSOLVING) )
{
if ( collisionGroup0 != COLLISION_GROUP_NONE )
return false;
}
// doesn't collide with other members of this group
// or debris, but that's handled above
if ( collisionGroup0 == COLLISION_GROUP_INTERACTIVE_DEBRIS && collisionGroup1 == COLLISION_GROUP_INTERACTIVE_DEBRIS )
return false;
#ifndef HL2MP
// This change was breaking HL2DM
// Adrian: TEST! Interactive Debris doesn't collide with the player.
if ( collisionGroup0 == COLLISION_GROUP_INTERACTIVE_DEBRIS && ( collisionGroup1 == COLLISION_GROUP_PLAYER_MOVEMENT || collisionGroup1 == COLLISION_GROUP_PLAYER ) )
return false;
#endif
if ( collisionGroup0 == COLLISION_GROUP_BREAKABLE_GLASS && collisionGroup1 == COLLISION_GROUP_BREAKABLE_GLASS )
return false;
// interactive objects collide with everything except debris & interactive debris
if ( collisionGroup1 == COLLISION_GROUP_INTERACTIVE && collisionGroup0 != COLLISION_GROUP_NONE )
return false;
// Projectiles hit everything but debris, weapons, + other projectiles
if ( collisionGroup1 == COLLISION_GROUP_PROJECTILE )
{
if ( collisionGroup0 == COLLISION_GROUP_DEBRIS ||
collisionGroup0 == COLLISION_GROUP_WEAPON ||
collisionGroup0 == COLLISION_GROUP_PROJECTILE )
{
return false;
}
}
// Don't let vehicles collide with weapons
// Don't let players collide with weapons...
// Don't let NPCs collide with weapons
// Weapons are triggers, too, so they should still touch because of that
if ( collisionGroup1 == COLLISION_GROUP_WEAPON )
{
if ( collisionGroup0 == COLLISION_GROUP_VEHICLE ||
collisionGroup0 == COLLISION_GROUP_PLAYER ||
collisionGroup0 == COLLISION_GROUP_NPC )
{
return false;
}
}
// collision with vehicle clip entity??
if ( collisionGroup0 == COLLISION_GROUP_VEHICLE_CLIP || collisionGroup1 == COLLISION_GROUP_VEHICLE_CLIP )
{
// yes then if it's a vehicle, collide, otherwise no collision
// vehicle sorts lower than vehicle clip, so must be in 0
if ( collisionGroup0 == COLLISION_GROUP_VEHICLE )
return true;
// vehicle clip against non-vehicle, no collision
return false;
}
return true;
}
const CViewVectors* CGameRules::GetViewVectors() const
{
return &g_DefaultViewVectors;
}
//-----------------------------------------------------------------------------
// Purpose: Returns how much damage the given ammo type should do to the victim
// when fired by the attacker.
// Input : pAttacker - Dude what shot the gun.
// pVictim - Dude what done got shot.
// nAmmoType - What been shot out.
// Output : How much hurt to put on dude what done got shot (pVictim).
//-----------------------------------------------------------------------------
float CGameRules::GetAmmoDamage( CBaseEntity *pAttacker, CBaseEntity *pVictim, int nAmmoType )
{
float flDamage = 0;
CAmmoDef *pAmmoDef = GetAmmoDef();
if ( pAttacker->IsPlayer() )
{
flDamage = pAmmoDef->PlrDamage( nAmmoType );
}
else
{
flDamage = pAmmoDef->NPCDamage( nAmmoType );
}
return flDamage;
}
#ifndef CLIENT_DLL
const char *CGameRules::GetChatPrefix( bool bTeamOnly, CBasePlayer *pPlayer )
{
if ( bTeamOnly )
return "(TEAM)";
else
return "";
}
void CGameRules::ClientSettingsChanged( CBasePlayer *pPlayer )
{
const char *pszName = engine->GetClientConVarValue( pPlayer->entindex(), "name" );
const char *pszOldName = pPlayer->GetPlayerName();
// msg everyone if someone changes their name, and it isn't the first time (changing no name to current name)
// Note, not using FStrEq so that this is case sensitive
if ( pszOldName[0] != 0 && Q_strcmp( pszOldName, pszName ) )
{
char text[256];
Q_snprintf( text,sizeof(text), "%s changed name to %s\n", pszOldName, pszName );
UTIL_ClientPrintAll( HUD_PRINTTALK, text );
IGameEvent * event = gameeventmanager->CreateEvent( "player_changename" );
if ( event )
{
event->SetInt( "userid", pPlayer->GetUserID() );
event->SetString( "oldname", pszOldName );
event->SetString( "newname", pszName );
gameeventmanager->FireEvent( event );
}
pPlayer->SetPlayerName( pszName );
}
}
#endif