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.
1629 lines
38 KiB
1629 lines
38 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: Player for HL2. |
|
// |
|
//=============================================================================// |
|
|
|
#include "cbase.h" |
|
#include "weapon_hl2mpbasehlmpcombatweapon.h" |
|
#include "hl2mp_player.h" |
|
#include "globalstate.h" |
|
#include "game.h" |
|
#include "gamerules.h" |
|
#include "hl2mp_player_shared.h" |
|
#include "predicted_viewmodel.h" |
|
#include "in_buttons.h" |
|
#include "hl2mp_gamerules.h" |
|
#include "KeyValues.h" |
|
#include "team.h" |
|
#include "weapon_hl2mpbase.h" |
|
#include "grenade_satchel.h" |
|
#include "eventqueue.h" |
|
#include "gamestats.h" |
|
|
|
#include "engine/IEngineSound.h" |
|
#include "SoundEmitterSystem/isoundemittersystembase.h" |
|
|
|
#include "ilagcompensationmanager.h" |
|
|
|
int g_iLastCitizenModel = 0; |
|
int g_iLastCombineModel = 0; |
|
|
|
CBaseEntity *g_pLastCombineSpawn = NULL; |
|
CBaseEntity *g_pLastRebelSpawn = NULL; |
|
extern CBaseEntity *g_pLastSpawn; |
|
|
|
#define HL2MP_COMMAND_MAX_RATE 0.3 |
|
|
|
void DropPrimedFragGrenade( CHL2MP_Player *pPlayer, CBaseCombatWeapon *pGrenade ); |
|
|
|
LINK_ENTITY_TO_CLASS( player, CHL2MP_Player ); |
|
|
|
LINK_ENTITY_TO_CLASS( info_player_combine, CPointEntity ); |
|
LINK_ENTITY_TO_CLASS( info_player_rebel, CPointEntity ); |
|
|
|
IMPLEMENT_SERVERCLASS_ST(CHL2MP_Player, DT_HL2MP_Player) |
|
SendPropAngle( SENDINFO_VECTORELEM(m_angEyeAngles, 0), 11, SPROP_CHANGES_OFTEN ), |
|
SendPropAngle( SENDINFO_VECTORELEM(m_angEyeAngles, 1), 11, SPROP_CHANGES_OFTEN ), |
|
SendPropEHandle( SENDINFO( m_hRagdoll ) ), |
|
SendPropInt( SENDINFO( m_iSpawnInterpCounter), 4 ), |
|
SendPropInt( SENDINFO( m_iPlayerSoundType), 3 ), |
|
|
|
SendPropExclude( "DT_BaseAnimating", "m_flPoseParameter" ), |
|
SendPropExclude( "DT_BaseFlex", "m_viewtarget" ), |
|
|
|
// SendPropExclude( "DT_ServerAnimationData" , "m_flCycle" ), |
|
// SendPropExclude( "DT_AnimTimeMustBeFirst" , "m_flAnimTime" ), |
|
|
|
END_SEND_TABLE() |
|
|
|
BEGIN_DATADESC( CHL2MP_Player ) |
|
END_DATADESC() |
|
|
|
const char *g_ppszRandomCitizenModels[] = |
|
{ |
|
"models/humans/group03/male_01.mdl", |
|
"models/humans/group03/male_02.mdl", |
|
"models/humans/group03/female_01.mdl", |
|
"models/humans/group03/male_03.mdl", |
|
"models/humans/group03/female_02.mdl", |
|
"models/humans/group03/male_04.mdl", |
|
"models/humans/group03/female_03.mdl", |
|
"models/humans/group03/male_05.mdl", |
|
"models/humans/group03/female_04.mdl", |
|
"models/humans/group03/male_06.mdl", |
|
"models/humans/group03/female_06.mdl", |
|
"models/humans/group03/male_07.mdl", |
|
"models/humans/group03/female_07.mdl", |
|
"models/humans/group03/male_08.mdl", |
|
"models/humans/group03/male_09.mdl", |
|
}; |
|
|
|
const char *g_ppszRandomCombineModels[] = |
|
{ |
|
"models/combine_soldier.mdl", |
|
"models/combine_soldier_prisonguard.mdl", |
|
"models/combine_super_soldier.mdl", |
|
"models/police.mdl", |
|
}; |
|
|
|
|
|
#define MAX_COMBINE_MODELS 4 |
|
#define MODEL_CHANGE_INTERVAL 5.0f |
|
#define TEAM_CHANGE_INTERVAL 5.0f |
|
|
|
#define HL2MPPLAYER_PHYSDAMAGE_SCALE 4.0f |
|
|
|
#pragma warning( disable : 4355 ) |
|
|
|
CHL2MP_Player::CHL2MP_Player() : m_PlayerAnimState( this ) |
|
{ |
|
m_angEyeAngles.Init(); |
|
|
|
m_iLastWeaponFireUsercmd = 0; |
|
|
|
m_flNextModelChangeTime = 0.0f; |
|
m_flNextTeamChangeTime = 0.0f; |
|
|
|
m_iSpawnInterpCounter = 0; |
|
|
|
m_bEnterObserver = false; |
|
m_bReady = false; |
|
|
|
BaseClass::ChangeTeam( 0 ); |
|
|
|
// UseClientSideAnimation(); |
|
} |
|
|
|
CHL2MP_Player::~CHL2MP_Player( void ) |
|
{ |
|
|
|
} |
|
|
|
void CHL2MP_Player::UpdateOnRemove( void ) |
|
{ |
|
if ( m_hRagdoll ) |
|
{ |
|
UTIL_RemoveImmediate( m_hRagdoll ); |
|
m_hRagdoll = NULL; |
|
} |
|
|
|
BaseClass::UpdateOnRemove(); |
|
} |
|
|
|
void CHL2MP_Player::Precache( void ) |
|
{ |
|
BaseClass::Precache(); |
|
|
|
PrecacheModel ( "sprites/glow01.vmt" ); |
|
|
|
//Precache Citizen models |
|
int nHeads = ARRAYSIZE( g_ppszRandomCitizenModels ); |
|
int i; |
|
|
|
for ( i = 0; i < nHeads; ++i ) |
|
PrecacheModel( g_ppszRandomCitizenModels[i] ); |
|
|
|
//Precache Combine Models |
|
nHeads = ARRAYSIZE( g_ppszRandomCombineModels ); |
|
|
|
for ( i = 0; i < nHeads; ++i ) |
|
PrecacheModel( g_ppszRandomCombineModels[i] ); |
|
|
|
PrecacheFootStepSounds(); |
|
|
|
PrecacheScriptSound( "NPC_MetroPolice.Die" ); |
|
PrecacheScriptSound( "NPC_CombineS.Die" ); |
|
PrecacheScriptSound( "NPC_Citizen.die" ); |
|
} |
|
|
|
void CHL2MP_Player::GiveAllItems( void ) |
|
{ |
|
EquipSuit(); |
|
|
|
CBasePlayer::GiveAmmo( 255, "Pistol"); |
|
CBasePlayer::GiveAmmo( 255, "AR2" ); |
|
CBasePlayer::GiveAmmo( 5, "AR2AltFire" ); |
|
CBasePlayer::GiveAmmo( 255, "SMG1"); |
|
CBasePlayer::GiveAmmo( 1, "smg1_grenade"); |
|
CBasePlayer::GiveAmmo( 255, "Buckshot"); |
|
CBasePlayer::GiveAmmo( 32, "357" ); |
|
CBasePlayer::GiveAmmo( 3, "rpg_round"); |
|
|
|
CBasePlayer::GiveAmmo( 1, "grenade" ); |
|
CBasePlayer::GiveAmmo( 2, "slam" ); |
|
|
|
GiveNamedItem( "weapon_crowbar" ); |
|
GiveNamedItem( "weapon_stunstick" ); |
|
GiveNamedItem( "weapon_pistol" ); |
|
GiveNamedItem( "weapon_357" ); |
|
|
|
GiveNamedItem( "weapon_smg1" ); |
|
GiveNamedItem( "weapon_ar2" ); |
|
|
|
GiveNamedItem( "weapon_shotgun" ); |
|
GiveNamedItem( "weapon_frag" ); |
|
|
|
GiveNamedItem( "weapon_crossbow" ); |
|
|
|
GiveNamedItem( "weapon_rpg" ); |
|
|
|
GiveNamedItem( "weapon_slam" ); |
|
|
|
GiveNamedItem( "weapon_physcannon" ); |
|
|
|
} |
|
|
|
void CHL2MP_Player::GiveDefaultItems( void ) |
|
{ |
|
EquipSuit(); |
|
|
|
CBasePlayer::GiveAmmo( 255, "Pistol"); |
|
CBasePlayer::GiveAmmo( 45, "SMG1"); |
|
CBasePlayer::GiveAmmo( 1, "grenade" ); |
|
CBasePlayer::GiveAmmo( 6, "Buckshot"); |
|
CBasePlayer::GiveAmmo( 6, "357" ); |
|
|
|
if ( GetPlayerModelType() == PLAYER_SOUNDS_METROPOLICE || GetPlayerModelType() == PLAYER_SOUNDS_COMBINESOLDIER ) |
|
{ |
|
GiveNamedItem( "weapon_stunstick" ); |
|
} |
|
else if ( GetPlayerModelType() == PLAYER_SOUNDS_CITIZEN ) |
|
{ |
|
GiveNamedItem( "weapon_crowbar" ); |
|
} |
|
|
|
GiveNamedItem( "weapon_pistol" ); |
|
GiveNamedItem( "weapon_smg1" ); |
|
GiveNamedItem( "weapon_frag" ); |
|
GiveNamedItem( "weapon_physcannon" ); |
|
|
|
const char *szDefaultWeaponName = engine->GetClientConVarValue( engine->IndexOfEdict( edict() ), "cl_defaultweapon" ); |
|
|
|
CBaseCombatWeapon *pDefaultWeapon = Weapon_OwnsThisType( szDefaultWeaponName ); |
|
|
|
if ( pDefaultWeapon ) |
|
{ |
|
Weapon_Switch( pDefaultWeapon ); |
|
} |
|
else |
|
{ |
|
Weapon_Switch( Weapon_OwnsThisType( "weapon_physcannon" ) ); |
|
} |
|
} |
|
|
|
void CHL2MP_Player::PickDefaultSpawnTeam( void ) |
|
{ |
|
if ( GetTeamNumber() == 0 ) |
|
{ |
|
if ( HL2MPRules()->IsTeamplay() == false ) |
|
{ |
|
if ( GetModelPtr() == NULL ) |
|
{ |
|
const char *szModelName = NULL; |
|
szModelName = engine->GetClientConVarValue( engine->IndexOfEdict( edict() ), "cl_playermodel" ); |
|
|
|
if ( ValidatePlayerModel( szModelName ) == false ) |
|
{ |
|
char szReturnString[512]; |
|
|
|
Q_snprintf( szReturnString, sizeof (szReturnString ), "cl_playermodel models/combine_soldier.mdl\n" ); |
|
engine->ClientCommand ( edict(), szReturnString ); |
|
} |
|
|
|
ChangeTeam( TEAM_UNASSIGNED ); |
|
} |
|
} |
|
else |
|
{ |
|
CTeam *pCombine = g_Teams[TEAM_COMBINE]; |
|
CTeam *pRebels = g_Teams[TEAM_REBELS]; |
|
|
|
if ( pCombine == NULL || pRebels == NULL ) |
|
{ |
|
ChangeTeam( random->RandomInt( TEAM_COMBINE, TEAM_REBELS ) ); |
|
} |
|
else |
|
{ |
|
if ( pCombine->GetNumPlayers() > pRebels->GetNumPlayers() ) |
|
{ |
|
ChangeTeam( TEAM_REBELS ); |
|
} |
|
else if ( pCombine->GetNumPlayers() < pRebels->GetNumPlayers() ) |
|
{ |
|
ChangeTeam( TEAM_COMBINE ); |
|
} |
|
else |
|
{ |
|
ChangeTeam( random->RandomInt( TEAM_COMBINE, TEAM_REBELS ) ); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Sets HL2 specific defaults. |
|
//----------------------------------------------------------------------------- |
|
void CHL2MP_Player::Spawn(void) |
|
{ |
|
m_flNextModelChangeTime = 0.0f; |
|
m_flNextTeamChangeTime = 0.0f; |
|
|
|
PickDefaultSpawnTeam(); |
|
|
|
BaseClass::Spawn(); |
|
|
|
if ( !IsObserver() ) |
|
{ |
|
pl.deadflag = false; |
|
RemoveSolidFlags( FSOLID_NOT_SOLID ); |
|
|
|
RemoveEffects( EF_NODRAW ); |
|
|
|
GiveDefaultItems(); |
|
} |
|
|
|
SetNumAnimOverlays( 3 ); |
|
ResetAnimation(); |
|
|
|
m_nRenderFX = kRenderNormal; |
|
|
|
m_Local.m_iHideHUD = 0; |
|
|
|
AddFlag(FL_ONGROUND); // set the player on the ground at the start of the round. |
|
|
|
m_impactEnergyScale = HL2MPPLAYER_PHYSDAMAGE_SCALE; |
|
|
|
if ( HL2MPRules()->IsIntermission() ) |
|
{ |
|
AddFlag( FL_FROZEN ); |
|
} |
|
else |
|
{ |
|
RemoveFlag( FL_FROZEN ); |
|
} |
|
|
|
m_iSpawnInterpCounter = (m_iSpawnInterpCounter + 1) % 8; |
|
|
|
m_Local.m_bDucked = false; |
|
|
|
SetPlayerUnderwater(false); |
|
|
|
m_bReady = false; |
|
} |
|
|
|
void CHL2MP_Player::PickupObject( CBaseEntity *pObject, bool bLimitMassAndSize ) |
|
{ |
|
|
|
} |
|
|
|
bool CHL2MP_Player::ValidatePlayerModel( const char *pModel ) |
|
{ |
|
int iModels = ARRAYSIZE( g_ppszRandomCitizenModels ); |
|
int i; |
|
|
|
for ( i = 0; i < iModels; ++i ) |
|
{ |
|
if ( !Q_stricmp( g_ppszRandomCitizenModels[i], pModel ) ) |
|
{ |
|
return true; |
|
} |
|
} |
|
|
|
iModels = ARRAYSIZE( g_ppszRandomCombineModels ); |
|
|
|
for ( i = 0; i < iModels; ++i ) |
|
{ |
|
if ( !Q_stricmp( g_ppszRandomCombineModels[i], pModel ) ) |
|
{ |
|
return true; |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
void CHL2MP_Player::SetPlayerTeamModel( void ) |
|
{ |
|
const char *szModelName = NULL; |
|
szModelName = engine->GetClientConVarValue( engine->IndexOfEdict( edict() ), "cl_playermodel" ); |
|
|
|
int modelIndex = modelinfo->GetModelIndex( szModelName ); |
|
|
|
if ( modelIndex == -1 || ValidatePlayerModel( szModelName ) == false ) |
|
{ |
|
szModelName = "models/Combine_Soldier.mdl"; |
|
m_iModelType = TEAM_COMBINE; |
|
|
|
char szReturnString[512]; |
|
|
|
Q_snprintf( szReturnString, sizeof (szReturnString ), "cl_playermodel %s\n", szModelName ); |
|
engine->ClientCommand ( edict(), szReturnString ); |
|
} |
|
|
|
if ( GetTeamNumber() == TEAM_COMBINE ) |
|
{ |
|
if ( Q_stristr( szModelName, "models/human") ) |
|
{ |
|
int nHeads = ARRAYSIZE( g_ppszRandomCombineModels ); |
|
|
|
g_iLastCombineModel = ( g_iLastCombineModel + 1 ) % nHeads; |
|
szModelName = g_ppszRandomCombineModels[g_iLastCombineModel]; |
|
} |
|
|
|
m_iModelType = TEAM_COMBINE; |
|
} |
|
else if ( GetTeamNumber() == TEAM_REBELS ) |
|
{ |
|
if ( !Q_stristr( szModelName, "models/human") ) |
|
{ |
|
int nHeads = ARRAYSIZE( g_ppszRandomCitizenModels ); |
|
|
|
g_iLastCitizenModel = ( g_iLastCitizenModel + 1 ) % nHeads; |
|
szModelName = g_ppszRandomCitizenModels[g_iLastCitizenModel]; |
|
} |
|
|
|
m_iModelType = TEAM_REBELS; |
|
} |
|
|
|
SetModel( szModelName ); |
|
SetupPlayerSoundsByModel( szModelName ); |
|
|
|
m_flNextModelChangeTime = gpGlobals->curtime + MODEL_CHANGE_INTERVAL; |
|
} |
|
|
|
void CHL2MP_Player::SetPlayerModel( void ) |
|
{ |
|
const char *szModelName = NULL; |
|
const char *pszCurrentModelName = modelinfo->GetModelName( GetModel()); |
|
|
|
szModelName = engine->GetClientConVarValue( engine->IndexOfEdict( edict() ), "cl_playermodel" ); |
|
|
|
if ( ValidatePlayerModel( szModelName ) == false ) |
|
{ |
|
char szReturnString[512]; |
|
|
|
if ( ValidatePlayerModel( pszCurrentModelName ) == false ) |
|
{ |
|
pszCurrentModelName = "models/Combine_Soldier.mdl"; |
|
} |
|
|
|
Q_snprintf( szReturnString, sizeof (szReturnString ), "cl_playermodel %s\n", pszCurrentModelName ); |
|
engine->ClientCommand ( edict(), szReturnString ); |
|
|
|
szModelName = pszCurrentModelName; |
|
} |
|
|
|
if ( GetTeamNumber() == TEAM_COMBINE ) |
|
{ |
|
int nHeads = ARRAYSIZE( g_ppszRandomCombineModels ); |
|
|
|
g_iLastCombineModel = ( g_iLastCombineModel + 1 ) % nHeads; |
|
szModelName = g_ppszRandomCombineModels[g_iLastCombineModel]; |
|
|
|
m_iModelType = TEAM_COMBINE; |
|
} |
|
else if ( GetTeamNumber() == TEAM_REBELS ) |
|
{ |
|
int nHeads = ARRAYSIZE( g_ppszRandomCitizenModels ); |
|
|
|
g_iLastCitizenModel = ( g_iLastCitizenModel + 1 ) % nHeads; |
|
szModelName = g_ppszRandomCitizenModels[g_iLastCitizenModel]; |
|
|
|
m_iModelType = TEAM_REBELS; |
|
} |
|
else |
|
{ |
|
if ( Q_strlen( szModelName ) == 0 ) |
|
{ |
|
szModelName = g_ppszRandomCitizenModels[0]; |
|
} |
|
|
|
if ( Q_stristr( szModelName, "models/human") ) |
|
{ |
|
m_iModelType = TEAM_REBELS; |
|
} |
|
else |
|
{ |
|
m_iModelType = TEAM_COMBINE; |
|
} |
|
} |
|
|
|
int modelIndex = modelinfo->GetModelIndex( szModelName ); |
|
|
|
if ( modelIndex == -1 ) |
|
{ |
|
szModelName = "models/Combine_Soldier.mdl"; |
|
m_iModelType = TEAM_COMBINE; |
|
|
|
char szReturnString[512]; |
|
|
|
Q_snprintf( szReturnString, sizeof (szReturnString ), "cl_playermodel %s\n", szModelName ); |
|
engine->ClientCommand ( edict(), szReturnString ); |
|
} |
|
|
|
SetModel( szModelName ); |
|
SetupPlayerSoundsByModel( szModelName ); |
|
|
|
m_flNextModelChangeTime = gpGlobals->curtime + MODEL_CHANGE_INTERVAL; |
|
} |
|
|
|
void CHL2MP_Player::SetupPlayerSoundsByModel( const char *pModelName ) |
|
{ |
|
if ( Q_stristr( pModelName, "models/human") ) |
|
{ |
|
m_iPlayerSoundType = (int)PLAYER_SOUNDS_CITIZEN; |
|
} |
|
else if ( Q_stristr(pModelName, "police" ) ) |
|
{ |
|
m_iPlayerSoundType = (int)PLAYER_SOUNDS_METROPOLICE; |
|
} |
|
else if ( Q_stristr(pModelName, "combine" ) ) |
|
{ |
|
m_iPlayerSoundType = (int)PLAYER_SOUNDS_COMBINESOLDIER; |
|
} |
|
} |
|
|
|
void CHL2MP_Player::ResetAnimation( void ) |
|
{ |
|
if ( IsAlive() ) |
|
{ |
|
SetSequence ( -1 ); |
|
SetActivity( ACT_INVALID ); |
|
|
|
if (!GetAbsVelocity().x && !GetAbsVelocity().y) |
|
SetAnimation( PLAYER_IDLE ); |
|
else if ((GetAbsVelocity().x || GetAbsVelocity().y) && ( GetFlags() & FL_ONGROUND )) |
|
SetAnimation( PLAYER_WALK ); |
|
else if (GetWaterLevel() > 1) |
|
SetAnimation( PLAYER_WALK ); |
|
} |
|
} |
|
|
|
|
|
bool CHL2MP_Player::Weapon_Switch( CBaseCombatWeapon *pWeapon, int viewmodelindex ) |
|
{ |
|
bool bRet = BaseClass::Weapon_Switch( pWeapon, viewmodelindex ); |
|
|
|
if ( bRet == true ) |
|
{ |
|
ResetAnimation(); |
|
} |
|
|
|
return bRet; |
|
} |
|
|
|
void CHL2MP_Player::PreThink( void ) |
|
{ |
|
QAngle vOldAngles = GetLocalAngles(); |
|
QAngle vTempAngles = GetLocalAngles(); |
|
|
|
vTempAngles = EyeAngles(); |
|
|
|
if ( vTempAngles[PITCH] > 180.0f ) |
|
{ |
|
vTempAngles[PITCH] -= 360.0f; |
|
} |
|
|
|
SetLocalAngles( vTempAngles ); |
|
|
|
BaseClass::PreThink(); |
|
State_PreThink(); |
|
|
|
//Reset bullet force accumulator, only lasts one frame |
|
m_vecTotalBulletForce = vec3_origin; |
|
SetLocalAngles( vOldAngles ); |
|
} |
|
|
|
void CHL2MP_Player::PostThink( void ) |
|
{ |
|
BaseClass::PostThink(); |
|
|
|
if ( GetFlags() & FL_DUCKING ) |
|
{ |
|
SetCollisionBounds( VEC_CROUCH_TRACE_MIN, VEC_CROUCH_TRACE_MAX ); |
|
} |
|
|
|
m_PlayerAnimState.Update(); |
|
|
|
// Store the eye angles pitch so the client can compute its animation state correctly. |
|
m_angEyeAngles = EyeAngles(); |
|
|
|
QAngle angles = GetLocalAngles(); |
|
angles[PITCH] = 0; |
|
SetLocalAngles( angles ); |
|
} |
|
|
|
void CHL2MP_Player::PlayerDeathThink() |
|
{ |
|
if( !IsObserver() ) |
|
{ |
|
BaseClass::PlayerDeathThink(); |
|
} |
|
} |
|
|
|
void CHL2MP_Player::FireBullets ( const FireBulletsInfo_t &info ) |
|
{ |
|
// Move other players back to history positions based on local player's lag |
|
lagcompensation->StartLagCompensation( this, this->GetCurrentCommand() ); |
|
|
|
FireBulletsInfo_t modinfo = info; |
|
|
|
CWeaponHL2MPBase *pWeapon = dynamic_cast<CWeaponHL2MPBase *>( GetActiveWeapon() ); |
|
|
|
if ( pWeapon ) |
|
{ |
|
modinfo.m_iPlayerDamage = modinfo.m_flDamage = pWeapon->GetHL2MPWpnData().m_iPlayerDamage; |
|
} |
|
|
|
NoteWeaponFired(); |
|
|
|
BaseClass::FireBullets( modinfo ); |
|
|
|
// Move other players back to history positions based on local player's lag |
|
lagcompensation->FinishLagCompensation( this ); |
|
} |
|
|
|
void CHL2MP_Player::NoteWeaponFired( void ) |
|
{ |
|
Assert( m_pCurrentCommand ); |
|
if( m_pCurrentCommand ) |
|
{ |
|
m_iLastWeaponFireUsercmd = m_pCurrentCommand->command_number; |
|
} |
|
} |
|
|
|
extern ConVar sv_maxunlag; |
|
|
|
bool CHL2MP_Player::WantsLagCompensationOnEntity( const CBasePlayer *pPlayer, const CUserCmd *pCmd, const CBitVec<MAX_EDICTS> *pEntityTransmitBits ) const |
|
{ |
|
// No need to lag compensate at all if we're not attacking in this command and |
|
// we haven't attacked recently. |
|
if ( !( pCmd->buttons & IN_ATTACK ) && (pCmd->command_number - m_iLastWeaponFireUsercmd > 5) ) |
|
return false; |
|
|
|
// If this entity hasn't been transmitted to us and acked, then don't bother lag compensating it. |
|
if ( pEntityTransmitBits && !pEntityTransmitBits->Get( pPlayer->entindex() ) ) |
|
return false; |
|
|
|
const Vector &vMyOrigin = GetAbsOrigin(); |
|
const Vector &vHisOrigin = pPlayer->GetAbsOrigin(); |
|
|
|
// get max distance player could have moved within max lag compensation time, |
|
// multiply by 1.5 to to avoid "dead zones" (sqrt(2) would be the exact value) |
|
float maxDistance = 1.5 * pPlayer->MaxSpeed() * sv_maxunlag.GetFloat(); |
|
|
|
// If the player is within this distance, lag compensate them in case they're running past us. |
|
if ( vHisOrigin.DistTo( vMyOrigin ) < maxDistance ) |
|
return true; |
|
|
|
// If their origin is not within a 45 degree cone in front of us, no need to lag compensate. |
|
Vector vForward; |
|
AngleVectors( pCmd->viewangles, &vForward ); |
|
|
|
Vector vDiff = vHisOrigin - vMyOrigin; |
|
VectorNormalize( vDiff ); |
|
|
|
float flCosAngle = 0.707107f; // 45 degree angle |
|
if ( vForward.Dot( vDiff ) < flCosAngle ) |
|
return false; |
|
|
|
return true; |
|
} |
|
|
|
Activity CHL2MP_Player::TranslateTeamActivity( Activity ActToTranslate ) |
|
{ |
|
if ( m_iModelType == TEAM_COMBINE ) |
|
return ActToTranslate; |
|
|
|
if ( ActToTranslate == ACT_RUN ) |
|
return ACT_RUN_AIM_AGITATED; |
|
|
|
if ( ActToTranslate == ACT_IDLE ) |
|
return ACT_IDLE_AIM_AGITATED; |
|
|
|
if ( ActToTranslate == ACT_WALK ) |
|
return ACT_WALK_AIM_AGITATED; |
|
|
|
return ActToTranslate; |
|
} |
|
|
|
extern ConVar hl2_normspeed; |
|
|
|
// Set the activity based on an event or current state |
|
void CHL2MP_Player::SetAnimation( PLAYER_ANIM playerAnim ) |
|
{ |
|
int animDesired; |
|
|
|
float speed; |
|
|
|
speed = GetAbsVelocity().Length2D(); |
|
|
|
|
|
// bool bRunning = true; |
|
|
|
//Revisit! |
|
/* if ( ( m_nButtons & ( IN_FORWARD | IN_BACK | IN_MOVELEFT | IN_MOVERIGHT ) ) ) |
|
{ |
|
if ( speed > 1.0f && speed < hl2_normspeed.GetFloat() - 20.0f ) |
|
{ |
|
bRunning = false; |
|
} |
|
}*/ |
|
|
|
if ( GetFlags() & ( FL_FROZEN | FL_ATCONTROLS ) ) |
|
{ |
|
speed = 0; |
|
playerAnim = PLAYER_IDLE; |
|
} |
|
|
|
Activity idealActivity = ACT_HL2MP_RUN; |
|
|
|
// This could stand to be redone. Why is playerAnim abstracted from activity? (sjb) |
|
if ( playerAnim == PLAYER_JUMP ) |
|
{ |
|
idealActivity = ACT_HL2MP_JUMP; |
|
} |
|
else if ( playerAnim == PLAYER_DIE ) |
|
{ |
|
if ( m_lifeState == LIFE_ALIVE ) |
|
{ |
|
return; |
|
} |
|
} |
|
else if ( playerAnim == PLAYER_ATTACK1 ) |
|
{ |
|
if ( GetActivity( ) == ACT_HOVER || |
|
GetActivity( ) == ACT_SWIM || |
|
GetActivity( ) == ACT_HOP || |
|
GetActivity( ) == ACT_LEAP || |
|
GetActivity( ) == ACT_DIESIMPLE ) |
|
{ |
|
idealActivity = GetActivity( ); |
|
} |
|
else |
|
{ |
|
idealActivity = ACT_HL2MP_GESTURE_RANGE_ATTACK; |
|
} |
|
} |
|
else if ( playerAnim == PLAYER_RELOAD ) |
|
{ |
|
idealActivity = ACT_HL2MP_GESTURE_RELOAD; |
|
} |
|
else if ( playerAnim == PLAYER_IDLE || playerAnim == PLAYER_WALK ) |
|
{ |
|
if ( !( GetFlags() & FL_ONGROUND ) && GetActivity( ) == ACT_HL2MP_JUMP ) // Still jumping |
|
{ |
|
idealActivity = GetActivity( ); |
|
} |
|
/* |
|
else if ( GetWaterLevel() > 1 ) |
|
{ |
|
if ( speed == 0 ) |
|
idealActivity = ACT_HOVER; |
|
else |
|
idealActivity = ACT_SWIM; |
|
} |
|
*/ |
|
else |
|
{ |
|
if ( GetFlags() & FL_DUCKING ) |
|
{ |
|
if ( speed > 0 ) |
|
{ |
|
idealActivity = ACT_HL2MP_WALK_CROUCH; |
|
} |
|
else |
|
{ |
|
idealActivity = ACT_HL2MP_IDLE_CROUCH; |
|
} |
|
} |
|
else |
|
{ |
|
if ( speed > 0 ) |
|
{ |
|
/* |
|
if ( bRunning == false ) |
|
{ |
|
idealActivity = ACT_WALK; |
|
} |
|
else |
|
*/ |
|
{ |
|
idealActivity = ACT_HL2MP_RUN; |
|
} |
|
} |
|
else |
|
{ |
|
idealActivity = ACT_HL2MP_IDLE; |
|
} |
|
} |
|
} |
|
|
|
idealActivity = TranslateTeamActivity( idealActivity ); |
|
} |
|
|
|
if ( idealActivity == ACT_HL2MP_GESTURE_RANGE_ATTACK ) |
|
{ |
|
RestartGesture( Weapon_TranslateActivity( idealActivity ) ); |
|
|
|
// FIXME: this seems a bit wacked |
|
Weapon_SetActivity( Weapon_TranslateActivity( ACT_RANGE_ATTACK1 ), 0 ); |
|
|
|
return; |
|
} |
|
else if ( idealActivity == ACT_HL2MP_GESTURE_RELOAD ) |
|
{ |
|
RestartGesture( Weapon_TranslateActivity( idealActivity ) ); |
|
return; |
|
} |
|
else |
|
{ |
|
SetActivity( idealActivity ); |
|
|
|
animDesired = SelectWeightedSequence( Weapon_TranslateActivity ( idealActivity ) ); |
|
|
|
if (animDesired == -1) |
|
{ |
|
animDesired = SelectWeightedSequence( idealActivity ); |
|
|
|
if ( animDesired == -1 ) |
|
{ |
|
animDesired = 0; |
|
} |
|
} |
|
|
|
// Already using the desired animation? |
|
if ( GetSequence() == animDesired ) |
|
return; |
|
|
|
m_flPlaybackRate = 1.0; |
|
ResetSequence( animDesired ); |
|
SetCycle( 0 ); |
|
return; |
|
} |
|
|
|
// Already using the desired animation? |
|
if ( GetSequence() == animDesired ) |
|
return; |
|
|
|
//Msg( "Set animation to %d\n", animDesired ); |
|
// Reset to first frame of desired animation |
|
ResetSequence( animDesired ); |
|
SetCycle( 0 ); |
|
} |
|
|
|
|
|
extern int gEvilImpulse101; |
|
//----------------------------------------------------------------------------- |
|
// Purpose: Player reacts to bumping a weapon. |
|
// Input : pWeapon - the weapon that the player bumped into. |
|
// Output : Returns true if player picked up the weapon |
|
//----------------------------------------------------------------------------- |
|
bool CHL2MP_Player::BumpWeapon( CBaseCombatWeapon *pWeapon ) |
|
{ |
|
CBaseCombatCharacter *pOwner = pWeapon->GetOwner(); |
|
|
|
// Can I have this weapon type? |
|
if ( !IsAllowedToPickupWeapons() ) |
|
return false; |
|
|
|
if ( pOwner || !Weapon_CanUse( pWeapon ) || !g_pGameRules->CanHavePlayerItem( this, pWeapon ) ) |
|
{ |
|
if ( gEvilImpulse101 ) |
|
{ |
|
UTIL_Remove( pWeapon ); |
|
} |
|
return false; |
|
} |
|
|
|
// Don't let the player fetch weapons through walls (use MASK_SOLID so that you can't pickup through windows) |
|
if( !pWeapon->FVisible( this, MASK_SOLID ) && !(GetFlags() & FL_NOTARGET) ) |
|
{ |
|
return false; |
|
} |
|
|
|
bool bOwnsWeaponAlready = !!Weapon_OwnsThisType( pWeapon->GetClassname(), pWeapon->GetSubType()); |
|
|
|
if ( bOwnsWeaponAlready == true ) |
|
{ |
|
//If we have room for the ammo, then "take" the weapon too. |
|
if ( Weapon_EquipAmmoOnly( pWeapon ) ) |
|
{ |
|
pWeapon->CheckRespawn(); |
|
|
|
UTIL_Remove( pWeapon ); |
|
return true; |
|
} |
|
else |
|
{ |
|
return false; |
|
} |
|
} |
|
|
|
pWeapon->CheckRespawn(); |
|
Weapon_Equip( pWeapon ); |
|
|
|
return true; |
|
} |
|
|
|
void CHL2MP_Player::ChangeTeam( int iTeam ) |
|
{ |
|
/* if ( GetNextTeamChangeTime() >= gpGlobals->curtime ) |
|
{ |
|
char szReturnString[128]; |
|
Q_snprintf( szReturnString, sizeof( szReturnString ), "Please wait %d more seconds before trying to switch teams again.\n", (int)(GetNextTeamChangeTime() - gpGlobals->curtime) ); |
|
|
|
ClientPrint( this, HUD_PRINTTALK, szReturnString ); |
|
return; |
|
}*/ |
|
|
|
bool bKill = false; |
|
|
|
if ( HL2MPRules()->IsTeamplay() != true && iTeam != TEAM_SPECTATOR ) |
|
{ |
|
//don't let them try to join combine or rebels during deathmatch. |
|
iTeam = TEAM_UNASSIGNED; |
|
} |
|
|
|
if ( HL2MPRules()->IsTeamplay() == true ) |
|
{ |
|
if ( iTeam != GetTeamNumber() && GetTeamNumber() != TEAM_UNASSIGNED ) |
|
{ |
|
bKill = true; |
|
} |
|
} |
|
|
|
BaseClass::ChangeTeam( iTeam ); |
|
|
|
m_flNextTeamChangeTime = gpGlobals->curtime + TEAM_CHANGE_INTERVAL; |
|
|
|
if ( HL2MPRules()->IsTeamplay() == true ) |
|
{ |
|
SetPlayerTeamModel(); |
|
} |
|
else |
|
{ |
|
SetPlayerModel(); |
|
} |
|
|
|
if ( iTeam == TEAM_SPECTATOR ) |
|
{ |
|
RemoveAllItems( true ); |
|
|
|
State_Transition( STATE_OBSERVER_MODE ); |
|
} |
|
|
|
if ( bKill == true ) |
|
{ |
|
CommitSuicide(); |
|
} |
|
} |
|
|
|
bool CHL2MP_Player::HandleCommand_JoinTeam( int team ) |
|
{ |
|
if ( !GetGlobalTeam( team ) || team == 0 ) |
|
{ |
|
Warning( "HandleCommand_JoinTeam( %d ) - invalid team index.\n", team ); |
|
return false; |
|
} |
|
|
|
if ( team == TEAM_SPECTATOR ) |
|
{ |
|
// Prevent this is the cvar is set |
|
if ( !mp_allowspectators.GetInt() ) |
|
{ |
|
ClientPrint( this, HUD_PRINTCENTER, "#Cannot_Be_Spectator" ); |
|
return false; |
|
} |
|
|
|
if ( GetTeamNumber() != TEAM_UNASSIGNED && !IsDead() ) |
|
{ |
|
m_fNextSuicideTime = gpGlobals->curtime; // allow the suicide to work |
|
|
|
CommitSuicide(); |
|
|
|
// add 1 to frags to balance out the 1 subtracted for killing yourself |
|
IncrementFragCount( 1 ); |
|
} |
|
|
|
ChangeTeam( TEAM_SPECTATOR ); |
|
|
|
return true; |
|
} |
|
else |
|
{ |
|
StopObserverMode(); |
|
State_Transition(STATE_ACTIVE); |
|
} |
|
|
|
// Switch their actual team... |
|
ChangeTeam( team ); |
|
|
|
return true; |
|
} |
|
|
|
bool CHL2MP_Player::ClientCommand( const CCommand &args ) |
|
{ |
|
if ( FStrEq( args[0], "spectate" ) ) |
|
{ |
|
if ( ShouldRunRateLimitedCommand( args ) ) |
|
{ |
|
// instantly join spectators |
|
HandleCommand_JoinTeam( TEAM_SPECTATOR ); |
|
} |
|
return true; |
|
} |
|
else if ( FStrEq( args[0], "jointeam" ) ) |
|
{ |
|
if ( args.ArgC() < 2 ) |
|
{ |
|
Warning( "Player sent bad jointeam syntax\n" ); |
|
} |
|
|
|
if ( ShouldRunRateLimitedCommand( args ) ) |
|
{ |
|
int iTeam = atoi( args[1] ); |
|
HandleCommand_JoinTeam( iTeam ); |
|
} |
|
return true; |
|
} |
|
else if ( FStrEq( args[0], "joingame" ) ) |
|
{ |
|
return true; |
|
} |
|
|
|
return BaseClass::ClientCommand( args ); |
|
} |
|
|
|
void CHL2MP_Player::CheatImpulseCommands( int iImpulse ) |
|
{ |
|
switch ( iImpulse ) |
|
{ |
|
case 101: |
|
{ |
|
if( sv_cheats->GetBool() ) |
|
{ |
|
GiveAllItems(); |
|
} |
|
} |
|
break; |
|
|
|
default: |
|
BaseClass::CheatImpulseCommands( iImpulse ); |
|
} |
|
} |
|
|
|
bool CHL2MP_Player::ShouldRunRateLimitedCommand( const CCommand &args ) |
|
{ |
|
int i = m_RateLimitLastCommandTimes.Find( args[0] ); |
|
if ( i == m_RateLimitLastCommandTimes.InvalidIndex() ) |
|
{ |
|
m_RateLimitLastCommandTimes.Insert( args[0], gpGlobals->curtime ); |
|
return true; |
|
} |
|
else if ( (gpGlobals->curtime - m_RateLimitLastCommandTimes[i]) < HL2MP_COMMAND_MAX_RATE ) |
|
{ |
|
// Too fast. |
|
return false; |
|
} |
|
else |
|
{ |
|
m_RateLimitLastCommandTimes[i] = gpGlobals->curtime; |
|
return true; |
|
} |
|
} |
|
|
|
void CHL2MP_Player::CreateViewModel( int index /*=0*/ ) |
|
{ |
|
Assert( index >= 0 && index < MAX_VIEWMODELS ); |
|
|
|
if ( GetViewModel( index ) ) |
|
return; |
|
|
|
CPredictedViewModel *vm = ( CPredictedViewModel * )CreateEntityByName( "predicted_viewmodel" ); |
|
if ( vm ) |
|
{ |
|
vm->SetAbsOrigin( GetAbsOrigin() ); |
|
vm->SetOwner( this ); |
|
vm->SetIndex( index ); |
|
DispatchSpawn( vm ); |
|
vm->FollowEntity( this, false ); |
|
m_hViewModel.Set( index, vm ); |
|
} |
|
} |
|
|
|
bool CHL2MP_Player::BecomeRagdollOnClient( const Vector &force ) |
|
{ |
|
return true; |
|
} |
|
|
|
// -------------------------------------------------------------------------------- // |
|
// Ragdoll entities. |
|
// -------------------------------------------------------------------------------- // |
|
|
|
class CHL2MPRagdoll : public CBaseAnimatingOverlay |
|
{ |
|
public: |
|
DECLARE_CLASS( CHL2MPRagdoll, CBaseAnimatingOverlay ); |
|
DECLARE_SERVERCLASS(); |
|
|
|
// Transmit ragdolls to everyone. |
|
virtual int UpdateTransmitState() |
|
{ |
|
return SetTransmitState( FL_EDICT_ALWAYS ); |
|
} |
|
|
|
public: |
|
// In case the client has the player entity, we transmit the player index. |
|
// In case the client doesn't have it, we transmit the player's model index, origin, and angles |
|
// so they can create a ragdoll in the right place. |
|
CNetworkHandle( CBaseEntity, m_hPlayer ); // networked entity handle |
|
CNetworkVector( m_vecRagdollVelocity ); |
|
CNetworkVector( m_vecRagdollOrigin ); |
|
}; |
|
|
|
LINK_ENTITY_TO_CLASS( hl2mp_ragdoll, CHL2MPRagdoll ); |
|
|
|
IMPLEMENT_SERVERCLASS_ST_NOBASE( CHL2MPRagdoll, DT_HL2MPRagdoll ) |
|
SendPropVector( SENDINFO(m_vecRagdollOrigin), -1, SPROP_COORD ), |
|
SendPropEHandle( SENDINFO( m_hPlayer ) ), |
|
SendPropModelIndex( SENDINFO( m_nModelIndex ) ), |
|
SendPropInt ( SENDINFO(m_nForceBone), 8, 0 ), |
|
SendPropVector ( SENDINFO(m_vecForce), -1, SPROP_NOSCALE ), |
|
SendPropVector( SENDINFO( m_vecRagdollVelocity ) ) |
|
END_SEND_TABLE() |
|
|
|
|
|
void CHL2MP_Player::CreateRagdollEntity( void ) |
|
{ |
|
if ( m_hRagdoll ) |
|
{ |
|
UTIL_RemoveImmediate( m_hRagdoll ); |
|
m_hRagdoll = NULL; |
|
} |
|
|
|
// If we already have a ragdoll, don't make another one. |
|
CHL2MPRagdoll *pRagdoll = dynamic_cast< CHL2MPRagdoll* >( m_hRagdoll.Get() ); |
|
|
|
if ( !pRagdoll ) |
|
{ |
|
// create a new one |
|
pRagdoll = dynamic_cast< CHL2MPRagdoll* >( CreateEntityByName( "hl2mp_ragdoll" ) ); |
|
} |
|
|
|
if ( pRagdoll ) |
|
{ |
|
pRagdoll->m_hPlayer = this; |
|
pRagdoll->m_vecRagdollOrigin = GetAbsOrigin(); |
|
pRagdoll->m_vecRagdollVelocity = GetAbsVelocity(); |
|
pRagdoll->m_nModelIndex = m_nModelIndex; |
|
pRagdoll->m_nForceBone = m_nForceBone; |
|
pRagdoll->m_vecForce = m_vecTotalBulletForce; |
|
pRagdoll->SetAbsOrigin( GetAbsOrigin() ); |
|
} |
|
|
|
// ragdolls will be removed on round restart automatically |
|
m_hRagdoll = pRagdoll; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
//----------------------------------------------------------------------------- |
|
int CHL2MP_Player::FlashlightIsOn( void ) |
|
{ |
|
return IsEffectActive( EF_DIMLIGHT ); |
|
} |
|
|
|
extern ConVar flashlight; |
|
|
|
//----------------------------------------------------------------------------- |
|
//----------------------------------------------------------------------------- |
|
void CHL2MP_Player::FlashlightTurnOn( void ) |
|
{ |
|
if( flashlight.GetInt() > 0 && IsAlive() ) |
|
{ |
|
AddEffects( EF_DIMLIGHT ); |
|
EmitSound( "HL2Player.FlashlightOn" ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
//----------------------------------------------------------------------------- |
|
void CHL2MP_Player::FlashlightTurnOff( void ) |
|
{ |
|
RemoveEffects( EF_DIMLIGHT ); |
|
|
|
if( IsAlive() ) |
|
{ |
|
EmitSound( "HL2Player.FlashlightOff" ); |
|
} |
|
} |
|
|
|
void CHL2MP_Player::Weapon_Drop( CBaseCombatWeapon *pWeapon, const Vector *pvecTarget, const Vector *pVelocity ) |
|
{ |
|
//Drop a grenade if it's primed. |
|
if ( GetActiveWeapon() ) |
|
{ |
|
CBaseCombatWeapon *pGrenade = Weapon_OwnsThisType("weapon_frag"); |
|
|
|
if ( GetActiveWeapon() == pGrenade ) |
|
{ |
|
if ( ( m_nButtons & IN_ATTACK ) || (m_nButtons & IN_ATTACK2) ) |
|
{ |
|
DropPrimedFragGrenade( this, pGrenade ); |
|
return; |
|
} |
|
} |
|
} |
|
|
|
BaseClass::Weapon_Drop( pWeapon, pvecTarget, pVelocity ); |
|
} |
|
|
|
|
|
void CHL2MP_Player::DetonateTripmines( void ) |
|
{ |
|
CBaseEntity *pEntity = NULL; |
|
|
|
while ((pEntity = gEntList.FindEntityByClassname( pEntity, "npc_satchel" )) != NULL) |
|
{ |
|
CSatchelCharge *pSatchel = dynamic_cast<CSatchelCharge *>(pEntity); |
|
if (pSatchel->m_bIsLive && pSatchel->GetThrower() == this ) |
|
{ |
|
g_EventQueue.AddEvent( pSatchel, "Explode", 0.20, this, this ); |
|
} |
|
} |
|
|
|
// Play sound for pressing the detonator |
|
EmitSound( "Weapon_SLAM.SatchelDetonate" ); |
|
} |
|
|
|
void CHL2MP_Player::Event_Killed( const CTakeDamageInfo &info ) |
|
{ |
|
//update damage info with our accumulated physics force |
|
CTakeDamageInfo subinfo = info; |
|
subinfo.SetDamageForce( m_vecTotalBulletForce ); |
|
|
|
SetNumAnimOverlays( 0 ); |
|
|
|
// Note: since we're dead, it won't draw us on the client, but we don't set EF_NODRAW |
|
// because we still want to transmit to the clients in our PVS. |
|
CreateRagdollEntity(); |
|
|
|
DetonateTripmines(); |
|
|
|
BaseClass::Event_Killed( subinfo ); |
|
|
|
if ( info.GetDamageType() & DMG_DISSOLVE ) |
|
{ |
|
if ( m_hRagdoll ) |
|
{ |
|
m_hRagdoll->GetBaseAnimating()->Dissolve( NULL, gpGlobals->curtime, false, ENTITY_DISSOLVE_NORMAL ); |
|
} |
|
} |
|
|
|
CBaseEntity *pAttacker = info.GetAttacker(); |
|
|
|
if ( pAttacker ) |
|
{ |
|
int iScoreToAdd = 1; |
|
|
|
if ( pAttacker == this ) |
|
{ |
|
iScoreToAdd = -1; |
|
} |
|
|
|
GetGlobalTeam( pAttacker->GetTeamNumber() )->AddScore( iScoreToAdd ); |
|
} |
|
|
|
FlashlightTurnOff(); |
|
|
|
m_lifeState = LIFE_DEAD; |
|
|
|
RemoveEffects( EF_NODRAW ); // still draw player body |
|
StopZooming(); |
|
} |
|
|
|
int CHL2MP_Player::OnTakeDamage( const CTakeDamageInfo &inputInfo ) |
|
{ |
|
//return here if the player is in the respawn grace period vs. slams. |
|
if ( gpGlobals->curtime < m_flSlamProtectTime && (inputInfo.GetDamageType() == DMG_BLAST ) ) |
|
return 0; |
|
|
|
m_vecTotalBulletForce += inputInfo.GetDamageForce(); |
|
|
|
gamestats->Event_PlayerDamage( this, inputInfo ); |
|
|
|
return BaseClass::OnTakeDamage( inputInfo ); |
|
} |
|
|
|
void CHL2MP_Player::DeathSound( const CTakeDamageInfo &info ) |
|
{ |
|
if ( m_hRagdoll && m_hRagdoll->GetBaseAnimating()->IsDissolving() ) |
|
return; |
|
|
|
char szStepSound[128]; |
|
|
|
Q_snprintf( szStepSound, sizeof( szStepSound ), "%s.Die", GetPlayerModelSoundPrefix() ); |
|
|
|
const char *pModelName = STRING( GetModelName() ); |
|
|
|
CSoundParameters params; |
|
if ( GetParametersForSound( szStepSound, params, pModelName ) == false ) |
|
return; |
|
|
|
Vector vecOrigin = GetAbsOrigin(); |
|
|
|
CRecipientFilter filter; |
|
filter.AddRecipientsByPAS( vecOrigin ); |
|
|
|
EmitSound_t ep; |
|
ep.m_nChannel = params.channel; |
|
ep.m_pSoundName = params.soundname; |
|
ep.m_flVolume = params.volume; |
|
ep.m_SoundLevel = params.soundlevel; |
|
ep.m_nFlags = 0; |
|
ep.m_nPitch = params.pitch; |
|
ep.m_pOrigin = &vecOrigin; |
|
|
|
EmitSound( filter, entindex(), ep ); |
|
} |
|
|
|
CBaseEntity* CHL2MP_Player::EntSelectSpawnPoint( void ) |
|
{ |
|
CBaseEntity *pSpot = NULL; |
|
CBaseEntity *pLastSpawnPoint = g_pLastSpawn; |
|
edict_t *player = edict(); |
|
const char *pSpawnpointName = "info_player_deathmatch"; |
|
|
|
if ( HL2MPRules()->IsTeamplay() == true ) |
|
{ |
|
if ( GetTeamNumber() == TEAM_COMBINE ) |
|
{ |
|
pSpawnpointName = "info_player_combine"; |
|
pLastSpawnPoint = g_pLastCombineSpawn; |
|
} |
|
else if ( GetTeamNumber() == TEAM_REBELS ) |
|
{ |
|
pSpawnpointName = "info_player_rebel"; |
|
pLastSpawnPoint = g_pLastRebelSpawn; |
|
} |
|
|
|
if ( gEntList.FindEntityByClassname( NULL, pSpawnpointName ) == NULL ) |
|
{ |
|
pSpawnpointName = "info_player_deathmatch"; |
|
pLastSpawnPoint = g_pLastSpawn; |
|
} |
|
} |
|
|
|
pSpot = pLastSpawnPoint; |
|
// Randomize the start spot |
|
for ( int i = random->RandomInt(1,5); i > 0; i-- ) |
|
pSpot = gEntList.FindEntityByClassname( pSpot, pSpawnpointName ); |
|
if ( !pSpot ) // skip over the null point |
|
pSpot = gEntList.FindEntityByClassname( pSpot, pSpawnpointName ); |
|
|
|
CBaseEntity *pFirstSpot = pSpot; |
|
|
|
do |
|
{ |
|
if ( pSpot ) |
|
{ |
|
// check if pSpot is valid |
|
if ( g_pGameRules->IsSpawnPointValid( pSpot, this ) ) |
|
{ |
|
if ( pSpot->GetLocalOrigin() == vec3_origin ) |
|
{ |
|
pSpot = gEntList.FindEntityByClassname( pSpot, pSpawnpointName ); |
|
continue; |
|
} |
|
|
|
// if so, go to pSpot |
|
goto ReturnSpot; |
|
} |
|
} |
|
// increment pSpot |
|
pSpot = gEntList.FindEntityByClassname( pSpot, pSpawnpointName ); |
|
} while ( pSpot != pFirstSpot ); // loop if we're not back to the start |
|
|
|
// we haven't found a place to spawn yet, so kill any guy at the first spawn point and spawn there |
|
if ( pSpot ) |
|
{ |
|
CBaseEntity *ent = NULL; |
|
for ( CEntitySphereQuery sphere( pSpot->GetAbsOrigin(), 128 ); (ent = sphere.GetCurrentEntity()) != NULL; sphere.NextEntity() ) |
|
{ |
|
// if ent is a client, kill em (unless they are ourselves) |
|
if ( ent->IsPlayer() && !(ent->edict() == player) ) |
|
ent->TakeDamage( CTakeDamageInfo( GetContainingEntity(INDEXENT(0)), GetContainingEntity(INDEXENT(0)), 300, DMG_GENERIC ) ); |
|
} |
|
goto ReturnSpot; |
|
} |
|
|
|
if ( !pSpot ) |
|
{ |
|
pSpot = gEntList.FindEntityByClassname( pSpot, "info_player_start" ); |
|
|
|
if ( pSpot ) |
|
goto ReturnSpot; |
|
} |
|
|
|
ReturnSpot: |
|
|
|
if ( HL2MPRules()->IsTeamplay() == true ) |
|
{ |
|
if ( GetTeamNumber() == TEAM_COMBINE ) |
|
{ |
|
g_pLastCombineSpawn = pSpot; |
|
} |
|
else if ( GetTeamNumber() == TEAM_REBELS ) |
|
{ |
|
g_pLastRebelSpawn = pSpot; |
|
} |
|
} |
|
|
|
g_pLastSpawn = pSpot; |
|
|
|
m_flSlamProtectTime = gpGlobals->curtime + 0.5; |
|
|
|
return pSpot; |
|
} |
|
|
|
|
|
CON_COMMAND( timeleft, "prints the time remaining in the match" ) |
|
{ |
|
CHL2MP_Player *pPlayer = ToHL2MPPlayer( UTIL_GetCommandClient() ); |
|
|
|
int iTimeRemaining = (int)HL2MPRules()->GetMapRemainingTime(); |
|
|
|
if ( iTimeRemaining == 0 ) |
|
{ |
|
if ( pPlayer ) |
|
{ |
|
ClientPrint( pPlayer, HUD_PRINTTALK, "This game has no timelimit." ); |
|
} |
|
else |
|
{ |
|
Msg( "* No Time Limit *\n" ); |
|
} |
|
} |
|
else |
|
{ |
|
int iMinutes, iSeconds; |
|
iMinutes = iTimeRemaining / 60; |
|
iSeconds = iTimeRemaining % 60; |
|
|
|
char minutes[8]; |
|
char seconds[8]; |
|
|
|
Q_snprintf( minutes, sizeof(minutes), "%d", iMinutes ); |
|
Q_snprintf( seconds, sizeof(seconds), "%2.2d", iSeconds ); |
|
|
|
if ( pPlayer ) |
|
{ |
|
ClientPrint( pPlayer, HUD_PRINTTALK, "Time left in map: %s1:%s2", minutes, seconds ); |
|
} |
|
else |
|
{ |
|
Msg( "Time Remaining: %s:%s\n", minutes, seconds ); |
|
} |
|
} |
|
} |
|
|
|
|
|
void CHL2MP_Player::Reset() |
|
{ |
|
ResetDeathCount(); |
|
ResetFragCount(); |
|
} |
|
|
|
bool CHL2MP_Player::IsReady() |
|
{ |
|
return m_bReady; |
|
} |
|
|
|
void CHL2MP_Player::SetReady( bool bReady ) |
|
{ |
|
m_bReady = bReady; |
|
} |
|
|
|
void CHL2MP_Player::CheckChatText( char *p, int bufsize ) |
|
{ |
|
//Look for escape sequences and replace |
|
|
|
char *buf = new char[bufsize]; |
|
int pos = 0; |
|
|
|
// Parse say text for escape sequences |
|
for ( char *pSrc = p; pSrc != NULL && *pSrc != 0 && pos < bufsize-1; pSrc++ ) |
|
{ |
|
// copy each char across |
|
buf[pos] = *pSrc; |
|
pos++; |
|
} |
|
|
|
buf[pos] = '\0'; |
|
|
|
// copy buf back into p |
|
Q_strncpy( p, buf, bufsize ); |
|
|
|
delete[] buf; |
|
|
|
const char *pReadyCheck = p; |
|
|
|
HL2MPRules()->CheckChatForReadySignal( this, pReadyCheck ); |
|
} |
|
|
|
void CHL2MP_Player::State_Transition( HL2MPPlayerState newState ) |
|
{ |
|
State_Leave(); |
|
State_Enter( newState ); |
|
} |
|
|
|
|
|
void CHL2MP_Player::State_Enter( HL2MPPlayerState newState ) |
|
{ |
|
m_iPlayerState = newState; |
|
m_pCurStateInfo = State_LookupInfo( newState ); |
|
|
|
// Initialize the new state. |
|
if ( m_pCurStateInfo && m_pCurStateInfo->pfnEnterState ) |
|
(this->*m_pCurStateInfo->pfnEnterState)(); |
|
} |
|
|
|
|
|
void CHL2MP_Player::State_Leave() |
|
{ |
|
if ( m_pCurStateInfo && m_pCurStateInfo->pfnLeaveState ) |
|
{ |
|
(this->*m_pCurStateInfo->pfnLeaveState)(); |
|
} |
|
} |
|
|
|
|
|
void CHL2MP_Player::State_PreThink() |
|
{ |
|
if ( m_pCurStateInfo && m_pCurStateInfo->pfnPreThink ) |
|
{ |
|
(this->*m_pCurStateInfo->pfnPreThink)(); |
|
} |
|
} |
|
|
|
|
|
CHL2MPPlayerStateInfo *CHL2MP_Player::State_LookupInfo( HL2MPPlayerState state ) |
|
{ |
|
// This table MUST match the |
|
static CHL2MPPlayerStateInfo playerStateInfos[] = |
|
{ |
|
{ STATE_ACTIVE, "STATE_ACTIVE", &CHL2MP_Player::State_Enter_ACTIVE, NULL, &CHL2MP_Player::State_PreThink_ACTIVE }, |
|
{ STATE_OBSERVER_MODE, "STATE_OBSERVER_MODE", &CHL2MP_Player::State_Enter_OBSERVER_MODE, NULL, &CHL2MP_Player::State_PreThink_OBSERVER_MODE } |
|
}; |
|
|
|
for ( int i=0; i < ARRAYSIZE( playerStateInfos ); i++ ) |
|
{ |
|
if ( playerStateInfos[i].m_iPlayerState == state ) |
|
return &playerStateInfos[i]; |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
bool CHL2MP_Player::StartObserverMode(int mode) |
|
{ |
|
//we only want to go into observer mode if the player asked to, not on a death timeout |
|
if ( m_bEnterObserver == true ) |
|
{ |
|
VPhysicsDestroyObject(); |
|
return BaseClass::StartObserverMode( mode ); |
|
} |
|
return false; |
|
} |
|
|
|
void CHL2MP_Player::StopObserverMode() |
|
{ |
|
m_bEnterObserver = false; |
|
BaseClass::StopObserverMode(); |
|
} |
|
|
|
void CHL2MP_Player::State_Enter_OBSERVER_MODE() |
|
{ |
|
int observerMode = m_iObserverLastMode; |
|
if ( IsNetClient() ) |
|
{ |
|
const char *pIdealMode = engine->GetClientConVarValue( engine->IndexOfEdict( edict() ), "cl_spec_mode" ); |
|
if ( pIdealMode ) |
|
{ |
|
observerMode = atoi( pIdealMode ); |
|
if ( observerMode <= OBS_MODE_FIXED || observerMode > OBS_MODE_ROAMING ) |
|
{ |
|
observerMode = m_iObserverLastMode; |
|
} |
|
} |
|
} |
|
m_bEnterObserver = true; |
|
StartObserverMode( observerMode ); |
|
} |
|
|
|
void CHL2MP_Player::State_PreThink_OBSERVER_MODE() |
|
{ |
|
// Make sure nobody has changed any of our state. |
|
// Assert( GetMoveType() == MOVETYPE_FLY ); |
|
Assert( m_takedamage == DAMAGE_NO ); |
|
Assert( IsSolidFlagSet( FSOLID_NOT_SOLID ) ); |
|
// Assert( IsEffectActive( EF_NODRAW ) ); |
|
|
|
// Must be dead. |
|
Assert( m_lifeState == LIFE_DEAD ); |
|
Assert( pl.deadflag ); |
|
} |
|
|
|
|
|
void CHL2MP_Player::State_Enter_ACTIVE() |
|
{ |
|
SetMoveType( MOVETYPE_WALK ); |
|
|
|
// md 8/15/07 - They'll get set back to solid when they actually respawn. If we set them solid now and mp_forcerespawn |
|
// is false, then they'll be spectating but blocking live players from moving. |
|
// RemoveSolidFlags( FSOLID_NOT_SOLID ); |
|
|
|
m_Local.m_iHideHUD = 0; |
|
} |
|
|
|
|
|
void CHL2MP_Player::State_PreThink_ACTIVE() |
|
{ |
|
//we don't really need to do anything here. |
|
//This state_prethink structure came over from CS:S and was doing an assert check that fails the way hl2dm handles death |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool CHL2MP_Player::CanHearAndReadChatFrom( CBasePlayer *pPlayer ) |
|
{ |
|
// can always hear the console unless we're ignoring all chat |
|
if ( !pPlayer ) |
|
return false; |
|
|
|
return true; |
|
}
|
|
|