//========= 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( 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 *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(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; }