diff options
Diffstat (limited to 'game/server/cstrike/cs_player.cpp')
| -rw-r--r-- | game/server/cstrike/cs_player.cpp | 8267 |
1 files changed, 8267 insertions, 0 deletions
diff --git a/game/server/cstrike/cs_player.cpp b/game/server/cstrike/cs_player.cpp new file mode 100644 index 0000000..79ed9c1 --- /dev/null +++ b/game/server/cstrike/cs_player.cpp @@ -0,0 +1,8267 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Player for HL1. +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "cs_player.h" +#include "cs_gamerules.h" +#include "trains.h" +#include "vcollide_parse.h" +#include "in_buttons.h" +#include "igamemovement.h" +#include "ai_hull.h" +#include "ndebugoverlay.h" +#include "weapon_csbase.h" +#include "decals.h" +#include "cs_ammodef.h" +#include "IEffects.h" +#include "cs_client.h" +#include "client.h" +#include "cs_shareddefs.h" +#include "shake.h" +#include "team.h" +#include "weapon_c4.h" +#include "weapon_parse.h" +#include "weapon_knife.h" +#include "movehelper_server.h" +#include "tier0/vprof.h" +#include "te_effect_dispatch.h" +#include "vphysics/player_controller.h" +#include "weapon_hegrenade.h" +#include "weapon_flashbang.h" +#include "weapon_csbasegun.h" +#include "weapon_smokegrenade.h" +#include <KeyValues.h> +#include "engine/IEngineSound.h" +#include "bot.h" +#include "studio.h" +#include <coordsize.h> +#include "predicted_viewmodel.h" +#include "props_shared.h" +#include "tier0/icommandline.h" +#include "info_camera_link.h" +#include "hintmessage.h" +#include "obstacle_pushaway.h" +#include "movevars_shared.h" +#include "death_pose.h" +#include "basecsgrenade_projectile.h" +#include "SoundEmitterSystem/isoundemittersystembase.h" +#include "CRagdollMagnet.h" +#include "datacache/imdlcache.h" +#include "npcevent.h" +#include "cs_gamestats.h" +#include "gamestats.h" +#include "holiday_gift.h" +#include "../../shared/cstrike/cs_achievement_constants.h" + +//============================================================================= +// HPE_BEGIN +//============================================================================= + +// [dwenger] Needed for global hostage list +#include "cs_simple_hostage.h" + +// [dwenger] Needed for weapon type used tracking +#include "../../shared/cstrike/cs_weapon_parse.h" + +#define REPORT_PLAYER_DAMAGE 0 + +//============================================================================= +// HPE_END +//============================================================================= + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#pragma optimize( "", off ) + +#pragma warning( disable : 4355 ) + +// Minimum interval between rate-limited commands that players can run. +#define CS_COMMAND_MAX_RATE 0.3 + +const float CycleLatchInterval = 0.2f; + +#define CS_PUSHAWAY_THINK_CONTEXT "CSPushawayThink" + +ConVar cs_ShowStateTransitions( "cs_ShowStateTransitions", "-2", FCVAR_CHEAT, "cs_ShowStateTransitions <ent index or -1 for all>. Show player state transitions." ); +ConVar sv_max_usercmd_future_ticks( "sv_max_usercmd_future_ticks", "8", 0, "Prevents clients from running usercmds too far in the future. Prevents speed hacks." ); +ConVar sv_motd_unload_on_dismissal( "sv_motd_unload_on_dismissal", "0", 0, "If enabled, the MOTD contents will be unloaded when the player closes the MOTD." ); +//============================================================================= +// HPE_BEGIN: +// [Forrest] Allow MVP to be turned off for a server +// [Forrest] Allow freezecam to be turned off for a server +// [Forrest] Allow win panel to be turned off for a server +//============================================================================= +static void SvNoMVPChangeCallback( IConVar *pConVar, const char *pOldValue, float flOldValue ) +{ + ConVarRef var( pConVar ); + if ( var.IsValid() && var.GetBool() ) + { + // Clear the MVPs of all players when MVP is turned off. + for ( int i = 1; i <= MAX_PLAYERS; i++ ) + { + CCSPlayer *pPlayer = ToCSPlayer( UTIL_PlayerByIndex( i ) ); + + if ( pPlayer ) + { + pPlayer->SetNumMVPs( 0 ); + } + } + } +} +ConVar sv_nomvp( "sv_nomvp", "0", 0, "Disable MVP awards.", SvNoMVPChangeCallback ); +ConVar sv_disablefreezecam( "sv_disablefreezecam", "0", FCVAR_REPLICATED, "Turn on/off freezecam on server" ); +ConVar sv_nowinpanel( "sv_nowinpanel", "0", FCVAR_REPLICATED, "Turn on/off win panel on server" ); +//============================================================================= +// HPE_END +//============================================================================= + + +// ConVar bot_mimic( "bot_mimic", "0", FCVAR_CHEAT ); +ConVar bot_freeze( "bot_freeze", "0", FCVAR_CHEAT ); +ConVar bot_crouch( "bot_crouch", "0", FCVAR_CHEAT ); +ConVar bot_mimic_yaw_offset( "bot_mimic_yaw_offset", "180", FCVAR_CHEAT ); + +ConVar sv_legacy_grenade_damage( "sv_legacy_grenade_damage", "0", FCVAR_REPLICATED, "Enable to replicate grenade damage behavior of the original Counter-Strike Source game." ); + +extern ConVar mp_autokick; +extern ConVar mp_holiday_nogifts; +extern ConVar sv_turbophysics; +//============================================================================= +// HPE_BEGIN: +// [menglish] Added in convars for freeze cam time length +//============================================================================= + +extern ConVar spec_freeze_time; +extern ConVar spec_freeze_traveltime; + +//============================================================================= +// HPE_END +//============================================================================= + +extern ConVar ammo_hegrenade_max; +extern ConVar ammo_flashbang_max; +extern ConVar ammo_smokegrenade_max; + + +#define THROWGRENADE_COUNTER_BITS 3 + + +EHANDLE g_pLastCTSpawn; +EHANDLE g_pLastTerroristSpawn; + +void TE_RadioIcon( IRecipientFilter& filter, float delay, CBaseEntity *pPlayer ); + + +// -------------------------------------------------------------------------------- // +// Classes +// -------------------------------------------------------------------------------- // + +class CPhysicsPlayerCallback : public IPhysicsPlayerControllerEvent +{ +public: + int ShouldMoveTo( IPhysicsObject *pObject, const Vector &position ) + { + CCSPlayer *pPlayer = (CCSPlayer *)pObject->GetGameData(); + if ( pPlayer ) + { + if ( pPlayer->TouchedPhysics() ) + { + return 0; + } + } + return 1; + } +}; + +static CPhysicsPlayerCallback playerCallback; + + +// -------------------------------------------------------------------------------- // +// Ragdoll entities. +// -------------------------------------------------------------------------------- // + +class CCSRagdoll : public CBaseAnimatingOverlay +{ +public: + DECLARE_CLASS( CCSRagdoll, CBaseAnimatingOverlay ); + DECLARE_SERVERCLASS(); + + // Transmit ragdolls to everyone. + virtual int UpdateTransmitState() + { + return SetTransmitState( FL_EDICT_ALWAYS ); + } + + void Init( void ) + { + SetSolid( SOLID_BBOX ); + SetMoveType( MOVETYPE_STEP ); + SetFriction( 1.0f ); + SetCollisionBounds( VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX ); + m_takedamage = DAMAGE_NO; + SetCollisionGroup( COLLISION_GROUP_DEBRIS ); + SetAbsOrigin( m_hPlayer->GetAbsOrigin() ); + SetAbsVelocity( m_hPlayer->GetAbsVelocity() ); + AddSolidFlags( FSOLID_NOT_SOLID ); + ChangeTeam( m_hPlayer->GetTeamNumber() ); + UseClientSideAnimation(); + } + +public: + // In case the client has the player entity, we transmit the player index. + // In case the client doesn't have it, we transmit the player's model index, origin, and angles + // so they can create a ragdoll in the right place. + CNetworkHandle( CBaseEntity, m_hPlayer ); // networked entity handle + CNetworkVector( m_vecRagdollVelocity ); + CNetworkVector( m_vecRagdollOrigin ); + CNetworkVar(int, m_iDeathPose ); + CNetworkVar(int, m_iDeathFrame ); +}; + +LINK_ENTITY_TO_CLASS( cs_ragdoll, CCSRagdoll ); + +IMPLEMENT_SERVERCLASS_ST_NOBASE( CCSRagdoll, DT_CSRagdoll ) + SendPropVector (SENDINFO(m_vecOrigin), -1, SPROP_COORD|SPROP_CHANGES_OFTEN, 0.0f, HIGH_DEFAULT, SendProxy_Origin ), + SendPropVector( SENDINFO(m_vecRagdollOrigin), -1, SPROP_COORD ), + SendPropEHandle( SENDINFO( m_hPlayer ) ), + SendPropModelIndex( SENDINFO( m_nModelIndex ) ), + SendPropInt ( SENDINFO(m_nForceBone), 8, 0 ), + SendPropVector ( SENDINFO(m_vecForce), -1, SPROP_NOSCALE ), + SendPropVector( SENDINFO( m_vecRagdollVelocity ) ), + SendPropInt( SENDINFO( m_iDeathPose ), ANIMATION_SEQUENCE_BITS, SPROP_UNSIGNED ), + SendPropInt( SENDINFO( m_iDeathFrame ), 5 ), + SendPropInt( SENDINFO(m_iTeamNum), TEAMNUM_NUM_BITS, 0), + SendPropInt( SENDINFO( m_bClientSideAnimation ), 1, SPROP_UNSIGNED ), +END_SEND_TABLE() + + +// -------------------------------------------------------------------------------- // +// Player animation event. Sent to the client when a player fires, jumps, reloads, etc.. +// -------------------------------------------------------------------------------- // + +class CTEPlayerAnimEvent : public CBaseTempEntity +{ +public: + DECLARE_CLASS( CTEPlayerAnimEvent, CBaseTempEntity ); + DECLARE_SERVERCLASS(); + + CTEPlayerAnimEvent( const char *name ) : CBaseTempEntity( name ) + { + } + + CNetworkHandle( CBasePlayer, m_hPlayer ); + CNetworkVar( int, m_iEvent ); + CNetworkVar( int, m_nData ); +}; + +IMPLEMENT_SERVERCLASS_ST_NOBASE( CTEPlayerAnimEvent, DT_TEPlayerAnimEvent ) + SendPropEHandle( SENDINFO( m_hPlayer ) ), + SendPropInt( SENDINFO( m_iEvent ), Q_log2( PLAYERANIMEVENT_COUNT ) + 1, SPROP_UNSIGNED ), + SendPropInt( SENDINFO( m_nData ), 32 ) +END_SEND_TABLE() + +static CTEPlayerAnimEvent g_TEPlayerAnimEvent( "PlayerAnimEvent" ); + +void TE_PlayerAnimEvent( CBasePlayer *pPlayer, PlayerAnimEvent_t event, int nData ) +{ + CPVSFilter filter( (const Vector&)pPlayer->EyePosition() ); + + g_TEPlayerAnimEvent.m_hPlayer = pPlayer; + g_TEPlayerAnimEvent.m_iEvent = event; + g_TEPlayerAnimEvent.m_nData = nData; + g_TEPlayerAnimEvent.Create( filter, 0 ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Filters updates to a variable so that only non-local players see +// the changes. This is so we can send a low-res origin to non-local players +// while sending a hi-res one to the local player. +// Input : *pVarData - +// *pOut - +// objectID - +//----------------------------------------------------------------------------- + +void* SendProxy_SendNonLocalDataTable( const SendProp *pProp, const void *pStruct, const void *pVarData, CSendProxyRecipients *pRecipients, int objectID ) +{ + pRecipients->SetAllRecipients(); + pRecipients->ClearRecipient( objectID - 1 ); + return ( void * )pVarData; +} +REGISTER_SEND_PROXY_NON_MODIFIED_POINTER( SendProxy_SendNonLocalDataTable ); + + +// -------------------------------------------------------------------------------- // +// Tables. +// -------------------------------------------------------------------------------- // + +LINK_ENTITY_TO_CLASS( player, CCSPlayer ); +PRECACHE_REGISTER(player); + +BEGIN_SEND_TABLE_NOBASE( CCSPlayer, DT_CSLocalPlayerExclusive ) + SendPropFloat( SENDINFO( m_flStamina ), 14, 0, 0, 1400 ), + SendPropInt( SENDINFO( m_iDirection ), 1, SPROP_UNSIGNED ), + SendPropInt( SENDINFO( m_iShotsFired ), 8, SPROP_UNSIGNED ), + SendPropFloat( SENDINFO( m_flVelocityModifier ), 8, 0, 0, 1 ), + + // send a hi-res origin to the local player for use in prediction + SendPropVector (SENDINFO(m_vecOrigin), -1, SPROP_NOSCALE|SPROP_CHANGES_OFTEN, 0.0f, HIGH_DEFAULT, SendProxy_Origin ), + + //============================================================================= + // HPE_BEGIN: + // [tj]Set up the send table for per-client domination data + //============================================================================= + + SendPropArray3( SENDINFO_ARRAY3( m_bPlayerDominated ), SendPropBool( SENDINFO_ARRAY( m_bPlayerDominated ) ) ), + SendPropArray3( SENDINFO_ARRAY3( m_bPlayerDominatingMe ), SendPropBool( SENDINFO_ARRAY( m_bPlayerDominatingMe ) ) ), + + //============================================================================= + // HPE_END + //============================================================================= + +END_SEND_TABLE() + + +BEGIN_SEND_TABLE_NOBASE( CCSPlayer, DT_CSNonLocalPlayerExclusive ) + // send a lo-res origin to other players + SendPropVector (SENDINFO(m_vecOrigin), -1, SPROP_COORD|SPROP_CHANGES_OFTEN, 0.0f, HIGH_DEFAULT, SendProxy_Origin ), +END_SEND_TABLE() + + +IMPLEMENT_SERVERCLASS_ST( CCSPlayer, DT_CSPlayer ) + SendPropExclude( "DT_BaseAnimating", "m_flPoseParameter" ), + SendPropExclude( "DT_BaseAnimating", "m_flPlaybackRate" ), + SendPropExclude( "DT_BaseAnimating", "m_nSequence" ), + SendPropExclude( "DT_BaseAnimating", "m_nNewSequenceParity" ), + SendPropExclude( "DT_BaseAnimating", "m_nResetEventsParity" ), + SendPropExclude( "DT_BaseAnimating", "m_nMuzzleFlashParity" ), + SendPropExclude( "DT_BaseEntity", "m_angRotation" ), + SendPropExclude( "DT_BaseAnimatingOverlay", "overlay_vars" ), + + // cs_playeranimstate and clientside animation takes care of these on the client + SendPropExclude( "DT_ServerAnimationData" , "m_flCycle" ), + SendPropExclude( "DT_AnimTimeMustBeFirst" , "m_flAnimTime" ), + + // We need to send a hi-res origin to the local player to avoid prediction errors sliding along walls + SendPropExclude( "DT_BaseEntity", "m_vecOrigin" ), + + // Data that only gets sent to the local player. + SendPropDataTable( "cslocaldata", 0, &REFERENCE_SEND_TABLE(DT_CSLocalPlayerExclusive), SendProxy_SendLocalDataTable ), + SendPropDataTable( "csnonlocaldata", 0, &REFERENCE_SEND_TABLE(DT_CSNonLocalPlayerExclusive), SendProxy_SendNonLocalDataTable ), + + SendPropInt( SENDINFO( m_iThrowGrenadeCounter ), THROWGRENADE_COUNTER_BITS, SPROP_UNSIGNED ), + SendPropInt( SENDINFO( m_iAddonBits ), NUM_ADDON_BITS, SPROP_UNSIGNED ), + SendPropInt( SENDINFO( m_iPrimaryAddon ), 8, SPROP_UNSIGNED ), + SendPropInt( SENDINFO( m_iSecondaryAddon ), 8, SPROP_UNSIGNED ), + SendPropInt( SENDINFO( m_iPlayerState ), Q_log2( NUM_PLAYER_STATES )+1, SPROP_UNSIGNED ), + SendPropInt( SENDINFO( m_iAccount ), 16, SPROP_UNSIGNED ), + SendPropInt( SENDINFO( m_bInBombZone ), 1, SPROP_UNSIGNED ), + SendPropInt( SENDINFO( m_bInBuyZone ), 1, SPROP_UNSIGNED ), + SendPropInt( SENDINFO( m_iClass ), Q_log2( CS_NUM_CLASSES )+1, SPROP_UNSIGNED ), + SendPropInt( SENDINFO( m_ArmorValue ), 8 ), + SendPropAngle( SENDINFO_VECTORELEM(m_angEyeAngles, 0), 11, SPROP_CHANGES_OFTEN ), + SendPropAngle( SENDINFO_VECTORELEM(m_angEyeAngles, 1), 11, SPROP_CHANGES_OFTEN ), + SendPropBool( SENDINFO( m_bHasDefuser ) ), + SendPropBool( SENDINFO( m_bNightVisionOn ) ), //send as int so we can use a RecvProxy on the client + SendPropBool( SENDINFO( m_bHasNightVision ) ), + + //============================================================================= + // HPE_BEGIN: + // [dwenger] Added for fun-fact support + //============================================================================= + + //SendPropBool( SENDINFO( m_bPickedUpDefuser ) ), + //SendPropBool( SENDINFO( m_bDefusedWithPickedUpKit) ), + + //============================================================================= + // HPE_END + //============================================================================= + + SendPropBool( SENDINFO( m_bInHostageRescueZone ) ), + SendPropBool( SENDINFO( m_bIsDefusing ) ), + + SendPropBool( SENDINFO( m_bResumeZoom ) ), + SendPropInt( SENDINFO( m_iLastZoom ), 8, SPROP_UNSIGNED ), + +#ifdef CS_SHIELD_ENABLED + SendPropBool( SENDINFO( m_bHasShield ) ), + SendPropBool( SENDINFO( m_bShieldDrawn ) ), +#endif + SendPropBool( SENDINFO( m_bHasHelmet ) ), + SendPropFloat (SENDINFO(m_flFlashDuration), 0, SPROP_NOSCALE ), + SendPropFloat( SENDINFO(m_flFlashMaxAlpha), 0, SPROP_NOSCALE ), + SendPropInt( SENDINFO( m_iProgressBarDuration ), 4, SPROP_UNSIGNED ), + SendPropFloat( SENDINFO( m_flProgressBarStartTime ), 0, SPROP_NOSCALE ), + SendPropEHandle( SENDINFO( m_hRagdoll ) ), + SendPropInt( SENDINFO( m_cycleLatch ), 4, SPROP_UNSIGNED ), + + +END_SEND_TABLE() + + +BEGIN_DATADESC( CCSPlayer ) + + DEFINE_INPUTFUNC( FIELD_VOID, "OnRescueZoneTouch", RescueZoneTouch ), + DEFINE_THINKFUNC( PushawayThink ) + +END_DATADESC() + + +// has to be included after above macros +#include "cs_bot.h" +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + + +// -------------------------------------------------------------------------------- // + +void cc_CreatePredictionError_f( const CCommand &args ) +{ + float distance = 32; + + if ( args.ArgC() >= 2 ) + { + distance = atof(args[1]); + } + + CBaseEntity *pEnt = CBaseEntity::Instance( 1 ); + pEnt->SetAbsOrigin( pEnt->GetAbsOrigin() + Vector( distance, 0, 0 ) ); +} + +ConCommand cc_CreatePredictionError( "CreatePredictionError", cc_CreatePredictionError_f, "Create a prediction error", FCVAR_CHEAT ); + + +// -------------------------------------------------------------------------------- // +// CCSPlayer implementation. +// -------------------------------------------------------------------------------- // + +CCSPlayer::CCSPlayer() +{ + m_PlayerAnimState = CreatePlayerAnimState( this, this, LEGANIM_9WAY, true ); + + UseClientSideAnimation(); + + m_iLastWeaponFireUsercmd = 0; + m_iAddonBits = 0; + m_bEscaped = false; + m_iAccount = 0; + + m_bIsVIP = false; + m_iClass = (int)CS_CLASS_NONE; + m_angEyeAngles.Init(); + + SetViewOffset( VEC_VIEW_SCALED( this ) ); + + m_pCurStateInfo = NULL; // no state yet + m_iThrowGrenadeCounter = 0; + + m_lifeState = LIFE_DEAD; // Start "dead". + m_bInBombZone = false; + m_bInBuyZone = false; + m_bInHostageRescueZone = false; + m_flDeathTime = 0.0f; + m_iHostagesKilled = 0; + iRadioMenu = -1; + m_bTeamChanged = false; + m_iShotsFired = 0; + m_iDirection = 0; + m_receivesMoneyNextRound = true; + m_bIsBeingGivenItem = false; + m_isVIP = false; + + m_bJustKilledTeammate = false; + m_bPunishedForTK = false; + m_iTeamKills = 0; + m_flLastMovement = gpGlobals->curtime; + m_iNextTimeCheck = 0; + + m_szNewName[0] = 0; + m_szClanTag[0] = 0; + + for ( int i=0; i<NAME_CHANGE_HISTORY_SIZE; i++ ) + { + m_flNameChangeHistory[i] = -NAME_CHANGE_HISTORY_INTERVAL; + } + + m_iIgnoreGlobalChat = 0; + m_bIgnoreRadio = false; + + m_pHintMessageQueue = new CHintMessageQueue(this); + m_iDisplayHistoryBits = 0; + m_bShowHints = true; + m_flNextMouseoverUpdate = gpGlobals->curtime; + + m_lastDamageHealth = 0; + m_lastDamageArmor = 0; + + m_applyDeafnessTime = 0.0f; + + m_cycleLatch = 0; + m_cycleLatchTimer.Invalidate(); + + m_iShouldHaveCash = 0; + + m_lastNavArea = NULL; + + //============================================================================= + // HPE_BEGIN: + // [menglish] Init achievement variables + // [menglish] Init bullet collision variables + //============================================================================= + + m_NumEnemiesKilledThisRound = 0; + m_NumEnemiesAtRoundStart = 0; + m_KillingSpreeStartTime = -1; + m_firstKillBlindStartTime = -1; + m_killsWhileBlind = 0; + m_bSurvivedHeadshotDueToHelmet = false; + m_pGooseChaseDistractingPlayer = NULL; + m_gooseChaseStep = GC_NONE; + m_defuseDefenseStep = DD_NONE; + m_lastRoundResult = Invalid_Round_End_Reason; + m_bMadeFootstepNoise = false; + m_bombPickupTime = -1; + m_bMadePurchseThisRound = false; + m_roundsWonWithoutPurchase = 0; + m_iDeathFlags = 0; + m_lastFlashBangAttacker = NULL; + m_iMVPs = 0; + m_bKilledDefuser = false; + m_bKilledRescuer = false; + m_maxGrenadeKills = 0; + m_grenadeDamageTakenThisRound = 0; + + m_vLastHitLocationObjectSpace = Vector(0,0,0); + + m_wasNotKilledNaturally = false; + + //============================================================================= + // HPE_END + //============================================================================= +} + + +CCSPlayer::~CCSPlayer() +{ + delete m_pHintMessageQueue; + m_pHintMessageQueue = NULL; + + // delete the records of damage taken and given + ResetDamageCounters(); + m_PlayerAnimState->Release(); +} + + +CCSPlayer *CCSPlayer::CreatePlayer( const char *className, edict_t *ed ) +{ + CCSPlayer::s_PlayerEdict = ed; + return (CCSPlayer*)CreateEntityByName( className ); +} + + +void CCSPlayer::Precache() +{ + Vector mins( -13, -13, -10 ); + Vector maxs( 13, 13, 75 ); + + int i; + for ( i=0; i<CTPlayerModels.Count(); ++i ) + { + PrecacheModel( CTPlayerModels[i] ); + engine->ForceModelBounds( CTPlayerModels[i], mins, maxs ); + } + for ( i=0; i<TerroristPlayerModels.Count(); ++i ) + { + PrecacheModel( TerroristPlayerModels[i] ); + engine->ForceModelBounds( TerroristPlayerModels[i], mins, maxs ); + } + + // Sigh - have to force identical VMTs for the player models. I'm just going to hard-code these + // strings here, rather than have char***'s or the CUtlVector<CUtlVector<>> equivalent. + engine->ForceSimpleMaterial( "materials/models/player/ct_urban/ct_urban.vmt" ); + engine->ForceSimpleMaterial( "materials/models/player/ct_urban/ct_urban_glass.vmt" ); + engine->ForceSimpleMaterial( "materials/models/player/ct_sas/ct_sas.vmt" ); + engine->ForceSimpleMaterial( "materials/models/player/ct_sas/ct_sas_glass.vmt" ); + engine->ForceSimpleMaterial( "materials/models/player/ct_gsg9/ct_gsg9.vmt" ); + engine->ForceSimpleMaterial( "materials/models/player/ct_gign/ct_gign.vmt" ); + engine->ForceSimpleMaterial( "materials/models/player/ct_gign/ct_gign_glass.vmt" ); + engine->ForceSimpleMaterial( "materials/models/player/t_phoenix/t_phoenix.vmt" ); + engine->ForceSimpleMaterial( "materials/models/player/t_guerilla/t_guerilla.vmt" ); + engine->ForceSimpleMaterial( "materials/models/player/t_leet/t_leet.vmt" ); + engine->ForceSimpleMaterial( "materials/models/player/t_leet/t_leet_glass.vmt" ); + engine->ForceSimpleMaterial( "materials/models/player/t_arctic/t_arctic.vmt" ); + +#ifdef CS_SHIELD_ENABLED + PrecacheModel( SHIELD_VIEW_MODEL ); +#endif + + PrecacheScriptSound( "Player.DeathHeadShot" ); + PrecacheScriptSound( "Player.Death" ); + PrecacheScriptSound( "Player.DamageHelmet" ); + PrecacheScriptSound( "Player.DamageHeadShot" ); + PrecacheScriptSound( "Flesh.BulletImpact" ); + PrecacheScriptSound( "Player.DamageKevlar" ); + PrecacheScriptSound( "Player.PickupWeapon" ); + PrecacheScriptSound( "Player.NightVisionOff" ); + PrecacheScriptSound( "Player.NightVisionOn" ); + PrecacheScriptSound( "Player.FlashlightOn" ); + PrecacheScriptSound( "Player.FlashlightOff" ); + + // CS Bot sounds + PrecacheScriptSound( "Bot.StuckSound" ); + PrecacheScriptSound( "Bot.StuckStart" ); + PrecacheScriptSound( "Bot.FellOff" ); + + UTIL_PrecacheOther( "item_kevlar" ); + UTIL_PrecacheOther( "item_assaultsuit" ); + UTIL_PrecacheOther( "item_defuser" ); + + PrecacheModel ( "sprites/glow01.vmt" ); + PrecacheModel ( "models/items/cs_gift.mdl" ); + + BaseClass::Precache(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Allow pre-frame adjustments on the player +//----------------------------------------------------------------------------- +ConVar sv_runcmds( "sv_runcmds", "1" ); +void CCSPlayer::PlayerRunCommand( CUserCmd *ucmd, IMoveHelper *moveHelper ) +{ + VPROF( "CCSPlayer::PlayerRunCommand" ); + + if ( !sv_runcmds.GetInt() ) + return; + + // don't run commands in the future + if ( !IsEngineThreaded() && + ( ucmd->tick_count > (gpGlobals->tickcount + sv_max_usercmd_future_ticks.GetInt()) ) ) + { + DevMsg( "Client cmd out of sync (delta %i).\n", ucmd->tick_count - gpGlobals->tickcount ); + return; + } + + // If they use a negative bot_mimic value, then don't process their usercmds, but have + // bots process them instead (so they can stay still and have the bot move around). + CUserCmd tempCmd; + if ( -bot_mimic.GetInt() == entindex() ) + { + tempCmd = *ucmd; + ucmd = &tempCmd; + + ucmd->forwardmove = ucmd->sidemove = ucmd->upmove = 0; + ucmd->buttons = 0; + ucmd->impulse = 0; + } + + if ( IsBot() && bot_crouch.GetInt() ) + ucmd->buttons |= IN_DUCK; + + BaseClass::PlayerRunCommand( ucmd, moveHelper ); +} + + +bool CCSPlayer::RunMimicCommand( CUserCmd& cmd ) +{ + if ( !IsBot() ) + return false; + + int iMimic = abs( bot_mimic.GetInt() ); + if ( iMimic > gpGlobals->maxClients ) + return false; + + CBasePlayer *pPlayer = UTIL_PlayerByIndex( iMimic ); + if ( !pPlayer ) + return false; + + if ( !pPlayer->GetLastUserCommand() ) + return false; + + cmd = *pPlayer->GetLastUserCommand(); + cmd.viewangles[YAW] += bot_mimic_yaw_offset.GetFloat(); + + pl.fixangle = FIXANGLE_NONE; + + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: Simulates a single frame of movement for a player +//----------------------------------------------------------------------------- +void CCSPlayer::RunPlayerMove( const QAngle& viewangles, float forwardmove, float sidemove, float upmove, unsigned short buttons, byte impulse, float frametime ) +{ + CUserCmd cmd; + + // Store off the globals.. they're gonna get whacked + float flOldFrametime = gpGlobals->frametime; + float flOldCurtime = gpGlobals->curtime; + + float flTimeBase = gpGlobals->curtime + gpGlobals->frametime - frametime; + this->SetTimeBase( flTimeBase ); + + CUserCmd lastUserCmd = *GetLastUserCommand(); + Q_memset( &cmd, 0, sizeof( cmd ) ); + + if ( !RunMimicCommand( cmd ) ) + { + cmd.forwardmove = forwardmove; + cmd.sidemove = sidemove; + cmd.upmove = upmove; + cmd.buttons = buttons; + cmd.impulse = impulse; + + VectorCopy( viewangles, cmd.viewangles ); + cmd.random_seed = random->RandomInt( 0, 0x7fffffff ); + } + + MoveHelperServer()->SetHost( this ); + PlayerRunCommand( &cmd, MoveHelperServer() ); + + // save off the last good usercmd + if ( -bot_mimic.GetInt() == entindex() ) + { + CUserCmd lastCmd = *GetLastUserCommand(); + lastCmd.command_number = cmd.command_number; + lastCmd.tick_count = cmd.tick_count; + SetLastUserCommand( lastCmd ); + } + else + { + SetLastUserCommand( cmd ); + } + + // Clear out any fixangle that has been set + pl.fixangle = FIXANGLE_NONE; + + // Restore the globals.. + gpGlobals->frametime = flOldFrametime; + gpGlobals->curtime = flOldCurtime; + + MoveHelperServer()->SetHost( NULL ); +} + + +void CCSPlayer::InitialSpawn( void ) +{ + BaseClass::InitialSpawn(); + + // we're going to give the bots money here instead of FinishClientPutInServer() + // because of the bots' timing for purchasing weapons/items. + if ( IsBot() ) + { + m_iAccount = CSGameRules()->GetStartMoney(); + } + + if ( !engine->IsDedicatedServer() && TheNavMesh->IsOutOfDate() && this == UTIL_GetListenServerHost() ) + { + ClientPrint( this, HUD_PRINTCENTER, "The Navigation Mesh was built using a different version of this map." ); + } + + State_Enter( STATE_WELCOME ); + + //============================================================================= + // HPE_BEGIN: + // [tj] We reset the stats at the beginning of the map (including domination tracking) + //============================================================================= + + CCS_GameStats.ResetPlayerStats(this); + RemoveNemesisRelationships(); + + //============================================================================= + // HPE_END + //============================================================================= + +} + +void CCSPlayer::SetModelFromClass( void ) +{ + if ( GetTeamNumber() == TEAM_TERRORIST ) + { + int index = m_iClass - FIRST_T_CLASS; + if ( index < 0 || index >= TerroristPlayerModels.Count() ) + { + index = RandomInt( 0, TerroristPlayerModels.Count() - 1 ); + m_iClass = index + FIRST_T_CLASS; // clean up players who selected a higher class than we support yet + } + SetModel( TerroristPlayerModels[index] ); + } + else if ( GetTeamNumber() == TEAM_CT ) + { + int index = m_iClass - FIRST_CT_CLASS; + if ( index < 0 || index >= CTPlayerModels.Count() ) + { + index = RandomInt( 0, CTPlayerModels.Count() - 1 ); + m_iClass = index + FIRST_CT_CLASS; // clean up players who selected a higher class than we support yet + } + SetModel( CTPlayerModels[index] ); + } + else + { + SetModel( CTPlayerModels[0] ); + } +} + +void CCSPlayer::Spawn() +{ + m_RateLimitLastCommandTimes.Purge(); + + // Get rid of the progress bar... + SetProgressBarTime( 0 ); + + CreateViewModel( 1 ); + + // Set their player model. + SetModelFromClass(); + + BaseClass::Spawn(); + + //============================================================================= + // HPE_BEGIN: + // [pfreese] Clear the last known nav area (used to be done by CBasePlayer) + //============================================================================= + + m_lastNavArea = NULL; + + //============================================================================= + // HPE_END + //============================================================================= + + AddFlag(FL_ONGROUND); // set the player on the ground at the start of the round. + + // Override what CBasePlayer set for the view offset. + SetViewOffset( VEC_VIEW_SCALED( this ) ); + + // + // Our player movement speed is set once here. This will override the cl_xxxx + // cvars unless they are set to be lower than this. + // + SetMaxSpeed( CS_PLAYER_SPEED_RUN ); + + SetFOV( this, 0 ); + + m_bIsDefusing = false; + + //============================================================================= + // HPE_BEGIN + // [dwenger] Reset hostage-related variables + //============================================================================= + + m_bIsRescuing = false; + m_bInjuredAHostage = false; + m_iNumFollowers = 0; + + // [tj] Reset this flag if the player is not in observer mode (as happens when a player spawns late) + if (m_iPlayerState != STATE_OBSERVER_MODE) + { + m_wasNotKilledNaturally = false; + } + + //============================================================================= + // HPE_END + //============================================================================= + + m_iShotsFired = 0; + m_iDirection = 0; + + if ( m_pHintMessageQueue ) + { + m_pHintMessageQueue->Reset(); + } + m_iDisplayHistoryBits &= ~DHM_ROUND_CLEAR; + + // Special-case here. A bunch of things happen in CBasePlayer::Spawn(), and we really want the + // player states to control these things, so give whatever player state we're in a chance + // to reinitialize itself. + State_Transition( m_iPlayerState ); + + ClearFlashbangScreenFade(); + + m_flVelocityModifier = 1.0f; + + ResetStamina(); + + m_flLastRadarUpdateTime = 0.0f; + + m_iNumSpawns++; + + if ( !engine->IsDedicatedServer() && CSGameRules()->m_iTotalRoundsPlayed < 2 && TheNavMesh->IsOutOfDate() && this == UTIL_GetListenServerHost() ) + { + ClientPrint( this, HUD_PRINTCENTER, "The Navigation Mesh was built using a different version of this map." ); + } + + m_bTeamChanged = false; + m_iOldTeam = TEAM_UNASSIGNED; + + m_iRadioMessages = 60; + m_flRadioTime = gpGlobals->curtime; + + if ( m_hRagdoll ) + { + UTIL_Remove( m_hRagdoll ); + } + + m_hRagdoll = NULL; + + // did we change our name while we were dead? + if ( m_szNewName[0] != 0 ) + { + ChangeName( m_szNewName ); + m_szNewName[0] = 0; + } + + if ( m_bIsVIP ) + { + HintMessage( "#Hint_you_are_the_vip", true, true ); + } + + m_bIsInAutoBuy = false; + m_bIsInRebuy = false; + m_bAutoReload = false; + + SetContextThink( &CCSPlayer::PushawayThink, gpGlobals->curtime + PUSHAWAY_THINK_INTERVAL, CS_PUSHAWAY_THINK_CONTEXT ); + + if ( GetActiveWeapon() && !IsObserver() ) + { + GetActiveWeapon()->Deploy(); + m_flNextAttack = gpGlobals->curtime; // Allow reloads to finish, since we're playing the deploy anim instead. This mimics goldsrc behavior, anyway. + } + + m_applyDeafnessTime = 0.0f; + + m_cycleLatch = 0; + m_cycleLatchTimer.Start( RandomFloat( 0.0f, CycleLatchInterval ) ); + + StockPlayerAmmo(); + } + +void CCSPlayer::ShowViewPortPanel( const char * name, bool bShow, KeyValues *data ) +{ + if ( CSGameRules()->IsLogoMap() ) + return; + + if ( CommandLine()->FindParm("-makedevshots") ) + return; + + BaseClass::ShowViewPortPanel( name, bShow, data ); +} + +void CCSPlayer::ClearFlashbangScreenFade( void ) +{ + if( IsBlind() ) + { + color32 clr = { 0, 0, 0, 0 }; + UTIL_ScreenFade( this, clr, 0.01, 0.0, FFADE_OUT | FFADE_PURGE ); + + m_flFlashDuration = 0.0f; + m_flFlashMaxAlpha = 255.0f; + } + + // clear blind time (after screen fades are canceled) + m_blindUntilTime = 0.0f; + m_blindStartTime = 0.0f; +} + +void CCSPlayer::GiveDefaultItems() +{ + // Always give the player the knife. + CBaseCombatWeapon *pistol = Weapon_GetSlot( WEAPON_SLOT_PISTOL ); + if ( pistol ) + { + return; + } + + m_bUsingDefaultPistol = true; + + if ( GetTeamNumber() == TEAM_CT ) + { + GiveNamedItem( "weapon_knife" ); + GiveNamedItem( "weapon_usp" ); + GiveAmmo( 24, BULLET_PLAYER_45ACP ); + } + else if ( GetTeamNumber() == TEAM_TERRORIST ) + { + GiveNamedItem( "weapon_knife" ); + GiveNamedItem( "weapon_glock" ); + GiveAmmo( 40, BULLET_PLAYER_9MM ); + } +} + +void CCSPlayer::SetClanTag( const char *pTag ) +{ + if ( pTag ) + { + Q_strncpy( m_szClanTag, pTag, sizeof( m_szClanTag ) ); + } +} +void CCSPlayer::CreateRagdollEntity() +{ + // If we already have a ragdoll, don't make another one. + CCSRagdoll *pRagdoll = dynamic_cast< CCSRagdoll* >( m_hRagdoll.Get() ); + + if ( !pRagdoll ) + { + // create a new one + pRagdoll = dynamic_cast< CCSRagdoll* >( CreateEntityByName( "cs_ragdoll" ) ); + } + + if ( pRagdoll ) + { + pRagdoll->m_hPlayer = this; + pRagdoll->m_vecRagdollOrigin = GetAbsOrigin(); + pRagdoll->m_vecRagdollVelocity = GetAbsVelocity(); + pRagdoll->m_nModelIndex = m_nModelIndex; + pRagdoll->m_nForceBone = m_nForceBone; + pRagdoll->m_vecForce = m_vecTotalBulletForce; + pRagdoll->m_iDeathPose = m_iDeathPose; + pRagdoll->m_iDeathFrame = m_iDeathFrame; + pRagdoll->Init(); + } + + // ragdolls will be removed on round restart automatically + m_hRagdoll = pRagdoll; +} + +int CCSPlayer::OnTakeDamage_Alive( const CTakeDamageInfo &info ) +{ + // set damage type sustained + m_bitsDamageType |= info.GetDamageType(); + + if ( !CBaseCombatCharacter::OnTakeDamage_Alive( info ) ) + return 0; + + // don't apply damage forces in CS + + // 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("armor", MAX(0, ArmorValue()) ); + + event->SetInt( "dmg_health", m_lastDamageHealth ); + event->SetInt( "dmg_armor", m_lastDamageArmor ); + + if ( info.GetDamageType() & DMG_BLAST ) + { + event->SetInt( "hitgroup", HITGROUP_GENERIC ); + } + else + { + event->SetInt( "hitgroup", m_LastHitGroup ); + } + + CBaseEntity * attacker = info.GetAttacker(); + const char *weaponName = ""; + + if ( attacker->IsPlayer() ) + { + CBasePlayer *player = ToBasePlayer( attacker ); + event->SetInt("attacker", player->GetUserID() ); // hurt by other player + + CBaseEntity *pInflictor = info.GetInflictor(); + if ( pInflictor ) + { + if ( pInflictor == player ) + { + // If the inflictor is the killer, then it must be their current weapon doing the damage + if ( player->GetActiveWeapon() ) + { + weaponName = player->GetActiveWeapon()->GetClassname(); + } + } + else + { + weaponName = STRING( pInflictor->m_iClassname ); // it's just that easy + } + } + } + else + { + event->SetInt("attacker", 0 ); // hurt by "world" + } + + if ( strncmp( weaponName, "weapon_", 7 ) == 0 ) + { + weaponName += 7; + } + else if( strncmp( weaponName, "hegrenade", 9 ) == 0 ) //"hegrenade_projectile" + { + //============================================================================= + // HPE_BEGIN: + // [tj] Handle grenade-surviving achievement + //============================================================================= + if (info.GetAttacker()->GetTeamNumber() != GetTeamNumber()) + { + m_grenadeDamageTakenThisRound += info.GetDamage(); + } + //============================================================================= + // HPE_END + //============================================================================= + + weaponName = "hegrenade"; + } + else if( strncmp( weaponName, "flashbang", 9 ) == 0 ) //"flashbang_projectile" + { + weaponName = "flashbang"; + } + else if( strncmp( weaponName, "smokegrenade", 12 ) == 0 ) //"smokegrenade_projectile" + { + weaponName = "smokegrenade"; + } + + event->SetString( "weapon", weaponName ); + event->SetInt( "priority", 5 ); + + gameeventmanager->FireEvent( event ); + } + + return 1; +} + +//============================================================================= +// HPE_BEGIN: +// [dwenger] Supports fun-fact +//============================================================================= + +// Returns the % of the enemies this player killed in the round +int CCSPlayer::GetPercentageOfEnemyTeamKilled() +{ + if ( m_NumEnemiesAtRoundStart > 0 ) + { + return (int)( ( (float)m_NumEnemiesKilledThisRound / (float)m_NumEnemiesAtRoundStart ) * 100.0f ); + } + + return 0; +} + +//============================================================================= +// HPE_END +//============================================================================= + +void CCSPlayer::Event_Killed( const CTakeDamageInfo &info ) +{ + //============================================================================= + // HPE_BEGIN: + // [pfreese] Process on-death achievements + //============================================================================= + + ProcessPlayerDeathAchievements(ToCSPlayer(info.GetAttacker()), this, info); + + //============================================================================= + // HPE_END + //============================================================================= + + SetArmorValue( 0 ); + + //============================================================================= + // HPE_BEGIN: + // [tj] Added a parameter so we know if it was death that caused the drop + // [menglish] Keep track of what the player has dropped for the freeze panel callouts + //============================================================================= + + CBaseEntity* pAttacker = info.GetAttacker(); + bool friendlyFire = pAttacker && pAttacker->GetTeamNumber() == GetTeamNumber(); + + //Only count the drop if it was not friendly fire + DropWeapons(true, !friendlyFire); + + //============================================================================= + // HPE_END + //============================================================================= + + + // Just in case the progress bar is on screen, kill it. + SetProgressBarTime( 0 ); + + m_bIsDefusing = false; + + m_bHasNightVision = false; + m_bNightVisionOn = false; + + //============================================================================= + // HPE_BEGIN: + // [dwenger] Added for fun-fact support + //============================================================================= + + m_bPickedUpDefuser = false; + m_bDefusedWithPickedUpKit = false; + + //============================================================================= + // HPE_END + //============================================================================= + + m_bHasHelmet = false; + + m_flFlashDuration = 0.0f; + + FlashlightTurnOff(); + + // show killer in death cam mode + if( IsValidObserverTarget( info.GetAttacker() ) ) + { + SetObserverTarget( info.GetAttacker() ); + } + else + { + ResetObserverMode(); + } + + //update damage info with our accumulated physics force + CTakeDamageInfo subinfo = info; + + // HACK[pfreese]: scale impulse up for visual effect + const float kImpulseBonusScale = 2.0f; + subinfo.SetDamageForce( m_vecTotalBulletForce * kImpulseBonusScale); + + //Adrian: Select a death pose to extrapolate the ragdoll's velocity. + SelectDeathPose( info ); + + // See if there's a ragdoll magnet that should influence our force. + CRagdollMagnet *pMagnet = CRagdollMagnet::FindBestMagnet( this ); + if( pMagnet ) + { + m_vecTotalBulletForce += pMagnet->GetForceVector( this ); + } + + // Note: since we're dead, it won't draw us on the client, but we don't set EF_NODRAW + // because we still want to transmit to the clients in our PVS. + CreateRagdollEntity(); + + // Special code to drop holiday gifts for the holiday achievement + if ( ( mp_holiday_nogifts.GetBool() == false ) && UTIL_IsHolidayActive( 3 /*kHoliday_Christmas*/ ) ) + { + if ( RandomInt( 0, 100 ) < 20 ) + { + CHolidayGift::Create( WorldSpaceCenter(), GetAbsAngles(), EyeAngles(), GetAbsVelocity(), this ); + } + } + + State_Transition( STATE_DEATH_ANIM ); // Transition into the dying state. + BaseClass::Event_Killed( subinfo ); + + //============================================================================= + // HPE_BEGIN: + // [pfreese] If this kill ended the round, award the MVP to someone on the + // winning team. + // TODO - move this code somewhere else more MVP related + //============================================================================= + + bool roundWasAlreadyWon = (CSGameRules()->m_iRoundWinStatus != WINNER_NONE); + bool roundIsWonNow = CSGameRules()->CheckWinConditions(); + + if ( !roundWasAlreadyWon && roundIsWonNow ) + { + CCSPlayer* pMVP = NULL; + int maxKills = 0; + int maxDamage = 0; + + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CCSPlayer* pPlayer = ToCSPlayer( UTIL_PlayerByIndex( i ) ); + if ( pPlayer ) + { + // only consider players on the winning team + if ( pPlayer->GetTeamNumber() != CSGameRules()->m_iRoundWinStatus ) + continue; + + int nKills = CCS_GameStats.FindPlayerStats( pPlayer ).statsCurrentRound[CSSTAT_KILLS]; + int nDamage = CCS_GameStats.FindPlayerStats( pPlayer ).statsCurrentRound[CSSTAT_DAMAGE]; + + if ( nKills > maxKills || ( nKills == maxKills && nDamage > maxDamage ) ) + { + pMVP = pPlayer; + maxKills = nKills; + maxDamage = nDamage; + } + } + } + + if ( pMVP ) + { + pMVP->IncrementNumMVPs( CSMVP_ELIMINATION ); + } + } + + //============================================================================= + // HPE_END + //============================================================================= + + OutputDamageGiven(); + OutputDamageTaken(); + ResetDamageCounters(); + + if ( m_bPunishedForTK ) + { + m_bPunishedForTK = false; + HintMessage( "#Hint_cannot_play_because_tk", true, true ); + } + + if ( !(m_iDisplayHistoryBits & DHF_SPEC_DUCK) ) + { + m_iDisplayHistoryBits |= DHF_SPEC_DUCK; + HintMessage( "#Spec_Duck", true, true ); + } +} + +//============================================================================= +// HPE_BEGIN: +// [menglish, tj] Update and check any one-off achievements based on the kill +//============================================================================= + +// Notify that I've killed some other entity. (called from Victim's Event_Killed). +void CCSPlayer::Event_KilledOther( CBaseEntity *pVictim, const CTakeDamageInfo &info ) +{ + BaseClass::Event_KilledOther(pVictim, info); +} + +//============================================================================= +// HPE_END +//============================================================================= + +void CCSPlayer::DeathSound( const CTakeDamageInfo &info ) +{ + if( m_LastHitGroup == HITGROUP_HEAD ) + { + EmitSound( "Player.DeathHeadShot" ); + } + else + { + EmitSound( "Player.Death" ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCSPlayer::InitVCollision( const Vector &vecAbsOrigin, const Vector &vecAbsVelocity ) +{ + BaseClass::InitVCollision( vecAbsOrigin, vecAbsVelocity ); + + if ( sv_turbophysics.GetBool() ) + return; + + // Setup the HL2 specific callback. + GetPhysicsController()->SetEventHandler( &playerCallback ); +} + +void CCSPlayer::VPhysicsShadowUpdate( IPhysicsObject *pPhysics ) +{ + if ( !CanMove() ) + return; + + BaseClass::VPhysicsShadowUpdate( pPhysics ); +} + +bool CCSPlayer::HasShield() const +{ +#ifdef CS_SHIELD_ENABLED + return m_bHasShield; +#else + return false; +#endif +} + + +bool CCSPlayer::IsShieldDrawn() const +{ +#ifdef CS_SHIELD_ENABLED + return m_bShieldDrawn; +#else + return false; +#endif +} + + +void CCSPlayer::CheatImpulseCommands( int iImpulse ) +{ + switch( iImpulse ) + { + case 101: + { + if( sv_cheats->GetBool() ) + { + extern int gEvilImpulse101; + gEvilImpulse101 = true; + + AddAccount( 16000 ); + + GiveAmmo( 250, BULLET_PLAYER_50AE ); + GiveAmmo( 250, BULLET_PLAYER_762MM ); + GiveAmmo( 250, BULLET_PLAYER_338MAG ); + GiveAmmo( 250, BULLET_PLAYER_556MM ); + GiveAmmo( 250, BULLET_PLAYER_556MM_BOX ); + GiveAmmo( 250, BULLET_PLAYER_9MM ); + GiveAmmo( 250, BULLET_PLAYER_BUCKSHOT ); + GiveAmmo( 250, BULLET_PLAYER_45ACP ); + GiveAmmo( 250, BULLET_PLAYER_357SIG ); + GiveAmmo( 250, BULLET_PLAYER_57MM ); + + gEvilImpulse101 = false; + } + } + break; + + default: + { + BaseClass::CheatImpulseCommands( iImpulse ); + } + } +} + +void CCSPlayer::SetupVisibility( CBaseEntity *pViewEntity, unsigned char *pvs, int pvssize ) +{ + BaseClass::SetupVisibility( pViewEntity, pvs, pvssize ); + + int area = pViewEntity ? pViewEntity->NetworkProp()->AreaNum() : NetworkProp()->AreaNum(); + PointCameraSetupVisibility( this, area, pvs, pvssize ); +} + +void CCSPlayer::UpdateAddonBits() +{ + int iNewBits = 0; + + int nFlashbang = GetAmmoCount( GetAmmoDef()->Index( AMMO_TYPE_FLASHBANG ) ); + if ( dynamic_cast< CFlashbang* >( GetActiveWeapon() ) ) + { + --nFlashbang; + } + + if ( nFlashbang >= 1 ) + iNewBits |= ADDON_FLASHBANG_1; + + if ( nFlashbang >= 2 ) + iNewBits |= ADDON_FLASHBANG_2; + + if ( GetAmmoCount( GetAmmoDef()->Index( AMMO_TYPE_HEGRENADE ) ) && + !dynamic_cast< CHEGrenade* >( GetActiveWeapon() ) ) + { + iNewBits |= ADDON_HE_GRENADE; + } + + if ( GetAmmoCount( GetAmmoDef()->Index( AMMO_TYPE_SMOKEGRENADE ) ) && + !dynamic_cast< CSmokeGrenade* >( GetActiveWeapon() ) ) + { + iNewBits |= ADDON_SMOKE_GRENADE; + } + + if ( HasC4() && !dynamic_cast< CC4* >( GetActiveWeapon() ) ) + iNewBits |= ADDON_C4; + + if ( HasDefuser() ) + iNewBits |= ADDON_DEFUSEKIT; + + CWeaponCSBase *weapon = dynamic_cast< CWeaponCSBase * >(Weapon_GetSlot( WEAPON_SLOT_RIFLE )); + if ( weapon && weapon != GetActiveWeapon() ) + { + iNewBits |= ADDON_PRIMARY; + m_iPrimaryAddon = weapon->GetWeaponID(); + } + else + { + m_iPrimaryAddon = WEAPON_NONE; + } + + weapon = dynamic_cast< CWeaponCSBase * >(Weapon_GetSlot( WEAPON_SLOT_PISTOL )); + if ( weapon && weapon != GetActiveWeapon() ) + { + iNewBits |= ADDON_PISTOL; + if ( weapon->GetWeaponID() == WEAPON_ELITE ) + { + iNewBits |= ADDON_PISTOL2; + } + m_iSecondaryAddon = weapon->GetWeaponID(); + } + else if ( weapon && weapon->GetWeaponID() == WEAPON_ELITE ) + { + // The active weapon is weapon_elite. Set ADDON_PISTOL2 without ADDON_PISTOL, so we know + // to display the empty holster. + iNewBits |= ADDON_PISTOL2; + m_iSecondaryAddon = weapon->GetWeaponID(); + } + else + { + m_iSecondaryAddon = WEAPON_NONE; + } + + m_iAddonBits = iNewBits; +} + +void CCSPlayer::UpdateRadar() +{ + // update once a second + if ( (m_flLastRadarUpdateTime + 1.0) > gpGlobals->curtime ) + return; + + m_flLastRadarUpdateTime = gpGlobals->curtime; + + // update positions of all players outside of my PVS + CBitVec< ABSOLUTE_PLAYER_LIMIT > playerbits; + engine->Message_DetermineMulticastRecipients( false, EyePosition(), playerbits ); + + CSingleUserRecipientFilter user( this ); + UserMessageBegin( user, "UpdateRadar" ); + + for ( int i=0; i < MAX_PLAYERS; i++ ) + { + CCSPlayer *pPlayer = ToCSPlayer( UTIL_PlayerByIndex( i+1 ) ); + + if ( !pPlayer ) + continue; // nothing there + + bool bSameTeam = pPlayer->GetTeamNumber() == GetTeamNumber(); + + if ( playerbits.Get(i) && bSameTeam == true ) + continue; // this player is in my PVS and not in my team, don't update radar pos + + if ( pPlayer == this ) + continue; + + if ( !pPlayer->IsAlive() || pPlayer->IsObserver() || !pPlayer->IsConnected() ) + continue; // don't update specattors or dead players + + WRITE_BYTE( i+1 ); // player index as entity + WRITE_SBITLONG( pPlayer->GetAbsOrigin().x/4, COORD_INTEGER_BITS-1 ); + WRITE_SBITLONG( pPlayer->GetAbsOrigin().y/4, COORD_INTEGER_BITS-1 ); + WRITE_SBITLONG( pPlayer->GetAbsOrigin().z/4, COORD_INTEGER_BITS-1 ); + WRITE_SBITLONG( AngleNormalize( pPlayer->GetAbsAngles().y ), 9 ); + } + + WRITE_BYTE( 0 ); // end marker + + MessageEnd(); +} + +void CCSPlayer::UpdateMouseoverHints() +{ + if ( IsBlind() || IsObserver() ) + return; + + Vector forward, up; + EyeVectors( &forward, NULL, &up ); + + trace_t tr; + // Search for objects in a sphere (tests for entities that are not solid, yet still useable) + Vector searchStart = EyePosition(); + Vector searchEnd = searchStart + forward * 2048; + + int useableContents = MASK_NPCSOLID_BRUSHONLY | MASK_VISIBLE_AND_NPCS; + + UTIL_TraceLine( searchStart, searchEnd, useableContents, this, COLLISION_GROUP_NONE, &tr ); + + if ( tr.fraction != 1.0f ) + { + if (tr.DidHitNonWorldEntity() && tr.m_pEnt) + { + CBaseEntity *pObject = tr.m_pEnt; + switch ( pObject->Classify() ) + { + case CLASS_PLAYER: + { + const float grenadeBloat = 1.2f; // Be conservative in estimating what a player can distinguish + if ( !TheBots->IsLineBlockedBySmoke( EyePosition(), pObject->EyePosition(), grenadeBloat ) ) + { + if ( g_pGameRules->PlayerRelationship( this, pObject ) == GR_TEAMMATE ) + { + if ( !(m_iDisplayHistoryBits & DHF_FRIEND_SEEN) ) + { + m_iDisplayHistoryBits |= DHF_FRIEND_SEEN; + HintMessage( "#Hint_spotted_a_friend", true ); + } + } + else + { + if ( !(m_iDisplayHistoryBits & DHF_ENEMY_SEEN) ) + { + m_iDisplayHistoryBits |= DHF_ENEMY_SEEN; + HintMessage( "#Hint_spotted_an_enemy", true ); + } + } + } + } + break; + case CLASS_PLAYER_ALLY: + switch ( GetTeamNumber() ) + { + case TEAM_CT: + if ( !(m_iDisplayHistoryBits & DHF_HOSTAGE_SEEN_FAR) && tr.fraction > 0.1f ) + { + m_iDisplayHistoryBits |= DHF_HOSTAGE_SEEN_FAR; + HintMessage( "#Hint_rescue_the_hostages", true ); + } + else if ( !(m_iDisplayHistoryBits & DHF_HOSTAGE_SEEN_NEAR) && tr.fraction <= 0.1f ) + { + m_iDisplayHistoryBits |= DHF_HOSTAGE_SEEN_FAR; + m_iDisplayHistoryBits |= DHF_HOSTAGE_SEEN_NEAR; + HintMessage( "#Hint_press_use_so_hostage_will_follow", false ); + } + break; + case TEAM_TERRORIST: + if ( !(m_iDisplayHistoryBits & DHF_HOSTAGE_SEEN_FAR) ) + { + m_iDisplayHistoryBits |= DHF_HOSTAGE_SEEN_FAR; + HintMessage( "#Hint_prevent_hostage_rescue", true ); + } + break; + } + break; + } + } + } +} + +void CCSPlayer::PostThink() +{ + BaseClass::PostThink(); + + UpdateAddonBits(); + + UpdateRadar(); + + if ( !(m_iDisplayHistoryBits & DHF_ROUND_STARTED) && CanPlayerBuy(false) ) + { + HintMessage( "#Hint_press_buy_to_purchase", false ); + m_iDisplayHistoryBits |= DHF_ROUND_STARTED; + } + if ( m_flNextMouseoverUpdate < gpGlobals->curtime ) + { + m_flNextMouseoverUpdate = gpGlobals->curtime + 0.2f; + if ( m_bShowHints ) + { + UpdateMouseoverHints(); + } + } + if ( GetActiveWeapon() && !(m_iDisplayHistoryBits & DHF_AMMO_EXHAUSTED) ) + { + CBaseCombatWeapon *pWeapon = GetActiveWeapon(); + if ( !pWeapon->HasAnyAmmo() && !(pWeapon->GetWpnData().iFlags & ITEM_FLAG_EXHAUSTIBLE) ) + { + m_iDisplayHistoryBits |= DHF_AMMO_EXHAUSTED; + HintMessage( "#Hint_out_of_ammo", false ); + } + } + + QAngle angles = GetLocalAngles(); + angles[PITCH] = 0; + SetLocalAngles( angles ); + + // Store the eye angles pitch so the client can compute its animation state correctly. + m_angEyeAngles = EyeAngles(); + + m_PlayerAnimState->Update( m_angEyeAngles[YAW], m_angEyeAngles[PITCH] ); + + // check if we need to apply a deafness DSP effect. + if ((m_applyDeafnessTime != 0.0f) && (m_applyDeafnessTime <= gpGlobals->curtime)) + { + ApplyDeafnessEffect(); + } + + if ( IsPlayerUnderwater() && GetWaterLevel() < 3 ) + { + StopSound( "Player.AmbientUnderWater" ); + SetPlayerUnderwater( false ); + } + + if( IsAlive() && m_cycleLatchTimer.IsElapsed() ) + { + m_cycleLatchTimer.Start( CycleLatchInterval ); + + // Cycle is a float from 0 to 1. We don't need to transmit a whole float for that. Compress it in to a small fixed point + m_cycleLatch.GetForModify() = 16 * GetCycle();// 4 point fixed + } +} + + +void CCSPlayer::PushawayThink() +{ + // Push physics props out of our way. + PerformObstaclePushaway( this ); + SetNextThink( gpGlobals->curtime + PUSHAWAY_THINK_INTERVAL, CS_PUSHAWAY_THINK_CONTEXT ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Returns whether or not we can switch to the given weapon. +// Input : pWeapon - +//----------------------------------------------------------------------------- +bool CCSPlayer::Weapon_CanSwitchTo( CBaseCombatWeapon *pWeapon ) +{ + if ( !pWeapon->CanDeploy() ) + return false; + + if ( GetActiveWeapon() ) + { + if ( !GetActiveWeapon()->CanHolster() ) + return false; + } + + return true; +} + +bool CCSPlayer::ShouldDoLargeFlinch( int nHitGroup, CBaseEntity *pAttacker ) +{ + if ( FBitSet( GetFlags(), FL_DUCKING ) ) + return FALSE; + + if ( nHitGroup == HITGROUP_LEFTLEG ) + return FALSE; + + if ( nHitGroup == HITGROUP_RIGHTLEG ) + return FALSE; + + CCSPlayer *pPlayer = ToCSPlayer( pAttacker ); + + if ( pPlayer == NULL || !pPlayer->IsPlayer() ) + pPlayer = NULL; + + if ( pPlayer ) + { + CWeaponCSBase *pWeapon = pPlayer->GetActiveCSWeapon(); + + if ( pWeapon ) + { + if ( pWeapon->GetCSWpnData().m_WeaponType == WEAPONTYPE_RIFLE || + pWeapon->GetCSWpnData().m_WeaponType == WEAPONTYPE_SHOTGUN || + pWeapon->GetCSWpnData().m_WeaponType == WEAPONTYPE_SNIPER_RIFLE ) + return true; + } + else + return false; + } + + return false; +} + +bool CCSPlayer::IsArmored( int nHitGroup ) +{ + bool bApplyArmor = false; + + if ( ArmorValue() > 0 ) + { + switch ( nHitGroup ) + { + case HITGROUP_GENERIC: + case HITGROUP_CHEST: + case HITGROUP_STOMACH: + case HITGROUP_LEFTARM: + case HITGROUP_RIGHTARM: + bApplyArmor = true; + break; + case HITGROUP_HEAD: + if ( m_bHasHelmet ) + { + bApplyArmor = true; + } + break; + default: + break; + } + } + + return bApplyArmor; +} + +void CCSPlayer::Pain( bool bHasArmour ) +{ + switch (m_LastHitGroup) + { + case HITGROUP_HEAD: + if (m_bHasHelmet) // He's wearing a helmet + { + EmitSound( "Player.DamageHelmet" ); + } + else // He's not wearing a helmet + { + EmitSound( "Player.DamageHeadShot" ); + } + break; + default: + if ( bHasArmour == false ) + { + EmitSound( "Flesh.BulletImpact" ); + } + else + { + EmitSound( "Player.DamageKevlar" ); + } + break; + } +} + +int CCSPlayer::OnTakeDamage( const CTakeDamageInfo &inputInfo ) +{ + CTakeDamageInfo info = inputInfo; + + CBaseEntity *pInflictor = info.GetInflictor(); + + if ( !pInflictor ) + return 0; + + if ( GetMoveType() == MOVETYPE_NOCLIP || GetMoveType() == MOVETYPE_OBSERVER ) + return 0; + + const float flArmorBonus = 0.5f; + float flArmorRatio = 0.5f; + float flDamage = info.GetDamage(); + + bool bFriendlyFire = CSGameRules()->IsFriendlyFireOn(); + + //============================================================================= + // HPE_BEGIN: + // [tj] Added properties for goose chase achievement + //============================================================================= + + CSGameRules()->PlayerTookDamage(this, inputInfo); + + //Check "Goose Chase" achievement + CCSPlayer *pAttacker = ToCSPlayer(info.GetAttacker()); + if (m_bIsDefusing && m_gooseChaseStep == GC_NONE && pAttacker && pAttacker->GetTeamNumber() != GetTeamNumber() ) + { + + //Count enemies + int livingEnemies = 0; + CTeam *pAttackerTeam = GetGlobalTeam( pAttacker->GetTeamNumber() ); + for ( int iPlayer=0; iPlayer < pAttackerTeam->GetNumPlayers(); iPlayer++ ) + { + CCSPlayer *pPlayer = ToCSPlayer( pAttackerTeam->GetPlayer( iPlayer ) ); + Assert( pPlayer ); + if ( !pPlayer ) + continue; + + Assert( pPlayer->GetTeamNumber() == pAttackerTeam->GetTeamNumber() ); + + if ( pPlayer->m_lifeState == LIFE_ALIVE ) + { + livingEnemies++; + } + } + + //Must be last enemy alive; + if (livingEnemies == 1) + { + m_gooseChaseStep = GC_SHOT_DURING_DEFUSE; + m_pGooseChaseDistractingPlayer = pAttacker; + } + } + + //============================================================================= + // HPE_END + //============================================================================= + + + // warn about team attacks + if ( bFriendlyFire && pInflictor->GetTeamNumber() == GetTeamNumber() && pInflictor != this && info.GetAttacker() != this ) + { + CCSPlayer *pCSAttacker = ToCSPlayer( pInflictor ); + if ( !pCSAttacker ) + pCSAttacker = ToCSPlayer( info.GetAttacker() ); + + if ( pCSAttacker ) + { + if ( !(pCSAttacker->m_iDisplayHistoryBits & DHF_FRIEND_INJURED) ) + { + pCSAttacker->HintMessage( "#Hint_try_not_to_injure_teammates", false ); + pCSAttacker->m_iDisplayHistoryBits |= DHF_FRIEND_INJURED; + } + + if ( (pCSAttacker->m_flLastAttackedTeammate + 0.6f) < gpGlobals->curtime ) + { + pCSAttacker->m_flLastAttackedTeammate = gpGlobals->curtime; + + // tell the rest of this player's team + Msg( "%s attacked a teammate\n", pCSAttacker->GetPlayerName() ); + for ( int i=1; i<=gpGlobals->maxClients; ++i ) + { + CBasePlayer *pPlayer = UTIL_PlayerByIndex( i ); + if ( pPlayer && pPlayer->GetTeamNumber() == GetTeamNumber() ) + { + ClientPrint( pPlayer, HUD_PRINTTALK, "#Game_teammate_attack", pCSAttacker->GetPlayerName() ); + } + } + } + } + } + + if ( bFriendlyFire || + pInflictor->GetTeamNumber() != GetTeamNumber() || + pInflictor == this || + info.GetAttacker() == this ) + { + if ( bFriendlyFire && (info.GetDamageType() & DMG_BLAST) == 0 ) + { + if ( pInflictor->GetTeamNumber() == GetTeamNumber() ) + { + flDamage *= 0.35; // bullets hurt teammates less + } + } + + if ( ShouldDoLargeFlinch( m_LastHitGroup, info.GetAttacker() ) ) + { + if ( GetAbsVelocity().Length() < 300 ) + { + m_flVelocityModifier = 0.65; + } + } + else + { + m_flVelocityModifier = 0.5; + } + +//============================================================================= +// HPE_BEGIN: +//============================================================================= + // [menglish] Store whether or not the knife did this damage as knives do bullet damage, + // so we need to specifically check the weapon here + bool bKnifeDamage = false; + CCSPlayer *pPlayer = ToCSPlayer( info.GetAttacker() ); + + if ( pPlayer ) + { + + // [paquin. forest] if this is blast damage, and we haven't opted out with a cvar, + // we need to get the armor ratio out of the inflictor + + if ( (info.GetDamageType() & DMG_BLAST) && !sv_legacy_grenade_damage.GetBool() ) + { + + // [paquin] if we know this is a grenade, use it's armor ratio, otherwise + // use the he grenade armor ratio + + CBaseCSGrenadeProjectile *pGrenade = dynamic_cast< CBaseCSGrenadeProjectile * >( pInflictor ); + CCSWeaponInfo* pWeaponInfo; + + if ( pGrenade && pGrenade->m_pWeaponInfo ) + { + pWeaponInfo = pGrenade->m_pWeaponInfo; + } + else + { + pWeaponInfo = GetWeaponInfo( WEAPON_HEGRENADE ); + } + + if ( pWeaponInfo ) + { + flArmorRatio *= pWeaponInfo->m_flArmorRatio; + } + } + else + { + CWeaponCSBase *pWeapon = pPlayer->GetActiveCSWeapon(); + + if ( pWeapon ) + { + flArmorRatio *= pWeapon->GetCSWpnData().m_flArmorRatio; + //Knives do bullet damage, so we need to specifically check the weapon here + bKnifeDamage = pWeapon->GetCSWpnData().m_WeaponType == WEAPONTYPE_KNIFE; + + if ( info.GetDamageType() & DMG_BULLET && !bKnifeDamage && pPlayer->GetTeam() != GetTeam() ) + { + CCS_GameStats.Event_ShotHit( pPlayer, info ); + } + } + } + } + +//============================================================================= +// HPE_END +//============================================================================= + + // keep track of amount of damage last sustained + m_lastDamageAmount = flDamage; + + // Deal with Armour + if ( ArmorValue() && !( info.GetDamageType() & (DMG_FALL | DMG_DROWN)) && IsArmored( m_LastHitGroup ) ) + { + float fDamageToHealth = flDamage * flArmorRatio; + float fDamageToArmor = (flDamage - fDamageToHealth) * flArmorBonus; + + int armorValue = ArmorValue(); + + // Does this use more armor than we have? + if (fDamageToArmor > armorValue ) + { + fDamageToHealth = flDamage - armorValue / flArmorBonus; + fDamageToArmor = armorValue; + armorValue = 0; + } + else + { + if ( fDamageToArmor < 0 ) + fDamageToArmor = 1; + + armorValue -= fDamageToArmor; + } + m_lastDamageArmor = (int)fDamageToArmor; + SetArmorValue(armorValue); + + //============================================================================= + // HPE_BEGIN: + // [tj] Handle headshot-surviving achievement + //============================================================================= + if (m_LastHitGroup == HITGROUP_HEAD && flDamage > m_iHealth && fDamageToHealth < m_iHealth) + { + m_bSurvivedHeadshotDueToHelmet = true; + } + //============================================================================= + // HPE_END + //============================================================================= + + flDamage = fDamageToHealth; + + info.SetDamage( flDamage ); + + if ( ArmorValue() <= 0.0) + m_bHasHelmet = false; + + if( !(info.GetDamageType() & DMG_FALL) ) + Pain( true /*has armor*/ ); + } + else + { + m_lastDamageArmor = 0; + if( !(info.GetDamageType() & DMG_FALL) ) + Pain( false /*no armor*/ ); + } + + // round damage to integer + m_lastDamageHealth = (int)flDamage; + info.SetDamage( m_lastDamageHealth ); + + if ( info.GetDamage() <= 0 ) + return 0; + + CSingleUserRecipientFilter user( this ); + user.MakeReliable(); + UserMessageBegin( user, "Damage" ); + WRITE_BYTE( (int)info.GetDamage() ); + WRITE_VEC3COORD( info.GetInflictor()->WorldSpaceCenter() ); +//============================================================================= +// HPE_BEGIN: +// [menglish] Send the info about where the player was hit +//============================================================================= + if ( !( info.GetDamageType() & DMG_BULLET ) || bKnifeDamage ) + { + WRITE_LONG( -1 ); + } + else + { + WRITE_LONG( m_LastHitBox ); + } + WRITE_VEC3COORD( m_vLastHitLocationObjectSpace ); + +//============================================================================= +// HPE_END +//============================================================================= + MessageEnd(); + + // Do special explosion damage effect + if ( info.GetDamageType() & DMG_BLAST ) + { + OnDamagedByExplosion( info ); + } + +//============================================================================= +// HPE_BEGIN: +// [menglish] Achievement award for kill stealing i.e. killing an enemy who was very damaged from other players +// [Forrest] Moved this check before RecordDamageTaken so that the damage currently being dealt by this player +// won't disqualify them from getting the achievement. +//============================================================================= + + if(m_iHealth - info.GetDamage() <= 0 && m_iHealth <= AchievementConsts::KillLowDamage_MaxHealthLeft) + { + bool onlyDamage = true; + CCSPlayer *pAttacker = ToCSPlayer(info.GetAttacker()); + if(pAttacker && pAttacker->GetTeamNumber() != GetTeamNumber()) + { + //Verify that the killer has not done damage to this player beforehand + FOR_EACH_LL( m_DamageTakenList, i ) + { + if( Q_strncmp( pAttacker->GetPlayerName(), m_DamageTakenList[i]->GetPlayerName(), MAX_PLAYER_NAME_LENGTH ) == 0 ) + { + onlyDamage = false; + break; + } + } + if(onlyDamage) + { + pAttacker->AwardAchievement(CSKillLowDamage); + } + } + } + +//============================================================================= +// HPE_END +//============================================================================= + +#if REPORT_PLAYER_DAMAGE + // damage output spew + char dmgtype[64]; + CTakeDamageInfo::DebugGetDamageTypeString( info.GetDamageType(), dmgtype, sizeof(dmgtype) ); + + if ( info.GetDamageType() & DMG_HEADSHOT ) + Q_strncat(dmgtype, "HEADSHOT", sizeof(dmgtype)); + + char outputString[256]; + Q_snprintf( outputString, sizeof(outputString), "%f: Player %s incoming %f damage from %s, type %s; applied %d health and %d armor\n", + gpGlobals->curtime, GetPlayerName(), + inputInfo.GetDamage(), info.GetInflictor()->GetDebugName(), dmgtype, + m_lastDamageHealth, m_lastDamageArmor); + + Msg(outputString); +#endif + + + if ( pPlayer ) + { + // Record for the shooter + pPlayer->RecordDamageGiven( GetPlayerName(), info.GetDamage() ); + + // And for the victim + RecordDamageTaken( pPlayer->GetPlayerName(), info.GetDamage() ); + } + else + { + RecordDamageTaken( "world", info.GetDamage() ); + } + + m_vecTotalBulletForce += info.GetDamageForce(); + + gamestats->Event_PlayerDamage( this, info ); + + return CBaseCombatCharacter::OnTakeDamage( info ); + } + else + { + return 0; + } +} + + +//MIKETODO: this probably should let the shield model catch the trace attacks. +bool CCSPlayer::IsHittingShield( const Vector &vecDirection, trace_t *ptr ) +{ + if ( HasShield() == false ) + return false; + + if ( IsShieldDrawn() == false ) + return false; + + float flDot; + Vector vForward; + Vector2D vec2LOS = vecDirection.AsVector2D(); + AngleVectors( GetLocalAngles(), &vForward ); + + Vector2DNormalize( vForward.AsVector2D() ); + Vector2DNormalize( vec2LOS ); + + flDot = DotProduct2D ( vec2LOS , vForward.AsVector2D() ); + + if ( flDot < -0.87f ) + return true; + + return false; +} + + +void CCSPlayer::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator ) +{ + bool bShouldBleed = true; + bool bShouldSpark = false; + bool bHitShield = IsHittingShield( vecDir, ptr ); + + CBasePlayer *pAttacker = (CBasePlayer*)ToBasePlayer( info.GetAttacker() ); + + // show blood for firendly fire only if FF is on + if ( pAttacker && ( GetTeamNumber() == pAttacker->GetTeamNumber() ) ) + bShouldBleed = CSGameRules()->IsFriendlyFireOn(); + + if ( m_takedamage != DAMAGE_YES ) + return; + + m_LastHitGroup = ptr->hitgroup; +//============================================================================= +// HPE_BEGIN: +// [menglish] Used when calculating the position this player was hit at in the bone space +//============================================================================= + m_LastHitBox = ptr->hitbox; +//============================================================================= +// HPE_END +//============================================================================= + + m_nForceBone = ptr->physicsbone; //Save this bone for ragdoll + + float flDamage = info.GetDamage(); + + bool bHeadShot = false; + + if ( bHitShield ) + { + flDamage = 0; + bShouldBleed = false; + bShouldSpark = true; + } + else if( info.GetDamageType() & DMG_BLAST ) + { + if ( ArmorValue() > 0 ) + bShouldBleed = false; + + if ( bShouldBleed == true ) + { + // punch view if we have no armor + QAngle punchAngle = GetPunchAngle(); + punchAngle.x = flDamage * -0.1; + + if ( punchAngle.x < -4 ) + punchAngle.x = -4; + + SetPunchAngle( punchAngle ); + } + } + else + { +//============================================================================= +// HPE_BEGIN: +// [menglish] Calculate the position this player was hit at in the bone space +//============================================================================= + + matrix3x4_t boneTransformToWorld, boneTransformToObject; + GetBoneTransform(GetHitboxBone(ptr->hitbox), boneTransformToWorld); + MatrixInvert(boneTransformToWorld, boneTransformToObject); + VectorTransform(ptr->endpos, boneTransformToObject, m_vLastHitLocationObjectSpace); + +//============================================================================= +// HPE_END +//============================================================================= + + switch ( ptr->hitgroup ) + { + case HITGROUP_GENERIC: + break; + + case HITGROUP_HEAD: + + if ( m_bHasHelmet ) + { +// bShouldBleed = false; + bShouldSpark = true; + } + + flDamage *= 4; + + if ( !m_bHasHelmet ) + { + QAngle punchAngle = GetPunchAngle(); + punchAngle.x = flDamage * -0.5; + + if ( punchAngle.x < -12 ) + punchAngle.x = -12; + + punchAngle.z = flDamage * random->RandomFloat(-1,1); + + if ( punchAngle.z < -9 ) + punchAngle.z = -9; + + else if ( punchAngle.z > 9 ) + punchAngle.z = 9; + + SetPunchAngle( punchAngle ); + } + + bHeadShot = true; + + break; + + case HITGROUP_CHEST: + + flDamage *= 1.0; + + if ( ArmorValue() <= 0 ) + { + QAngle punchAngle = GetPunchAngle(); + punchAngle.x = flDamage * -0.1; + + if ( punchAngle.x < -4 ) + punchAngle.x = -4; + + SetPunchAngle( punchAngle ); + } + break; + + case HITGROUP_STOMACH: + + flDamage *= 1.25; + + if ( ArmorValue() <= 0 ) + { + QAngle punchAngle = GetPunchAngle(); + punchAngle.x = flDamage * -0.1; + + if ( punchAngle.x < -4 ) + punchAngle.x = -4; + + SetPunchAngle( punchAngle ); + } + + break; + + case HITGROUP_LEFTARM: + case HITGROUP_RIGHTARM: + flDamage *= 1.0; + break; + + case HITGROUP_LEFTLEG: + case HITGROUP_RIGHTLEG: + flDamage *= 0.75; + break; + + default: + break; + } + } + + // Since this code only runs on the server, make sure it shows the tempents it creates. + CDisablePredictionFiltering disabler; + + if ( bShouldBleed ) + { + // This does smaller splotches on the guy and splats blood on the world. + TraceBleed( flDamage, vecDir, ptr, info.GetDamageType() ); + + CEffectData data; + data.m_vOrigin = ptr->endpos; + data.m_vNormal = vecDir * -1; + data.m_nEntIndex = ptr->m_pEnt ? ptr->m_pEnt->entindex() : 0; + data.m_flMagnitude = flDamage; + + // reduce blood effect if target has armor + if ( ArmorValue() > 0 ) + data.m_flMagnitude *= 0.5f; + + // reduce blood effect if target is hit in the helmet + if ( ptr->hitgroup == HITGROUP_HEAD && bShouldSpark ) + data.m_flMagnitude *= 0.5; + + DispatchEffect( "csblood", data ); + } + if ( ( ptr->hitgroup == HITGROUP_HEAD || bHitShield ) && bShouldSpark ) // they hit a helmet + { + // show metal spark effect + g_pEffects->Sparks( ptr->endpos, 1, 1, &ptr->plane.normal ); + } + + if ( !bHitShield ) + { + CTakeDamageInfo subInfo = info; + + subInfo.SetDamage( flDamage ); + + if( bHeadShot ) + subInfo.AddDamageType( DMG_HEADSHOT ); + + AddMultiDamage( subInfo, this ); + } +} + + +void CCSPlayer::Reset() +{ + ResetFragCount(); + ResetDeathCount(); + m_iAccount = 0; + AddAccount( -16000, false ); + + //remove any weapons they bought before the round started + RemoveAllItems( true ); + + //RemoveShield(); + + AddAccount( CSGameRules()->GetStartMoney(), true ); +} + +//----------------------------------------------------------------------------- +// Purpose: Displays a hint message to the player +// Input : *pMessage - +// bDisplayIfDead - +// bOverrideClientSettings - +//----------------------------------------------------------------------------- +void CCSPlayer::HintMessage( const char *pMessage, bool bDisplayIfDead, bool bOverrideClientSettings ) +{ + if ( ( !bDisplayIfDead && !IsAlive() ) || !IsNetClient() || !m_pHintMessageQueue ) + return; + + if ( bOverrideClientSettings || m_bShowHints ) + m_pHintMessageQueue->AddMessage( pMessage ); +} + +void CCSPlayer::AddAccount( int amount, bool bTrackChange, bool bItemBought, const char *pItemName ) +{ + m_iAccount += amount; + + //============================================================================= + // HPE_BEGIN: + // [menglish] Description of reason for change + //============================================================================= + + if(amount > 0) + { + CCS_GameStats.Event_MoneyEarned( this, amount ); + } + else if( amount < 0 && bItemBought) + { + CCS_GameStats.Event_MoneySpent( this, ABS(amount), pItemName ); + } + + //============================================================================= + // HPE_END + //============================================================================= + + if ( m_iAccount < 0 ) + m_iAccount = 0; + else if ( m_iAccount > 16000 ) + m_iAccount = 16000; +} + +void CCSPlayer::MarkAsNotReceivingMoneyNextRound() +{ + m_receivesMoneyNextRound = false; +} + +bool CCSPlayer::DoesPlayerGetRoundStartMoney() +{ + return m_receivesMoneyNextRound; +} + +CCSPlayer* CCSPlayer::Instance( int iEnt ) +{ + return dynamic_cast< CCSPlayer* >( CBaseEntity::Instance( INDEXENT( iEnt ) ) ); +} + + +void CCSPlayer::DropC4() +{ +} + + +bool CCSPlayer::HasDefuser() +{ + return m_bHasDefuser; +} + +void CCSPlayer::RemoveDefuser() +{ + m_bHasDefuser = false; +} + +void CCSPlayer::GiveDefuser(bool bPickedUp /* = false */) +{ + if ( !m_bHasDefuser ) + { + IGameEvent * event = gameeventmanager->CreateEvent( "item_pickup" ); + if( event ) + { + event->SetInt( "userid", GetUserID() ); + event->SetString( "item", "defuser" ); + gameeventmanager->FireEvent( event ); + } + } + + m_bHasDefuser = true; + + //============================================================================= + // HPE_BEGIN: + // [dwenger] Added for fun-fact support + //============================================================================= + + m_bPickedUpDefuser = bPickedUp; + + //============================================================================= + // HPE_END + //============================================================================= +} + +// player blinded by a flashbang +void CCSPlayer::Blind( float holdTime, float fadeTime, float startingAlpha ) +{ + // Don't flash a spectator. + color32 clr = {255, 255, 255, 255}; + + clr.a = startingAlpha; + + // estimate when we can see again + float oldBlindUntilTime = m_blindUntilTime; + float oldBlindStartTime = m_blindStartTime; + m_blindUntilTime = MAX( m_blindUntilTime, gpGlobals->curtime + holdTime + 0.5f * fadeTime ); + m_blindStartTime = gpGlobals->curtime; + + // Spectators get a lessened flash. + if ( (GetObserverMode() != OBS_MODE_NONE) && (GetObserverMode() != OBS_MODE_IN_EYE) ) + { + if ( !mp_fadetoblack.GetBool() ) + { + clr.a = 150; + + fadeTime = MIN(fadeTime, 0.5f); // make sure the spectator flashbang time is 1/2 second or less. + holdTime = MIN(holdTime, fadeTime * 0.5f); // adjust the hold time to match the fade time. + UTIL_ScreenFade( this, clr, fadeTime, holdTime, FFADE_IN ); + } + } + else + { + fadeTime /= 1.4; + + if ( gpGlobals->curtime > oldBlindUntilTime ) + { + // The previous flashbang is wearing off, or completely gone + m_flFlashDuration = fadeTime; + m_flFlashMaxAlpha = startingAlpha; + } + else + { + // The previous flashbang is still going strong - only extend the duration + float remainingDuration = oldBlindStartTime + m_flFlashDuration - gpGlobals->curtime; + + m_flFlashDuration = MAX( remainingDuration, fadeTime ); + m_flFlashMaxAlpha = MAX( m_flFlashMaxAlpha, startingAlpha ); + } + + // allow bots to react + IGameEvent * event = gameeventmanager->CreateEvent( "player_blind" ); + if ( event ) + { + event->SetInt( "userid", GetUserID() ); + gameeventmanager->FireEvent( event ); + } + } +} + +void CCSPlayer::Deafen( float flDistance ) +{ + // Spectators don't get deafened + if ( (GetObserverMode() == OBS_MODE_NONE) || (GetObserverMode() == OBS_MODE_IN_EYE) ) + { + // dsp presets are defined in hl2/scripts/dsp_presets.txt + + int effect; + + if( flDistance < 600 ) + { + effect = 134; + } + else if( flDistance < 800 ) + { + effect = 135; + } + else if( flDistance < 1000 ) + { + effect = 136; + } + else + { + // too far for us to get an effect + return; + } + + CSingleUserRecipientFilter user( this ); + enginesound->SetPlayerDSP( user, effect, false ); + + //TODO: bots can't hear sound for a while? + } +} + +void CCSPlayer::GiveShield( void ) +{ +#ifdef CS_SHIELD_ENABLED + m_bHasShield = true; + m_bShieldDrawn = false; + + if ( HasSecondaryWeapon() ) + { + CBaseCombatWeapon *pWeapon = Weapon_GetSlot( WEAPON_SLOT_PISTOL ); + pWeapon->SetModel( pWeapon->GetViewModel() ); + pWeapon->Deploy(); + } + + CBaseViewModel *pVM = GetViewModel( 1 ); + + if ( pVM ) + { + ShowViewModel( true ); + pVM->RemoveEffects( EF_NODRAW ); + pVM->SetWeaponModel( SHIELD_VIEW_MODEL, GetActiveWeapon() ); + pVM->SendViewModelMatchingSequence( 1 ); + } +#endif +} + +void CCSPlayer::RemoveShield( void ) +{ +#ifdef CS_SHIELD_ENABLED + m_bHasShield = false; + + CBaseViewModel *pVM = GetViewModel( 1 ); + + if ( pVM ) + { + pVM->AddEffects( EF_NODRAW ); + } +#endif +} + +void CCSPlayer::RemoveAllItems( bool removeSuit ) +{ + if( HasDefuser() ) + { + RemoveDefuser(); + } + + if ( HasShield() ) + { + RemoveShield(); + } + + m_bHasNightVision = false; + m_bNightVisionOn = false; + + //============================================================================= + // HPE_BEGIN: + // [dwenger] Added for fun-fact support + //============================================================================= + + m_bPickedUpDefuser = false; + m_bDefusedWithPickedUpKit = false; + + //============================================================================= + // HPE_END + //============================================================================= + + if ( removeSuit ) + { + m_bHasHelmet = false; + SetArmorValue( 0 ); + } + + BaseClass::RemoveAllItems( removeSuit ); +} + +void CCSPlayer::ObserverRoundRespawn() +{ + ClearFlashbangScreenFade(); + + // did we change our name last round? + if ( m_szNewName[0] != 0 ) + { + // ... and force the name change now. After this happens, the gamerules will get + // a ClientSettingsChanged callback from the above ClientCommand, but the name + // matches what we're setting here, so it will do nothing. + ChangeName( m_szNewName ); + m_szNewName[0] = 0; + } +} + +void CCSPlayer::RoundRespawn() +{ + //MIKETODO: menus + //if ( m_iMenu != Menu_ChooseAppearance ) + { + // Put them back into the game. + StopObserverMode(); + State_Transition( STATE_ACTIVE ); + respawn( this, false ); + m_nButtons = 0; + SetNextThink( TICK_NEVER_THINK ); + } + + m_receivesMoneyNextRound = true; // reset this variable so they can receive their cash next round. + + //If they didn't die, this will print out their damage info + OutputDamageGiven(); + OutputDamageTaken(); + ResetDamageCounters(); +} + +void CCSPlayer::CheckTKPunishment( void ) +{ + // teamkill punishment.. + if ( (m_bJustKilledTeammate == true) && mp_tkpunish.GetInt() ) + { + m_bJustKilledTeammate = false; + m_bPunishedForTK = true; + CommitSuicide(); + } +} + +CWeaponCSBase* CCSPlayer::GetActiveCSWeapon() const +{ + return dynamic_cast< CWeaponCSBase* >( GetActiveWeapon() ); +} + +void CCSPlayer::PreThink() +{ + BaseClass::PreThink(); + if ( m_bAutoReload ) + { + m_bAutoReload = false; + m_nButtons |= IN_RELOAD; + } + + if ( m_afButtonLast != m_nButtons ) + m_flLastMovement = gpGlobals->curtime; + + if ( g_fGameOver ) + return; + + State_PreThink(); + + if ( m_pHintMessageQueue ) + m_pHintMessageQueue->Update(); + + //Reset bullet force accumulator, only lasts one frame + m_vecTotalBulletForce = vec3_origin; + + if ( mp_autokick.GetBool() && !IsBot() && !IsHLTV() && !IsAutoKickDisabled() ) + { + if ( m_flLastMovement + CSGameRules()->GetRoundLength()*2 < gpGlobals->curtime ) + { + UTIL_ClientPrintAll( HUD_PRINTCONSOLE, "#Game_idle_kick", GetPlayerName() ); + engine->ServerCommand( UTIL_VarArgs( "kickid %d\n", GetUserID() ) ); + m_flLastMovement = gpGlobals->curtime; + } + } +#ifndef _XBOX + // CS would like their players to continue to update their LastArea since it is displayed in the hud voice chat UI + // But we won't do the population tracking while dead. + CNavArea *area = TheNavMesh->GetNavArea( GetAbsOrigin(), 1000 ); + if (area && area != m_lastNavArea) + { + 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 ); + } + } + } +#endif +} + +void CCSPlayer::MoveToNextIntroCamera() +{ + m_pIntroCamera = gEntList.FindEntityByClassname( m_pIntroCamera, "point_viewcontrol" ); + + // if m_pIntroCamera is NULL we just were at end of list, start searching from start again + if(!m_pIntroCamera) + m_pIntroCamera = gEntList.FindEntityByClassname(m_pIntroCamera, "point_viewcontrol"); + + // find the target + CBaseEntity *Target = NULL; + + if( m_pIntroCamera ) + { + Target = gEntList.FindEntityByName( NULL, STRING(m_pIntroCamera->m_target) ); + } + + // if we still couldn't find a camera, goto T spawn + if(!m_pIntroCamera) + m_pIntroCamera = gEntList.FindEntityByClassname(m_pIntroCamera, "info_player_terrorist"); + + SetViewOffset( vec3_origin ); // no view offset + UTIL_SetSize( this, vec3_origin, vec3_origin ); // no bbox + + if( !Target ) //if there are no cameras(or the camera has no target, find a spawn point and black out the screen + { + if ( m_pIntroCamera.IsValid() ) + SetAbsOrigin( m_pIntroCamera->GetAbsOrigin() + VEC_VIEW ); + + SetAbsAngles( QAngle( 0, 0, 0 ) ); + + m_pIntroCamera = NULL; // never update again + return; + } + + + Vector vCamera = Target->GetAbsOrigin() - m_pIntroCamera->GetAbsOrigin(); + Vector vIntroCamera = m_pIntroCamera->GetAbsOrigin(); + + VectorNormalize( vCamera ); + + QAngle CamAngles; + VectorAngles( vCamera, CamAngles ); + + SetAbsOrigin( vIntroCamera ); + SetAbsAngles( CamAngles ); + SnapEyeAngles( CamAngles ); + m_fIntroCamTime = gpGlobals->curtime + 6; +} + +class NotVIP +{ +public: + bool operator()( CBasePlayer *player ) + { + CCSPlayer *csPlayer = static_cast< CCSPlayer * >(player); + csPlayer->MakeVIP( false ); + + return true; + } +}; + +// Expose the VIP selection to plugins, since we don't have an official VIP mode. This +// allows plugins to access the (limited) VIP functionality already present (scoreboard +// identification and radar color). +CON_COMMAND( cs_make_vip, "Marks a player as the VIP" ) +{ + if ( !UTIL_IsCommandIssuedByServerAdmin() ) + return; + + if ( args.ArgC() != 2 ) + { + return; + } + + CCSPlayer *player = static_cast< CCSPlayer * >(UTIL_PlayerByIndex( atoi( args[1] ) )); + if ( !player ) + { + // Invalid value clears out VIP + NotVIP notVIP; + ForEachPlayer( notVIP ); + return; + } + + player->MakeVIP( true ); +} + +void CCSPlayer::MakeVIP( bool isVIP ) +{ + if ( isVIP ) + { + NotVIP notVIP; + ForEachPlayer( notVIP ); + } + m_isVIP = isVIP; +} + +bool CCSPlayer::IsVIP() const +{ + return m_isVIP; +} + +void CCSPlayer::DropShield( void ) +{ +#ifdef CS_SHIELD_ENABLED + //Drop an item_defuser + Vector vForward, vRight; + AngleVectors( GetAbsAngles(), &vForward, &vRight, NULL ); + + RemoveShield(); + + CBaseAnimating *pShield = (CBaseAnimating *)CBaseEntity::Create( "item_shield", WorldSpaceCenter(), GetLocalAngles() ); + pShield->ApplyAbsVelocityImpulse( vForward * 200 + vRight * random->RandomFloat( -50, 50 ) ); + + CBaseCombatWeapon *pActive = GetActiveWeapon(); + + if ( pActive ) + { + pActive->Deploy(); + } +#endif +} + +void CCSPlayer::SetShieldDrawnState( bool bState ) +{ +#ifdef CS_SHIELD_ENABLED + m_bShieldDrawn = bState; +#endif +} + +bool CCSPlayer::CSWeaponDrop( CBaseCombatWeapon *pWeapon, bool bDropShield, bool bThrowForward ) +{ + bool bSuccess = false; + + if ( HasShield() && bDropShield == true ) + { + DropShield(); + return true; + } + + if ( pWeapon ) + { + Vector vForward; + + AngleVectors( EyeAngles(), &vForward, NULL, NULL ); + //GetVectors( &vForward, NULL, NULL ); + Vector vTossPos = WorldSpaceCenter(); + + if( bThrowForward ) + vTossPos = vTossPos + vForward * 64; + + Weapon_Drop( pWeapon, &vTossPos, NULL ); + + pWeapon->SetSolidFlags( FSOLID_NOT_STANDABLE | FSOLID_TRIGGER | FSOLID_USE_TRIGGER_BOUNDS ); + pWeapon->SetMoveCollide( MOVECOLLIDE_FLY_BOUNCE ); + + CWeaponCSBase *pCSWeapon = dynamic_cast< CWeaponCSBase* >( pWeapon ); + + if( pCSWeapon ) + { + pCSWeapon->SetWeaponModelIndex( pCSWeapon->GetCSWpnData().szWorldModel ); + + //Find out the index of the ammo type + int iAmmoIndex = pCSWeapon->GetPrimaryAmmoType(); + + //If it has an ammo type, find out how much the player has + if( iAmmoIndex != -1 ) + { + // Check to make sure we don't have other weapons using this ammo type + bool bAmmoTypeInUse = false; + if ( IsAlive() && GetHealth() > 0 ) + { + for ( int i=0; i<MAX_WEAPONS; ++i ) + { + CBaseCombatWeapon *pOtherWeapon = GetWeapon(i); + if ( pOtherWeapon && pOtherWeapon != pWeapon && pOtherWeapon->GetPrimaryAmmoType() == iAmmoIndex ) + { + bAmmoTypeInUse = true; + break; + } + } + } + + if ( !bAmmoTypeInUse ) + { + int iAmmoToDrop = GetAmmoCount( iAmmoIndex ); + + //Add this much to the dropped weapon + pCSWeapon->SetExtraAmmoCount( iAmmoToDrop ); + + //Remove all ammo of this type from the player + SetAmmoCount( 0, iAmmoIndex ); + } + } + } + + //========================================= + // Teleport the weapon to the player's hand + //========================================= + int iBIndex = -1; + int iWeaponBoneIndex = -1; + + MDLCACHE_CRITICAL_SECTION(); + CStudioHdr *hdr = pWeapon->GetModelPtr(); + // If I have a hand, set the weapon position to my hand bone position. + if ( hdr && hdr->numbones() > 0 ) + { + // Assume bone zero is the root + for ( iWeaponBoneIndex = 0; iWeaponBoneIndex < hdr->numbones(); ++iWeaponBoneIndex ) + { + iBIndex = LookupBone( hdr->pBone( iWeaponBoneIndex )->pszName() ); + // Found one! + if ( iBIndex != -1 ) + { + break; + } + } + + if ( iWeaponBoneIndex == hdr->numbones() ) + return true; + + if ( iBIndex == -1 ) + { + iBIndex = LookupBone( "ValveBiped.Bip01_R_Hand" ); + } + } + else + { + iBIndex = LookupBone( "ValveBiped.Bip01_R_Hand" ); + } + + if ( iBIndex != -1) + { + Vector origin; + QAngle angles; + matrix3x4_t transform; + + // Get the transform for the weapon bonetoworldspace in the NPC + GetBoneTransform( iBIndex, transform ); + + // find offset of root bone from origin in local space + // Make sure we're detached from hierarchy before doing this!!! + pWeapon->StopFollowingEntity(); + pWeapon->SetAbsOrigin( Vector( 0, 0, 0 ) ); + pWeapon->SetAbsAngles( QAngle( 0, 0, 0 ) ); + pWeapon->InvalidateBoneCache(); + matrix3x4_t rootLocal; + pWeapon->GetBoneTransform( iWeaponBoneIndex, rootLocal ); + + // invert it + matrix3x4_t rootInvLocal; + MatrixInvert( rootLocal, rootInvLocal ); + + matrix3x4_t weaponMatrix; + ConcatTransforms( transform, rootInvLocal, weaponMatrix ); + MatrixAngles( weaponMatrix, angles, origin ); + + pWeapon->Teleport( &origin, &angles, NULL ); + + //Have to teleport the physics object as well + + IPhysicsObject *pWeaponPhys = pWeapon->VPhysicsGetObject(); + + if( pWeaponPhys ) + { + Vector vPos; + QAngle vAngles; + pWeaponPhys->GetPosition( &vPos, &vAngles ); + pWeaponPhys->SetPosition( vPos, angles, true ); + + AngularImpulse angImp(0,0,0); + Vector vecAdd = GetAbsVelocity(); + pWeaponPhys->AddVelocity( &vecAdd, &angImp ); + } + } + + bSuccess = true; + } + + return bSuccess; +} + + +bool CCSPlayer::DropRifle( bool fromDeath ) +{ + bool bSuccess = false; + + CBaseCombatWeapon *pWeapon = Weapon_GetSlot( WEAPON_SLOT_RIFLE ); + if ( pWeapon ) + { + bSuccess = CSWeaponDrop( pWeapon, false ); + } + + //============================================================================= + // HPE_BEGIN: + // [menglish] Add the dropped weapon to the dropped equipment list + //============================================================================= + if( fromDeath && bSuccess ) + { + m_hDroppedEquipment[DROPPED_WEAPON] = static_cast<CBaseEntity *>(pWeapon); + } + //============================================================================= + // HPE_END + //============================================================================= + + return bSuccess; +} + + +bool CCSPlayer::DropPistol( bool fromDeath ) +{ + bool bSuccess = false; + + CBaseCombatWeapon *pWeapon = Weapon_GetSlot( WEAPON_SLOT_PISTOL ); + if ( pWeapon ) + { + bSuccess = CSWeaponDrop( pWeapon, false ); + m_bUsingDefaultPistol = false; + } + //============================================================================= + // HPE_BEGIN: + // [menglish] Add the dropped weapon to the dropped equipment list + //============================================================================= + if( fromDeath && bSuccess ) + { + m_hDroppedEquipment[DROPPED_WEAPON] = static_cast<CBaseEntity *>(pWeapon); + } + //============================================================================= + // HPE_END + //============================================================================= + + return bSuccess; +} + +bool CCSPlayer::HasPrimaryWeapon( void ) +{ + bool bSuccess = false; + + CBaseCombatWeapon *pWeapon = Weapon_GetSlot( WEAPON_SLOT_RIFLE ); + + if ( pWeapon ) + { + bSuccess = true; + } + + return bSuccess; +} + + +bool CCSPlayer::HasSecondaryWeapon( void ) +{ + bool bSuccess = false; + + CBaseCombatWeapon *pWeapon = Weapon_GetSlot( WEAPON_SLOT_PISTOL ); + if ( pWeapon ) + { + bSuccess = true; + } + + return bSuccess; +} + +bool CCSPlayer::IsInBuyZone() +{ + return m_bInBuyZone && !IsVIP(); +} + +bool CCSPlayer::CanPlayerBuy( bool display ) +{ + // is the player in a buy zone? + if ( !IsInBuyZone() ) + { + return false; + } + + CCSGameRules* mp = CSGameRules(); + + // is the player alive? + if ( m_lifeState != LIFE_ALIVE ) + { + return false; + } + + int buyTime = (int)(mp_buytime.GetFloat() * 60); + + if ( mp->IsBuyTimeElapsed() ) + { + if ( display == true ) + { + char strBuyTime[16]; + Q_snprintf( strBuyTime, sizeof( strBuyTime ), "%d", buyTime ); + ClientPrint( this, HUD_PRINTCENTER, "#Cant_buy", strBuyTime ); + } + + return false; + } + + if ( m_bIsVIP ) + { + if ( display == true ) + ClientPrint( this, HUD_PRINTCENTER, "#VIP_cant_buy" ); + + return false; + } + + if ( mp->m_bCTCantBuy && ( GetTeamNumber() == TEAM_CT ) ) + { + if ( display == true ) + ClientPrint( this, HUD_PRINTCENTER, "#CT_cant_buy" ); + + return false; + } + + if ( mp->m_bTCantBuy && ( GetTeamNumber() == TEAM_TERRORIST ) ) + { + if ( display == true ) + ClientPrint( this, HUD_PRINTCENTER, "#Terrorist_cant_buy" ); + + return false; + } + + return true; +} + + +BuyResult_e CCSPlayer::AttemptToBuyVest( void ) +{ + int iKevlarPrice = KEVLAR_PRICE; + + if ( CSGameRules()->IsBlackMarket() ) + { + iKevlarPrice = CSGameRules()->GetBlackMarketPriceForWeapon( WEAPON_KEVLAR ); + } + + if ( ArmorValue() >= 100 ) + { + if( !m_bIsInAutoBuy && !m_bIsInRebuy ) + ClientPrint( this, HUD_PRINTCENTER, "#Already_Have_Kevlar" ); + return BUY_ALREADY_HAVE; + } + else if ( m_iAccount < iKevlarPrice ) + { + if( !m_bIsInAutoBuy && !m_bIsInRebuy ) + ClientPrint( this, HUD_PRINTCENTER, "#Not_Enough_Money" ); + return BUY_CANT_AFFORD; + } + else + { + if ( m_bHasHelmet ) + { + if( !m_bIsInAutoBuy && !m_bIsInRebuy ) + ClientPrint( this, HUD_PRINTCENTER, "#Already_Have_Helmet_Bought_Kevlar" ); + } + + IGameEvent * event = gameeventmanager->CreateEvent( "item_pickup" ); + if( event ) + { + event->SetInt( "userid", GetUserID() ); + event->SetString( "item", "vest" ); + gameeventmanager->FireEvent( event ); + } + + GiveNamedItem( "item_kevlar" ); + AddAccount( -iKevlarPrice, true, true, "item_kevlar" ); + BlackMarketAddWeapon( "item_kevlar", this ); + return BUY_BOUGHT; + } +} + + +BuyResult_e CCSPlayer::AttemptToBuyAssaultSuit( void ) +{ + // WARNING: This price logic also exists in C_CSPlayer::GetCurrentAssaultSuitPrice + // and must be kept in sync if changes are made. + + int fullArmor = ArmorValue() >= 100 ? 1 : 0; + + int price = 0, enoughMoney = 0; + + int iHelmetPrice = HELMET_PRICE; + int iKevlarPrice = KEVLAR_PRICE; + int iAssaultSuitPrice = ASSAULTSUIT_PRICE; + + if ( CSGameRules()->IsBlackMarket() ) + { + iKevlarPrice = CSGameRules()->GetBlackMarketPriceForWeapon( WEAPON_KEVLAR ); + iAssaultSuitPrice = CSGameRules()->GetBlackMarketPriceForWeapon( WEAPON_ASSAULTSUIT ); + + iHelmetPrice = iAssaultSuitPrice - iKevlarPrice; + } + + if ( fullArmor && m_bHasHelmet ) + { + if( !m_bIsInAutoBuy && !m_bIsInRebuy ) + ClientPrint( this, HUD_PRINTCENTER, "#Already_Have_Kevlar_Helmet" ); + return BUY_ALREADY_HAVE; + } + else if ( fullArmor && !m_bHasHelmet && m_iAccount >= iHelmetPrice ) + { + enoughMoney = 1; + price = iHelmetPrice; + if( !m_bIsInAutoBuy && !m_bIsInRebuy ) + ClientPrint( this, HUD_PRINTCENTER, "#Already_Have_Kevlar_Bought_Helmet" ); + } + else if ( !fullArmor && m_bHasHelmet && m_iAccount >= iKevlarPrice ) + { + enoughMoney = 1; + price = iKevlarPrice; + if( !m_bIsInAutoBuy && !m_bIsInRebuy ) + ClientPrint( this, HUD_PRINTCENTER, "#Already_Have_Helmet_Bought_Kevlar" ); + } + else if ( m_iAccount >= iAssaultSuitPrice ) + { + enoughMoney = 1; + price = iAssaultSuitPrice; + } + + // process the result + if ( !enoughMoney ) + { + if( !m_bIsInAutoBuy && !m_bIsInRebuy ) + ClientPrint( this, HUD_PRINTCENTER, "#Not_Enough_Money" ); + return BUY_CANT_AFFORD; + } + else + { + IGameEvent * event = gameeventmanager->CreateEvent( "item_pickup" ); + if( event ) + { + event->SetInt( "userid", GetUserID() ); + event->SetString( "item", "vesthelm" ); + gameeventmanager->FireEvent( event ); + } + + GiveNamedItem( "item_assaultsuit" ); + AddAccount( -price, true, true, "item_assaultsuit" ); + BlackMarketAddWeapon( "item_assaultsuit", this ); + return BUY_BOUGHT; + } +} + +BuyResult_e CCSPlayer::AttemptToBuyShield( void ) +{ +#ifdef CS_SHIELD_ENABLED + if ( HasShield() ) // prevent this guy from buying more than 1 Defuse Kit + { + if( !m_bIsInAutoBuy && !m_bIsInRebuy ) + ClientPrint( this, HUD_PRINTCENTER, "#Already_Have_One" ); + return BUY_ALREADY_HAVE; + } + else if ( m_iAccount < SHIELD_PRICE ) + { + if( !m_bIsInAutoBuy && !m_bIsInRebuy ) + ClientPrint( this, HUD_PRINTCENTER, "#Not_Enough_Money" ); + return BUY_CANT_AFFORD; + } + else + { + if ( HasSecondaryWeapon() ) + { + CBaseCombatWeapon *pWeapon = Weapon_GetSlot( WEAPON_SLOT_PISTOL ); + CWeaponCSBase *pCSWeapon = dynamic_cast< CWeaponCSBase* >( pWeapon ); + + if ( pCSWeapon && pCSWeapon->GetCSWpnData().m_bCanUseWithShield == false ) + return; + } + + if ( HasPrimaryWeapon() ) + DropRifle(); + + GiveShield(); + + CPASAttenuationFilter filter( this, "Player.PickupWeapon" ); + EmitSound( filter, entindex(), "Player.PickupWeapon" ); + + m_bAnythingBought = true; + AddAccount( -SHIELD_PRICE, true, true, "item_shield" ); + return BUY_BOUGHT; + } +#else + ClientPrint( this, HUD_PRINTCENTER, "Tactical shield disabled" ); + return BUY_NOT_ALLOWED; +#endif +} + +BuyResult_e CCSPlayer::AttemptToBuyDefuser( void ) +{ + CCSGameRules *MPRules = CSGameRules(); + + if( ( GetTeamNumber() == TEAM_CT ) && MPRules->IsBombDefuseMap() ) + { + if ( HasDefuser() ) // prevent this guy from buying more than 1 Defuse Kit + { + if( !m_bIsInAutoBuy && !m_bIsInRebuy ) + ClientPrint( this, HUD_PRINTCENTER, "#Already_Have_One" ); + return BUY_ALREADY_HAVE; + } + else if ( m_iAccount < DEFUSEKIT_PRICE ) + { + if( !m_bIsInAutoBuy && !m_bIsInRebuy ) + ClientPrint( this, HUD_PRINTCENTER, "#Not_Enough_Money" ); + return BUY_CANT_AFFORD; + } + else + { + GiveDefuser(); + + CPASAttenuationFilter filter( this, "Player.PickupWeapon" ); + EmitSound( filter, entindex(), "Player.PickupWeapon" ); + + AddAccount( -DEFUSEKIT_PRICE, true, true, "item_defuser" ); + return BUY_BOUGHT; + } + } + + return BUY_NOT_ALLOWED; +} + +BuyResult_e CCSPlayer::AttemptToBuyNightVision( void ) +{ + int iNVGPrice = NVG_PRICE; + + if ( CSGameRules()->IsBlackMarket() ) + { + iNVGPrice = CSGameRules()->GetBlackMarketPriceForWeapon( WEAPON_NVG ); + } + + if ( m_bHasNightVision == TRUE ) + { + if( !m_bIsInAutoBuy && !m_bIsInRebuy ) + ClientPrint( this, HUD_PRINTCENTER, "#Already_Have_One" ); + return BUY_ALREADY_HAVE; + } + else if ( m_iAccount < iNVGPrice ) + { + if( !m_bIsInAutoBuy && !m_bIsInRebuy ) + ClientPrint( this, HUD_PRINTCENTER, "#Not_Enough_Money" ); + return BUY_CANT_AFFORD; + } + else + { + IGameEvent * event = gameeventmanager->CreateEvent( "item_pickup" ); + if( event ) + { + event->SetInt( "userid", GetUserID() ); + event->SetString( "item", "nvgs" ); + gameeventmanager->FireEvent( event ); + } + + GiveNamedItem( "item_nvgs" ); + AddAccount( -iNVGPrice, true, true ); + BlackMarketAddWeapon( "nightvision", this ); + + if ( !(m_iDisplayHistoryBits & DHF_NIGHTVISION) ) + { + HintMessage( "#Hint_use_nightvision", false ); + m_iDisplayHistoryBits |= DHF_NIGHTVISION; + } + return BUY_BOUGHT; + } +} + + +// Handles the special "buy" alias commands we're creating to accommodate the buy +// scripts players use (now that we've rearranged the buy menus and broken the scripts) +//============================================================================= +// HPE_BEGIN: +//[tj] This is essentially a shim so I can easily check the return +// value without adding new code to all the return points. +//============================================================================= + +BuyResult_e CCSPlayer::HandleCommand_Buy( const char *item ) +{ + BuyResult_e result = HandleCommand_Buy_Internal(item); + if (result == BUY_BOUGHT) + { + m_bMadePurchseThisRound = true; + CCS_GameStats.IncrementStat(this, CSSTAT_ITEMS_PURCHASED, 1); + } + return result; +} + +BuyResult_e CCSPlayer::HandleCommand_Buy_Internal( const char* wpnName ) +//============================================================================= +// HPE_END +//============================================================================= +{ + BuyResult_e result = CanPlayerBuy( false ) ? BUY_PLAYER_CANT_BUY : BUY_INVALID_ITEM; // set some defaults + + // translate the new weapon names to the old ones that are actually being used. + wpnName = GetTranslatedWeaponAlias(wpnName); + + CCSWeaponInfo *pWeaponInfo = GetWeaponInfo( AliasToWeaponID( wpnName ) ); + if ( pWeaponInfo == NULL ) + { + if ( Q_stricmp( wpnName, "primammo" ) == 0 ) + { + result = AttemptToBuyAmmo( 0 ); + } + else if ( Q_stricmp( wpnName, "secammo" ) == 0 ) + { + result = AttemptToBuyAmmo( 1 ); + } + else if ( Q_stristr( wpnName, "defuser" ) ) + { + if( CanPlayerBuy( true ) ) + { + result = AttemptToBuyDefuser(); + } + } + } + else + { + + if( !CanPlayerBuy( true ) ) + { + return BUY_PLAYER_CANT_BUY; + } + + BuyResult_e equipResult = BUY_INVALID_ITEM; + + if ( Q_stristr( wpnName, "kevlar" ) ) + { + equipResult = AttemptToBuyVest(); + } + else if ( Q_stristr( wpnName, "assaultsuit" ) ) + { + equipResult = AttemptToBuyAssaultSuit(); + } + else if ( Q_stristr( wpnName, "shield" ) ) + { + equipResult = AttemptToBuyShield(); + } + else if ( Q_stristr( wpnName, "nightvision" ) ) + { + equipResult = AttemptToBuyNightVision(); + } + + if ( equipResult != BUY_INVALID_ITEM ) + { + if ( equipResult == BUY_BOUGHT ) + { + BuildRebuyStruct(); + } + return equipResult; // intentional early return here + } + + bool bPurchase = false; + + // MIKETODO: assasination maps have a specific set of weapons that can be used in them. + if ( pWeaponInfo->m_iTeam != TEAM_UNASSIGNED && GetTeamNumber() != pWeaponInfo->m_iTeam ) + { + result = BUY_NOT_ALLOWED; + if ( pWeaponInfo->m_WrongTeamMsg[0] != 0 ) + { + ClientPrint( this, HUD_PRINTCENTER, "#Alias_Not_Avail", pWeaponInfo->m_WrongTeamMsg ); + } + } + else if ( pWeaponInfo->GetWeaponPrice() <= 0 ) + { + // ClientPrint( this, HUD_PRINTCENTER, "#Cant_buy_this_item", pWeaponInfo->m_WrongTeamMsg ); + } + else if( pWeaponInfo->m_WeaponType == WEAPONTYPE_GRENADE ) + { + // make sure the player can afford this item. + if ( m_iAccount >= pWeaponInfo->GetWeaponPrice() ) + { + bPurchase = true; + + const char *szWeaponName = NULL; + int ammoMax = 0; + if ( Q_strstr( pWeaponInfo->szClassName, "flashbang" ) ) + { + szWeaponName = "weapon_flashbang"; + ammoMax = ammo_flashbang_max.GetInt(); + } + else if ( Q_strstr( pWeaponInfo->szClassName, "hegrenade" ) ) + { + szWeaponName = "weapon_hegrenade"; + ammoMax = ammo_hegrenade_max.GetInt(); + } + else if ( Q_strstr( pWeaponInfo->szClassName, "smokegrenade" ) ) + { + szWeaponName = "weapon_smokegrenade"; + ammoMax = ammo_smokegrenade_max.GetInt(); + } + + if ( szWeaponName != NULL ) + { + CBaseCombatWeapon* pGrenadeWeapon = Weapon_OwnsThisType( szWeaponName ); + { + if ( pGrenadeWeapon != NULL ) + { + int nAmmoType = pGrenadeWeapon->GetPrimaryAmmoType(); + + if( nAmmoType != -1 ) + { + if( GetAmmoCount(nAmmoType) >= ammoMax ) + { + result = BUY_ALREADY_HAVE; + if( !m_bIsInAutoBuy && !m_bIsInRebuy ) + ClientPrint( this, HUD_PRINTCENTER, "#Cannot_Carry_Anymore" ); + bPurchase = false; + } + } + } + } + } + } + } + else if ( !Weapon_OwnsThisType( pWeaponInfo->szClassName ) ) //don't buy duplicate weapons + { + // do they have enough money? + if ( m_iAccount >= pWeaponInfo->GetWeaponPrice() ) + { + if ( m_lifeState != LIFE_DEAD ) + { + if ( pWeaponInfo->iSlot == WEAPON_SLOT_PISTOL ) + { + DropPistol(); + } + else if ( pWeaponInfo->iSlot == WEAPON_SLOT_RIFLE ) + { + DropRifle(); + } + } + + bPurchase = true; + } + else + { + result = BUY_CANT_AFFORD; + if( !m_bIsInAutoBuy && !m_bIsInRebuy ) + ClientPrint( this, HUD_PRINTCENTER, "#Not_Enough_Money" ); + } + } + else + { + result = BUY_ALREADY_HAVE; + } + + if ( HasShield() ) + { + if ( pWeaponInfo->m_bCanUseWithShield == false ) + { + result = BUY_NOT_ALLOWED; + bPurchase = false; + } + } + + if( bPurchase ) + { + result = BUY_BOUGHT; + + if ( bPurchase && pWeaponInfo->iSlot == WEAPON_SLOT_PISTOL ) + m_bUsingDefaultPistol = false; + + GiveNamedItem( pWeaponInfo->szClassName ); + AddAccount( -pWeaponInfo->GetWeaponPrice(), true, true, pWeaponInfo->szClassName ); + BlackMarketAddWeapon( wpnName, this ); + } + } + + if ( result == BUY_BOUGHT ) + { + BuildRebuyStruct(); + } + + return result; +} + + +BuyResult_e CCSPlayer::BuyGunAmmo( CBaseCombatWeapon *pWeapon, bool bBlinkMoney ) +{ + if ( !CanPlayerBuy( false ) ) + { + return BUY_PLAYER_CANT_BUY; + } + + // Ensure that the weapon uses ammo + int nAmmo = pWeapon->GetPrimaryAmmoType(); + if ( nAmmo == -1 ) + { + return BUY_ALREADY_HAVE; + } + + // Can only buy if the player does not already have full ammo + if ( GetAmmoCount( nAmmo ) >= GetAmmoDef()->MaxCarry( nAmmo ) ) + { + return BUY_ALREADY_HAVE; + } + + // Purchase the ammo if the player has enough money + if ( m_iAccount >= GetCSAmmoDef()->GetCost( nAmmo ) ) + { + GiveAmmo( GetCSAmmoDef()->GetBuySize( nAmmo ), nAmmo, true ); + AddAccount( -GetCSAmmoDef()->GetCost( nAmmo ), true, true, GetCSAmmoDef()->GetAmmoOfIndex( nAmmo )->pName ); + return BUY_BOUGHT; + } + + if ( bBlinkMoney ) + { + // Not enough money.. let the player know + if( !m_bIsInAutoBuy && !m_bIsInRebuy ) + ClientPrint( this, HUD_PRINTCENTER, "#Not_Enough_Money" ); + } + + return BUY_CANT_AFFORD; +} + + +BuyResult_e CCSPlayer::BuyAmmo( int nSlot, bool bBlinkMoney ) +{ + if ( !CanPlayerBuy( false ) ) + { + return BUY_PLAYER_CANT_BUY; + } + + if ( nSlot < 0 || nSlot > 1 ) + { + return BUY_INVALID_ITEM; + } + + // Buy one ammo clip for all weapons in the given slot + // + // nSlot == 1 : Primary weapons + // nSlot == 2 : Secondary weapons + + CBaseCombatWeapon *pSlot = Weapon_GetSlot( nSlot ); + if ( !pSlot ) + return BUY_INVALID_ITEM; + + //MIKETODO: shield. + //if ( player->HasShield() && player->m_rgpPlayerItems[2] ) + // pItem = player->m_rgpPlayerItems[2]; + + return BuyGunAmmo( pSlot, bBlinkMoney ); +} + + +BuyResult_e CCSPlayer::AttemptToBuyAmmo( int iAmmoType ) +{ + Assert( iAmmoType == 0 || iAmmoType == 1 ); + + BuyResult_e result = BuyAmmo( iAmmoType, true ); + + if ( result == BUY_BOUGHT ) + { + while ( BuyAmmo( iAmmoType, false ) == BUY_BOUGHT ) + { + // empty loop - keep buying + } + + return BUY_BOUGHT; + } + + return result; +} + +BuyResult_e CCSPlayer::AttemptToBuyAmmoSingle( int iAmmoType ) +{ + Assert( iAmmoType == 0 || iAmmoType == 1 ); + + BuyResult_e result = BuyAmmo( iAmmoType, true ); + + if ( result == BUY_BOUGHT ) + { + BuildRebuyStruct(); + } + + return result; +} + +const char *RadioEventName[ RADIO_NUM_EVENTS+1 ] = +{ + "RADIO_INVALID", + + "EVENT_START_RADIO_1", + + "EVENT_RADIO_COVER_ME", + "EVENT_RADIO_YOU_TAKE_THE_POINT", + "EVENT_RADIO_HOLD_THIS_POSITION", + "EVENT_RADIO_REGROUP_TEAM", + "EVENT_RADIO_FOLLOW_ME", + "EVENT_RADIO_TAKING_FIRE", + + "EVENT_START_RADIO_2", + + "EVENT_RADIO_GO_GO_GO", + "EVENT_RADIO_TEAM_FALL_BACK", + "EVENT_RADIO_STICK_TOGETHER_TEAM", + "EVENT_RADIO_GET_IN_POSITION_AND_WAIT", + "EVENT_RADIO_STORM_THE_FRONT", + "EVENT_RADIO_REPORT_IN_TEAM", + + "EVENT_START_RADIO_3", + + "EVENT_RADIO_AFFIRMATIVE", + "EVENT_RADIO_ENEMY_SPOTTED", + "EVENT_RADIO_NEED_BACKUP", + "EVENT_RADIO_SECTOR_CLEAR", + "EVENT_RADIO_IN_POSITION", + "EVENT_RADIO_REPORTING_IN", + "EVENT_RADIO_GET_OUT_OF_THERE", + "EVENT_RADIO_NEGATIVE", + "EVENT_RADIO_ENEMY_DOWN", + + "EVENT_RADIO_END", + + NULL // must be NULL-terminated +}; + + +/** + * Convert name to RadioType + */ +RadioType NameToRadioEvent( const char *name ) +{ + for( int i=0; RadioEventName[i]; ++i ) + if (!stricmp( RadioEventName[i], name )) + return static_cast<RadioType>( i ); + + return RADIO_INVALID; +} + + +void CCSPlayer::HandleMenu_Radio1( int slot ) +{ + if( m_iRadioMessages < 0 ) + return; + + if( m_flRadioTime > gpGlobals->curtime ) + return; + + m_iRadioMessages--; + m_flRadioTime = gpGlobals->curtime + 1.5; + + switch ( slot ) + { + case 1 : + Radio( "Radio.CoverMe", "#Cstrike_TitlesTXT_Cover_me" ); + break; + + case 2 : + Radio( "Radio.YouTakeThePoint", "#Cstrike_TitlesTXT_You_take_the_point" ); + break; + + case 3 : + Radio( "Radio.HoldPosition", "#Cstrike_TitlesTXT_Hold_this_position" ); + break; + + case 4 : + Radio( "Radio.Regroup", "#Cstrike_TitlesTXT_Regroup_team" ); + break; + + case 5 : + Radio( "Radio.FollowMe", "#Cstrike_TitlesTXT_Follow_me" ); + break; + + case 6 : + Radio( "Radio.TakingFire", "#Cstrike_TitlesTXT_Taking_fire" ); + break; + } + + // tell bots about radio message + IGameEvent * event = gameeventmanager->CreateEvent( "player_radio" ); + if ( event ) + { + event->SetInt("userid", GetUserID() ); + event->SetInt("slot", RADIO_START_1 + slot ); + gameeventmanager->FireEvent( event ); + } +} + +void CCSPlayer::HandleMenu_Radio2( int slot ) +{ + if( m_iRadioMessages < 0 ) + return; + + if( m_flRadioTime > gpGlobals->curtime ) + return; + + m_iRadioMessages--; + m_flRadioTime = gpGlobals->curtime + 1.5; + + switch ( slot ) + { + case 1 : + Radio( "Radio.GoGoGo", "#Cstrike_TitlesTXT_Go_go_go" ); + break; + + case 2 : + Radio( "Radio.TeamFallBack", "#Cstrike_TitlesTXT_Team_fall_back" ); + break; + + case 3 : + Radio( "Radio.StickTogether", "#Cstrike_TitlesTXT_Stick_together_team" ); + break; + + case 4 : + Radio( "Radio.GetInPosition", "#Cstrike_TitlesTXT_Get_in_position_and_wait" ); + break; + + case 5 : + Radio( "Radio.StormFront", "#Cstrike_TitlesTXT_Storm_the_front" ); + break; + + case 6 : + Radio( "Radio.ReportInTeam", "#Cstrike_TitlesTXT_Report_in_team" ); + break; + } + + // tell bots about radio message + IGameEvent * event = gameeventmanager->CreateEvent( "player_radio" ); + if ( event ) + { + event->SetInt("userid", GetUserID() ); + event->SetInt("slot", RADIO_START_2 + slot ); + gameeventmanager->FireEvent( event ); + } +} + +void CCSPlayer::HandleMenu_Radio3( int slot ) +{ + if( m_iRadioMessages < 0 ) + return; + + if( m_flRadioTime > gpGlobals->curtime ) + return; + + m_iRadioMessages--; + m_flRadioTime = gpGlobals->curtime + 1.5; + + switch ( slot ) + { + case 1 : + if ( random->RandomInt( 0,1 ) ) + Radio( "Radio.Affirmitive", "#Cstrike_TitlesTXT_Affirmative" ); + else + Radio( "Radio.Roger", "#Cstrike_TitlesTXT_Roger_that" ); + + break; + + case 2 : + Radio( "Radio.EnemySpotted", "#Cstrike_TitlesTXT_Enemy_spotted" ); + break; + + case 3 : + Radio( "Radio.NeedBackup", "#Cstrike_TitlesTXT_Need_backup" ); + break; + + case 4 : + Radio( "Radio.SectorClear", "#Cstrike_TitlesTXT_Sector_clear" ); + break; + + case 5 : + Radio( "Radio.InPosition", "#Cstrike_TitlesTXT_In_position" ); + break; + + case 6 : + Radio( "Radio.ReportingIn", "#Cstrike_TitlesTXT_Reporting_in" ); + break; + + case 7 : + Radio( "Radio.GetOutOfThere", "#Cstrike_TitlesTXT_Get_out_of_there" ); + break; + + case 8 : + Radio( "Radio.Negative", "#Cstrike_TitlesTXT_Negative" ); + break; + + case 9 : + Radio( "Radio.EnemyDown", "#Cstrike_TitlesTXT_Enemy_down" ); + break; + } + + // tell bots about radio message + IGameEvent * event = gameeventmanager->CreateEvent( "player_radio" ); + if ( event ) + { + event->SetInt("userid", GetUserID() ); + event->SetInt("slot", RADIO_START_3 + slot ); + gameeventmanager->FireEvent( event ); + } +} + +void UTIL_CSRadioMessage( IRecipientFilter& filter, int iClient, int msg_dest, const char *msg_name, const char *param1 = NULL, const char *param2 = NULL, const char *param3 = NULL, const char *param4 = NULL ) +{ + UserMessageBegin( filter, "RadioText" ); + WRITE_BYTE( msg_dest ); + WRITE_BYTE( iClient ); + WRITE_STRING( msg_name ); + + if ( param1 ) + WRITE_STRING( param1 ); + else + WRITE_STRING( "" ); + + if ( param2 ) + WRITE_STRING( param2 ); + else + WRITE_STRING( "" ); + + if ( param3 ) + WRITE_STRING( param3 ); + else + WRITE_STRING( "" ); + + if ( param4 ) + WRITE_STRING( param4 ); + else + WRITE_STRING( "" ); + + MessageEnd(); +} + +void CCSPlayer::ConstructRadioFilter( CRecipientFilter& filter ) +{ + filter.MakeReliable(); + + int localTeam = GetTeamNumber(); + + int i; + for ( i = 1; i <= gpGlobals->maxClients; ++i ) + { + CCSPlayer *player = static_cast<CCSPlayer *>( UTIL_PlayerByIndex( i ) ); + if ( !player ) + continue; + + // Skip players ignoring the radio + if ( player->m_bIgnoreRadio ) + continue; + + if( player->GetTeamNumber() == TEAM_SPECTATOR ) + { + // add spectators + if( player->m_iObserverMode == OBS_MODE_IN_EYE || player->m_iObserverMode == OBS_MODE_CHASE ) + { + filter.AddRecipient( player ); + } + } + else if( player->GetTeamNumber() == localTeam ) + { + // add teammates + filter.AddRecipient( player ); + } + } +} + +void CCSPlayer::Radio( const char *pszRadioSound, const char *pszRadioText ) +{ + if( !IsAlive() ) + return; + + if ( IsObserver() ) + return; + + CRecipientFilter filter; + ConstructRadioFilter( filter ); + + if( pszRadioText ) + { + const char *pszLocationText = CSGameRules()->GetChatLocation( true, this ); + if ( pszLocationText && *pszLocationText ) + { + UTIL_CSRadioMessage( filter, entindex(), HUD_PRINTTALK, "#Game_radio_location", GetPlayerName(), pszLocationText, pszRadioText ); + } + else + { + UTIL_CSRadioMessage( filter, entindex(), HUD_PRINTTALK, "#Game_radio", GetPlayerName(), pszRadioText ); + } + } + + UserMessageBegin ( filter, "SendAudio" ); + WRITE_STRING( pszRadioSound ); + MessageEnd(); + + //icon over the head for teammates + TE_RadioIcon( filter, 0.0, this ); +} + +//----------------------------------------------------------------------------- +// Purpose: Outputs currently connected players to the console +//----------------------------------------------------------------------------- +void CCSPlayer::ListPlayers() +{ + char buf[64]; + for ( int i=1; i <= gpGlobals->maxClients; i++ ) + { + CCSPlayer *pPlayer = dynamic_cast< CCSPlayer* >( UTIL_PlayerByIndex( i ) ); + if ( pPlayer && !pPlayer->IsDormant() ) + { + if ( pPlayer->IsBot() ) + { + Q_snprintf( buf, sizeof(buf), "B %d : %s", pPlayer->GetUserID(), pPlayer->GetPlayerName() ); + } + else + { + Q_snprintf( buf, sizeof(buf), " %d : %s", pPlayer->GetUserID(), pPlayer->GetPlayerName() ); + } + ClientPrint( this, HUD_PRINTCONSOLE, buf ); + } + } + ClientPrint( this, HUD_PRINTCONSOLE, "\n" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &info - +//----------------------------------------------------------------------------- +void CCSPlayer::OnDamagedByExplosion( const CTakeDamageInfo &info ) +{ + float lastDamage = info.GetDamage(); + + //Adrian - This is hacky since we might have been damaged by something else + //but since the round is ending, who cares. + if ( CSGameRules()->m_bTargetBombed == true ) + return; + + float distanceFromPlayer = 9999.0f; + + CBaseEntity *inflictor = info.GetInflictor(); + if ( inflictor ) + { + Vector delta = GetAbsOrigin() - inflictor->GetAbsOrigin(); + distanceFromPlayer = delta.Length(); + } + + bool shock = lastDamage >= 30.0f; + + if ( !shock ) + return; + + m_applyDeafnessTime = gpGlobals->curtime + 0.3; + m_currentDeafnessFilter = 0; +} + +void CCSPlayer::ApplyDeafnessEffect() +{ + // what's happening here is that the low-pass filter and the oscillator frequency effects need + // to fade in and out slowly. So we have several filters that we switch between to achieve this + // effect. The first 3rd of the total effect will be the "fade in" of the effect. Which means going + // from filter to filter from the first to the last. Then we keep on the "last" filter for another + // third of the total effect time. Then the last third of the time we go back from the last filter + // to the first. Clear as mud? + + // glossary: + // filter: an individual filter as defined in dsp_presets.txt + // section: one of the sections for the total effect, fade in, full, fade out are the possible sections + // effect: the total effect of combining all the sections, the whole of what the player hears from start to finish. + + const int firstGrenadeFilterIndex = 137; + const int lastGrenadeFilterIndex = 139; + const float grenadeEffectLengthInSecs = 4.5f; // time of the total effect + const float fadeInSectionTime = 0.1f; + const float fadeOutSectionTime = 1.5f; + + const float timeForEachFilterInFadeIn = fadeInSectionTime / (lastGrenadeFilterIndex - firstGrenadeFilterIndex); + const float timeForEachFilterInFadeOut = fadeOutSectionTime / (lastGrenadeFilterIndex - firstGrenadeFilterIndex); + + float timeIntoEffect = gpGlobals->curtime - m_applyDeafnessTime; + + if (timeIntoEffect >= grenadeEffectLengthInSecs) + { + // the effect is done, so reset the deafness variables. + m_applyDeafnessTime = 0.0f; + m_currentDeafnessFilter = 0; + return; + } + + int section = 0; + + if (timeIntoEffect < fadeInSectionTime) + { + section = 0; + } + else if (timeIntoEffect < (grenadeEffectLengthInSecs - fadeOutSectionTime)) + { + section = 1; + } + else + { + section = 2; + } + + int filterToUse = 0; + + if (section == 0) + { + // fade into the effect. + int filterIndex = (int)(timeIntoEffect / timeForEachFilterInFadeIn); + filterToUse = filterIndex += firstGrenadeFilterIndex; + } + else if (section == 1) + { + // in full effect. + filterToUse = lastGrenadeFilterIndex; + } + else if (section == 2) + { + // fade out of the effect + float timeIntoSection = timeIntoEffect - (grenadeEffectLengthInSecs - fadeOutSectionTime); + int filterIndex = (int)(timeIntoSection / timeForEachFilterInFadeOut); + filterToUse = lastGrenadeFilterIndex - filterIndex - 1; + } + + if (filterToUse != m_currentDeafnessFilter) + { + m_currentDeafnessFilter = filterToUse; + + CSingleUserRecipientFilter user( this ); + enginesound->SetPlayerDSP( user, m_currentDeafnessFilter, false ); + } +} + + +void CCSPlayer::NoteWeaponFired() +{ + Assert( m_pCurrentCommand ); + if( m_pCurrentCommand ) + { + m_iLastWeaponFireUsercmd = m_pCurrentCommand->command_number; + } +} + + +bool CCSPlayer::WantsLagCompensationOnEntity( const CBasePlayer *pPlayer, const CUserCmd *pCmd, const CBitVec<MAX_EDICTS> *pEntityTransmitBits ) const +{ + // No need to lag compensate at all if we're not attacking in this command and + // we haven't attacked recently. + if ( !( pCmd->buttons & IN_ATTACK ) && (pCmd->command_number - m_iLastWeaponFireUsercmd > 5) ) + { + if ( ( pCmd->buttons & IN_ATTACK2 ) == 0 ) + return false; + + CWeaponCSBase *weapon = GetActiveCSWeapon(); + if ( !weapon ) + return false; + + if ( weapon->GetWeaponID() != WEAPON_KNIFE ) + return false; // IN_ATTACK2 with WEAPON_KNIFE should do lag compensation + } + + return BaseClass::WantsLagCompensationOnEntity( pPlayer, pCmd, pEntityTransmitBits ); +} + +// Handles the special "radio" alias commands we're creating to accommodate the scripts players use +// ** Returns true if we've handled the command ** +bool HandleRadioAliasCommands( CCSPlayer *pPlayer, const char *pszCommand ) +{ + bool bRetVal = false; + + // don't execute them if we are not alive or are an observer + if( !pPlayer->IsAlive() || pPlayer->IsObserver() ) + return false; + + // Radio1 commands + if ( FStrEq( pszCommand, "coverme" ) ) + { + bRetVal = true; + pPlayer->HandleMenu_Radio1( 1 ); + } + else if ( FStrEq( pszCommand, "takepoint" ) ) + { + bRetVal = true; + pPlayer->HandleMenu_Radio1( 2 ); + } + else if ( FStrEq( pszCommand, "holdpos" ) ) + { + bRetVal = true; + pPlayer->HandleMenu_Radio1( 3 ); + } + else if ( FStrEq( pszCommand, "regroup" ) ) + { + bRetVal = true; + pPlayer->HandleMenu_Radio1( 4 ); + } + else if ( FStrEq( pszCommand, "followme" ) ) + { + bRetVal = true; + pPlayer->HandleMenu_Radio1( 5 ); + } + else if ( FStrEq( pszCommand, "takingfire" ) ) + { + bRetVal = true; + pPlayer->HandleMenu_Radio1( 6 ); + } + // Radio2 commands + else if ( FStrEq( pszCommand, "go" ) ) + { + bRetVal = true; + pPlayer->HandleMenu_Radio2( 1 ); + } + else if ( FStrEq( pszCommand, "fallback" ) ) + { + bRetVal = true; + pPlayer->HandleMenu_Radio2( 2 ); + } + else if ( FStrEq( pszCommand, "sticktog" ) ) + { + bRetVal = true; + pPlayer->HandleMenu_Radio2( 3 ); + } + else if ( FStrEq( pszCommand, "getinpos" ) ) + { + bRetVal = true; + pPlayer->HandleMenu_Radio2( 4 ); + } + else if ( FStrEq( pszCommand, "stormfront" ) ) + { + bRetVal = true; + pPlayer->HandleMenu_Radio2( 5 ); + } + else if ( FStrEq( pszCommand, "report" ) ) + { + bRetVal = true; + pPlayer->HandleMenu_Radio2( 6 ); + } + // Radio3 commands + else if ( FStrEq( pszCommand, "roger" ) ) + { + bRetVal = true; + pPlayer->HandleMenu_Radio3( 1 ); + } + else if ( FStrEq( pszCommand, "enemyspot" ) ) + { + bRetVal = true; + pPlayer->HandleMenu_Radio3( 2 ); + } + else if ( FStrEq( pszCommand, "needbackup" ) ) + { + bRetVal = true; + pPlayer->HandleMenu_Radio3( 3 ); + } + else if ( FStrEq( pszCommand, "sectorclear" ) ) + { + bRetVal = true; + pPlayer->HandleMenu_Radio3( 4 ); + } + else if ( FStrEq( pszCommand, "inposition" ) ) + { + bRetVal = true; + pPlayer->HandleMenu_Radio3( 5 ); + } + else if ( FStrEq( pszCommand, "reportingin" ) ) + { + bRetVal = true; + pPlayer->HandleMenu_Radio3( 6 ); + } + else if ( FStrEq( pszCommand, "getout" ) ) + { + bRetVal = true; + pPlayer->HandleMenu_Radio3( 7 ); + } + else if ( FStrEq( pszCommand, "negative" ) ) + { + bRetVal = true; + pPlayer->HandleMenu_Radio3( 8 ); + } + else if ( FStrEq( pszCommand, "enemydown" ) ) + { + bRetVal = true; + pPlayer->HandleMenu_Radio3( 9 ); + } + + return bRetVal; +} + +bool CCSPlayer::ShouldRunRateLimitedCommand( const CCommand &args ) +{ + const char *pcmd = args[0]; + + int i = m_RateLimitLastCommandTimes.Find( pcmd ); + if ( i == m_RateLimitLastCommandTimes.InvalidIndex() ) + { + m_RateLimitLastCommandTimes.Insert( pcmd, gpGlobals->curtime ); + return true; + } + else if ( (gpGlobals->curtime - m_RateLimitLastCommandTimes[i]) < CS_COMMAND_MAX_RATE ) + { + // Too fast. + return false; + } + else + { + m_RateLimitLastCommandTimes[i] = gpGlobals->curtime; + return true; + } +} + +bool CCSPlayer::ClientCommand( const CCommand &args ) +{ + const char *pcmd = args[0]; + + // Bots mimic our client commands. +/* + if ( bot_mimic.GetInt() && !( GetFlags() & FL_FAKECLIENT ) ) + { + for ( int i=1; i <= gpGlobals->maxClients; i++ ) + { + CCSPlayer *pPlayer = dynamic_cast< CCSPlayer* >( UTIL_PlayerByIndex( i ) ); + if ( pPlayer && pPlayer != this && ( pPlayer->GetFlags() & FL_FAKECLIENT ) ) + { + pPlayer->ClientCommand( pcmd ); + } + } + } +*/ + +#if defined ( DEBUG ) + + if ( FStrEq( pcmd, "bot_cmd" ) ) + { + CCSPlayer *pPlayer = dynamic_cast< CCSPlayer* >( UTIL_PlayerByIndex( atoi( args[1] ) ) ); + if ( pPlayer && pPlayer != this && ( pPlayer->GetFlags() & FL_FAKECLIENT ) ) + { + CCommand botArgs( args.ArgC() - 2, &args.ArgV()[2] ); + pPlayer->ClientCommand( botArgs ); + pPlayer->RemoveEffects( EF_NODRAW ); + } + return true; + } + + if ( FStrEq( pcmd, "blind" ) ) + { + if ( ShouldRunRateLimitedCommand( args ) ) + { + if ( args.ArgC() == 3 ) + { + Blind( atof( args[1] ), atof( args[2] ) ); + } + else + { + ClientPrint( this, HUD_PRINTCONSOLE, "usage: blind holdtime fadetime\n" ); + } + } + return true; + } + + if ( FStrEq( pcmd, "deafen" ) ) + { + Deafen( 0.0f ); + return true; + } + + if ( FStrEq( pcmd, "he_deafen" ) ) + { + m_applyDeafnessTime = gpGlobals->curtime + 0.3; + m_currentDeafnessFilter = 0; + return true; + } + + if ( FStrEq( pcmd, "hint_reset" ) ) + { + m_iDisplayHistoryBits = 0; + return true; + } + + if ( FStrEq( pcmd, "punch" ) ) + { + float flDamage = 100; + + QAngle punchAngle = GetPunchAngle(); + + punchAngle.x = flDamage * random->RandomFloat ( -0.15, 0.15 ); + punchAngle.y = flDamage * random->RandomFloat ( -0.15, 0.15 ); + punchAngle.z = flDamage * random->RandomFloat ( -0.15, 0.15 ); + + clamp( punchAngle.x, -4, punchAngle.x ); + clamp( punchAngle.y, -5, 5 ); + clamp( punchAngle.z, -5, 5 ); + + // +y == down + // +x == left + // +z == roll clockwise + if ( args.ArgC() == 4 ) + { + punchAngle.x = atof(args[1]); + punchAngle.y = atof(args[2]); + punchAngle.z = atof(args[3]); + } + + SetPunchAngle( punchAngle ); + + return true; + } + +#endif //DEBUG + + if ( FStrEq( pcmd, "jointeam" ) ) + { + if ( args.ArgC() < 2 ) + { + Warning( "Player sent bad jointeam syntax\n" ); + } + + if ( ShouldRunRateLimitedCommand( args ) ) + { + int iTeam = atoi( args[1] ); + HandleCommand_JoinTeam( iTeam ); + } + return true; + } + else if ( FStrEq( pcmd, "spectate" ) ) + { + if ( ShouldRunRateLimitedCommand( args ) ) + { + // instantly join spectators + HandleCommand_JoinTeam( TEAM_SPECTATOR ); + } + return true; + } + else if ( FStrEq( pcmd, "joingame" ) ) + { + // player just closed MOTD dialog + if ( m_iPlayerState == STATE_WELCOME ) + { + State_Transition( STATE_PICKINGTEAM ); + } + + return true; + } + else if ( FStrEq( pcmd, "joinclass" ) ) + { + if ( args.ArgC() < 2 ) + { + Warning( "Player sent bad joinclass syntax\n" ); + } + + if ( ShouldRunRateLimitedCommand( args ) ) + { + int iClass = atoi( args[1] ); + HandleCommand_JoinClass( iClass ); + } + return true; + } + else if ( FStrEq( pcmd, "drop" ) ) + { + CWeaponCSBase *pWeapon = dynamic_cast< CWeaponCSBase* >( GetActiveWeapon() ); + + if( pWeapon ) + { + //============================================================================= + // HPE_BEGIN: + // [dwenger] Determine value of dropped item. + //============================================================================= + + if ( !pWeapon->IsAPriorOwner( this ) ) + { + pWeapon->AddToPriorOwnerList( this ); + + CCS_GameStats.IncrementStat(this, CSTAT_ITEMS_DROPPED_VALUE, pWeapon->GetCSWpnData().GetWeaponPrice()); + } + + //============================================================================= + // HPE_END + //============================================================================= + + CSWeaponType type = pWeapon->GetCSWpnData().m_WeaponType; + + if( type != WEAPONTYPE_KNIFE && type != WEAPONTYPE_GRENADE ) + { + if (CSGameRules()->GetCanDonateWeapon() && !pWeapon->GetDonated()) + { + pWeapon->SetDonated(true); + pWeapon->SetDonor(this); + } + CSWeaponDrop( pWeapon, true, true ); + + } + } + + return true; + } + else if ( FStrEq( pcmd, "buy" ) ) + { + BuyResult_e result = BUY_INVALID_ITEM; + if ( args.ArgC() == 2 ) + { + result = HandleCommand_Buy( args[1] ); + } + if ( result == BUY_INVALID_ITEM ) + { + // Print out a message on the console + int msg_dest = HUD_PRINTCONSOLE; + + ClientPrint( this, msg_dest, "usage: buy <item>\n" ); + ClientPrint( this, msg_dest, " primammo\n" ); + ClientPrint( this, msg_dest, " secammo\n" ); + ClientPrint( this, msg_dest, " vest\n" ); + ClientPrint( this, msg_dest, " vesthelm\n" ); + ClientPrint( this, msg_dest, " defuser\n" ); + //ClientPrint( this, msg_dest, " shield\n" ); + ClientPrint( this, msg_dest, " nvgs\n" ); + ClientPrint( this, msg_dest, " flashbang\n" ); + ClientPrint( this, msg_dest, " hegrenade\n" ); + ClientPrint( this, msg_dest, " smokegrenade\n" ); + ClientPrint( this, msg_dest, " galil\n" ); + ClientPrint( this, msg_dest, " ak47\n" ); + ClientPrint( this, msg_dest, " scout\n" ); + ClientPrint( this, msg_dest, " sg552\n" ); + ClientPrint( this, msg_dest, " awp\n" ); + ClientPrint( this, msg_dest, " g3sg1\n" ); + ClientPrint( this, msg_dest, " famas\n" ); + ClientPrint( this, msg_dest, " m4a1\n" ); + ClientPrint( this, msg_dest, " aug\n" ); + ClientPrint( this, msg_dest, " sg550\n" ); + ClientPrint( this, msg_dest, " glock\n" ); + ClientPrint( this, msg_dest, " usp\n" ); + ClientPrint( this, msg_dest, " p228\n" ); + ClientPrint( this, msg_dest, " deagle\n" ); + ClientPrint( this, msg_dest, " elite\n" ); + ClientPrint( this, msg_dest, " fiveseven\n" ); + ClientPrint( this, msg_dest, " m3\n" ); + ClientPrint( this, msg_dest, " xm1014\n" ); + ClientPrint( this, msg_dest, " mac10\n" ); + ClientPrint( this, msg_dest, " tmp\n" ); + ClientPrint( this, msg_dest, " mp5navy\n" ); + ClientPrint( this, msg_dest, " ump45\n" ); + ClientPrint( this, msg_dest, " p90\n" ); + ClientPrint( this, msg_dest, " m249\n" ); + } + + return true; + } + else if ( FStrEq( pcmd, "buyammo1" ) ) + { + AttemptToBuyAmmoSingle(0); + return true; + } + else if ( FStrEq( pcmd, "buyammo2" ) ) + { + AttemptToBuyAmmoSingle(1); + return true; + } + else if ( FStrEq( pcmd, "nightvision" ) ) + { + if ( ShouldRunRateLimitedCommand( args ) ) + { + if( m_bHasNightVision ) + { + if( m_bNightVisionOn ) + { + CPASAttenuationFilter filter( this ); + EmitSound( filter, entindex(), "Player.NightVisionOff" ); + } + else + { + CPASAttenuationFilter filter( this ); + EmitSound( filter, entindex(), "Player.NightVisionOn" ); + } + + m_bNightVisionOn = !m_bNightVisionOn; + } + } + return true; + } + else if ( FStrEq( pcmd, "menuselect" ) ) + { + return true; + } + else if ( HandleRadioAliasCommands( this, pcmd ) ) + { + return true; + } + else if ( FStrEq( pcmd, "listplayers" ) ) + { + ListPlayers(); + return true; + } + + else if ( FStrEq( pcmd, "ignorerad" ) ) + { + m_bIgnoreRadio = !m_bIgnoreRadio; + if ( m_bIgnoreRadio ) + { + ClientPrint( this, HUD_PRINTTALK, "#Ignore_Radio" ); + } + else + { + ClientPrint( this, HUD_PRINTTALK, "#Accept_Radio" ); + } + return true; + } + else if ( FStrEq( pcmd, "become_vip" ) ) + { + //MIKETODO: VIP mode + /* + if ( ( CSGameRules()->m_iMapHasVIPSafetyZone == 1 ) && ( m_iTeam == TEAM_CT ) ) + { + mp->AddToVIPQueue( this ); + } + */ + return true; + } + + return BaseClass::ClientCommand( args ); +} + + +// returns true if the selection has been handled and the player's menu +// can be closed...false if the menu should be displayed again +bool CCSPlayer::HandleCommand_JoinTeam( int team ) +{ + CCSGameRules *mp = CSGameRules(); + + if ( !GetGlobalTeam( team ) ) + { + DevWarning( "HandleCommand_JoinTeam( %d ) - invalid team index.\n", team ); + return false; + } + + // If this player is a VIP, don't allow him to switch teams/appearances unless the following conditions are met : + // a) There is another TEAM_CT player who is in the queue to be a VIP + // b) This player is dead + + //MIKETODO: handle this when doing VIP mode + /* + if ( m_bIsVIP == true ) + { + if ( !IsDead() ) + { + ClientPrint( this, HUD_PRINTCENTER, "#Cannot_Switch_From_VIP" ); + MenuReset(); + return true; + } + else if ( mp->IsVIPQueueEmpty() == true ) + { + ClientPrint( this, HUD_PRINTCENTER, "#Cannot_Switch_From_VIP" ); + MenuReset(); + return true; + } + } + + //MIKETODO: VIP mode + + case 3: + if ( ( mp->m_iMapHasVIPSafetyZone == 1 ) && ( m_iTeam == TEAM_CT ) ) + { + mp->AddToVIPQueue( player ); + MenuReset(); + return true; + } + else + { + return false; + } + break; + */ + + // If we already died and changed teams once, deny + if( m_bTeamChanged && team != m_iOldTeam && team != TEAM_SPECTATOR ) + { + ClientPrint( this, HUD_PRINTCENTER, "#Only_1_Team_Change" ); + return true; + } + + // check if we're limited in our team selection + if ( team == TEAM_UNASSIGNED && !IsBot() ) + { + team = mp->GetHumanTeam(); // returns TEAM_UNASSIGNED if we're unrestricted + } + + if ( team == TEAM_UNASSIGNED ) + { + // Attempt to auto-select a team, may set team to T, CT or SPEC + team = mp->SelectDefaultTeam( !IsBot() ); + + if ( team == TEAM_UNASSIGNED ) + { + // still team unassigned, try to kick a bot if possible + + // kick a bot to allow human to join + if (cv_bot_auto_vacate.GetBool() && !IsBot()) + { + team = (random->RandomInt( 0, 1 ) == 0) ? TEAM_TERRORIST : TEAM_CT; + if (UTIL_KickBotFromTeam( team ) == false) + { + // no bots on that team, try the other + team = (team == TEAM_CT) ? TEAM_TERRORIST : TEAM_CT; + if (UTIL_KickBotFromTeam( team ) == false) + { + // couldn't kick any bots, fail + team = TEAM_UNASSIGNED; + } + } + } + + if (team == TEAM_UNASSIGNED) + { + ClientPrint( this, HUD_PRINTCENTER, "#All_Teams_Full" ); + ShowViewPortPanel( PANEL_TEAM ); + return false; + } + } + } + + if ( team == GetTeamNumber() ) + { + // Let people change class (skin) by re-joining the same team + if ( GetTeamNumber() == TEAM_TERRORIST && TerroristPlayerModels.Count() > 1 ) + { + ShowViewPortPanel( PANEL_CLASS_TER ); + } + else if ( GetTeamNumber() == TEAM_CT && CTPlayerModels.Count() > 1 ) + { + ShowViewPortPanel( PANEL_CLASS_CT ); + } + return true; // we wouldn't change the team + } + + if ( mp->TeamFull( team ) ) + { + // attempt to kick a bot to make room for this player + bool madeRoom = false; + if (cv_bot_auto_vacate.GetBool() && !IsBot()) + { + if (UTIL_KickBotFromTeam( team )) + madeRoom = true; + } + + if (!madeRoom) + { + if ( team == TEAM_TERRORIST ) + { + ClientPrint( this, HUD_PRINTCENTER, "#Terrorists_Full" ); + } + else if ( team == TEAM_CT ) + { + ClientPrint( this, HUD_PRINTCENTER, "#CTs_Full" ); + } + + ShowViewPortPanel( PANEL_TEAM ); + return false; + } + } + + // check if humans are restricted to a single team (Tour of Duty, etc) + if ( !IsBot() && team != TEAM_SPECTATOR) + { + int humanTeam = mp->GetHumanTeam(); + if ( humanTeam != TEAM_UNASSIGNED && humanTeam != team ) + { + if ( humanTeam == TEAM_TERRORIST ) + { + ClientPrint( this, HUD_PRINTCENTER, "#Humans_Join_Team_T" ); + } + else if ( humanTeam == TEAM_CT ) + { + ClientPrint( this, HUD_PRINTCENTER, "#Humans_Join_Team_CT" ); + } + + ShowViewPortPanel( PANEL_TEAM ); + return false; + } + } + + if ( team == TEAM_SPECTATOR ) + { + // Prevent this is the cvar is set + if ( !mp_allowspectators.GetInt() && !IsHLTV() ) + { + ClientPrint( this, HUD_PRINTCENTER, "#Cannot_Be_Spectator" ); + return false; + } + + if ( GetTeamNumber() != TEAM_UNASSIGNED && State_Get() == STATE_ACTIVE ) + { + m_fNextSuicideTime = gpGlobals->curtime; // allow the suicide to work + + CommitSuicide(); + + // add 1 to frags to balance out the 1 subtracted for killing yourself + IncrementFragCount( 1 ); + } + + ChangeTeam( TEAM_SPECTATOR ); + m_iClass = (int)CS_CLASS_NONE; + + if ( !(m_iDisplayHistoryBits & DHF_SPEC_DUCK) ) + { + m_iDisplayHistoryBits |= DHF_SPEC_DUCK; + HintMessage( "#Spec_Duck", true, true ); + } + + // do we have fadetoblack on? (need to fade their screen back in) + if ( mp_fadetoblack.GetBool() ) + { + color32_s clr = { 0,0,0,255 }; + UTIL_ScreenFade( this, clr, 0, 0, FFADE_IN | FFADE_PURGE ); + } + + return true; + } + + // If the code gets this far, the team is not TEAM_UNASSIGNED + + + if (mp->TeamStacked( team, GetTeamNumber() ))//players are allowed to change to their own team so they can just change their model + { + // attempt to kick a bot to make room for this player + bool madeRoom = false; + if (cv_bot_auto_vacate.GetBool() && !IsBot()) + { + if (UTIL_KickBotFromTeam( team )) + madeRoom = true; + } + + if (!madeRoom) + { + // The specified team is full + ClientPrint( + this, + HUD_PRINTCENTER, + ( team == TEAM_TERRORIST ) ? "#Too_Many_Terrorists" : "#Too_Many_CTs" ); + + ShowViewPortPanel( PANEL_TEAM ); + return false; + } + } + + // Show the appropriate Choose Appearance menu + // This must come before ClientKill() for CheckWinConditions() to function properly + + // Switch their actual team... + ChangeTeam( team ); + + return true; +} + + +bool CCSPlayer::HandleCommand_JoinClass( int iClass ) +{ + if( iClass == CS_CLASS_NONE ) + { + // User choosed random class + switch ( GetTeamNumber() ) + { + case TEAM_TERRORIST : iClass = RandomInt(FIRST_T_CLASS, LAST_T_CLASS); + break; + + case TEAM_CT : iClass = RandomInt(FIRST_CT_CLASS, LAST_CT_CLASS); + break; + + default : iClass = CS_CLASS_NONE; + break; + } + } + + // clamp to valid classes + switch ( GetTeamNumber() ) + { + case TEAM_TERRORIST: + iClass = clamp( iClass, FIRST_T_CLASS, LAST_T_CLASS ); + break; + case TEAM_CT: + iClass = clamp( iClass, FIRST_CT_CLASS, LAST_CT_CLASS ); + break; + default: + iClass = CS_CLASS_NONE; + } + + // Reset the player's state + if ( State_Get() == STATE_ACTIVE ) + { + CSGameRules()->CheckWinConditions(); + } + + if ( !IsBot() && State_Get() == STATE_ACTIVE ) // Bots are responsible about only switching classes when they join. + { + // Kill player if switching classes while alive. + // This mimics goldsrc CS 1.6, and prevents a player from hiding, and switching classes to + // make the opposing team think there are more enemies than there really are. + CommitSuicide(); + } + + m_iClass = iClass; + + if (State_Get() == STATE_PICKINGCLASS) + { +// SetModelFromClass(); + GetIntoGame(); + } + + return true; +} + + +/* +void CheckStartMoney( void ) +{ + if ( mp_startmoney.GetInt() > 16000 ) + { + mp_startmoney.SetInt( 16000 ); + } + else if ( mp_startmoney.GetInt() < 800 ) + { + mp_startmoney.SetInt( 800 ); + } +} +*/ + +void CCSPlayer::GetIntoGame() +{ + // Set their model and if they're allowed to spawn right now, put them into the world. + //SetPlayerModel( iClass ); + + SetFOV( this, 0 ); + m_flLastMovement = gpGlobals->curtime; + + CCSGameRules *MPRules = CSGameRules(); + +/* //MIKETODO: Escape gameplay ? + if ( ( MPRules->m_bMapHasEscapeZone == true ) && ( m_iTeam == TEAM_CT ) ) + { + m_iAccount = 0; + + CheckStartMoney(); + AddAccount( (int)startmoney.value, true ); + } + */ + + + //****************New Code by SupraFiend************ + if ( !MPRules->FPlayerCanRespawn( this ) ) + { + // This player is joining in the middle of a round or is an observer. Put them directly into observer mode. + //pev->deadflag = DEAD_RESPAWNABLE; + //pev->classname = MAKE_STRING("player"); + //pev->flags &= ( FL_PROXY | FL_FAKECLIENT ); // clear flags, but keep proxy and bot flags that might already be set + //pev->flags |= FL_CLIENT | FL_SPECTATOR; + //SetThink(PlayerDeathThink); + if ( !(m_iDisplayHistoryBits & DHF_SPEC_DUCK) ) + { + m_iDisplayHistoryBits |= DHF_SPEC_DUCK; + HintMessage( "#Spec_Duck", true, true ); + } + + State_Transition( STATE_OBSERVER_MODE ); + + m_wasNotKilledNaturally = true; + + MPRules->CheckWinConditions(); + } + else// else spawn them right in + { + State_Transition( STATE_ACTIVE ); + + Spawn(); + + MPRules->CheckWinConditions(); + + //============================================================================= + // HPE_BEGIN: + // [menglish] Have the rules update anything related to a player spawning in late + //============================================================================= + + MPRules->SpawningLatePlayer(this); + + //============================================================================= + // HPE_END + //============================================================================= + + if( MPRules->m_flRestartRoundTime == 0.0f ) + { + //Bomb target, no bomber and no bomb lying around. + if( MPRules->IsBombDefuseMap() && !MPRules->IsThereABomber() && !MPRules->IsThereABomb() ) + MPRules->GiveC4(); //Checks for terrorists. + } + + // If a new terrorist is entering the fray, then up the # of potential escapers. + if ( GetTeamNumber() == TEAM_TERRORIST ) + MPRules->m_iNumEscapers++; + + //============================================================================= + // HPE_BEGIN: + // [menglish] Reset Round Based Achievement Variables + //============================================================================= + + ResetRoundBasedAchievementVariables(); + + //============================================================================= + // HPE_END + //============================================================================= + + } +} + + +int CCSPlayer::PlayerClass() const +{ + return m_iClass; +} + + + +bool CCSPlayer::SelectSpawnSpot( const char *pEntClassName, CBaseEntity* &pSpot ) +{ + // Find the next spawn spot. + pSpot = gEntList.FindEntityByClassname( pSpot, pEntClassName ); + + if ( pSpot == NULL ) // skip over the null point + pSpot = gEntList.FindEntityByClassname( pSpot, pEntClassName ); + + CBaseEntity *pFirstSpot = pSpot; + do + { + if ( pSpot ) + { + // check if pSpot is valid + if ( g_pGameRules->IsSpawnPointValid( pSpot, this ) ) + { + if ( pSpot->GetAbsOrigin() == Vector( 0, 0, 0 ) ) + { + pSpot = gEntList.FindEntityByClassname( pSpot, pEntClassName ); + continue; + } + + // if so, go to pSpot + return true; + } + } + // increment pSpot + pSpot = gEntList.FindEntityByClassname( pSpot, pEntClassName ); + } while ( pSpot != pFirstSpot ); // loop if we're not back to the start + + DevMsg("CCSPlayer::SelectSpawnSpot: couldn't find valid spawn point.\n"); + + return true; +} + + +CBaseEntity* CCSPlayer::EntSelectSpawnPoint() +{ + CBaseEntity *pSpot; + + /* MIKETODO: VIP + // VIP spawn point ************* + if ( ( g_pGameRules->IsDeathmatch() ) && ( ((CBasePlayer*)pPlayer)->m_bIsVIP == TRUE) ) + { + //ALERT (at_console,"Looking for a VIP spawn point\n"); + // Randomize the start spot + //for ( int i = RANDOM_LONG(1,5); i > 0; i-- ) + pSpot = UTIL_FindEntityByClassname( NULL, "info_vip_start" ); + if ( !FNullEnt( pSpot ) ) // skip over the null point + goto ReturnSpot; + else + goto CTSpawn; + } + + // + // the counter-terrorist spawns at "info_player_start" + else + */ + + pSpot = NULL; + if ( CSGameRules()->IsLogoMap() ) + { + // This is a logo map. Don't allow movement or logos or menus. + SelectSpawnSpot( "info_player_logo", pSpot ); + LockPlayerInPlace(); + goto ReturnSpot; + } + else + { + if ( GetTeamNumber() == TEAM_CT ) + { + pSpot = g_pLastCTSpawn; + if ( SelectSpawnSpot( "info_player_counterterrorist", pSpot )) + { + + g_pLastCTSpawn = pSpot; + goto ReturnSpot; + } + } + + /*********************************************************/ + // The terrorist spawn points + else if ( GetTeamNumber() == TEAM_TERRORIST ) + { + pSpot = g_pLastTerroristSpawn; + + if ( SelectSpawnSpot( "info_player_terrorist", pSpot ) ) + { + g_pLastTerroristSpawn = pSpot; + goto ReturnSpot; + } + } + } + + + // If startspot is set, (re)spawn there. + if ( !gpGlobals->startspot || !strlen(STRING(gpGlobals->startspot))) + { + pSpot = gEntList.FindEntityByClassname(NULL, "info_player_terrorist"); + if ( pSpot ) + goto ReturnSpot; + } + else + { + pSpot = gEntList.FindEntityByTarget( NULL, STRING(gpGlobals->startspot) ); + if ( pSpot ) + goto ReturnSpot; + } + +ReturnSpot: + if ( !pSpot ) + { + if( CSGameRules()->IsLogoMap() ) + Warning( "PutClientInServer: no info_player_logo on level\n" ); + else + Warning( "PutClientInServer: no info_player_start on level\n" ); + + return CBaseEntity::Instance( INDEXENT(0) ); + } + + return pSpot; +} + + +void CCSPlayer::SetProgressBarTime( int barTime ) +{ + m_iProgressBarDuration = barTime; + m_flProgressBarStartTime = this->m_flSimulationTime; +} + + +void CCSPlayer::PlayerDeathThink() +{ +} + + +void CCSPlayer::State_Transition( CSPlayerState newState ) +{ + State_Leave(); + State_Enter( newState ); +} + + +void CCSPlayer::State_Enter( CSPlayerState newState ) +{ + m_iPlayerState = newState; + m_pCurStateInfo = State_LookupInfo( newState ); + + if ( cs_ShowStateTransitions.GetInt() == -1 || cs_ShowStateTransitions.GetInt() == entindex() ) + { + if ( m_pCurStateInfo ) + Msg( "ShowStateTransitions: entering '%s'\n", m_pCurStateInfo->m_pStateName ); + else + Msg( "ShowStateTransitions: entering #%d\n", newState ); + } + + // Initialize the new state. + if ( m_pCurStateInfo && m_pCurStateInfo->pfnEnterState ) + (this->*m_pCurStateInfo->pfnEnterState)(); +} + + +void CCSPlayer::State_Leave() +{ + if ( m_pCurStateInfo && m_pCurStateInfo->pfnLeaveState ) + { + (this->*m_pCurStateInfo->pfnLeaveState)(); + } +} + + +void CCSPlayer::State_PreThink() +{ + if ( m_pCurStateInfo && m_pCurStateInfo->pfnPreThink ) + { + (this->*m_pCurStateInfo->pfnPreThink)(); + } +} + + +CCSPlayerStateInfo* CCSPlayer::State_LookupInfo( CSPlayerState state ) +{ + // This table MUST match the + static CCSPlayerStateInfo playerStateInfos[] = + { + { STATE_ACTIVE, "STATE_ACTIVE", &CCSPlayer::State_Enter_ACTIVE, NULL, &CCSPlayer::State_PreThink_ACTIVE }, + { STATE_WELCOME, "STATE_WELCOME", &CCSPlayer::State_Enter_WELCOME, NULL, &CCSPlayer::State_PreThink_WELCOME }, + { STATE_PICKINGTEAM, "STATE_PICKINGTEAM", &CCSPlayer::State_Enter_PICKINGTEAM, NULL, &CCSPlayer::State_PreThink_OBSERVER_MODE }, + { STATE_PICKINGCLASS, "STATE_PICKINGCLASS", &CCSPlayer::State_Enter_PICKINGCLASS, NULL, &CCSPlayer::State_PreThink_OBSERVER_MODE }, + { STATE_DEATH_ANIM, "STATE_DEATH_ANIM", &CCSPlayer::State_Enter_DEATH_ANIM, NULL, &CCSPlayer::State_PreThink_DEATH_ANIM }, + { STATE_DEATH_WAIT_FOR_KEY, "STATE_DEATH_WAIT_FOR_KEY", &CCSPlayer::State_Enter_DEATH_WAIT_FOR_KEY, NULL, &CCSPlayer::State_PreThink_DEATH_WAIT_FOR_KEY }, + { STATE_OBSERVER_MODE, "STATE_OBSERVER_MODE", &CCSPlayer::State_Enter_OBSERVER_MODE, NULL, &CCSPlayer::State_PreThink_OBSERVER_MODE } + }; + + for ( int i=0; i < ARRAYSIZE( playerStateInfos ); i++ ) + { + if ( playerStateInfos[i].m_iPlayerState == state ) + return &playerStateInfos[i]; + } + + return NULL; +} + + +void CCSPlayer::PhysObjectSleep() +{ + IPhysicsObject *pObj = VPhysicsGetObject(); + if ( pObj ) + pObj->Sleep(); +} + + +void CCSPlayer::PhysObjectWake() +{ + IPhysicsObject *pObj = VPhysicsGetObject(); + if ( pObj ) + pObj->Wake(); +} + + +void CCSPlayer::State_Enter_WELCOME() +{ + StartObserverMode( OBS_MODE_ROAMING ); + + // Important to set MOVETYPE_NONE or our physics object will fall while we're sitting at one of the intro cameras. + SetMoveType( MOVETYPE_NONE ); + AddSolidFlags( FSOLID_NOT_SOLID ); + + PhysObjectSleep(); + + const ConVar *hostname = cvar->FindVar( "hostname" ); + const char *title = (hostname) ? hostname->GetString() : "MESSAGE OF THE DAY"; + + // Show info panel (if it's not a simple demo map). + if ( !CSGameRules()->IsLogoMap() ) + { + if ( CommandLine()->FindParm( "-makereslists" ) ) // don't show the MOTD when making reslists + { + engine->ClientCommand( edict(), "jointeam 3\n" ); + } + else + { + KeyValues *data = new KeyValues("data"); + data->SetString( "title", title ); // info panel title + data->SetString( "type", "1" ); // show userdata from stringtable entry + data->SetString( "msg", "motd" ); // use this stringtable entry + data->SetInt( "cmd", TEXTWINDOW_CMD_JOINGAME ); // exec this command if panel closed + data->SetBool( "unload", sv_motd_unload_on_dismissal.GetBool() ); + + ShowViewPortPanel( PANEL_INFO, true, data ); + + data->deleteThis(); + } + } +} + + +void CCSPlayer::State_PreThink_WELCOME() +{ + // Verify some state. + Assert( IsSolidFlagSet( FSOLID_NOT_SOLID ) ); + Assert( GetAbsVelocity().Length() == 0 ); + + // Update whatever intro camera it's at. + if( m_pIntroCamera && (gpGlobals->curtime >= m_fIntroCamTime) ) + { + MoveToNextIntroCamera(); + } +} + + +void CCSPlayer::State_Enter_PICKINGTEAM() +{ + ShowViewPortPanel( "team" ); // show the team menu +} + + +void CCSPlayer::State_Enter_DEATH_ANIM() +{ + 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(); + } + + // Used for a timer. + m_flDeathTime = gpGlobals->curtime; + + m_bAbortFreezeCam = false; + + StartObserverMode( OBS_MODE_DEATHCAM ); // go to observer mode + RemoveEffects( EF_NODRAW ); // still draw player body + + if ( mp_fadetoblack.GetBool() ) + { + color32_s clr = {0,0,0,255}; + UTIL_ScreenFade( this, clr, 3, 3, FFADE_OUT | FFADE_STAYOUT ); + //Don't perform any freezecam stuff if we are fading to black + State_Transition( STATE_DEATH_WAIT_FOR_KEY ); + } +} + + +//============================================================================= +// HPE_BEGIN: +// [menglish, pfreese] Added freeze cam logic +//============================================================================= + +void CCSPlayer::State_PreThink_DEATH_ANIM() +{ + // If the anim is done playing, go to the next state (waiting for a keypress to + // either respawn the guy or put him into observer mode). + if ( GetFlags() & FL_ONGROUND ) + { + float flForward = GetAbsVelocity().Length() - 20; + if (flForward <= 0) + { + SetAbsVelocity( vec3_origin ); + } + else + { + Vector vAbsVel = GetAbsVelocity(); + VectorNormalize( vAbsVel ); + vAbsVel *= flForward; + SetAbsVelocity( vAbsVel ); + } + } + + float fDeathEnd = m_flDeathTime + CS_DEATH_ANIMATION_TIME; + float fFreezeEnd = fDeathEnd + spec_freeze_traveltime.GetFloat() + spec_freeze_time.GetFloat(); + + // transition to Freezecam mode once the death animation is complete + if ( gpGlobals->curtime >= fDeathEnd ) + { + if ( GetObserverTarget() && GetObserverTarget() != this && + !m_bAbortFreezeCam && gpGlobals->curtime < fFreezeEnd && GetObserverMode() != OBS_MODE_FREEZECAM) + { + StartObserverMode( OBS_MODE_FREEZECAM ); + } + else if(GetObserverMode() == OBS_MODE_FREEZECAM) + { + if ( m_bAbortFreezeCam && !mp_fadetoblack.GetBool() ) + { + State_Transition( STATE_OBSERVER_MODE ); + } + } + } + + // Don't transfer to observer state until the freeze cam is done + if ( gpGlobals->curtime < fFreezeEnd ) + return; + + State_Transition( STATE_OBSERVER_MODE ); +} + +//============================================================================= +// HPE_END +//============================================================================= + + +void CCSPlayer::State_Enter_DEATH_WAIT_FOR_KEY() +{ + // Remember when we died, so we can automatically put them into observer mode + // if they don't hit a key soon enough. + + m_lifeState = LIFE_DEAD; + + StopAnimation(); + + // Don't do this. The ragdoll system expects to be able to read from this player on + // the next update and will read it at the new origin if this is set. + // Since it is more complicated to redesign the ragdoll system to not need that data + // it is easier to cause a less obvious bug than popping ragdolls + //AddEffects( EF_NOINTERP ); +} + + +void CCSPlayer::State_PreThink_DEATH_WAIT_FOR_KEY() +{ + // once we're done animating our death and we're on the ground, we want to set movetype to None so our dead body won't do collisions and stuff anymore + // this prevents a bug where the dead body would go to a player's head if he walked over it while the dead player was clicking their button to respawn + if ( GetMoveType() != MOVETYPE_NONE && (GetFlags() & FL_ONGROUND) ) + SetMoveType( MOVETYPE_NONE ); + + // 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. + + bool fAnyButtonDown = (m_nButtons & ~IN_SCORE) != 0; + if ( mp_fadetoblack.GetBool() ) + fAnyButtonDown = false; + + // after a certain amount of time switch to observer mode even if they don't press a key. + if (gpGlobals->curtime >= (m_flDeathTime + DEATH_ANIMATION_TIME + 3.0)) + { + fAnyButtonDown = true; + } + + if ( fAnyButtonDown ) + { + if ( GetObserverTarget() ) + { + StartReplayMode( 8, 8, GetObserverTarget()->entindex() ); + } + + State_Transition( STATE_OBSERVER_MODE ); + } +} + +void CCSPlayer::State_Enter_OBSERVER_MODE() +{ + // do we have fadetoblack on? (need to fade their screen back in) + if ( mp_fadetoblack.GetBool() && mp_forcecamera.GetInt() != OBS_ALLOW_NONE) + { + color32_s clr = { 0,0,0,255 }; + UTIL_ScreenFade( this, clr, 0, 0, FFADE_IN | FFADE_PURGE ); + } + + int observerMode = m_iObserverLastMode; + if ( IsNetClient() ) + { + const char *pIdealMode = engine->GetClientConVarValue( engine->IndexOfEdict( edict() ), "cl_spec_mode" ); + if ( pIdealMode ) + { + int nIdealMode = atoi( pIdealMode ); + + if ( nIdealMode < OBS_MODE_IN_EYE ) + { + nIdealMode = OBS_MODE_IN_EYE; + } + else if ( nIdealMode > OBS_MODE_ROAMING ) + { + nIdealMode = OBS_MODE_ROAMING; + } + + observerMode = nIdealMode; + } + } + + StartObserverMode( observerMode ); + + PhysObjectSleep(); +} + +void CCSPlayer::State_PreThink_OBSERVER_MODE() +{ + // Make sure nobody has changed any of our state. +// Assert( GetMoveType() == MOVETYPE_FLY ); + Assert( m_takedamage == DAMAGE_NO ); + Assert( IsSolidFlagSet( FSOLID_NOT_SOLID ) ); +// Assert( IsEffectActive( EF_NODRAW ) ); + + // Must be dead. + Assert( m_lifeState == LIFE_DEAD ); + Assert( pl.deadflag ); +} + + +void CCSPlayer::State_Enter_PICKINGCLASS() +{ + if ( CommandLine()->FindParm( "-makereslists" ) ) // don't show the menu when making reslists + { + engine->ClientCommand( edict(), "joinclass 0\n" ); + return; + } + + // go to spec mode, if dying keep deathcam + if ( GetObserverMode() == OBS_MODE_DEATHCAM ) + { + StartObserverMode( OBS_MODE_DEATHCAM ); + } + else + { + StartObserverMode( OBS_MODE_FIXED ); + } + + m_iClass = (int)CS_CLASS_NONE; + + PhysObjectSleep(); + + // show the class menu: + if ( GetTeamNumber() == TEAM_TERRORIST && TerroristPlayerModels.Count() > 1 ) + { + ShowViewPortPanel( PANEL_CLASS_TER ); + } + else if ( GetTeamNumber() == TEAM_CT && CTPlayerModels.Count() > 1 ) + { + ShowViewPortPanel( PANEL_CLASS_CT ); + } + else + { + HandleCommand_JoinClass( 0 ); + } +} + +void CCSPlayer::State_Enter_ACTIVE() +{ + SetMoveType( MOVETYPE_WALK ); + RemoveSolidFlags( FSOLID_NOT_SOLID ); + m_Local.m_iHideHUD = 0; + PhysObjectWake(); +} + + +void CCSPlayer::State_PreThink_ACTIVE() +{ + // We only allow noclip here only because noclip is useful for debugging. + // It would be nice if the noclip command set some flag so we could tell that they + // did it intentionally. + if ( IsEFlagSet( EFL_NOCLIP_ACTIVE ) ) + { +// Assert( GetMoveType() == MOVETYPE_NOCLIP ); + } + else + { +// Assert( GetMoveType() == MOVETYPE_WALK ); + } + + Assert( !IsSolidFlagSet( FSOLID_NOT_SOLID ) ); +} + + +void CCSPlayer::Weapon_Equip( CBaseCombatWeapon *pWeapon ) +{ + CWeaponCSBase *pCSWeapon = dynamic_cast< CWeaponCSBase* >( pWeapon ); + if ( pCSWeapon ) + { + // For rifles, pistols, or the knife, drop our old weapon in this slot. + if ( pCSWeapon->GetSlot() == WEAPON_SLOT_RIFLE || + pCSWeapon->GetSlot() == WEAPON_SLOT_PISTOL || + pCSWeapon->GetSlot() == WEAPON_SLOT_KNIFE ) + { + CBaseCombatWeapon *pDropWeapon = Weapon_GetSlot( pCSWeapon->GetSlot() ); + if ( pDropWeapon ) + { + CSWeaponDrop( pDropWeapon, false, true ); + } + } + else if( pCSWeapon->GetCSWpnData().m_WeaponType == WEAPONTYPE_GRENADE ) + { + //if we already have this weapon, just add the ammo and destroy it + if( Weapon_OwnsThisType( pCSWeapon->GetClassname() ) ) + { + Weapon_EquipAmmoOnly( pWeapon ); + UTIL_Remove( pCSWeapon ); + return; + } + } + + pCSWeapon->SetSolidFlags( FSOLID_NOT_SOLID ); + pCSWeapon->SetOwnerEntity( this ); + } + + BaseClass::Weapon_Equip( pWeapon ); +} + +bool CCSPlayer::Weapon_CanUse( CBaseCombatWeapon *pBaseWeapon ) +{ + CWeaponCSBase *pWeapon = dynamic_cast< CWeaponCSBase* >( pBaseWeapon ); + + if ( pWeapon ) + { + // Don't give weapon_c4 to non-terrorists + if( pWeapon->GetCSWpnData().m_WeaponType == WEAPONTYPE_C4 && GetTeamNumber() != TEAM_TERRORIST ) + { + return false; + } + } + + return true; +} + +bool CCSPlayer::BumpWeapon( CBaseCombatWeapon *pBaseWeapon ) +{ + CWeaponCSBase *pWeapon = dynamic_cast< CWeaponCSBase* >( pBaseWeapon ); + if ( !pWeapon ) + { + Assert( !pWeapon ); + pBaseWeapon->AddSolidFlags( FSOLID_NOT_SOLID ); + pBaseWeapon->AddEffects( EF_NODRAW ); + Weapon_Equip( pBaseWeapon ); + return true; + } + + CBaseCombatCharacter *pOwner = pWeapon->GetOwner(); + + // Can I have this weapon type? + if ( pOwner || !Weapon_CanUse( pWeapon ) || !g_pGameRules->CanHavePlayerItem( this, pWeapon ) ) + { + extern int gEvilImpulse101; + if ( gEvilImpulse101 ) + { + UTIL_Remove( pWeapon ); + } + return false; + } + + // Even if we already have a grenade in this slot, we can pickup another one if we don't already + // own this type of grenade. + bool bPickupGrenade = ( pWeapon->GetCSWpnData().m_WeaponType == WEAPONTYPE_GRENADE ); + + + /* + // ---------------------------------------- + // If I already have it just take the ammo + // ---------------------------------------- + if ( !bPickupGrenade && Weapon_SlotOccupied( pWeapon ) ) + { + Weapon_EquipAmmoOnly( pWeapon ); + // Only remove me if I have no ammo left + // Can't just check HasAnyAmmo because if I don't use clips, I want to be removed, + if ( pWeapon->UsesClipsForAmmo1() && pWeapon->HasPrimaryAmmo() ) + return false; + + UTIL_Remove( pWeapon ); + return false; + } + */ + + if ( HasShield() && pWeapon->GetCSWpnData().m_bCanUseWithShield == false ) + return false; + + // Check ammo counts for grenades, and don't try to pick up more grenades than we can carry + if ( bPickupGrenade ) + { + CBaseCombatWeapon *pOwnedGrenade = Weapon_OwnsThisType( pWeapon->GetClassname() ); + + if( pOwnedGrenade ) + { + int numGrenades = 0; + int maxGrenades = 0; + + int ammoIndex = pOwnedGrenade->GetPrimaryAmmoType(); + if( ammoIndex != -1 ) + { + numGrenades = GetAmmoCount( ammoIndex ); + } + maxGrenades = GetAmmoDef()->MaxCarry(ammoIndex); + + if( numGrenades >= maxGrenades ) + { + return false; + } + } + } + + if( bPickupGrenade || !Weapon_SlotOccupied( pWeapon ) ) + { + pWeapon->CheckRespawn(); + + pWeapon->AddSolidFlags( FSOLID_NOT_SOLID ); + pWeapon->AddEffects( EF_NODRAW ); + + CCSPlayer* pDonor = pWeapon->GetDonor(); + if ( pDonor && pDonor != this && pWeapon->GetCSWpnData().GetWeaponPrice() > m_iAccount ) + { + CCS_GameStats.Event_PlayerDonatedWeapon( pDonor ); + } + pWeapon->SetDonor(NULL); + + Weapon_Equip( pWeapon ); + + int iExtraAmmo = pWeapon->GetExtraAmmoCount(); + + if( iExtraAmmo && !bPickupGrenade ) + { + //Find out the index of the ammo + int iAmmoIndex = pWeapon->GetPrimaryAmmoType(); + + if( iAmmoIndex != -1 ) + { + //Remove the extra ammo from the weapon + pWeapon->SetExtraAmmoCount(0); + + //Give it to the player + SetAmmoCount( iExtraAmmo, iAmmoIndex ); + } + } + + IGameEvent * event = gameeventmanager->CreateEvent( "item_pickup" ); + if( event ) + { + const char *weaponName = pWeapon->GetClassname(); + if ( strncmp( weaponName, "weapon_", 7 ) == 0 ) + { + weaponName += 7; + } + event->SetInt( "userid", GetUserID() ); + event->SetString( "item", weaponName ); + gameeventmanager->FireEvent( event ); + } + + return true; + } + + return false; +} + + +void CCSPlayer::ResetStamina( void ) +{ + m_flStamina = 0.0f; +} + +void CCSPlayer::RescueZoneTouch( inputdata_t &inputdata ) +{ + m_bInHostageRescueZone = true; + if ( GetTeamNumber() == TEAM_CT && !(m_iDisplayHistoryBits & DHF_IN_RESCUE_ZONE) ) + { + HintMessage( "#Hint_hostage_rescue_zone", false ); + m_iDisplayHistoryBits |= DHF_IN_RESCUE_ZONE; + } +} + +//------------------------------------------------------------------------------------------ +CON_COMMAND( timeleft, "prints the time remaining in the match" ) +{ + CCSPlayer *pPlayer = ToCSPlayer( UTIL_GetCommandClient() ); + if ( pPlayer && pPlayer->m_iNextTimeCheck >= gpGlobals->curtime ) + { + return; // rate limiting + } + + int iTimeRemaining = (int)CSGameRules()->GetMapRemainingTime(); + + if ( iTimeRemaining < 0 ) + { + if ( pPlayer ) + { + ClientPrint( pPlayer, HUD_PRINTTALK, "#Game_no_timelimit" ); + } + else + { + Msg( "* No Time Limit *\n" ); + } + } + else if ( iTimeRemaining == 0 ) + { + if ( pPlayer ) + { + ClientPrint( pPlayer, HUD_PRINTTALK, "#Game_last_round" ); + } + else + { + Msg( "* Last Round *\n" ); + } + } + else + { + int iMinutes, iSeconds; + iMinutes = iTimeRemaining / 60; + iSeconds = iTimeRemaining % 60; + + char minutes[8]; + char seconds[8]; + + Q_snprintf( minutes, sizeof(minutes), "%d", iMinutes ); + Q_snprintf( seconds, sizeof(seconds), "%2.2d", iSeconds ); + + if ( pPlayer ) + { + ClientPrint( pPlayer, HUD_PRINTTALK, "#Game_timelimit", minutes, seconds ); + } + else + { + Msg( "Time Remaining: %s:%s\n", minutes, seconds ); + } + } + + if ( pPlayer ) + { + pPlayer->m_iNextTimeCheck = gpGlobals->curtime + 1; + } +} + +//------------------------------------------------------------------------------------------ +/** + * Emit given sound that only we can hear + */ +void CCSPlayer::EmitPrivateSound( const char *soundName ) +{ + CSoundParameters params; + if (!GetParametersForSound( soundName, params, NULL )) + return; + + CSingleUserRecipientFilter filter( this ); + EmitSound( filter, entindex(), soundName ); +} + + +//===================== +//Autobuy +//===================== +static void AutoBuy( void ) +{ + CCSPlayer *player = ToCSPlayer( UTIL_GetCommandClient() ); + + if ( player ) + player->AutoBuy(); +} +static ConCommand autobuy( "autobuy", AutoBuy, "Attempt to purchase items with the order listed in cl_autobuy" ); + +//============================================== +//AutoBuy - do the work of deciding what to buy +//============================================== +void CCSPlayer::AutoBuy() +{ + if ( !IsInBuyZone() ) + { + EmitPrivateSound( "BuyPreset.CantBuy" ); + return; + } + + const char *autobuyString = engine->GetClientConVarValue( engine->IndexOfEdict( edict() ), "cl_autobuy" ); + if ( !autobuyString || !*autobuyString ) + { + EmitPrivateSound( "BuyPreset.AlreadyBought" ); + return; + } + + bool boughtPrimary = false, boughtSecondary = false; + + m_bIsInAutoBuy = true; + ParseAutoBuyString(autobuyString, boughtPrimary, boughtSecondary); + m_bIsInAutoBuy = false; + + m_bAutoReload = true; + + //TODO ?: stripped out all the attempts to buy career weapons. + // as we're not porting cs:cz, these were skipped +} + +void CCSPlayer::ParseAutoBuyString(const char *string, bool &boughtPrimary, bool &boughtSecondary) +{ + char command[32]; + int nBuffSize = sizeof(command) - 1; // -1 to leave space for the NULL at the end of the string + const char *c = string; + + if (c == NULL) + { + EmitPrivateSound( "BuyPreset.AlreadyBought" ); + return; + } + + BuyResult_e overallResult = BUY_ALREADY_HAVE; + + // loop through the string of commands, trying each one in turn. + while (*c != 0) + { + int i = 0; + // copy the next word into the command buffer. + while ((*c != 0) && (*c != ' ') && (i < nBuffSize)) + { + command[i] = *(c); + ++c; + ++i; + } + if (*c == ' ') + { + ++c; // skip the space. + } + + command[i] = 0; // terminate the string. + + // clear out any spaces. + i = 0; + while (command[i] != 0) + { + if (command[i] == ' ') + { + command[i] = 0; + break; + } + ++i; + } + + // make sure we actually have a command. + if (strlen(command) == 0) + { + continue; + } + + AutoBuyInfoStruct * commandInfo = GetAutoBuyCommandInfo(command); + + if (ShouldExecuteAutoBuyCommand(commandInfo, boughtPrimary, boughtSecondary)) + { + BuyResult_e result = HandleCommand_Buy( command ); + + overallResult = CombineBuyResults( overallResult, result ); + + // check to see if we actually bought a primary or secondary weapon this time. + PostAutoBuyCommandProcessing(commandInfo, boughtPrimary, boughtSecondary); + } + } + + if ( overallResult == BUY_CANT_AFFORD ) + { + EmitPrivateSound( "BuyPreset.CantBuy" ); + } + else if ( overallResult == BUY_ALREADY_HAVE ) + { + EmitPrivateSound( "BuyPreset.AlreadyBought" ); + } + else if ( overallResult == BUY_BOUGHT ) + { + g_iAutoBuyPurchases++; + } +} + +BuyResult_e CCSPlayer::CombineBuyResults( BuyResult_e prevResult, BuyResult_e newResult ) +{ + if ( newResult == BUY_BOUGHT ) + { + prevResult = BUY_BOUGHT; + } + else if ( prevResult != BUY_BOUGHT && + (newResult == BUY_CANT_AFFORD || newResult == BUY_INVALID_ITEM || newResult == BUY_PLAYER_CANT_BUY ) ) + { + prevResult = BUY_CANT_AFFORD; + } + + return prevResult; +} + +//============================================== +//PostAutoBuyCommandProcessing +//============================================== +void CCSPlayer::PostAutoBuyCommandProcessing(const AutoBuyInfoStruct *commandInfo, bool &boughtPrimary, bool &boughtSecondary) +{ + if (commandInfo == NULL) + { + return; + } + + CBaseCombatWeapon *pPrimary = Weapon_GetSlot( WEAPON_SLOT_RIFLE ); + CBaseCombatWeapon *pSecondary = Weapon_GetSlot( WEAPON_SLOT_PISTOL ); + + if ((pPrimary != NULL) && (stricmp(pPrimary->GetClassname(), commandInfo->m_classname) == 0)) + { + // I just bought the gun I was trying to buy. + boughtPrimary = true; + } + else if ((pPrimary == NULL) && ((commandInfo->m_class & AUTOBUYCLASS_SHIELD) == AUTOBUYCLASS_SHIELD) && HasShield()) + { + // the shield is a primary weapon even though it isn't a "real" weapon. + boughtPrimary = true; + } + else if ((pSecondary != NULL) && (stricmp(pSecondary->GetClassname(), commandInfo->m_classname) == 0)) + { + // I just bought the pistol I was trying to buy. + boughtSecondary = true; + } +} + +bool CCSPlayer::ShouldExecuteAutoBuyCommand(const AutoBuyInfoStruct *commandInfo, bool boughtPrimary, bool boughtSecondary) +{ + if (commandInfo == NULL) + { + return false; + } + + if ((boughtPrimary) && ((commandInfo->m_class & AUTOBUYCLASS_PRIMARY) != 0) && ((commandInfo->m_class & AUTOBUYCLASS_AMMO) == 0)) + { + // this is a primary weapon and we already have one. + return false; + } + + if ((boughtSecondary) && ((commandInfo->m_class & AUTOBUYCLASS_SECONDARY) != 0) && ((commandInfo->m_class & AUTOBUYCLASS_AMMO) == 0)) + { + // this is a secondary weapon and we already have one. + return false; + } + + if( commandInfo->m_class & AUTOBUYCLASS_ARMOR && ArmorValue() >= 100 ) + { + return false; + } + + return true; +} + +AutoBuyInfoStruct *CCSPlayer::GetAutoBuyCommandInfo(const char *command) +{ + int i = 0; + AutoBuyInfoStruct *ret = NULL; + AutoBuyInfoStruct *temp = &(g_autoBuyInfo[i]); + + // loop through all the commands till we find the one that matches. + while ((ret == NULL) && (temp->m_class != (AutoBuyClassType)0)) + { + temp = &(g_autoBuyInfo[i]); + ++i; + + if (stricmp(temp->m_command, command) == 0) + { + ret = temp; + } + } + + return ret; +} + +//============================================== +//PostAutoBuyCommandProcessing +//- reorders the tokens in autobuyString based on the order of tokens in the priorityString. +//============================================== +void CCSPlayer::PrioritizeAutoBuyString(char *autobuyString, const char *priorityString) +{ + char newString[256]; + int newStringPos = 0; + char priorityToken[32]; + + if ((priorityString == NULL) || (autobuyString == NULL)) + { + return; + } + + const char *priorityChar = priorityString; + + while (*priorityChar != 0) + { + int i = 0; + + // get the next token from the priority string. + while ((*priorityChar != 0) && (*priorityChar != ' ')) + { + priorityToken[i] = *priorityChar; + ++i; + ++priorityChar; + } + priorityToken[i] = 0; + + // skip spaces + while (*priorityChar == ' ') + { + ++priorityChar; + } + + if (strlen(priorityToken) == 0) + { + continue; + } + + // see if the priority token is in the autobuy string. + // if it is, copy that token to the new string and blank out + // that token in the autobuy string. + char *autoBuyPosition = strstr(autobuyString, priorityToken); + if (autoBuyPosition != NULL) + { + while ((*autoBuyPosition != 0) && (*autoBuyPosition != ' ')) + { + newString[newStringPos] = *autoBuyPosition; + *autoBuyPosition = ' '; + ++newStringPos; + ++autoBuyPosition; + } + + newString[newStringPos++] = ' '; + } + } + + // now just copy anything left in the autobuyString to the new string in the order it's in already. + char *autobuyPosition = autobuyString; + while (*autobuyPosition != 0) + { + // skip spaces + while (*autobuyPosition == ' ') + { + ++autobuyPosition; + } + + // copy the token over to the new string. + while ((*autobuyPosition != 0) && (*autobuyPosition != ' ')) + { + newString[newStringPos] = *autobuyPosition; + ++newStringPos; + ++autobuyPosition; + } + + // add a space at the end. + newString[newStringPos++] = ' '; + } + + // terminate the string. Trailing spaces shouldn't matter. + newString[newStringPos] = 0; + + Q_snprintf(autobuyString, sizeof(autobuyString), "%s", newString); +} + + +//============================================================== +// ReBuy +// system for attempting to buy the weapons you had last round +//============================================================== +static void Rebuy( void ) +{ + CCSPlayer *player = ToCSPlayer( UTIL_GetCommandClient() ); + + if ( player ) + player->Rebuy(); +} +static ConCommand rebuy( "rebuy", Rebuy, "Attempt to repurchase items with the order listed in cl_rebuy" ); + +void CCSPlayer::BuildRebuyStruct() +{ + if (m_bIsInRebuy) + { + // if we are in the middle of a rebuy, we don't want to update the buy struct. + return; + } + + CBaseCombatWeapon *primary = Weapon_GetSlot( WEAPON_SLOT_RIFLE ); + CBaseCombatWeapon *secondary = Weapon_GetSlot( WEAPON_SLOT_PISTOL ); + + // do the primary weapon/ammo stuff. + if (primary == NULL) + { + // count a shieldgun as a primary. + if (HasShield()) + { + //m_rebuyStruct.m_primaryWeapon = WEAPON_SHIELDGUN; + Q_strncpy( m_rebuyStruct.m_szPrimaryWeapon, "shield", sizeof(m_rebuyStruct.m_szPrimaryWeapon) ); + m_rebuyStruct.m_primaryAmmo = 0; // shields don't have ammo. + } + else + { + + m_rebuyStruct.m_szPrimaryWeapon[0] = 0; // if we don't have a shield and we don't have a primary weapon, we got nuthin. + m_rebuyStruct.m_primaryAmmo = 0; // can't have ammo if we don't have a gun right? + } + } + else + { + //strip off the "weapon_" + + const char *wpnName = primary->GetClassname(); + + Q_strncpy( m_rebuyStruct.m_szPrimaryWeapon, wpnName + 7, sizeof(m_rebuyStruct.m_szPrimaryWeapon) ); + + if( primary->GetPrimaryAmmoType() != -1 ) + { + m_rebuyStruct.m_primaryAmmo = GetAmmoCount( primary->GetPrimaryAmmoType() ); + } + } + + // do the secondary weapon/ammo stuff. + if (secondary == NULL) + { + m_rebuyStruct.m_szSecondaryWeapon[0] = 0; + m_rebuyStruct.m_secondaryAmmo = 0; // can't have ammo if we don't have a gun right? + } + else + { + const char *wpnName = secondary->GetClassname(); + + Q_strncpy( m_rebuyStruct.m_szSecondaryWeapon, wpnName + 7, sizeof(m_rebuyStruct.m_szSecondaryWeapon) ); + + if( secondary->GetPrimaryAmmoType() != -1 ) + { + m_rebuyStruct.m_secondaryAmmo = GetAmmoCount( secondary->GetPrimaryAmmoType() ); + } + } + + CBaseCombatWeapon *pGrenade; + + //MATTTODO: right now you can't buy more than one grenade. make it so you can + //buy more and query the number you have. + // HE Grenade + pGrenade = Weapon_OwnsThisType( "weapon_hegrenade" ); + if ( pGrenade && pGrenade->GetPrimaryAmmoType() != -1 ) + { + m_rebuyStruct.m_heGrenade = GetAmmoCount(pGrenade->GetPrimaryAmmoType()); + } + else + m_rebuyStruct.m_heGrenade = 0; + + + // flashbang + pGrenade = Weapon_OwnsThisType( "weapon_flashbang" ); + if ( pGrenade && pGrenade->GetPrimaryAmmoType() != -1 ) + { + m_rebuyStruct.m_flashbang = GetAmmoCount(pGrenade->GetPrimaryAmmoType()); + } + else + m_rebuyStruct.m_flashbang = 0; + + // smokegrenade + pGrenade = Weapon_OwnsThisType( "weapon_smokegrenade" ); + if ( pGrenade /*&& pGrenade->GetPrimaryAmmoType() != -1*/ ) + { + m_rebuyStruct.m_smokeGrenade = 1; //GetAmmoCount(pGrenade->GetPrimaryAmmoType()); + } + else + m_rebuyStruct.m_smokeGrenade = 0; + + // defuser + m_rebuyStruct.m_defuser = HasDefuser(); + + // night vision + m_rebuyStruct.m_nightVision = m_bHasNightVision.Get(); //cast to avoid strange compiler warning + + // check for armor. + m_rebuyStruct.m_armor = ( m_bHasHelmet ? 2 : ( ArmorValue() > 0 ? 1 : 0 ) ); +} + +void CCSPlayer::Rebuy( void ) +{ + if ( !IsInBuyZone() ) + { + EmitPrivateSound( "BuyPreset.CantBuy" ); + return; + } + + const char *rebuyString = engine->GetClientConVarValue( engine->IndexOfEdict( edict() ), "cl_rebuy" ); + if ( !rebuyString || !*rebuyString ) + { + EmitPrivateSound( "BuyPreset.AlreadyBought" ); + return; + } + + m_bIsInRebuy = true; + BuyResult_e overallResult = BUY_ALREADY_HAVE; + + char token[256]; + rebuyString = engine->ParseFile( rebuyString, token, sizeof( token ) ); + + while (rebuyString != NULL) + { + BuyResult_e result = BUY_ALREADY_HAVE; + + if (!Q_strncmp(token, "PrimaryWeapon", 14)) + { + result = RebuyPrimaryWeapon(); + } + else if (!Q_strncmp(token, "PrimaryAmmo", 12)) + { + result = RebuyPrimaryAmmo(); + } + else if (!Q_strncmp(token, "SecondaryWeapon", 16)) + { + result = RebuySecondaryWeapon(); + } + else if (!Q_strncmp(token, "SecondaryAmmo", 14)) + { + result = RebuySecondaryAmmo(); + } + else if (!Q_strncmp(token, "HEGrenade", 10)) + { + result = RebuyHEGrenade(); + } + else if (!Q_strncmp(token, "Flashbang", 10)) + { + result = RebuyFlashbang(); + } + else if (!Q_strncmp(token, "SmokeGrenade", 13)) + { + result = RebuySmokeGrenade(); + } + else if (!Q_strncmp(token, "Defuser", 8)) + { + result = RebuyDefuser(); + } + else if (!Q_strncmp(token, "NightVision", 12)) + { + result = RebuyNightVision(); + } + else if (!Q_strncmp(token, "Armor", 6)) + { + result = RebuyArmor(); + } + + overallResult = CombineBuyResults( overallResult, result ); + + rebuyString = engine->ParseFile( rebuyString, token, sizeof( token ) ); + } + + m_bIsInRebuy = false; + + // after we're done buying, the user is done with their equipment purchasing experience. + // so we are effectively out of the buy zone. +// if (TheTutor != NULL) +// { +// TheTutor->OnEvent(EVENT_PLAYER_LEFT_BUY_ZONE); +// } + + m_bAutoReload = true; + + if ( overallResult == BUY_CANT_AFFORD ) + { + EmitPrivateSound( "BuyPreset.CantBuy" ); + } + else if ( overallResult == BUY_ALREADY_HAVE ) + { + EmitPrivateSound( "BuyPreset.AlreadyBought" ); + } + else if ( overallResult == BUY_BOUGHT ) + { + g_iReBuyPurchases++; + } +} + +BuyResult_e CCSPlayer::RebuyPrimaryWeapon() +{ + CBaseCombatWeapon *primary = Weapon_GetSlot( WEAPON_SLOT_RIFLE ); + if (primary != NULL) + { + return BUY_ALREADY_HAVE; // don't drop primary weapons via rebuy - if the player picked up a different weapon, he wants to keep it. + } + + if( strlen( m_rebuyStruct.m_szPrimaryWeapon ) > 0 ) + return HandleCommand_Buy(m_rebuyStruct.m_szPrimaryWeapon); + + return BUY_ALREADY_HAVE; +} + +BuyResult_e CCSPlayer::RebuySecondaryWeapon() +{ + CBaseCombatWeapon *pistol = Weapon_GetSlot( WEAPON_SLOT_PISTOL ); + if (pistol != NULL && !m_bUsingDefaultPistol) + { + return BUY_ALREADY_HAVE; // don't drop pistols via rebuy if we've bought one other than the default pistol + } + + if( strlen( m_rebuyStruct.m_szSecondaryWeapon ) > 0 ) + return HandleCommand_Buy(m_rebuyStruct.m_szSecondaryWeapon); + + return BUY_ALREADY_HAVE; +} + +BuyResult_e CCSPlayer::RebuyPrimaryAmmo() +{ + CBaseCombatWeapon *primary = Weapon_GetSlot( WEAPON_SLOT_RIFLE ); + + if (primary == NULL) + { + return BUY_ALREADY_HAVE; // can't buy ammo when we don't even have a gun. + } + + // Ensure that the weapon uses ammo + int nAmmo = primary->GetPrimaryAmmoType(); + if ( nAmmo == -1 ) + { + return BUY_ALREADY_HAVE; + } + + // if we had more ammo before than we have now, buy more. + if (m_rebuyStruct.m_primaryAmmo > GetAmmoCount( nAmmo )) + { + return HandleCommand_Buy("primammo"); + } + + return BUY_ALREADY_HAVE; +} + + +BuyResult_e CCSPlayer::RebuySecondaryAmmo() +{ + CBaseCombatWeapon *secondary = Weapon_GetSlot( WEAPON_SLOT_PISTOL ); + + if (secondary == NULL) + { + return BUY_ALREADY_HAVE; // can't buy ammo when we don't even have a gun. + } + + // Ensure that the weapon uses ammo + int nAmmo = secondary->GetPrimaryAmmoType(); + if ( nAmmo == -1 ) + { + return BUY_ALREADY_HAVE; + } + + if (m_rebuyStruct.m_secondaryAmmo > GetAmmoCount( nAmmo )) + { + return HandleCommand_Buy("secammo"); + } + + return BUY_ALREADY_HAVE; +} + +BuyResult_e CCSPlayer::RebuyHEGrenade() +{ + CBaseCombatWeapon *pGrenade = Weapon_OwnsThisType( "weapon_hegrenade" ); + + int numGrenades = 0; + + if( pGrenade ) + { + int nAmmo = pGrenade->GetPrimaryAmmoType(); + if ( nAmmo == -1 ) + { + return BUY_ALREADY_HAVE; + } + + numGrenades = GetAmmoCount( nAmmo ); + } + + BuyResult_e overallResult = BUY_ALREADY_HAVE; + int numToBuy = MAX( 0, m_rebuyStruct.m_heGrenade - numGrenades ); + for (int i = 0; i < numToBuy; ++i) + { + BuyResult_e result = HandleCommand_Buy("hegrenade"); + overallResult = CombineBuyResults( overallResult, result ); + } + + return overallResult; +} + +BuyResult_e CCSPlayer::RebuyFlashbang() +{ + CBaseCombatWeapon *pGrenade = Weapon_OwnsThisType( "weapon_flashbang" ); + + int numGrenades = 0; + + if( pGrenade ) + { + int nAmmo = pGrenade->GetPrimaryAmmoType(); + if ( nAmmo == -1 ) + { + return BUY_ALREADY_HAVE; + } + numGrenades = GetAmmoCount( nAmmo ); + + } + + BuyResult_e overallResult = BUY_ALREADY_HAVE; + int numToBuy = MAX( 0, m_rebuyStruct.m_flashbang - numGrenades ); + for (int i = 0; i < numToBuy; ++i) + { + BuyResult_e result = HandleCommand_Buy("flashbang"); + overallResult = CombineBuyResults( overallResult, result ); + } + + return overallResult; +} + +BuyResult_e CCSPlayer::RebuySmokeGrenade() +{ + CBaseCombatWeapon *pGrenade = Weapon_OwnsThisType( "weapon_smokegrenade" ); + + int numGrenades = 0; + + if( pGrenade ) + { + int nAmmo = pGrenade->GetPrimaryAmmoType(); + if ( nAmmo == -1 ) + { + return BUY_ALREADY_HAVE; + } + + numGrenades = GetAmmoCount( nAmmo ); + } + + BuyResult_e overallResult = BUY_ALREADY_HAVE; + int numToBuy = MAX( 0, m_rebuyStruct.m_smokeGrenade - numGrenades ); + for (int i = 0; i < numToBuy; ++i) + { + BuyResult_e result = HandleCommand_Buy("smokegrenade"); + overallResult = CombineBuyResults( overallResult, result ); + } + + return overallResult; +} + +BuyResult_e CCSPlayer::RebuyDefuser() +{ + //If we don't have a defuser, and we want one, buy it! + if( !HasDefuser() && m_rebuyStruct.m_defuser ) + { + return HandleCommand_Buy("defuser"); + } + + return BUY_ALREADY_HAVE; +} + +BuyResult_e CCSPlayer::RebuyNightVision() +{ + //if we don't have night vision and we want one, buy it! + if( !m_bHasNightVision && m_rebuyStruct.m_nightVision ) + { + return HandleCommand_Buy("nvgs"); + } + + return BUY_ALREADY_HAVE; +} + +BuyResult_e CCSPlayer::RebuyArmor() +{ + if (m_rebuyStruct.m_armor > 0 ) + { + int armor = 0; + + if( m_bHasHelmet ) + armor = 2; + else if( ArmorValue() > 0 ) + armor = 1; + + if( armor < m_rebuyStruct.m_armor ) + { + if (m_rebuyStruct.m_armor == 1) + { + return HandleCommand_Buy("vest"); + } + else + { + return HandleCommand_Buy("vesthelm"); + } + } + + } + + return BUY_ALREADY_HAVE; +} + +bool CCSPlayer::IsUseableEntity( CBaseEntity *pEntity, unsigned int requiredCaps ) +{ + CWeaponCSBase *pCSWepaon = dynamic_cast<CWeaponCSBase*>(pEntity); + + if( pCSWepaon ) + { + // we can't USE dropped weapons + return true; + } + + CBaseCSGrenadeProjectile *pGrenade = dynamic_cast<CBaseCSGrenadeProjectile*>(pEntity); + if ( pGrenade ) + { + // we can't USE thrown grenades + } + + return BaseClass::IsUseableEntity( pEntity, requiredCaps ); +} + +CBaseEntity *CCSPlayer::FindUseEntity() +{ + CBaseEntity *entity = NULL; + + // Check to see if the bomb is close enough to use before attempting to use anything else. + + if ( CSGameRules()->IsBombDefuseMap() && GetTeamNumber() == TEAM_CT ) + { + // This is done separately since there might be something blocking our LOS to it + // but we might want to use it anyway if it's close enough. This should eliminate + // the vast majority of bomb placement exploits (places where the bomb can be planted + // but can't be "used". This also mimics goldsrc cstrike behavior. + CBaseEntity *bomb = gEntList.FindEntityByClassname( NULL, PLANTED_C4_CLASSNAME ); + if (bomb != NULL) + { + Vector bombPos = bomb->GetAbsOrigin(); + Vector vecLOS = EyePosition() - bombPos; + + if (vecLOS.LengthSqr() < (96*96)) // 64 is the distance in Goldsrc. However since Goldsrc did distance from the player's origin and we're doing distance from the player's eye, make the radius a bit bigger. + { + // bomb is close enough, now make sure the player is facing the bomb. + Vector forward; + AngleVectors(EyeAngles(), &forward, NULL, NULL); + + vecLOS.NormalizeInPlace(); + + float flDot = DotProduct(forward, vecLOS); + if (flDot < -0.7) // 0.7 taken from Goldsrc, +/- ~45 degrees + { + entity = bomb; + } + } + } + } + + if ( entity == NULL ) + { + entity = BaseClass::FindUseEntity(); + } + + return entity; +} + +void CCSPlayer::StockPlayerAmmo( CBaseCombatWeapon *pNewWeapon ) +{ + CWeaponCSBase *pWeapon = dynamic_cast< CWeaponCSBase * >( pNewWeapon ); + + if ( pWeapon ) + { + if ( pWeapon->GetWpnData().iFlags & ITEM_FLAG_EXHAUSTIBLE ) + return; + + int nAmmo = pWeapon->GetPrimaryAmmoType(); + + if ( nAmmo != -1 ) + { + GiveAmmo( 9999, GetAmmoDef()->GetAmmoOfIndex(nAmmo)->pName ); + pWeapon->m_iClip1 = pWeapon->GetMaxClip1(); + } + + return; + } + + pWeapon = dynamic_cast< CWeaponCSBase * >(Weapon_GetSlot( WEAPON_SLOT_RIFLE )); + + if ( pWeapon ) + { + int nAmmo = pWeapon->GetPrimaryAmmoType(); + + if ( nAmmo != -1 ) + { + GiveAmmo( 9999, GetAmmoDef()->GetAmmoOfIndex(nAmmo)->pName ); + pWeapon->m_iClip1 = pWeapon->GetMaxClip1(); + } + } + + pWeapon = dynamic_cast< CWeaponCSBase * >(Weapon_GetSlot( WEAPON_SLOT_PISTOL )); + + if ( pWeapon ) + { + int nAmmo = pWeapon->GetPrimaryAmmoType(); + + if ( nAmmo != -1 ) + { + GiveAmmo( 9999, GetAmmoDef()->GetAmmoOfIndex(nAmmo)->pName ); + pWeapon->m_iClip1 = pWeapon->GetMaxClip1(); + } + } +} + +CBaseEntity *CCSPlayer::GiveNamedItem( const char *pszName, int iSubType ) +{ + EHANDLE pent; + + if ( !pszName || !pszName[0] ) + return NULL; + +#ifndef CS_SHIELD_ENABLED + if ( !Q_stricmp( pszName, "weapon_shield" ) ) + return NULL; +#endif + + pent = CreateEntityByName(pszName); + if ( pent == NULL ) + { + Msg( "NULL Ent in GiveNamedItem!\n" ); + return NULL; + } + + pent->SetLocalOrigin( GetLocalOrigin() ); + pent->AddSpawnFlags( SF_NORESPAWN ); + + CBaseCombatWeapon *pWeapon = dynamic_cast<CBaseCombatWeapon*>( (CBaseEntity*)pent ); + if ( pWeapon ) + { + if ( iSubType ) + { + pWeapon->SetSubType( iSubType ); + } + } + + DispatchSpawn( pent ); + + m_bIsBeingGivenItem = true; + if ( pent != NULL && !(pent->IsMarkedForDeletion()) ) + { + pent->Touch( this ); + } + m_bIsBeingGivenItem = false; + + StockPlayerAmmo( pWeapon ); + return pent; +} + + +void CCSPlayer::DoAnimationEvent( PlayerAnimEvent_t event, int nData ) +{ + if ( event == PLAYERANIMEVENT_THROW_GRENADE ) + { + // Grenade throwing has to synchronize exactly with the player's grenade weapon going away, + // and events get delayed a bit, so we let CCSPlayerAnimState pickup the change to this + // variable. + m_iThrowGrenadeCounter = (m_iThrowGrenadeCounter+1) % (1<<THROWGRENADE_COUNTER_BITS); + } + else + { + m_PlayerAnimState->DoAnimationEvent( event, nData ); + TE_PlayerAnimEvent( this, event, nData ); // Send to any clients who can see this guy. + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +int CCSPlayer::FlashlightIsOn( void ) +{ + return IsEffectActive( EF_DIMLIGHT ); +} + +extern ConVar flashlight; + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CCSPlayer::FlashlightTurnOn( void ) +{ + if( flashlight.GetInt() > 0 && IsAlive() ) + { + AddEffects( EF_DIMLIGHT ); + EmitSound( "Player.FlashlightOn" ); + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CCSPlayer::FlashlightTurnOff( void ) +{ + RemoveEffects( EF_DIMLIGHT ); + + if( IsAlive() ) + { + EmitSound( "Player.FlashlightOff" ); + } +} + + +//Drop the appropriate weapons: +// Defuser if we have one +// C4 if we have one +// The best weapon we have, first check primary, +// then secondary and drop the best one + +//============================================================================= +// HPE_BEGIN: +// [tj] Added a parameter so we know if it was death that caused the drop +// [menglish] Clear all previously dropped equipment and add the c4 to the dropped equipment +//============================================================================= + +void CCSPlayer::DropWeapons( bool fromDeath, bool friendlyFire ) +{ + for ( int i = 0; i < DROPPED_COUNT; ++i ) + { + m_hDroppedEquipment[i] = NULL; + } + + CBaseCombatWeapon *pC4 = Weapon_OwnsThisType( "weapon_c4" ); + if ( pC4 ) + { + CSWeaponDrop( pC4, false, true ); + if( fromDeath ) + { + if( friendlyFire ) + { + (static_cast<CC4*> (pC4))->SetDroppedFromDeath(true); + } + m_hDroppedEquipment[DROPPED_C4] = static_cast<CBaseEntity *>(pC4); + } + } + + //NOTE: Function continues beyond comment block. This is just the part I touched. + +//============================================================================= +// HPE_END +//============================================================================= + + + if( HasDefuser() ) + { + //Drop an item_defuser + Vector vForward, vRight; + AngleVectors( GetAbsAngles(), &vForward, &vRight, NULL ); + + CBaseAnimating *pDefuser = (CBaseAnimating *)CBaseEntity::Create( "item_defuser", WorldSpaceCenter(), GetLocalAngles(), this ); + pDefuser->ApplyAbsVelocityImpulse( vForward * 200 + vRight * random->RandomFloat( -50, 50 ) ); + + RemoveDefuser(); + + //============================================================================= + // HPE_BEGIN: + // [menglish] Add the newly created defuser to the dropped equipment list + //============================================================================= + + if(fromDeath) + { + m_hDroppedEquipment[DROPPED_DEFUSE] = static_cast<CBaseEntity *>(pDefuser); + } + + //============================================================================= + // HPE_END + //============================================================================= + } + + if( HasShield() ) + { + DropShield(); + } + else + { + //drop the best weapon we have + if( !DropRifle( true ) ) + DropPistol( true ); + } + + // drop any live grenades so they explode + CBaseCSGrenade *pGrenade = dynamic_cast< CBaseCSGrenade * >(Weapon_OwnsThisType("weapon_hegrenade")); + if ( pGrenade && ( pGrenade->IsPinPulled() || pGrenade->IsBeingThrown() ) ) + { + pGrenade->DropGrenade(); + pGrenade->DecrementAmmo( this ); + } + else + { + pGrenade = dynamic_cast< CBaseCSGrenade * >(Weapon_OwnsThisType("weapon_flashbang")); + if ( pGrenade && ( pGrenade->IsPinPulled() || pGrenade->IsBeingThrown() ) ) + { + pGrenade->DropGrenade(); + pGrenade->DecrementAmmo( this ); + } + else + { + pGrenade = dynamic_cast< CBaseCSGrenade * >(Weapon_OwnsThisType("weapon_smokegrenade")); + if ( pGrenade && ( pGrenade->IsPinPulled() || pGrenade->IsBeingThrown() ) ) + { + pGrenade->DropGrenade(); + pGrenade->DecrementAmmo( this ); + } + } + } + + // drop the "best" grenade remaining + CBaseCombatWeapon *pWeapon = Weapon_OwnsThisType("weapon_hegrenade"); + bool grenadeDrop = false; + if ( pWeapon && pWeapon->HasAmmo() ) + { + grenadeDrop = CSWeaponDrop(pWeapon, false); + } + else + { + pWeapon = Weapon_OwnsThisType("weapon_flashbang"); + if ( pWeapon && pWeapon->HasAmmo() ) + { + grenadeDrop = CSWeaponDrop(pWeapon, false); + } + else + { + pWeapon = Weapon_OwnsThisType("weapon_smokegrenade"); + if ( pWeapon && pWeapon->HasAmmo() ) + { + grenadeDrop = CSWeaponDrop(pWeapon, false); + } + } + } + + //============================================================================= + // HPE_BEGIN: + // [menglish] Add whichever, if any, grenade was dropped + //============================================================================= + + if( pWeapon && grenadeDrop ) + { + m_hDroppedEquipment[DROPPED_GRENADE] = static_cast<CBaseEntity *>(pWeapon); + } + + //============================================================================= + // HPE_END + //============================================================================= +} + +//----------------------------------------------------------------------------- +// Purpose: Put the player in the specified team +//----------------------------------------------------------------------------- +void CCSPlayer::ChangeTeam( int iTeamNum ) +{ + if ( !GetGlobalTeam( iTeamNum ) ) + { + Warning( "CCSPlayer::ChangeTeam( %d ) - invalid team index.\n", iTeamNum ); + return; + } + + int iOldTeam = GetTeamNumber(); + + // if this is our current team, just abort + if ( iTeamNum == iOldTeam ) + return; + + //============================================================================= + // HPE_BEGIN: + //============================================================================= + + // [tj] Added a parameter so we know if it was death that caused the drop + // Drop Our best weapon + DropWeapons(false, false); + + // [tj] Clear out dominations + RemoveNemesisRelationships(); + + //============================================================================= + // HPE_END + //============================================================================= + + + // Always allow a change to spectator, and don't count it as one of our team changes. + // We now store the old team, so if a player changes once to one team, then to spectator, + // they won't be able to change back to their old old team, but will still be able to join + // the team they initially changed to. + if( iTeamNum != TEAM_SPECTATOR ) + { + m_bTeamChanged = true; + } + else + { + m_iOldTeam = iOldTeam; + } + + // do the team change: + BaseClass::ChangeTeam( iTeamNum ); + + //reset class + m_iClass = (int)CS_CLASS_NONE; + + // update client state + + if ( iTeamNum == TEAM_UNASSIGNED ) + { + State_Transition( STATE_OBSERVER_MODE ); + } + else if ( iTeamNum == TEAM_SPECTATOR ) + { + //============================================================================= + // HPE_BEGIN: + // [tj] Removed these lines so players keep their money when switching to spectator. + //============================================================================= + //Reset money + //m_iAccount = 0; + //============================================================================= + // HPE_END + //============================================================================= + RemoveAllItems( true ); + + State_Transition( STATE_OBSERVER_MODE ); + } + else // active player + { + if ( iOldTeam == TEAM_SPECTATOR ) + { + // If they're switching from being a spectator to ingame player + //============================================================================= + // HPE_BEGIN: + // [tj] Changed this so players either retain their existing money or, + // if they have less than the default, give them the default. + //============================================================================= + int startMoney = CSGameRules()->GetStartMoney(); + if (startMoney > m_iAccount) + { + m_iAccount = startMoney; + } + //============================================================================= + // HPE_END + //============================================================================= + } + + // bots get to this state on TEAM_UNASSIGNED, yet they are marked alive. Don't kill them. + else if ( iOldTeam != TEAM_UNASSIGNED && !IsDead() ) + { + // Kill player if switching teams while alive + CommitSuicide(); + } + + // Put up the class selection menu. + State_Transition( STATE_PICKINGCLASS ); + } + + // Initialize the player counts now that a player has switched teams + int NumDeadCT, NumDeadTerrorist, NumAliveTerrorist, NumAliveCT; + CSGameRules()->InitializePlayerCounts( NumAliveTerrorist, NumAliveCT, NumDeadTerrorist, NumDeadCT ); +} + +//----------------------------------------------------------------------------- +// Purpose: Put the player in the specified team without penalty +//----------------------------------------------------------------------------- +void CCSPlayer::SwitchTeam( int iTeamNum ) +{ + if ( !GetGlobalTeam( iTeamNum ) || (iTeamNum != TEAM_CT && iTeamNum != TEAM_TERRORIST) ) + { + Warning( "CCSPlayer::SwitchTeam( %d ) - invalid team index.\n", iTeamNum ); + return; + } + + int iOldTeam = GetTeamNumber(); + + // if this is our current team, just abort + if ( iTeamNum == iOldTeam ) + return; + + // Always allow a change to spectator, and don't count it as one of our team changes. + // We now store the old team, so if a player changes once to one team, then to spectator, + // they won't be able to change back to their old old team, but will still be able to join + // the team they initially changed to. + m_bTeamChanged = true; + + // do the team change: + BaseClass::ChangeTeam( iTeamNum ); + + if( HasDefuser() ) + { + RemoveDefuser(); + } + + //reset class + switch ( m_iClass ) + { + // Terrorist -> CT + case CS_CLASS_PHOENIX_CONNNECTION: + m_iClass = (int)CS_CLASS_SEAL_TEAM_6; + break; + case CS_CLASS_L337_KREW: + m_iClass = (int)CS_CLASS_GSG_9; + break; + case CS_CLASS_ARCTIC_AVENGERS: + m_iClass = (int)CS_CLASS_SAS; + break; + case CS_CLASS_GUERILLA_WARFARE: + m_iClass = (int)CS_CLASS_GIGN; + break; + + // CT -> Terrorist + case CS_CLASS_SEAL_TEAM_6: + m_iClass = (int)CS_CLASS_PHOENIX_CONNNECTION; + break; + case CS_CLASS_GSG_9: + m_iClass = (int)CS_CLASS_L337_KREW; + break; + case CS_CLASS_SAS: + m_iClass = (int)CS_CLASS_ARCTIC_AVENGERS; + break; + case CS_CLASS_GIGN: + m_iClass = (int)CS_CLASS_GUERILLA_WARFARE; + break; + + case CS_CLASS_NONE: + default: + break; + } + + // Initialize the player counts now that a player has switched teams + int NumDeadCT, NumDeadTerrorist, NumAliveTerrorist, NumAliveCT; + CSGameRules()->InitializePlayerCounts( NumAliveTerrorist, NumAliveCT, NumDeadTerrorist, NumDeadCT ); +} + +void CCSPlayer::ModifyOrAppendPlayerCriteria( AI_CriteriaSet& set ) +{ + // this is for giving player info to the hostage response system + // and is as yet unused. + // Eventually we could give the hostage a few tidbits about this player, + // eg their health, what weapons they have, and the hostage could + // comment accordingly. + + //do not append any player data to the Criteria! + + //we don't know which player we should be caring about +} + +static unsigned int s_BulletGroupCounter = 0; + +void CCSPlayer::StartNewBulletGroup() +{ + s_BulletGroupCounter++; +} + +//======================================================= +// Remember this amount of damage that we dealt for stats +//======================================================= +void CCSPlayer::RecordDamageGiven( const char *szDamageTaker, int iDamageGiven ) +{ + FOR_EACH_LL( m_DamageGivenList, i ) + { + if( Q_strncmp( szDamageTaker, m_DamageGivenList[i]->GetPlayerName(), MAX_PLAYER_NAME_LENGTH ) == 0 ) + { + m_DamageGivenList[i]->AddDamage( iDamageGiven, s_BulletGroupCounter ); + return; + } + } + + CDamageRecord *record = new CDamageRecord( szDamageTaker, iDamageGiven, s_BulletGroupCounter ); + int k = m_DamageGivenList.AddToTail(); + m_DamageGivenList[k] = record; +} + +//======================================================= +// Remember this amount of damage that we took for stats +//======================================================= +void CCSPlayer::RecordDamageTaken( const char *szDamageDealer, int iDamageTaken ) +{ + FOR_EACH_LL( m_DamageTakenList, i ) + { + if( Q_strncmp( szDamageDealer, m_DamageTakenList[i]->GetPlayerName(), MAX_PLAYER_NAME_LENGTH ) == 0 ) + { + m_DamageTakenList[i]->AddDamage( iDamageTaken, s_BulletGroupCounter ); + return; + } + } + + CDamageRecord *record = new CDamageRecord( szDamageDealer, iDamageTaken, s_BulletGroupCounter ); + int k = m_DamageTakenList.AddToTail(); + m_DamageTakenList[k] = record; +} + +//======================================================= +// Reset our damage given and taken counters +//======================================================= +void CCSPlayer::ResetDamageCounters() +{ + m_DamageGivenList.PurgeAndDeleteElements(); + m_DamageTakenList.PurgeAndDeleteElements(); +} + +//======================================================= +// Output the damage that we dealt to other players +//======================================================= +void CCSPlayer::OutputDamageTaken( void ) +{ + bool bPrintHeader = true; + CDamageRecord *pRecord; + char buf[64]; + int msg_dest = HUD_PRINTCONSOLE; + + FOR_EACH_LL( m_DamageTakenList, i ) + { + if( bPrintHeader ) + { + ClientPrint( this, msg_dest, "Player: %s1 - Damage Taken\n", GetPlayerName() ); + ClientPrint( this, msg_dest, "-------------------------\n" ); + bPrintHeader = false; + } + pRecord = m_DamageTakenList[i]; + + if( pRecord ) + { + if (pRecord->GetNumHits() == 1) + { + Q_snprintf( buf, sizeof(buf), "%d in %d hit", pRecord->GetDamage(), pRecord->GetNumHits() ); + } + else + { + Q_snprintf( buf, sizeof(buf), "%d in %d hits", pRecord->GetDamage(), pRecord->GetNumHits() ); + } + ClientPrint( this, msg_dest, "Damage Taken from \"%s1\" - %s2\n", pRecord->GetPlayerName(), buf ); + } + } +} + +//======================================================= +// Output the damage that we took from other players +//======================================================= +void CCSPlayer::OutputDamageGiven( void ) +{ + bool bPrintHeader = true; + CDamageRecord *pRecord; + char buf[64]; + int msg_dest = HUD_PRINTCONSOLE; + + FOR_EACH_LL( m_DamageGivenList, i ) + { + if( bPrintHeader ) + { + ClientPrint( this, msg_dest, "Player: %s1 - Damage Given\n", GetPlayerName() ); + ClientPrint( this, msg_dest, "-------------------------\n" ); + bPrintHeader = false; + } + + pRecord = m_DamageGivenList[i]; + + if( pRecord ) + { + if (pRecord->GetNumHits() == 1) + { + Q_snprintf( buf, sizeof(buf), "%d in %d hit", pRecord->GetDamage(), pRecord->GetNumHits() ); + } + else + { + Q_snprintf( buf, sizeof(buf), "%d in %d hits", pRecord->GetDamage(), pRecord->GetNumHits() ); + } + ClientPrint( this, msg_dest, "Damage Given to \"%s1\" - %s2\n", pRecord->GetPlayerName(), buf ); + } + } +} + +void CCSPlayer::CreateViewModel( int index /*=0*/ ) +{ + Assert( index >= 0 && index < MAX_VIEWMODELS ); + + if ( GetViewModel( index ) ) + return; + + CPredictedViewModel *vm = ( CPredictedViewModel * )CreateEntityByName( "predicted_viewmodel" ); + if ( vm ) + { + vm->SetAbsOrigin( GetAbsOrigin() ); + vm->SetOwner( this ); + vm->SetIndex( index ); + DispatchSpawn( vm ); + vm->FollowEntity( this, false ); + m_hViewModel.Set( index, vm ); + } +} + +bool CCSPlayer::HasC4() const +{ + return ( Weapon_OwnsThisType( "weapon_c4" ) != NULL ); +} + +int CCSPlayer::GetNextObserverSearchStartPoint( bool bReverse ) +{ + // If we are currently watching someone who is dead, they must have died while we were watching (since + // a dead guy is not a valid pick to start watching). He was given his killer as an observer target + // when he died, so let's start by trying to observe his killer. If we fail, we'll use the normal way. + // And this is just the start point anyway, but we want to start the search here in case it is okay. + if( m_hObserverTarget && !m_hObserverTarget->IsAlive() ) + { + CCSPlayer *targetPlayer = ToCSPlayer(m_hObserverTarget); + if( targetPlayer && targetPlayer->GetObserverTarget() ) + return targetPlayer->GetObserverTarget()->entindex(); + } + + return BaseClass::GetNextObserverSearchStartPoint( bReverse ); +} + +void CCSPlayer::PlayStepSound( Vector &vecOrigin, surfacedata_t *psurface, float fvol, bool force ) +{ + BaseClass::PlayStepSound( vecOrigin, psurface, fvol, force ); + + if ( !sv_footsteps.GetFloat() ) + return; + + if ( !psurface ) + return; + + IGameEvent * event = gameeventmanager->CreateEvent( "player_footstep" ); + if ( event ) + { + event->SetInt("userid", GetUserID() ); + gameeventmanager->FireEvent( event ); + } + + m_bMadeFootstepNoise = true; +} + + +void CCSPlayer::SelectDeathPose( const CTakeDamageInfo &info ) +{ + MDLCACHE_CRITICAL_SECTION(); + if ( !GetModelPtr() ) + return; + + Activity aActivity = ACT_INVALID; + int iDeathFrame = 0; + + SelectDeathPoseActivityAndFrame( this, info, m_LastHitGroup, aActivity, iDeathFrame ); + if ( aActivity == ACT_INVALID ) + { + SetDeathPose( ACT_INVALID ); + SetDeathPoseFrame( 0 ); + return; + } + + SetDeathPose( SelectWeightedSequence( aActivity ) ); + SetDeathPoseFrame( iDeathFrame ); +} + + +void CCSPlayer::HandleAnimEvent( animevent_t *pEvent ) +{ + if ( pEvent->event == 4001 || pEvent->event == 4002 ) + { + // Ignore these for now - soon we will be playing footstep sounds based on these events + // that mark footfalls in the anims. + } + else + { + BaseClass::HandleAnimEvent( pEvent ); + } +} + + +bool CCSPlayer::CanChangeName( void ) +{ + if ( IsBot() ) + return true; + + // enforce the minimum interval + if ( (m_flNameChangeHistory[0] + MIN_NAME_CHANGE_INTERVAL) >= gpGlobals->curtime ) + { + return false; + } + + // enforce that we dont do more than NAME_CHANGE_HISTORY_SIZE + // changes within NAME_CHANGE_HISTORY_INTERVAL + if ( (m_flNameChangeHistory[NAME_CHANGE_HISTORY_SIZE-1] + NAME_CHANGE_HISTORY_INTERVAL) >= gpGlobals->curtime ) + { + return false; + } + + return true; +} + +void CCSPlayer::ChangeName( const char *pszNewName ) +{ + // make sure name is not too long + char trimmedName[MAX_PLAYER_NAME_LENGTH]; + Q_strncpy( trimmedName, pszNewName, sizeof( trimmedName ) ); + + const char *pszOldName = GetPlayerName(); + + // send colored message to everyone + CReliableBroadcastRecipientFilter filter; + UTIL_SayText2Filter( filter, this, false, "#Cstrike_Name_Change", pszOldName, trimmedName ); + + // broadcast event + IGameEvent * event = gameeventmanager->CreateEvent( "player_changename" ); + if ( event ) + { + event->SetInt( "userid", GetUserID() ); + event->SetString( "oldname", pszOldName ); + event->SetString( "newname", trimmedName ); + gameeventmanager->FireEvent( event ); + } + + // change shared player name + SetPlayerName( trimmedName ); + + // tell engine to use new name + engine->ClientCommand( edict(), "name \"%s\"", trimmedName ); + + // remember time of name change + for ( int i=NAME_CHANGE_HISTORY_SIZE-1; i>0; i-- ) + { + m_flNameChangeHistory[i] = m_flNameChangeHistory[i-1]; + } + + m_flNameChangeHistory[0] = gpGlobals->curtime; // last change +} + +bool CCSPlayer::StartReplayMode( float fDelay, float fDuration, int iEntity ) +{ + if ( !BaseClass::StartReplayMode( fDelay, fDuration, iEntity ) ) + return false; + + CSingleUserRecipientFilter filter( this ); + filter.MakeReliable(); + + UserMessageBegin( filter, "KillCam" ); + WRITE_BYTE( OBS_MODE_IN_EYE ); + + if ( m_hObserverTarget.Get() ) + { + WRITE_BYTE( m_hObserverTarget.Get()->entindex() ); // first target + WRITE_BYTE( entindex() ); //second target + } + else + { + WRITE_BYTE( entindex() ); // first target + WRITE_BYTE( 0 ); //second target + } + MessageEnd(); + + ClientPrint( this, HUD_PRINTCENTER, "Kill Cam Replay" ); + + return true; +} + +void CCSPlayer::StopReplayMode() +{ + BaseClass::StopReplayMode(); + + CSingleUserRecipientFilter filter( this ); + filter.MakeReliable(); + + UserMessageBegin( filter, "KillCam" ); + WRITE_BYTE( OBS_MODE_NONE ); + WRITE_BYTE( 0 ); + WRITE_BYTE( 0 ); + MessageEnd(); +} + +void CCSPlayer::PlayUseDenySound() +{ + // Don't do a sound here because it can mute your footsteps giving you an advantage. + // The CS:S content for this sound is silent anyways. + //EmitSound( "Player.UseDeny" ); +} + +//============================================================================= +// HPE_BEGIN: +//============================================================================= + +// [menglish, tj] This is where we reset all the per-round information for achievements for this player +void CCSPlayer::ResetRoundBasedAchievementVariables() +{ + m_KillingSpreeStartTime = -1; + + int numCTPlayers = 0, numTPlayers = 0; + for (int i = 0; i < g_Teams.Count(); i++ ) + { + if(g_Teams[i]) + { + if ( g_Teams[i]->GetTeamNumber() == TEAM_CT ) + numCTPlayers = g_Teams[i]->GetNumPlayers(); + else if(g_Teams[i]->GetTeamNumber() == TEAM_TERRORIST) + numTPlayers = g_Teams[i]->GetNumPlayers(); + } + } + m_NumEnemiesKilledThisRound = 0; + if(GetTeamNumber() == TEAM_CT) + m_NumEnemiesAtRoundStart = numTPlayers; + else if(GetTeamNumber() == TEAM_TERRORIST) + m_NumEnemiesAtRoundStart = numCTPlayers; + + + //Clear the previous owner field for currently held weapons + CWeaponCSBase* pWeapon = dynamic_cast< CWeaponCSBase * >(Weapon_GetSlot( WEAPON_SLOT_RIFLE )); + if ( pWeapon ) + { + pWeapon->SetPreviousOwner(NULL); + } + pWeapon = dynamic_cast< CWeaponCSBase * >(Weapon_GetSlot( WEAPON_SLOT_PISTOL)); + if ( pWeapon ) + { + pWeapon->SetPreviousOwner(NULL); + } + + //Clear list of weapons used to get kills + m_killWeapons.RemoveAll(); + + //Clear sliding window of kill times + m_killTimes.RemoveAll(); + + //clear round kills + m_enemyPlayersKilledThisRound.RemoveAll(); + + m_killsWhileBlind = 0; + + m_bSurvivedHeadshotDueToHelmet = false; + + m_gooseChaseStep = GC_NONE; + m_defuseDefenseStep = DD_NONE; + m_pGooseChaseDistractingPlayer = NULL; + + m_bMadeFootstepNoise = false; + + m_bombPickupTime = -1; + + m_bMadePurchseThisRound = false; + + m_bKilledDefuser = false; + m_bKilledRescuer = false; + m_maxGrenadeKills = 0; + m_grenadeDamageTakenThisRound = 0; + + //============================================================================= + // HPE_BEGIN: + // [dwenger] Needed for fun-fact implementation + //============================================================================= + + WieldingKnifeAndKilledByGun(false); + + m_WeaponTypesUsed.RemoveAll(); + + m_bPickedUpDefuser = false; + m_bDefusedWithPickedUpKit = false; + + //============================================================================= + // HPE_END + //============================================================================= +} + + +/** + * static public CCSPlayer::GetCSWeaponIDCausingDamage() + * + * Helper function to get the ID of the weapon used to kill a player. + * This is slightly non-trivial because the grenade because a separate + * entity when thrown. + * + * Parameters: + * info - + * + * Returns: + * int - + */ +CSWeaponID CCSPlayer::GetWeaponIdCausingDamange( const CTakeDamageInfo &info ) +{ + CBaseEntity *pInflictor = info.GetInflictor(); + CCSPlayer *pAttacker = ToCSPlayer(info.GetAttacker()); + if (pAttacker == pInflictor) + { + CWeaponCSBase* pAttackerWeapon = dynamic_cast< CWeaponCSBase * >(pAttacker->GetActiveWeapon()); + if (!pAttackerWeapon) + return WEAPON_NONE; + + return pAttackerWeapon->GetWeaponID(); + } + else if (pInflictor && V_strcmp(pInflictor->GetClassname(), "hegrenade_projectile") == 0) + { + return WEAPON_HEGRENADE; + } + return WEAPON_NONE; +} + + +//============================================================================= +// HPE_BEGIN: +// [dwenger] adding tracking for weapon used fun fact +//============================================================================= +void CCSPlayer::PlayerUsedFirearm( CBaseCombatWeapon* pBaseWeapon ) +{ + if ( pBaseWeapon ) + { + CWeaponCSBase* pWeapon = dynamic_cast< CWeaponCSBase* >( pBaseWeapon ); + + if ( pWeapon ) + { + CSWeaponType weaponType = pWeapon->GetCSWpnData().m_WeaponType; + CSWeaponID weaponID = pWeapon->GetWeaponID(); + + if ( weaponType != WEAPONTYPE_KNIFE && weaponType != WEAPONTYPE_C4 && weaponType != WEAPONTYPE_GRENADE ) + { + if ( m_WeaponTypesUsed.Find( weaponID ) == -1 ) + { + // Add this weapon to the list of weapons used by the player + m_WeaponTypesUsed.AddToTail( weaponID ); + } + } + } + } +} + + +/** + * public CCSPlayer::ProcessPlayerDeathAchievements() + * + * Do Achievement processing whenever a player is killed + * + * Parameters: + * pAttacker - + * pVictim - + * info - + */ +void CCSPlayer::ProcessPlayerDeathAchievements( CCSPlayer *pAttacker, CCSPlayer *pVictim, const CTakeDamageInfo &info ) +{ + Assert(pVictim != NULL); + CBaseEntity *pInflictor = info.GetInflictor(); + + // all these achievements require a valid attacker on a different team + if ( pAttacker != NULL && pVictim != NULL && pVictim->GetTeamNumber() != pAttacker->GetTeamNumber() ) + { + // get the weapon used - some of the achievements will need this data + CWeaponCSBase* pAttackerWeapon = dynamic_cast< CWeaponCSBase * >(pAttacker->GetActiveWeapon()); + + //============================================================================= + // HPE_BEGIN: + // [dwenger] Fun-fact processing + //============================================================================= + + CWeaponCSBase* pVictimWeapon = dynamic_cast< CWeaponCSBase* >(pVictim->GetActiveWeapon()); + + //============================================================================= + // HPE_END + //============================================================================= + + CSWeaponID attackerWeaponId = GetWeaponIdCausingDamange(info); + + if (pVictim->m_bIsDefusing) + { + pAttacker->AwardAchievement(CSKilledDefuser); + pAttacker->m_bKilledDefuser = true; + + if (attackerWeaponId == WEAPON_HEGRENADE) + { + pAttacker->AwardAchievement(CSKilledDefuserWithGrenade); + } + } + + // [pfreese] Achievement check for attacker killing player while reloading + if (pVictim->IsReloading()) + { + pAttacker->AwardAchievement(CSKillEnemyReloading); + } + + if (pVictim->IsRescuing()) + { + // Ensure the killer did not injure any hostages + if ( !pAttacker->InjuredAHostage() && pVictim->GetNumFollowers() == g_Hostages.Count() ) + { + pAttacker->AwardAchievement(CSKilledRescuer); + pAttacker->m_bKilledRescuer = true; + } + } + + // [menglish] Achievement check for doing 95% or more damage to a player and having another player kill them + FOR_EACH_LL( pVictim->m_DamageTakenList, i ) + { + if( pVictim->m_DamageTakenList[i]->GetDamage() >= pVictim->GetMaxHealth() - AchievementConsts::DamageNoKill_MaxHealthLeftOnKill && + Q_strncmp( pAttacker->GetPlayerName(), pVictim->m_DamageTakenList[i]->GetPlayerName(), MAX_PLAYER_NAME_LENGTH ) != 0 ) + { + //Now find the player who did that amount of damage + for ( int j = 1; j <= MAX_PLAYERS; j++ ) + { + CBasePlayer *pPlayerIter = UTIL_PlayerByIndex( j ); + + if ( pPlayerIter && Q_strncmp( pPlayerIter->GetPlayerName(), pVictim->m_DamageTakenList[i]->GetPlayerName(), MAX_PLAYER_NAME_LENGTH ) == 0 && + pPlayerIter->GetTeamNumber() != pVictim->GetTeamNumber() ) + { + ToCSPlayer(pPlayerIter)->AwardAchievement(CSDamageNoKill); + break; + } + } + } + } + + pAttacker->m_NumEnemiesKilledThisRound++; + + //store a list of kill times for spree tracking + pAttacker->m_killTimes.AddToTail(gpGlobals->curtime); + + //Add the victim to the list of players killed this round + pAttacker->m_enemyPlayersKilledThisRound.AddToTail(pVictim); + + //Calculate Avenging for all players the victim has killed + for ( int avengedIndex = 0; avengedIndex < pVictim->m_enemyPlayersKilledThisRound.Count(); avengedIndex++ ) + { + CCSPlayer* avengedPlayer = pVictim->m_enemyPlayersKilledThisRound[avengedIndex]; + + if (avengedPlayer) + { + //Make sure you are avenging someone on your own team (This is the expected flow. Just here to avoid edge cases like team-switching). + if (pAttacker->GetTeamNumber() == avengedPlayer->GetTeamNumber()) + { + CCS_GameStats.Event_PlayerAvengedTeammate(pAttacker, pVictim->m_enemyPlayersKilledThisRound[avengedIndex]); + } + } + } + + + + //remove elements older than a certain time + while (pAttacker->m_killTimes.Count() > 0 && pAttacker->m_killTimes[0] + AchievementConsts::KillingSpree_WindowTime < gpGlobals->curtime) + { + pAttacker->m_killTimes.Remove(0); + } + + //If we killed enough players in the time window, award the achievement + if (pAttacker->m_killTimes.Count() >= AchievementConsts::KillingSpree_Kills) + { + pAttacker->m_KillingSpreeStartTime = gpGlobals->curtime; + pAttacker->AwardAchievement(CSKillingSpree); + } + + // Did the attacker just kill someone on a killing spree? + if (pVictim->m_KillingSpreeStartTime >= 0 && pVictim->m_KillingSpreeStartTime - gpGlobals->curtime <= AchievementConsts::KillingSpreeEnder_TimeWindow) + { + pAttacker->AwardAchievement(CSKillingSpreeEnder); + } + + //Check the "killed someone with their own weapon" achievement + if (pAttackerWeapon && pAttackerWeapon->GetPreviousOwner() == pVictim) + { + pAttacker->AwardAchievement(CSKillEnemyWithFormerGun); + } + + //If this player has killed the entire team award him the achievement + if (pAttacker->m_NumEnemiesKilledThisRound == pAttacker->m_NumEnemiesAtRoundStart && pAttacker->m_NumEnemiesKilledThisRound >= AchievementConsts::KillEnemyTeam_MinKills) + { + pAttacker->AwardAchievement(CSKillEnemyTeam); + } + + //If this is a posthumous kill award the achievement + if (!pAttacker->IsAlive() && attackerWeaponId == WEAPON_HEGRENADE) + { + CCS_GameStats.IncrementStat(pAttacker, CSSTAT_GRENADE_POSTHUMOUSKILLS, 1); + ToCSPlayer(pAttacker)->AwardAchievement(CSPosthumousGrenadeKill); + } + + if (pAttacker->GetActiveWeapon() && pAttacker->GetActiveWeapon()->Clip1() == 0 && pAttackerWeapon && pAttackerWeapon->GetCSWpnData().m_WeaponType != WEAPONTYPE_SNIPER_RIFLE) + { + if (pInflictor == pAttacker) + { + pAttacker->AwardAchievement(CSKillEnemyLastBullet); + CCS_GameStats.IncrementStat(pAttacker, CSSTAT_KILLS_WITH_LAST_ROUND, 1); + } + } + + //============================================================================= + // HPE_BEGIN: + // [dwenger] Fun-fact processing + //============================================================================= + + if (pVictimWeapon && pVictimWeapon->GetCSWpnData().m_WeaponType == WEAPONTYPE_KNIFE && pAttackerWeapon && + pAttackerWeapon->GetCSWpnData().m_WeaponType != WEAPONTYPE_KNIFE && pAttackerWeapon->GetCSWpnData().m_WeaponType != WEAPONTYPE_C4 && pAttackerWeapon->GetCSWpnData().m_WeaponType != WEAPONTYPE_GRENADE) + { + // Victim was wielding knife when killed by a gun + pVictim->WieldingKnifeAndKilledByGun(true); + } + + //============================================================================= + // HPE_END + //============================================================================= + + //see if this is a unique weapon + if (attackerWeaponId != WEAPON_NONE) + { + if (pAttacker->m_killWeapons.Find(attackerWeaponId) == -1) + { + pAttacker->m_killWeapons.AddToTail(attackerWeaponId); + if (pAttacker->m_killWeapons.Count() >= AchievementConsts::KillsWithMultipleGuns_MinWeapons) + { + pAttacker->AwardAchievement(CSKillsWithMultipleGuns); + } + } + } + + //Check for kills while blind + if (pAttacker->IsBlindForAchievement()) + { + //if this is from a different blinding, restart the kill counter and set the time + if (pAttacker->m_blindStartTime != pAttacker->m_firstKillBlindStartTime) + { + pAttacker->m_killsWhileBlind = 0; + pAttacker->m_firstKillBlindStartTime = pAttacker->m_blindStartTime; + } + + ++pAttacker->m_killsWhileBlind; + if (pAttacker->m_killsWhileBlind >= AchievementConsts::KillEnemiesWhileBlind_Kills) + { + pAttacker->AwardAchievement(CSKillEnemiesWhileBlind); + } + + if (pAttacker->m_killsWhileBlind >= AchievementConsts::KillEnemiesWhileBlindHard_Kills) + { + pAttacker->AwardAchievement(CSKillEnemiesWhileBlindHard); + } + } + + //Check sniper killing achievements + bool victimZoomed = ( pVictim->GetFOV() != pVictim->GetDefaultFOV() ); + bool attackerZoomed = ( pAttacker->GetFOV() != pAttacker->GetDefaultFOV() ); + bool attackerUsedSniperRifle = pAttackerWeapon && pAttackerWeapon->GetCSWpnData().m_WeaponType == WEAPONTYPE_SNIPER_RIFLE && pInflictor == pAttacker; + if (victimZoomed && attackerUsedSniperRifle) + { + pAttacker->AwardAchievement(CSKillSniperWithSniper); + } + + if (attackerWeaponId == WEAPON_KNIFE && victimZoomed) + { + pAttacker->AwardAchievement(CSKillSniperWithKnife); + } + if (attackerUsedSniperRifle && !attackerZoomed) + { + pAttacker->AwardAchievement(CSHipShot); + } + + //Kill a player at low health + if (pAttacker->IsAlive() && pAttacker->GetHealth() <= AchievementConsts::KillWhenAtLowHealth_MaxHealth) + { + pAttacker->AwardAchievement(CSKillWhenAtLowHealth); + } + + //Kill a player with a knife during the pistol round + if (CSGameRules()->IsPistolRound()) + { + if (attackerWeaponId == WEAPON_KNIFE) + { + pAttacker->AwardAchievement(CSPistolRoundKnifeKill); + } + } + + //[tj] Check for dual elites fight + CWeaponCSBase* victimWeapon = pVictim->GetActiveCSWeapon(); + + if (victimWeapon) + { + CSWeaponID victimWeaponID = victimWeapon->GetWeaponID(); + + if (attackerWeaponId == WEAPON_ELITE && victimWeaponID == WEAPON_ELITE) + { + pAttacker->AwardAchievement(CSWinDualDuel); + } + } + + //[tj] See if the attacker or defender are in the air [sbodenbender] dont include ladders + bool attackerInAir = pAttacker->GetMoveType() != MOVETYPE_LADDER && pAttacker->GetNearestSurfaceBelow(AchievementConsts::KillInAir_MinimumHeight) == NULL; + bool victimInAir = pVictim->GetMoveType() != MOVETYPE_LADDER && pVictim->GetNearestSurfaceBelow(AchievementConsts::KillInAir_MinimumHeight) == NULL; + + if (attackerInAir) + { + pAttacker->AwardAchievement(CSKillWhileInAir); + } + if (victimInAir) + { + pAttacker->AwardAchievement(CSKillEnemyInAir); + } + if (attackerInAir && victimInAir) + { + pAttacker->AwardAchievement(CSKillerAndEnemyInAir); + } + + //[tj] advance to the next stage of the defuse defense achievement + if (pAttacker->m_defuseDefenseStep == DD_STARTED_DEFUSE) + { + pAttacker->m_defuseDefenseStep = DD_KILLED_TERRORIST; + } + + if (pVictim->HasC4() && pVictim->GetBombPickuptime() + AchievementConsts::KillBombPickup_MaxTime > gpGlobals->curtime) + { + pAttacker->AwardAchievement(CSKillBombPickup); + } + + } + + + //If you kill a friendly player while blind (from an enemy player), give the guy that blinded you an achievement + if ( pAttacker != NULL && pVictim != NULL && pVictim->GetTeamNumber() == pAttacker->GetTeamNumber() && pAttacker->IsBlind()) + { + CCSPlayer* flashbangAttacker = pAttacker->GetLastFlashbangAttacker(); + if (flashbangAttacker && pAttacker->GetTeamNumber() != flashbangAttacker->GetTeamNumber()) + { + flashbangAttacker->AwardAchievement(CSCauseFriendlyFireWithFlashbang); + } + } + + // do a scan to determine count of players still alive + int livePlayerCount = 0; + int teamCount[TEAM_MAXCOUNT]; + int teamIgnoreCount[TEAM_MAXCOUNT]; + memset(teamCount, 0, sizeof(teamCount)); + memset(teamIgnoreCount, 0, sizeof(teamIgnoreCount)); + CCSPlayer *pAlivePlayer = NULL; + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CCSPlayer* pPlayer = (CCSPlayer*)UTIL_PlayerByIndex( i ); + if (pPlayer) + { + int teamNum = pPlayer->GetTeamNumber(); + if ( teamNum >= 0 ) + { + ++teamCount[teamNum]; + if (pPlayer->WasNotKilledNaturally()) + { + teamIgnoreCount[teamNum]++; + } + } + if (pPlayer->IsAlive() && pPlayer != pVictim) + { + ++livePlayerCount; + pAlivePlayer = pPlayer; + } + } + } + + // Achievement check for being the last player alive in a match + if (pAlivePlayer) + { + int alivePlayerTeam = pAlivePlayer->GetTeamNumber(); + int alivePlayerOpposingTeam = alivePlayerTeam == TEAM_CT ? TEAM_TERRORIST : TEAM_CT; + if (livePlayerCount == 1 + && CSGameRules()->m_iRoundWinStatus == WINNER_NONE + && teamCount[alivePlayerTeam] - teamIgnoreCount[alivePlayerTeam] >= AchievementConsts::LastPlayerAlive_MinPlayersOnTeam + && teamCount[alivePlayerOpposingTeam] - teamIgnoreCount[alivePlayerOpposingTeam] >= AchievementConsts::DefaultMinOpponentsForAchievement + && ( !(pAlivePlayer->m_iDisplayHistoryBits & DHF_FRIEND_KILLED) )) + { + pAlivePlayer->AwardAchievement(CSLastPlayerAlive); + } + } + + // [tj] Added hook into player killed stat that happens before weapon drop + CCS_GameStats.Event_PlayerKilled_PreWeaponDrop(pVictim, info); +} + +//[tj] traces up to maxTrace units down and returns any standable object it hits +// (doesn't check slope for standability) +CBaseEntity* CCSPlayer::GetNearestSurfaceBelow(float maxTrace) +{ + trace_t trace; + Ray_t ray; + + Vector traceStart = this->GetAbsOrigin(); + Vector traceEnd = traceStart; + traceEnd.z -= maxTrace; + + Vector minExtent = this->m_Local.m_bDucked ? VEC_DUCK_HULL_MIN_SCALED( this ) : VEC_HULL_MIN_SCALED( this ); + Vector maxExtent = this->m_Local.m_bDucked ? VEC_DUCK_HULL_MAX_SCALED( this ) : VEC_HULL_MAX_SCALED( this ); + + ray.Init( traceStart, traceEnd, minExtent, maxExtent ); + UTIL_TraceRay( ray, MASK_PLAYERSOLID, this, COLLISION_GROUP_PLAYER_MOVEMENT, &trace ); + + return trace.m_pEnt; +} + +// [tj] Added a way to react to the round ending before we reset. +// It is important to note that this happens before the bomb explodes, so a player may die +// after this from a bomb explosion or a late kill after a defuse/detonation/rescue. +void CCSPlayer::OnRoundEnd(int winningTeam, int reason) +{ + if (winningTeam == WINNER_CT || winningTeam == WINNER_TER) + { + int losingTeamId = (winningTeam == TEAM_CT) ? TEAM_TERRORIST : TEAM_CT; + + CTeam* losingTeam = GetGlobalTeam(losingTeamId); + + int losingTeamPlayers = 0; + + if (losingTeam) + { + losingTeamPlayers = losingTeam->GetNumPlayers(); + + int ignoreCount = 0; + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CCSPlayer* pPlayer = (CCSPlayer*)UTIL_PlayerByIndex( i ); + if (pPlayer) + { + int teamNum = pPlayer->GetTeamNumber(); + if ( teamNum == losingTeamId ) + { + if (pPlayer->WasNotKilledNaturally()) + { + ignoreCount++; + } + } + + } + } + + losingTeamPlayers -= ignoreCount; + } + + //Check fast round win achievement + if ( IsAlive() && + gpGlobals->curtime - CSGameRules()->GetRoundStartTime() < AchievementConsts::FastRoundWin_Time && + GetTeamNumber() == winningTeam && + losingTeamPlayers >= AchievementConsts::DefaultMinOpponentsForAchievement) + { + AwardAchievement(CSFastRoundWin); + } + + //Check goosechase achievement + if (IsAlive() && reason == Target_Bombed && m_gooseChaseStep == GC_STOPPED_AFTER_GETTING_SHOT && m_pGooseChaseDistractingPlayer) + { + m_pGooseChaseDistractingPlayer->AwardAchievement(CSGooseChase); + } + + //Check Defuse Defense achievement + if (IsAlive() && reason == Bomb_Defused && m_defuseDefenseStep == DD_KILLED_TERRORIST) + { + AwardAchievement(CSDefuseDefense); + } + + //Check silent win + if (m_NumEnemiesKilledThisRound > 0 && GetTeamNumber() == winningTeam && !m_bMadeFootstepNoise) + { + AwardAchievement(CSSilentWin); + } + + //Process && Check "win rounds without buying" achievement + if (GetTeamNumber() == winningTeam && !m_bMadePurchseThisRound) + { + m_roundsWonWithoutPurchase++; + if (m_roundsWonWithoutPurchase > AchievementConsts::WinRoundsWithoutBuying_Rounds) + { + AwardAchievement(CSWinRoundsWithoutBuying); + } + } + else + { + m_roundsWonWithoutPurchase = 0; + } + } + + m_lastRoundResult = reason; +} + +void CCSPlayer::OnPreResetRound() +{ + //Check headshot survival achievement + if (IsAlive() && m_bSurvivedHeadshotDueToHelmet) + { + AwardAchievement(CSSurvivedHeadshotDueToHelmet); + } + + if (IsAlive() && m_grenadeDamageTakenThisRound > AchievementConsts::SurviveGrenade_MinDamage) + { + AwardAchievement(CSSurviveGrenade); + } + + + //Check achievement for surviving attacks from multiple players. + if (IsAlive()) + { + int numberOfEnemyDamagers = GetNumEnemyDamagers(); + + if (numberOfEnemyDamagers >= AchievementConsts::SurviveManyAttacks_NumberDamagingPlayers) + { + AwardAchievement(CSSurviveManyAttacks); + } + } +} + +void CCSPlayer::OnCanceledDefuse() +{ + if (m_gooseChaseStep == GC_SHOT_DURING_DEFUSE) + { + m_gooseChaseStep = GC_STOPPED_AFTER_GETTING_SHOT; + } +} + + +void CCSPlayer::OnStartedDefuse() +{ + if (m_defuseDefenseStep == DD_NONE) + { + m_defuseDefenseStep = DD_STARTED_DEFUSE; + } +} +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCSPlayer::AttemptToExitFreezeCam( void ) +{ + float fEndFreezeTravel = m_flDeathTime + CS_DEATH_ANIMATION_TIME + spec_freeze_traveltime.GetFloat(); + if ( gpGlobals->curtime < fEndFreezeTravel ) + return; + + m_bAbortFreezeCam = true; +} + +//----------------------------------------------------------------------------- +// Purpose: Sets whether this player is dominating the specified other player +//----------------------------------------------------------------------------- +void CCSPlayer::SetPlayerDominated( CCSPlayer *pPlayer, bool bDominated ) +{ + int iPlayerIndex = pPlayer->entindex(); + m_bPlayerDominated.Set( iPlayerIndex, bDominated ); + pPlayer->SetPlayerDominatingMe( this, bDominated ); +} + +//----------------------------------------------------------------------------- +// Purpose: Sets whether this player is being dominated by the other player +//----------------------------------------------------------------------------- +void CCSPlayer::SetPlayerDominatingMe( CCSPlayer *pPlayer, bool bDominated ) +{ + int iPlayerIndex = pPlayer->entindex(); + m_bPlayerDominatingMe.Set( iPlayerIndex, bDominated ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Returns whether this player is dominating the specified other player +//----------------------------------------------------------------------------- +bool CCSPlayer::IsPlayerDominated( int iPlayerIndex ) +{ + return m_bPlayerDominated.Get( iPlayerIndex ); +} + +bool CCSPlayer::IsPlayerDominatingMe( int iPlayerIndex ) +{ + return m_bPlayerDominatingMe.Get( iPlayerIndex ); +} + +//============================================================================= +// HPE_BEGIN: +// [menglish] MVP functions +//============================================================================= + +void CCSPlayer::IncrementNumMVPs( CSMvpReason_t mvpReason ) +{ + //============================================================================= + // HPE_BEGIN: + // [Forrest] Allow MVP to be turned off for a server + //============================================================================= + if ( sv_nomvp.GetBool() ) + { + Msg( "Round MVP disabled: sv_nomvp is set.\n" ); + return; + } + //============================================================================= + // HPE_END + //============================================================================= + + m_iMVPs++; + CCS_GameStats.Event_MVPEarned( this ); + IGameEvent *mvpEvent = gameeventmanager->CreateEvent( "round_mvp" ); + + if ( mvpEvent ) + { + mvpEvent->SetInt( "userid", GetUserID() ); + mvpEvent->SetInt( "reason", mvpReason ); + gameeventmanager->FireEvent( mvpEvent ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Sets the number of rounds this player has caused to be won for their team +//----------------------------------------------------------------------------- +void CCSPlayer::SetNumMVPs( int iNumMVP ) +{ + m_iMVPs = iNumMVP; +} +//----------------------------------------------------------------------------- +// Purpose: Returns the number of rounds this player has caused to be won for their team +//----------------------------------------------------------------------------- +int CCSPlayer::GetNumMVPs() +{ + return m_iMVPs; +} + +//============================================================================= +// HPE_END +//============================================================================= + +//----------------------------------------------------------------------------- +// Purpose: Removes all nemesis relationships between this player and others +//----------------------------------------------------------------------------- +void CCSPlayer::RemoveNemesisRelationships() +{ + for ( int i = 1 ; i <= gpGlobals->maxClients ; i++ ) + { + CCSPlayer *pTemp = ToCSPlayer( UTIL_PlayerByIndex( i ) ); + if ( pTemp && pTemp != this ) + { + // set this player to be not dominating anyone else + SetPlayerDominated( pTemp, false ); + + // set no one else to be dominating this player + pTemp->SetPlayerDominated( this, false ); + } + } +} + +void CCSPlayer::CheckMaxGrenadeKills(int grenadeKills) +{ + if (grenadeKills > m_maxGrenadeKills) + { + m_maxGrenadeKills = grenadeKills; + } +} + +void CCSPlayer::CommitSuicide( bool bExplode /*= false*/, bool bForce /*= false*/ ) +{ + m_wasNotKilledNaturally = true; + BaseClass::CommitSuicide(bExplode, bForce); +} + +void CCSPlayer::CommitSuicide( const Vector &vecForce, bool bExplode /*= false*/, bool bForce /*= false*/ ) +{ + m_wasNotKilledNaturally = true; + BaseClass::CommitSuicide(vecForce, bExplode, bForce); +} + +int CCSPlayer::GetNumEnemyDamagers() +{ + int numberOfEnemyDamagers = 0; + FOR_EACH_LL( m_DamageTakenList, i ) + { + for ( int j = 1; j <= MAX_PLAYERS; j++ ) + { + CBasePlayer *pPlayer = UTIL_PlayerByIndex( j ); + + if ( pPlayer && V_strncmp( pPlayer->GetPlayerName(), m_DamageTakenList[i]->GetPlayerName(), MAX_PLAYER_NAME_LENGTH ) == 0 && + pPlayer->GetTeamNumber() != GetTeamNumber() ) + { + numberOfEnemyDamagers++; + } + } + } + return numberOfEnemyDamagers; +} + + +int CCSPlayer::GetNumEnemiesDamaged() +{ + int numberOfEnemiesDamaged = 0; + FOR_EACH_LL( m_DamageGivenList, i ) + { + for ( int j = 1; j <= MAX_PLAYERS; j++ ) + { + CBasePlayer *pPlayer = UTIL_PlayerByIndex( j ); + + if ( pPlayer && V_strncmp( pPlayer->GetPlayerName(), m_DamageGivenList[i]->GetPlayerName(), MAX_PLAYER_NAME_LENGTH ) == 0 && + pPlayer->GetTeamNumber() != GetTeamNumber() ) + { + numberOfEnemiesDamaged++; + } + } + } + return numberOfEnemiesDamaged; +} + +//============================================================================= +// HPE_END +//============================================================================= + +void UTIL_AwardMoneyToTeam( int iAmount, int iTeam, CBaseEntity *pIgnore ) +{ + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CCSPlayer *pPlayer = (CCSPlayer*) UTIL_PlayerByIndex( i ); + + if ( !pPlayer ) + continue; + + if ( pPlayer->GetTeamNumber() != iTeam ) + continue; + + if ( pPlayer == pIgnore ) + continue; + + pPlayer->AddAccount( iAmount ); + } +} + |