//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ // // Purpose: Functions dealing with the player. // //============================================================================= #include "cbase.h" #include "const.h" #include "baseplayer_shared.h" #include "trains.h" #include "soundent.h" #include "gib.h" #include "shake.h" #include "decals.h" #include "gamerules.h" #include "game.h" #include "entityapi.h" #include "entitylist.h" #include "eventqueue.h" #include "worldsize.h" #include "isaverestore.h" #include "globalstate.h" #include "basecombatweapon.h" #include "ai_basenpc.h" #include "ai_network.h" #include "ai_node.h" #include "ai_networkmanager.h" #include "ammodef.h" #include "mathlib.h" #include "ndebugoverlay.h" #include "baseviewmodel.h" #include "in_buttons.h" #include "client.h" #include "team.h" #include "particle_smokegrenade.h" #include "IEffects.h" #include "vstdlib/random.h" #include "engine/IEngineSound.h" #include "movehelper_server.h" #include "igamemovement.h" #include "saverestoretypes.h" #include "iservervehicle.h" #include "movevars_shared.h" #include "vcollide_parse.h" #include "player_command.h" #include "vehicle_base.h" #include "AI_Criteria.h" #include "globals.h" #include "usermessages.h" #include "gamevars_shared.h" #include "world.h" #include "physobj.h" #include "KeyValues.h" #include "coordsize.h" #include "vphysics/player_controller.h" #include "saverestore_utlvector.h" #include "hltvdirector.h" #include "nav_mesh.h" #include "env_zoom.h" #ifdef HL2_DLL #include "combine_mine.h" #include "weapon_physcannon.h" #endif // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" static ConVar old_armor( "player_old_armor", "0" ); static ConVar physicsshadowupdate_render( "physicsshadowupdate_render", "0" ); // This is declared in the engine, too ConVar sv_noclipduringpause( "sv_noclipduringpause", "0", FCVAR_REPLICATED | FCVAR_CHEAT, "If cheats are enabled, then you can noclip with the game paused (for doing screenshots, etc.)." ); extern ConVar sv_maxunlag; extern ConVar sv_turbophysics; // TIME BASED DAMAGE AMOUNT // tweak these values based on gameplay feedback: #define PARALYZE_DURATION 2 // number of 2 second intervals to take damage #define PARALYZE_DAMAGE 1.0 // damage to take each 2 second interval #define NERVEGAS_DURATION 2 #define NERVEGAS_DAMAGE 5.0 #define POISON_DURATION 5 #define POISON_DAMAGE 2.0 #define RADIATION_DURATION 2 #define RADIATION_DAMAGE 1.0 #define ACID_DURATION 2 #define ACID_DAMAGE 5.0 #define SLOWBURN_DURATION 2 #define SLOWBURN_DAMAGE 1.0 #define SLOWFREEZE_DURATION 2 #define SLOWFREEZE_DAMAGE 1.0 //---------------------------------------------------- // Player Physics Shadow //---------------------------------------------------- #define VPHYS_MAX_DISTANCE 2.0 #define VPHYS_MAX_VEL 10 #define VPHYS_MAX_DISTSQR (VPHYS_MAX_DISTANCE*VPHYS_MAX_DISTANCE) #define VPHYS_MAX_VELSQR (VPHYS_MAX_VEL*VPHYS_MAX_VEL) extern bool g_fDrawLines; int gEvilImpulse101; bool gInitHUD = true; extern void respawn(CBaseEntity *pEdict, bool fCopyCorpse); int MapTextureTypeStepType(char chTextureType); extern void SpawnBlood(Vector vecSpot, const Vector &vecDir, int bloodColor, float flDamage); extern void AddMultiDamage( const CTakeDamageInfo &info, CBaseEntity *pEntity ); #define CMD_MOSTRECENT 0 //#define FLASH_DRAIN_TIME 1.2 //100 units/3 minutes //#define FLASH_CHARGE_TIME 0.2 // 100 units/20 seconds (seconds per unit) //#define PLAYER_MAX_SAFE_FALL_DIST 20// falling any farther than this many feet will inflict damage //#define PLAYER_FATAL_FALL_DIST 60// 100% damage inflicted if player falls this many feet //#define DAMAGE_PER_UNIT_FALLEN (float)( 100 ) / ( ( PLAYER_FATAL_FALL_DIST - PLAYER_MAX_SAFE_FALL_DIST ) * 12 ) //#define MAX_SAFE_FALL_UNITS ( PLAYER_MAX_SAFE_FALL_DIST * 12 ) // player damage adjusters ConVar sk_player_head( "sk_player_head","2" ); ConVar sk_player_chest( "sk_player_chest","1" ); ConVar sk_player_stomach( "sk_player_stomach","1" ); ConVar sk_player_arm( "sk_player_arm","1" ); ConVar sk_player_leg( "sk_player_leg","1" ); // pl BEGIN_SIMPLE_DATADESC( CPlayerState ) // DEFINE_FIELD( netname, FIELD_STRING ), // Don't stomp player name with what's in save/restore DEFINE_FIELD( v_angle, FIELD_VECTOR ), DEFINE_FIELD( deadflag, FIELD_BOOLEAN ), // this is always set to true on restore, don't bother saving it. // DEFINE_FIELD( fixangle, FIELD_INTEGER ), // DEFINE_FIELD( anglechange, FIELD_FLOAT ), // DEFINE_FIELD( hltv, FIELD_BOOLEAN ), // DEFINE_FIELD( frags, FIELD_INTEGER ), // DEFINE_FIELD( deaths, FIELD_INTEGER ), END_DATADESC() // Global Savedata for player BEGIN_DATADESC( CBasePlayer ) DEFINE_EMBEDDED( m_Local ), DEFINE_UTLVECTOR( m_hTriggerSoundscapeList, FIELD_EHANDLE ), DEFINE_EMBEDDED( pl ), DEFINE_FIELD( m_StuckLast, FIELD_INTEGER ), DEFINE_FIELD( m_nButtons, FIELD_INTEGER ), DEFINE_FIELD( m_afButtonLast, FIELD_INTEGER ), DEFINE_FIELD( m_afButtonPressed, FIELD_INTEGER ), DEFINE_FIELD( m_afButtonReleased, FIELD_INTEGER ), //DEFINE_FIELD( m_fOnTarget, FIELD_BOOLEAN ), // Don't need to restore DEFINE_FIELD( m_iObserverMode, FIELD_INTEGER ), DEFINE_FIELD( m_iObserverLastMode, FIELD_INTEGER ), DEFINE_FIELD( m_hObserverTarget, FIELD_EHANDLE ), DEFINE_FIELD( m_bForcedObserverMode, FIELD_BOOLEAN ), // DEFINE_FIELD( m_bIsRecording, FIELD_BOOLEAN ), // DEFINE_FIELD( m_hAutoAimTarget, FIELD_EHANDLE ), DEFINE_AUTO_ARRAY( m_szAnimExtension, FIELD_CHARACTER ), // DEFINE_CUSTOM_FIELD( m_Activity, ActivityDataOps() ), DEFINE_FIELD( m_nUpdateRate, FIELD_INTEGER ), DEFINE_FIELD( m_fLerpTime, FIELD_FLOAT ), DEFINE_FIELD( m_bLagCompensation, FIELD_BOOLEAN ), DEFINE_FIELD( m_bPredictWeapons, FIELD_BOOLEAN ), DEFINE_FIELD( m_vecAdditionalPVSOrigin, FIELD_POSITION_VECTOR ), DEFINE_FIELD( m_vecCameraPVSOrigin, FIELD_POSITION_VECTOR ), DEFINE_FIELD( m_hUseEntity, FIELD_EHANDLE ), DEFINE_FIELD( m_iTrain, FIELD_INTEGER ), DEFINE_FIELD( m_iRespawnFrames, FIELD_FLOAT ), DEFINE_FIELD( m_afPhysicsFlags, FIELD_INTEGER ), DEFINE_FIELD( m_hVehicle, FIELD_EHANDLE ), // recreate, don't restore // DEFINE_FIELD( m_CommandContext, CUtlVector < CCommandContext > ), //DEFINE_FIELD( m_pPhysicsController, FIELD_POINTER ), //DEFINE_FIELD( m_pShadowStand, FIELD_POINTER ), //DEFINE_FIELD( m_pShadowCrouch, FIELD_POINTER ), //DEFINE_FIELD( m_vphysicsCollisionState, FIELD_INTEGER ), // DEFINE_FIELD( m_lastNavArea, CNavArea ), DEFINE_ARRAY( m_szNetworkIDString, FIELD_CHARACTER, MAX_NETWORKID_LENGTH ), DEFINE_FIELD( m_oldOrigin, FIELD_POSITION_VECTOR ), DEFINE_FIELD( m_vecSmoothedVelocity, FIELD_VECTOR ), //DEFINE_FIELD( m_touchedPhysObject, FIELD_BOOLEAN ), //DEFINE_FIELD( m_iPlayerSound, FIELD_INTEGER ), // Don't restore, set in Precache() DEFINE_FIELD( m_iTargetVolume, FIELD_INTEGER ), DEFINE_AUTO_ARRAY( m_rgItems, FIELD_INTEGER ), //DEFINE_FIELD( m_fNextSuicideTime, FIELD_TIME ), DEFINE_FIELD( m_flTimeStepSound, FIELD_TIME ), DEFINE_FIELD( m_flSwimTime, FIELD_TIME ), DEFINE_FIELD( m_flDuckTime, FIELD_TIME ), DEFINE_FIELD( m_flDuckJumpTime, FIELD_TIME ), DEFINE_FIELD( m_flSuitUpdate, FIELD_TIME ), DEFINE_AUTO_ARRAY( m_rgSuitPlayList, FIELD_INTEGER ), DEFINE_FIELD( m_iSuitPlayNext, FIELD_INTEGER ), DEFINE_AUTO_ARRAY( m_rgiSuitNoRepeat, FIELD_INTEGER ), DEFINE_AUTO_ARRAY( m_rgflSuitNoRepeatTime, FIELD_TIME ), DEFINE_FIELD( m_lastDamageAmount, FIELD_INTEGER ), DEFINE_FIELD( m_tbdPrev, FIELD_TIME ), //DEFINE_FIELD( m_flgeigerRange, FIELD_FLOAT ), // Don't restore, reset in Precache() //DEFINE_FIELD( m_flgeigerDelay, FIELD_FLOAT ), // Don't restore, reset in Precache() //DEFINE_FIELD( m_igeigerRangePrev, FIELD_FLOAT ), // Don't restore, reset in Precache() //DEFINE_FIELD( m_iStepLeft, FIELD_INTEGER ), // Don't need to restore //DEFINE_FIELD( m_chTextureType, FIELD_CHARACTER ), // Don't need to restore DEFINE_FIELD( m_idrowndmg, FIELD_INTEGER ), DEFINE_FIELD( m_idrownrestored, FIELD_INTEGER ), DEFINE_FIELD( m_nPoisonDmg, FIELD_INTEGER ), DEFINE_FIELD( m_nPoisonRestored, FIELD_INTEGER ), DEFINE_FIELD( m_bitsHUDDamage, FIELD_INTEGER ), DEFINE_FIELD( m_fInitHUD, FIELD_BOOLEAN ), DEFINE_FIELD( m_flDeathTime, FIELD_TIME ), //DEFINE_FIELD( m_fGameHUDInitialized, FIELD_BOOLEAN ), // only used in multiplayer games //DEFINE_FIELD( m_fWeapon, FIELD_BOOLEAN ), // Don't restore, client needs reset //DEFINE_FIELD( m_iUpdateTime, FIELD_INTEGER ), // Don't need to restore //DEFINE_FIELD( m_iClientBattery, FIELD_INTEGER ), // Don't restore, client needs reset //DEFINE_FIELD( m_iClientHideHUD, FIELD_INTEGER ), // Don't restore, client needs reset //DEFINE_FIELD( m_vecAutoAim, FIELD_VECTOR ), // Don't save/restore - this is recomputed //DEFINE_FIELD( m_lastx, FIELD_INTEGER ), //DEFINE_FIELD( m_lasty, FIELD_INTEGER ), DEFINE_FIELD( m_iFrags, FIELD_INTEGER ), DEFINE_FIELD( m_iDeaths, FIELD_INTEGER ), DEFINE_FIELD( m_flNextDecalTime, FIELD_TIME ), //DEFINE_AUTO_ARRAY( m_szTeamName, FIELD_STRING ), // mp //DEFINE_FIELD( m_iConnected, FIELD_INTEGER ), // from edict_t DEFINE_FIELD( m_ArmorValue, FIELD_INTEGER ), DEFINE_FIELD( m_DmgOrigin, FIELD_VECTOR ), DEFINE_FIELD( m_DmgTake, FIELD_FLOAT ), DEFINE_FIELD( m_DmgSave, FIELD_FLOAT ), DEFINE_FIELD( m_AirFinished, FIELD_TIME ), DEFINE_FIELD( m_PainFinished, FIELD_TIME ), DEFINE_FIELD( m_iPlayerLocked, FIELD_INTEGER ), DEFINE_AUTO_ARRAY( m_hViewModel, FIELD_EHANDLE ), DEFINE_AUTO_ARRAY( m_hObserverViewModel, FIELD_EHANDLE ), DEFINE_FIELD( m_flMaxspeed, FIELD_FLOAT ), DEFINE_FIELD( m_flWaterJumpTime, FIELD_TIME ), DEFINE_FIELD( m_vecWaterJumpVel, FIELD_VECTOR ), DEFINE_FIELD( m_nImpulse, FIELD_INTEGER ), DEFINE_FIELD( m_flStepSoundTime, FIELD_TIME ), DEFINE_FIELD( m_flSwimSoundTime, FIELD_TIME ), DEFINE_FIELD( m_vecLadderNormal, FIELD_VECTOR ), DEFINE_FIELD( m_flFlashTime, FIELD_TIME ), DEFINE_FIELD( m_nDrownDmgRate, FIELD_INTEGER ), // NOT SAVED //DEFINE_FIELD( m_vForcedOrigin, FIELD_VECTOR ), //DEFINE_FIELD( m_bForceOrigin, FIELD_BOOLEAN ), //DEFINE_FIELD( m_nTickBase, FIELD_INTEGER ), //DEFINE_FIELD( m_LastCmd, FIELD_ ), // DEFINE_FIELD( m_pCurrentCommand, CUserCmd ), //DEFINE_FIELD( m_bGamePaused, FIELD_BOOLEAN ), DEFINE_FIELD( m_bitsDamageType, FIELD_INTEGER ), DEFINE_AUTO_ARRAY( m_rgbTimeBasedDamage, FIELD_CHARACTER ), DEFINE_FIELD( m_fLastPlayerTalkTime, FIELD_FLOAT ), DEFINE_FIELD( m_hLastWeapon, FIELD_EHANDLE ), // DEFINE_FIELD( m_SimulatedByThisPlayer, CUtlVector < CHandle < CBaseEntity > > ), DEFINE_FIELD( m_flOldPlayerZ, FIELD_FLOAT ), DEFINE_FIELD( m_flOldPlayerViewOffsetZ, FIELD_FLOAT ), DEFINE_FIELD( m_bPlayerUnderwater, FIELD_BOOLEAN ), DEFINE_FIELD( m_hViewEntity, FIELD_EHANDLE ), DEFINE_FIELD( m_hConstraintEntity, FIELD_EHANDLE ), DEFINE_FIELD( m_vecConstraintCenter, FIELD_VECTOR ), DEFINE_FIELD( m_flConstraintRadius, FIELD_FLOAT ), DEFINE_FIELD( m_flConstraintWidth, FIELD_FLOAT ), DEFINE_FIELD( m_flConstraintSpeedFactor, FIELD_FLOAT ), DEFINE_FIELD( m_hZoomOwner, FIELD_EHANDLE ), DEFINE_FIELD( m_flLaggedMovementValue, FIELD_FLOAT ), DEFINE_FIELD( m_vNewVPhysicsPosition, FIELD_VECTOR ), DEFINE_FIELD( m_vNewVPhysicsVelocity, FIELD_VECTOR ), // Function Pointers DEFINE_FUNCTION( PlayerDeathThink ), // Inputs DEFINE_INPUTFUNC( FIELD_INTEGER, "SetHealth", InputSetHealth ), END_DATADESC() BEGIN_PREDICTION_DATA_NO_BASE( CPlayerState ) END_PREDICTION_DATA() BEGIN_PREDICTION_DATA( CBasePlayer ) END_PREDICTION_DATA() int giPrecacheGrunt = 0; edict_t *CBasePlayer::s_PlayerEdict = NULL; inline bool ShouldRunCommandsInContext( const CCommandContext *ctx ) { // TODO: This should be enabled at some point. If usercmds can run while paused, then // they can create entities which will never die and it will fill up the entity list. #ifdef NO_USERCMDS_DURING_PAUSE return !ctx->paused || sv_noclipduringpause.GetInt(); #else return true; #endif } //----------------------------------------------------------------------------- // Purpose: // Output : CBaseViewModel //----------------------------------------------------------------------------- CBaseViewModel *CBasePlayer::GetViewModel( int index /*= 0*/ ) { Assert( index >= 0 && index < MAX_VIEWMODELS ); return m_hViewModel[ index ].Get(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CBasePlayer::CreateViewModel( int index /*=0*/ ) { Assert( index >= 0 && index < MAX_VIEWMODELS ); if ( GetViewModel( index ) ) return; CBaseViewModel *vm = ( CBaseViewModel * )CreateEntityByName( "viewmodel" ); if ( vm ) { vm->SetAbsOrigin( GetAbsOrigin() ); vm->SetOwner( this ); vm->SetIndex( index ); DispatchSpawn( vm ); vm->FollowEntity( this ); m_hViewModel.Set( index, vm ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CBasePlayer::DestroyViewModels( void ) { int i; for ( i = MAX_VIEWMODELS - 1; i >= 0; i-- ) { CBaseViewModel *vm = GetViewModel( i ); if ( !vm ) continue; UTIL_Remove( vm ); m_hViewModel.Set( i, NULL ); } } //----------------------------------------------------------------------------- // Purpose: Static member function to create a player of the specified class // Input : *className - // *ed - // Output : CBasePlayer //----------------------------------------------------------------------------- CBasePlayer *CBasePlayer::CreatePlayer( const char *className, edict_t *ed ) { CBasePlayer *player; CBasePlayer::s_PlayerEdict = ed; player = ( CBasePlayer * )CreateEntityByName( className ); return player; } //----------------------------------------------------------------------------- // Purpose: // Input : // Output : //----------------------------------------------------------------------------- CBasePlayer::CBasePlayer( ) { CONSTRUCT_PREDICTABLE( CBasePlayer ); AddEFlags( EFL_NO_AUTO_EDICT_ATTACH ); #ifdef _DEBUG m_vecAutoAim.Init(); m_vecAdditionalPVSOrigin.Init(); m_vecCameraPVSOrigin.Init(); m_DmgOrigin.Init(); m_vecLadderNormal.Init(); m_oldOrigin.Init(); m_vecSmoothedVelocity.Init(); #endif if ( s_PlayerEdict ) { // take the assigned edict_t and attach it Assert( s_PlayerEdict != NULL ); NetworkProp()->AttachEdict( s_PlayerEdict ); s_PlayerEdict = NULL; } m_flFlashTime = -1; pl.fixangle = FIXANGLE_ABSOLUTE; pl.hltv = false; pl.frags = 0; pl.deaths = 0; m_iHealth = 0; Weapon_SetLast( NULL ); m_bitsDamageType = 0; m_bForceOrigin = false; m_hVehicle = NULL; m_pCurrentCommand = NULL; // Setup our default FOV m_Local.m_iDefaultFOV = g_pGameRules->DefaultFOV(); m_hZoomOwner = NULL; m_nUpdateRate = 20; // cl_updaterate defualt m_fLerpTime = 0.1f; // cl_interp default m_bPredictWeapons = true; m_bLagCompensation = false; m_flLaggedMovementValue = 1.0f; m_StuckLast = 0; m_impactEnergyScale = 1.0f; m_fLastPlayerTalkTime = 0.0f; m_PlayerInfo.SetParent( this ); ResetObserverMode(); m_surfaceProps = 0; m_pSurfaceData = NULL; m_surfaceFriction = 1.0f; m_chTextureType = 0; m_chPreviousTextureType = 0; } CBasePlayer::~CBasePlayer( ) { VPhysicsDestroyObject(); } //----------------------------------------------------------------------------- // Purpose: // Input : // Output : //----------------------------------------------------------------------------- void CBasePlayer::UpdateOnRemove( void ) { VPhysicsDestroyObject(); // Remove him from his current team if ( GetTeam() ) { GetTeam()->RemovePlayer( this ); } // Chain at end to mimic destructor unwind order BaseClass::UpdateOnRemove(); } //----------------------------------------------------------------------------- // Purpose: // Input : **pvs - // **pas - //----------------------------------------------------------------------------- void CBasePlayer::SetupVisibility( CBaseEntity *pViewEntity, unsigned char *pvs, int pvssize ) { // If we have a viewentity, we don't add the player's origin. if ( pViewEntity ) return; Vector org; org = EyePosition(); engine->AddOriginToPVS( org ); } int CBasePlayer::UpdateTransmitState() { // always call ShouldTransmit() for players return SetTransmitState( FL_EDICT_FULLCHECK ); } int CBasePlayer::ShouldTransmit( const CCheckTransmitInfo *pInfo ) { // Allow me to introduce myself to, err, myself. // I.e., always update the recipient player data even if it's nodraw (first person mode) if ( pInfo->m_pClientEnt == edict() ) { return FL_EDICT_ALWAYS; } // when HLTV is connected and spectators press +USE, they // signal that they are recording a interesting scene // so transmit these 'cameramans' to the HLTV client if ( m_bIsRecording ) { CBaseEntity *pRecipientEntity = CBaseEntity::Instance( pInfo->m_pClientEnt ); Assert( pRecipientEntity->IsPlayer() ); CBasePlayer *pRecipientPlayer = static_cast( pRecipientEntity ); if ( pRecipientPlayer->IsHLTV() ) { // HACK force calling RecomputePVSInformation to update PVS data NetworkProp()->AreaNum(); return FL_EDICT_ALWAYS; } } // Transmit for a short time after death so ragdolls can access reliable player data if ( IsEffectActive( EF_NODRAW ) || ( IsObserver() && ( gpGlobals->curtime - m_flDeathTime > 0.5 ) ) ) { return FL_EDICT_DONTSEND; } return BaseClass::ShouldTransmit( pInfo ); } bool CBasePlayer::WantsLagCompensationOnEntity( const CBasePlayer *pPlayer, const CUserCmd *pCmd, const CBitVec *pEntityTransmitBits ) const { // Team members shouldn't be adjusted unless friendly fire is on. if ( !friendlyfire.GetInt() && pPlayer->GetTeamNumber() == GetTeamNumber() ) 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; } //----------------------------------------------------------------------------- // Sets the view angles //----------------------------------------------------------------------------- void CBasePlayer::SnapEyeAngles( const QAngle &viewAngles ) { pl.v_angle = viewAngles; pl.fixangle = FIXANGLE_ABSOLUTE; } //----------------------------------------------------------------------------- // Purpose: // Input : iSpeed - // iMax - // Output : int //----------------------------------------------------------------------------- int TrainSpeed(int iSpeed, int iMax) { float fSpeed, fMax; int iRet = 0; fMax = (float)iMax; fSpeed = iSpeed; fSpeed = fSpeed/fMax; if (iSpeed < 0) iRet = TRAIN_BACK; else if (iSpeed == 0) iRet = TRAIN_NEUTRAL; else if (fSpeed < 0.33) iRet = TRAIN_SLOW; else if (fSpeed < 0.66) iRet = TRAIN_MEDIUM; else iRet = TRAIN_FAST; return iRet; } void CBasePlayer::DeathSound( void ) { // temporarily using pain sounds for death sounds // Did we die from falling? if ( m_bitsDamageType & DMG_FALL ) { // They died in the fall. Play a splat sound. EmitSound( "Player.FallGib" ); } else { EmitSound( "Player.Death" ); } // play one of the suit death alarms if ( IsSuitEquipped() ) { UTIL_EmitGroupnameSuit(edict(), "HEV_DEAD"); } } // override takehealth // bitsDamageType indicates type of damage healed. int CBasePlayer::TakeHealth( float flHealth, int bitsDamageType ) { // clear out any damage types we healed. // UNDONE: generic health should not heal any // UNDONE: time-based damage if (m_takedamage) { m_bitsDamageType &= ~(bitsDamageType & ~DMG_TIMEBASED); } return BaseClass::TakeHealth (flHealth, bitsDamageType); } //----------------------------------------------------------------------------- // Purpose: Draw all overlays (should be implemented in cascade by subclass to add // any additional non-text overlays) // Input : // Output : Current text offset from the top //----------------------------------------------------------------------------- void CBasePlayer::DrawDebugGeometryOverlays(void) { // -------------------------------------------------------- // If in buddha mode and dead draw lines to indicate death // -------------------------------------------------------- if ((m_debugOverlays & OVERLAY_BUDDHA_MODE) && m_iHealth == 1) { Vector vBodyDir = BodyDirection2D( ); Vector eyePos = EyePosition() + vBodyDir*10.0; Vector vUp = Vector(0,0,8); Vector vSide; CrossProduct( vBodyDir, vUp, vSide); NDebugOverlay::Line(eyePos+vSide+vUp, eyePos-vSide-vUp, 255,0,0, false, 0); NDebugOverlay::Line(eyePos+vSide-vUp, eyePos-vSide+vUp, 255,0,0, false, 0); } BaseClass::DrawDebugGeometryOverlays(); } //========================================================= // TraceAttack //========================================================= void CBasePlayer::TraceAttack( const CTakeDamageInfo &inputInfo, const Vector &vecDir, trace_t *ptr ) { if ( m_takedamage ) { CTakeDamageInfo info = inputInfo; // -------------------------------------------------- // If an NPC check if friendly fire is disallowed // -------------------------------------------------- CAI_BaseNPC *pNPC = info.GetAttacker()->MyNPCPointer(); if ( pNPC && (pNPC->CapabilitiesGet() & bits_CAP_NO_HIT_PLAYER) && pNPC->IRelationType( this ) != D_HT ) { return; } // Prevent team damage here so blood doesn't appear if ( info.GetAttacker()->IsPlayer() ) { if ( !g_pGameRules->FPlayerCanTakeDamage( this, info.GetAttacker() ) ) return; } SetLastHitGroup( ptr->hitgroup ); switch ( ptr->hitgroup ) { case HITGROUP_GENERIC: break; case HITGROUP_HEAD: info.ScaleDamage( sk_player_head.GetFloat() ); break; case HITGROUP_CHEST: info.ScaleDamage( sk_player_chest.GetFloat() ); break; case HITGROUP_STOMACH: info.ScaleDamage( sk_player_stomach.GetFloat() ); break; case HITGROUP_LEFTARM: case HITGROUP_RIGHTARM: info.ScaleDamage( sk_player_arm.GetFloat() ); break; case HITGROUP_LEFTLEG: case HITGROUP_RIGHTLEG: info.ScaleDamage( sk_player_leg.GetFloat() ); break; default: break; } SpawnBlood(ptr->endpos, vecDir, BloodColor(), info.GetDamage());// a little surface blood. TraceBleed( info.GetDamage(), vecDir, ptr, info.GetDamageType() ); AddMultiDamage( info, this ); } } //------------------------------------------------------------------------------ // Purpose : Do some kind of damage effect for the type of damage // Input : // Output : //------------------------------------------------------------------------------ void CBasePlayer::DamageEffect(float flDamage, int fDamageType) { if (fDamageType & DMG_CRUSH) { //Red damage indicator color32 red = {128,0,0,128}; UTIL_ScreenFade( this, red, 1.0f, 0.1f, FFADE_IN ); } else if (fDamageType & DMG_DROWN) { //Red damage indicator color32 blue = {0,0,128,128}; UTIL_ScreenFade( this, blue, 1.0f, 0.1f, FFADE_IN ); } else if (fDamageType & DMG_SLASH) { // If slash damage shoot some blood SpawnBlood(EyePosition(), g_vecAttackDir, BloodColor(), flDamage); } else if (fDamageType & DMG_PLASMA) { // Blue screen fade color32 blue = {0,0,255,100}; UTIL_ScreenFade( this, blue, 0.2, 0.4, FFADE_MODULATE ); // Very small screen shake ViewPunch(QAngle(random->RandomInt(-0.1,0.1), random->RandomInt(-0.1,0.1), random->RandomInt(-0.1,0.1))); // Burn sound EmitSound( "Player.PlasmaDamage" ); } else if (fDamageType & DMG_SONIC) { // Sonic damage sound EmitSound( "Player.SonicDamage" ); } else if ( fDamageType & DMG_BULLET ) { EmitSound( "Flesh.BulletImpact" ); } } /* Take some damage. NOTE: each call to OnTakeDamage with bitsDamageType set to a time-based damage type will cause the damage time countdown to be reset. Thus the ongoing effects of poison, radiation etc are implemented with subsequent calls to OnTakeDamage using DMG_GENERIC. */ // Old values #define OLD_ARMOR_RATIO 0.2 // Armor Takes 80% of the damage #define OLD_ARMOR_BONUS 0.5 // Each Point of Armor is work 1/x points of health // New values #define ARMOR_RATIO 0.2 #define ARMOR_BONUS 1.0 int CBasePlayer::OnTakeDamage( const CTakeDamageInfo &inputInfo ) { // have suit diagnose the problem - ie: report damage type int bitsDamage = inputInfo.GetDamageType(); int ffound = true; int fmajor; int fcritical; int fTookDamage; int ftrivial; float flRatio; float flBonus; float flHealthPrev = m_iHealth; CTakeDamageInfo info = inputInfo; IServerVehicle *pVehicle = GetVehicle(); if ( pVehicle ) { // Players don't take blast or radiation damage while in vehicles. // The vehicle has to deal it to him if ( info.GetDamageType() & (DMG_BLAST|DMG_RADIATION) ) return 0; info.ScaleDamage(pVehicle->DamageModifier(info)); } if ( GetFlags() & FL_GODMODE ) return 0; if ( m_debugOverlays & OVERLAY_BUDDHA_MODE ) { if ((m_iHealth - info.GetDamage()) <= 0) { m_iHealth = 1; return 0; } } // Early out if there's no damage if ( !info.GetDamage() ) return 0; if( old_armor.GetBool() ) { flBonus = OLD_ARMOR_BONUS; flRatio = OLD_ARMOR_RATIO; } else { flBonus = ARMOR_BONUS; flRatio = ARMOR_RATIO; } if ( ( info.GetDamageType() & DMG_BLAST ) && g_pGameRules->IsMultiplayer() ) { // blasts damage armor more. flBonus *= 2; } // Already dead if ( !IsAlive() ) return 0; // go take the damage first if ( !g_pGameRules->FPlayerCanTakeDamage( this, info.GetAttacker() ) ) { // Refuse the damage return 0; } // keep track of amount of damage last sustained m_lastDamageAmount = info.GetDamage(); // Armor. if (m_ArmorValue && !(info.GetDamageType() & (DMG_FALL | DMG_DROWN | DMG_POISON | DMG_RADIATION)) )// armor doesn't protect against fall or drown damage! { float flNew = info.GetDamage() * flRatio; float flArmor; flArmor = (info.GetDamage() - flNew) * flBonus; if( !old_armor.GetBool() ) { if( flArmor < 1.0 ) { flArmor = 1.0; } } // Does this use more armor than we have? if (flArmor > m_ArmorValue) { flArmor = m_ArmorValue; flArmor *= (1/flBonus); flNew = info.GetDamage() - flArmor; m_DmgSave = m_ArmorValue; m_ArmorValue = 0; } else { m_DmgSave = flArmor; m_ArmorValue -= flArmor; } info.SetDamage( flNew ); } // this cast to INT is critical!!! If a player ends up with 0.5 health, the engine will get that // as an int (zero) and think the player is dead! (this will incite a clientside screentilt, etc) // NOTENOTE: jdw - We are now capable of retaining the matissa of this damage value and deferring its application // info.SetDamage( (int)info.GetDamage() ); // Call up to the base class fTookDamage = BaseClass::OnTakeDamage( info ); // Early out if the base class took no damage if ( !fTookDamage ) return 0; // add to the damage total for clients, which will be sent as a single // message at the end of the frame // todo: remove after combining shotgun blasts? if ( info.GetInflictor() && info.GetInflictor()->edict() ) m_DmgOrigin = info.GetInflictor()->GetAbsOrigin(); m_DmgTake += (int)info.GetDamage(); // reset damage time countdown for each type of time based damage player just sustained for (int i = 0; i < CDMG_TIMEBASED; i++) { if (info.GetDamageType() & (DMG_PARALYZE << i)) { m_rgbTimeBasedDamage[i] = 0; } } // Display any effect associate with this damage type DamageEffect(info.GetDamage(),bitsDamage); // how bad is it, doc? ftrivial = (m_iHealth > 75 || m_lastDamageAmount < 5); fmajor = (m_lastDamageAmount > 25); fcritical = (m_iHealth < 30); // handle all bits set in this damage message, // let the suit give player the diagnosis // UNDONE: add sounds for types of damage sustained (ie: burn, shock, slash ) // UNDONE: still need to record damage and heal messages for the following types // DMG_BURN // DMG_FREEZE // DMG_BLAST // DMG_SHOCK m_bitsDamageType |= bitsDamage; // Save this so we can report it to the client m_bitsHUDDamage = -1; // make sure the damage bits get resent while (fTookDamage && (!ftrivial || (bitsDamage & DMG_TIMEBASED)) && ffound && bitsDamage) { ffound = false; if (bitsDamage & DMG_CLUB) { if (fmajor) SetSuitUpdate("!HEV_DMG4", false, SUIT_NEXT_IN_30SEC); // minor fracture bitsDamage &= ~DMG_CLUB; ffound = true; } if (bitsDamage & (DMG_FALL | DMG_CRUSH)) { if (fmajor) SetSuitUpdate("!HEV_DMG5", false, SUIT_NEXT_IN_30SEC); // major fracture else SetSuitUpdate("!HEV_DMG4", false, SUIT_NEXT_IN_30SEC); // minor fracture bitsDamage &= ~(DMG_FALL | DMG_CRUSH); ffound = true; } if (bitsDamage & DMG_BULLET) { if (m_lastDamageAmount > 5) SetSuitUpdate("!HEV_DMG6", false, SUIT_NEXT_IN_30SEC); // blood loss detected //else // SetSuitUpdate("!HEV_DMG0", false, SUIT_NEXT_IN_30SEC); // minor laceration bitsDamage &= ~DMG_BULLET; ffound = true; } if (bitsDamage & DMG_SLASH) { if (fmajor) SetSuitUpdate("!HEV_DMG1", false, SUIT_NEXT_IN_30SEC); // major laceration else SetSuitUpdate("!HEV_DMG0", false, SUIT_NEXT_IN_30SEC); // minor laceration bitsDamage &= ~DMG_SLASH; ffound = true; } if (bitsDamage & DMG_SONIC) { if (fmajor) SetSuitUpdate("!HEV_DMG2", false, SUIT_NEXT_IN_1MIN); // internal bleeding bitsDamage &= ~DMG_SONIC; ffound = true; } if (bitsDamage & (DMG_POISON | DMG_PARALYZE)) { if (bitsDamage & DMG_POISON) { m_nPoisonDmg += info.GetDamage(); m_tbdPrev = gpGlobals->curtime; m_rgbTimeBasedDamage[itbd_PoisonRecover] = 0; } SetSuitUpdate("!HEV_DMG3", false, SUIT_NEXT_IN_1MIN); // blood toxins detected bitsDamage &= ~( DMG_POISON | DMG_PARALYZE ); ffound = true; } if (bitsDamage & DMG_ACID) { SetSuitUpdate("!HEV_DET1", false, SUIT_NEXT_IN_1MIN); // hazardous chemicals detected bitsDamage &= ~DMG_ACID; ffound = true; } if (bitsDamage & DMG_NERVEGAS) { SetSuitUpdate("!HEV_DET0", false, SUIT_NEXT_IN_1MIN); // biohazard detected bitsDamage &= ~DMG_NERVEGAS; ffound = true; } if (bitsDamage & DMG_RADIATION) { SetSuitUpdate("!HEV_DET2", false, SUIT_NEXT_IN_1MIN); // radiation detected bitsDamage &= ~DMG_RADIATION; ffound = true; } if (bitsDamage & DMG_SHOCK) { bitsDamage &= ~DMG_SHOCK; ffound = true; } } m_Local.m_vecPunchAngle.SetX( -2 ); if (fTookDamage && !ftrivial && fmajor && flHealthPrev >= 75) { // first time we take major damage... // turn automedic on if not on SetSuitUpdate("!HEV_MED1", false, SUIT_NEXT_IN_30MIN); // automedic on // give morphine shot if not given recently SetSuitUpdate("!HEV_HEAL7", false, SUIT_NEXT_IN_30MIN); // morphine shot } if (fTookDamage && !ftrivial && fcritical && flHealthPrev < 75) { // already took major damage, now it's critical... if (m_iHealth < 6) SetSuitUpdate("!HEV_HLTH3", false, SUIT_NEXT_IN_10MIN); // near death else if (m_iHealth < 20) SetSuitUpdate("!HEV_HLTH2", false, SUIT_NEXT_IN_10MIN); // health critical // give critical health warnings if (!random->RandomInt(0,3) && flHealthPrev < 50) SetSuitUpdate("!HEV_DMG7", false, SUIT_NEXT_IN_5MIN); //seek medical attention } // if we're taking time based damage, warn about its continuing effects if (fTookDamage && (info.GetDamageType() & DMG_TIMEBASED) && flHealthPrev < 75) { if (flHealthPrev < 50) { if (!random->RandomInt(0,3)) SetSuitUpdate("!HEV_DMG7", false, SUIT_NEXT_IN_5MIN); //seek medical attention } else SetSuitUpdate("!HEV_HLTH1", false, SUIT_NEXT_IN_10MIN); // health dropping } // Do special explosion damage effect if ( bitsDamage & DMG_BLAST ) { OnDamagedByExplosion( info ); } return fTookDamage; } //----------------------------------------------------------------------------- // Purpose: // Input : &info - // damageAmount - //----------------------------------------------------------------------------- #define MIN_SHOCK_AND_CONFUSION_DAMAGE 30.0f // This code has been updated for nightfall, so that the effect is not so much as a // kamkazy easter egg then a proper, working effect. // 12 Units = 1 foot = ~30cm #define MIN_EAR_RINGING_DISTANCE 400.0f // ~33Feet/~1.1M // Old Code //#define MIN_EAR_RINGING_DISTANCE 240.0f // 20 feet ConVar DSPCheck("DSPCheck", "0", 0, " "); //checker for the motion blur timer //----------------------------------------------------------------------------- // Purpose: // Input : &info - //----------------------------------------------------------------------------- void CBasePlayer::OnDamagedByExplosion( const CTakeDamageInfo &info ) { float lastDamage = info.GetDamage(); float distanceFromPlayer = 9999.0f; CBaseEntity *inflictor = info.GetInflictor(); if ( inflictor ) { Vector delta = GetAbsOrigin() - inflictor->GetAbsOrigin(); distanceFromPlayer = delta.Length(); } bool ear_ringing = distanceFromPlayer < MIN_EAR_RINGING_DISTANCE ? true : false; bool shock = lastDamage >= MIN_SHOCK_AND_CONFUSION_DAMAGE; if ( !shock && !ear_ringing ) return; int effect = shock ? random->RandomInt( 35, 37 ) : random->RandomInt( 32, 34 ); CSingleUserRecipientFilter user( this ); enginesound->SetPlayerDSP( user, effect, false ); //----------------------------------------------------------------------------- // Nightfall - amckern - amckern@yahoo.com //----------------------------------------------------------------------------- // Explosion Effect //----------------------------------------------------------------------------- float effect_time = 0; if ( DSPCheck.GetBool() == 0 ) { //Turn on the blur - console hack engine->ServerCommand( "pp_motionblur 1\n" ); engine->ServerCommand( "DSPCheck 1\n" ); //Nice and fast effect, yet still tells the player they have been fucked up by an explosion engine->ServerCommand( "pp_motionblur_time 0.01\n" ); //Add a timer here, so that when the DSP is worn off, the blur is turned off // We have no idea how long the dsp lasts, and stacking the dsp, will also cause this // effect to stack as well (so we can end up with a very laggy motionblur_time effect) effect_time = gpGlobals->curtime +1.5; if (gpGlobals->curtime > effect_time) { engine->ServerCommand( "play dsp-end.wav\n" ); engine->ServerCommand( "pp_motionblur 0\n" ); engine->ServerCommand( "DSPCheck 0\n" ); } } } //========================================================= // PackDeadPlayerItems - call this when a player dies to // pack up the appropriate weapons and ammo items, and to // destroy anything that shouldn't be packed. // // This is pretty brute force :( //========================================================= void CBasePlayer::PackDeadPlayerItems( void ) { //----------------------------------------------------------------------------- // Nightfall - amckern - amckern@yahoo.com //----------------------------------------------------------------------------- // Explosion Effect - Pack up, and die, so no front buffer issues on respawn //----------------------------------------------------------------------------- engine->ServerCommand( "pp_motionblur 0\n" ); engine->ServerCommand( "DSPCheck 0\n" ); int iWeaponRules; int iAmmoRules; int i; CBaseCombatWeapon *rgpPackWeapons[ 20 ];// 20 hardcoded for now. How to determine exactly how many weapons we have? int iPackAmmo[ MAX_AMMO_SLOTS + 1]; int iPW = 0;// index into packweapons array int iPA = 0;// index into packammo array memset(rgpPackWeapons, NULL, sizeof(rgpPackWeapons) ); memset(iPackAmmo, -1, sizeof(iPackAmmo) ); // get the game rules iWeaponRules = g_pGameRules->DeadPlayerWeapons( this ); iAmmoRules = g_pGameRules->DeadPlayerAmmo( this ); if ( iWeaponRules == GR_PLR_DROP_GUN_NO && iAmmoRules == GR_PLR_DROP_AMMO_NO ) { // nothing to pack. Remove the weapons and return. Don't call create on the box! RemoveAllItems( true ); return; } // go through all of the weapons and make a list of the ones to pack for ( i = 0 ; i < WeaponCount() ; i++ ) { // there's a weapon here. Should I pack it? CBaseCombatWeapon *pPlayerItem = GetWeapon( i ); if ( pPlayerItem ) { switch( iWeaponRules ) { case GR_PLR_DROP_GUN_ACTIVE: if ( GetActiveWeapon() && pPlayerItem == GetActiveWeapon() ) { // this is the active item. Pack it. rgpPackWeapons[ iPW++ ] = pPlayerItem; } break; case GR_PLR_DROP_GUN_ALL: rgpPackWeapons[ iPW++ ] = pPlayerItem; break; default: break; } } } // now go through ammo and make a list of which types to pack. if ( iAmmoRules != GR_PLR_DROP_AMMO_NO ) { for ( i = 0 ; i < MAX_AMMO_SLOTS ; i++ ) { if ( GetAmmoCount( i ) > 0 ) { // player has some ammo of this type. switch ( iAmmoRules ) { case GR_PLR_DROP_AMMO_ALL: iPackAmmo[ iPA++ ] = i; break; case GR_PLR_DROP_AMMO_ACTIVE: // WEAPONTODO: Make this work /* if ( GetActiveWeapon() && i == GetActiveWeapon()->m_iPrimaryAmmoType ) { // this is the primary ammo type for the active weapon iPackAmmo[ iPA++ ] = i; } else if ( GetActiveWeapon() && i == GetActiveWeapon()->m_iSecondaryAmmoType ) { // this is the secondary ammo type for the active weapon iPackAmmo[ iPA++ ] = i; } */ break; default: break; } } } } RemoveAllItems( true );// now strip off everything that wasn't handled by the code above. } void CBasePlayer::RemoveAllItems( bool removeSuit ) { if (GetActiveWeapon()) { ResetAutoaim( ); GetActiveWeapon()->Holster( ); } Weapon_SetLast( NULL ); RemoveAllWeapons(); RemoveAllAmmo(); if ( removeSuit ) { m_Local.m_bWearingSuit = false; } UpdateClientData(); } bool CBasePlayer::IsDead() const { return m_lifeState == LIFE_DEAD; } static float DamageForce( const Vector &size, float damage ) { float force = damage * ((32 * 32 * 72.0) / (size.x * size.y * size.z)) * 5; if ( force > 1000.0) { force = 1000.0; } return force; } int CBasePlayer::OnTakeDamage_Alive( const CTakeDamageInfo &info ) { // set damage type sustained m_bitsDamageType |= info.GetDamageType(); if ( !BaseClass::OnTakeDamage_Alive( info ) ) return 0; CBaseEntity * attacker = info.GetAttacker(); if ( !attacker ) return 0; Vector vecDir = vec3_origin; if ( info.GetInflictor() ) { vecDir = info.GetInflictor()->WorldSpaceCenter() - Vector ( 0, 0, 10 ) - WorldSpaceCenter(); VectorNormalize( vecDir ); } if ( info.GetInflictor() && (GetMoveType() == MOVETYPE_WALK) && ( !attacker->IsSolidFlagSet(FSOLID_TRIGGER)) ) { Vector force = vecDir * -DamageForce( WorldAlignSize(), info.GetBaseDamage() ); if ( force.z > 250.0f ) { force.z = 250.0f; } ApplyAbsVelocityImpulse( force ); } // fire global game event IGameEvent * event = gameeventmanager->CreateEvent( "player_hurt" ); if ( event ) { event->SetInt("userid", GetUserID() ); event->SetInt("health", max(0, m_iHealth) ); event->SetInt("priority", 5 ); // HLTV event priority, not transmitted if ( attacker->IsPlayer() ) { CBasePlayer *player = ToBasePlayer( attacker ); event->SetInt("attacker", player->GetUserID() ); // hurt by other player } else { event->SetInt("attacker", 0 ); // hurt by "world" } gameeventmanager->FireEvent( event ); } // Insert a combat sound so that nearby NPCs hear battle if ( attacker->IsNPC() ) { CSoundEnt::InsertSound( SOUND_COMBAT, GetAbsOrigin(), 512, 0.5, this );//<>//magic number } return 1; } void CBasePlayer::Event_Killed( const CTakeDamageInfo &info ) { CSound *pSound; g_pGameRules->PlayerKilled( this, info ); ClearUseEntity(); // this client isn't going to be thinking for a while, so reset the sound until they respawn pSound = CSoundEnt::SoundPointerForIndex( CSoundEnt::ClientSoundIndex( edict() ) ); { if ( pSound ) { pSound->Reset(); } } // don't let the status bar glitch for players with <0 health. if (m_iHealth < -99) { m_iHealth = 0; } // holster the current weapon if ( GetActiveWeapon() ) { GetActiveWeapon()->Holster(); } SetAnimation( PLAYER_DIE ); SetViewOffset( VEC_DEAD_VIEWHEIGHT ); m_lifeState = LIFE_DYING; pl.deadflag = true; AddSolidFlags( FSOLID_NOT_SOLID ); SetMoveType( MOVETYPE_FLYGRAVITY ); SetGroundEntity( NULL ); // clear out the suit message cache so we don't keep chattering SetSuitUpdate(NULL, false, 0); // reset FOV SetFOV( this, 0 ); if ( FlashlightIsOn() ) { FlashlightTurnOff(); } m_flDeathTime = gpGlobals->curtime; // only count alive players if (m_lastNavArea) { m_lastNavArea->DecrementPlayerCount( GetTeamNumber() ); m_lastNavArea = NULL; } BaseClass::Event_Killed( info ); } void CBasePlayer::Event_Dying() { // NOT GIBBED, RUN THIS CODE DeathSound(); // The dead body rolls out of the vehicle. if ( IsInAVehicle() ) { LeaveVehicle(); } QAngle angles = GetLocalAngles(); angles.x = 0; angles.z = 0; SetLocalAngles( angles ); SetThink(&CBasePlayer::PlayerDeathThink); SetNextThink( gpGlobals->curtime + 0.1f ); BaseClass::Event_Dying(); } // Set the activity based on an event or current state void CBasePlayer::SetAnimation( PLAYER_ANIM playerAnim ) { int animDesired; char szAnim[64]; float speed; speed = GetAbsVelocity().Length2D(); if (GetFlags() & (FL_FROZEN|FL_ATCONTROLS)) { speed = 0; playerAnim = PLAYER_IDLE; } Activity idealActivity = ACT_WALK;// TEMP!!!!! // This could stand to be redone. Why is playerAnim abstracted from activity? (sjb) if (playerAnim == PLAYER_JUMP) { idealActivity = ACT_HOP; } else if (playerAnim == PLAYER_SUPERJUMP) { idealActivity = ACT_LEAP; } else if (playerAnim == PLAYER_DIE) { if ( m_lifeState == LIFE_ALIVE ) { idealActivity = GetDeathActivity(); } } else if (playerAnim == PLAYER_ATTACK1) { if ( m_Activity == ACT_HOVER || m_Activity == ACT_SWIM || m_Activity == ACT_HOP || m_Activity == ACT_LEAP || m_Activity == ACT_DIESIMPLE ) { idealActivity = m_Activity; } else { idealActivity = ACT_RANGE_ATTACK1; } } else if (playerAnim == PLAYER_IDLE || playerAnim == PLAYER_WALK) { if ( !( GetFlags() & FL_ONGROUND ) && (m_Activity == ACT_HOP || m_Activity == ACT_LEAP) ) // Still jumping { idealActivity = m_Activity; } else if ( GetWaterLevel() > 1 ) { if ( speed == 0 ) idealActivity = ACT_HOVER; else idealActivity = ACT_SWIM; } else { idealActivity = ACT_WALK; } } if (idealActivity == ACT_RANGE_ATTACK1) { if ( GetFlags() & FL_DUCKING ) // crouching { Q_strncpy( szAnim, "crouch_shoot_" ,sizeof(szAnim)); } else { Q_strncpy( szAnim, "ref_shoot_" ,sizeof(szAnim)); } Q_strncat( szAnim, m_szAnimExtension ,sizeof(szAnim), COPY_ALL_CHARACTERS ); animDesired = LookupSequence( szAnim ); if (animDesired == -1) animDesired = 0; if ( GetSequence() != animDesired || !SequenceLoops() ) { SetCycle( 0 ); } // Tracker 24588: In single player when firing own weapon this causes eye and punchangle to jitter //if (!SequenceLoops()) //{ // AddEffects( EF_NOINTERP ); //} SetActivity( idealActivity ); ResetSequence( animDesired ); } else if (idealActivity == ACT_WALK) { if (GetActivity() != ACT_RANGE_ATTACK1 || IsActivityFinished()) { if ( GetFlags() & FL_DUCKING ) // crouching { Q_strncpy( szAnim, "crouch_aim_" ,sizeof(szAnim)); } else { Q_strncpy( szAnim, "ref_aim_" ,sizeof(szAnim)); } Q_strncat( szAnim, m_szAnimExtension,sizeof(szAnim), COPY_ALL_CHARACTERS ); animDesired = LookupSequence( szAnim ); if (animDesired == -1) animDesired = 0; SetActivity( ACT_WALK ); } else { animDesired = GetSequence(); } } else { if ( GetActivity() == idealActivity) return; SetActivity( idealActivity ); animDesired = SelectWeightedSequence( m_Activity ); // Already using the desired animation? if (GetSequence() == animDesired) return; 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 ); } //----------------------------------------------------------------------------- // Purpose: data accessor //----------------------------------------------------------------------------- void CBasePlayer::SetPlayerUnderwater( bool state ) { m_bPlayerUnderwater = state; } /* =========== WaterMove ============ */ #ifdef HL2_DLL // test for HL2 drowning damage increase (aux power used instead) #define AIRTIME 7 // lung full of air lasts this many seconds #define DROWNING_DAMAGE_INITIAL 10 #define DROWNING_DAMAGE_MAX 10 #else #define AIRTIME 12 // lung full of air lasts this many seconds #define DROWNING_DAMAGE_INITIAL 2 #define DROWNING_DAMAGE_MAX 5 #endif void CBasePlayer::WaterMove() { int air; if ( ( GetMoveType() == MOVETYPE_NOCLIP ) && !GetMoveParent() ) { m_AirFinished = gpGlobals->curtime + AIRTIME; return; } if (m_iHealth < 0) return; // waterlevel 0 - not in water // waterlevel 1 - feet in water // waterlevel 2 - waist in water // waterlevel 3 - head in water if (GetWaterLevel() != 3 || CanBreatheUnderwater()) { // not underwater // play 'up for air' sound if (m_AirFinished < gpGlobals->curtime) { EmitSound( "Player.DrownStart" ); } m_AirFinished = gpGlobals->curtime + AIRTIME; m_nDrownDmgRate = DROWNING_DAMAGE_INITIAL; // if we took drowning damage, give it back slowly if (m_idrowndmg > m_idrownrestored) { // set drowning damage bit. hack - dmg_drownrecover actually // makes the time based damage code 'give back' health over time. // make sure counter is cleared so we start count correctly. // NOTE: this actually causes the count to continue restarting // until all drowning damage is healed. m_bitsDamageType |= DMG_DROWNRECOVER; m_bitsDamageType &= ~DMG_DROWN; m_rgbTimeBasedDamage[itbd_DrownRecover] = 0; } } else { // fully under water // stop restoring damage while underwater m_bitsDamageType &= ~DMG_DROWNRECOVER; m_rgbTimeBasedDamage[itbd_DrownRecover] = 0; if (m_AirFinished < gpGlobals->curtime && !(GetFlags() & FL_GODMODE) ) // drown! { if (m_PainFinished < gpGlobals->curtime) { // take drowning damage m_nDrownDmgRate += 1; if (m_nDrownDmgRate > DROWNING_DAMAGE_MAX) { m_nDrownDmgRate = DROWNING_DAMAGE_MAX; } OnTakeDamage( CTakeDamageInfo( GetContainingEntity(INDEXENT(0)), GetContainingEntity(INDEXENT(0)), m_nDrownDmgRate, DMG_DROWN ) ); m_PainFinished = gpGlobals->curtime + 1; // track drowning damage, give it back when // player finally takes a breath m_idrowndmg += m_nDrownDmgRate; } } else { m_bitsDamageType &= ~DMG_DROWN; } } if ( GetWaterLevel() < 3 ) { if ( m_bPlayerUnderwater ) { StopSound( "Player.AmbientUnderWater" ); SetPlayerUnderwater( false ); } } else if ( GetWaterLevel() < 2 ) { if ( GetWaterLevel() == 0 ) { if ( GetFlags() & FL_INWATER ) { EmitSound( "Player.Wade" ); RemoveFlag( FL_INWATER ); } return; } } else if ( GetWaterLevel() > 2 ) { if ( m_bPlayerUnderwater == false ) { EmitSound( "Player.AmbientUnderWater" ); SetPlayerUnderwater( true ); } return; } // make bubbles air = (int)( m_AirFinished - gpGlobals->curtime ); #if 0 if (GetWaterType() == CONTENT_LAVA) // do damage { if (m_flDamageTime < gpGlobals->curtime) { OnTakeDamage( GetContainingEntity(INDEXENT(0)), GetContainingEntity(INDEXENT(0)), 10 * GetWaterLevel(), DMG_BURN); } } else if (GetWaterType() == CONTENT_SLIME) // do damage { m_flDamageTime = gpGlobals->curtime + 1; OnTakeDamage(GetContainingEntity(INDEXENT(0)), GetContainingEntity(INDEXENT(0)), 4 * GetWaterLevel(), DMG_ACID); } #endif if (!(GetFlags() & FL_INWATER)) { // player enter water sound if (GetWaterType() == CONTENTS_WATER) { EmitSound( "Player.Wade" ); } AddFlag( FL_INWATER ); } } // true if the player is attached to a ladder bool CBasePlayer::IsOnLadder( void ) { return (GetMoveType() == MOVETYPE_LADDER); } float CBasePlayer::GetWaterJumpTime() const { return m_flWaterJumpTime; } void CBasePlayer::SetWaterJumpTime( float flWaterJumpTime ) { m_flWaterJumpTime = flWaterJumpTime; } float CBasePlayer::GetSwimSoundTime( void ) const { return m_flSwimSoundTime; } void CBasePlayer::SetSwimSoundTime( float flSwimSoundTime ) { m_flSwimSoundTime = flSwimSoundTime; } void CBasePlayer::ShowViewPortPanel( const char * name, bool bShow, KeyValues *data ) { CSingleUserRecipientFilter filter( this ); filter.MakeReliable(); int count = 0; KeyValues *subkey = NULL; if ( data ) { subkey = data->GetFirstSubKey(); while ( subkey ) { count++; subkey = subkey->GetNextKey(); } subkey = data->GetFirstSubKey(); // reset } UserMessageBegin( filter, "VGUIMenu" ); WRITE_STRING( name ); // menu name WRITE_BYTE( bShow?1:0 ); WRITE_BYTE( count ); // write additional data (be carefull not more than 192 bytes!) while ( subkey ) { WRITE_STRING( subkey->GetName() ); WRITE_STRING( subkey->GetString() ); subkey = subkey->GetNextKey(); } MessageEnd(); } void CBasePlayer::PlayerDeathThink(void) { float flForward; SetNextThink( gpGlobals->curtime + 0.1f ); if (GetFlags() & FL_ONGROUND) { flForward = GetAbsVelocity().Length() - 20; if (flForward <= 0) { SetAbsVelocity( vec3_origin ); } else { Vector vecNewVelocity = GetAbsVelocity(); VectorNormalize( vecNewVelocity ); vecNewVelocity *= flForward; SetAbsVelocity( vecNewVelocity ); } } if ( HasWeapons() ) { // we drop the guns here because weapons that have an area effect and can kill their user // will sometimes crash coming back from CBasePlayer::Killed() if they kill their owner because the // player class sometimes is freed. It's safer to manipulate the weapons once we know // we aren't calling into any of their code anymore through the player pointer. PackDeadPlayerItems(); } if (GetModelIndex() && (!IsSequenceFinished()) && (m_lifeState == LIFE_DYING)) { StudioFrameAdvance( ); m_iRespawnFrames++; if ( m_iRespawnFrames < 60 ) // animations should be no longer than this return; } if (m_lifeState == LIFE_DYING) m_lifeState = LIFE_DEAD; StopAnimation(); AddEffects( EF_NOINTERP ); m_flPlaybackRate = 0.0; int fAnyButtonDown = (m_nButtons & ~IN_SCORE); // wait for all buttons released if (m_lifeState == LIFE_DEAD) { if (fAnyButtonDown) return; if ( g_pGameRules->FPlayerCanRespawn( this ) ) { m_lifeState = LIFE_RESPAWNABLE; } return; } // if the player has been dead for one second longer than allowed by forcerespawn, // forcerespawn isn't on. Send the player off to an intermission camera until they // choose to respawn. if ( g_pGameRules->IsMultiplayer() && ( gpGlobals->curtime > (m_flDeathTime + DEATH_ANIMATION_TIME) ) && !IsObserver() ) { // go to dead camera. StartObserverMode( m_iObserverLastMode ); } // wait for any button down, or mp_forcerespawn is set and the respawn time is up if (!fAnyButtonDown && !( g_pGameRules->IsMultiplayer() && forcerespawn.GetInt() > 0 && (gpGlobals->curtime > (m_flDeathTime + 5))) ) return; m_nButtons = 0; m_iRespawnFrames = 0; //Msg( "Respawn\n"); respawn( this, !IsObserver() );// don't copy a corpse if we're in deathcam. SetNextThink( TICK_NEVER_THINK ); } /* //========================================================= // StartDeathCam - find an intermission spot and send the // player off into observer mode //========================================================= void CBasePlayer::StartDeathCam( void ) { CBaseEntity *pSpot, *pNewSpot; int iRand; if ( GetViewOffset() == vec3_origin ) { // don't accept subsequent attempts to StartDeathCam() return; } pSpot = gEntList.FindEntityByClassname( NULL, "info_intermission"); if ( pSpot ) { // at least one intermission spot in the world. iRand = random->RandomInt( 0, 3 ); while ( iRand > 0 ) { pNewSpot = gEntList.FindEntityByClassname( pSpot, "info_intermission"); if ( pNewSpot ) { pSpot = pNewSpot; } iRand--; } CreateCorpse(); StartObserverMode( pSpot->GetAbsOrigin(), pSpot->GetAbsAngles() ); } else { // no intermission spot. Push them up in the air, looking down at their corpse trace_t tr; CreateCorpse(); UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + Vector( 0, 0, 128 ), MASK_PLAYERSOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr ); QAngle angles; VectorAngles( GetAbsOrigin() - tr.endpos, angles ); StartObserverMode( tr.endpos, angles ); return; } } */ void CBasePlayer::StopObserverMode() { m_bForcedObserverMode = false; m_bIsRecording = false; m_afPhysicsFlags &= ~PFLAG_OBSERVER; if ( m_iObserverMode == OBS_MODE_NONE ) return; if ( m_iObserverMode > OBS_MODE_DEATHCAM ) { m_iObserverLastMode = m_iObserverMode; } m_iObserverMode = OBS_MODE_NONE; ShowViewPortPanel( "specmenu", false ); ShowViewPortPanel( "specgui", false ); ShowViewPortPanel( "overview", false ); } bool CBasePlayer::StartObserverMode(int mode) { if ( !IsObserver() ) { // set position to last view offset SetAbsOrigin( GetAbsOrigin() + GetViewOffset() ); SetViewOffset( vec3_origin ); } Assert( mode > OBS_MODE_NONE ); m_afPhysicsFlags |= PFLAG_OBSERVER; // Holster weapon immediately, to allow it to cleanup if ( GetActiveWeapon() ) GetActiveWeapon()->Holster(); // clear out the suit message cache so we don't keep chattering SetSuitUpdate(NULL, FALSE, 0); SetGroundEntity( (CBaseEntity *)NULL ); RemoveFlag( FL_DUCKING ); AddSolidFlags( FSOLID_NOT_SOLID ); SetObserverMode( mode ); ShowViewPortPanel( "specgui" ); // Setup flags m_Local.m_iHideHUD = HIDEHUD_HEALTH; m_takedamage = DAMAGE_NO; //Don't set the player to EF_NODRAW - the client can determine //whether to draw the player or not with ShouldDraw //AddEffects( EF_NODRAW ); m_iHealth = 1; m_lifeState = LIFE_DEAD; // Can't be dead, otherwise movement doesn't work right. pl.deadflag = true; return true; } bool CBasePlayer::SetObserverMode(int mode ) { if ( mode < OBS_MODE_NONE || mode > OBS_MODE_ROAMING ) return false; // check mp_forcecamera settings for dead players if ( mode > OBS_MODE_FIXED && GetTeamNumber() > TEAM_SPECTATOR ) { switch ( mp_forcecamera.GetInt() ) { case OBS_ALLOW_ALL : break; // no restrictions case OBS_ALLOW_TEAM : mode = OBS_MODE_IN_EYE; break; case OBS_ALLOW_NONE : mode = OBS_MODE_FIXED; break; // donw't allow anything } } if ( m_iObserverMode > OBS_MODE_DEATHCAM ) { // remember mode if we were really spectating before m_iObserverLastMode = m_iObserverMode; } m_iObserverMode = mode; switch ( mode ) { case OBS_MODE_NONE: case OBS_MODE_FIXED : case OBS_MODE_DEATHCAM : SetFOV( this, 0 ); // Reset FOV SetViewOffset( vec3_origin ); SetMoveType( MOVETYPE_NONE ); break; case OBS_MODE_CHASE : case OBS_MODE_IN_EYE : // udpate FOV and viewmodels SetObserverTarget( m_hObserverTarget ); SetMoveType( MOVETYPE_OBSERVER ); break; case OBS_MODE_ROAMING : SetFOV( this, 0 ); // Reset FOV SetObserverTarget( m_hObserverTarget ); SetViewOffset( vec3_origin ); SetMoveType( MOVETYPE_OBSERVER ); break; } CheckObserverSettings(); return true; } int CBasePlayer::GetObserverMode() { return m_iObserverMode; } void CBasePlayer::ForceObserverMode(int mode) { int tempMode = OBS_MODE_ROAMING; if ( m_iObserverMode == mode ) return; // don't change last mode if already in forced mode if ( m_bForcedObserverMode ) { tempMode = m_iObserverLastMode; } SetObserverMode( mode ); if ( m_bForcedObserverMode ) { m_iObserverLastMode = tempMode; } m_bForcedObserverMode = true; } void CBasePlayer::CheckObserverSettings() { // check if we are in forced mode and may go back to old mode if ( m_bForcedObserverMode ) { CBaseEntity * target = m_hObserverTarget; if ( !IsValidObserverTarget(target) ) { // if old target is still invalid, try to find valid one target = FindNextObserverTarget( false ); } if ( target ) { // we found a valid target m_bForcedObserverMode = false; // disable force mode SetObserverMode( m_iObserverLastMode ); // switch to last mode SetObserverTarget( target ); // goto target // TODO check for HUD icons return; } else { // else stay in forced mode, no changes return; } } // make sure our last mode is valid if ( m_iObserverLastMode < OBS_MODE_FIXED ) { m_iObserverLastMode = OBS_MODE_ROAMING; } // check if our spectating target is still a valid one if ( m_iObserverMode == OBS_MODE_IN_EYE || m_iObserverMode == OBS_MODE_CHASE ) { if ( !IsValidObserverTarget( m_hObserverTarget.Get() ) ) { // our target is not valid, try to find new target CBaseEntity * target = FindNextObserverTarget( false ); if ( target ) { // switch to new valid target SetObserverTarget( target ); } else { // couldn't find new target, switch to temporary mode if ( mp_forcecamera.GetInt() == OBS_ALLOW_ALL ) { // let player roam around ForceObserverMode( OBS_MODE_ROAMING ); } else { // fix player view right where it is ForceObserverMode( OBS_MODE_FIXED ); m_hObserverTarget.Set( NULL ); // no traget to follow } } } CBasePlayer *target = ToBasePlayer( m_hObserverTarget.Get() ); // for ineye mode we have to copy several data to see exactly the same if ( target && m_iObserverMode == OBS_MODE_IN_EYE ) { float fFov = target->GetFOV(); if ( GetFOV() != fFov ) { SetFOV( this, fFov, 0 ); } int flagMask = FL_ONGROUND | FL_DUCKING ; int flags = target->GetFlags() & flagMask; if ( (GetFlags() & flagMask) != flags ) { flags |= GetFlags() & (~flagMask); // keep other flags ClearFlags(); AddFlag( flags ); } if ( target->GetViewOffset() != GetViewOffset() ) { SetViewOffset( target->GetViewOffset() ); } } } } CBaseEntity * CBasePlayer::GetObserverTarget() { return m_hObserverTarget.Get(); } void CBasePlayer::ObserverUse( bool bIsPressed ) { if ( !HLTVDirector()->IsActive() ) return; if ( GetTeamNumber() != TEAM_SPECTATOR ) return; // only pure spectators can play cameraman if ( !bIsPressed ) return; m_bIsRecording = !m_bIsRecording; IGameEvent *event = gameeventmanager->CreateEvent( "hltv_cameraman" ); if ( event ) { event->SetInt("index", entindex() ); event->SetBool("active", m_bIsRecording ); gameeventmanager->FireEvent( event ); } if ( m_bIsRecording ) { ClientPrint( this, HUD_PRINTTALK, "Recording...ON" ); } else { ClientPrint( this, HUD_PRINTTALK, "Recording...OFF" ); } /* UTIL_SayText( "Spectator can not USE anything", this ); Vector dir,end; Vector start = GetAbsOrigin(); AngleVectors( GetAbsAngles(), &dir ); VectorNormalize( dir ); VectorMA( start, 32.0f, dir, end ); trace_t tr; UTIL_TraceLine( start, end, MASK_PLAYERSOLID, this, COLLISION_GROUP_PLAYER_MOVEMENT, &tr ); if ( tr.fraction == 1.0f ) return; // no obstacles in spectators way VectorMA( start, 128.0f, dir, end ); Ray_t ray; ray.Init( end, start, VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX ); UTIL_TraceRay( ray, MASK_PLAYERSOLID, this, COLLISION_GROUP_PLAYER_MOVEMENT, &tr ); if ( tr.startsolid || tr.allsolid ) return; SetAbsOrigin( tr.endpos ); */ } void CBasePlayer::JumptoPosition(const Vector &origin, const QAngle &angles) { SetAbsOrigin( origin ); SetAbsVelocity( vec3_origin ); // stop movement SetLocalAngles( angles ); SnapEyeAngles( angles ); } bool CBasePlayer::SetObserverTarget(CBaseEntity *target) { if ( !IsValidObserverTarget( target ) ) return false; // set new target m_hObserverTarget.Set( target ); // reset fov to default SetFOV( this, 0 ); if ( m_iObserverMode == OBS_MODE_ROAMING ) { Vector dir, end; Vector start = target->EyePosition(); AngleVectors( target->EyeAngles(), &dir ); VectorNormalize( dir ); VectorMA( start, -64.0f, dir, end ); Ray_t ray; ray.Init( start, end, VEC_DUCK_HULL_MIN , VEC_DUCK_HULL_MAX ); trace_t tr; UTIL_TraceRay( ray, MASK_PLAYERSOLID, target, COLLISION_GROUP_PLAYER_MOVEMENT, &tr ); JumptoPosition( tr.endpos, target->EyeAngles() ); } else if ( m_iObserverMode == OBS_MODE_IN_EYE ) { // only update view models in ineye mode if ( target->IsPlayer() ) { CBasePlayer * pPlayer = ToBasePlayer( target ); // copy viewmodels from target for ( int i = 0; i < MAX_VIEWMODELS; i++ ) { m_hObserverViewModel.Set( i, pPlayer->GetViewModel(i) ); } // set FOV of player we observe SetFOV( this, pPlayer->GetFOV() ); } } return true; } bool CBasePlayer::IsValidObserverTarget(CBaseEntity * target) { if ( target == NULL ) return false; // MOD AUTHORS: Add checks on target here or in derived methode if ( !target->IsPlayer() ) // only track players return false; CBasePlayer * player = ToBasePlayer( target ); /* Don't spec observers or players who haven't picked a class yet if ( player->IsObserver() ) return false; */ if ( player->IsEffectActive( EF_NODRAW ) ) // don't watch invisible players return false; if ( player->m_lifeState == LIFE_RESPAWNABLE ) // target is dead, waiting for respawn return false; if ( player->m_lifeState == LIFE_DEAD || player->m_lifeState == LIFE_DYING ) { if ( (player->m_flDeathTime + DEATH_ANIMATION_TIME ) < gpGlobals->curtime ) { return false; // allow watching until 3 seconds after death to see death animation } } // check forcecamera settings for active players if ( GetTeamNumber() != TEAM_SPECTATOR ) { switch ( mp_forcecamera.GetInt() ) { case OBS_ALLOW_ALL : break; case OBS_ALLOW_TEAM : if ( GetTeamNumber() != target->GetTeamNumber() ) return false; break; case OBS_ALLOW_NONE : return false; } } return true; // passed all test } CBaseEntity * CBasePlayer::FindNextObserverTarget(bool bReverse) { // MOD AUTHORS: Modify the logic of this function if you want to restrict the observer to watching // only a subset of the players. e.g. Make it check the target's team. /* if ( m_flNextFollowTime && m_flNextFollowTime > gpGlobals->time ) { return; } m_flNextFollowTime = gpGlobals->time + 0.25; */ // TODO move outside this function int startIndex; if ( m_hObserverTarget ) { // start using last followed player startIndex = m_hObserverTarget->entindex(); } else { // start using own player index startIndex = this->entindex(); } int currentIndex = startIndex; int iDir = bReverse ? -1 : 1; do { currentIndex += iDir; // Loop through the clients if (currentIndex > gpGlobals->maxClients) currentIndex = 1; else if (currentIndex < 1) currentIndex = gpGlobals->maxClients; CBaseEntity * nextTarget = UTIL_PlayerByIndex( currentIndex ); if ( IsValidObserverTarget( nextTarget ) ) { return nextTarget; // found next valid player } } while ( currentIndex != startIndex ); return NULL; } //----------------------------------------------------------------------------- // Purpose: Return true if this object can be +used by the player //----------------------------------------------------------------------------- bool CBasePlayer::IsUseableEntity( CBaseEntity *pEntity, unsigned int requiredCaps ) { if ( pEntity ) { int caps = pEntity->ObjectCaps(); if ( caps & (FCAP_IMPULSE_USE|FCAP_CONTINUOUS_USE|FCAP_ONOFF_USE|FCAP_DIRECTIONAL_USE) ) { if ( (caps & requiredCaps) == requiredCaps ) return true; } } return false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CBasePlayer::CanPickupObject( CBaseEntity *pObject, float massLimit, float sizeLimit ) { // UNDONE: Make this virtual and move to HL2 player #ifdef HL2_DLL //Must be valid if ( pObject == NULL ) return false; //Must move with physics if ( pObject->GetMoveType() != MOVETYPE_VPHYSICS ) return false; IPhysicsObject *pList[VPHYSICS_MAX_OBJECT_LIST_COUNT]; int count = pObject->VPhysicsGetObjectList( pList, ARRAYSIZE(pList) ); //Must have a physics object if (!count) return false; float objectMass = 0; bool checkEnable = false; for ( int i = 0; i < count; i++ ) { objectMass += pList[i]->GetMass(); if ( !pList[i]->IsMoveable() ) { checkEnable = true; } if ( pList[i]->GetGameFlags() & FVPHYSICS_NO_PLAYER_PICKUP ) return false; if ( pList[i]->IsHinged() ) return false; } //Msg( "Target mass: %f\n", pPhys->GetMass() ); //Must be under our threshold weight if ( massLimit > 0 && objectMass > massLimit ) return false; if ( checkEnable ) { // Allowing picking up of bouncebombs that player placed. CBounceBomb *pBomb = dynamic_cast(pObject); if( pBomb && pBomb->IsPlayerPlaced() ) return true; // Allow pickup of phys props that are motion enabled on player pickup CPhysicsProp *pProp = dynamic_cast(pObject); CPhysBox *pBox = dynamic_cast(pObject); if ( !pProp && !pBox ) return false; if ( pProp && !(pProp->HasSpawnFlags( SF_PHYSPROP_ENABLE_ON_PHYSCANNON )) ) return false; if ( pBox && !(pBox->HasSpawnFlags( SF_PHYSBOX_ENABLE_ON_PHYSCANNON )) ) return false; } if ( sizeLimit > 0 ) { const Vector &size = pObject->CollisionProp()->OBBSize(); if ( size.x > sizeLimit || size.y > sizeLimit || size.z > sizeLimit ) return false; } return true; #else return false; #endif } float CBasePlayer::GetHeldObjectMass( IPhysicsObject *pHeldObject ) { return 0; } //----------------------------------------------------------------------------- // Purpose: Server side of jumping rules. Most jumping logic is already // handled in shared gamemovement code. Put stuff here that should // only be done server side. //----------------------------------------------------------------------------- void CBasePlayer::Jump() { } void CBasePlayer::Duck( ) { if (m_nButtons & IN_DUCK) { if ( m_Activity != ACT_LEAP ) { SetAnimation( PLAYER_WALK ); } } } // // ID's player as such. // Class_T CBasePlayer::Classify ( void ) { return CLASS_PLAYER; } void CBasePlayer::ResetFragCount() { m_iFrags = 0; pl.frags = m_iFrags; } void CBasePlayer::IncrementFragCount( int nCount ) { m_iFrags += nCount; pl.frags = m_iFrags; } void CBasePlayer::ResetDeathCount() { m_iDeaths = 0; pl.deaths = m_iDeaths; } void CBasePlayer::IncrementDeathCount( int nCount ) { m_iDeaths += nCount; pl.deaths = m_iDeaths; } void CBasePlayer::AddPoints( int score, bool bAllowNegativeScore ) { // Positive score always adds if ( score < 0 ) { if ( !bAllowNegativeScore ) { if ( m_iFrags < 0 ) // Can't go more negative return; if ( -score > m_iFrags ) // Will this go negative? { score = -m_iFrags; // Sum will be 0 } } } m_iFrags += score; pl.frags = m_iFrags; } void CBasePlayer::AddPointsToTeam( int score, bool bAllowNegativeScore ) { if ( GetTeam() ) { GetTeam()->AddScore( score ); } } //----------------------------------------------------------------------------- // Purpose: // Output : int //----------------------------------------------------------------------------- int CBasePlayer::GetCommandContextCount( void ) const { return m_CommandContext.Count(); } //----------------------------------------------------------------------------- // Purpose: // Input : index - // Output : CCommandContext //----------------------------------------------------------------------------- CCommandContext *CBasePlayer::GetCommandContext( int index ) { if ( index < 0 || index >= m_CommandContext.Count() ) return NULL; return &m_CommandContext[ index ]; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CCommandContext *CBasePlayer::AllocCommandContext( void ) { int idx = m_CommandContext.AddToTail(); return &m_CommandContext[ idx ]; } //----------------------------------------------------------------------------- // Purpose: // Input : index - //----------------------------------------------------------------------------- void CBasePlayer::RemoveCommandContext( int index ) { m_CommandContext.Remove( index ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CBasePlayer::RemoveAllCommandContexts() { m_CommandContext.RemoveAll(); } //----------------------------------------------------------------------------- // Purpose: Determine how much time we will be running this frame // Output : float //----------------------------------------------------------------------------- int CBasePlayer::DetermineSimulationTicks( void ) { int command_context_count = GetCommandContextCount(); int context_number; int simulation_ticks = 0; // Determine how much time we will be running this frame and fixup player clock as needed for ( context_number = 0; context_number < command_context_count; context_number++ ) { CCommandContext const *ctx = GetCommandContext( context_number ); Assert( ctx ); Assert( ctx->numcmds > 0 ); Assert( ctx->dropped_packets >= 0 ); // Determine how long it will take to run those packets simulation_ticks += ctx->numcmds + ctx->dropped_packets; } return simulation_ticks; } // 2 ticks ahead or behind current clock means we need to fix clock on client #define TARGET_CLOCK_CORRECTION_TICKS (TIME_TO_TICKS(0.06f)) extern ConVar skip; //----------------------------------------------------------------------------- // Purpose: Based upon amount of time in simulation time, adjust m_nTickBase so that // we just end at the end of the current frame (so the player is basically on clock // with the server) // Input : simulation_ticks - //----------------------------------------------------------------------------- void CBasePlayer::AdjustPlayerTimeBase( int simulation_ticks ) { Assert( simulation_ticks >= 0 ); if ( simulation_ticks < 0 ) return; // Start in the past so that we get to the sv.time that we'll hit at the end of the // frame, just as we process the final command if ( gpGlobals->maxClients == 1 ) { // set TickBase so that player simulation tick matches gpGlobals->tickcount after // all commands have been executed m_nTickBase = gpGlobals->tickcount - simulation_ticks + 1; } else // multiplayer { // set the target tick 2 ticks ahead in the future. this way the client can // alternate around this targettick without getting smaller than gpGlobals->tickcount // after running the commands simulation time should never be smaller than the // current gpGlobals->tickcount, otherwise the simulation time drops out of the // clientside view interpolation buffer. int end_of_frame_ticks = gpGlobals->tickcount + TARGET_CLOCK_CORRECTION_TICKS; int estimated_end_tick = m_nTickBase + simulation_ticks; // If client gets ahead of this, we'll need to correct int too_fast_limit = end_of_frame_ticks + TARGET_CLOCK_CORRECTION_TICKS; // If client falls behind this, we'll also need to correct int too_slow_limit = end_of_frame_ticks - TARGET_CLOCK_CORRECTION_TICKS; // See if we are too fast if ( estimated_end_tick > too_fast_limit ) { // DevMsg( "client too fast by %i ticks\n", estimated_end_tick - end_of_frame_ticks ); m_nTickBase = end_of_frame_ticks - simulation_ticks + 1; } // Or to slow else if ( estimated_end_tick < too_slow_limit ) { // DevMsg( "client too slow by %i ticks\n", end_of_frame_ticks - estimated_end_tick ); m_nTickBase = end_of_frame_ticks - simulation_ticks + 1; } } } void CBasePlayer::RunNullCommand( void ) { CUserCmd cmd; // NULL command // Store off the globals.. they're gonna get whacked float flOldFrametime = gpGlobals->frametime; float flOldCurtime = gpGlobals->curtime; pl.fixangle = FIXANGLE_NONE; cmd.viewangles = EyeAngles(); float flTimeBase = gpGlobals->curtime; SetTimeBase( flTimeBase ); MoveHelperServer()->SetHost( this ); PlayerRunCommand( &cmd, MoveHelperServer() ); // save off the last good usercmd SetLastUserCommand( cmd ); // Restore the globals.. gpGlobals->frametime = flOldFrametime; gpGlobals->curtime = flOldCurtime; } //----------------------------------------------------------------------------- // Purpose: Note, don't chain to BaseClass::PhysicsSimulate //----------------------------------------------------------------------------- void CBasePlayer::PhysicsSimulate( void ) { VPROF( "CBasePlayer::PhysicsSimulate" ); // If we've got a moveparent, we must simulate that first. CBaseEntity *pMoveParent = GetMoveParent(); if (pMoveParent) { pMoveParent->PhysicsSimulate(); } // Make sure not to simulate this guy twice per frame if (m_nSimulationTick == gpGlobals->tickcount ) { return; } m_nSimulationTick = gpGlobals->tickcount; // See how much time has queued up for running int simulation_ticks = DetermineSimulationTicks(); // If some time will elapse, make sure our clock (m_nTickBase) starts at the correct time if ( simulation_ticks > 0 ) { AdjustPlayerTimeBase( simulation_ticks ); } if ( IsHLTV() ) { // just run a single, empty command to makke sure // all preThink/Postthink functions are called as usual Assert ( GetCommandContextCount() == 0 ); RunNullCommand(); RemoveAllCommandContexts(); return; } // Store off true server timestamps float savetime = gpGlobals->curtime; float saveframetime = gpGlobals->frametime; int command_context_count = GetCommandContextCount(); for ( int context_number = 0; context_number < command_context_count; context_number++ ) { // Get oldest ( newer are added to tail ) CCommandContext *ctx = GetCommandContext( context_number ); Assert( ctx ); int i; int numbackup = ctx->totalcmds - ctx->numcmds; // If the server is paused, zero out motion,buttons,view changes if ( ctx->paused ) { bool clear_angles = true; // If no clipping and cheats enabled and noclipduring game enabled, then leave // forwardmove and angles stuff in usercmd if ( GetMoveType() == MOVETYPE_NOCLIP && sv_cheats->GetBool() && sv_noclipduringpause.GetBool() ) { clear_angles = false; } for ( i = 0; i < ctx->numcmds; i++ ) { ctx->cmds[ i ].buttons = 0; if ( clear_angles ) { ctx->cmds[ i ].forwardmove = 0; ctx->cmds[ i ].sidemove = 0; ctx->cmds[ i ].upmove = 0; VectorCopy ( pl.v_angle, ctx->cmds[ i ].viewangles ); } } ctx->dropped_packets = 0; } MoveHelperServer()->SetHost( this ); // Suppress predicted events, etc. if ( IsPredictingWeapons() ) { IPredictionSystem::SuppressHostEvents( this ); } // If we haven't dropped too many packets, then run some commands if ( ctx->dropped_packets < 24 ) { int droppedcmds = ctx->dropped_packets; if ( droppedcmds > numbackup ) { // Msg( "lost %i cmds\n", droppedcmds ); } // run the last known cmd for each dropped cmd we don't have a backup for while ( droppedcmds > numbackup ) { m_LastCmd.tick_count++; if ( ShouldRunCommandsInContext( ctx ) ) { PlayerRunCommand( &m_LastCmd, MoveHelperServer() ); } droppedcmds--; } // Now run the "history" commands if we still have dropped packets while ( droppedcmds > 0 ) { int cmdnum = ctx->numcmds + droppedcmds - 1; if ( ShouldRunCommandsInContext( ctx ) ) { PlayerRunCommand( &ctx->cmds[cmdnum], MoveHelperServer() ); } droppedcmds--; } } // Now run any new command(s). Go backward because the most recent command is at index 0. for ( i = ctx->numcmds - 1; i >= 0; i-- ) { if ( ShouldRunCommandsInContext( ctx ) ) { PlayerRunCommand( &ctx->cmds[ i ], MoveHelperServer() ); } } // Save off the last good command in case we drop > numbackup packets and need to rerun them // we'll use this to "guess" at what was in the missing packets m_LastCmd = ctx->cmds[ CMD_MOSTRECENT ]; // Update our vphysics object. if ( m_pPhysicsController ) { // If simulating at 2 * TICK_INTERVAL, add an extra TICK_INTERVAL to position arrival computation int additionalTick = CBaseEntity::IsSimulatingOnAlternateTicks() ? 1 : 0; float flSecondsToArrival = ( ctx->numcmds + ctx->dropped_packets + additionalTick ) * TICK_INTERVAL; UpdateVPhysicsPosition( m_vNewVPhysicsPosition, m_vNewVPhysicsVelocity, flSecondsToArrival ); } // Always reset after running commands IPredictionSystem::SuppressHostEvents( NULL ); } // Clear all contexts RemoveAllCommandContexts(); // Restore the true server clock // FIXME: Should this occur after simulation of children so // that they are in the timespace of the player? gpGlobals->curtime = savetime; gpGlobals->frametime = saveframetime; } unsigned int CBasePlayer::PhysicsSolidMaskForEntity() const { return MASK_PLAYERSOLID; } //----------------------------------------------------------------------------- // Purpose: // Input : *buf - // totalcmds - // dropped_packets - // ignore - // paused - // Output : float -- Time in seconds of last movement command //----------------------------------------------------------------------------- void CBasePlayer::ProcessUsercmds( CUserCmd *cmds, int numcmds, int totalcmds, int dropped_packets, bool paused ) { CCommandContext *ctx = AllocCommandContext(); Assert( ctx ); int i; for ( i = totalcmds - 1; i >= 0; i-- ) { ctx->cmds[ i ] = cmds[ i ]; } ctx->numcmds = numcmds; ctx->totalcmds = totalcmds, ctx->dropped_packets = dropped_packets; ctx->paused = paused; // Set global pause state for this player m_bGamePaused = paused; if ( paused ) { m_nSimulationTick = -1; // Just run the commands right away if paused PhysicsSimulate(); } } //----------------------------------------------------------------------------- // Purpose: // Input : *ucmd - // *moveHelper - //----------------------------------------------------------------------------- void CBasePlayer::PlayerRunCommand(CUserCmd *ucmd, IMoveHelper *moveHelper) { m_touchedPhysObject = false; if ( pl.fixangle == FIXANGLE_NONE) { VectorCopy ( ucmd->viewangles, pl.v_angle ); } // Handle FL_FROZEN. // Prevent player moving for some seconds after New Game, so that they pick up everything if( GetFlags() & FL_FROZEN || (developer.GetInt() == 0 && gpGlobals->eLoadType == MapLoad_NewGame && gpGlobals->curtime < 3.0 ) ) { ucmd->forwardmove = 0; ucmd->sidemove = 0; ucmd->upmove = 0; ucmd->buttons = 0; ucmd->impulse = 0; VectorCopy ( pl.v_angle, ucmd->viewangles ); } PlayerMove()->RunCommand(this, ucmd, moveHelper); } void CBasePlayer::HandleFuncTrain(void) { if ( m_afPhysicsFlags & PFLAG_DIROVERRIDE ) AddFlag( FL_ONTRAIN ); else RemoveFlag( FL_ONTRAIN ); // Train speed control if (( m_afPhysicsFlags & PFLAG_DIROVERRIDE ) == 0) { if (m_iTrain & TRAIN_ACTIVE) { m_iTrain = TRAIN_NEW; // turn off train } return; } CBaseEntity *pTrain = GetGroundEntity(); float vel; if ( pTrain ) { if ( !(pTrain->ObjectCaps() & FCAP_DIRECTIONAL_USE) ) pTrain = NULL; } if ( !pTrain ) { if ( GetActiveWeapon()->ObjectCaps() & FCAP_DIRECTIONAL_USE ) { m_iTrain = TRAIN_ACTIVE | TRAIN_NEW; if ( m_nButtons & IN_FORWARD ) { m_iTrain |= TRAIN_FAST; } else if ( m_nButtons & IN_BACK ) { m_iTrain |= TRAIN_BACK; } else { m_iTrain |= TRAIN_NEUTRAL; } return; } else { trace_t trainTrace; // Maybe this is on the other side of a level transition UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + Vector(0,0,-38), MASK_PLAYERSOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &trainTrace ); if ( trainTrace.fraction != 1.0 && trainTrace.m_pEnt ) pTrain = trainTrace.m_pEnt; if ( !pTrain || !(pTrain->ObjectCaps() & FCAP_DIRECTIONAL_USE) || !pTrain->OnControls(this) ) { m_afPhysicsFlags &= ~PFLAG_DIROVERRIDE; m_iTrain = TRAIN_NEW|TRAIN_OFF; return; } } } else if ( !( GetFlags() & FL_ONGROUND ) || pTrain->HasSpawnFlags( SF_TRACKTRAIN_NOCONTROL ) || (m_nButtons & (IN_MOVELEFT|IN_MOVERIGHT) ) ) { // Turn off the train if you jump, strafe, or the train controls go dead m_afPhysicsFlags &= ~PFLAG_DIROVERRIDE; m_iTrain = TRAIN_NEW|TRAIN_OFF; return; } SetAbsVelocity( vec3_origin ); vel = 0; if ( m_afButtonPressed & IN_FORWARD ) { vel = 1; pTrain->Use( this, this, USE_SET, (float)vel ); } else if ( m_afButtonPressed & IN_BACK ) { vel = -1; pTrain->Use( this, this, USE_SET, (float)vel ); } if (vel) { m_iTrain = TrainSpeed(pTrain->m_flSpeed, ((CFuncTrackTrain*)pTrain)->GetMaxSpeed()); m_iTrain |= TRAIN_ACTIVE|TRAIN_NEW; } } void CBasePlayer::PreThink(void) { if ( g_fGameOver || m_iPlayerLocked ) return; // intermission or finale ItemPreFrame( ); WaterMove(); if ( g_pGameRules && g_pGameRules->FAllowFlashlight() ) m_Local.m_iHideHUD &= ~HIDEHUD_FLASHLIGHT; else m_Local.m_iHideHUD |= HIDEHUD_FLASHLIGHT; // checks if new client data (for HUD and view control) needs to be sent to the client UpdateClientData(); CheckTimeBasedDamage(); CheckSuitUpdate(); if ( GetObserverMode() > OBS_MODE_FIXED ) { CheckObserverSettings(); // do this each frame } if (m_lifeState >= LIFE_DYING) return; HandleFuncTrain(); if (m_nButtons & IN_JUMP) { // If on a ladder, jump off the ladder // else Jump Jump(); } // If trying to duck, already ducked, or in the process of ducking if ((m_nButtons & IN_DUCK) || (GetFlags() & FL_DUCKING) || (m_afPhysicsFlags & PFLAG_DUCKING) ) Duck(); // // If we're not on the ground, we're falling. Update our falling velocity. // if ( !( GetFlags() & FL_ONGROUND ) ) { m_Local.m_flFallVelocity = -GetAbsVelocity().z; } CNavArea *area = TheNavMesh->GetNavArea( GetAbsOrigin() ); if (area && area != m_lastNavArea) { // player entered a new nav area if (m_lastNavArea) { m_lastNavArea->DecrementPlayerCount( GetTeamNumber() ); } area->IncrementPlayerCount( GetTeamNumber() ); m_lastNavArea = area; if ( area->GetPlace() != UNDEFINED_PLACE ) { const char *placeName = TheNavMesh->PlaceToName( area->GetPlace() ); if ( placeName && *placeName ) { Q_strncpy( m_szLastPlaceName.GetForModify(), placeName, MAX_PLACE_NAME_LENGTH ); } } // generate event //KeyValues *event = new KeyValues( "player_entered_area" ); //event->SetInt( "userid", GetUserID() ); //event->SetInt( "areaid", area->GetID() ); //gameeventmanager->FireEvent( event ); } // StudioFrameAdvance( );//!!!HACKHACK!!! Can't be hit by traceline when not animating? } /* Time based Damage works as follows: 1) There are several types of timebased damage: #define DMG_PARALYZE (1 << 14) // slows affected creature down #define DMG_NERVEGAS (1 << 15) // nerve toxins, very bad #define DMG_POISON (1 << 16) // blood poisioning #define DMG_RADIATION (1 << 17) // radiation exposure #define DMG_DROWNRECOVER (1 << 18) // drown recovery #define DMG_ACID (1 << 19) // toxic chemicals or acid burns #define DMG_SLOWBURN (1 << 20) // in an oven 2) A new hit inflicting tbd restarts the tbd counter - each NPC has an 8bit counter, per damage type. The counter is decremented every second, so the maximum time an effect will last is 255/60 = 4.25 minutes. Of course, staying within the radius of a damaging effect like fire, nervegas, radiation will continually reset the counter to max. 3) Every second that a tbd counter is running, the player takes damage. The damage is determined by the type of tdb. Paralyze - 1/2 movement rate, 30 second duration. Nervegas - 5 points per second, 16 second duration = 80 points max dose. Poison - 2 points per second, 25 second duration = 50 points max dose. Radiation - 1 point per second, 50 second duration = 50 points max dose. Drown - 5 points per second, 2 second duration. Acid/Chemical - 5 points per second, 10 second duration = 50 points max. Burn - 10 points per second, 2 second duration. Freeze - 3 points per second, 10 second duration = 30 points max. 4) Certain actions or countermeasures counteract the damaging effects of tbds: Armor/Heater/Cooler - Chemical(acid),burn, freeze all do damage to armor power, then to body - recharged by suit recharger Air In Lungs - drowning damage is done to air in lungs first, then to body - recharged by poking head out of water - 10 seconds if swiming fast Air In SCUBA - drowning damage is done to air in tanks first, then to body - 2 minutes in tanks. Need new tank once empty. Radiation Syringe - Each syringe full provides protection vs one radiation dosage Antitoxin Syringe - Each syringe full provides protection vs one poisoning (nervegas or poison). Health kit - Immediate stop to acid/chemical, fire or freeze damage. Radiation Shower - Immediate stop to radiation damage, acid/chemical or fire damage. */ // If player is taking time based damage, continue doing damage to player - // this simulates the effect of being poisoned, gassed, dosed with radiation etc - // anything that continues to do damage even after the initial contact stops. // Update all time based damage counters, and shut off any that are done. // The m_bitsDamageType bit MUST be set if any damage is to be taken. // This routine will detect the initial on value of the m_bitsDamageType // and init the appropriate counter. Only processes damage every second. //#define PARALYZE_DURATION 30 // number of 2 second intervals to take damage //#define PARALYZE_DAMAGE 0.0 // damage to take each 2 second interval //#define NERVEGAS_DURATION 16 //#define NERVEGAS_DAMAGE 5.0 //#define POISON_DURATION 25 //#define POISON_DAMAGE 2.0 //#define RADIATION_DURATION 50 //#define RADIATION_DAMAGE 1.0 //#define ACID_DURATION 10 //#define ACID_DAMAGE 5.0 //#define SLOWBURN_DURATION 2 //#define SLOWBURN_DAMAGE 1.0 //#define SLOWFREEZE_DURATION 1.0 //#define SLOWFREEZE_DAMAGE 3.0 /* */ void CBasePlayer::CheckTimeBasedDamage() { int i; byte bDuration = 0; static float gtbdPrev = 0.0; if (!(m_bitsDamageType & DMG_TIMEBASED)) return; // only check for time based damage approx. every 2 seconds if (abs(gpGlobals->curtime - m_tbdPrev) < 2.0) return; m_tbdPrev = gpGlobals->curtime; for (i = 0; i < CDMG_TIMEBASED; i++) { // make sure bit is set for damage type if (m_bitsDamageType & (DMG_PARALYZE << i)) { switch (i) { case itbd_Paralyze: // UNDONE - flag movement as half-speed bDuration = PARALYZE_DURATION; break; case itbd_NerveGas: // OnTakeDamage(pev, pev, NERVEGAS_DAMAGE, DMG_GENERIC); bDuration = NERVEGAS_DURATION; break; // case itbd_Poison: // OnTakeDamage( CTakeDamageInfo( this, this, POISON_DAMAGE, DMG_GENERIC ) ); // bDuration = POISON_DURATION; // break; case itbd_Radiation: // OnTakeDamage(pev, pev, RADIATION_DAMAGE, DMG_GENERIC); bDuration = RADIATION_DURATION; break; case itbd_DrownRecover: // NOTE: this hack is actually used to RESTORE health // after the player has been drowning and finally takes a breath if (m_idrowndmg > m_idrownrestored) { int idif = min(m_idrowndmg - m_idrownrestored, 10); TakeHealth(idif, DMG_GENERIC); m_idrownrestored += idif; } bDuration = 4; // get up to 5*10 = 50 points back break; case itbd_PoisonRecover: { // NOTE: this hack is actually used to RESTORE health // after the player has been poisoned. if (m_nPoisonDmg > m_nPoisonRestored) { int nDif = min(m_nPoisonDmg - m_nPoisonRestored, 10); TakeHealth(nDif, DMG_GENERIC); m_nPoisonRestored += nDif; } bDuration = 9; // get up to 10*10 = 100 points back break; } case itbd_Acid: // OnTakeDamage(pev, pev, ACID_DAMAGE, DMG_GENERIC); bDuration = ACID_DURATION; break; case itbd_SlowBurn: // OnTakeDamage(pev, pev, SLOWBURN_DAMAGE, DMG_GENERIC); bDuration = SLOWBURN_DURATION; break; case itbd_SlowFreeze: // OnTakeDamage(pev, pev, SLOWFREEZE_DAMAGE, DMG_GENERIC); bDuration = SLOWFREEZE_DURATION; break; default: bDuration = 0; } if (m_rgbTimeBasedDamage[i]) { // decrement damage duration, detect when done. if (!m_rgbTimeBasedDamage[i] || --m_rgbTimeBasedDamage[i] == 0) { m_rgbTimeBasedDamage[i] = 0; // if we're done, clear damage bits m_bitsDamageType &= ~(DMG_PARALYZE << i); } } else // first time taking this damage type - init damage duration m_rgbTimeBasedDamage[i] = bDuration; } } } /* THE POWER SUIT The Suit provides 3 main functions: Protection, Notification and Augmentation. Some functions are automatic, some require power. The player gets the suit shortly after getting off the train in C1A0 and it stays with him for the entire game. Protection Heat/Cold When the player enters a hot/cold area, the heating/cooling indicator on the suit will come on and the battery will drain while the player stays in the area. After the battery is dead, the player starts to take damage. This feature is built into the suit and is automatically engaged. Radiation Syringe This will cause the player to be immune from the effects of radiation for N seconds. Single use item. Anti-Toxin Syringe This will cure the player from being poisoned. Single use item. Health Small (1st aid kits, food, etc.) Large (boxes on walls) Armor The armor works using energy to create a protective field that deflects a percentage of damage projectile and explosive attacks. After the armor has been deployed, it will attempt to recharge itself to full capacity with the energy reserves from the battery. It takes the armor N seconds to fully charge. Notification (via the HUD) x Health x Ammo x Automatic Health Care Notifies the player when automatic healing has been engaged. x Geiger counter Classic Geiger counter sound and status bar at top of HUD alerts player to dangerous levels of radiation. This is not visible when radiation levels are normal. x Poison Armor Displays the current level of armor. Augmentation Reanimation (w/adrenaline) Causes the player to come back to life after he has been dead for 3 seconds. Will not work if player was gibbed. Single use. Long Jump Used by hitting the ??? key(s). Caused the player to further than normal. SCUBA Used automatically after picked up and after player enters the water. Works for N seconds. Single use. Things powered by the battery Armor Uses N watts for every M units of damage. Heat/Cool Uses N watts for every second in hot/cold area. Long Jump Uses N watts for every jump. Alien Cloak Uses N watts for each use. Each use lasts M seconds. Alien Shield Augments armor. Reduces Armor drain by one half */ // if in range of radiation source, ping geiger counter #define GEIGERDELAY 0.25 void CBasePlayer::UpdateGeigerCounter( void ) { byte range; // delay per update ie: don't flood net with these msgs if (gpGlobals->curtime < m_flgeigerDelay) return; m_flgeigerDelay = gpGlobals->curtime + GEIGERDELAY; // send range to radition source to client range = (byte) clamp(m_flgeigerRange / 4, 0, 255); // This is to make sure you aren't driven crazy by geiger while in the airboat if ( IsInAVehicle() ) { range = clamp( (int)range * 4, 0, 255 ); } if (range != m_igeigerRangePrev) { m_igeigerRangePrev = range; CSingleUserRecipientFilter user( this ); user.MakeReliable(); UserMessageBegin( user, "Geiger" ); WRITE_BYTE( range ); MessageEnd(); } // reset counter and semaphore if (!random->RandomInt(0,3)) { m_flgeigerRange = 1000; } } /* ================ CheckSuitUpdate Play suit update if it's time ================ */ #define SUITUPDATETIME 3.5 #define SUITFIRSTUPDATETIME 0.1 void CBasePlayer::CheckSuitUpdate() { int i; int isentence = 0; int isearch = m_iSuitPlayNext; // Ignore suit updates if no suit if ( !IsSuitEquipped() ) return; // if in range of radiation source, ping geiger counter UpdateGeigerCounter(); if ( g_pGameRules->IsMultiplayer() ) { // don't bother updating HEV voice in multiplayer. return; } if ( gpGlobals->curtime >= m_flSuitUpdate && m_flSuitUpdate > 0) { // play a sentence off of the end of the queue for (i = 0; i < CSUITPLAYLIST; i++) { if ((isentence = m_rgSuitPlayList[isearch]) != 0) break; if (++isearch == CSUITPLAYLIST) isearch = 0; } if (isentence) { m_rgSuitPlayList[isearch] = 0; if (isentence > 0) { // play sentence number char sentence[512]; Q_snprintf( sentence, sizeof( sentence ), "!%s", engine->SentenceNameFromIndex( isentence ) ); UTIL_EmitSoundSuit( edict(), sentence ); } else { // play sentence group UTIL_EmitGroupIDSuit(edict(), -isentence); } m_flSuitUpdate = gpGlobals->curtime + SUITUPDATETIME; } else // queue is empty, don't check m_flSuitUpdate = 0; } } // add sentence to suit playlist queue. if fgroup is true, then // name is a sentence group (HEV_AA), otherwise name is a specific // sentence name ie: !HEV_AA0. If iNoRepeat is specified in // seconds, then we won't repeat playback of this word or sentence // for at least that number of seconds. void CBasePlayer::SetSuitUpdate(char *name, int fgroup, int iNoRepeatTime) { int i; int isentence; int iempty = -1; // Ignore suit updates if no suit if ( !IsSuitEquipped() ) return; if ( g_pGameRules->IsMultiplayer() ) { // due to static channel design, etc. We don't play HEV sounds in multiplayer right now. return; } // if name == NULL, then clear out the queue if (!name) { for (i = 0; i < CSUITPLAYLIST; i++) m_rgSuitPlayList[i] = 0; return; } // get sentence or group number if (!fgroup) { isentence = SENTENCEG_Lookup(name); // Lookup sentence index (not group) by name if (isentence < 0) return; } else // mark group number as negative isentence = -SENTENCEG_GetIndex(name); // Lookup group index by name // check norepeat list - this list lets us cancel // the playback of words or sentences that have already // been played within a certain time. for (i = 0; i < CSUITNOREPEAT; i++) { if (isentence == m_rgiSuitNoRepeat[i]) { // this sentence or group is already in // the norepeat list if (m_rgflSuitNoRepeatTime[i] < gpGlobals->curtime) { // norepeat time has expired, clear it out m_rgiSuitNoRepeat[i] = 0; m_rgflSuitNoRepeatTime[i] = 0.0; iempty = i; break; } else { // don't play, still marked as norepeat return; } } // keep track of empty slot if (!m_rgiSuitNoRepeat[i]) iempty = i; } // sentence is not in norepeat list, save if norepeat time was given if (iNoRepeatTime) { if (iempty < 0) iempty = random->RandomInt(0, CSUITNOREPEAT-1); // pick random slot to take over m_rgiSuitNoRepeat[iempty] = isentence; m_rgflSuitNoRepeatTime[iempty] = iNoRepeatTime + gpGlobals->curtime; } // find empty spot in queue, or overwrite last spot m_rgSuitPlayList[m_iSuitPlayNext++] = isentence; if (m_iSuitPlayNext == CSUITPLAYLIST) m_iSuitPlayNext = 0; if (m_flSuitUpdate <= gpGlobals->curtime) { if (m_flSuitUpdate == 0) // play queue is empty, don't delay too long before playback m_flSuitUpdate = gpGlobals->curtime + SUITFIRSTUPDATETIME; else m_flSuitUpdate = gpGlobals->curtime + SUITUPDATETIME; } } //========================================================= // UpdatePlayerSound - updates the position of the player's // reserved sound slot in the sound list. //========================================================= void CBasePlayer::UpdatePlayerSound ( void ) { int iBodyVolume; int iVolume; CSound *pSound; pSound = CSoundEnt::SoundPointerForIndex( CSoundEnt::ClientSoundIndex( edict() ) ); if ( !pSound ) { Msg( "Client lost reserved sound!\n" ); return; } if (GetFlags() & FL_NOTARGET) { pSound->m_iVolume = 0; return; } // now figure out how loud the player's movement is. if ( GetFlags() & FL_ONGROUND ) { iBodyVolume = GetAbsVelocity().Length(); // clamp the noise that can be made by the body, in case a push trigger, // weapon recoil, or anything shoves the player abnormally fast. // NOTE: 512 units is a pretty large radius for a sound made by the player's body. // then again, I think some materials are pretty loud. if ( iBodyVolume > 512 ) { iBodyVolume = 512; } } else { iBodyVolume = 0; } if ( m_nButtons & IN_JUMP ) { // Jumping is a little louder. iBodyVolume += 100; } m_iTargetVolume = iBodyVolume; // if target volume is greater than the player sound's current volume, we paste the new volume in // immediately. If target is less than the current volume, current volume is not set immediately to the // lower volume, rather works itself towards target volume over time. This gives NPCs a much better chance // to hear a sound, especially if they don't listen every frame. iVolume = pSound->Volume(); if ( m_iTargetVolume > iVolume ) { iVolume = m_iTargetVolume; } else if ( iVolume > m_iTargetVolume ) { iVolume -= 250 * gpGlobals->frametime; if ( iVolume < m_iTargetVolume ) { iVolume = 0; } } if ( pSound ) { pSound->SetSoundOrigin( GetAbsOrigin() ); pSound->m_iType = SOUND_PLAYER; pSound->m_iVolume = iVolume; } // Below are a couple of useful little bits that make it easier to visualize just how much noise the // player is making. //Vector forward = UTIL_YawToVector( pl.v_angle.y ); //UTIL_Sparks( GetAbsOrigin() + forward * iVolume ); //Msg( "%d/%d\n", iVolume, m_iTargetVolume ); } // This is a glorious hack to find free space when you've crouched into some solid space // Our crouching collisions do not work correctly for some reason and this is easier // than fixing the problem :( void FixPlayerCrouchStuck( CBasePlayer *pPlayer ) { trace_t trace; // Move up as many as 18 pixels if the player is stuck. for ( int i = 0; i < 18; i++ ) { UTIL_TraceHull( pPlayer->GetAbsOrigin(), pPlayer->GetAbsOrigin(), VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX, MASK_PLAYERSOLID, pPlayer, COLLISION_GROUP_NONE, &trace ); if ( trace.startsolid ) { Vector origin = pPlayer->GetAbsOrigin(); origin.z += 1.0f; pPlayer->SetLocalOrigin( origin ); } else break; } } #define SMOOTHING_FACTOR 0.9 extern CMoveData *g_pMoveData; // UNDONE: Look and see if the ground entity is in hierarchy with a MOVETYPE_VPHYSICS? // Behavior in that case is not as good currently when the parent is rideable bool CBasePlayer::IsRideablePhysics( IPhysicsObject *pPhysics ) { if ( pPhysics ) { if ( pPhysics->GetMass() > (VPhysicsGetObject()->GetMass()*2) ) return true; } return false; } IPhysicsObject *CBasePlayer::GetGroundVPhysics() { CBaseEntity *pGroundEntity = GetGroundEntity(); if ( pGroundEntity && pGroundEntity->GetMoveType() == MOVETYPE_VPHYSICS ) { IPhysicsObject *pPhysGround = pGroundEntity->VPhysicsGetObject(); if ( pPhysGround && pPhysGround->IsMoveable() ) return pPhysGround; } return NULL; } //----------------------------------------------------------------------------- // For debugging... //----------------------------------------------------------------------------- void CBasePlayer::ForceOrigin( const Vector &vecOrigin ) { m_bForceOrigin = true; m_vForcedOrigin = vecOrigin; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CBasePlayer::PostThink() { m_vecSmoothedVelocity = m_vecSmoothedVelocity * SMOOTHING_FACTOR + GetAbsVelocity() * ( 1 - SMOOTHING_FACTOR ); if ( !g_fGameOver && !m_iPlayerLocked && IsAlive() ) { // set correct collision bounds (may have changed in player movement code) if ( GetFlags() & FL_DUCKING ) { SetCollisionBounds( VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX ); } else { SetCollisionBounds( VEC_HULL_MIN, VEC_HULL_MAX ); } // Handle controlling an entity if ( m_hUseEntity != NULL ) { // if they've moved too far from the gun, or deployed another weapon, unuse the gun if ( m_hUseEntity->OnControls( this ) && ( !GetActiveWeapon() || GetActiveWeapon()->IsEffectActive( EF_NODRAW ) || ( GetActiveWeapon()->GetActivity() == ACT_VM_HOLSTER ) ) ) { m_hUseEntity->Use( this, this, USE_SET, 2 ); // try fire the gun } else { // they've moved off the controls ClearUseEntity(); } } // do weapon stuff ItemPostFrame(); if ( GetFlags() & FL_ONGROUND ) { if (m_Local.m_flFallVelocity > 64 && !g_pGameRules->IsMultiplayer()) { CSoundEnt::InsertSound ( SOUND_PLAYER, GetAbsOrigin(), m_Local.m_flFallVelocity, 0.2, this ); // Msg( "fall %f\n", m_Local.m_flFallVelocity ); } m_Local.m_flFallVelocity = 0; } // select the proper animation for the player character if ( IsAlive() ) { // If he's in a vehicle, sit down if ( IsInAVehicle() ) SetAnimation( PLAYER_IN_VEHICLE ); else 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 ); } // Don't allow bogus sequence on player if ( GetSequence() == -1 ) { SetSequence( 0 ); } StudioFrameAdvance(); DispatchAnimEvents( this ); SetSimulationTime( gpGlobals->curtime ); //Let the weapon update as well Weapon_FrameUpdate(); UpdatePlayerSound(); if ( m_bForceOrigin ) { SetLocalOrigin( m_vForcedOrigin ); SetLocalAngles( m_Local.m_vecPunchAngle ); m_Local.m_vecPunchAngle = RandomAngle( -25, 25 ); m_Local.m_vecPunchAngleVel.Init(); } PostThinkVPhysics(); } // Even if dead simulate entities SimulatePlayerSimulatedEntities(); } // handles touching physics objects void CBasePlayer::Touch( CBaseEntity *pOther ) { if ( pOther == GetGroundEntity() ) return; if ( pOther->GetMoveType() != MOVETYPE_VPHYSICS || pOther->GetSolid() != SOLID_VPHYSICS || (pOther->GetSolidFlags() & FSOLID_TRIGGER) ) return; IPhysicsObject *pPhys = pOther->VPhysicsGetObject(); if ( !pPhys || !pPhys->IsMoveable() ) return; SetTouchedPhysics( true ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CBasePlayer::PostThinkVPhysics( void ) { // Check to see if things are initialized! if ( !m_pPhysicsController ) return; Vector newPosition = GetAbsOrigin(); float frametime = gpGlobals->frametime; if ( frametime <= 0 || frametime > 0.1f ) frametime = 0.1f; IPhysicsObject *pPhysGround = GetGroundVPhysics(); if ( !pPhysGround && m_touchedPhysObject && g_pMoveData->m_outStepHeight <= 0.f && (GetFlags() & FL_ONGROUND) ) { newPosition = m_oldOrigin + frametime * g_pMoveData->m_outWishVel; newPosition = (GetAbsOrigin() * 0.5f) + (newPosition * 0.5f); } int collisionState = VPHYS_WALK; if ( GetMoveType() == MOVETYPE_NOCLIP || GetMoveType() == MOVETYPE_OBSERVER ) { collisionState = VPHYS_NOCLIP; } else if ( GetFlags() & FL_DUCKING ) { collisionState = VPHYS_CROUCH; } if ( collisionState != m_vphysicsCollisionState ) { SetVCollisionState( collisionState ); } if ( !(TouchedPhysics() || pPhysGround) ) { float maxSpeed = m_flMaxspeed > 0.0f ? m_flMaxspeed : sv_maxspeed.GetFloat(); g_pMoveData->m_outWishVel.Init( maxSpeed, maxSpeed, maxSpeed ); } // teleport the physics object up by stepheight (game code does this - reflect in the physics) if ( g_pMoveData->m_outStepHeight > 0.1f ) { if ( g_pMoveData->m_outStepHeight > 4.0f ) { VPhysicsGetObject()->SetPosition( GetAbsOrigin(), vec3_angle, true ); } else { // don't ever teleport into solid Vector position, end; VPhysicsGetObject()->GetPosition( &position, NULL ); end = position; end.z += g_pMoveData->m_outStepHeight; trace_t trace; UTIL_TraceEntity( this, position, end, MASK_PLAYERSOLID, this, COLLISION_GROUP_PLAYER_MOVEMENT, &trace ); if ( trace.DidHit() ) { g_pMoveData->m_outStepHeight = trace.endpos.z - position.z; } m_pPhysicsController->StepUp( g_pMoveData->m_outStepHeight ); } m_pPhysicsController->Jump(); } g_pMoveData->m_outStepHeight = 0.0f; // Store these off because after running the usercmds, it'll pass them // to UpdateVPhysicsPosition. m_vNewVPhysicsPosition = newPosition; m_vNewVPhysicsVelocity = g_pMoveData->m_outWishVel; m_oldOrigin = GetAbsOrigin(); } void CBasePlayer::UpdateVPhysicsPosition( const Vector &position, const Vector &velocity, float secondsToArrival ) { bool onground = (GetFlags() & FL_ONGROUND) ? true : false; IPhysicsObject *pPhysGround = GetGroundVPhysics(); // if the object is much heavier than the player, treat it as a local coordinate system // the player controller will solve movement differently in this case. if ( !IsRideablePhysics(pPhysGround) ) { pPhysGround = NULL; } m_pPhysicsController->Update( position, velocity, secondsToArrival, onground, pPhysGround ); } void CBasePlayer::UpdatePhysicsShadowToCurrentPosition() { UpdateVPhysicsPosition( GetAbsOrigin(), vec3_origin, gpGlobals->frametime ); } Vector CBasePlayer::GetSmoothedVelocity( void ) { if ( IsInAVehicle() ) { return GetVehicle()->GetVehicleEnt()->GetSmoothedVelocity(); } return m_vecSmoothedVelocity; } CBaseEntity *g_pLastSpawn = NULL; //----------------------------------------------------------------------------- // Purpose: Finds a player start entity of the given classname. If any entity of // of the given classname has the SF_PLAYER_START_MASTER flag set, that // is the entity that will be returned. Otherwise, the first entity of // the given classname is returned. // Input : pszClassName - should be "info_player_start", "info_player_coop", or // "info_player_deathmatch" //----------------------------------------------------------------------------- CBaseEntity *FindPlayerStart(const char *pszClassName) { #define SF_PLAYER_START_MASTER 1 CBaseEntity *pStart = gEntList.FindEntityByClassname(NULL, pszClassName); CBaseEntity *pStartFirst = pStart; while (pStart != NULL) { if (pStart->HasSpawnFlags(SF_PLAYER_START_MASTER)) { return pStart; } pStart = gEntList.FindEntityByClassname(pStart, pszClassName); } return pStartFirst; } /* ============ EntSelectSpawnPoint Returns the entity to spawn at USES AND SETS GLOBAL g_pLastSpawn ============ */ CBaseEntity *CBasePlayer::EntSelectSpawnPoint() { CBaseEntity *pSpot; edict_t *player; player = edict(); // choose a info_player_deathmatch point if (g_pGameRules->IsCoOp()) { pSpot = gEntList.FindEntityByClassname( g_pLastSpawn, "info_player_coop"); if ( pSpot ) goto ReturnSpot; pSpot = gEntList.FindEntityByClassname( g_pLastSpawn, "info_player_start"); if ( pSpot ) goto ReturnSpot; } else if ( g_pGameRules->IsDeathmatch() ) { pSpot = g_pLastSpawn; // Randomize the start spot for ( int i = random->RandomInt(1,5); i > 0; i-- ) pSpot = gEntList.FindEntityByClassname( pSpot, "info_player_deathmatch" ); if ( !pSpot ) // skip over the null point pSpot = gEntList.FindEntityByClassname( pSpot, "info_player_deathmatch" ); 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, "info_player_deathmatch" ); continue; } // if so, go to pSpot goto ReturnSpot; } } // increment pSpot pSpot = gEntList.FindEntityByClassname( pSpot, "info_player_deathmatch" ); } 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 startspot is set, (re)spawn there. if ( !gpGlobals->startspot || !strlen(STRING(gpGlobals->startspot))) { pSpot = FindPlayerStart( "info_player_start" ); if ( pSpot ) goto ReturnSpot; } else { pSpot = gEntList.FindEntityByName( NULL, gpGlobals->startspot, NULL ); if ( pSpot ) goto ReturnSpot; } ReturnSpot: if ( !pSpot ) { Warning( "PutClientInServer: no info_player_start on level\n"); return CBaseEntity::Instance( INDEXENT( 0 ) ); } g_pLastSpawn = pSpot; return pSpot; } //----------------------------------------------------------------------------- // Purpose: Called the first time the player's created //----------------------------------------------------------------------------- void CBasePlayer::InitialSpawn( void ) { m_iConnected = PlayerConnected; } //----------------------------------------------------------------------------- // Purpose: Called everytime the player respawns //----------------------------------------------------------------------------- void CBasePlayer::Spawn( void ) { SetClassname( "player" ); // Shared spawning code.. SharedSpawn(); SetSimulatedEveryTick( true ); SetAnimatedEveryTick( true ); m_ArmorValue = 0; SetBlocksLOS( false ); m_iMaxHealth = m_iHealth; // Clear all flags except for FL_FULLEDICT if ( GetFlags() & FL_FAKECLIENT ) { ClearFlags(); AddFlag( FL_CLIENT | FL_FAKECLIENT ); } else { ClearFlags(); AddFlag( FL_CLIENT ); } AddFlag( FL_AIMTARGET ); m_AirFinished = gpGlobals->curtime + AIRTIME; m_nDrownDmgRate = DROWNING_DAMAGE_INITIAL; // only preserve the shadow flag int effects = GetEffects() & EF_NOSHADOW; SetEffects( effects ); m_DmgTake = 0; m_DmgSave = 0; m_bitsHUDDamage = -1; m_bitsDamageType = 0; m_afPhysicsFlags = 0; SetFOV( this, 0 ); m_flNextDecalTime = 0;// let this player decal as soon as he spawns. m_flgeigerDelay = gpGlobals->curtime + 2.0; // wait a few seconds until user-defined message registrations // are recieved by all clients m_flTimeStepSound = 0; m_flFieldOfView = 0.766;// some NPCs use this to determine whether or not the player is looking at them. m_vecAdditionalPVSOrigin = vec3_origin; m_vecCameraPVSOrigin = vec3_origin; if ( !m_fGameHUDInitialized ) g_pGameRules->SetDefaultPlayerTeam( this ); g_pGameRules->GetPlayerSpawnSpot( this ); SetViewOffset( VEC_VIEW ); Precache(); m_bitsDamageType = 0; m_bitsHUDDamage = -1; m_bPlayerUnderwater = false; m_iTrain = TRAIN_NEW; m_HackedGunPos = Vector( 0, 32, 0 ); if ( m_iPlayerSound == SOUNDLIST_EMPTY ) { Msg( "Couldn't alloc player sound slot!\n" ); } SetThink(NULL); m_fInitHUD = true; m_fWeapon = false; m_iClientBattery = -1; m_lastx = m_lasty = 0; m_lastNavArea = NULL; /// @todo Do this once per round instead of once per player if (TheNavMesh) { TheNavMesh->ClearPlayerCounts(); } Q_strncpy( m_szLastPlaceName.GetForModify(), "", MAX_PLACE_NAME_LENGTH ); CSingleUserRecipientFilter user( this ); enginesound->SetPlayerDSP( user, 0, false ); CreateViewModel(); SetCollisionGroup( COLLISION_GROUP_PLAYER ); // if the player is locked, make sure he stays locked if ( m_iPlayerLocked ) { m_iPlayerLocked = false; LockPlayerInPlace(); } if ( GetTeamNumber() != TEAM_SPECTATOR ) { StopObserverMode(); } else { StartObserverMode( m_iObserverLastMode ); } // Clear any screenfade color32 nothing = {0,0,0,255}; UTIL_ScreenFade( this, nothing, 0, 0, FFADE_IN | FFADE_PURGE ); g_pGameRules->PlayerSpawn( this ); m_flLaggedMovementValue = 1.0f; m_vecSmoothedVelocity = vec3_origin; InitVCollision(); IGameEvent *event = gameeventmanager->CreateEvent( "player_spawn" ); if ( event ) { event->SetInt("userid", GetUserID() ); gameeventmanager->FireEvent( event ); } } void CBasePlayer::Activate( void ) { BaseClass::Activate(); AimTarget_ForceRepopulateList(); } void CBasePlayer::Precache( void ) { BaseClass::Precache(); PrecacheScriptSound( "Player.FallGib" ); PrecacheScriptSound( "Player.Death" ); PrecacheScriptSound( "Player.PlasmaDamage" ); PrecacheScriptSound( "Player.SonicDamage" ); PrecacheScriptSound( "Player.DrownStart" ); PrecacheScriptSound( "Player.DrownContinue" ); PrecacheScriptSound( "Player.Wade" ); PrecacheScriptSound( "Player.AmbientUnderWater" ); PrecacheScriptSound( "Player.Wade" ); // in the event that the player JUST spawned, and the level node graph // was loaded, fix all of the node graph pointers before the game starts. // !!!BUGBUG - now that we have multiplayer, this needs to be moved! /* todo - put in better spot and use new ainetowrk stuff if ( WorldGraph.m_fGraphPresent && !WorldGraph.m_fGraphPointersSet ) { if ( !WorldGraph.FSetGraphPointers() ) { Msg( "**Graph pointers were not set!\n"); } else { Msg( "**Graph Pointers Set!\n" ); } } */ // SOUNDS / MODELS ARE PRECACHED in ClientPrecache() (game specific) // because they need to precache before any clients have connected // init geiger counter vars during spawn and each time // we cross a level transition m_flgeigerRange = 1000; m_igeigerRangePrev = 1000; #if 0 // @Note (toml 04-19-04): These are saved, used to be slammed here m_bitsDamageType = 0; m_bitsHUDDamage = -1; m_bPlayerUnderwater = false; m_iTrain = TRAIN_NEW; #endif m_iClientBattery = -1; m_iUpdateTime = 5; // won't update for 1/2 a second if ( gInitHUD ) m_fInitHUD = true; } int CBasePlayer::Save( ISave &save ) { if ( !BaseClass::Save(save) ) return 0; return 1; } int CBasePlayer::Restore( IRestore &restore ) { int status = BaseClass::Restore(restore); if ( !status ) return 0; CSaveRestoreData *pSaveData = gpGlobals->pSaveData; // landmark isn't present. if ( !pSaveData->levelInfo.fUseLandmark ) { Msg( "No Landmark:%s\n", pSaveData->levelInfo.szLandmarkName ); // default to normal spawn CBaseEntity *pSpawnSpot = EntSelectSpawnPoint(); SetLocalOrigin( pSpawnSpot->GetLocalOrigin() + Vector(0,0,1) ); SetLocalAngles( pSpawnSpot->GetLocalAngles() ); } QAngle newViewAngles = pl.v_angle; newViewAngles.z = 0; // Clear out roll SetLocalAngles( newViewAngles ); SnapEyeAngles( newViewAngles ); // Copied from spawn() for now SetBloodColor( BLOOD_COLOR_RED ); if ( GetFlags() & FL_DUCKING ) { // Use the crouch HACK FixPlayerCrouchStuck( this ); UTIL_SetSize(this, VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX); m_Local.m_bDucked = true; } else { m_Local.m_bDucked = false; UTIL_SetSize(this, VEC_HULL_MIN, VEC_HULL_MAX); } InitVCollision(); // success return 1; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CBasePlayer::OnRestore( void ) { BaseClass::OnRestore(); SetViewEntity( m_hViewEntity ); } /* void CBasePlayer::SetTeamName( const char *pTeamName ) { Q_strncpy( m_szTeamName, pTeamName, TEAM_NAME_LENGTH ); } */ void CBasePlayer::SetArmorValue( int value ) { m_ArmorValue = value; } void CBasePlayer::IncrementArmorValue( int nCount, int nMaxValue ) { m_ArmorValue += nCount; if (nMaxValue > 0) { if (m_ArmorValue > nMaxValue) m_ArmorValue = nMaxValue; } } // Only used by the physics gun... is there a better interface? void CBasePlayer::SetPhysicsFlag( int nFlag, bool bSet ) { if (bSet) m_afPhysicsFlags |= nFlag; else m_afPhysicsFlags &= ~nFlag; } void CBasePlayer::NotifyNearbyRadiationSource( float flRange ) { // if player's current geiger counter range is larger // than range to this trigger hurt, reset player's // geiger counter range if (m_flgeigerRange >= flRange) m_flgeigerRange = flRange; } void CBasePlayer::AllowImmediateDecalPainting() { m_flNextDecalTime = gpGlobals->curtime; } // Suicide... void CBasePlayer::CommitSuicide() { if( !IsAlive() ) return; // prevent suiciding too often if ( m_fNextSuicideTime > gpGlobals->curtime ) return; // don't let them suicide for 5 seconds after suiciding m_fNextSuicideTime = gpGlobals->curtime + 5; // have the player kill themself m_iHealth = 0; Event_Killed( CTakeDamageInfo( this, this, 0, DMG_NEVERGIB ) ); Event_Dying(); } //============================================== // HasWeapons - do I have any weapons at all? //============================================== bool CBasePlayer::HasWeapons( void ) { int i; for ( i = 0 ; i < WeaponCount() ; i++ ) { if ( GetWeapon(i) ) { return true; } } return false; } //----------------------------------------------------------------------------- // Purpose: // Input : &vecForce - //----------------------------------------------------------------------------- void CBasePlayer::VelocityPunch( const Vector &vecForce ) { // Clear onground and add velocity. SetGroundEntity( NULL ); ApplyAbsVelocityImpulse(vecForce ); } //-------------------------------------------------------------------------------------------------------------- // VEHICLES //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // Purpose: Put this player in a vehicle //----------------------------------------------------------------------------- void CBasePlayer::GetInVehicle( IServerVehicle *pVehicle, int nRole ) { Assert( NULL == m_hVehicle.Get() ); Assert( nRole >= 0 ); if ( pVehicle->GetPassenger( nRole ) ) return; CBaseEntity *pEnt = pVehicle->GetVehicleEnt(); Assert( pEnt ); if (!pVehicle->IsPassengerUsingStandardWeapons( nRole )) { CBaseCombatWeapon *pWeapon = GetActiveWeapon(); //Must be able to stow our weapon if ( ( pWeapon != NULL ) && ( pWeapon->Holster( NULL ) == false ) ) return; #ifndef HL2_DLL m_Local.m_iHideHUD |= HIDEHUD_WEAPONSELECTION; #endif m_Local.m_iHideHUD |= HIDEHUD_INVEHICLE; } if ( !pVehicle->IsPassengerVisible( nRole ) ) { AddEffects( EF_NODRAW ); } ViewPunchReset(); // Setting the velocity to 0 will cause the IDLE animation to play SetAbsVelocity( vec3_origin ); SetMoveType( MOVETYPE_NOCLIP ); // Choose the entry point of the vehicle, // By default, just stay at the point you started at... // NOTE: we have to set this first so that when the parent is set // the local position just works Vector vNewPos = GetAbsOrigin(); QAngle qAngles = GetAbsAngles(); pVehicle->GetPassengerStartPoint( nRole, &vNewPos, &qAngles ); SetAbsOrigin( vNewPos ); SetAbsAngles( qAngles ); SetParent( pEnt ); SetCollisionGroup( COLLISION_GROUP_IN_VEHICLE ); // We cannot be ducking -- do all this before SetPassenger because it // saves our view offset for restoration when we exit the vehicle. RemoveFlag( FL_DUCKING ); SetViewOffset( VEC_VIEW ); m_Local.m_bDucked = false; m_Local.m_bDucking = false; m_Local.m_flDucktime = 0; pVehicle->SetPassenger( nRole, this ); m_hVehicle = pEnt; // Throw an event indicating that the player entered the vehicle. g_pNotify->ReportNamedEvent( this, "PlayerEnteredVehicle" ); OnVehicleStart(); } //----------------------------------------------------------------------------- // Purpose: Remove this player from a vehicle //----------------------------------------------------------------------------- void CBasePlayer::LeaveVehicle( const Vector &vecExitPoint, const QAngle &vecExitAngles ) { if ( NULL == m_hVehicle.Get() ) return; IServerVehicle *pVehicle = GetVehicle(); Assert( pVehicle ); int nRole = pVehicle->GetPassengerRole( this ); Assert( nRole >= 0 ); SetParent( NULL ); // Find the first non-blocked exit point: Vector vNewPos = GetAbsOrigin(); QAngle qAngles = GetAbsAngles(); if ( vecExitPoint == vec3_origin ) { // FIXME: this might fail to find a safe exit point!! pVehicle->GetPassengerExitPoint( nRole, &vNewPos, &qAngles ); } else { vNewPos = vecExitPoint; qAngles = vecExitAngles; } OnVehicleEnd( vNewPos ); SetAbsOrigin( vNewPos ); SetAbsAngles( qAngles ); // Clear out any leftover velocity SetAbsVelocity( vec3_origin ); qAngles[ROLL] = 0; SnapEyeAngles( qAngles ); #ifndef HL2_DLL m_Local.m_iHideHUD &= ~HIDEHUD_WEAPONSELECTION; #endif m_Local.m_iHideHUD &= ~HIDEHUD_INVEHICLE; RemoveEffects( EF_NODRAW ); SetMoveType( MOVETYPE_WALK ); SetCollisionGroup( COLLISION_GROUP_PLAYER ); m_hVehicle = NULL; pVehicle->SetPassenger(nRole, NULL); // Re-deploy our weapon if ( IsAlive() ) { if ( GetActiveWeapon() && GetActiveWeapon()->IsWeaponVisible() == false ) { GetActiveWeapon()->Deploy(); ShowCrosshair( true ); } } } //============================================== // !!!UNDONE:ultra temporary SprayCan entity to apply // decal frame at a time. For PreAlpha CD //============================================== class CSprayCan : public CPointEntity { public: DECLARE_CLASS( CSprayCan, CPointEntity ); void Spawn ( CBasePlayer *pOwner ); void Think( void ); virtual void Precache(); virtual int ObjectCaps( void ) { return FCAP_DONT_SAVE; } }; LINK_ENTITY_TO_CLASS( spraycan, CSprayCan ); PRECACHE_REGISTER( spraycan ); void CSprayCan::Spawn ( CBasePlayer *pOwner ) { SetLocalOrigin( pOwner->WorldSpaceCenter() + Vector ( 0 , 0 , 32 ) ); SetLocalAngles( pOwner->EyeAngles() ); SetOwnerEntity( pOwner ); SetNextThink( gpGlobals->curtime ); EmitSound( "SprayCan.Paint" ); } void CSprayCan::Precache() { BaseClass::Precache(); PrecacheScriptSound( "SprayCan.Paint" ); } void CSprayCan::Think( void ) { trace_t tr; int playernum; int nFrames; CBasePlayer *pPlayer; pPlayer = ToBasePlayer( GetOwnerEntity() ); nFrames = 1; // FIXME, look up from material playernum = GetOwnerEntity()->entindex(); // Msg( "Spray by player %i, %i of %i\n", playernum, (int)(m_flFrame + 1), nFrames); Vector forward; AngleVectors( GetAbsAngles(), &forward ); UTIL_TraceLine ( GetAbsOrigin(), GetAbsOrigin() + forward * 128, MASK_SOLID_BRUSHONLY, pPlayer, COLLISION_GROUP_NONE, & tr); UTIL_PlayerDecalTrace( &tr, playernum ); // Just painted last custom frame. UTIL_Remove( this ); } class CBloodSplat : public CPointEntity { public: DECLARE_CLASS( CBloodSplat, CPointEntity ); void Spawn ( CBaseEntity *pOwner ); void Think ( void ); }; void CBloodSplat::Spawn ( CBaseEntity *pOwner ) { SetLocalOrigin( pOwner->WorldSpaceCenter() + Vector ( 0 , 0 , 32 ) ); SetLocalAngles( pOwner->GetLocalAngles() ); SetOwnerEntity( pOwner ); SetNextThink( gpGlobals->curtime + 0.1f ); } void CBloodSplat::Think( void ) { trace_t tr; if ( g_Language.GetInt() != LANGUAGE_GERMAN ) { CBasePlayer *pPlayer; pPlayer = ToBasePlayer( GetOwnerEntity() ); Vector forward; AngleVectors( GetAbsAngles(), &forward ); UTIL_TraceLine ( GetAbsOrigin(), GetAbsOrigin() + forward * 128, MASK_SOLID_BRUSHONLY, pPlayer, COLLISION_GROUP_NONE, & tr); UTIL_BloodDecalTrace( &tr, BLOOD_COLOR_RED ); } UTIL_Remove( this ); } //============================================== //----------------------------------------------------------------------------- // Purpose: Create and give the named item to the player. Then return it. //----------------------------------------------------------------------------- CBaseEntity *CBasePlayer::GiveNamedItem( const char *pszName, int iSubType ) { // If I already own this type don't create one if ( Weapon_OwnsThisType(pszName, iSubType) ) return NULL; // Msg( "giving %s\n", pszName ); EHANDLE pent; pent = CreateEntityByName(pszName); if ( pent == NULL ) { Msg( "NULL Ent in GiveNamedItem!\n" ); return NULL; } pent->SetLocalOrigin( GetLocalOrigin() ); pent->AddSpawnFlags( SF_NORESPAWN ); if ( iSubType ) { CBaseCombatWeapon *pWeapon = dynamic_cast( (CBaseEntity*)pent ); if ( pWeapon ) { pWeapon->SetSubType( iSubType ); } } DispatchSpawn( pent ); if ( pent != NULL && !(pent->IsMarkedForDeletion()) ) { pent->Touch( this ); } return pent; } //----------------------------------------------------------------------------- // Purpose: Returns the nearest COLLIBALE entity in front of the player // that has a clear line of sight with the given classname // Input : // Output : //----------------------------------------------------------------------------- CBaseEntity *FindEntityClassForward( CBasePlayer *pMe, char *classname ) { trace_t tr; Vector forward; pMe->EyeVectors( &forward ); UTIL_TraceLine(pMe->EyePosition(), pMe->EyePosition() + forward * MAX_COORD_RANGE, MASK_SOLID, pMe, COLLISION_GROUP_NONE, &tr ); if ( tr.fraction != 1.0 && tr.DidHitNonWorldEntity() ) { CBaseEntity *pHit = tr.m_pEnt; if (FClassnameIs( pHit,classname ) ) { return pHit; } } return NULL; } //----------------------------------------------------------------------------- // Purpose: Returns the nearest COLLIBALE entity in front of the player // that has a clear line of sight. If HULL is true, the trace will // hit the collision hull of entities. Otherwise, the trace will hit // hitboxes. // Input : // Output : //----------------------------------------------------------------------------- CBaseEntity *FindEntityForward( CBasePlayer *pMe, bool fHull ) { if ( pMe ) { trace_t tr; Vector forward; int mask; if( fHull ) { mask = MASK_SOLID; } else { mask = MASK_SHOT; } pMe->EyeVectors( &forward ); UTIL_TraceLine(pMe->EyePosition(), pMe->EyePosition() + forward * MAX_COORD_RANGE, mask, pMe, COLLISION_GROUP_NONE, &tr ); if ( tr.fraction != 1.0 && tr.DidHitNonWorldEntity() ) { return tr.m_pEnt; } } return NULL; } //----------------------------------------------------------------------------- // Purpose: Finds the nearest entity in front of the player of the given // classname, preferring collidable entities, but allows selection of // enities that are on the other side of walls or objects // // Input : // Output : //----------------------------------------------------------------------------- CBaseEntity *FindPickerEntityClass( CBasePlayer *pPlayer, char *classname ) { // First try to trace a hull to an entity CBaseEntity *pEntity = FindEntityClassForward( pPlayer, classname ); // If that fails just look for the nearest facing entity if (!pEntity) { Vector forward; Vector origin; pPlayer->EyeVectors( &forward ); origin = pPlayer->WorldSpaceCenter(); pEntity = gEntList.FindEntityClassNearestFacing( origin, forward,0.95,classname); } return pEntity; } //----------------------------------------------------------------------------- // Purpose: Finds the nearest entity in front of the player, preferring // collidable entities, but allows selection of enities that are // on the other side of walls or objects // Input : // Output : //----------------------------------------------------------------------------- CBaseEntity *FindPickerEntity( CBasePlayer *pPlayer ) { // First try to trace a hull to an entity CBaseEntity *pEntity = FindEntityForward( pPlayer, true ); // If that fails just look for the nearest facing entity if (!pEntity) { Vector forward; Vector origin; pPlayer->EyeVectors( &forward ); origin = pPlayer->WorldSpaceCenter(); pEntity = gEntList.FindEntityNearestFacing( origin, forward,0.95); } return pEntity; } //----------------------------------------------------------------------------- // Purpose: Finds the nearest node in front of the player // Input : // Output : //----------------------------------------------------------------------------- CAI_Node *FindPickerAINode( CBasePlayer *pPlayer, NodeType_e nNodeType ) { Vector forward; Vector origin; pPlayer->EyeVectors( &forward ); origin = pPlayer->EyePosition(); return g_pAINetworkManager->GetEditOps()->FindAINodeNearestFacing( origin, forward,0.90, nNodeType); } //----------------------------------------------------------------------------- // Purpose: Finds the nearest link in front of the player // Input : // Output : //----------------------------------------------------------------------------- CAI_Link *FindPickerAILink( CBasePlayer* pPlayer ) { Vector forward; Vector origin; pPlayer->EyeVectors( &forward ); origin = pPlayer->EyePosition(); return g_pAINetworkManager->GetEditOps()->FindAILinkNearestFacing( origin, forward,0.90); } /* =============== ForceClientDllUpdate When recording a demo, we need to have the server tell us the entire client state so that the client side .dll can behave correctly. Reset stuff so that the state is transmitted. =============== */ void CBasePlayer::ForceClientDllUpdate( void ) { m_iClientBattery = -1; m_iTrain |= TRAIN_NEW; // Force new train message. m_fWeapon = false; // Force weapon send // Force all HUD data to be resent to client m_fInitHUD = true; // Now force all the necessary messages // to be sent. UpdateClientData(); UTIL_RestartAmbientSounds(); // MOTODO that updates the sounds for everybody } /* ============ ImpulseCommands ============ */ void CBasePlayer::ImpulseCommands( ) { trace_t tr; int iImpulse = (int)m_nImpulse; switch (iImpulse) { case 100: // temporary flashlight for level designers if ( FlashlightIsOn() ) { FlashlightTurnOff(); } else { FlashlightTurnOn(); } break; case 200: if ( sv_cheats->GetBool() ) { CBaseCombatWeapon *pWeapon; pWeapon = GetActiveWeapon(); if( pWeapon->IsEffectActive( EF_NODRAW ) ) { pWeapon->Deploy(); } else { pWeapon->Holster(); } } break; case 201:// paint decal if ( gpGlobals->curtime < m_flNextDecalTime ) { // too early! break; } { Vector forward; EyeVectors( &forward ); UTIL_TraceLine ( EyePosition(), EyePosition() + forward * 128, MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, & tr); } if ( tr.fraction != 1.0 ) {// line hit something, so paint a decal m_flNextDecalTime = gpGlobals->curtime + decalfrequency.GetFloat(); CSprayCan *pCan = CREATE_UNSAVED_ENTITY( CSprayCan, "spraycan" ); pCan->Spawn( this ); } break; case 202:// player jungle sound if ( gpGlobals->curtime < m_flNextDecalTime ) { // too early! break; } EntityMessageBegin( this ); WRITE_BYTE( PLAY_PLAYER_JINGLE ); MessageEnd(); m_flNextDecalTime = gpGlobals->curtime + decalfrequency.GetFloat(); break; default: // check all of the cheat impulse commands now CheatImpulseCommands( iImpulse ); break; } m_nImpulse = 0; } //========================================================= //========================================================= void CBasePlayer::CheatImpulseCommands( int iImpulse ) { #if !defined( HLDEMO_BUILD ) if ( !sv_cheats->GetBool() ) { return; } CBaseEntity *pEntity; trace_t tr; switch ( iImpulse ) { case 76: { if (!giPrecacheGrunt) { giPrecacheGrunt = 1; Msg( "You must now restart to use Grunt-o-matic.\n"); } else { Vector forward = UTIL_YawToVector( EyeAngles().y ); Create("NPC_human_grunt", GetLocalOrigin() + forward * 128, GetLocalAngles()); } break; } case 81: GiveNamedItem( "weapon_cubemap" ); break; case 102: // Gibbage!!! CGib::SpawnRandomGibs( this, 1, GIB_HUMAN ); break; case 103: // What the hell are you doing? pEntity = FindEntityForward( this, true ); if ( pEntity ) { CAI_BaseNPC *pNPC = pEntity->MyNPCPointer(); if ( pNPC ) pNPC->ReportAIState(); } break; case 106: // Give me the classname and targetname of this entity. pEntity = FindEntityForward( this, true ); if ( pEntity ) { Msg( "Classname: %s", pEntity->GetClassname() ); if ( pEntity->GetEntityName() != NULL_STRING ) { Msg( " - Name: %s\n", STRING( pEntity->GetEntityName() ) ); } else { Msg( " - Name: No Targetname\n" ); } if ( pEntity->m_iParent != NULL_STRING ) Msg( "Parent: %s\n", STRING(pEntity->m_iParent) ); Msg( "Model: %s\n", STRING( pEntity->GetModelName() ) ); if ( pEntity->m_iGlobalname != NULL_STRING ) Msg( "Globalname: %s\n", STRING(pEntity->m_iGlobalname) ); } break; case 107: { trace_t tr; edict_t *pWorld = engine->PEntityOfEntIndex( 0 ); Vector start = EyePosition(); Vector forward; EyeVectors( &forward ); Vector end = start + forward * 1024; UTIL_TraceLine( start, end, MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr ); if ( tr.m_pEnt ) pWorld = tr.m_pEnt->edict(); const char *pTextureName = tr.surface.name; if ( pTextureName ) Msg( "Texture: %s\n", pTextureName ); } break; // // Sets the debug NPC to be the NPC under the crosshair. // case 108: { pEntity = FindEntityForward( this, true ); if ( pEntity ) { CAI_BaseNPC *pNPC = pEntity->MyNPCPointer(); if ( pNPC != NULL ) { Msg( "Debugging %s (0x%x)\n", pNPC->GetClassname(), pNPC ); CAI_BaseNPC::SetDebugNPC( pNPC ); } } break; } case 195:// show shortest paths for entire level to nearest node { Create("node_viewer_fly", GetLocalOrigin(), GetLocalAngles()); } break; case 196:// show shortest paths for entire level to nearest node { Create("node_viewer_large", GetLocalOrigin(), GetLocalAngles()); } break; case 197:// show shortest paths for entire level to nearest node { Create("node_viewer_human", GetLocalOrigin(), GetLocalAngles()); } break; case 202:// Random blood splatter { Vector forward; EyeVectors( &forward ); UTIL_TraceLine ( EyePosition(), EyePosition() + forward * 128, MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, & tr); if ( tr.fraction != 1.0 ) {// line hit something, so paint a decal CBloodSplat *pBlood = CREATE_UNSAVED_ENTITY( CBloodSplat, "bloodsplat" ); pBlood->Spawn( this ); } } break; case 203:// remove creature. pEntity = FindEntityForward( this, true ); if ( pEntity ) { UTIL_Remove( pEntity ); // if ( pEntity->m_takedamage ) // pEntity->SetThink(SUB_Remove); } break; } #endif // HLDEMO_BUILD } bool CBasePlayer::ClientCommand(const char *cmd) { #ifdef _DEBUG if( stricmp( cmd, "test_SmokeGrenade" ) == 0 ) { if ( sv_cheats && sv_cheats->GetBool() ) { ParticleSmokeGrenade *pSmoke = dynamic_cast( CreateEntityByName(PARTICLESMOKEGRENADE_ENTITYNAME) ); if ( pSmoke ) { Vector vForward; AngleVectors( GetLocalAngles(), &vForward ); vForward.z = 0; VectorNormalize( vForward ); pSmoke->SetLocalOrigin( GetLocalOrigin() + vForward * 100 ); pSmoke->SetFadeTime(25, 30); // Fade out between 25 seconds and 30 seconds. pSmoke->Activate(); pSmoke->SetLifetime(30); pSmoke->FillVolume(); return true; } } } else #endif // _DEBUG if( stricmp( cmd, "vehicleRole" ) == 0 ) { // Get the vehicle role value. if ( engine->Cmd_Argc() == 2 ) { // Check to see if a player is in a vehicle. if ( IsInAVehicle() ) { int nRole = atoi( engine->Cmd_Argv( 1 ) ); IServerVehicle *pVehicle = GetVehicle(); if ( pVehicle ) { // Only switch roles if role is empty! if ( !pVehicle->GetPassenger( nRole ) ) { LeaveVehicle(); GetInVehicle( pVehicle, nRole ); } } } return true; } } else if ( stricmp( cmd, "spectate" ) == 0 ) // join spectator team & start observer mode { if ( GetTeamNumber() == TEAM_SPECTATOR ) return true; if ( !IsDead() ) { ClientKill( edict() ); // kill player // add 1 to frags to balance out the 1 subtracted for killing yourself IncrementFragCount( 1 ); } RemoveAllItems( true ); ChangeTeam( TEAM_SPECTATOR ); StartObserverMode( OBS_MODE_ROAMING ); return true; } else if ( stricmp( cmd, "spec_mode" ) == 0 ) // new observer mode { int mode; // check for parameters. if ( engine->Cmd_Argc() >= 2 ) { mode = atoi( engine->Cmd_Argv(1) ); if ( mode < OBS_MODE_IN_EYE || mode > OBS_MODE_ROAMING ) mode = OBS_MODE_IN_EYE; } else { // sitch to next spec mode if no parameter give mode = GetObserverMode() + 1; if ( mode > OBS_MODE_ROAMING ) { mode = OBS_MODE_IN_EYE; } else if ( mode < OBS_MODE_IN_EYE ) { mode = OBS_MODE_ROAMING; } } // don't allow input while player or death cam animation if ( GetObserverMode() > OBS_MODE_DEATHCAM ) { // set new spectator mode, don't allow OBS_MODE_NONE if ( !SetObserverMode( mode ) ) ClientPrint( this, HUD_PRINTCONSOLE, "#Spectator_Mode_Unkown"); else engine->ClientCommand( edict(), "cl_spec_mode %d", mode ); } else { // remember spectator mode for later use m_iObserverLastMode = mode; engine->ClientCommand( edict(), "cl_spec_mode %d", mode ); } return true; } else if ( stricmp( cmd, "spec_next" ) == 0 ) // chase next player { if ( GetObserverMode() > OBS_MODE_FIXED ) { // set new spectator mode CBaseEntity * target = FindNextObserverTarget( false ); if ( target ) SetObserverTarget( target ); } return true; } else if ( stricmp( cmd, "spec_prev" ) == 0 ) // chase prevoius player { if ( GetObserverMode() > OBS_MODE_FIXED ) { // set new spectator mode CBaseEntity * target = FindNextObserverTarget( true ); if ( target ) SetObserverTarget( target ); } return true; } else if ( stricmp( cmd, "spec_player" ) == 0 ) // chase next player { if ( GetObserverMode() > OBS_MODE_FIXED && engine->Cmd_Argc() == 2 ) { int index = atoi( engine->Cmd_Argv(1) ); CBasePlayer * target; if ( index == 0 ) { target = UTIL_PlayerByName( engine->Cmd_Argv(1) ); } else { target = UTIL_PlayerByIndex( index ); } if ( IsValidObserverTarget( target ) ) { SetObserverTarget( target ); } } return true; } else if ( stricmp( cmd, "spec_goto" ) == 0 ) // chase next player { if ( ( GetObserverMode() == OBS_MODE_FIXED || GetObserverMode() == OBS_MODE_ROAMING ) && engine->Cmd_Argc() == 6 ) { Vector origin; origin.x = atof( engine->Cmd_Argv(1) ); origin.y = atof( engine->Cmd_Argv(2) ); origin.z = atof( engine->Cmd_Argv(3) ); QAngle angle; angle.x = atof( engine->Cmd_Argv(4) ); angle.y = atof( engine->Cmd_Argv(5) ); angle.z = 0.0f; JumptoPosition( origin, angle ); } return true; } return false; } //----------------------------------------------------------------------------- // 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 CBasePlayer::BumpWeapon( CBaseCombatWeapon *pWeapon ) { CBaseCombatCharacter *pOwner = pWeapon->GetOwner(); // Can I have this weapon type? if ( IsEFlagSet( EFL_NO_WEAPON_PICKUP ) ) 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; } // ---------------------------------------- // If I already have it just take the ammo // ---------------------------------------- if (Weapon_OwnsThisType( pWeapon->GetClassname(), pWeapon->GetSubType())) { if( Weapon_EquipAmmoOnly( pWeapon ) ) { // Only remove me if I have no ammo left if ( pWeapon->HasPrimaryAmmo() ) return false; UTIL_Remove( pWeapon ); return true; } else { return false; } } // ------------------------- // Otherwise take the weapon // ------------------------- else { pWeapon->CheckRespawn(); pWeapon->AddSolidFlags( FSOLID_NOT_SOLID ); pWeapon->AddEffects( EF_NODRAW ); Weapon_Equip( pWeapon ); if ( IsInAVehicle() ) { pWeapon->Holster(); } else { #ifdef HL2_DLL // Always switch to a newly-picked up weapon if ( !PlayerHasMegaPhysCannon() ) { // If it uses clips, load it full. (this is the first time you've picked up this type of weapon) if ( pWeapon->UsesClipsForAmmo1() ) { pWeapon->m_iClip1 = pWeapon->GetMaxClip1(); } Weapon_Switch( pWeapon ); } #endif } return true; } } bool CBasePlayer::RemovePlayerItem( CBaseCombatWeapon *pItem ) { if (GetActiveWeapon() == pItem) { ResetAutoaim( ); pItem->Holster( ); pItem->SetNextThink( TICK_NEVER_THINK );; // crowbar may be trying to swing again, etc pItem->SetThink( NULL ); } if ( m_hLastWeapon.Get() == pItem ) { Weapon_SetLast( NULL ); } return Weapon_Detach( pItem ); } //----------------------------------------------------------------------------- // Purpose: Hides or shows the player's view model. The "r_drawviewmodel" cvar // can still hide the viewmodel even if this is set to true. // Input : bShow - true to show, false to hide the view model. //----------------------------------------------------------------------------- void CBasePlayer::ShowViewModel(bool bShow) { m_Local.m_bDrawViewmodel = bShow ? 1 : 0; } //----------------------------------------------------------------------------- // Purpose: // Input : bDraw - //----------------------------------------------------------------------------- void CBasePlayer::ShowCrosshair( bool bShow ) { if ( bShow ) { m_Local.m_iHideHUD &= ~HIDEHUD_CROSSHAIR; } else { m_Local.m_iHideHUD |= HIDEHUD_CROSSHAIR; } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- QAngle CBasePlayer::BodyAngles() { return EyeAngles(); } //------------------------------------------------------------------------------ // Purpose : Add noise to BodyTarget() to give enemy a better chance of // getting a clear shot when the player is peeking above a hole // or behind a ladder (eventually the randomly-picked point // along the spine will be one that is exposed above the hole or // between rungs of a ladder.) // Input : // Output : //------------------------------------------------------------------------------ Vector CBasePlayer::BodyTarget( const Vector &posSrc, bool bNoisy ) { if ( IsInAVehicle() ) { return GetVehicle()->GetVehicleEnt()->BodyTarget( posSrc, bNoisy ); } if (bNoisy) { return GetAbsOrigin() + (GetViewOffset() * random->RandomFloat( 0.7, 1.0 )); } else { return EyePosition(); } }; /* ========================================================= UpdateClientData resends any changed player HUD info to the client. Called every frame by PlayerPreThink Also called at start of demo recording and playback by ForceClientDllUpdate to ensure the demo gets messages reflecting all of the HUD state info. ========================================================= */ void CBasePlayer::UpdateClientData( void ) { CSingleUserRecipientFilter user( this ); user.MakeReliable(); if (m_fInitHUD) { m_fInitHUD = false; gInitHUD = false; UserMessageBegin( user, "ResetHUD" ); WRITE_BYTE( 0 ); MessageEnd(); if ( !m_fGameHUDInitialized ) { g_pGameRules->InitHUD( this ); InitHUD(); m_fGameHUDInitialized = true; if ( g_pGameRules->IsMultiplayer() ) { variant_t value; g_EventQueue.AddEvent( "game_player_manager", "OnPlayerJoin", value, 0, this, this ); } } variant_t value; g_EventQueue.AddEvent( "game_player_manager", "OnPlayerSpawn", value, 0, this, this ); } // HACKHACK -- send the message to display the game title CWorld *world = GetWorldEntity(); if ( world && world->GetDisplayTitle() ) { UserMessageBegin( user, "GameTitle" ); MessageEnd(); world->SetDisplayTitle( false ); } if (m_ArmorValue != m_iClientBattery) { m_iClientBattery = m_ArmorValue; // send "battery" update message if ( usermessages->LookupUserMessage( "Battery" ) != -1 ) { UserMessageBegin( user, "Battery" ); WRITE_SHORT( (int)m_ArmorValue); MessageEnd(); } } #if 0 // BYE BYE!! // Update Flashlight if ((m_flFlashLightTime) && (m_flFlashLightTime <= gpGlobals->curtime)) { if (FlashlightIsOn()) { if (m_iFlashBattery) { m_flFlashLightTime = FLASH_DRAIN_TIME + gpGlobals->curtime; m_iFlashBattery--; if (!m_iFlashBattery) FlashlightTurnOff(); } } else { if (m_iFlashBattery < 100) { m_flFlashLightTime = FLASH_CHARGE_TIME + gpGlobals->curtime; m_iFlashBattery++; } else m_flFlashLightTime = 0; } } #endif CheckTrainUpdate(); // Update all the items for ( int i = 0; i < WeaponCount(); i++ ) { if ( GetWeapon(i) ) // each item updates it's successors GetWeapon(i)->UpdateClientData( this ); } // update the client with our poison state m_Local.m_bPoisoned = ( m_bitsDamageType & DMG_POISON ) && ( m_nPoisonDmg > m_nPoisonRestored ) && ( m_iHealth < 100 ); // Let any global rules update the HUD, too g_pGameRules->UpdateClientData( this ); } void CBasePlayer::EnableControl(bool fControl) { if (!fControl) AddFlag( FL_FROZEN ); else RemoveFlag( FL_FROZEN ); } void CBasePlayer::CheckTrainUpdate( void ) { if (m_iTrain & TRAIN_NEW) { CSingleUserRecipientFilter user( this ); user.MakeReliable(); // send "Train" update message UserMessageBegin( user, "Train" ); WRITE_BYTE(m_iTrain & 0xF); MessageEnd(); m_iTrain &= ~TRAIN_NEW; } } //----------------------------------------------------------------------------- // Purpose: Returns whether the player should autoaim or not // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CBasePlayer::ShouldAutoaim( void ) { // cannot be in multiplayer if ( gpGlobals->maxClients > 1 ) return false; // autoaiming is only for easy and medium skill return !g_pGameRules->IsSkillLevel(SKILL_HARD); } //========================================================= // Autoaim // set crosshair position to point to enemey //========================================================= Vector CBasePlayer::GetAutoaimVector( float flDelta ) { if ( ( ShouldAutoaim() == false ) || ( flDelta == 0 ) ) { Vector forward; AngleVectors( EyeAngles() + m_Local.m_vecPunchAngle, &forward ); return forward; } Vector vecSrc = Weapon_ShootPosition( ); float flDist = MAX_COORD_RANGE; m_vecAutoAim.Init( 0.0f, 0.0f, 0.0f ); QAngle angles = AutoaimDeflection( vecSrc, flDist, flDelta ); // update ontarget if changed if ( !g_pGameRules->AllowAutoTargetCrosshair() ) m_fOnTarget = 0; if (angles.x > 180) angles.x -= 360; if (angles.x < -180) angles.x += 360; if (angles.y > 180) angles.y -= 360; if (angles.y < -180) angles.y += 360; if (angles.x > 25) angles.x = 25; if (angles.x < -25) angles.x = -25; if (angles.y > 12) angles.y = 12; if (angles.y < -12) angles.y = -12; // always use non-sticky autoaim m_vecAutoAim = angles * 0.9f; Vector forward; AngleVectors( EyeAngles() + m_Local.m_vecPunchAngle + m_vecAutoAim, &forward ); return forward; } //----------------------------------------------------------------------------- // Purpose: // Input : &vecSrc - // flDist - // flDelta - // Output : Vector //----------------------------------------------------------------------------- QAngle CBasePlayer::AutoaimDeflection( Vector &vecSrc, float flDist, float flDelta ) { float bestdot; Vector bestdir; CBaseEntity *bestent; trace_t tr; Vector v_forward, v_right, v_up; if ( ShouldAutoaim() == false ) { m_fOnTarget = false; return vec3_angle; } AngleVectors( EyeAngles() + m_Local.m_vecPunchAngle + m_vecAutoAim, &v_forward, &v_right, &v_up ); // try all possible entities bestdir = v_forward; bestdot = flDelta; // +- 10 degrees bestent = NULL; //Reset this data m_fOnTarget = false; m_hAutoAimTarget = NULL; UTIL_TraceLine( vecSrc, vecSrc + bestdir * flDist, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr ); CBaseEntity *pEntHit = tr.m_pEnt; if ( pEntHit && pEntHit->m_takedamage != DAMAGE_NO) { m_hAutoAimTarget = pEntHit; // don't look through water if (!((GetWaterLevel() != 3 && pEntHit->GetWaterLevel() == 3) || (GetWaterLevel() == 3 && pEntHit->GetWaterLevel() == 0))) { if ( pEntHit->GetFlags() & FL_AIMTARGET ) { m_fOnTarget = true; } //Already on target, don't autoaim return vec3_angle; } } int count = AimTarget_ListCount(); if ( count ) { CBaseEntity **pList = (CBaseEntity **)stackalloc( sizeof(CBaseEntity *) * count ); AimTarget_ListCopy( pList, count ); for ( int i = 0; i < count; i++ ) { Vector center; Vector dir; float dot; CBaseEntity *pEntity = pList[i]; // Don't shoot yourself if ( pEntity == this ) continue; if (!pEntity->IsAlive() || !pEntity->edict() ) continue; if ( !g_pGameRules->ShouldAutoAim( this, pEntity->edict() ) ) continue; // don't look through water if ((GetWaterLevel() != 3 && pEntity->GetWaterLevel() == 3) || (GetWaterLevel() == 3 && pEntity->GetWaterLevel() == 0)) continue; // Only shoot enemies! if ( IRelationType( pEntity ) != D_HT ) { if ( !pEntity->IsPlayer() && !g_pGameRules->IsDeathmatch()) // Msg( "friend\n"); continue; } center = pEntity->BodyTarget( vecSrc ); dir = (center - vecSrc); VectorNormalize( dir ); // make sure it's in front of the player if (DotProduct (dir, v_forward ) < 0) continue; dot = fabs( DotProduct (dir, v_right ) ) + fabs( DotProduct (dir, v_up ) ) * 0.5; // tweak for distance dot *= 1.0 + 0.2 * ((center - vecSrc).Length() / flDist); if (dot > bestdot) continue; // to far to turn UTIL_TraceLine( vecSrc, center, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr ); if (tr.fraction != 1.0 && tr.m_pEnt != pEntity ) { // Msg( "hit %s, can't see %s\n", STRING( tr.u.ent->classname ), STRING( pEdict->classname ) ); continue; } // can shoot at this one bestdot = dot; bestent = pEntity; bestdir = dir; } if ( bestent ) { QAngle bestang; VectorAngles( bestdir, bestang ); bestang -= EyeAngles() - m_Local.m_vecPunchAngle; m_hAutoAimTarget = bestent; m_fOnTarget = true; return bestang; } } return QAngle( 0, 0, 0 ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CBasePlayer::ResetAutoaim( void ) { if (m_vecAutoAim.x != 0 || m_vecAutoAim.y != 0) { m_vecAutoAim = QAngle( 0, 0, 0 ); engine->CrosshairAngle( edict(), 0, 0 ); } m_fOnTarget = false; } // ========================================================================== // > Weapon stuff // ========================================================================== //----------------------------------------------------------------------------- // Purpose: Override base class, player can always use weapon // Input : A weapon // Output : true or false //----------------------------------------------------------------------------- bool CBasePlayer::Weapon_CanUse( CBaseCombatWeapon *pWeapon ) { return true; } //----------------------------------------------------------------------------- // Purpose: Override to clear dropped weapon from the hud //----------------------------------------------------------------------------- void CBasePlayer::Weapon_Drop( CBaseCombatWeapon *pWeapon, const Vector *pvecTarget /* = NULL */, const Vector *pVelocity /* = NULL */ ) { bool bWasActiveWeapon = false; if ( pWeapon == GetActiveWeapon() ) { bWasActiveWeapon = true; } if ( pWeapon ) { if ( bWasActiveWeapon ) { pWeapon->SendWeaponAnim( ACT_VM_IDLE ); } } BaseClass::Weapon_Drop( pWeapon, pvecTarget, pVelocity ); if ( bWasActiveWeapon ) { if (!SwitchToNextBestWeapon( NULL )) { CBaseViewModel *vm = GetViewModel(); if ( vm ) { vm->AddEffects( EF_NODRAW ); } } } } //----------------------------------------------------------------------------- // Purpose: // Input : weaponSlot - //----------------------------------------------------------------------------- void CBasePlayer::Weapon_DropSlot( int weaponSlot ) { CBaseCombatWeapon *pWeapon; // Check for that slot being occupied already for ( int i=0; i < MAX_WEAPONS; i++ ) { pWeapon = GetWeapon( i ); if ( pWeapon != NULL ) { // If the slots match, it's already occupied if ( pWeapon->GetSlot() == weaponSlot ) { Weapon_Drop( pWeapon, NULL, NULL ); } } } } //----------------------------------------------------------------------------- // Purpose: Override to add weapon to the hud //----------------------------------------------------------------------------- void CBasePlayer::Weapon_Equip( CBaseCombatWeapon *pWeapon ) { BaseClass::Weapon_Equip( pWeapon ); // should we switch to this item? if ( g_pGameRules->FShouldSwitchWeapon( this, pWeapon ) ) { Weapon_Switch( pWeapon ); } } //========================================================= // HasNamedPlayerItem Does the player already have this item? //========================================================= CBaseEntity *CBasePlayer::HasNamedPlayerItem( const char *pszItemName ) { for ( int i = 0 ; i < WeaponCount() ; i++ ) { if ( !GetWeapon(i) ) continue; if ( FStrEq( pszItemName, GetWeapon(i)->GetClassname() ) ) { return GetWeapon(i); } } return NULL; } //================================================================================ // TEAM HANDLING //================================================================================ //----------------------------------------------------------------------------- // Purpose: Put the player in the specified team //----------------------------------------------------------------------------- void CBasePlayer::ChangeTeam( int iTeamNum ) { if ( !GetGlobalTeam( iTeamNum ) ) { Warning( "CBasePlayer::ChangeTeam( %d ) - invalid team index.\n", iTeamNum ); return; } // if this is our current team, just abort if ( iTeamNum == GetTeamNumber() ) { return; } // Immediately tell all clients that he's changing team. This has to be done // first, so that all user messages that follow as a result of the team change // come after this one, allowing the client to be prepared for them. IGameEvent * event = gameeventmanager->CreateEvent( "player_team" ); if ( event ) { event->SetInt("userid", GetUserID() ); event->SetInt("team", iTeamNum ); event->SetInt("oldteam", GetTeamNumber() ); event->SetInt("disconnect", IsDisconnecting()); gameeventmanager->FireEvent( event ); } // Remove him from his current team if ( GetTeam() ) { GetTeam()->RemovePlayer( this ); } // Are we being added to a team? if ( iTeamNum ) { GetGlobalTeam( iTeamNum )->AddPlayer( this ); } BaseClass::ChangeTeam( iTeamNum ); } //----------------------------------------------------------------------------- // Purpose: Locks a player to the spot; they can't move, shoot, or be hurt //----------------------------------------------------------------------------- void CBasePlayer::LockPlayerInPlace( void ) { if ( m_iPlayerLocked ) return; AddFlag( FL_GODMODE | FL_FROZEN ); SetMoveType( MOVETYPE_NONE ); m_iPlayerLocked = true; // force a client data update, so that anything that has been done to // this player previously this frame won't get delayed in being sent UpdateClientData(); } //----------------------------------------------------------------------------- // Purpose: Unlocks a previously locked player //----------------------------------------------------------------------------- void CBasePlayer::UnlockPlayer( void ) { if ( !m_iPlayerLocked ) return; RemoveFlag( FL_GODMODE | FL_FROZEN ); SetMoveType( MOVETYPE_WALK ); m_iPlayerLocked = false; } bool CBasePlayer::ClearUseEntity() { if ( m_hUseEntity != NULL ) { // Stop controlling the train/object // TODO: Send HUD Update m_hUseEntity->Use( this, this, USE_OFF, 0 ); m_hUseEntity = NULL; return true; } return false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CBasePlayer::HideViewModels( void ) { for ( int i = 0 ; i < MAX_VIEWMODELS; i++ ) { CBaseViewModel *vm = GetViewModel( i ); if ( !vm ) continue; vm->SetWeaponModel( NULL, NULL ); } } class CStripWeapons : public CPointEntity { DECLARE_CLASS( CStripWeapons, CPointEntity ); public: void InputStripWeapons(inputdata_t &data); void InputStripWeaponsAndSuit(inputdata_t &data); void StripWeapons(inputdata_t &data, bool stripSuit); DECLARE_DATADESC(); }; LINK_ENTITY_TO_CLASS( player_weaponstrip, CStripWeapons ); BEGIN_DATADESC( CStripWeapons ) DEFINE_INPUTFUNC( FIELD_VOID, "Strip", InputStripWeapons ), DEFINE_INPUTFUNC( FIELD_VOID, "StripWeaponsAndSuit", InputStripWeaponsAndSuit ), END_DATADESC() void CStripWeapons::InputStripWeapons(inputdata_t &data) { StripWeapons(data, false); } void CStripWeapons::InputStripWeaponsAndSuit(inputdata_t &data) { StripWeapons(data, true); } void CStripWeapons::StripWeapons(inputdata_t &data, bool stripSuit) { CBasePlayer *pPlayer = NULL; if ( data.pActivator && data.pActivator->IsPlayer() ) { pPlayer = (CBasePlayer *)data.pActivator; } else if ( !g_pGameRules->IsDeathmatch() ) { pPlayer = UTIL_GetLocalPlayer(); } if ( pPlayer ) { pPlayer->RemoveAllItems( stripSuit ); } } class CRevertSaved : public CPointEntity { DECLARE_CLASS( CRevertSaved, CPointEntity ); public: void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); void LoadThink( void ); DECLARE_DATADESC(); inline float Duration( void ) { return m_Duration; } inline float HoldTime( void ) { return m_HoldTime; } inline float LoadTime( void ) { return m_loadTime; } inline void SetDuration( float duration ) { m_Duration = duration; } inline void SetHoldTime( float hold ) { m_HoldTime = hold; } inline void SetLoadTime( float time ) { m_loadTime = time; } //Inputs void InputReload(inputdata_t &data); #ifdef HL1_DLL void MessageThink( void ); inline float MessageTime( void ) { return m_messageTime; } inline void SetMessageTime( float time ) { m_messageTime = time; } #endif private: float m_loadTime; float m_Duration; float m_HoldTime; #ifdef HL1_DLL string_t m_iszMessage; float m_messageTime; #endif }; LINK_ENTITY_TO_CLASS( player_loadsaved, CRevertSaved ); BEGIN_DATADESC( CRevertSaved ) #ifdef HL1_DLL DEFINE_KEYFIELD( m_iszMessage, FIELD_STRING, "message" ), DEFINE_KEYFIELD( m_messageTime, FIELD_FLOAT, "messagetime" ), // These are not actual times, but durations, so save as floats DEFINE_FUNCTION( MessageThink ), #endif DEFINE_KEYFIELD( m_loadTime, FIELD_FLOAT, "loadtime" ), DEFINE_KEYFIELD( m_Duration, FIELD_FLOAT, "duration" ), DEFINE_KEYFIELD( m_HoldTime, FIELD_FLOAT, "holdtime" ), DEFINE_INPUTFUNC( FIELD_VOID, "Reload", InputReload ), // Function Pointers DEFINE_FUNCTION( LoadThink ), END_DATADESC() void CRevertSaved::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) { UTIL_ScreenFadeAll( m_clrRender, Duration(), HoldTime(), FFADE_OUT ); SetNextThink( gpGlobals->curtime + LoadTime() ); SetThink( &CRevertSaved::LoadThink ); CBasePlayer *pPlayer = UTIL_GetLocalPlayer(); if ( pPlayer ) { //Adrian: Setting this flag so we can't move or save a game. pPlayer->pl.deadflag = true; pPlayer->AddFlag( FL_NOTARGET ); } } void CRevertSaved::InputReload( inputdata_t &inputdata ) { UTIL_ScreenFadeAll( m_clrRender, Duration(), HoldTime(), FFADE_OUT ); #ifdef HL1_DLL SetNextThink( gpGlobals->curtime + MessageTime() ); SetThink( &CRevertSaved::MessageThink ); #else SetNextThink( gpGlobals->curtime + LoadTime() ); SetThink( &CRevertSaved::LoadThink ); #endif CBasePlayer *pPlayer = UTIL_GetLocalPlayer(); if ( pPlayer ) { //Adrian: Setting this flag so we can't move or save a game. pPlayer->pl.deadflag = true; pPlayer->AddFlag( FL_NOTARGET ); } } #ifdef HL1_DLL void CRevertSaved::MessageThink( void ) { UTIL_ShowMessageAll( STRING( m_iszMessage ) ); float nextThink = LoadTime() - MessageTime(); if ( nextThink > 0 ) { SetNextThink( gpGlobals->curtime + nextThink ); SetThink( &CRevertSaved::LoadThink ); } else LoadThink(); } #endif void CRevertSaved::LoadThink( void ) { if ( !gpGlobals->deathmatch ) { engine->ServerCommand("reload\n"); } } class CMovementSpeedMod : public CPointEntity { DECLARE_CLASS( CMovementSpeedMod, CPointEntity ); public: void InputSpeedMod(inputdata_t &data); DECLARE_DATADESC(); }; LINK_ENTITY_TO_CLASS( player_speedmod, CMovementSpeedMod ); BEGIN_DATADESC( CMovementSpeedMod ) DEFINE_INPUTFUNC( FIELD_FLOAT, "ModifySpeed", InputSpeedMod ), END_DATADESC() void CMovementSpeedMod::InputSpeedMod(inputdata_t &data) { CBasePlayer *pPlayer = NULL; if ( data.pActivator && data.pActivator->IsPlayer() ) { pPlayer = (CBasePlayer *)data.pActivator; } else if ( !g_pGameRules->IsDeathmatch() ) { pPlayer = UTIL_GetLocalPlayer(); } if ( pPlayer ) { pPlayer->SetLaggedMovementValue( data.value.Float() ); } } void SendProxy_CropFlagsToPlayerFlagBitsLength( const SendProp *pProp, const void *pStruct, const void *pVarData, DVariant *pOut, int iElement, int objectID) { int mask = (1<m_Int = ( data & mask ); } // -------------------------------------------------------------------------------- // // SendTable for CPlayerState. // -------------------------------------------------------------------------------- // BEGIN_SEND_TABLE_NOBASE(CPlayerState, DT_PlayerState) SendPropInt (SENDINFO(deadflag), 1, SPROP_UNSIGNED ), END_SEND_TABLE() // -------------------------------------------------------------------------------- // // This data only gets sent to clients that ARE this player entity. // -------------------------------------------------------------------------------- // BEGIN_SEND_TABLE_NOBASE( CBasePlayer, DT_LocalPlayerExclusive ) SendPropDataTable ( SENDINFO_DT(m_Local), &REFERENCE_SEND_TABLE(DT_Local) ), SendPropFloat ( SENDINFO_VECTORELEM(m_vecViewOffset, 0), 8, 0, -32.0, 32.0f), SendPropFloat ( SENDINFO_VECTORELEM(m_vecViewOffset, 1), 8, 0, -32.0, 32.0f), SendPropFloat ( SENDINFO_VECTORELEM(m_vecViewOffset, 2), 10, SPROP_CHANGES_OFTEN, 0.0f, 128.0f), SendPropFloat ( SENDINFO(m_flFriction), 8, SPROP_ROUNDDOWN, 0.0f, 4.0f), SendPropArray3 ( SENDINFO_ARRAY3(m_iAmmo), SendPropInt( SENDINFO_ARRAY(m_iAmmo), 10, SPROP_UNSIGNED ) ), SendPropInt ( SENDINFO( m_fOnTarget ), 2, SPROP_UNSIGNED ), SendPropInt ( SENDINFO( m_nTickBase ), -1, SPROP_CHANGES_OFTEN ), SendPropInt ( SENDINFO( m_nNextThinkTick ) ), SendPropEHandle ( SENDINFO( m_hLastWeapon ) ), SendPropEHandle ( SENDINFO( m_hGroundEntity ), SPROP_CHANGES_OFTEN ), SendPropFloat ( SENDINFO_VECTORELEM(m_vecVelocity, 0), 20, SPROP_CHANGES_OFTEN, -2048.0f, 2048.0f ), SendPropFloat ( SENDINFO_VECTORELEM(m_vecVelocity, 1), 20, SPROP_CHANGES_OFTEN, -2048.0f, 2048.0f ), SendPropFloat ( SENDINFO_VECTORELEM(m_vecVelocity, 2), 16, SPROP_CHANGES_OFTEN, -2048.0f, 2048.0f ), SendPropVector ( SENDINFO( m_vecBaseVelocity ), 20, 0, -1000, 1000 ), SendPropArray ( SendPropEHandle( SENDINFO_ARRAY( m_hViewModel ) ), m_hViewModel ), SendPropEHandle ( SENDINFO( m_hConstraintEntity)), SendPropVector ( SENDINFO( m_vecConstraintCenter), 0, SPROP_NOSCALE ), SendPropFloat ( SENDINFO( m_flConstraintRadius ), 0, SPROP_NOSCALE ), SendPropFloat ( SENDINFO( m_flConstraintWidth ), 0, SPROP_NOSCALE ), SendPropFloat ( SENDINFO( m_flConstraintSpeedFactor ), 0, SPROP_NOSCALE ), SendPropFloat ( SENDINFO( m_flDeathTime ), 0, SPROP_NOSCALE ), SendPropInt ( SENDINFO( m_nWaterLevel ), 2, SPROP_UNSIGNED ), SendPropFloat ( SENDINFO( m_flLaggedMovementValue ), 0, SPROP_NOSCALE ), END_SEND_TABLE() // -------------------------------------------------------------------------------- // // DT_BasePlayer sendtable. // -------------------------------------------------------------------------------- // IMPLEMENT_SERVERCLASS_ST( CBasePlayer, DT_BasePlayer ) SendPropDataTable(SENDINFO_DT(pl), &REFERENCE_SEND_TABLE(DT_PlayerState), SendProxy_DataTableToDataTable), SendPropEHandle(SENDINFO(m_hVehicle)), SendPropInt (SENDINFO(m_MoveType),MOVETYPE_MAX_BITS, SPROP_UNSIGNED ), SendPropInt (SENDINFO(m_iHealth), 10 ), SendPropInt (SENDINFO(m_lifeState), 3, SPROP_UNSIGNED ), SendPropFloat (SENDINFO(m_flMaxspeed), 12, SPROP_ROUNDDOWN, 0.0f, 2048.0f ), // CL SendPropInt (SENDINFO(m_fFlags), PLAYER_FLAG_BITS, SPROP_UNSIGNED|SPROP_CHANGES_OFTEN, SendProxy_CropFlagsToPlayerFlagBitsLength ), SendPropInt (SENDINFO(m_iObserverMode), 3, SPROP_UNSIGNED ), SendPropEHandle (SENDINFO(m_hObserverTarget) ), SendPropArray ( SendPropEHandle( SENDINFO_ARRAY( m_hObserverViewModel ) ), m_hObserverViewModel ), SendPropString (SENDINFO(m_szLastPlaceName) ), // Data that only gets sent to the local player. SendPropDataTable( "localdata", 0, &REFERENCE_SEND_TABLE(DT_LocalPlayerExclusive), SendProxy_SendLocalDataTable ), END_SEND_TABLE() //============================================================================= // // Player Physics Shadow Code // void CBasePlayer::SetupVPhysicsShadow( CPhysCollide *pStandModel, const char *pStandHullName, CPhysCollide *pCrouchModel, const char *pCrouchHullName ) { solid_t solid; Q_strncpy( solid.surfaceprop, "player", sizeof(solid.surfaceprop) ); solid.params = g_PhysDefaultObjectParams; solid.params.mass = 85.0f; solid.params.inertia = 1e24f; solid.params.enableCollisions = false; //disable drag solid.params.dragCoefficient = 0; // create standing hull m_pShadowStand = PhysModelCreateCustom( this, pStandModel, GetLocalOrigin(), GetLocalAngles(), pStandHullName, false, &solid ); m_pShadowStand->SetCallbackFlags( CALLBACK_GLOBAL_COLLISION | CALLBACK_SHADOW_COLLISION ); // create crouchig hull m_pShadowCrouch = PhysModelCreateCustom( this, pCrouchModel, GetLocalOrigin(), GetLocalAngles(), pCrouchHullName, false, &solid ); m_pShadowCrouch->SetCallbackFlags( CALLBACK_GLOBAL_COLLISION | CALLBACK_SHADOW_COLLISION ); // default to stand VPhysicsSetObject( m_pShadowStand ); // tell physics lists I'm a shadow controller object PhysAddShadow( this ); m_pPhysicsController = physenv->CreatePlayerController( m_pShadowStand ); m_pPhysicsController->SetPushMassLimit( 350.0f ); m_pPhysicsController->SetPushSpeedLimit( 50.0f ); // Give the controller a valid position so it doesn't do anything rash. UpdatePhysicsShadowToCurrentPosition(); // init state if ( GetFlags() & FL_DUCKING ) { SetVCollisionState( VPHYS_CROUCH ); } else { SetVCollisionState( VPHYS_WALK ); } } //----------------------------------------------------------------------------- // Purpose: Empty, just want to keep the baseentity version from being called // current so we don't kick up dust, etc. //----------------------------------------------------------------------------- void CBasePlayer::VPhysicsCollision( int index, gamevcollisionevent_t *pEvent ) { } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CBasePlayer::VPhysicsUpdate( IPhysicsObject *pPhysics ) { float savedImpact = m_impactEnergyScale; // HACKHACK: Reduce player's stress by 1/8th m_impactEnergyScale *= 0.125f; ApplyStressDamage( pPhysics ); m_impactEnergyScale = savedImpact; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CBasePlayer::VPhysicsShadowUpdate( IPhysicsObject *pPhysics ) { if ( sv_turbophysics.GetBool() ) return; Vector newPosition; bool physicsUpdated = m_pPhysicsController->GetShadowPosition( &newPosition, NULL ) > 0 ? true : false; // UNDONE: If the player is penetrating, but the player's game collisions are not stuck, teleport the physics shadow to the game position if ( pPhysics->GetGameFlags() & FVPHYSICS_PENETRATING ) { CUtlVector list; PhysGetListOfPenetratingEntities( this, list ); for ( int i = list.Count()-1; i >= 0; --i ) { // filter out anything that isn't simulated by vphysics // UNDONE: Filter out motion disabled objects? if ( list[i]->GetMoveType() == MOVETYPE_VPHYSICS ) { // I'm currently stuck inside a moving object, so allow vphysics to // apply velocity to the player in order to separate these objects m_touchedPhysObject = true; } } } if ( m_pPhysicsController->IsInContact() || (m_afPhysicsFlags & PFLAG_VPHYSICS_MOTIONCONTROLLER) ) { m_touchedPhysObject = true; } if ( IsFollowingPhysics() ) { m_touchedPhysObject = true; } if ( GetMoveType() == MOVETYPE_NOCLIP ) { m_oldOrigin = GetAbsOrigin(); return; } if ( phys_timescale.GetFloat() == 0.0f ) { physicsUpdated = false; } if ( !physicsUpdated ) return; IPhysicsObject *pPhysGround = GetGroundVPhysics(); Vector newVelocity; pPhysics->GetPosition( &newPosition, 0 ); m_pPhysicsController->GetShadowVelocity( &newVelocity ); if ( physicsshadowupdate_render.GetBool() ) { NDebugOverlay::Box( GetAbsOrigin(), WorldAlignMins(), WorldAlignMaxs(), 255, 0, 0, 24, 15.0f ); NDebugOverlay::Box( newPosition, WorldAlignMins(), WorldAlignMaxs(), 0,0,255, 24, 15.0f); // NDebugOverlay::Box( newPosition, WorldAlignMins(), WorldAlignMaxs(), 0,0,255, 24, .01f); } Vector tmp = GetAbsOrigin() - newPosition; if ( !m_touchedPhysObject && !(GetFlags() & FL_ONGROUND) ) { tmp.z *= 0.5f; // don't care about z delta as much } float dist = tmp.LengthSqr(); float deltaV = (newVelocity - GetAbsVelocity()).LengthSqr(); float maxDistErrorSqr = VPHYS_MAX_DISTSQR; float maxVelErrorSqr = VPHYS_MAX_VELSQR; if ( IsRideablePhysics(pPhysGround) ) { maxDistErrorSqr *= 0.25; maxVelErrorSqr *= 0.25; } if ( dist >= maxDistErrorSqr || deltaV >= maxVelErrorSqr || (pPhysGround && !m_touchedPhysObject) ) { if ( m_touchedPhysObject || pPhysGround ) { // BUGBUG: Rewrite this code using fixed timestep if ( deltaV >= maxVelErrorSqr ) { Vector dir = GetAbsVelocity(); float len = VectorNormalize(dir); float dot = DotProduct( newVelocity, dir ); if ( dot > len ) { dot = len; } else if ( dot < -len ) { dot = -len; } VectorMA( newVelocity, -dot, dir, newVelocity ); if ( m_afPhysicsFlags & PFLAG_VPHYSICS_MOTIONCONTROLLER ) { float val = Lerp( 0.1f, len, dot ); VectorMA( newVelocity, val - len, dir, newVelocity ); } if ( !IsRideablePhysics(pPhysGround) ) { if ( !(m_afPhysicsFlags & PFLAG_VPHYSICS_MOTIONCONTROLLER ) && IsSimulatingOnAlternateTicks() ) { newVelocity *= 0.5f; } ApplyAbsVelocityImpulse( newVelocity ); } } trace_t trace; UTIL_TraceEntity( this, newPosition, newPosition, MASK_PLAYERSOLID, this, COLLISION_GROUP_PLAYER_MOVEMENT, &trace ); if ( !trace.allsolid && !trace.startsolid ) { SetAbsOrigin( newPosition ); } } else { trace_t trace; UTIL_TraceEntity( this, GetAbsOrigin(), GetAbsOrigin(), MASK_PLAYERSOLID, this, COLLISION_GROUP_PLAYER_MOVEMENT, &trace ); // current position is not ok, fixup if ( trace.allsolid || trace.startsolid ) { // STUCK!?!?! //Warning( "Stuck2 on %s!!\n", trace.m_pEnt->GetClassname() ); SetAbsOrigin( newPosition ); } } } else { if ( m_touchedPhysObject ) { // check my position (physics object could have simulated into my position // physics is not very far away, check my position trace_t trace; UTIL_TraceEntity( this, GetAbsOrigin(), GetAbsOrigin(), MASK_PLAYERSOLID, this, COLLISION_GROUP_PLAYER_MOVEMENT, &trace ); // is current position ok? if ( trace.allsolid || trace.startsolid ) { // stuck????!?!? //Msg("Stuck on %s\n", trace.m_pEnt->GetClassName()); SetAbsOrigin( newPosition ); UTIL_TraceEntity( this, GetAbsOrigin(), GetAbsOrigin(), MASK_PLAYERSOLID, this, COLLISION_GROUP_PLAYER_MOVEMENT, &trace ); if ( trace.allsolid || trace.startsolid ) { //Msg("Double Stuck\n"); SetAbsOrigin( m_oldOrigin ); } } } } m_oldOrigin = GetAbsOrigin(); // UNDONE: Force physics object to be at player position when not touching phys??? } // recreate physics on save/load, don't try to save the state! bool CBasePlayer::ShouldSavePhysics() { return false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CBasePlayer::InitVCollision( void ) { // Cleanup any old vphysics stuff. VPhysicsDestroyObject(); // in turbo physics players dont have a physics shadow if ( sv_turbophysics.GetBool() ) return; CPhysCollide *pModel = PhysCreateBbox( WorldAlignMins(), WorldAlignMaxs() ); CPhysCollide *pCrouchModel = PhysCreateBbox( VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX ); SetupVPhysicsShadow( pModel, "player_stand", pCrouchModel, "player_crouch" ); } void CBasePlayer::VPhysicsDestroyObject() { // Since CBasePlayer aliases its pointer to the physics object, tell CBaseEntity to // clear out its physics object pointer so we don't wind up deleting one of // the aliased objects twice. VPhysicsSetObject( NULL ); PhysRemoveShadow( this ); if ( m_pPhysicsController ) { physenv->DestroyPlayerController( m_pPhysicsController ); m_pPhysicsController = NULL; } if ( m_pShadowStand ) { PhysDestroyObject( m_pShadowStand ); m_pShadowStand = NULL; } if ( m_pShadowCrouch ) { PhysDestroyObject( m_pShadowCrouch ); m_pShadowCrouch = NULL; } BaseClass::VPhysicsDestroyObject(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CBasePlayer::SetVCollisionState( int collisionState ) { Vector vel = vec3_origin; Vector pos = vec3_origin; vel = GetAbsVelocity(); pos = GetAbsOrigin(); m_vphysicsCollisionState = collisionState; switch( collisionState ) { case VPHYS_WALK: m_pShadowStand->SetPosition( pos, vec3_angle, true ); m_pShadowStand->SetVelocity( &vel, NULL ); m_pShadowCrouch->EnableCollisions( false ); m_pShadowStand->EnableCollisions( true ); m_pPhysicsController->SetObject( m_pShadowStand ); VPhysicsSwapObject( m_pShadowStand ); break; case VPHYS_CROUCH: m_pShadowCrouch->SetPosition( pos, vec3_angle, true ); m_pShadowCrouch->SetVelocity( &vel, NULL ); m_pShadowStand->EnableCollisions( false ); m_pShadowCrouch->EnableCollisions( true ); m_pPhysicsController->SetObject( m_pShadowCrouch ); VPhysicsSwapObject( m_pShadowCrouch ); break; case VPHYS_NOCLIP: m_pShadowCrouch->EnableCollisions( false ); m_pShadowStand->EnableCollisions( false ); break; } } //----------------------------------------------------------------------------- // Purpose: // Output : int //----------------------------------------------------------------------------- int CBasePlayer::GetFOV( void ) const { int iFOV = ( m_Local.m_iFOV == 0 ) ? GetDefaultFOV() : m_Local.m_iFOV; return iFOV; } //----------------------------------------------------------------------------- // Purpose: // Input : FOV - // zoomRate - //----------------------------------------------------------------------------- bool CBasePlayer::SetFOV( CBaseEntity *pRequester, int FOV, float zoomRate ) { //NOTENOTE: You MUST specify who is requesting the zoom change assert( pRequester != NULL ); if ( pRequester == NULL ) return false; // If we already have an owner, we only allow requests from that owner if ( ( m_hZoomOwner != NULL ) && ( m_hZoomOwner != pRequester ) ) { if ( CanOverrideEnvZoomOwner( m_hZoomOwner ) == false ) return false; } else { //FIXME: Maybe do this is as an accessor instead if ( FOV == 0 ) { m_hZoomOwner = NULL; } else { m_hZoomOwner = pRequester; } } m_Local.m_iFOV = FOV; m_Local.m_flFOVRate = zoomRate; return true; } //----------------------------------------------------------------------------- // Purpose: Sets the default FOV for the player if nothing else is going on // Input : FOV - the new base FOV for this player //----------------------------------------------------------------------------- void CBasePlayer::SetDefaultFOV( int FOV ) { m_Local.m_iDefaultFOV = ( FOV == 0 ) ? g_pGameRules->DefaultFOV() : FOV; } //----------------------------------------------------------------------------- // Purpose: // static func // Input : set - //----------------------------------------------------------------------------- void CBasePlayer::ModifyOrAppendPlayerCriteria( AI_CriteriaSet& set ) { // Append our health set.AppendCriteria( "playerhealth", UTIL_VarArgs( "%i", GetHealth() ) ); float healthfrac = 0.0f; if ( GetMaxHealth() > 0 ) { healthfrac = (float)GetHealth() / (float)GetMaxHealth(); } set.AppendCriteria( "playerhealthfrac", UTIL_VarArgs( "%.3f", healthfrac ) ); CBaseCombatWeapon *weapon = GetActiveWeapon(); if ( weapon ) { set.AppendCriteria( "playerweapon", weapon->GetClassname() ); } else { set.AppendCriteria( "playerweapon", "none" ); } // Append current activity name set.AppendCriteria( "playeractivity", CAI_BaseNPC::GetActivityName( GetActivity() ) ); set.AppendCriteria( "playerspeed", UTIL_VarArgs( "%.3f", GetAbsVelocity().Length() ) ); AppendContextToCriteria( set, "player" ); } const QAngle& CBasePlayer::GetPunchAngle() { return m_Local.m_vecPunchAngle.Get(); } void CBasePlayer::SetPunchAngle( const QAngle &punchAngle ) { m_Local.m_vecPunchAngle = punchAngle; if ( IsAlive() ) { int index = entindex(); for ( int i = 1; i <= gpGlobals->maxClients; i++ ) { CBasePlayer *pPlayer = UTIL_PlayerByIndex( i ); if ( pPlayer && i != index && pPlayer->GetObserverTarget() == this && pPlayer->GetObserverMode() == OBS_MODE_IN_EYE ) { pPlayer->SetPunchAngle( punchAngle ); } } } } //----------------------------------------------------------------------------- // Purpose: Apply a movement constraint to the player //----------------------------------------------------------------------------- void CBasePlayer::ActivateMovementConstraint( CBaseEntity *pEntity, const Vector &vecCenter, float flRadius, float flConstraintWidth, float flSpeedFactor ) { m_hConstraintEntity = pEntity; m_vecConstraintCenter = vecCenter; m_flConstraintRadius = flRadius; m_flConstraintWidth = flConstraintWidth; m_flConstraintSpeedFactor = flSpeedFactor; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CBasePlayer::DeactivateMovementConstraint( ) { m_hConstraintEntity = NULL; m_flConstraintRadius = 0.0f; m_vecConstraintCenter = vec3_origin; } //----------------------------------------------------------------------------- // Perhaps a poorly-named function. This function traces against the supplied // NPC's hitboxes (instead of hull). If the trace hits a different NPC, the // new NPC is selected. Otherwise, the supplied NPC is determined to be the // one the citizen wants. This function allows the selection of a citizen over // another citizen's shoulder, which is impossible without tracing against // hitboxes instead of the hull (sjb) //----------------------------------------------------------------------------- CBaseEntity *CBasePlayer::DoubleCheckUseNPC( CBaseEntity *pNPC, const Vector &vecSrc, const Vector &vecDir ) { trace_t tr; UTIL_TraceLine( vecSrc, vecSrc + vecDir * 1024, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr ); if( tr.m_pEnt != NULL && tr.m_pEnt->MyNPCPointer() && tr.m_pEnt != pNPC ) { // Player is selecting a different NPC through some negative space // in the first NPC's hitboxes (between legs, over shoulder, etc). return tr.m_pEnt; } return pNPC; } bool CBasePlayer::IsBot() { return (GetFlags() & FL_FAKECLIENT) != 0; } bool CBasePlayer::IsFakeClient() { return (GetFlags() & FL_FAKECLIENT) != 0; } void CBasePlayer::EquipSuit( void ) { m_Local.m_bWearingSuit = true; } //----------------------------------------------------------------------------- // Purpose: // Output : const char //----------------------------------------------------------------------------- const char *CBasePlayer::GetTracerType( void ) { if ( GetActiveWeapon() ) { return GetActiveWeapon()->GetTracerType(); } return BaseClass::GetTracerType(); } //----------------------------------------------------------------------------- // Purpose: // Input : &tr - // nDamageType - //----------------------------------------------------------------------------- void CBasePlayer::DoImpactEffect( trace_t &tr, int nDamageType ) { if ( GetActiveWeapon() ) { GetActiveWeapon()->DoImpactEffect( tr, nDamageType ); return; } BaseClass::DoImpactEffect( tr, nDamageType ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CBasePlayer::InputSetHealth( inputdata_t &inputdata ) { int iNewHealth = inputdata.value.Int(); int iDelta = abs(GetHealth() - iNewHealth); if ( iNewHealth > GetHealth() ) { TakeHealth( iDelta, DMG_GENERIC ); } else if ( iNewHealth < GetHealth() ) { // Strip off and restore armor so that it doesn't absorb any of this damage. int armor = m_ArmorValue; m_ArmorValue = 0; TakeDamage( CTakeDamageInfo( this, this, iDelta, DMG_GENERIC ) ); m_ArmorValue = armor; } } //----------------------------------------------------------------------------- // Purpose: // Input : *pEntity - //----------------------------------------------------------------------------- void CBasePlayer::SetViewEntity( CBaseEntity *pEntity ) { m_hViewEntity = pEntity; if ( m_hViewEntity ) { engine->SetView( edict(), m_hViewEntity->edict() ); } else { engine->SetView( edict(), edict() ); } } //----------------------------------------------------------------------------- // Purpose: Looks at the player's reserve ammo and also all his weapons for any ammo // of the specified type // Input : nAmmoIndex - ammo to look for // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CBasePlayer::HasAnyAmmoOfType( int nAmmoIndex ) { // Must be a valid index if ( nAmmoIndex < 0 ) return false; // If we have some in reserve, we're already done if ( GetAmmoCount( nAmmoIndex ) ) return true; CBaseCombatWeapon *pWeapon; // Check all held weapons for ( int i=0; i < MAX_WEAPONS; i++ ) { pWeapon = GetWeapon( i ); if ( !pWeapon ) continue; // We must use clips and use this sort of ammo if ( pWeapon->UsesClipsForAmmo1() && pWeapon->GetPrimaryAmmoType() == nAmmoIndex ) { // If we have any ammo, we're done if ( pWeapon->HasPrimaryAmmo() ) return true; } // We'll check both clips for the same ammo type, just in case if ( pWeapon->UsesClipsForAmmo2() && pWeapon->GetSecondaryAmmoType() == nAmmoIndex ) { if ( pWeapon->HasSecondaryAmmo() ) return true; } } // We're completely without this type of ammo return false; } //----------------------------------------------------------------------------- // return a string version of the players network (i.e steam) ID. // //----------------------------------------------------------------------------- const char *CBasePlayer::GetNetworkIDString() { Q_strncpy( m_szNetworkIDString, engine->GetPlayerNetworkIDString( edict() ), sizeof(m_szNetworkIDString) ); return m_szNetworkIDString; } //----------------------------------------------------------------------------- // CPlayerInfo functions (simple passthroughts to get around the CBasePlayer multiple inheritence limitation) //----------------------------------------------------------------------------- const char *CPlayerInfo::GetName() { Assert( m_pParent ); return m_pParent->GetPlayerName(); } int CPlayerInfo::GetUserID() { Assert( m_pParent ); return engine->GetPlayerUserId( m_pParent->edict() ); } const char *CPlayerInfo::GetNetworkIDString() { Assert( m_pParent ); return m_pParent->GetNetworkIDString(); } int CPlayerInfo::GetTeamIndex() { Assert( m_pParent ); return m_pParent->GetTeamNumber(); } void CPlayerInfo::ChangeTeam( int iTeamNum ) { Assert( m_pParent ); m_pParent->ChangeTeam(iTeamNum); } int CPlayerInfo::GetFragCount() { Assert( m_pParent ); return m_pParent->FragCount(); } int CPlayerInfo::GetDeathCount() { Assert( m_pParent ); return m_pParent->DeathCount(); } bool CPlayerInfo::IsConnected() { Assert( m_pParent ); return m_pParent->IsConnected(); } int CPlayerInfo::GetArmorValue() { Assert( m_pParent ); return m_pParent->ArmorValue(); } bool CPlayerInfo::IsHLTV() { Assert( m_pParent ); return m_pParent->IsHLTV(); } bool CPlayerInfo::IsPlayer() { Assert( m_pParent ); return m_pParent->IsPlayer(); } bool CPlayerInfo::IsFakeClient() { Assert( m_pParent ); return m_pParent->IsFakeClient(); } bool CPlayerInfo::IsDead() { Assert( m_pParent ); return m_pParent->IsDead(); } bool CPlayerInfo::IsInAVehicle() { Assert( m_pParent ); return m_pParent->IsInAVehicle(); } bool CPlayerInfo::IsObserver() { Assert( m_pParent ); return m_pParent->IsObserver(); } const Vector CPlayerInfo::GetAbsOrigin() { Assert( m_pParent ); return m_pParent->GetAbsOrigin(); } const QAngle CPlayerInfo::GetAbsAngles() { Assert( m_pParent ); return m_pParent->GetAbsAngles(); } const Vector CPlayerInfo::GetPlayerMins() { Assert( m_pParent ); return m_pParent->GetPlayerMins(); } const Vector CPlayerInfo::GetPlayerMaxs() { Assert( m_pParent ); return m_pParent->GetPlayerMaxs(); } const char *CPlayerInfo::GetWeaponName() { Assert( m_pParent ); CBaseCombatWeapon *weap = m_pParent->GetActiveWeapon(); if ( !weap ) { return NULL; } return weap->GetName(); } const char *CPlayerInfo::GetModelName() { Assert( m_pParent ); return m_pParent->GetModelName().ToCStr(); } const int CPlayerInfo::GetHealth() { Assert( m_pParent ); return m_pParent->GetHealth(); } const int CPlayerInfo::GetMaxHealth() { Assert( m_pParent ); return m_pParent->GetMaxHealth(); } void CPlayerInfo::SetAbsOrigin( Vector & vec ) { Assert( m_pParent ); if ( m_pParent->IsBot() ) { m_pParent->SetAbsOrigin(vec); } } void CPlayerInfo::SetAbsAngles( QAngle & ang ) { Assert( m_pParent ); if ( m_pParent->IsBot() ) { m_pParent->SetAbsAngles(ang); } } void CPlayerInfo::RemoveAllItems( bool removeSuit ) { Assert( m_pParent ); if ( m_pParent->IsBot() ) { m_pParent->RemoveAllItems(removeSuit); } } void CPlayerInfo::SetActiveWeapon( const char *WeaponName ) { Assert( m_pParent ); if ( m_pParent->IsBot() ) { CBaseCombatWeapon *weap = m_pParent->Weapon_Create( WeaponName ); if ( weap ) { m_pParent->Weapon_Equip(weap); m_pParent->Weapon_Switch(weap); } } } void CPlayerInfo::SetLocalOrigin( const Vector& origin ) { Assert( m_pParent ); if ( m_pParent->IsBot() ) { m_pParent->SetLocalOrigin(origin); } } const Vector CPlayerInfo::GetLocalOrigin( void ) { Assert( m_pParent ); if ( m_pParent->IsBot() ) { Vector origin = m_pParent->GetLocalOrigin(); return origin; } else { return Vector( 0, 0, 0 ); } } void CPlayerInfo::SetLocalAngles( const QAngle& angles ) { Assert( m_pParent ); if ( m_pParent->IsBot() ) { m_pParent->SetLocalAngles( angles ); } } const QAngle CPlayerInfo::GetLocalAngles( void ) { Assert( m_pParent ); if ( m_pParent->IsBot() ) { return m_pParent->GetLocalAngles(); } else { return QAngle(); } } void CPlayerInfo::PostClientMessagesSent( void ) { Assert( m_pParent ); if ( m_pParent->IsBot() ) { m_pParent->PostClientMessagesSent(); } } bool CPlayerInfo::IsEFlagSet( int nEFlagMask ) { Assert( m_pParent ); if ( m_pParent->IsBot() ) { return m_pParent->IsEFlagSet(nEFlagMask); } return false; } void CPlayerInfo::RunPlayerMove( CBotCmd *ucmd ) { if ( m_pParent->IsBot() ) { Assert( m_pParent ); CUserCmd cmd; cmd.buttons = ucmd->buttons; cmd.command_number = ucmd->command_number; cmd.forwardmove = ucmd->forwardmove; cmd.hasbeenpredicted = ucmd->hasbeenpredicted; cmd.impulse = ucmd->impulse; cmd.mousedx = ucmd->mousedx; cmd.mousedy = ucmd->mousedy; cmd.random_seed = ucmd->random_seed; cmd.sidemove = ucmd->sidemove; cmd.tick_count = ucmd->tick_count; cmd.upmove = ucmd->upmove; cmd.viewangles = ucmd->viewangles; cmd.weaponselect = ucmd->weaponselect; cmd.weaponsubtype = ucmd->weaponsubtype; // Store off the globals.. they're gonna get whacked float flOldFrametime = gpGlobals->frametime; float flOldCurtime = gpGlobals->curtime; m_pParent->SetTimeBase( gpGlobals->curtime ); MoveHelperServer()->SetHost( m_pParent ); m_pParent->PlayerRunCommand( &cmd, MoveHelperServer() ); // save off the last good usercmd m_pParent->SetLastUserCommand( cmd ); // Clear out any fixangle that has been set m_pParent->pl.fixangle = FIXANGLE_NONE; // Restore the globals.. gpGlobals->frametime = flOldFrametime; gpGlobals->curtime = flOldCurtime; } } void CPlayerInfo::SetLastUserCommand( const CBotCmd &ucmd ) { if ( m_pParent->IsBot() ) { Assert( m_pParent ); CUserCmd cmd; cmd.buttons = ucmd.buttons; cmd.command_number = ucmd.command_number; cmd.forwardmove = ucmd.forwardmove; cmd.hasbeenpredicted = ucmd.hasbeenpredicted; cmd.impulse = ucmd.impulse; cmd.mousedx = ucmd.mousedx; cmd.mousedy = ucmd.mousedy; cmd.random_seed = ucmd.random_seed; cmd.sidemove = ucmd.sidemove; cmd.tick_count = ucmd.tick_count; cmd.upmove = ucmd.upmove; cmd.viewangles = ucmd.viewangles; cmd.weaponselect = ucmd.weaponselect; cmd.weaponsubtype = ucmd.weaponsubtype; m_pParent->SetLastUserCommand(cmd); } } CBotCmd CPlayerInfo::GetLastUserCommand() { CBotCmd cmd; const CUserCmd *ucmd = m_pParent->GetLastUserCommand(); if ( ucmd ) { cmd.buttons = ucmd->buttons; cmd.command_number = ucmd->command_number; cmd.forwardmove = ucmd->forwardmove; cmd.hasbeenpredicted = ucmd->hasbeenpredicted; cmd.impulse = ucmd->impulse; cmd.mousedx = ucmd->mousedx; cmd.mousedy = ucmd->mousedy; cmd.random_seed = ucmd->random_seed; cmd.sidemove = ucmd->sidemove; cmd.tick_count = ucmd->tick_count; cmd.upmove = ucmd->upmove; cmd.viewangles = ucmd->viewangles; cmd.weaponselect = ucmd->weaponselect; cmd.weaponsubtype = ucmd->weaponsubtype; } return cmd; }