You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
4936 lines
125 KiB
4936 lines
125 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: Player for HL1. |
|
// |
|
// $NoKeywords: $ |
|
//=============================================================================// |
|
|
|
#include "cbase.h" |
|
#include "dod_player.h" |
|
#include "dod_gamerules.h" |
|
#include "team.h" //GetGlobalTeam |
|
#include "in_buttons.h" |
|
#include "dod_shareddefs.h" |
|
#include "dod_cvars.h" |
|
#include "weapon_dodbase.h" |
|
#include "weapon_dodbasegrenade.h" |
|
#include "weapon_dodbaserpg.h" |
|
#include "weapon_dodbipodgun.h" |
|
#include "dod_basegrenade.h" |
|
#include "dod_ammo_box.h" |
|
#include "effect_dispatch_data.h" |
|
#include "movehelper_server.h" |
|
#include "tier0/vprof.h" |
|
#include "te_effect_dispatch.h" |
|
#include "vphysics/player_controller.h" |
|
#include <KeyValues.h> |
|
#include "engine/IEngineSound.h" |
|
#include "studio.h" |
|
#include "dod_viewmodel.h" |
|
#include "info_camera_link.h" |
|
#include "dod_team.h" |
|
#include "dod_control_point.h" |
|
#include "dod_location.h" |
|
#include "viewport_panel_names.h" |
|
#include "obstacle_pushaway.h" |
|
#include "datacache/imdlcache.h" |
|
#include "bone_setup.h" |
|
#include "dod_baserocket.h" |
|
#include "dod_basegrenade.h" |
|
#include "dod_bombtarget.h" |
|
#include "collisionutils.h" |
|
#include "gamestats.h" |
|
#include "gameinterface.h" |
|
#include "holiday_gift.h" |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
#pragma warning( disable: 4355 ) // disables ' 'this' : used in base member initializer list' |
|
|
|
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." ); |
|
|
|
EHANDLE g_pLastAlliesSpawn; |
|
EHANDLE g_pLastAxisSpawn; |
|
|
|
int g_iLastAlliesSpawnIndex = 0; |
|
int g_iLastAxisSpawnIndex = 0; |
|
|
|
ConVar dod_playerstatetransitions( "dod_playerstatetransitions", "-2", FCVAR_CHEAT, "dod_playerstatetransitions <ent index or -1 for all>. Show player state transitions." ); |
|
ConVar dod_debugdamage( "dod_debugdamage", "0", FCVAR_CHEAT ); |
|
|
|
ConVar mp_bandage_heal_amount( "mp_bandage_heal_amount", |
|
"40", |
|
FCVAR_GAMEDLL, |
|
"How much health to give after a successful bandage\n", |
|
true, 0, false, 0 ); |
|
|
|
ConVar dod_freezecam( "dod_freezecam", "1", FCVAR_REPLICATED | FCVAR_ARCHIVE, "show players a freezecam shot of their killer on death" ); |
|
|
|
extern ConVar spec_freeze_time; |
|
extern ConVar spec_freeze_traveltime; |
|
|
|
#define DOD_PUSHAWAY_THINK_CONTEXT "DODPushawayThink" |
|
#define DOD_DEAFEN_CONTEXT "DeafenContext" |
|
|
|
int g_iCurrentLifeID = 0; |
|
|
|
int GetNextLifeID( void ) |
|
{ |
|
return ++g_iCurrentLifeID; |
|
} |
|
|
|
// -------------------------------------------------------------------------------- // |
|
// Classes |
|
// -------------------------------------------------------------------------------- // |
|
|
|
class CPhysicsPlayerCallback : public IPhysicsPlayerControllerEvent |
|
{ |
|
public: |
|
int ShouldMoveTo( IPhysicsObject *pObject, const Vector &position ) |
|
{ |
|
CDODPlayer *pPlayer = (CDODPlayer *)pObject->GetGameData(); |
|
if ( pPlayer ) |
|
{ |
|
if ( pPlayer->TouchedPhysics() ) |
|
{ |
|
return 0; |
|
} |
|
} |
|
return 1; |
|
} |
|
}; |
|
|
|
static CPhysicsPlayerCallback playerCallback; |
|
|
|
// -------------------------------------------------------------------------------- // |
|
// Ragdoll entities. |
|
// -------------------------------------------------------------------------------- // |
|
|
|
class CDODRagdoll : public CBaseAnimatingOverlay |
|
{ |
|
public: |
|
DECLARE_CLASS( CDODRagdoll, CBaseAnimatingOverlay ); |
|
DECLARE_SERVERCLASS(); |
|
|
|
// Transmit ragdolls to everyone. |
|
virtual int UpdateTransmitState() |
|
{ |
|
return SetTransmitState( FL_EDICT_ALWAYS ); |
|
} |
|
|
|
public: |
|
// In case the client has the player entity, we transmit the player index. |
|
// In case the client doesn't have it, we transmit the player's model index, origin, and angles |
|
// so they can create a ragdoll in the right place. |
|
CNetworkHandle( CBaseEntity, m_hPlayer ); // networked entity handle |
|
CNetworkVector( m_vecRagdollVelocity ); |
|
CNetworkVector( m_vecRagdollOrigin ); |
|
}; |
|
|
|
LINK_ENTITY_TO_CLASS( dod_ragdoll, CDODRagdoll ); |
|
|
|
IMPLEMENT_SERVERCLASS_ST_NOBASE( CDODRagdoll, DT_DODRagdoll ) |
|
SendPropVector( SENDINFO(m_vecRagdollOrigin), -1, SPROP_COORD ), |
|
SendPropEHandle( SENDINFO( m_hPlayer ) ), |
|
SendPropModelIndex( SENDINFO( m_nModelIndex ) ), |
|
SendPropInt ( SENDINFO(m_nForceBone), 8, 0 ), |
|
SendPropVector ( SENDINFO(m_vecForce), -1, SPROP_NOSCALE ), |
|
SendPropVector( SENDINFO( m_vecRagdollVelocity ) ) |
|
END_SEND_TABLE() |
|
|
|
|
|
// -------------------------------------------------------------------------------- // |
|
// 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; |
|
} |
|
|
|
// -------------------------------------------------------------------------------- // |
|
// Tables. |
|
// -------------------------------------------------------------------------------- // |
|
|
|
LINK_ENTITY_TO_CLASS( player, CDODPlayer ); |
|
PRECACHE_REGISTER(player); |
|
|
|
// CDODPlayerShared Data Tables |
|
//============================= |
|
|
|
// specific to the local player |
|
BEGIN_SEND_TABLE_NOBASE( CDODPlayerShared, DT_DODSharedLocalPlayerExclusive ) |
|
SendPropInt( SENDINFO( m_iPlayerClass), 4 ), |
|
SendPropInt( SENDINFO( m_iDesiredPlayerClass ), 4 ), |
|
SendPropFloat( SENDINFO( m_flDeployedYawLimitLeft ) ), |
|
SendPropFloat( SENDINFO( m_flDeployedYawLimitRight ) ), |
|
SendPropInt( SENDINFO( m_iCPIndex ), 4 ), |
|
SendPropArray3( SENDINFO_ARRAY3( m_bPlayerDominated ), SendPropBool( SENDINFO_ARRAY( m_bPlayerDominated ) ) ), |
|
SendPropArray3( SENDINFO_ARRAY3( m_bPlayerDominatingMe ), SendPropBool( SENDINFO_ARRAY( m_bPlayerDominatingMe ) ) ), |
|
END_SEND_TABLE() |
|
|
|
// main table |
|
BEGIN_SEND_TABLE_NOBASE( CDODPlayerShared, DT_DODPlayerShared ) |
|
SendPropFloat( SENDINFO( m_flStamina ), 0, SPROP_NOSCALE | SPROP_CHANGES_OFTEN ), |
|
SendPropTime( SENDINFO( m_flSlowedUntilTime ) ), |
|
SendPropBool( SENDINFO( m_bProne ) ), |
|
SendPropBool( SENDINFO( m_bIsSprinting ) ), |
|
SendPropTime( SENDINFO( m_flGoProneTime ) ), |
|
SendPropTime( SENDINFO( m_flUnProneTime ) ), |
|
SendPropTime( SENDINFO( m_flDeployChangeTime ) ), |
|
SendPropTime( SENDINFO( m_flDeployedHeight ) ), |
|
SendPropBool( SENDINFO( m_bPlanting ) ), |
|
SendPropBool( SENDINFO( m_bDefusing ) ), |
|
SendPropDataTable( "dodsharedlocaldata", 0, &REFERENCE_SEND_TABLE(DT_DODSharedLocalPlayerExclusive), SendProxy_SendLocalDataTable ), |
|
END_SEND_TABLE() |
|
|
|
|
|
// CDODPlayer Data Tables |
|
//========================= |
|
extern void SendProxy_Origin( const SendProp *pProp, const void *pStruct, const void *pData, DVariant *pOut, int iElement, int objectID ); |
|
|
|
// specific to the local player |
|
BEGIN_SEND_TABLE_NOBASE( CDODPlayer, DT_DODLocalPlayerExclusive ) |
|
// 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 ), |
|
SendPropFloat( SENDINFO(m_flStunDuration), 0, SPROP_NOSCALE ), |
|
SendPropFloat( SENDINFO(m_flStunMaxAlpha), 0, SPROP_NOSCALE ), |
|
SendPropInt( SENDINFO( m_iProgressBarDuration ), 4, SPROP_UNSIGNED ), |
|
SendPropFloat( SENDINFO( m_flProgressBarStartTime ), 0, SPROP_NOSCALE ), |
|
END_SEND_TABLE() |
|
|
|
// all players except the local player |
|
BEGIN_SEND_TABLE_NOBASE( CDODPlayer, DT_DODNonLocalPlayerExclusive ) |
|
// 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() |
|
|
|
// main table |
|
IMPLEMENT_SERVERCLASS_ST( CDODPlayer, DT_DODPlayer ) |
|
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_BaseEntity", "m_angRotation" ), |
|
SendPropExclude( "DT_BaseAnimatingOverlay", "overlay_vars" ), |
|
|
|
// dod_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( SENDINFO_DT( m_Shared ), &REFERENCE_SEND_TABLE( DT_DODPlayerShared ) ), |
|
|
|
// Data that only gets sent to the local player. |
|
SendPropDataTable( "dodlocaldata", 0, &REFERENCE_SEND_TABLE(DT_DODLocalPlayerExclusive), SendProxy_SendLocalDataTable ), |
|
SendPropDataTable( "dodnonlocaldata", 0, &REFERENCE_SEND_TABLE(DT_DODNonLocalPlayerExclusive), SendProxy_SendNonLocalDataTable ), |
|
|
|
SendPropAngle( SENDINFO_VECTORELEM(m_angEyeAngles, 0), 13, SPROP_CHANGES_OFTEN ), |
|
SendPropAngle( SENDINFO_VECTORELEM(m_angEyeAngles, 1), 13, SPROP_CHANGES_OFTEN ), |
|
SendPropEHandle( SENDINFO( m_hRagdoll ) ), |
|
SendPropBool( SENDINFO( m_bSpawnInterpCounter ) ), |
|
SendPropInt( SENDINFO( m_iAchievementAwardsMask ), NUM_ACHIEVEMENT_AWARDS, SPROP_UNSIGNED ), |
|
END_SEND_TABLE() |
|
|
|
BEGIN_DATADESC( CDODPlayer ) |
|
DEFINE_THINKFUNC( PushawayThink ), |
|
DEFINE_THINKFUNC( DeafenThink ) |
|
END_DATADESC() |
|
|
|
// -------------------------------------------------------------------------------- // |
|
|
|
void cc_CreatePredictionError_f() |
|
{ |
|
CBaseEntity *pEnt = CBaseEntity::Instance( 1 ); |
|
pEnt->SetAbsOrigin( pEnt->GetAbsOrigin() + Vector( 63, 0, 0 ) ); |
|
} |
|
|
|
ConCommand cc_CreatePredictionError( "CreatePredictionError", cc_CreatePredictionError_f, "Create a prediction error", FCVAR_CHEAT ); |
|
|
|
bool PlayerStatLessFunc( const int &lhs, const int &rhs ) |
|
{ |
|
return lhs < rhs; |
|
} |
|
|
|
// -------------------------------------------------------------------------------- // |
|
// CDODPlayer implementation. |
|
// -------------------------------------------------------------------------------- // |
|
CDODPlayer::CDODPlayer() |
|
#if !defined(NO_STEAM) |
|
: m_CallbackGSStatsReceived( this, &CDODPlayer::OnGSStatsReceived ) |
|
#endif |
|
{ |
|
m_PlayerAnimState = CreatePlayerAnimState( this ); |
|
m_Shared.Init( this ); |
|
|
|
UseClientSideAnimation(); |
|
m_angEyeAngles.Init(); |
|
|
|
SetViewOffset( DOD_PLAYER_VIEW_OFFSET ); |
|
|
|
m_pCurStateInfo = NULL; // no state yet |
|
m_lifeState = LIFE_DEAD; // Start "dead". |
|
|
|
m_Shared.SetPlayerClass( PLAYERCLASS_UNDEFINED ); |
|
|
|
m_bTeamChanged = false; |
|
|
|
// Clear the signals |
|
m_signals.Update(); |
|
m_fHandleSignalsTime = 0.0f; |
|
|
|
// autokick |
|
m_flLastMovement = gpGlobals->curtime; |
|
|
|
m_flNextVoice = gpGlobals->curtime; |
|
m_flNextHandSignal = gpGlobals->curtime; |
|
|
|
m_flNextTimeCheck = gpGlobals->curtime; |
|
|
|
m_bAutoReload = true; |
|
|
|
// Init our hint system |
|
Hints()->Init( this, NUM_HINTS, g_pszHintMessages ); |
|
Hints()->RegisterHintTimer( HINT_USE_MELEE, 60 ); |
|
Hints()->RegisterHintTimer( HINT_USE_ZOOM, 30 ); |
|
Hints()->RegisterHintTimer( HINT_USE_IRON_SIGHTS, 60 ); |
|
Hints()->RegisterHintTimer( HINT_USE_SEMI_AUTO, 60 ); |
|
Hints()->RegisterHintTimer( HINT_USE_SPRINT, 120 ); |
|
Hints()->RegisterHintTimer( HINT_USE_DEPLOY, 30 ); |
|
Hints()->RegisterHintTimer( HINT_USE_PRIME, 2 ); |
|
|
|
memset( &m_WeaponStats, 0, sizeof(weaponstat_t) ); |
|
|
|
m_KilledPlayers.SetLessFunc( PlayerStatLessFunc ); |
|
m_KilledByPlayers.SetLessFunc( PlayerStatLessFunc ); |
|
|
|
m_iNumAreaDefenses = 0; |
|
m_iNumAreaCaptures = 0; |
|
m_iNumBonusRoundKills = 0; |
|
|
|
memset( &m_flTimePlayedPerClass_Allies, 0, sizeof(m_flTimePlayedPerClass_Allies) ); |
|
memset( &m_flTimePlayedPerClass_Axis, 0, sizeof(m_flTimePlayedPerClass_Axis) ); |
|
m_flLastClassChangeTime = gpGlobals->curtime; |
|
|
|
Q_memset( iNumKilledByUnanswered, 0, sizeof( iNumKilledByUnanswered ) ); |
|
|
|
m_iAchievementAwardsMask = 0; |
|
|
|
m_hLastDroppedWeapon = NULL; |
|
m_hLastDroppedAmmoBox = NULL; |
|
|
|
m_flTimeAsClassAccumulator = 0; |
|
} |
|
|
|
|
|
CDODPlayer::~CDODPlayer() |
|
{ |
|
m_PlayerAnimState->Release(); |
|
} |
|
|
|
|
|
CDODPlayer *CDODPlayer::CreatePlayer( const char *className, edict_t *ed ) |
|
{ |
|
CDODPlayer::s_PlayerEdict = ed; |
|
return (CDODPlayer*)CreateEntityByName( className ); |
|
} |
|
|
|
void CDODPlayer::PrecachePlayerModel( const char *szPlayerModel ) |
|
{ |
|
PrecacheModel( szPlayerModel ); |
|
|
|
// Strange numbers, but gotten from the actual max bounds of our |
|
// player models |
|
const static Vector mins( -13.9, -30.1, -12.1 ); |
|
const static Vector maxs( 30.9, 30.1, 73.1 ); |
|
|
|
// Moved to pure_server_minimal.txt |
|
// engine->ForceModelBounds( szPlayerModel, mins, maxs ); |
|
} |
|
|
|
void CDODPlayer::Precache() |
|
{ |
|
PrecachePlayerModel( DOD_PLAYERMODEL_AXIS_RIFLEMAN ); |
|
PrecachePlayerModel( DOD_PLAYERMODEL_AXIS_ASSAULT ); |
|
PrecachePlayerModel( DOD_PLAYERMODEL_AXIS_SUPPORT ); |
|
PrecachePlayerModel( DOD_PLAYERMODEL_AXIS_SNIPER ); |
|
PrecachePlayerModel( DOD_PLAYERMODEL_AXIS_MG ); |
|
PrecachePlayerModel( DOD_PLAYERMODEL_AXIS_ROCKET ); |
|
|
|
PrecachePlayerModel( DOD_PLAYERMODEL_US_RIFLEMAN ); |
|
PrecachePlayerModel( DOD_PLAYERMODEL_US_ASSAULT ); |
|
PrecachePlayerModel( DOD_PLAYERMODEL_US_SUPPORT ); |
|
PrecachePlayerModel( DOD_PLAYERMODEL_US_SNIPER ); |
|
PrecachePlayerModel( DOD_PLAYERMODEL_US_MG ); |
|
PrecachePlayerModel( DOD_PLAYERMODEL_US_ROCKET ); |
|
|
|
/// Moved to pure_server_minimal.txt |
|
// engine->ForceSimpleMaterial( "materials/models/player/american/american_body.vmt" ); |
|
// engine->ForceSimpleMaterial( "materials/models/player/american/american_gear.vmt" ); |
|
// engine->ForceSimpleMaterial( "materials/models/player/german/german_body.vmt" ); |
|
// engine->ForceSimpleMaterial( "materials/models/player/german/german_gear.vmt" ); |
|
|
|
UTIL_PrecacheOther( "dod_ammo_box" ); |
|
|
|
PrecacheScriptSound( "Player.FlashlightOn" ); |
|
PrecacheScriptSound( "Player.FlashlightOff" ); |
|
|
|
PrecacheScriptSound( "Player.FreezeCam" ); |
|
PrecacheScriptSound( "Camera.SnapShot" ); |
|
PrecacheScriptSound( "Achievement.Earned" ); |
|
PrecacheScriptSound( "Game.Revenge" ); |
|
PrecacheScriptSound( "Game.Domination" ); |
|
PrecacheScriptSound( "Game.Nemesis" ); |
|
|
|
PrecacheModel ( "sprites/glow01.vmt" ); |
|
|
|
BaseClass::Precache(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Allow pre-frame adjustments on the player |
|
//----------------------------------------------------------------------------- |
|
ConVar sv_runcmds( "sv_runcmds", "1" ); |
|
void CDODPlayer::PlayerRunCommand( CUserCmd *ucmd, IMoveHelper *moveHelper ) |
|
{ |
|
VPROF( "CDODPlayer::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; |
|
} |
|
|
|
BaseClass::PlayerRunCommand( ucmd, moveHelper ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Simulates a single frame of movement for a player |
|
//----------------------------------------------------------------------------- |
|
void CDODPlayer::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 ) ); |
|
MoveHelperServer()->SetHost( this ); |
|
this->PlayerRunCommand( &cmd, MoveHelperServer() ); |
|
this->SetLastUserCommand( cmd ); |
|
|
|
// Clear out any fixangle that has been set |
|
this->pl.fixangle = FIXANGLE_NONE; |
|
|
|
// Restore the globals.. |
|
gpGlobals->frametime = flOldFrametime; |
|
gpGlobals->curtime = flOldCurtime; |
|
} |
|
|
|
|
|
void CDODPlayer::InitialSpawn( void ) |
|
{ |
|
BaseClass::InitialSpawn(); |
|
|
|
State_Enter( STATE_WELCOME ); |
|
} |
|
|
|
void CDODPlayer::Spawn() |
|
{ |
|
// Get rid of the progress bar... |
|
SetProgressBarTime( 0 ); |
|
|
|
SetModel( DOD_PLAYERMODEL_US_RIFLEMAN ); //temporarily |
|
BaseClass::Spawn(); |
|
|
|
AddFlag( FL_ONGROUND ); // set the player on the ground at the start of the round. |
|
|
|
m_Shared.SetStamina( 100 ); |
|
m_bTeamChanged = false; |
|
|
|
ClearCapAreaIndex(); |
|
|
|
m_bHasGenericAmmo = true; |
|
|
|
m_bSlowedByHit = false; |
|
|
|
m_flIdleTime = gpGlobals->curtime + random->RandomFloat( 20, 30 ); |
|
|
|
InitProne(); |
|
InitSprinting(); |
|
|
|
// update this counter, used to not interp players when they spawn |
|
m_bSpawnInterpCounter = !m_bSpawnInterpCounter; |
|
|
|
EmitSound( "Player.Spawn" ); |
|
|
|
SetContextThink( &CDODPlayer::PushawayThink, gpGlobals->curtime + PUSHAWAY_THINK_INTERVAL, DOD_PUSHAWAY_THINK_CONTEXT ); |
|
|
|
m_Shared.SetCPIndex( -1 ); |
|
|
|
ResetBleeding(); |
|
|
|
// Our own version of remove decals, as we can't pass entity messages to |
|
// the target's base class. |
|
|
|
EntityMessageBegin( this ); |
|
WRITE_BYTE( DOD_PLAYER_REMOVE_DECALS ); |
|
MessageEnd(); |
|
|
|
if ( m_Shared.PlayerClass() != PLAYERCLASS_UNDEFINED ) |
|
{ |
|
Hints()->StartHintTimer( HINT_USE_SPRINT ); |
|
} |
|
|
|
// Get a unique "life id" used for tracking stats |
|
m_iLifeID = GetNextLifeID(); |
|
|
|
SetDefusing( NULL ); |
|
SetPlanting( NULL ); |
|
|
|
m_iLastBlockAreaIndex = -1; |
|
m_iLastBlockCapAttempt = -1; |
|
|
|
if ( DODGameRules()->IsBombingTeam( GetTeamNumber() ) ) |
|
{ |
|
Hints()->HintMessage( HINT_BOMB_PLANT_MAP ); |
|
} |
|
|
|
//CollisionProp()->SetSurroundingBoundsType( USE_GAME_CODE ); |
|
|
|
// Reset per-life achievement counts |
|
HandleHeadshotAchievement( 0 ); |
|
HandleDeployedMGKillCount( 0 ); |
|
HandleEnemyWeaponsAchievement( 0 ); |
|
ResetComboWeaponKill(); |
|
|
|
RecalculateAchievementAwardsMask(); |
|
|
|
// If we're spawning as a non-player team, flush the stats |
|
int iTeam = GetTeamNumber(); |
|
if ( iTeam != TEAM_ALLIES && iTeam != TEAM_AXIS ) |
|
{ |
|
StatEvent_UploadStats(); |
|
} |
|
|
|
m_StatProperty.SetClassAndTeamForThisLife( m_Shared.PlayerClass(), GetTeamNumber() ); |
|
} |
|
|
|
void CDODPlayer::PlayerDeathThink() |
|
{ |
|
//overridden, do nothing |
|
} |
|
|
|
void CDODPlayer::CreateRagdollEntity() |
|
{ |
|
// If we already have a ragdoll, don't make another one. |
|
CDODRagdoll *pRagdoll = dynamic_cast< CDODRagdoll* >( m_hRagdoll.Get() ); |
|
|
|
if( pRagdoll ) |
|
{ |
|
// MATTTODO: put ragdolls in a queue to disappear |
|
// for now remove the old one .. |
|
UTIL_Remove( pRagdoll ); |
|
pRagdoll = NULL; |
|
} |
|
|
|
if ( !pRagdoll ) |
|
{ |
|
// and create a new one |
|
pRagdoll = dynamic_cast< CDODRagdoll* >( CreateEntityByName( "dod_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; |
|
} |
|
|
|
// ragdolls will be removed on round restart automatically |
|
m_hRagdoll = pRagdoll; |
|
} |
|
|
|
// Called when a player is disconnecting |
|
void CDODPlayer::DestroyRagdoll( void ) |
|
{ |
|
CDODRagdoll *pRagdoll = dynamic_cast< CDODRagdoll* >( m_hRagdoll.Get() ); |
|
|
|
if( pRagdoll ) |
|
{ |
|
UTIL_Remove( pRagdoll ); |
|
} |
|
} |
|
|
|
void CDODPlayer::Event_Killed( const CTakeDamageInfo &info ) |
|
{ |
|
// allow bots to react |
|
// TheBots->OnEvent( EVENT_PLAYER_DIED, this, info.GetAttacker() ); |
|
|
|
CBaseEntity *pInflictor = info.GetInflictor(); |
|
CBaseEntity *pKiller = info.GetAttacker(); |
|
CDODPlayer *pScorer = ToDODPlayer( DODGameRules()->GetDeathScorer( pKiller, pInflictor ) ); |
|
|
|
// Do this before we drop the victim's weapons to check the streak |
|
if ( GetDeployedKillStreak() >= ACHIEVEMENT_MG_STREAK_IS_DOMINATING ) |
|
{ |
|
if ( pScorer && pScorer->GetTeamNumber() != GetTeamNumber() && DODGameRules()->State_Get() == STATE_RND_RUNNING ) |
|
{ |
|
pScorer->AwardAchievement( ACHIEVEMENT_DOD_KILL_DOMINATING_MG ); |
|
} |
|
} |
|
|
|
// see if this was a long range kill |
|
// distance from scorer to inflictor > 1200 |
|
if ( pScorer && GetTeamNumber() != pScorer->GetTeamNumber() && pInflictor != pScorer ) |
|
{ |
|
const char *killer_weapon_name = STRING( pInflictor->m_iClassname ); |
|
|
|
if ( strncmp( killer_weapon_name, "rocket_", 7 ) == 0 ) |
|
{ |
|
float flDist = ( pScorer->GetAbsOrigin() - pInflictor->GetAbsOrigin() ).Length(); |
|
|
|
if ( flDist > ACHIEVEMENT_LONG_RANGE_ROCKET_DIST && DODGameRules()->State_Get() == STATE_RND_RUNNING ) |
|
{ |
|
bool bIsSniperZoomed = false; |
|
|
|
CWeaponDODBase *pWeapon = GetActiveDODWeapon(); |
|
if( pWeapon && ( pWeapon->GetDODWpnData().m_WeaponType == WPN_TYPE_SNIPER ) ) |
|
{ |
|
CWeaponDODBaseGun *pGun = static_cast<CWeaponDODBaseGun *>( pWeapon ); |
|
bIsSniperZoomed = pGun->IsSniperZoomed(); |
|
} |
|
|
|
// victim must be deployed sniper or deployed mg |
|
if ( bIsSniperZoomed || m_Shared.IsInMGDeploy() ) |
|
{ |
|
pScorer->AwardAchievement( ACHIEVEMENT_DOD_LONG_RANGE_ROCKET ); |
|
} |
|
} |
|
} |
|
} |
|
|
|
DropPrimaryWeapon(); |
|
|
|
if ( DODGameRules()->GetPresentDropChance() >= RandomFloat() ) |
|
{ |
|
Vector vForward, vRight, vUp; |
|
AngleVectors( EyeAngles(), &vForward, &vRight, &vUp ); |
|
|
|
CHolidayGift::Create( WorldSpaceCenter(), GetAbsAngles(), EyeAngles(), GetAbsVelocity(), this ); |
|
} |
|
|
|
FlashlightTurnOff(); |
|
|
|
// Just in case the progress bar is on screen, kill it. |
|
SetProgressBarTime( 0 ); |
|
|
|
// turn off our cap area index |
|
HandleSignals(); |
|
|
|
//if we have a primed grenade, drop it |
|
CWeaponDODBase *pWpn = GetActiveDODWeapon(); |
|
|
|
if( pWpn && pWpn->GetDODWpnData().m_WeaponType == WPN_TYPE_GRENADE ) |
|
{ |
|
CWeaponDODBaseGrenade *pGrenade = dynamic_cast<CWeaponDODBaseGrenade *>(pWpn); |
|
|
|
if( pGrenade && pGrenade->IsArmed() ) |
|
{ |
|
pGrenade->DropGrenade(); |
|
} |
|
} |
|
|
|
if ( pScorer && pScorer != this && pScorer->GetTeamNumber() != GetTeamNumber() ) |
|
{ |
|
if ( m_bIsPlanting && m_pPlantTarget ) |
|
{ |
|
CDODBombTarget *pTarget = m_pPlantTarget; |
|
|
|
Assert( pTarget ); |
|
|
|
if ( pTarget ) |
|
pTarget->PlantBlocked( pScorer ); |
|
|
|
IGameEvent *event = gameeventmanager->CreateEvent( "dod_kill_planter" ); |
|
if ( event ) |
|
{ |
|
event->SetInt( "userid", pScorer->GetUserID() ); |
|
event->SetInt( "victimid", GetUserID() ); |
|
|
|
gameeventmanager->FireEvent( event ); |
|
} |
|
} |
|
|
|
if ( m_bIsDefusing && m_pDefuseTarget && pScorer->GetTeamNumber() != GetTeamNumber() ) |
|
{ |
|
// Re-enable if we want to give a defend point to someone who kills a defuser |
|
|
|
//CDODBombTarget *pTarget = m_pDefuseTarget; |
|
//pTarget->DefuseBlocked( pScorer ); |
|
|
|
IGameEvent *event = gameeventmanager->CreateEvent( "dod_kill_defuser" ); |
|
if ( event ) |
|
{ |
|
event->SetInt( "userid", pScorer->GetUserID() ); |
|
event->SetInt( "victimid", GetUserID() ); |
|
|
|
gameeventmanager->FireEvent( event ); |
|
} |
|
} |
|
} |
|
|
|
SetDefusing( NULL ); |
|
SetPlanting( NULL ); |
|
|
|
// show killer in death cam mode |
|
// chopped down version of SetObserverTarget without the team check |
|
if( info.GetAttacker() && info.GetAttacker()->IsPlayer() ) |
|
{ |
|
// set new target |
|
m_hObserverTarget.Set( info.GetAttacker() ); |
|
|
|
// reset fov to default |
|
SetFOV( this, 0 ); |
|
} |
|
else |
|
m_hObserverTarget.Set( NULL ); |
|
|
|
//update damage info with our accumulated physics force |
|
CTakeDamageInfo subinfo = info; |
|
subinfo.SetDamageForce( m_vecTotalBulletForce ); |
|
|
|
// 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(); |
|
|
|
State_Transition( STATE_DEATH_ANIM ); // Transition into the dying state. |
|
BaseClass::Event_Killed( subinfo ); |
|
|
|
OutputDamageGiven(); |
|
OutputDamageTaken(); |
|
ResetDamageCounters(); |
|
|
|
// stop any voice command or pain sounds that we may be making |
|
CPASFilter filter( WorldSpaceCenter() ); |
|
EmitSound( filter, entindex(), "Voice.StopSounds" ); |
|
|
|
ResetBleeding(); |
|
|
|
m_flStunDuration = 0.0f; |
|
m_flStunMaxAlpha = 0; |
|
|
|
// cancel deafen think |
|
SetContextThink( NULL, 0.0, DOD_DEAFEN_CONTEXT ); |
|
|
|
// cancel deafen dsp |
|
CSingleUserRecipientFilter user( this ); |
|
enginesound->SetPlayerDSP( user, 0, false ); |
|
|
|
CDODPlayer *pAttacker = ToDODPlayer( info.GetAttacker() ); |
|
|
|
if( pAttacker && pAttacker->IsPlayer() ) |
|
{ |
|
// find the weapon id of the weapon |
|
|
|
DODWeaponID weaponID = WEAPON_NONE; |
|
|
|
CBaseEntity *pInflictor = info.GetInflictor(); |
|
|
|
if ( pInflictor == pAttacker ) |
|
{ |
|
CWeaponDODBase *pWpn = pAttacker->GetActiveDODWeapon(); |
|
|
|
if ( pWpn ) |
|
{ |
|
if ( info.GetDamageCustom() & MELEE_DMG_SECONDARYATTACK ) |
|
weaponID = pWpn->GetAltWeaponID(); |
|
else |
|
weaponID = pWpn->GetStatsWeaponID(); |
|
} |
|
} |
|
else |
|
{ |
|
Assert( pInflictor ); |
|
|
|
const char *weaponName = STRING( pInflictor->m_iClassname ); |
|
|
|
if ( Q_strncmp( weaponName, "rocket_", 7 ) == 0 ) |
|
{ |
|
CDODBaseRocket *pRocket = dynamic_cast< CDODBaseRocket *>( pInflictor ); |
|
if ( pRocket ) |
|
{ |
|
weaponID = pRocket->GetEmitterWeaponID(); |
|
} |
|
} |
|
else if ( Q_strncmp( weaponName, "grenade_", 8 ) == 0 ) |
|
{ |
|
CDODBaseGrenade *pGren = dynamic_cast< CDODBaseGrenade *>( pInflictor ); |
|
if ( pGren ) |
|
{ |
|
weaponID = pGren->GetEmitterWeaponID(); |
|
} |
|
} |
|
} |
|
|
|
IGameEvent * event = gameeventmanager->CreateEvent( "dod_stats_player_killed" ); |
|
if ( event ) |
|
{ |
|
event->SetInt( "attacker", pAttacker->GetUserID() ); |
|
event->SetInt( "victim", GetUserID() ); |
|
event->SetInt( "weapon", weaponID ); |
|
|
|
gameeventmanager->FireEvent( event ); |
|
} |
|
|
|
if ( DODGameRules()->IsInBonusRound() ) |
|
{ |
|
pAttacker->Stats_BonusRoundKill(); |
|
} |
|
} |
|
|
|
m_bIsDefusing = false; |
|
m_bIsPlanting = false; |
|
|
|
if ( pScorer != this ) |
|
{ |
|
StatEvent_WasKilled(); |
|
} |
|
|
|
StatEvent_UploadStats(); |
|
} |
|
|
|
bool CDODPlayer::ShouldInstantRespawn( void ) |
|
{ |
|
CUtlVector<EHANDLE> *pSpawnPoints = DODGameRules()->GetSpawnPointListForTeam( GetTeamNumber() ); |
|
|
|
if ( !pSpawnPoints ) |
|
return false; |
|
|
|
const float flMaxDistSqr = ( 500*500 ); |
|
Vector vecOrigin = GetAbsOrigin(); |
|
int i; |
|
int count = pSpawnPoints->Count(); |
|
for ( i=0;i<count;i++ ) |
|
{ |
|
EHANDLE handle = pSpawnPoints->Element(i); |
|
if ( handle ) |
|
{ |
|
CSpawnPoint *point = dynamic_cast<CSpawnPoint *>( handle.Get() ); |
|
if ( point && !point->IsDisabled() ) |
|
{ |
|
// range |
|
Vector vecDist = point->GetAbsOrigin() - vecOrigin; |
|
if ( vecDist.LengthSqr() > flMaxDistSqr ) |
|
{ |
|
continue; |
|
} |
|
|
|
// los |
|
if ( FVisible( point ) ) |
|
{ |
|
return true; |
|
} |
|
} |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CDODPlayer::InitVCollision( const Vector &vecAbsOrigin, const Vector &vecAbsVelocity ) |
|
{ |
|
if ( 0 ) //if ( !sv_turbophysics.GetBool() ) |
|
{ |
|
BaseClass::InitVCollision( vecAbsOrigin, vecAbsVelocity ); |
|
|
|
// Setup the HL2 specific callback. |
|
GetPhysicsController()->SetEventHandler( &playerCallback ); |
|
} |
|
} |
|
|
|
void CDODPlayer::VPhysicsShadowUpdate( IPhysicsObject *pPhysics ) |
|
{ |
|
if ( 0 ) //if ( !sv_turbophysics.GetBool() ) |
|
{ |
|
if ( !CanMove() ) |
|
return; |
|
|
|
BaseClass::VPhysicsShadowUpdate( pPhysics ); |
|
} |
|
} |
|
|
|
void CDODPlayer::PushawayThink() |
|
{ |
|
// Push physics props out of our way. |
|
PerformObstaclePushaway( this ); |
|
SetNextThink( gpGlobals->curtime + PUSHAWAY_THINK_INTERVAL, DOD_PUSHAWAY_THINK_CONTEXT ); |
|
} |
|
|
|
void CDODPlayer::CheatImpulseCommands( int iImpulse ) |
|
{ |
|
switch( iImpulse ) |
|
{ |
|
case 101: |
|
{ |
|
if( sv_cheats->GetBool() ) |
|
{ |
|
extern int gEvilImpulse101; |
|
gEvilImpulse101 = true; |
|
|
|
GiveAmmo( 1000, DOD_AMMO_COLT ); |
|
GiveAmmo( 1000, DOD_AMMO_P38 ); |
|
GiveAmmo( 1000, DOD_AMMO_C96 ); |
|
GiveAmmo( 1000, DOD_AMMO_GARAND ); |
|
GiveAmmo( 1000, DOD_AMMO_K98 ); |
|
GiveAmmo( 1000, DOD_AMMO_M1CARBINE ); |
|
GiveAmmo( 1000, DOD_AMMO_SPRING ); |
|
GiveAmmo( 1000, DOD_AMMO_SUBMG ); |
|
GiveAmmo( 1000, DOD_AMMO_BAR ); |
|
GiveAmmo( 1000, DOD_AMMO_30CAL ); |
|
GiveAmmo( 1000, DOD_AMMO_MG42 ); |
|
GiveAmmo( 1000, DOD_AMMO_ROCKET ); |
|
|
|
gEvilImpulse101 = false; |
|
} |
|
} |
|
break; |
|
|
|
default: |
|
{ |
|
BaseClass::CheatImpulseCommands( iImpulse ); |
|
} |
|
} |
|
} |
|
|
|
void CDODPlayer::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 CDODPlayer::DoAnimationEvent( PlayerAnimEvent_t event, int nData ) |
|
{ |
|
m_PlayerAnimState->DoAnimationEvent( event, nData ); |
|
TE_PlayerAnimEvent( this, event, nData ); // Send to any clients who can see this guy. |
|
} |
|
|
|
CBaseEntity *CDODPlayer::GiveNamedItem( const char *pszName, int iSubType ) |
|
{ |
|
EHANDLE pent; |
|
|
|
pent = CreateEntityByName(pszName); |
|
if ( pent == NULL ) |
|
{ |
|
Msg( "NULL Ent in GiveNamedItem!\n" ); |
|
return NULL; |
|
} |
|
|
|
pent->SetLocalOrigin( GetLocalOrigin() ); |
|
pent->AddSpawnFlags( SF_NORESPAWN ); |
|
|
|
if ( iSubType ) |
|
{ |
|
CBaseCombatWeapon *pWeapon = dynamic_cast<CBaseCombatWeapon*>( (CBaseEntity*)pent ); |
|
if ( pWeapon ) |
|
{ |
|
pWeapon->SetSubType( iSubType ); |
|
} |
|
} |
|
|
|
DispatchSpawn( pent ); |
|
|
|
if ( pent != NULL && !(pent->IsMarkedForDeletion()) ) |
|
{ |
|
pent->Touch( this ); |
|
} |
|
|
|
return pent; |
|
} |
|
|
|
extern ConVar flashlight; |
|
|
|
//----------------------------------------------------------------------------- |
|
//----------------------------------------------------------------------------- |
|
int CDODPlayer::FlashlightIsOn( void ) |
|
{ |
|
return IsEffectActive( EF_DIMLIGHT ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
//----------------------------------------------------------------------------- |
|
void CDODPlayer::FlashlightTurnOn( void ) |
|
{ |
|
if( flashlight.GetInt() > 0 && IsAlive() ) |
|
{ |
|
AddEffects( EF_DIMLIGHT ); |
|
EmitSound( "Player.FlashlightOn" ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
//----------------------------------------------------------------------------- |
|
void CDODPlayer::FlashlightTurnOff( void ) |
|
{ |
|
if( IsEffectActive(EF_DIMLIGHT) ) |
|
{ |
|
RemoveEffects( EF_DIMLIGHT ); |
|
|
|
if( m_iHealth > 0 ) |
|
{ |
|
EmitSound( "Player.FlashlightOff" ); |
|
} |
|
} |
|
} |
|
|
|
|
|
void CDODPlayer::PostThink() |
|
{ |
|
BaseClass::PostThink(); |
|
|
|
if( m_Shared.IsProne() ) |
|
{ |
|
SetCollisionBounds( VEC_PRONE_HULL_MIN, VEC_PRONE_HULL_MAX ); |
|
} |
|
|
|
if ( gpGlobals->curtime > m_fHandleSignalsTime ) |
|
{ |
|
m_fHandleSignalsTime = gpGlobals->curtime + 0.1; |
|
HandleSignals(); |
|
} |
|
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] ); |
|
} |
|
|
|
void CDODPlayer::HandleCommand_Voice( const char *pcmd ) |
|
{ |
|
// string should be formatted as "voice_gogogo" |
|
|
|
// go through our list of voice commands and if we find it, |
|
// emit a voice command |
|
|
|
int i = 0; |
|
while( g_VoiceCommands[i].pszCommandName != NULL ) |
|
{ |
|
if( Q_strcmp( pcmd, g_VoiceCommands[i].pszCommandName ) == 0 ) |
|
{ |
|
VoiceCommand( i ); |
|
break; |
|
} |
|
i++; |
|
} |
|
} |
|
|
|
void CDODPlayer::VoiceCommand( int iVoiceCommand ) |
|
{ |
|
//no voices in observer or when dead |
|
if ( !IsAlive() || IsObserver() ) |
|
return; |
|
|
|
//no voices when game is over |
|
if (g_fGameOver) |
|
return; |
|
|
|
if (m_flNextVoice > gpGlobals->curtime ) |
|
return; |
|
|
|
// Emit the voice sound |
|
CPASFilter filter( WorldSpaceCenter() ); |
|
|
|
// Get the country text to fill into the sound name |
|
char *pszCountry = "US"; |
|
|
|
if( GetTeamNumber() == TEAM_AXIS ) |
|
{ |
|
pszCountry = "German"; |
|
} |
|
|
|
char szSound[128]; |
|
Q_snprintf( szSound, sizeof(szSound), "Voice.%s_%s", pszCountry, g_VoiceCommands[iVoiceCommand].pszSoundName ); |
|
|
|
EmitSound( filter, entindex(), szSound ); |
|
|
|
// Don't show the subtitle to the other team |
|
int oppositeTeam = ( GetTeamNumber() == TEAM_ALLIES ) ? TEAM_AXIS : TEAM_ALLIES; |
|
filter.RemoveRecipientsByTeam( GetGlobalDODTeam(oppositeTeam) ); |
|
|
|
// further reduce the voice command subtitle radius |
|
float flDist; |
|
float flMaxDist = 1900; |
|
Vector vecEmitOrigin = GetAbsOrigin(); |
|
|
|
int i; |
|
for ( i=1; i<= MAX_PLAYERS; i++ ) |
|
{ |
|
CBasePlayer *pPlayer = UTIL_PlayerByIndex( i ); |
|
|
|
if ( !pPlayer ) |
|
continue; |
|
|
|
flDist = ( pPlayer->GetAbsOrigin() - vecEmitOrigin ).Length2D(); |
|
|
|
if ( flDist > flMaxDist ) |
|
filter.RemoveRecipient( pPlayer ); |
|
} |
|
|
|
// Send a subtitle to anyone in the PAS |
|
UserMessageBegin( filter, "VoiceSubtitle" ); |
|
WRITE_BYTE( entindex() ); |
|
WRITE_BYTE( GetTeamNumber() ); |
|
WRITE_BYTE( iVoiceCommand ); |
|
MessageEnd(); |
|
|
|
PlayerAnimEvent_t iHandSignal = g_VoiceCommands[iVoiceCommand].iHandSignal; |
|
|
|
if( iHandSignal != PLAYERANIMEVENT_HS_NONE ) |
|
DoAnimationEvent( iHandSignal ); |
|
|
|
m_flNextVoice = gpGlobals->curtime + 1.0; |
|
} |
|
|
|
void CDODPlayer::HandleCommand_HandSignal( const char *pcmd ) |
|
{ |
|
// string should be formatted as "signal_yes" |
|
|
|
int i = 0; |
|
while( g_HandSignals[i].pszCommandName != NULL ) |
|
{ |
|
if( Q_strcmp( pcmd, g_HandSignals[i].pszCommandName ) == 0 ) |
|
{ |
|
HandSignal( i ); |
|
break; |
|
} |
|
i++; |
|
} |
|
} |
|
|
|
void CDODPlayer::HandSignal( int iSignal ) |
|
{ |
|
//no hand signals in observer or when dead |
|
if ( !IsAlive() || IsObserver() ) |
|
return; |
|
|
|
//or when game is over |
|
if (g_fGameOver) |
|
return; |
|
|
|
if (m_flNextHandSignal > gpGlobals->curtime ) |
|
return; |
|
|
|
int oppositeTeam = ( GetTeamNumber() == TEAM_ALLIES ) ? TEAM_AXIS : TEAM_ALLIES; |
|
|
|
// Emit the voice sound |
|
CRecipientFilter filter; |
|
filter.AddRecipientsByPVS( WorldSpaceCenter() ); |
|
filter.RemoveRecipientsByTeam( GetGlobalTeam(oppositeTeam) ); |
|
|
|
// Send a subtitle to anyone in the PAS |
|
UserMessageBegin( filter, "HandSignalSubtitle" ); |
|
WRITE_BYTE( entindex() ); |
|
WRITE_BYTE( iSignal ); |
|
MessageEnd(); |
|
|
|
DoAnimationEvent( g_HandSignals[iSignal].iHandSignal ); |
|
|
|
m_flNextHandSignal = gpGlobals->curtime + 1.0; |
|
} |
|
|
|
void CDODPlayer::DropGenericAmmo( void ) |
|
{ |
|
if ( !IsAlive() ) |
|
return; |
|
|
|
if( m_bHasGenericAmmo == false ) |
|
return; |
|
|
|
Vector vForward, vRight, vUp; |
|
AngleVectors( EyeAngles(), &vForward, &vRight, &vUp ); |
|
|
|
CAmmoBox *pAmmo = CAmmoBox::Create( WorldSpaceCenter(), vec3_angle, this, GetTeamNumber() ); |
|
|
|
pAmmo->ApplyAbsVelocityImpulse( vForward * 300 + vUp * 100 ); |
|
|
|
m_hLastDroppedAmmoBox = pAmmo; |
|
|
|
m_bHasGenericAmmo = false; |
|
} |
|
|
|
void CDODPlayer::ReturnGenericAmmo( void ) |
|
{ |
|
//play pickup sound |
|
CPASFilter filter( WorldSpaceCenter() ); |
|
EmitSound( filter, entindex(), "BaseCombatCharacter.AmmoPickup" ); |
|
|
|
//allow them to drop generic ammo again |
|
m_bHasGenericAmmo = true; |
|
} |
|
|
|
bool CDODPlayer::GiveGenericAmmo( void ) |
|
{ |
|
//give primary weapon ammo |
|
CWeaponDODBase *pWpn = (CWeaponDODBase *)Weapon_GetSlot( WPN_SLOT_PRIMARY ); |
|
|
|
if( pWpn ) |
|
{ |
|
int nAmmoType = pWpn->GetPrimaryAmmoType(); |
|
|
|
int iClipSize = pWpn->GetDODWpnData().iMaxClip1; |
|
|
|
int iAmmoPickupClips = pWpn->GetDODWpnData().m_iAmmoPickupClips; |
|
|
|
if( GiveAmmo( iAmmoPickupClips * iClipSize, nAmmoType, false ) > 0 ) |
|
{ |
|
//some ammo was picked up, consume the ammo box |
|
|
|
//play pickup sound |
|
CPASFilter filter( WorldSpaceCenter() ); |
|
EmitSound( filter, entindex(), "BaseCombatCharacter.AmmoPickup" ); |
|
|
|
return true; |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
ConVar dod_friendlyfiresafezone( "dod_friendlyfiresafezone", |
|
"100", |
|
FCVAR_ARCHIVE, |
|
"Units around a player where they will not damage teammates, even if FF is on", |
|
true, 0, false, 0 ); |
|
|
|
void CDODPlayer::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator ) |
|
{ |
|
bool bTakeDamage = true; |
|
|
|
CBasePlayer *pAttacker = (CBasePlayer*)ToBasePlayer( info.GetAttacker() ); |
|
|
|
bool bFriendlyFire = DODGameRules()->IsFriendlyFireOn(); |
|
|
|
if ( pAttacker && ( GetTeamNumber() == pAttacker->GetTeamNumber() ) ) |
|
{ |
|
bTakeDamage = bFriendlyFire; |
|
|
|
// Create a FF-free zone around players for melee and bullets |
|
if ( bFriendlyFire && ( info.GetDamageType() & (DMG_SLASH | DMG_BULLET | DMG_CLUB) ) ) |
|
{ |
|
float flDist = dod_friendlyfiresafezone.GetFloat(); |
|
|
|
Vector vecDist = pAttacker->WorldSpaceCenter() - WorldSpaceCenter(); |
|
|
|
if ( vecDist.LengthSqr() < ( flDist*flDist ) ) |
|
{ |
|
bTakeDamage = false; |
|
} |
|
} |
|
} |
|
|
|
if ( m_takedamage != DAMAGE_YES ) |
|
return; |
|
|
|
m_LastHitGroup = ptr->hitgroup; |
|
m_LastDamageType = info.GetDamageType(); |
|
|
|
Assert( ptr->hitgroup != HITGROUP_GENERIC ); |
|
|
|
m_nForceBone = ptr->physicsbone; //Save this bone for ragdoll |
|
|
|
float flDamage = info.GetDamage(); |
|
float flOriginalDmg = flDamage; |
|
|
|
bool bHeadShot = false; |
|
|
|
//if its an enemy OR ff is on. |
|
if( bTakeDamage ) |
|
{ |
|
m_Shared.SetSlowedTime( 0.5f ); |
|
} |
|
|
|
char *szHitbox = NULL; |
|
|
|
if( info.GetDamageType() & DMG_BLAST ) |
|
{ |
|
// don't do hitgroup specific grenade damage |
|
flDamage *= 1.0; |
|
szHitbox = "dmg_blast"; |
|
} |
|
else |
|
{ |
|
switch ( ptr->hitgroup ) |
|
{ |
|
case HITGROUP_HEAD: |
|
{ |
|
flDamage *= 2.5; //regular head shot multiplier |
|
|
|
if( bTakeDamage ) |
|
{ |
|
Vector dir = vecDir; |
|
VectorNormalize(dir); |
|
|
|
if ( info.GetDamageType() & DMG_CLUB ) |
|
dir *= 800; |
|
else |
|
dir *= 400; |
|
|
|
dir.z += 100; // add some lift so it pops better. |
|
|
|
PopHelmet( dir, ptr->endpos ); |
|
} |
|
|
|
szHitbox = "head"; |
|
|
|
bHeadShot = true; |
|
} |
|
break; |
|
|
|
case HITGROUP_CHEST: |
|
szHitbox = "chest"; |
|
break; |
|
|
|
case HITGROUP_STOMACH: |
|
szHitbox = "stomach"; |
|
break; |
|
|
|
case HITGROUP_LEFTARM: |
|
flDamage *= 0.75; |
|
szHitbox = "left arm"; |
|
break; |
|
|
|
case HITGROUP_RIGHTARM: |
|
flDamage *= 0.75; |
|
szHitbox = "right arm"; |
|
break; |
|
|
|
case HITGROUP_LEFTLEG: |
|
case HITGROUP_RIGHTLEG: |
|
{ |
|
//we are slowed for 2 full seconds if we get a leg hit |
|
if( bTakeDamage ) |
|
{ |
|
//m_bSlowedByHit = true; |
|
//m_flUnslowTime = gpGlobals->curtime + 2; |
|
m_Shared.SetSlowedTime( 2.0f ); |
|
} |
|
|
|
flDamage *= 0.75; |
|
|
|
szHitbox = "leg"; |
|
} |
|
break; |
|
|
|
case HITGROUP_GENERIC: |
|
szHitbox = "(error - hit generic)"; |
|
break; |
|
|
|
default: |
|
szHitbox = "(error - hit default)"; |
|
break; |
|
} |
|
} |
|
|
|
if ( bTakeDamage ) |
|
{ |
|
if( dod_debugdamage.GetInt() ) |
|
{ |
|
char buf[256]; |
|
|
|
Q_snprintf( buf, sizeof(buf), "%s hit %s in the %s hitgroup for %f damage ( %f base damage ) ( %s now has %d health )\n", |
|
pAttacker->GetPlayerName(), |
|
GetPlayerName(), |
|
szHitbox, |
|
flDamage, |
|
flOriginalDmg, |
|
GetPlayerName(), |
|
GetHealth() - (int)flDamage ); |
|
|
|
// print to server |
|
UTIL_LogPrintf( "%s", buf ); |
|
|
|
// print to injured |
|
ClientPrint( this, HUD_PRINTCONSOLE, buf ); |
|
|
|
// print to shooter |
|
if ( pAttacker ) |
|
ClientPrint( pAttacker, HUD_PRINTCONSOLE, buf ); |
|
} |
|
|
|
// Since this code only runs on the server, make sure it shows the tempents it creates. |
|
CDisablePredictionFiltering disabler; |
|
|
|
// 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_flScale = 4; |
|
data.m_fFlags = FX_BLOODSPRAY_ALL; |
|
data.m_nEntIndex = ptr->m_pEnt ? ptr->m_pEnt->entindex() : 0; |
|
data.m_flMagnitude = flDamage; |
|
|
|
DispatchEffect( "dodblood", data ); |
|
|
|
CTakeDamageInfo subInfo = info; |
|
|
|
subInfo.SetDamage( flDamage ); |
|
|
|
AddMultiDamage( subInfo, this ); |
|
} |
|
} |
|
|
|
|
|
bool CDODPlayer::DropActiveWeapon( void ) |
|
{ |
|
CWeaponDODBase *pWeapon = GetActiveDODWeapon(); |
|
|
|
if( pWeapon ) |
|
{ |
|
if( pWeapon->CanDrop() ) |
|
{ |
|
DODWeaponDrop( pWeapon, true ); |
|
return true; |
|
} |
|
else |
|
{ |
|
int iWeaponType = pWeapon->GetDODWpnData().m_WeaponType; |
|
if( iWeaponType == WPN_TYPE_BAZOOKA || iWeaponType == WPN_TYPE_MG ) |
|
{ |
|
// they are deployed, cannot drop |
|
Hints()->HintMessage( "#game_cannot_drop_while" ); |
|
} |
|
else |
|
{ |
|
// must be a weapon type that cannot be dropped |
|
Hints()->HintMessage( "#game_cannot_drop" ); |
|
} |
|
|
|
return false; |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
CWeaponDODBase* CDODPlayer::GetActiveDODWeapon() const |
|
{ |
|
return dynamic_cast< CWeaponDODBase* >( GetActiveWeapon() ); |
|
} |
|
|
|
void CDODPlayer::PreThink() |
|
{ |
|
BaseClass::PreThink(); |
|
|
|
if ( m_afButtonLast != m_nButtons ) |
|
m_flLastMovement = gpGlobals->curtime; |
|
|
|
if ( g_fGameOver ) |
|
return; |
|
|
|
State_PreThink(); |
|
|
|
//Reset bullet force accumulator, only lasts one frame, for ragdoll forces from multiple shots |
|
m_vecTotalBulletForce = vec3_origin; |
|
|
|
if ( mp_autokick.GetInt() && !IsBot() && !IsHLTV() ) |
|
{ |
|
if ( m_flLastMovement + mp_autokick.GetInt()*60 < gpGlobals->curtime ) |
|
{ |
|
UTIL_ClientPrintAll( HUD_PRINTCONSOLE, "#Game_idle_kick", GetPlayerName() ); |
|
engine->ServerCommand( UTIL_VarArgs( "kickid %d\n", GetUserID() ) ); |
|
m_flLastMovement = gpGlobals->curtime; |
|
} |
|
} |
|
} |
|
|
|
|
|
bool CDODPlayer::DropPrimaryWeapon( void ) |
|
{ |
|
CWeaponDODBase *pWeapon = (CWeaponDODBase *)Weapon_GetSlot( WPN_SLOT_PRIMARY ); |
|
|
|
if( pWeapon ) |
|
{ |
|
DODWeaponDrop( pWeapon, false ); |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
bool CDODPlayer::DODWeaponDrop( CBaseCombatWeapon *pWeapon, bool bThrowForward ) |
|
{ |
|
bool bSuccess = false; |
|
|
|
if ( pWeapon ) |
|
{ |
|
Vector vForward; |
|
|
|
AngleVectors( EyeAngles(), &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 ); |
|
|
|
CWeaponDODBase *pDODWeapon = dynamic_cast< CWeaponDODBase* >( pWeapon ); |
|
|
|
if( pDODWeapon ) |
|
{ |
|
pDODWeapon->SetDieThink(true); |
|
|
|
pDODWeapon->SetWeaponModelIndex( pDODWeapon->GetDODWpnData().szWorldModel ); |
|
|
|
//Find out the index of the ammo type |
|
int iAmmoIndex = pDODWeapon->GetPrimaryAmmoType(); |
|
|
|
//If it has an ammo type, find out how much the player has |
|
if( iAmmoIndex != -1 ) |
|
{ |
|
int iAmmoToDrop = GetAmmoCount( iAmmoIndex ); |
|
|
|
//Add this much to the dropped weapon |
|
pDODWeapon->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; |
|
|
|
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 ); |
|
|
|
// trace the bounding box of the weapon in the desired position |
|
trace_t tr; |
|
Vector mins, maxs; |
|
|
|
// not exactly correct bounds, we haven't rotated them to match the attachment |
|
pWeapon->CollisionProp()->WorldSpaceSurroundingBounds( &mins, &maxs ); |
|
|
|
UTIL_TraceHull( WorldSpaceCenter(), origin, mins, maxs, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr ); |
|
|
|
if ( tr.fraction < 1.0 ) |
|
origin = WorldSpaceCenter(); |
|
|
|
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 ); |
|
} |
|
|
|
m_hLastDroppedWeapon = pWeapon; |
|
} |
|
|
|
if ( !GetActiveWeapon() ) |
|
{ |
|
// we haven't auto-switched to anything usable |
|
// switch to the first weapon we find, even if its empty |
|
|
|
CBaseCombatWeapon *pCheck; |
|
|
|
for ( int i = 0 ; i < WeaponCount(); ++i ) |
|
{ |
|
pCheck = GetWeapon( i ); |
|
if ( !pCheck || !pCheck->CanDeploy() ) |
|
{ |
|
continue; |
|
} |
|
|
|
Weapon_Switch( pCheck ); |
|
break; |
|
} |
|
} |
|
|
|
bSuccess = true; |
|
} |
|
|
|
return bSuccess; |
|
} |
|
|
|
extern int g_iHelmetModels[NUM_HELMETS]; |
|
|
|
void CDODPlayer::PopHelmet( Vector vecDir, Vector vecForceOrigin ) |
|
{ |
|
int iPlayerClass = m_Shared.PlayerClass(); |
|
|
|
CDODTeam *pTeam = GetGlobalDODTeam( GetTeamNumber() ); |
|
const CDODPlayerClassInfo &pClassInfo = pTeam->GetPlayerClassInfo( iPlayerClass ); |
|
|
|
// See if they already lost their helmet |
|
if( GetBodygroup( BODYGROUP_HELMET ) == pClassInfo.m_iHairGroup ) |
|
return; |
|
|
|
// Nope.. take it off |
|
SetBodygroup( BODYGROUP_HELMET, pClassInfo.m_iHairGroup ); |
|
|
|
// Add the velocity of the player |
|
vecDir += GetAbsVelocity(); |
|
|
|
//CDisablePredictionFiltering disabler; |
|
|
|
EntityMessageBegin( this, true ); |
|
WRITE_BYTE( DOD_PLAYER_POP_HELMET ); |
|
WRITE_VEC3COORD( vecDir ); |
|
WRITE_VEC3COORD( vecForceOrigin ); |
|
WRITE_SHORT( g_iHelmetModels[pClassInfo.m_iDropHelmet] ); |
|
MessageEnd(); |
|
} |
|
|
|
bool CDODPlayer::BumpWeapon( CBaseCombatWeapon *pBaseWeapon ) |
|
{ |
|
CWeaponDODBase *pWeapon = dynamic_cast< CWeaponDODBase* >( pBaseWeapon ); |
|
if ( !pWeapon ) |
|
{ |
|
Assert( false ); |
|
return false; |
|
} |
|
|
|
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; |
|
} |
|
|
|
// Don't let the player fetch weapons through walls |
|
if( !pWeapon->FVisible( this ) && !(GetFlags() & FL_NOTARGET) ) |
|
{ |
|
return false; |
|
} |
|
|
|
/* |
|
CBaseCombatWeapon *pOwnedWeapon = Weapon_OwnsThisType( pWeapon->GetClassname() ); |
|
|
|
if( pOwnedWeapon != NULL && ( pWeapon->GetWpnData().iFlags & ITEM_FLAG_EXHAUSTIBLE ) ) |
|
{ |
|
// its an item that we can hold several of, and use up |
|
|
|
// Just give one ammo |
|
if( GiveAmmo( 1, pOwnedWeapon->GetPrimaryAmmoType(), true ) > 0 ) |
|
return true; |
|
else |
|
return false; |
|
} |
|
*/ |
|
|
|
// ---------------------------------------- |
|
// If I already have it just take the ammo |
|
// ---------------------------------------- |
|
if (Weapon_OwnsThisType( pWeapon->GetClassname(), pWeapon->GetSubType())) |
|
{ |
|
if( Weapon_EquipAmmoOnly( pWeapon ) ) |
|
{ |
|
// Only remove me if I have no ammo left |
|
if ( pWeapon->HasPrimaryAmmo() ) |
|
return false; |
|
|
|
UTIL_Remove( pWeapon ); |
|
return true; |
|
} |
|
else |
|
{ |
|
return false; |
|
} |
|
} |
|
|
|
pWeapon->CheckRespawn(); |
|
|
|
pWeapon->AddSolidFlags( FSOLID_NOT_SOLID ); |
|
pWeapon->AddEffects( EF_NODRAW ); |
|
|
|
Weapon_Equip( pWeapon ); |
|
|
|
int iExtraAmmo = pWeapon->GetExtraAmmoCount(); |
|
|
|
if( iExtraAmmo ) |
|
{ |
|
//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 ); |
|
} |
|
} |
|
|
|
return true; |
|
} |
|
|
|
|
|
bool CDODPlayer::HandleCommand_JoinClass( int iClass ) |
|
{ |
|
Assert( GetTeamNumber() != TEAM_SPECTATOR ); |
|
Assert( GetTeamNumber() != TEAM_UNASSIGNED ); |
|
|
|
if( GetTeamNumber() == TEAM_SPECTATOR ) |
|
return false; |
|
|
|
if( iClass == PLAYERCLASS_UNDEFINED ) |
|
return false; //they typed in something weird |
|
|
|
int iOldPlayerClass = m_Shared.DesiredPlayerClass(); |
|
|
|
// See if we're joining the class we already are |
|
if( iClass == iOldPlayerClass ) |
|
return true; |
|
|
|
if( !DODGameRules()->IsPlayerClassOnTeam( iClass, GetTeamNumber() ) ) |
|
return false; |
|
|
|
const char *classname = DODGameRules()->GetPlayerClassName( iClass, GetTeamNumber() ); |
|
|
|
if( DODGameRules()->CanPlayerJoinClass( this, iClass ) ) |
|
{ |
|
m_Shared.SetDesiredPlayerClass( iClass ); //real class value is set when the player spawns |
|
|
|
if( State_Get() == STATE_PICKINGCLASS ) |
|
State_Transition( STATE_OBSERVER_MODE ); |
|
|
|
if( iClass == PLAYERCLASS_RANDOM ) |
|
{ |
|
if( IsAlive() ) |
|
{ |
|
ClientPrint(this, HUD_PRINTTALK, "#game_respawn_asrandom" ); |
|
} |
|
else |
|
{ |
|
ClientPrint(this, HUD_PRINTTALK, "#game_spawn_asrandom" ); |
|
} |
|
} |
|
else |
|
{ |
|
if( IsAlive() ) |
|
{ |
|
ClientPrint(this, HUD_PRINTTALK, "#game_respawn_as", classname ); |
|
} |
|
else |
|
{ |
|
ClientPrint(this, HUD_PRINTTALK, "#game_spawn_as", classname ); |
|
} |
|
} |
|
|
|
IGameEvent * event = gameeventmanager->CreateEvent( "player_changeclass" ); |
|
if ( event ) |
|
{ |
|
event->SetInt( "userid", GetUserID() ); |
|
event->SetInt( "class", iClass ); |
|
|
|
gameeventmanager->FireEvent( event ); |
|
} |
|
|
|
TallyLatestTimePlayedPerClass( GetTeamNumber(), iOldPlayerClass ); |
|
|
|
// if we're near a respawn point and can see it, just spawn us right away |
|
if ( ShouldInstantRespawn() ) |
|
{ |
|
DODRespawn(); |
|
m_fNextSuicideTime = gpGlobals->curtime + 1.0; |
|
|
|
// if we dropped our primary weapon, and noone else picked it up, destroy it |
|
CBaseCombatWeapon *pWeapon = dynamic_cast<CBaseCombatWeapon *>( m_hLastDroppedWeapon.Get() ); |
|
if ( pWeapon ) |
|
{ |
|
if ( !pWeapon->GetOwner() ) |
|
{ |
|
UTIL_Remove( m_hLastDroppedWeapon.Get() ); |
|
} |
|
} |
|
|
|
// if we dropped our primary weapon, and noone else picked it up, destroy it |
|
CAmmoBox *pAmmo = dynamic_cast<CAmmoBox *>( m_hLastDroppedAmmoBox.Get() ); |
|
if ( pAmmo ) |
|
{ |
|
UTIL_Remove( pAmmo ); |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
ClientPrint(this, HUD_PRINTTALK, "#game_class_limit", classname ); |
|
ShowClassSelectMenu(); |
|
} |
|
|
|
// if they missed the last wave while choosing class |
|
// then restart it now |
|
DODGameRules()->CreateOrJoinRespawnWave( this ); |
|
|
|
return true; |
|
} |
|
|
|
void CDODPlayer::ShowClassSelectMenu() |
|
{ |
|
if ( GetTeamNumber() == TEAM_ALLIES ) |
|
{ |
|
ShowViewPortPanel( PANEL_CLASS_ALLIES ); |
|
} |
|
else if ( GetTeamNumber() == TEAM_AXIS ) |
|
{ |
|
ShowViewPortPanel( PANEL_CLASS_AXIS ); |
|
} |
|
} |
|
|
|
void CDODPlayer::CheckRotateIntroCam( void ) |
|
{ |
|
// Update whatever intro camera it's at. |
|
if( m_pIntroCamera && (gpGlobals->curtime >= m_fIntroCamTime) ) |
|
{ |
|
MoveToNextIntroCamera(); |
|
} |
|
} |
|
|
|
int CDODPlayer::GetHealthAsString( char *pDest, int iDestSize ) |
|
{ |
|
Q_snprintf( pDest, iDestSize, "%d", GetHealth() ); |
|
return Q_strlen(pDest); |
|
} |
|
|
|
int CDODPlayer::GetLastPlayerIDAsString( char *pDest, int iDestSize ) |
|
{ |
|
Q_snprintf( pDest, iDestSize, "last player id'd" ); |
|
return Q_strlen(pDest); |
|
} |
|
|
|
int CDODPlayer::GetClosestPlayerHealthAsString( char *pDest, int iDestSize ) |
|
{ |
|
Q_snprintf( pDest, iDestSize, "some guy's health" ); |
|
return Q_strlen(pDest); |
|
} |
|
|
|
int CDODPlayer::GetPlayerClassAsString( char *pDest, int iDestSize ) |
|
{ |
|
int team = GetTeamNumber(); |
|
|
|
if( team == TEAM_ALLIES || team == TEAM_AXIS ) |
|
{ |
|
const char *pszClassName = DODGameRules()->GetPlayerClassName( m_Shared.PlayerClass(), GetTeamNumber() ); |
|
|
|
Q_snprintf( pDest, iDestSize, "%s", pszClassName ); |
|
return Q_strlen(pDest); |
|
} |
|
else |
|
{ |
|
pDest[0] = '\0'; |
|
return 0; |
|
} |
|
} |
|
|
|
int CDODPlayer::GetNearestLocationAsString( char *pDest, int iDestSize ) |
|
{ |
|
float flMinDist = FLT_MAX; |
|
float flDist; |
|
|
|
const char *pLocationName = ""; |
|
|
|
CBaseEntity *pEnt = gEntList.FindEntityByClassname( NULL, "dod_control_point" ); |
|
|
|
while( pEnt ) |
|
{ |
|
Vector vecDelta = GetAbsOrigin() - pEnt->GetAbsOrigin(); |
|
flDist = vecDelta.Length(); |
|
|
|
if( flDist < flMinDist ) |
|
{ |
|
CControlPoint *pPoint = static_cast< CControlPoint* >( pEnt ); |
|
pLocationName = pPoint->GetName(); |
|
flMinDist = flDist; |
|
} |
|
|
|
pEnt = gEntList.FindEntityByClassname( pEnt, "dod_control_point" ); |
|
} |
|
|
|
pEnt = gEntList.FindEntityByClassname( NULL, "dod_location" ); |
|
|
|
while( pEnt ) |
|
{ |
|
Vector vecDelta = GetAbsOrigin() - pEnt->GetAbsOrigin(); |
|
flDist = vecDelta.Length(); |
|
|
|
if( flDist < flMinDist ) |
|
{ |
|
CDODLocation *pPoint = static_cast< CDODLocation* >( pEnt ); |
|
pLocationName = pPoint->GetName(); |
|
flMinDist = flDist; |
|
} |
|
|
|
pEnt = gEntList.FindEntityByClassname( pEnt, "dod_location" ); |
|
} |
|
|
|
Q_snprintf( pDest, iDestSize, "%s", pLocationName ); |
|
return Q_strlen(pDest); |
|
} |
|
|
|
int CDODPlayer::GetTimeleftAsString( char *pDest, int iDestSize ) |
|
{ |
|
if( !DODGameRules()->IsGameUnderTimeLimit() ) |
|
{ |
|
Q_snprintf( pDest, iDestSize, "-:--" ); |
|
return 4; |
|
} |
|
else |
|
{ |
|
|
|
int iTimeLeft = DODGameRules()->GetTimeLeft(); |
|
|
|
if ( iTimeLeft < 0 ) |
|
{ |
|
Q_snprintf( pDest, iDestSize, "0:00" ); |
|
return 4; |
|
} |
|
|
|
int iMinutes = iTimeLeft / 60; |
|
int iSeconds = iTimeLeft % 60; |
|
|
|
Q_snprintf( pDest, iDestSize, "%d:%.02d", iMinutes, iSeconds ); |
|
return Q_strlen(pDest); |
|
} |
|
} |
|
|
|
// Copy the result into pDest |
|
// return value is number of chars copied |
|
int CDODPlayer::GetStringForEscapeSequence( char c, char *pDest, int iDestSize ) |
|
{ |
|
int iCharsCopied = 0; |
|
|
|
switch( c ) |
|
{ |
|
case 't': |
|
iCharsCopied = GetTimeleftAsString( pDest, iDestSize ); |
|
break; |
|
case 'h': |
|
iCharsCopied = GetHealthAsString( pDest, iDestSize ); |
|
break; |
|
case 'l': |
|
iCharsCopied = GetNearestLocationAsString( pDest, iDestSize ); |
|
break; |
|
case 'c': |
|
iCharsCopied = GetPlayerClassAsString( pDest, iDestSize ); |
|
break; |
|
/*case 'i': |
|
iCharsCopied = GetLastPlayerIDAsString( pDest, iDestSize ); |
|
break; |
|
case 'r': |
|
iCharsCopied = GetClosestPlayerHealthAsString( pDest, iDestSize ); |
|
break; |
|
*/ |
|
default: |
|
iCharsCopied = 0; |
|
break; |
|
} |
|
|
|
return iCharsCopied; |
|
} |
|
|
|
void CDODPlayer::CheckChatText( char *p, int bufsize ) |
|
{ |
|
//Look for escape sequences and replace |
|
|
|
char *buf = new char[bufsize]; |
|
int pos = 0; |
|
|
|
// Parse say text for escape sequences |
|
for ( char *pSrc = p; pSrc != NULL && *pSrc != 0 && pos < bufsize-1; pSrc++ ) |
|
{ |
|
if ( *pSrc == '%' ) |
|
{ |
|
char szSubst[32]; |
|
|
|
int iResult = GetStringForEscapeSequence( *(pSrc+1), szSubst, sizeof(szSubst) ); |
|
|
|
if( iResult ) |
|
{ |
|
buf[pos] = '\0'; |
|
Q_strncat( buf, szSubst, bufsize, COPY_ALL_CHARACTERS ); |
|
|
|
pos = MIN( pos + Q_strlen(szSubst), bufsize-1 ); |
|
|
|
pSrc++; |
|
} |
|
else |
|
{ |
|
//just copy in the '%' |
|
buf[pos] = *pSrc; |
|
pos++; |
|
} |
|
} |
|
else |
|
{ |
|
// copy each char across |
|
buf[pos] = *pSrc; |
|
pos++; |
|
} |
|
} |
|
|
|
buf[pos] = '\0'; |
|
|
|
// copy buf back into p |
|
Q_strncpy( p, buf, bufsize ); |
|
|
|
delete[] buf; |
|
|
|
const char *pReadyCheck = p; |
|
|
|
DODGameRules()->CheckChatForReadySignal( this, pReadyCheck ); |
|
} |
|
|
|
bool CDODPlayer::ClientCommand( const CCommand &args ) |
|
{ |
|
const char *pcmd = args[0]; |
|
if ( FStrEq( pcmd, "jointeam" ) ) |
|
{ |
|
if ( args.ArgC() < 2 ) |
|
{ |
|
Warning( "Player sent bad jointeam syntax\n" ); |
|
} |
|
|
|
int iTeam = atoi( args[1] ); |
|
HandleCommand_JoinTeam( iTeam ); |
|
return true; |
|
} |
|
else if( !Q_strncmp( pcmd, "cls_", 4 ) ) |
|
{ |
|
CDODTeam *pTeam = GetGlobalDODTeam( GetTeamNumber() ); |
|
|
|
Assert( pTeam ); |
|
|
|
int iClassIndex = PLAYERCLASS_UNDEFINED; |
|
|
|
if( pTeam->IsClassOnTeam( pcmd, iClassIndex ) ) |
|
{ |
|
HandleCommand_JoinClass( iClassIndex ); |
|
} |
|
else |
|
{ |
|
DevMsg( "player tried to join a class that isn't on this team ( %s )\n", pcmd ); |
|
ShowClassSelectMenu(); |
|
} |
|
return true; |
|
} |
|
else if ( FStrEq( pcmd, "spectate" ) ) |
|
{ |
|
// 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" ); |
|
} |
|
|
|
int iClass = atoi( args[1] ); |
|
HandleCommand_JoinClass( iClass ); |
|
return true; |
|
} |
|
else if ( FStrEq( pcmd, "dropammo" ) ) |
|
{ |
|
DropGenericAmmo(); |
|
return true; |
|
} |
|
else if ( FStrEq( pcmd, "drop" ) ) |
|
{ |
|
DropActiveWeapon(); |
|
return true; |
|
} |
|
else if( !Q_strncmp( pcmd, "voice_", 6 ) ) |
|
{ |
|
HandleCommand_Voice( pcmd ); |
|
return true; |
|
} |
|
else if( !Q_strncmp( pcmd, "signal_", 7 ) ) |
|
{ |
|
HandleCommand_HandSignal( pcmd ); |
|
return true; |
|
} |
|
else if ( FStrEq( pcmd, "extendfreeze" ) ) |
|
{ |
|
m_flDeathTime += 2.0f; |
|
return true; |
|
} |
|
|
|
#if defined ( DEBUG ) |
|
else if ( FStrEq( pcmd, "debug" ) ) |
|
{ |
|
DODGameRules()->WriteStatsFile( "1.xml" ); |
|
return true; |
|
} |
|
else if ( FStrEq( pcmd, "test_stun" ) ) |
|
{ |
|
Vector vecForward; |
|
AngleVectors( EyeAngles(), &vecForward ); |
|
|
|
trace_t tr; |
|
UTIL_TraceLine( EyePosition(), EyePosition() + vecForward * 1000, MASK_SOLID, NULL, &tr ); |
|
|
|
float flStunAmount = 100; |
|
float flStunRadius = 100; |
|
|
|
if ( args.ArgC() > 1 ) |
|
{ |
|
flStunAmount = atof( args[1] ); |
|
} |
|
|
|
if ( args.ArgC() > 2 ) |
|
{ |
|
flStunRadius = atof( args[2] ); |
|
} |
|
|
|
if ( tr.fraction < 1.0 ) |
|
{ |
|
CTakeDamageInfo info( this, this, vec3_origin, tr.endpos, flStunAmount, DMG_STUN ); |
|
DODGameRules()->RadiusStun( info, tr.endpos, flStunRadius ); |
|
} |
|
|
|
return true; |
|
} |
|
else if ( FStrEq( pcmd, "switch_prim" ) ) |
|
{ |
|
CWeaponDODBase *pWpn = (CWeaponDODBase *)Weapon_GetSlot( WPN_SLOT_PRIMARY ); |
|
|
|
Weapon_Switch( pWpn ); |
|
return true; |
|
} |
|
else if ( FStrEq( pcmd, "switch_sec" ) ) |
|
{ |
|
CWeaponDODBase *pWpn = (CWeaponDODBase *)Weapon_GetSlot( WPN_SLOT_SECONDARY ); |
|
|
|
Weapon_Switch( pWpn ); |
|
return true; |
|
} |
|
else if ( FStrEq( pcmd, "switch_melee" ) ) |
|
{ |
|
CWeaponDODBase *pWpn = (CWeaponDODBase *)Weapon_GetSlot( WPN_SLOT_MELEE ); |
|
|
|
Weapon_Switch( pWpn ); |
|
return true; |
|
} |
|
else if ( FStrEq( pcmd, "switch_gren" ) ) |
|
{ |
|
CWeaponDODBase *pWpn = (CWeaponDODBase *)Weapon_GetSlot( WPN_SLOT_GRENADES ); |
|
|
|
Weapon_Switch( pWpn ); |
|
return true; |
|
} |
|
else if ( FStrEq( pcmd, "switch_tnt" ) ) |
|
{ |
|
CWeaponDODBase *pWpn = (CWeaponDODBase *)Weapon_GetSlot( WPN_SLOT_BOMB ); |
|
|
|
Weapon_Switch( pWpn ); |
|
return true; |
|
} |
|
else if ( FStrEq( pcmd, "nextwpn" ) ) |
|
{ |
|
CBaseCombatWeapon *pWpn = GetActiveWeapon(); |
|
|
|
SwitchToNextBestWeapon( pWpn ); |
|
return true; |
|
} |
|
else if ( FStrEq( pcmd, "smoke" ) ) |
|
{ |
|
CWeaponDODBase *pWpn = (CWeaponDODBase *)Weapon_GetSlot( 3 ); |
|
|
|
Weapon_Switch( pWpn ); |
|
return true; |
|
} |
|
else if ( FStrEq( pcmd, "resetents" ) ) |
|
{ |
|
DODGameRules()->CleanUpMap(); |
|
return true; |
|
} |
|
else if( FStrEq( pcmd, "pop" ) ) |
|
{ |
|
Vector vecDir(0,0,200); |
|
|
|
if ( args.ArgC() > 1 ) |
|
{ |
|
vecDir.z = atof( args[1] ); |
|
} |
|
|
|
PopHelmet( vecDir, vec3_origin ); |
|
return true; |
|
} |
|
else if ( FStrEq( pcmd, "bandage" ) ) |
|
{ |
|
if ( sv_cheats->GetBool() ) |
|
{ |
|
Bandage(); |
|
} |
|
return true; |
|
} |
|
else if ( FStrEq( pcmd, "printstats" ) ) |
|
{ |
|
PrintLifetimeStats(); |
|
return true; |
|
} |
|
|
|
#endif //_DEBUG |
|
|
|
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 CDODPlayer::HandleCommand_JoinTeam( int team ) |
|
{ |
|
CDODGameRules *mp = DODGameRules(); |
|
int iOldTeam = GetTeamNumber(); |
|
int iOldPlayerClass = m_Shared.DesiredPlayerClass(); |
|
|
|
if ( !GetGlobalTeam( team ) ) |
|
{ |
|
Warning( "HandleCommand_JoinTeam( %d ) - invalid team index.\n", team ); |
|
return false; |
|
} |
|
|
|
// If we already died and changed teams once, deny |
|
if( m_bTeamChanged && team != TEAM_SPECTATOR && iOldTeam != TEAM_SPECTATOR ) |
|
{ |
|
ClientPrint( this, HUD_PRINTCENTER, "game_switch_teams_once" ); |
|
return true; |
|
} |
|
|
|
if ( team == TEAM_UNASSIGNED ) |
|
{ |
|
// Attempt to auto-select a team, may set team to T, CT or SPEC |
|
team = mp->SelectDefaultTeam(); |
|
|
|
if ( team == TEAM_UNASSIGNED ) |
|
{ |
|
// still team unassigned, try to kick a bot if possible |
|
|
|
ClientPrint( this, HUD_PRINTTALK, "#All_Teams_Full" ); |
|
|
|
team = TEAM_SPECTATOR; |
|
} |
|
} |
|
|
|
if ( team == iOldTeam ) |
|
return true; // we wouldn't change the team |
|
|
|
if ( mp->TeamFull( team ) ) |
|
{ |
|
if ( team == TEAM_ALLIES ) |
|
{ |
|
ClientPrint( this, HUD_PRINTTALK, "#Allies_Full" ); |
|
} |
|
else if ( team == TEAM_AXIS ) |
|
{ |
|
ClientPrint( this, HUD_PRINTTALK, "#Axis_Full" ); |
|
} |
|
|
|
ShowViewPortPanel( PANEL_TEAM ); |
|
return false; |
|
} |
|
|
|
if ( team == TEAM_SPECTATOR ) |
|
{ |
|
// Prevent this if the cvar is set |
|
if ( !mp_allowspectators.GetInt() && !IsHLTV() ) |
|
{ |
|
ClientPrint( this, HUD_PRINTTALK, "#Cannot_Be_Spectator" ); |
|
ShowViewPortPanel( PANEL_TEAM ); |
|
return false; |
|
} |
|
|
|
ChangeTeam( TEAM_SPECTATOR ); |
|
|
|
return true; |
|
} |
|
|
|
// If the code gets this far, the team is not TEAM_UNASSIGNED |
|
|
|
// Player is switching to a new team (It is possible to switch to the |
|
// same team just to choose a new appearance) |
|
|
|
if (mp->TeamStacked( team, GetTeamNumber() ))//players are allowed to change to their own team so they can just change their model |
|
{ |
|
// The specified team is full |
|
ClientPrint( |
|
this, |
|
HUD_PRINTCENTER, |
|
( team == TEAM_ALLIES ) ? "#Allies_full" : "#Axis_full" ); |
|
|
|
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 ); |
|
|
|
DODGameRules()->CreateOrJoinRespawnWave( this ); |
|
|
|
// Force them to choose a new class |
|
m_Shared.SetDesiredPlayerClass( PLAYERCLASS_UNDEFINED ); |
|
m_Shared.SetPlayerClass( PLAYERCLASS_UNDEFINED ); |
|
|
|
TallyLatestTimePlayedPerClass( iOldTeam, iOldPlayerClass ); |
|
|
|
return true; |
|
} |
|
|
|
void CDODPlayer::State_Transition( DODPlayerState newState ) |
|
{ |
|
State_Leave(); |
|
State_Enter( newState ); |
|
} |
|
|
|
|
|
void CDODPlayer::State_Enter( DODPlayerState newState ) |
|
{ |
|
m_iPlayerState = newState; |
|
m_pCurStateInfo = State_LookupInfo( newState ); |
|
|
|
if ( dod_playerstatetransitions.GetInt() == -1 || dod_playerstatetransitions.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 CDODPlayer::State_Leave() |
|
{ |
|
if ( m_pCurStateInfo && m_pCurStateInfo->pfnLeaveState ) |
|
{ |
|
(this->*m_pCurStateInfo->pfnLeaveState)(); |
|
} |
|
} |
|
|
|
|
|
void CDODPlayer::State_PreThink() |
|
{ |
|
if ( m_pCurStateInfo && m_pCurStateInfo->pfnPreThink ) |
|
{ |
|
(this->*m_pCurStateInfo->pfnPreThink)(); |
|
} |
|
} |
|
|
|
|
|
CDODPlayerStateInfo* CDODPlayer::State_LookupInfo( DODPlayerState state ) |
|
{ |
|
// This table MUST match the |
|
static CDODPlayerStateInfo playerStateInfos[] = |
|
{ |
|
{ STATE_ACTIVE, "STATE_ACTIVE", &CDODPlayer::State_Enter_ACTIVE, NULL, &CDODPlayer::State_PreThink_ACTIVE }, |
|
{ STATE_WELCOME, "STATE_WELCOME", &CDODPlayer::State_Enter_WELCOME, NULL, &CDODPlayer::State_PreThink_WELCOME }, |
|
{ STATE_PICKINGTEAM, "STATE_PICKINGTEAM", &CDODPlayer::State_Enter_PICKINGTEAM, NULL, &CDODPlayer::State_PreThink_PICKING }, |
|
{ STATE_PICKINGCLASS, "STATE_PICKINGCLASS", &CDODPlayer::State_Enter_PICKINGCLASS, NULL, &CDODPlayer::State_PreThink_PICKING }, |
|
{ STATE_DEATH_ANIM, "STATE_DEATH_ANIM", &CDODPlayer::State_Enter_DEATH_ANIM, NULL, &CDODPlayer::State_PreThink_DEATH_ANIM }, |
|
{ STATE_OBSERVER_MODE, "STATE_OBSERVER_MODE", &CDODPlayer::State_Enter_OBSERVER_MODE, NULL, &CDODPlayer::State_PreThink_OBSERVER_MODE } |
|
}; |
|
|
|
for ( int i=0; i < ARRAYSIZE( playerStateInfos ); i++ ) |
|
{ |
|
if ( playerStateInfos[i].m_iPlayerState == state ) |
|
return &playerStateInfos[i]; |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
void CDODPlayer::PhysObjectSleep() |
|
{ |
|
IPhysicsObject *pObj = VPhysicsGetObject(); |
|
if ( pObj ) |
|
pObj->Sleep(); |
|
} |
|
|
|
void CDODPlayer::PhysObjectWake() |
|
{ |
|
IPhysicsObject *pObj = VPhysicsGetObject(); |
|
if ( pObj ) |
|
pObj->Wake(); |
|
} |
|
|
|
void CDODPlayer::State_Enter_WELCOME() |
|
{ |
|
SetMoveType( MOVETYPE_NONE ); |
|
AddSolidFlags( FSOLID_NOT_SOLID ); |
|
|
|
PhysObjectSleep(); |
|
|
|
// Show info panel |
|
if ( IsBot() ) |
|
{ |
|
// If they want to auto join a team for debugging, pretend they clicked the button. |
|
CCommand args; |
|
args.Tokenize( "joingame" ); |
|
ClientCommand( args ); |
|
} |
|
else |
|
{ |
|
const ConVar *hostname = cvar->FindVar( "hostname" ); |
|
const char *title = (hostname) ? hostname->GetString() : "MESSAGE OF THE DAY"; |
|
|
|
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_CHANGETEAM ); // exec this command if panel closed |
|
data->SetBool( "unload", sv_motd_unload_on_dismissal.GetBool() ); |
|
|
|
ShowViewPortPanel( PANEL_INFO, true, data ); |
|
|
|
data->deleteThis(); |
|
} |
|
} |
|
|
|
|
|
void CDODPlayer::State_PreThink_WELCOME() |
|
{ |
|
// Verify some state. |
|
Assert( GetMoveType() == MOVETYPE_NONE ); |
|
Assert( IsSolidFlagSet( FSOLID_NOT_SOLID ) ); |
|
Assert( GetAbsVelocity().Length() == 0 ); |
|
|
|
CheckRotateIntroCam(); |
|
} |
|
|
|
|
|
void CDODPlayer::State_Enter_PICKINGTEAM() |
|
{ |
|
ShowViewPortPanel( PANEL_TEAM ); |
|
} |
|
|
|
void CDODPlayer::State_PreThink_PICKING() |
|
{ |
|
} |
|
|
|
void CDODPlayer::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; |
|
|
|
StartObserverMode( OBS_MODE_DEATHCAM ); // go to observer mode |
|
|
|
RemoveEffects( EF_NODRAW ); // still draw player body |
|
|
|
DODGameRules()->CreateOrJoinRespawnWave( this ); |
|
|
|
m_bAbortFreezeCam = false; |
|
|
|
m_bPlayedFreezeCamSound = false; |
|
} |
|
|
|
#define DOD_DEATH_ANIMATION_TIME 1.6f |
|
#define DOD_FREEZECAM_LENGTH 3.4f |
|
|
|
void CDODPlayer::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 ); |
|
} |
|
} |
|
|
|
if ( dod_freezecam.GetBool() ) |
|
{ |
|
// important! freeze end time must match DEATH_CAM_TIME |
|
// or else players will start missing respawn waves. |
|
|
|
float flFreezeEnd = (m_flDeathTime + DOD_DEATH_ANIMATION_TIME + DOD_FREEZECAM_LENGTH ); |
|
|
|
if ( !m_bPlayedFreezeCamSound && GetObserverTarget() && GetObserverTarget() != this ) |
|
{ |
|
// Start the sound so that it ends at the freezecam lock on time |
|
float flFreezeSoundLength = 0.3; |
|
float flFreezeSoundTime = m_flDeathTime + DOD_DEATH_ANIMATION_TIME - flFreezeSoundLength + 0.4f /* travel time */; |
|
if ( gpGlobals->curtime >= flFreezeSoundTime ) |
|
{ |
|
CSingleUserRecipientFilter filter( this ); |
|
EmitSound_t params; |
|
params.m_flSoundTime = 0; |
|
params.m_pSoundName = "Player.FreezeCam"; |
|
EmitSound( filter, entindex(), params ); |
|
|
|
m_bPlayedFreezeCamSound = true; |
|
} |
|
} |
|
|
|
if ( gpGlobals->curtime >= (m_flDeathTime + DOD_DEATH_ANIMATION_TIME ) ) // allow 2 seconds death animation / death cam |
|
{ |
|
if ( GetObserverTarget() && GetObserverTarget() != this ) |
|
{ |
|
if ( !m_bAbortFreezeCam && gpGlobals->curtime < flFreezeEnd ) |
|
{ |
|
if ( GetObserverMode() != OBS_MODE_FREEZECAM ) |
|
{ |
|
StartObserverMode( OBS_MODE_FREEZECAM ); |
|
PhysObjectSleep(); |
|
} |
|
return; |
|
} |
|
} |
|
|
|
if ( GetObserverMode() == OBS_MODE_FREEZECAM ) |
|
{ |
|
// If we're in freezecam, and we want out, abort. |
|
if ( m_bAbortFreezeCam ) |
|
{ |
|
m_lifeState = LIFE_DEAD; |
|
|
|
StopAnimation(); |
|
|
|
IncrementInterpolationFrame(); |
|
|
|
if ( GetMoveType() != MOVETYPE_NONE && (GetFlags() & FL_ONGROUND) ) |
|
SetMoveType( MOVETYPE_NONE ); |
|
|
|
State_Transition( STATE_OBSERVER_MODE ); |
|
} |
|
} |
|
|
|
// Don't allow anyone to respawn until freeze time is over, even if they're not |
|
// in freezecam. This prevents players skipping freezecam to spawn faster. |
|
|
|
if ( gpGlobals->curtime >= flFreezeEnd ) |
|
{ |
|
m_lifeState = LIFE_DEAD; |
|
|
|
StopAnimation(); |
|
|
|
IncrementInterpolationFrame(); |
|
|
|
if ( GetMoveType() != MOVETYPE_NONE && (GetFlags() & FL_ONGROUND) ) |
|
SetMoveType( MOVETYPE_NONE ); |
|
|
|
State_Transition( STATE_OBSERVER_MODE ); |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
if ( gpGlobals->curtime >= (m_flDeathTime + DEATH_CAM_TIME ) ) // allow x seconds death animation / death cam |
|
{ |
|
m_lifeState = LIFE_DEAD; |
|
|
|
StopAnimation(); |
|
|
|
IncrementInterpolationFrame(); |
|
|
|
if ( GetMoveType() != MOVETYPE_NONE && (GetFlags() & FL_ONGROUND) ) |
|
SetMoveType( MOVETYPE_NONE ); |
|
|
|
// Disabled death cam! |
|
//StartReplayMode( 8, 8, entindex() ); |
|
|
|
State_Transition( STATE_OBSERVER_MODE ); |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CDODPlayer::AttemptToExitFreezeCam( void ) |
|
{ |
|
float flFreezeTravelTime = (m_flDeathTime + DOD_DEATH_ANIMATION_TIME ) + 0.4 /*spec_freeze_traveltime.GetFloat()*/ + 0.5; |
|
if ( gpGlobals->curtime < flFreezeTravelTime ) |
|
return; |
|
|
|
m_bAbortFreezeCam = true; |
|
} |
|
|
|
void CDODPlayer::State_Enter_OBSERVER_MODE() |
|
{ |
|
// Always start a spectator session in chase mode |
|
m_iObserverLastMode = OBS_MODE_CHASE; |
|
|
|
if( m_hObserverTarget == NULL ) |
|
{ |
|
// find a new observer target |
|
CheckObserverSettings(); |
|
} |
|
|
|
// Change our observer target to the player nearest us |
|
CTeam *pTeam = GetGlobalTeam( TEAM_ALLIES ); |
|
|
|
CBasePlayer *pPlayer; |
|
Vector localOrigin = GetAbsOrigin(); |
|
Vector targetOrigin; |
|
float flMinDist = FLT_MAX; |
|
float flDist; |
|
|
|
for ( int i=0;i<pTeam->GetNumPlayers();i++ ) |
|
{ |
|
pPlayer = pTeam->GetPlayer(i); |
|
|
|
if ( !pPlayer ) |
|
continue; |
|
|
|
if ( !IsValidObserverTarget(pPlayer) ) |
|
continue; |
|
|
|
targetOrigin = pPlayer->GetAbsOrigin(); |
|
|
|
flDist = ( targetOrigin - localOrigin ).Length(); |
|
|
|
if ( flDist < flMinDist ) |
|
{ |
|
m_hObserverTarget.Set( pPlayer ); |
|
flMinDist = flDist; |
|
} |
|
} |
|
|
|
if ( GetTeamNumber() == TEAM_ALLIES || GetTeamNumber() == TEAM_AXIS ) |
|
{ |
|
// we are entering spec while playing ( not on TEAM_SPECTATOR ) |
|
Hints()->HintMessage( HINT_CLASSMENU, true ); |
|
} |
|
|
|
StartObserverMode( m_iObserverLastMode ); |
|
|
|
PhysObjectSleep(); |
|
} |
|
|
|
void CDODPlayer::State_PreThink_OBSERVER_MODE() |
|
{ |
|
// Make sure nobody has changed any of our state. |
|
Assert( m_takedamage == DAMAGE_NO ); |
|
Assert( IsSolidFlagSet( FSOLID_NOT_SOLID ) ); |
|
|
|
// Must be dead. |
|
Assert( m_lifeState == LIFE_DEAD ); |
|
Assert( pl.deadflag ); |
|
} |
|
|
|
void CDODPlayer::State_Enter_PICKINGCLASS() |
|
{ |
|
// go to spec mode, if dying keep deathcam |
|
if ( GetObserverMode() == OBS_MODE_DEATHCAM ) |
|
{ |
|
StartObserverMode( OBS_MODE_DEATHCAM ); |
|
} |
|
else |
|
{ |
|
StartObserverMode( OBS_MODE_ROAMING ); |
|
} |
|
|
|
PhysObjectSleep(); |
|
|
|
ShowClassSelectMenu(); |
|
} |
|
|
|
void CDODPlayer::State_Enter_ACTIVE() |
|
{ |
|
SetMoveType( MOVETYPE_WALK ); |
|
RemoveSolidFlags( FSOLID_NOT_SOLID ); |
|
m_Local.m_iHideHUD = 0; |
|
PhysObjectWake(); |
|
} |
|
|
|
void CDODPlayer::State_PreThink_ACTIVE() |
|
{ |
|
return; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Initialize prone at spawn. |
|
//----------------------------------------------------------------------------- |
|
void CDODPlayer::InitProne( void ) |
|
{ |
|
m_Shared.SetProne( false, true ); |
|
} |
|
|
|
void CDODPlayer::InitSprinting( void ) |
|
{ |
|
m_Shared.SetSprinting( false ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Returns whether or not we are allowed to sprint now. |
|
//----------------------------------------------------------------------------- |
|
bool CDODPlayer::CanSprint() |
|
{ |
|
return ( |
|
//!IsWalking() && // Not if we're walking |
|
!( m_Local.m_bDucked && !m_Local.m_bDucking ) && // Nor if we're ducking |
|
(GetWaterLevel() != 3) ); // Certainly not underwater |
|
} |
|
|
|
void CDODPlayer::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"); |
|
|
|
if( !m_pIntroCamera ) //if there are no cameras find a spawn point and black out the screen |
|
{ |
|
DODGameRules()->GetPlayerSpawnSpot( this ); |
|
SetAbsAngles( QAngle( 0, 0, 0 ) ); |
|
m_pIntroCamera = NULL; // never update again |
|
} |
|
else |
|
{ |
|
Vector vIntroCamera = m_pIntroCamera->GetAbsOrigin(); |
|
|
|
QAngle CamAngles = m_pIntroCamera->GetAbsAngles(); |
|
|
|
UTIL_SetSize( this, vec3_origin, vec3_origin ); |
|
|
|
SetAbsOrigin( vIntroCamera ); |
|
SetAbsAngles( CamAngles ); |
|
SnapEyeAngles( CamAngles ); |
|
SetViewOffset( vec3_origin ); |
|
m_fIntroCamTime = gpGlobals->curtime + 6; |
|
} |
|
} |
|
|
|
CBaseEntity *CDODPlayer::SelectSpawnSpot( CUtlVector<EHANDLE> *pSpawnPoints, int &iLastSpawnIndex ) |
|
{ |
|
Assert( pSpawnPoints ); |
|
|
|
int iNumSpawns = pSpawnPoints->Count(); |
|
|
|
CBaseEntity *pSpot = NULL; |
|
|
|
for ( int i=0;i<iNumSpawns;i++ ) |
|
{ |
|
int testIndex = ( iLastSpawnIndex + i ) % iNumSpawns; |
|
|
|
EHANDLE handle = pSpawnPoints->Element(testIndex); |
|
|
|
if ( !handle ) |
|
continue; |
|
|
|
pSpot = handle.Get(); |
|
|
|
if ( !pSpot ) |
|
continue; |
|
|
|
if( g_pGameRules->IsSpawnPointValid( pSpot, this ) ) |
|
{ |
|
if ( pSpot->GetAbsOrigin() == Vector( 0, 0, 0 ) ) |
|
{ |
|
continue; |
|
} |
|
|
|
// if so, go to pSpot |
|
iLastSpawnIndex = testIndex; |
|
return pSpot; |
|
} |
|
} |
|
|
|
DevMsg("CDODPlayer::SelectSpawnSpot: couldn't find valid spawn point.\n"); |
|
|
|
// If we get here, all spawn points are blocked. |
|
// Try the 4 locations around each spawn point |
|
|
|
Assert( pSpot ); |
|
|
|
Vector vecForward, vecRight, vecUp; |
|
|
|
Vector mins = VEC_HULL_MIN; |
|
Vector maxs = VEC_HULL_MAX; |
|
float flHalfWidth = maxs.x; |
|
float flTestDist = 3 * flHalfWidth; |
|
|
|
for ( int i=0;i<iNumSpawns;i++ ) |
|
{ |
|
int testIndex = ( iLastSpawnIndex + i ) % iNumSpawns; |
|
|
|
EHANDLE handle = pSpawnPoints->Element(testIndex); |
|
|
|
if ( !handle ) |
|
continue; |
|
|
|
pSpot = handle.Get(); |
|
|
|
if ( !pSpot ) |
|
continue; |
|
|
|
// we know this spot is blocked, but check to the N, E, S, W of it. |
|
|
|
// if we find a clear spot, create a new spawn point there, copied from pSpot |
|
|
|
AngleVectors( pSpot->GetAbsAngles(), &vecForward, &vecRight, &vecUp ); |
|
|
|
for( int i=0;i<4;i++ ) |
|
{ |
|
Vector origin = pSpot->GetAbsOrigin(); |
|
|
|
switch( i ) |
|
{ |
|
case 0: origin += vecForward * flTestDist; break; |
|
case 1: origin += vecRight * flTestDist; break; |
|
case 2: origin -= vecForward * flTestDist; break; |
|
case 3: origin -= vecRight * flTestDist; break; |
|
} |
|
|
|
Vector vTestMins = origin + mins; |
|
Vector vTestMaxs = origin + maxs; |
|
|
|
if ( UTIL_IsSpaceEmpty( this, vTestMins, vTestMaxs ) ) |
|
{ |
|
QAngle spotAngles = pSpot->GetAbsAngles(); |
|
|
|
// make a new spawnpoint so we don't have to do this a bunch of times |
|
pSpot = CreateEntityByName( pSpot->GetClassname() ); |
|
pSpot->SetAbsOrigin( origin ); |
|
pSpot->SetAbsAngles( spotAngles ); |
|
|
|
// delete it in a while so we don't accumulate entities |
|
pSpot->SetThink( &CBaseEntity::SUB_Remove ); |
|
pSpot->SetNextThink( gpGlobals->curtime + 0.5 ); |
|
|
|
Assert( pSpot ); |
|
|
|
iLastSpawnIndex = 0; |
|
|
|
return pSpot; |
|
} |
|
} |
|
} |
|
|
|
// if we get here, we're really screwed. Spawning the person is going to stick them |
|
// into someone or something, but we really tried hard, I swear. |
|
Assert( !"We should never be here" ); |
|
|
|
return NULL; |
|
} |
|
|
|
|
|
CBaseEntity* CDODPlayer::EntSelectSpawnPoint() |
|
{ |
|
CBaseEntity *pSpot = NULL; |
|
|
|
switch( GetTeamNumber() ) |
|
{ |
|
case TEAM_ALLIES: |
|
{ |
|
CUtlVector<EHANDLE> *pSpawnList = DODGameRules()->GetSpawnPointListForTeam( TEAM_ALLIES ); |
|
pSpot = SelectSpawnSpot( pSpawnList, g_iLastAlliesSpawnIndex ); |
|
} |
|
break; |
|
case TEAM_AXIS: |
|
{ |
|
CUtlVector<EHANDLE> *pSpawnList = DODGameRules()->GetSpawnPointListForTeam( TEAM_AXIS ); |
|
pSpot = SelectSpawnSpot( pSpawnList, g_iLastAxisSpawnIndex ); |
|
} |
|
break; |
|
case TEAM_SPECTATOR: |
|
case TEAM_UNASSIGNED: |
|
default: |
|
{ |
|
pSpot = CBaseEntity::Instance( INDEXENT(0) ); |
|
} |
|
break; |
|
} |
|
|
|
if ( !pSpot ) |
|
{ |
|
Warning( "PutClientInServer: no valid spawns on level\n" ); |
|
return CBaseEntity::Instance( INDEXENT(0) ); |
|
} |
|
|
|
return pSpot; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Put the player in the specified team |
|
//----------------------------------------------------------------------------- |
|
void CDODPlayer::ChangeTeam( int iTeamNum ) |
|
{ |
|
if ( !GetGlobalTeam( iTeamNum ) ) |
|
{ |
|
Warning( "CDODPlayer::ChangeTeam( %d ) - invalid team index.\n", iTeamNum ); |
|
return; |
|
} |
|
|
|
int iOldTeam = GetTeamNumber(); |
|
|
|
// if this is our current team, just abort |
|
if ( iTeamNum == iOldTeam ) |
|
return; |
|
|
|
m_bTeamChanged = true; |
|
|
|
// do the team change: |
|
BaseClass::ChangeTeam( iTeamNum ); |
|
|
|
// update client state |
|
if ( iTeamNum == TEAM_UNASSIGNED ) |
|
{ |
|
State_Transition( STATE_OBSERVER_MODE ); |
|
} |
|
else if ( iTeamNum == TEAM_SPECTATOR ) |
|
{ |
|
RemoveAllItems( true ); |
|
|
|
State_Transition( STATE_OBSERVER_MODE ); |
|
|
|
StatEvent_UploadStats(); |
|
m_StatProperty.SetClassAndTeamForThisLife( -1, TEAM_SPECTATOR ); |
|
} |
|
else // active player |
|
{ |
|
if ( !IsDead() ) |
|
{ |
|
// Kill player if switching teams while alive |
|
CommitSuicide(); |
|
|
|
// add 1 to frags to balance out the 1 subtracted for killing yourself |
|
// IncrementFragCount( 1 ); |
|
} |
|
|
|
if( iOldTeam == TEAM_SPECTATOR ) |
|
SetMoveType( MOVETYPE_NONE ); |
|
|
|
// Put up the class selection menu. |
|
State_Transition( STATE_PICKINGCLASS ); |
|
} |
|
|
|
RemoveNemesisRelationships(); |
|
} |
|
|
|
void CDODPlayer::DODRespawn( void ) |
|
{ |
|
// if it's a forced respawn, accumulate the stats now |
|
if ( IsAlive() ) |
|
{ |
|
StatEvent_UploadStats(); |
|
} |
|
|
|
RemoveAllItems(true); |
|
|
|
StopObserverMode(); |
|
|
|
State_Transition( STATE_ACTIVE ); |
|
Spawn(); |
|
m_nButtons = 0; |
|
SetNextThink( TICK_NEVER_THINK ); |
|
|
|
OutputDamageGiven(); |
|
OutputDamageTaken(); |
|
ResetDamageCounters(); |
|
} |
|
|
|
bool CDODPlayer::IsReadyToPlay( void ) |
|
{ |
|
bool bResult = ( ( GetTeamNumber() == TEAM_ALLIES || GetTeamNumber() == TEAM_AXIS ) && |
|
m_Shared.DesiredPlayerClass() != PLAYERCLASS_UNDEFINED ); |
|
|
|
return bResult; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Returns whether or not we can switch to the given weapon. |
|
// Input : pWeapon - |
|
//----------------------------------------------------------------------------- |
|
bool CDODPlayer::Weapon_CanSwitchTo( CBaseCombatWeapon *pWeapon ) |
|
{ |
|
#if !defined( CLIENT_DLL ) |
|
IServerVehicle *pVehicle = GetVehicle(); |
|
#else |
|
IClientVehicle *pVehicle = GetVehicle(); |
|
#endif |
|
|
|
if (pVehicle && !UsingStandardWeaponsInVehicle()) |
|
return false; |
|
|
|
if ( !pWeapon->CanDeploy() ) |
|
return false; |
|
|
|
if ( GetActiveWeapon() ) |
|
{ |
|
if ( !GetActiveWeapon()->CanHolster() ) |
|
return false; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
void CDODPlayer::SetScore( int score ) |
|
{ |
|
m_iScore = score; |
|
} |
|
|
|
void CDODPlayer::AddScore( int num ) |
|
{ |
|
int n = MAX( 0, num ); |
|
m_iScore += n; |
|
} |
|
|
|
void CDODPlayer::HandleSignals( void ) |
|
{ |
|
int changed = m_signals.Update(); |
|
int state = m_signals.GetState(); |
|
|
|
if ( changed & SIGNAL_CAPTUREAREA ) |
|
{ |
|
if ( state & SIGNAL_CAPTUREAREA ) |
|
{ |
|
//do nothing |
|
} |
|
else |
|
{ |
|
SetCapAreaIndex(-1); |
|
SetCPIndex(-1); |
|
m_iCapAreaIconIndex = -1; |
|
} |
|
} |
|
} |
|
|
|
void CDODPlayer::SetCapAreaIndex( int index ) |
|
{ |
|
m_iCapAreaIndex = index; |
|
} |
|
|
|
int CDODPlayer::GetCapAreaIndex( void ) |
|
{ |
|
return m_iCapAreaIndex; |
|
} |
|
|
|
void CDODPlayer::SetCPIndex( int index ) |
|
{ |
|
m_Shared.SetCPIndex( index ); |
|
} |
|
|
|
int CDODPlayer::OnTakeDamage_Alive( const CTakeDamageInfo &info ) |
|
{ |
|
// set damage type sustained |
|
m_bitsDamageType |= info.GetDamageType(); |
|
|
|
int iInitialHealth = m_iHealth; |
|
|
|
if ( !CBaseCombatCharacter::OnTakeDamage_Alive( info ) ) |
|
return 0; |
|
|
|
IGameEvent * event = gameeventmanager->CreateEvent( "player_hurt" ); |
|
|
|
if ( event ) |
|
{ |
|
event->SetInt("userid", GetUserID() ); |
|
event->SetInt("health", MAX(0, m_iHealth) ); |
|
event->SetInt("damage", info.GetDamage() ); |
|
event->SetInt("hitgroup", m_LastHitGroup ); |
|
|
|
CBaseEntity *attacker = info.GetAttacker(); |
|
const char *weaponName = ""; |
|
DODWeaponID weaponID = WEAPON_NONE; |
|
|
|
if ( attacker->IsPlayer() ) |
|
{ |
|
CBasePlayer *player = ToBasePlayer( attacker ); |
|
event->SetInt("attacker", player->GetUserID() ); // hurt by other player |
|
|
|
// ff damage |
|
// no hint for bomb explosions, it was their own fault! |
|
CDODPlayer *pDODPlayer = ToDODPlayer( player ); |
|
if( pDODPlayer->GetTeamNumber() == GetTeamNumber() && pDODPlayer != this && !FBitSet( info.GetDamageType(), DMG_BOMB ) ) |
|
{ |
|
pDODPlayer->Hints()->HintMessage( HINT_FRIEND_INJURED ); |
|
} |
|
|
|
CBaseEntity *pInflictor = info.GetInflictor(); |
|
if ( pInflictor ) |
|
{ |
|
if ( pInflictor == pDODPlayer ) |
|
{ |
|
// If the inflictor is the killer, then it must be their current weapon doing the damage |
|
if ( pDODPlayer->GetActiveWeapon() ) |
|
{ |
|
CWeaponDODBase *pWeapon = pDODPlayer->GetActiveDODWeapon(); |
|
weaponName = pWeapon->GetClassname(); |
|
|
|
if ( info.GetDamageCustom() & MELEE_DMG_SECONDARYATTACK ) |
|
weaponID = pWeapon->GetAltWeaponID(); |
|
else |
|
weaponID = pWeapon->GetStatsWeaponID(); |
|
} |
|
} |
|
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, "rocket_", 7 ) == 0 ) |
|
{ |
|
weaponName += 7; |
|
|
|
CDODBaseRocket *pRocket = dynamic_cast< CDODBaseRocket *>( info.GetInflictor() ); |
|
if ( pRocket ) |
|
{ |
|
weaponID = pRocket->GetEmitterWeaponID(); |
|
} |
|
} |
|
else if ( strncmp( weaponName, "grenade_", 8 ) == 0 ) |
|
{ |
|
weaponName += 8; |
|
|
|
CDODBaseGrenade *pGren = dynamic_cast< CDODBaseGrenade *>( info.GetInflictor() ); |
|
if ( pGren ) |
|
{ |
|
weaponID = pGren->GetEmitterWeaponID(); |
|
} |
|
} |
|
else if ( Q_stricmp( weaponName, "dod_bomb_target" ) == 0 ) |
|
{ |
|
weaponID = WEAPON_NONE; |
|
} |
|
|
|
event->SetString( "weapon", weaponName ); |
|
event->SetInt( "priority", 5 ); |
|
|
|
gameeventmanager->FireEvent( event ); |
|
|
|
#ifndef CLIENT_DLL |
|
IGameEvent * stats_event = gameeventmanager->CreateEvent( "dod_stats_player_damage" ); |
|
if ( stats_event && attacker->IsPlayer() && weaponID != WEAPON_NONE ) |
|
{ |
|
CBasePlayer *pAttacker = ToBasePlayer( attacker ); |
|
|
|
int iDamage = ( info.GetDamage() + 0.5f ); // round to nearest integer |
|
|
|
stats_event->SetInt( "attacker", pAttacker->GetUserID() ); |
|
stats_event->SetInt( "victim", GetUserID() ); |
|
stats_event->SetInt( "weapon", weaponID ); |
|
stats_event->SetInt( "damage", iDamage ); |
|
|
|
// damage_given is the amount of damage applied, not to exceed how much life we have |
|
stats_event->SetInt( "damage_given", MIN( iDamage, iInitialHealth ) ); |
|
|
|
CBaseEntity *pInflictor = info.GetInflictor(); |
|
float flDist = ( pInflictor->GetAbsOrigin() - GetAbsOrigin() ).Length(); |
|
stats_event->SetFloat( "distance", flDist ); |
|
|
|
stats_event->SetInt("hitgroup", m_LastHitGroup ); |
|
|
|
gameeventmanager->FireEvent( stats_event ); |
|
} |
|
#endif //CLIENT_DLL |
|
} |
|
|
|
return 1; |
|
} |
|
|
|
ConVar dod_explosionforcescale( "dod_explosionforcescale", "1.0", FCVAR_CHEAT ); |
|
ConVar dod_bulletforcescale( "dod_bulletforcescale", "1.0", FCVAR_CHEAT ); |
|
|
|
int CDODPlayer::OnTakeDamage( const CTakeDamageInfo &inputInfo ) |
|
{ |
|
CTakeDamageInfo info = inputInfo; |
|
|
|
CBaseEntity *pInflictor = info.GetInflictor(); |
|
|
|
if ( !pInflictor ) |
|
return 0; |
|
|
|
if ( GetMoveType() == MOVETYPE_NOCLIP ) |
|
return 0; |
|
|
|
// if the player's team does not match the inflictor's team |
|
// the player has changed teams between when they started the attack |
|
if( pInflictor->GetTeamNumber() != TEAM_UNASSIGNED && |
|
info.GetAttacker() != NULL && |
|
pInflictor->GetTeamNumber() != info.GetAttacker()->GetTeamNumber() ) |
|
{ |
|
info.SetDamage( 0 ); |
|
info.SetDamageType( 0 ); |
|
} |
|
|
|
bool bFriendlyFire = DODGameRules()->IsFriendlyFireOn(); |
|
|
|
if ( bFriendlyFire || |
|
info.GetAttacker()->GetTeamNumber() != GetTeamNumber() || |
|
pInflictor == this || |
|
info.GetAttacker() == this || |
|
info.GetDamageType() & DMG_BOMB ) |
|
{ |
|
// Do special stun damage effect |
|
if ( info.GetDamageType() & DMG_STUN ) |
|
{ |
|
OnDamageByStun( info ); |
|
return 0; |
|
} |
|
|
|
CDODPlayer *pPlayer = ToDODPlayer( info.GetAttacker() ); |
|
|
|
// keep track of amount of damage last sustained |
|
m_lastDamageAmount = info.GetDamage(); |
|
m_LastDamageType = info.GetDamageType(); |
|
|
|
if( !FBitSet( info.GetDamageType(), DMG_FALL ) ) |
|
Pain(); |
|
|
|
float flForceScale = 1.0; |
|
|
|
// Do special explosion damage effect |
|
if ( info.GetDamageType() & DMG_BLAST ) |
|
{ |
|
OnDamagedByExplosion( info ); |
|
flForceScale = dod_explosionforcescale.GetFloat(); |
|
} |
|
|
|
if( info.GetDamageType() & DMG_BULLET ) |
|
{ |
|
flForceScale = dod_bulletforcescale.GetFloat(); |
|
} |
|
|
|
// round up! |
|
int iDamage = (int)( info.GetDamage() + 0.5 ); |
|
|
|
if ( pPlayer ) |
|
{ |
|
// Record for the shooter |
|
pPlayer->RecordDamageGiven( this, iDamage ); |
|
|
|
// And for the victim |
|
RecordDamageTaken( pPlayer, iDamage ); |
|
} |
|
else |
|
{ |
|
RecordWorldDamageTaken( iDamage ); |
|
} |
|
|
|
m_vecTotalBulletForce += info.GetDamageForce() * flForceScale; |
|
|
|
CSingleUserRecipientFilter user( this ); |
|
user.MakeReliable(); |
|
UserMessageBegin( user, "Damage" ); |
|
WRITE_BYTE( (int)info.GetDamage() ); |
|
WRITE_LONG( info.GetDamageType() ); |
|
WRITE_VEC3COORD( info.GetInflictor()->WorldSpaceCenter() ); |
|
MessageEnd(); |
|
|
|
gamestats->Event_PlayerDamage( this, info ); |
|
|
|
return CBaseCombatCharacter::OnTakeDamage( info ); |
|
} |
|
else |
|
{ |
|
return 0; |
|
} |
|
} |
|
|
|
void CDODPlayer::Pain( void ) |
|
{ |
|
if ( m_LastDamageType & DMG_CLUB) |
|
{ |
|
EmitSound( "Player.MajorPain" ); |
|
} |
|
else if ( m_LastDamageType & DMG_BLAST ) |
|
{ |
|
EmitSound( "Player.MajorPain" ); |
|
} |
|
else |
|
{ |
|
EmitSound( "Player.MinorPain" ); |
|
} |
|
} |
|
|
|
void CDODPlayer::DeathSound( const CTakeDamageInfo &info ) |
|
{ |
|
if ( m_LastDamageType & DMG_CLUB ) |
|
{ |
|
EmitSound( "Player.MegaPain" ); |
|
} |
|
else if ( m_LastDamageType & DMG_BLAST ) |
|
{ |
|
EmitSound( "Player.MegaPain" ); |
|
} |
|
else if ( m_LastHitGroup == HITGROUP_HEAD ) |
|
{ |
|
EmitSound( "Player.DeathHeadShot" ); |
|
} |
|
else |
|
{ |
|
EmitSound( "Player.MinorPain" ); |
|
} |
|
} |
|
|
|
void CDODPlayer::OnDamagedByExplosion( const CTakeDamageInfo &info ) |
|
{ |
|
if ( info.GetDamage() >= 30.0f ) |
|
{ |
|
SetContextThink( &CDODPlayer::DeafenThink, gpGlobals->curtime + 0.3, DOD_DEAFEN_CONTEXT ); |
|
|
|
// The blast will naturally blow the temp ent helmet away |
|
PopHelmet( info.GetDamagePosition(), info.GetDamageForce() ); |
|
} |
|
} |
|
|
|
ConVar dod_stun_min_pitch( "dod_stun_min_pitch", "30", FCVAR_CHEAT ); |
|
ConVar dod_stun_max_pitch( "dod_stun_max_pitch", "50", FCVAR_CHEAT ); |
|
ConVar dod_stun_min_yaw( "dod_stun_min_yaw", "120", FCVAR_CHEAT ); |
|
ConVar dod_stun_max_yaw( "dod_stun_max_yaw", "150", FCVAR_CHEAT ); |
|
ConVar dod_stun_min_roll( "dod_stun_min_roll", "15", FCVAR_CHEAT ); |
|
ConVar dod_stun_max_roll( "dod_stun_max_roll", "30", FCVAR_CHEAT ); |
|
|
|
void CDODPlayer::OnDamageByStun( const CTakeDamageInfo &info ) |
|
{ |
|
DevMsg( 2, "took %.1f stun damage\n", info.GetDamage() ); |
|
|
|
float flPercent = ( info.GetDamage() / 100.0f ); |
|
|
|
m_flStunDuration = 0.0; // mark it as dirty so it transmits, incase we get the same value twice |
|
m_flStunDuration = 4.0f * flPercent; |
|
m_flStunMaxAlpha = 255; |
|
|
|
float flPitch = flPercent * RandomFloat( dod_stun_min_pitch.GetFloat(), dod_stun_max_pitch.GetFloat() ) * |
|
( (RandomInt( 0, 1 )) ? 1 : -1 ); |
|
|
|
float flYaw = flPercent * RandomFloat( dod_stun_min_yaw.GetFloat(), dod_stun_max_yaw.GetFloat() ) |
|
* ( (RandomInt( 0, 1 )) ? 1 : -1 ); |
|
|
|
float flRoll = flPercent * RandomFloat( dod_stun_min_roll.GetFloat(), dod_stun_max_roll.GetFloat() ) |
|
* ( (RandomInt( 0, 1 )) ? 1 : -1 ); |
|
|
|
DevMsg( 2, "punch: pitch %.1f yaw %.1f roll %.1f\n", flPitch, flYaw, flRoll ); |
|
|
|
SetPunchAngle( QAngle( flPitch, flYaw, flRoll ) ); |
|
} |
|
|
|
void CDODPlayer::DeafenThink( void ) |
|
{ |
|
int effect = random->RandomInt( 32, 34 ); |
|
|
|
CSingleUserRecipientFilter user( this ); |
|
enginesound->SetPlayerDSP( user, effect, false ); |
|
} |
|
|
|
//======================================================= |
|
// Remember this amount of damage that we dealt for stats |
|
//======================================================= |
|
void CDODPlayer::RecordDamageGiven( CDODPlayer *pVictim, int iDamageGiven ) |
|
{ |
|
if ( iDamageGiven <= 0 ) |
|
return; |
|
|
|
FOR_EACH_LL( m_DamageGivenList, i ) |
|
{ |
|
if ( pVictim->GetLifeID() == m_DamageGivenList[i]->GetLifeID() ) |
|
{ |
|
m_DamageGivenList[i]->AddDamage( iDamageGiven ); |
|
return; |
|
} |
|
} |
|
|
|
CDamageRecord *record = new CDamageRecord( pVictim->GetPlayerName(), pVictim->GetLifeID(), iDamageGiven ); |
|
int tail = m_DamageGivenList.AddToTail(); |
|
m_DamageGivenList[tail] = record; |
|
} |
|
|
|
//======================================================= |
|
// Remember this amount of damage that we took for stats |
|
//======================================================= |
|
void CDODPlayer::RecordDamageTaken( CDODPlayer *pAttacker, int iDamageTaken ) |
|
{ |
|
if ( iDamageTaken <= 0 ) |
|
return; |
|
|
|
FOR_EACH_LL( m_DamageTakenList, i ) |
|
{ |
|
if ( pAttacker->GetLifeID() == m_DamageTakenList[i]->GetLifeID() ) |
|
{ |
|
m_DamageTakenList[i]->AddDamage( iDamageTaken ); |
|
return; |
|
} |
|
} |
|
|
|
CDamageRecord *record = new CDamageRecord( pAttacker->GetPlayerName(), pAttacker->GetLifeID(), iDamageTaken ); |
|
int tail = m_DamageTakenList.AddToTail(); |
|
m_DamageTakenList[tail] = record; |
|
} |
|
|
|
void CDODPlayer::RecordWorldDamageTaken( int iDamageTaken ) |
|
{ |
|
if ( iDamageTaken <= 0 ) |
|
return; |
|
|
|
FOR_EACH_LL( m_DamageTakenList, i ) |
|
{ |
|
if ( m_DamageTakenList[i]->GetLifeID() == 0 ) |
|
{ |
|
m_DamageTakenList[i]->AddDamage( iDamageTaken ); |
|
return; |
|
} |
|
} |
|
|
|
CDamageRecord *record = new CDamageRecord( "world", 0, iDamageTaken ); |
|
int tail = m_DamageTakenList.AddToTail(); |
|
m_DamageTakenList[tail] = record; |
|
} |
|
|
|
//======================================================= |
|
// Reset our damage given and taken counters |
|
//======================================================= |
|
void CDODPlayer::ResetDamageCounters() |
|
{ |
|
m_DamageGivenList.PurgeAndDeleteElements(); |
|
m_DamageTakenList.PurgeAndDeleteElements(); |
|
} |
|
|
|
//======================================================= |
|
// Output the damage that we dealt to other players |
|
//======================================================= |
|
void CDODPlayer::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 ) |
|
{ |
|
Q_snprintf( buf, sizeof(buf), "( life ID %i ) - %d in %d %s", |
|
pRecord->GetLifeID(), |
|
pRecord->GetDamage(), |
|
pRecord->GetNumHits(), |
|
( pRecord->GetNumHits() == 1 ) ? "hit" : "hits" ); |
|
|
|
ClientPrint( this, msg_dest, "Damage Taken from \"%s1\" - %s2\n", pRecord->GetPlayerName(), buf ); |
|
} |
|
} |
|
} |
|
|
|
//======================================================= |
|
// Output the damage that we took from other players |
|
//======================================================= |
|
void CDODPlayer::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 ) |
|
{ |
|
Q_snprintf( buf, sizeof(buf), "( life ID %i ) - %d in %d %s", |
|
pRecord->GetLifeID(), |
|
pRecord->GetDamage(), |
|
pRecord->GetNumHits(), |
|
( pRecord->GetNumHits() == 1 ) ? "hit" : "hits" ); |
|
|
|
ClientPrint( this, msg_dest, "Damage Given to \"%s1\" - %s2\n", pRecord->GetPlayerName(), buf ); |
|
} |
|
} |
|
} |
|
|
|
// Sets the player's speed - returns true if successful |
|
bool CDODPlayer::SetSpeed( int speed ) |
|
{ |
|
DevMsg( 2, "Changing max speed to %d\n", speed ); |
|
|
|
SetMaxSpeed( (float)speed ); |
|
|
|
return true; |
|
} |
|
|
|
void CDODPlayer::CreateViewModel( int index /*=0*/ ) |
|
{ |
|
Assert( index >= 0 && index < MAX_VIEWMODELS ); |
|
|
|
if ( GetViewModel( index ) ) |
|
return; |
|
|
|
CDODViewModel *vm = ( CDODViewModel * )CreateEntityByName( "dod_viewmodel" ); |
|
if ( vm ) |
|
{ |
|
vm->SetAbsOrigin( GetAbsOrigin() ); |
|
vm->SetOwner( this ); |
|
vm->SetIndex( index ); |
|
DispatchSpawn( vm ); |
|
vm->FollowEntity( this, false ); |
|
m_hViewModel.Set( index, vm ); |
|
} |
|
} |
|
|
|
void CDODPlayer::ResetScores( void ) |
|
{ |
|
ResetFragCount(); |
|
ResetDeathCount(); |
|
SetScore(0); |
|
|
|
Q_memset( iNumKilledByUnanswered, 0, sizeof( iNumKilledByUnanswered ) ); |
|
} |
|
|
|
bool CDODPlayer::SetObserverMode(int mode) |
|
{ |
|
// DoD forces mp_forcecamera to be team only |
|
mp_forcecamera.SetValue( OBS_ALLOW_TEAM ); |
|
|
|
if ( mode < OBS_MODE_NONE || mode > OBS_MODE_ROAMING ) |
|
return false; |
|
|
|
// Skip over OBS_MODE_ROAMING for dead players |
|
if( GetTeamNumber() > TEAM_SPECTATOR ) |
|
{ |
|
if( mode == OBS_MODE_ROAMING ) |
|
mode = OBS_MODE_IN_EYE; |
|
} |
|
|
|
if ( m_iObserverMode > OBS_MODE_DEATHCAM ) |
|
{ |
|
// remember mode if we were really spectating before |
|
m_iObserverLastMode = m_iObserverMode; |
|
} |
|
|
|
m_iObserverMode = mode; |
|
|
|
switch ( mode ) |
|
{ |
|
case OBS_MODE_NONE: |
|
case OBS_MODE_FIXED : |
|
case OBS_MODE_DEATHCAM : |
|
SetFOV( this, 0 ); // Reset FOV |
|
SetViewOffset( vec3_origin ); |
|
SetMoveType( MOVETYPE_NONE ); |
|
break; |
|
|
|
case OBS_MODE_CHASE : |
|
case OBS_MODE_IN_EYE : |
|
// udpate FOV and viewmodels |
|
SetObserverTarget( m_hObserverTarget ); |
|
SetMoveType( MOVETYPE_OBSERVER ); |
|
break; |
|
|
|
case OBS_MODE_ROAMING : |
|
SetFOV( this, 0 ); // Reset FOV |
|
SetObserverTarget( m_hObserverTarget ); |
|
SetViewOffset( vec3_origin ); |
|
SetMoveType( MOVETYPE_OBSERVER ); |
|
break; |
|
|
|
} |
|
|
|
CheckObserverSettings(); |
|
|
|
return true; |
|
} |
|
|
|
extern ConVar sv_alltalk; |
|
bool CDODPlayer::CanHearChatFrom( CBasePlayer *pPlayer ) |
|
{ |
|
// can always hear the console |
|
if ( !pPlayer ) |
|
return true; |
|
|
|
// teammates can always hear each other if alltalk is on |
|
if ( sv_alltalk.GetBool() == true ) |
|
return true; |
|
|
|
// can hear dead people in bonus round and at round end |
|
if ( DODGameRules()->IsInBonusRound() || DODGameRules()->State_Get() == STATE_GAME_OVER ) |
|
return true; |
|
|
|
// alive players cannot hear dead players |
|
if ( IsAlive() && !pPlayer->IsAlive() && !( pPlayer->GetTeamNumber() == TEAM_SPECTATOR ) ) |
|
{ |
|
return false; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
void CDODPlayer::ResetBleeding( void ) |
|
{ |
|
SetBandager( NULL ); |
|
} |
|
|
|
void CDODPlayer::Bandage( void ) |
|
{ |
|
ResetBleeding(); |
|
|
|
TakeHealth( mp_bandage_heal_amount.GetFloat(), 0 ); |
|
|
|
// change helmet to bandages |
|
} |
|
|
|
void CDODPlayer::SetBandager( CDODPlayer *pPlayer ) |
|
{ |
|
m_hBandager = pPlayer; |
|
} |
|
|
|
bool CDODPlayer::IsBeingBandaged( void ) |
|
{ |
|
return ( m_hBandager.Get() != NULL ); |
|
} |
|
|
|
void CDODPlayer::CommitSuicide( bool bExplode /*= false*/, bool bForce /*= false*/ ) |
|
{ |
|
CommitSuicide( vec3_origin, false, bForce ); |
|
} |
|
|
|
void CDODPlayer::CommitSuicide( const Vector &vecForce, bool bExplode /*= false*/, bool bForce /*= false*/ ) |
|
{ |
|
// If they're suiciding in the spawn, its most likely they're changing class |
|
// ( or possible suiciding to avoid being killed in the endround ) |
|
// On linux the suicide option sometimes arrives before the joinclass so just |
|
// reject it here |
|
if ( ShouldInstantRespawn() ) |
|
return; |
|
|
|
if( !IsAlive() ) |
|
return; |
|
|
|
// prevent suiciding too often |
|
if ( m_fNextSuicideTime > gpGlobals->curtime ) |
|
return; |
|
|
|
// don't let them suicide for 5 seconds after suiciding |
|
m_fNextSuicideTime = gpGlobals->curtime + 5; |
|
|
|
// have the player kill themselves |
|
CTakeDamageInfo info( this, this, 0, DMG_NEVERGIB ); |
|
Event_Killed( info ); |
|
Event_Dying( info ); |
|
} |
|
|
|
bool CDODPlayer::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 CDODPlayer::StopReplayMode() |
|
{ |
|
BaseClass::StopReplayMode(); |
|
|
|
CSingleUserRecipientFilter filter( this ); |
|
filter.MakeReliable(); |
|
|
|
UserMessageBegin( filter, "KillCam" ); |
|
WRITE_BYTE( OBS_MODE_NONE ); |
|
WRITE_BYTE( 0 ); |
|
WRITE_BYTE( 0 ); |
|
MessageEnd(); |
|
} |
|
|
|
void CDODPlayer::PickUpWeapon( CWeaponDODBase *pWeapon ) |
|
{ |
|
// if we have a primary weapon and we are allowed to drop it, drop it and |
|
// pick up the one we +used |
|
|
|
CWeaponDODBase *pCurrentPrimaryWpn = (CWeaponDODBase *)Weapon_GetSlot( WPN_SLOT_PRIMARY ); |
|
|
|
// drop primary if we can |
|
if( pCurrentPrimaryWpn ) |
|
{ |
|
if ( pCurrentPrimaryWpn->CanDrop() == false ) |
|
{ |
|
return; |
|
} |
|
|
|
DODWeaponDrop( pCurrentPrimaryWpn, true ); |
|
} |
|
|
|
// pick up the new one |
|
if ( BumpWeapon( pWeapon ) ) |
|
{ |
|
pWeapon->OnPickedUp( this ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Override setup bones so that is uses the render angles from |
|
// the DOD animation state to setup the hitboxes. |
|
//----------------------------------------------------------------------------- |
|
void CDODPlayer::SetupBones( matrix3x4_t *pBoneToWorld, int boneMask ) |
|
{ |
|
VPROF_BUDGET( "CBaseAnimating::SetupBones", VPROF_BUDGETGROUP_SERVER_ANIM ); |
|
|
|
// Set the mdl cache semaphore. |
|
MDLCACHE_CRITICAL_SECTION(); |
|
|
|
// Get the studio header. |
|
Assert( GetModelPtr() ); |
|
CStudioHdr *pStudioHdr = GetModelPtr( ); |
|
|
|
Vector pos[MAXSTUDIOBONES]; |
|
Quaternion q[MAXSTUDIOBONES]; |
|
|
|
// Adjust hit boxes based on IK driven offset. |
|
Vector adjOrigin = GetAbsOrigin() + Vector( 0, 0, m_flEstIkOffset ); |
|
|
|
// FIXME: pass this into Studio_BuildMatrices to skip transforms |
|
CBoneBitList boneComputed; |
|
if ( m_pIk ) |
|
{ |
|
m_iIKCounter++; |
|
m_pIk->Init( pStudioHdr, GetAbsAngles(), adjOrigin, gpGlobals->curtime, m_iIKCounter, boneMask ); |
|
GetSkeleton( pStudioHdr, pos, q, boneMask ); |
|
|
|
m_pIk->UpdateTargets( pos, q, pBoneToWorld, boneComputed ); |
|
CalculateIKLocks( gpGlobals->curtime ); |
|
m_pIk->SolveDependencies( pos, q, pBoneToWorld, boneComputed ); |
|
} |
|
else |
|
{ |
|
GetSkeleton( pStudioHdr, pos, q, boneMask ); |
|
} |
|
|
|
CBaseAnimating *pParent = dynamic_cast< CBaseAnimating* >( GetMoveParent() ); |
|
if ( pParent ) |
|
{ |
|
// We're doing bone merging, so do special stuff here. |
|
CBoneCache *pParentCache = pParent->GetBoneCache(); |
|
if ( pParentCache ) |
|
{ |
|
BuildMatricesWithBoneMerge( |
|
pStudioHdr, |
|
m_PlayerAnimState->GetRenderAngles(), |
|
adjOrigin, |
|
pos, |
|
q, |
|
pBoneToWorld, |
|
pParent, |
|
pParentCache ); |
|
|
|
return; |
|
} |
|
} |
|
|
|
Studio_BuildMatrices( |
|
pStudioHdr, |
|
m_PlayerAnimState->GetRenderAngles(), |
|
adjOrigin, |
|
pos, |
|
q, |
|
-1, |
|
GetModelScale(), // Scaling |
|
pBoneToWorld, |
|
boneMask ); |
|
} |
|
|
|
|
|
extern ConVar sv_debug_player_use; |
|
extern float IntervalDistance( float x, float x0, float x1 ); |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Find which ents are higher priority for receiving our +use |
|
//----------------------------------------------------------------------------- |
|
int CDODPlayer::GetPriorityForPickUpEnt( CBaseEntity *pEnt ) |
|
{ |
|
CDODBombTarget *pBombTarget = dynamic_cast< CDODBombTarget *>( pEnt ); |
|
|
|
if ( pBombTarget ) |
|
{ |
|
// its a bomb target for planting or defusing, most important |
|
return 20; |
|
} |
|
|
|
CDODBaseGrenade *pGren = dynamic_cast< CDODBaseGrenade *>( pEnt ); |
|
|
|
if ( pGren ) |
|
{ |
|
// its a grenade, high priority |
|
return 10; |
|
} |
|
|
|
CWeaponDODBase *pWeapon = dynamic_cast< CWeaponDODBase *>( pEnt ); |
|
|
|
if ( pWeapon ) |
|
{ |
|
// weapons are medium priority |
|
return 5; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: like regular FindUseEntity, but picks up grenades before other things |
|
//----------------------------------------------------------------------------- |
|
CBaseEntity *CDODPlayer::FindUseEntity() |
|
{ |
|
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 searchCenter = EyePosition(); |
|
|
|
// NOTE: Some debris objects are useable too, so hit those as well |
|
// A button, etc. can be made out of clip brushes, make sure it's +useable via a traceline, too. |
|
int useableContents = MASK_SOLID | CONTENTS_DEBRIS | CONTENTS_PLAYERCLIP; |
|
|
|
UTIL_TraceLine( searchCenter, searchCenter + forward * 1024, useableContents, this, COLLISION_GROUP_NONE, &tr ); |
|
|
|
// try the hit entity if there is one, or the ground entity if there isn't. |
|
CBaseEntity *pNearest = NULL; |
|
CBaseEntity *pObject = tr.m_pEnt; |
|
|
|
// UNDONE: Might be faster to just fold this range into the sphere query |
|
int count = 0; |
|
const int NUM_TANGENTS = 7; |
|
while ( !IsUseableEntity(pObject, 0) && count < NUM_TANGENTS) |
|
{ |
|
// trace a box at successive angles down |
|
// 45 deg, 30 deg, 20 deg, 15 deg, 10 deg, -10, -15 |
|
const float tangents[NUM_TANGENTS] = { 1, 0.57735026919f, 0.3639702342f, 0.267949192431f, 0.1763269807f, -0.1763269807f, -0.267949192431f }; |
|
Vector down = forward - tangents[count]*up; |
|
VectorNormalize(down); |
|
UTIL_TraceHull( searchCenter, searchCenter + down * 72, -Vector(16,16,16), Vector(16,16,16), useableContents, this, COLLISION_GROUP_NONE, &tr ); |
|
pObject = tr.m_pEnt; |
|
count++; |
|
} |
|
float nearestDot = CONE_90_DEGREES; |
|
if ( IsUseableEntity(pObject, 0) ) |
|
{ |
|
Vector delta = tr.endpos - tr.startpos; |
|
float centerZ = CollisionProp()->WorldSpaceCenter().z; |
|
delta.z = IntervalDistance( tr.endpos.z, centerZ + CollisionProp()->OBBMins().z, centerZ + CollisionProp()->OBBMaxs().z ); |
|
float dist = delta.Length(); |
|
if ( dist < PLAYER_USE_RADIUS ) |
|
{ |
|
if ( sv_debug_player_use.GetBool() ) |
|
{ |
|
NDebugOverlay::Line( searchCenter, tr.endpos, 0, 255, 0, true, 30 ); |
|
NDebugOverlay::Cross3D( tr.endpos, 16, 0, 255, 0, true, 30 ); |
|
} |
|
|
|
pNearest = pObject; |
|
nearestDot = 0; |
|
} |
|
} |
|
|
|
// check ground entity first |
|
// if you've got a useable ground entity, then shrink the cone of this search to 45 degrees |
|
// otherwise, search out in a 90 degree cone (hemisphere) |
|
if ( GetGroundEntity() && IsUseableEntity(GetGroundEntity(), FCAP_USE_ONGROUND) ) |
|
{ |
|
pNearest = GetGroundEntity(); |
|
nearestDot = CONE_45_DEGREES; |
|
} |
|
|
|
int iHighestPriority = GetPriorityForPickUpEnt( pObject ); |
|
|
|
for ( CEntitySphereQuery sphere( searchCenter, PLAYER_USE_RADIUS ); ( pObject = sphere.GetCurrentEntity() ) != NULL; sphere.NextEntity() ) |
|
{ |
|
if ( !pObject ) |
|
continue; |
|
|
|
if ( !IsUseableEntity( pObject, FCAP_USE_IN_RADIUS ) ) |
|
continue; |
|
|
|
// see if it's more roughly in front of the player than previous guess |
|
Vector point; |
|
pObject->CollisionProp()->CalcNearestPoint( searchCenter, &point ); |
|
|
|
Vector dir = point - searchCenter; |
|
VectorNormalize(dir); |
|
float dot = DotProduct( dir, forward ); |
|
|
|
// Need to be looking at the object more or less |
|
if ( dot < 0.8 ) |
|
continue; |
|
|
|
//NEW FOR DOD |
|
// if this entity is higher priority than previous ent, use this one |
|
|
|
int iPriority = GetPriorityForPickUpEnt( pObject ); |
|
|
|
// if higher priority, always use |
|
// within the same priority, use the closer one |
|
if ( ( iPriority > iHighestPriority ) || ( iPriority == iHighestPriority && dot > nearestDot ) ) |
|
{ |
|
// Since this has purely been a radius search to this point, we now |
|
// make sure the object isn't behind glass or a grate. |
|
trace_t trCheckOccluded; |
|
UTIL_TraceLine( searchCenter, point, useableContents, this, COLLISION_GROUP_NONE, &trCheckOccluded ); |
|
|
|
if ( trCheckOccluded.fraction == 1.0 || trCheckOccluded.m_pEnt == pObject ) |
|
{ |
|
pNearest = pObject; |
|
nearestDot = dot; |
|
iHighestPriority = iPriority; |
|
} |
|
} |
|
} |
|
|
|
if ( sv_debug_player_use.GetBool() ) |
|
{ |
|
if ( !pNearest ) |
|
{ |
|
NDebugOverlay::Line( searchCenter, tr.endpos, 255, 0, 0, true, 30 ); |
|
NDebugOverlay::Cross3D( tr.endpos, 16, 255, 0, 0, true, 30 ); |
|
} |
|
else |
|
{ |
|
NDebugOverlay::Box( pNearest->WorldSpaceCenter(), Vector(-8, -8, -8), Vector(8, 8, 8), 0, 255, 0, true, 30 ); |
|
} |
|
} |
|
|
|
// Special check for bomb targets, whose radius for +use is larger than other entities |
|
if ( pNearest == NULL ) |
|
{ |
|
CBaseEntity *pEnt = NULL; |
|
float flBestDist = FLT_MAX; |
|
|
|
// If we didn't find anything, check to see if the bomb target is close enough to use. |
|
// 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. |
|
|
|
while( ( pEnt = gEntList.FindEntityByClassname( pEnt, "dod_bomb_target" ) ) != NULL ) |
|
{ |
|
CDODBombTarget *pTarget = static_cast<CDODBombTarget *>( pEnt ); |
|
|
|
if ( !pTarget->CanPlantHere( this ) ) |
|
continue; |
|
|
|
Vector pos = WorldSpaceCenter(); |
|
|
|
float flDist = ( pos - pTarget->GetAbsOrigin() ).Length(); |
|
|
|
Vector toBomb = pTarget->GetAbsOrigin() - EyePosition(); |
|
toBomb.NormalizeInPlace(); |
|
|
|
if ( DotProduct( forward, toBomb ) < 0.8 ) |
|
{ |
|
continue; |
|
} |
|
|
|
// if we are looking directly at a bomb target and it is within our radius, that automatically wins |
|
if ( flDist < flBestDist && flDist < DOD_BOMB_PLANT_RADIUS ) |
|
{ |
|
flBestDist = flDist; |
|
pNearest = pTarget; |
|
} |
|
} |
|
} |
|
|
|
return pNearest; |
|
} |
|
|
|
void CDODPlayer::PrintLifetimeStats( void ) |
|
{ |
|
unsigned int i; |
|
|
|
Msg( "\nWeapon Stats\n================\n" ); |
|
|
|
Msg( " (shots) (hits) (damage) (avg.dist) (kills) (hits taken) (dmg taken) (times killed) (accuracy)\n" ); |
|
// ( "* thompson 4 3 110 89.8 1 1 0 0 75.0 |
|
//Msg( "*%9s %2i %2i %3i %5.1f %2i %2i %3i %2i %3.1f\n", |
|
|
|
for ( i=0;i<WEAPON_MAX;i++ ) |
|
{ |
|
if ( m_WeaponStats[i].m_iNumShotsTaken > 0 ) |
|
{ |
|
Msg( "* %10s (%2i) %6i %6i %8i %9.1f %7i %9i %9i %9i %9.1f\n", |
|
WeaponIDToAlias( i ), i, |
|
m_WeaponStats[i].m_iNumShotsTaken, |
|
m_WeaponStats[i].m_iNumShotsHit, |
|
m_WeaponStats[i].m_iTotalDamageGiven, |
|
m_WeaponStats[i].m_flAverageHitDistance, |
|
m_WeaponStats[i].m_iNumKills, |
|
m_WeaponStats[i].m_iNumHitsTaken, |
|
m_WeaponStats[i].m_iTotalDamageTaken, |
|
m_WeaponStats[i].m_iTimesKilled, |
|
100.0 * ( (float)m_WeaponStats[i].m_iNumShotsHit / (float)m_WeaponStats[i].m_iNumShotsTaken ) ); |
|
} |
|
} |
|
|
|
Msg( "\nPlayer Stats\n================\n" ); |
|
|
|
for( i=0;i<m_KilledPlayers.Count();i++ ) |
|
{ |
|
CBasePlayer *pPlayer = UTIL_PlayerByUserId( m_KilledPlayers[i].m_iUserID ); |
|
|
|
const char *pName = pPlayer ? pPlayer->GetPlayerName() : m_KilledByPlayers[i].m_szPlayerName; |
|
|
|
Msg( "* Killed Player '%s' %i times ( %i damage )\n", |
|
pName, |
|
m_KilledPlayers[i].m_iKills, |
|
m_KilledPlayers[i].m_iTotalDamage ); |
|
} |
|
|
|
Msg( "\n" ); |
|
|
|
for( i=0;i<m_KilledByPlayers.Count();i++ ) |
|
{ |
|
CBasePlayer *pPlayer = UTIL_PlayerByUserId( m_KilledByPlayers[i].m_iUserID ); |
|
|
|
Assert( pPlayer && "If this fails, the player has disconnected. Fix this!" ); |
|
|
|
const char *pName = pPlayer ? pPlayer->GetPlayerName() : m_KilledByPlayers[i].m_szPlayerName; |
|
|
|
Msg( "* Player '%s' killed you %i times ( %i damage )\n", |
|
pName, |
|
m_KilledByPlayers[i].m_iKills, |
|
m_KilledByPlayers[i].m_iTotalDamage ); |
|
} |
|
|
|
Msg( "\n" ); |
|
|
|
Msg( "* Areas Captured: %i\n", m_iNumAreaCaptures ); |
|
Msg( "* Areas Defended: %i\n", m_iNumAreaDefenses ); |
|
Msg( "* Num Bonus Round Kills: %i\n", m_iNumBonusRoundKills ); |
|
|
|
Msg( "\n" ); |
|
|
|
// time spent as each class |
|
Msg( "Time spent as each class:\n" ); |
|
|
|
// Make sure to tally the latest time |
|
int playerclass = m_Shared.DesiredPlayerClass(); |
|
|
|
//evil, re-map -2 to 6 so it goes on the end of the array |
|
if ( playerclass == PLAYERCLASS_RANDOM ) |
|
playerclass = 6; |
|
|
|
/* |
|
m_flTimePlayedPerClass[playerclass] += ( gpGlobals->curtime - m_flLastClassChangeTime ); |
|
m_flLastClassChangeTime = gpGlobals->curtime; |
|
|
|
Msg( "* Rifleman: %.0f\n", m_flTimePlayedPerClass[0] ); |
|
Msg( "* Assault: %.0f\n", m_flTimePlayedPerClass[1] ); |
|
Msg( "* Support: %.0f\n", m_flTimePlayedPerClass[2] ); |
|
Msg( "* Sniper: %.0f\n", m_flTimePlayedPerClass[3] ); |
|
Msg( "* MG: %.0f\n", m_flTimePlayedPerClass[4] ); |
|
Msg( "* Rocket: %.0f\n", m_flTimePlayedPerClass[5] ); |
|
Msg( "* Random: %.0f\n", m_flTimePlayedPerClass[6] ); |
|
|
|
Msg( "\n" ); |
|
Msg( "Total time connected %.0f\n", ( gpGlobals->curtime - m_flConnectTime ) ); |
|
*/ |
|
} |
|
|
|
void CDODPlayer::Stats_WeaponFired( int weaponID ) |
|
{ |
|
// increment shots taken for this weapon |
|
m_WeaponStats[weaponID].m_iNumShotsTaken++; |
|
|
|
DODGameRules()->Stats_WeaponFired( weaponID ); |
|
|
|
StatEvent_WeaponFired( (DODWeaponID)weaponID ); |
|
} |
|
|
|
void CDODPlayer::Stats_WeaponHit( CDODPlayer *pVictim, int weaponID, int iDamage, int iDamageGiven, int hitgroup, float flHitDistance ) |
|
{ |
|
// distance |
|
float flTotalHitDistance = m_WeaponStats[weaponID].m_flAverageHitDistance * m_WeaponStats[weaponID].m_iNumShotsHit; |
|
flTotalHitDistance += flHitDistance; |
|
m_WeaponStats[weaponID].m_flAverageHitDistance = flTotalHitDistance / ( m_WeaponStats[weaponID].m_iNumShotsHit + 1 ); |
|
|
|
// damage |
|
m_WeaponStats[weaponID].m_iTotalDamageGiven += iDamageGiven; |
|
|
|
// hitgroup |
|
m_WeaponStats[weaponID].m_iBodygroupsHit[hitgroup]++; |
|
|
|
// shots hit |
|
m_WeaponStats[weaponID].m_iNumShotsHit++; |
|
|
|
int userID = pVictim->GetUserID(); |
|
|
|
// add total damage to the player record |
|
int lookup = m_KilledPlayers.Find( userID ); |
|
if ( lookup == m_KilledPlayers.InvalidIndex() ) |
|
{ |
|
// make a new one |
|
playerstat_t p; |
|
Q_strncpy( p.m_szPlayerName, pVictim->GetPlayerName(), MAX_PLAYER_NAME_LENGTH ); |
|
p.m_iUserID = userID; |
|
p.m_iKills = 0; |
|
p.m_iTotalDamage = iDamageGiven; |
|
|
|
m_KilledPlayers.Insert( userID, p ); |
|
} |
|
else |
|
{ |
|
m_KilledPlayers[lookup].m_iTotalDamage += iDamageGiven; |
|
} |
|
|
|
DODGameRules()->Stats_WeaponHit( weaponID, flHitDistance ); |
|
|
|
StatEvent_WeaponHit( (DODWeaponID)weaponID, ( hitgroup == HITGROUP_HEAD ) ); |
|
} |
|
|
|
void CDODPlayer::Stats_HitByWeapon( CDODPlayer *pAttacker, int weaponID, int iDamage, int iDamageGiven, int hitgroup ) |
|
{ |
|
// damage |
|
m_WeaponStats[weaponID].m_iTotalDamageTaken += iDamageGiven; |
|
|
|
// hitgroup |
|
m_WeaponStats[weaponID].m_iHitInBodygroups[hitgroup]++; |
|
|
|
// shots hit |
|
m_WeaponStats[weaponID].m_iNumHitsTaken++; |
|
|
|
int userID = pAttacker->GetUserID(); |
|
|
|
// add total damage to the player record |
|
int lookup = m_KilledByPlayers.Find( userID ); |
|
if ( lookup == m_KilledByPlayers.InvalidIndex() ) |
|
{ |
|
// make a new one |
|
playerstat_t p; |
|
Q_strncpy( p.m_szPlayerName, pAttacker->GetPlayerName(), MAX_PLAYER_NAME_LENGTH ); |
|
p.m_iUserID = userID; |
|
p.m_iKills = 0; |
|
p.m_iTotalDamage = iDamageGiven; |
|
|
|
m_KilledByPlayers.Insert( userID, p ); |
|
} |
|
else |
|
{ |
|
m_KilledByPlayers[lookup].m_iTotalDamage += iDamageGiven; |
|
} |
|
} |
|
|
|
void CDODPlayer::Stats_KilledPlayer( CDODPlayer *pVictim, int weaponID ) |
|
{ |
|
m_WeaponStats[weaponID].m_iNumKills++; |
|
|
|
int userID = pVictim->GetUserID(); |
|
|
|
// add a kill to the player record |
|
int lookup = m_KilledPlayers.Find( userID ); |
|
if ( lookup == m_KilledPlayers.InvalidIndex() ) |
|
{ |
|
// make a new one |
|
playerstat_t p; |
|
Q_strncpy( p.m_szPlayerName, pVictim->GetPlayerName(), MAX_PLAYER_NAME_LENGTH ); |
|
p.m_iUserID = userID; |
|
p.m_iKills = 1; |
|
p.m_iTotalDamage = 0; |
|
|
|
m_KilledPlayers.Insert( userID, p ); |
|
} |
|
else |
|
{ |
|
m_KilledPlayers[lookup].m_iKills++; |
|
} |
|
|
|
// Gamerules records kills per team |
|
DODGameRules()->Stats_PlayerKill( GetTeamNumber(), m_Shared.PlayerClass() ); |
|
|
|
m_iPerRoundKills++; |
|
|
|
StatEvent_KilledPlayer( (DODWeaponID)weaponID ); |
|
} |
|
|
|
void CDODPlayer::Stats_KilledByPlayer( CDODPlayer *pAttacker, int weaponID ) |
|
{ |
|
m_WeaponStats[weaponID].m_iTimesKilled++; |
|
|
|
int userID = pAttacker->GetUserID(); |
|
|
|
// add a kill to the player record |
|
int lookup = m_KilledByPlayers.Find( userID ); |
|
if ( lookup == m_KilledByPlayers.InvalidIndex() ) |
|
{ |
|
// make a new one |
|
playerstat_t p; |
|
Q_strncpy( p.m_szPlayerName, pAttacker->GetPlayerName(), MAX_PLAYER_NAME_LENGTH ); |
|
p.m_iUserID = userID; |
|
p.m_iKills = 1; |
|
p.m_iTotalDamage = 0; |
|
|
|
m_KilledByPlayers.Insert( userID, p ); |
|
} |
|
else |
|
{ |
|
m_KilledByPlayers[lookup].m_iKills++; |
|
} |
|
} |
|
|
|
void CDODPlayer::Stats_AreaDefended() |
|
{ |
|
// map count |
|
m_iNumAreaDefenses++; |
|
|
|
// round count |
|
m_iPerRoundDefenses++; |
|
} |
|
|
|
void CDODPlayer::Stats_AreaCaptured() |
|
{ |
|
// map count |
|
m_iNumAreaCaptures++; |
|
|
|
// round count |
|
m_iPerRoundCaptures++; |
|
} |
|
|
|
void CDODPlayer::Stats_BonusRoundKill() |
|
{ |
|
m_iNumBonusRoundKills++; |
|
} |
|
|
|
void CDODPlayer::Stats_BombDetonated() |
|
{ |
|
m_iPerRoundBombsDetonated++; |
|
} |
|
|
|
void CDODPlayer::NoteWeaponFired() |
|
{ |
|
Assert( m_pCurrentCommand ); |
|
if( m_pCurrentCommand ) |
|
{ |
|
m_iLastWeaponFireUsercmd = m_pCurrentCommand->command_number; |
|
} |
|
} |
|
|
|
bool CDODPlayer::WantsLagCompensationOnEntity( const CBasePlayer *pPlayer, const CUserCmd *pCmd, const CBitVec<MAX_EDICTS> *pEntityTransmitBits ) const |
|
{ |
|
// No need to lag compensate at all if we're not attacking in this command and |
|
// we haven't attacked recently. |
|
if ( !( pCmd->buttons & IN_ATTACK ) && (pCmd->command_number - m_iLastWeaponFireUsercmd > 5) ) |
|
return false; |
|
|
|
return BaseClass::WantsLagCompensationOnEntity( pPlayer, pCmd, pEntityTransmitBits ); |
|
} |
|
|
|
void CDODPlayer::TallyLatestTimePlayedPerClass( int iOldTeam, int iOldPlayerClass ) |
|
{ |
|
// Tally the time we spent in the last class, for stats purposes |
|
if ( iOldPlayerClass != PLAYERCLASS_UNDEFINED && ( iOldTeam == TEAM_ALLIES || iOldTeam == TEAM_AXIS ) ) |
|
{ |
|
// store random in the last slot |
|
if ( iOldPlayerClass == PLAYERCLASS_RANDOM ) |
|
iOldPlayerClass = 6; |
|
|
|
Assert( iOldPlayerClass >= 0 && iOldPlayerClass <= 6 ); |
|
Assert( iOldTeam == TEAM_ALLIES || iOldTeam == TEAM_AXIS ); |
|
|
|
if ( iOldTeam == TEAM_ALLIES ) |
|
{ |
|
m_flTimePlayedPerClass_Allies[iOldPlayerClass] += ( gpGlobals->curtime - m_flLastClassChangeTime ); |
|
} |
|
else if ( iOldTeam == TEAM_AXIS ) |
|
{ |
|
m_flTimePlayedPerClass_Axis[iOldPlayerClass] += ( gpGlobals->curtime - m_flLastClassChangeTime ); |
|
} |
|
} |
|
|
|
m_flLastClassChangeTime = gpGlobals->curtime; |
|
} |
|
|
|
void CDODPlayer::ResetProgressBar( void ) |
|
{ |
|
SetProgressBarTime( 0 ); |
|
} |
|
|
|
void CDODPlayer::SetProgressBarTime( int barTime ) |
|
{ |
|
m_iProgressBarDuration = barTime; |
|
m_flProgressBarStartTime = this->m_flSimulationTime; |
|
} |
|
|
|
void CDODPlayer::SetDefusing( CDODBombTarget *pTarget ) |
|
{ |
|
bool bIsDefusing = ( pTarget != NULL ); |
|
|
|
if ( bIsDefusing && !m_bIsDefusing ) |
|
{ |
|
// start defuse sound |
|
EmitSound( "Weapon_C4.Disarm" ); |
|
m_Shared.SetDefusing( true ); |
|
} |
|
else if ( !bIsDefusing && m_bIsDefusing ) |
|
{ |
|
// stop defuse sound |
|
StopSound( "Weapon_C4.Disarm" ); |
|
m_Shared.SetDefusing( false ); |
|
} |
|
|
|
m_bIsDefusing = bIsDefusing; |
|
|
|
m_pDefuseTarget = pTarget; |
|
} |
|
|
|
void CDODPlayer::SetPlanting( CDODBombTarget *pTarget ) |
|
{ |
|
bool bIsPlanting = ( pTarget != NULL ); |
|
|
|
if ( bIsPlanting && !m_bIsPlanting ) |
|
{ |
|
// start defuse sound |
|
EmitSound( "Weapon_C4.Plant" ); |
|
m_Shared.SetPlanting( true ); |
|
} |
|
else if ( !bIsPlanting && m_bIsPlanting ) |
|
{ |
|
// stop defuse sound |
|
StopSound( "Weapon_C4.Plant" ); |
|
m_Shared.SetPlanting( false ); |
|
} |
|
|
|
m_bIsPlanting = bIsPlanting; |
|
m_pPlantTarget = pTarget; |
|
} |
|
|
|
void CDODPlayer::StoreCaptureBlock( int iAreaIndex, int iCapAttempt ) |
|
{ |
|
m_iLastBlockAreaIndex = iAreaIndex; |
|
m_iLastBlockCapAttempt = iCapAttempt; |
|
} |
|
|
|
// The capture attempt number that was taking place the last time we blocked a capture |
|
int CDODPlayer::GetLastBlockCapAttempt( void ) |
|
{ |
|
return m_iLastBlockCapAttempt; |
|
} |
|
|
|
// The index of the area where we last blocked a capture |
|
int CDODPlayer::GetLastBlockAreaIndex( void ) |
|
{ |
|
return m_iLastBlockCapAttempt; |
|
} |
|
|
|
// NOTE! This is unused, unless we reenable our collision bounds to use USE_GAME_CODE |
|
// - This extends our trigger bounds out a long ways and breaks many things, so only |
|
// do this if you know what you're doing. |
|
void CDODPlayer::ComputeWorldSpaceSurroundingBox( Vector *pVecWorldMins, Vector *pVecWorldMaxs ) |
|
{ |
|
m_Shared.ComputeWorldSpaceSurroundingBox( pVecWorldMins, pVecWorldMaxs ); |
|
} |
|
|
|
// Called when we fire a bullet, with the number of headshots we hit |
|
void CDODPlayer::HandleHeadshotAchievement( int iNumHeadshots ) |
|
{ |
|
if ( iNumHeadshots <= 0 ) |
|
{ |
|
SetPerLifeCounterKV( "headshots", 0 ); |
|
} |
|
else |
|
{ |
|
if ( DODGameRules()->State_Get() != STATE_RND_RUNNING ) |
|
return; |
|
|
|
int iHeadshots = GetPerLifeCounterKV( "headshots" ) + iNumHeadshots; |
|
|
|
if ( iHeadshots >= ACHIEVEMENT_NUM_CONSECUTIVE_HEADSHOTS ) |
|
{ |
|
AwardAchievement( ACHIEVEMENT_DOD_CONSECUTIVE_HEADSHOTS ); |
|
} |
|
|
|
SetPerLifeCounterKV( "headshots", iHeadshots ); |
|
} |
|
} |
|
|
|
void CDODPlayer::HandleDeployedMGKillCount( int iNumDeployedKills ) |
|
{ |
|
if ( iNumDeployedKills <= 0 ) |
|
{ |
|
SetPerLifeCounterKV( "deployed_mg_kills", 0 ); |
|
} |
|
else |
|
{ |
|
if ( DODGameRules()->State_Get() != STATE_RND_RUNNING ) |
|
return; |
|
|
|
int iKillsFromPos = GetPerLifeCounterKV( "deployed_mg_kills" ) + iNumDeployedKills; |
|
|
|
if ( iKillsFromPos >= ACHIEVEMENT_MG_STREAK_IS_DOMINATING ) |
|
{ |
|
AwardAchievement( ACHIEVEMENT_DOD_MG_POSITION_STREAK ); |
|
} |
|
|
|
SetPerLifeCounterKV( "deployed_mg_kills", iKillsFromPos ); |
|
} |
|
} |
|
|
|
int CDODPlayer::GetDeployedKillStreak( void ) |
|
{ |
|
return GetPerLifeCounterKV( "deployed_mg_kills" ); |
|
} |
|
|
|
void CDODPlayer::HandleEnemyWeaponsAchievement( int iNumEnemyWpnKills ) |
|
{ |
|
if ( iNumEnemyWpnKills <= 0 ) |
|
{ |
|
SetPerLifeCounterKV( "enemy_wpn_kills", 0 ); |
|
} |
|
else |
|
{ |
|
if ( DODGameRules()->State_Get() != STATE_RND_RUNNING ) |
|
return; |
|
|
|
int iKills = GetPerLifeCounterKV( "enemy_wpn_kills" ) + iNumEnemyWpnKills; |
|
|
|
if ( iKills >= ACHIEVEMENT_NUM_ENEMY_WPN_KILLS ) |
|
{ |
|
AwardAchievement( ACHIEVEMENT_DOD_USE_ENEMY_WEAPONS ); |
|
} |
|
|
|
SetPerLifeCounterKV( "enemy_wpn_kills", iKills ); |
|
} |
|
} |
|
|
|
void CDODPlayer::ResetComboWeaponKill( void ) |
|
{ |
|
SetPerLifeCounterKV( "combo_wpn_mask", 0 ); |
|
} |
|
|
|
void CDODPlayer::HandleComboWeaponKill( int iWeaponType ) |
|
{ |
|
if ( DODGameRules()->State_Get() != STATE_RND_RUNNING ) |
|
return; |
|
|
|
int iMask = GetPerLifeCounterKV( "combo_wpn_mask" ); |
|
|
|
iMask |= iWeaponType; |
|
|
|
SetPerLifeCounterKV( "combo_wpn_mask", iMask ); |
|
|
|
const int iRequiredMask = ( WPN_TYPE_MG | WPN_TYPE_SNIPER | WPN_TYPE_RIFLE | WPN_TYPE_SUBMG ); |
|
const int iExplosiveMask = ( WPN_TYPE_GRENADE | WPN_TYPE_RIFLEGRENADE ); |
|
|
|
if ( ( iMask & iRequiredMask ) == iRequiredMask ) // must have all these bits set |
|
{ |
|
if ( ( iMask & iExplosiveMask ) > 0 ) // has one of these bits set |
|
{ |
|
AwardAchievement( ACHIEVEMENT_DOD_WEAPON_MASTERY ); |
|
} |
|
} |
|
} |
|
|
|
void CDODPlayer::PlayUseDenySound() |
|
{ |
|
EmitSound( "Player.UseDeny" ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Removes all nemesis relationships between this player and others |
|
//----------------------------------------------------------------------------- |
|
void CDODPlayer::RemoveNemesisRelationships() |
|
{ |
|
for ( int i = 1 ; i <= gpGlobals->maxClients ; i++ ) |
|
{ |
|
CDODPlayer *pPlayer = ToDODPlayer( UTIL_PlayerByIndex( i ) ); |
|
if ( pPlayer && pPlayer != this ) |
|
{ |
|
// we are no longer dominating anyone |
|
m_Shared.SetPlayerDominated( pPlayer, false ); |
|
Q_memset( iNumKilledByUnanswered, 0, sizeof( iNumKilledByUnanswered ) ); |
|
|
|
// noone is dominating us anymore |
|
pPlayer->m_Shared.SetPlayerDominated( this, false ); |
|
pPlayer->iNumKilledByUnanswered[entindex()] = 0; |
|
} |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Called when we get a new achievement, recalc our awards |
|
//----------------------------------------------------------------------------- |
|
void CDODPlayer::OnAchievementEarned( int iAchievement ) |
|
{ |
|
BaseClass::OnAchievementEarned( iAchievement ); |
|
|
|
RecalculateAchievementAwardsMask(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Determine what achievement awards this player has |
|
//----------------------------------------------------------------------------- |
|
void CDODPlayer::RecalculateAchievementAwardsMask( void ) |
|
{ |
|
#if !defined(NO_STEAM) |
|
|
|
if ( steamgameserverapicontext->SteamGameServerStats() ) |
|
{ |
|
CSteamID steamIDForPlayer; |
|
if ( GetSteamID( &steamIDForPlayer ) && steamIDForPlayer.BIndividualAccount() ) |
|
{ |
|
steamgameserverapicontext->SteamGameServerStats()->RequestUserStats( steamIDForPlayer ); |
|
} |
|
} |
|
#endif |
|
} |
|
|
|
#if !defined(NO_STEAM) |
|
//----------------------------------------------------------------------------- |
|
// Purpose: Called when Steam returns a user's stats |
|
//----------------------------------------------------------------------------- |
|
void CDODPlayer::OnGSStatsReceived( GSStatsReceived_t *pResponse ) |
|
{ |
|
if ( pResponse->m_eResult != k_EResultOK ) |
|
return; |
|
|
|
if ( pResponse->m_steamIDUser == GetSteamIDAsUInt64() ) |
|
{ |
|
for ( int i = 1; i < NUM_ACHIEVEMENT_AWARDS; i++ ) |
|
{ |
|
bool bUnlocked; |
|
if ( steamgameserverapicontext->SteamGameServerStats()->GetUserAchievement( pResponse->m_steamIDUser, g_pszAchievementAwards[i], &bUnlocked ) ) |
|
{ |
|
if ( bUnlocked ) |
|
{ |
|
m_iAchievementAwardsMask |= (1<<i); |
|
break; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
#endif |
|
|
|
void CDODPlayer::StatEvent_UploadStats( void ) |
|
{ |
|
int iSecondsAlive = (int)( gpGlobals->curtime - m_flTimeAsClassAccumulator ); |
|
|
|
m_StatProperty.IncrementPlayerClassStat( DODSTAT_PLAYTIME, iSecondsAlive ); |
|
|
|
m_StatProperty.SendStatsToPlayer( this ); |
|
|
|
m_flTimeAsClassAccumulator = gpGlobals->curtime; |
|
} |
|
|
|
void CDODPlayer::StatEvent_KilledPlayer( DODWeaponID iKillingWeapon ) |
|
{ |
|
m_StatProperty.IncrementPlayerClassStat( DODSTAT_KILLS ); |
|
|
|
m_StatProperty.IncrementWeaponStat( iKillingWeapon, DODSTAT_KILLS ); |
|
} |
|
|
|
void CDODPlayer::StatEvent_WasKilled( void ) |
|
{ |
|
m_StatProperty.IncrementPlayerClassStat( DODSTAT_DEATHS ); |
|
} |
|
|
|
void CDODPlayer::StatEvent_RoundWin( void ) |
|
{ |
|
m_StatProperty.IncrementPlayerClassStat( DODSTAT_ROUNDSWON ); |
|
} |
|
|
|
void CDODPlayer::StatEvent_RoundLoss( void ) |
|
{ |
|
m_StatProperty.IncrementPlayerClassStat( DODSTAT_ROUNDSLOST ); |
|
} |
|
|
|
void CDODPlayer::StatEvent_PointCaptured( void ) |
|
{ |
|
m_StatProperty.IncrementPlayerClassStat( DODSTAT_CAPTURES ); |
|
} |
|
|
|
void CDODPlayer::StatEvent_CaptureBlocked( void ) |
|
{ |
|
m_StatProperty.IncrementPlayerClassStat( DODSTAT_BLOCKS ); |
|
} |
|
|
|
void CDODPlayer::StatEvent_BombPlanted( void ) |
|
{ |
|
m_StatProperty.IncrementPlayerClassStat( DODSTAT_BOMBSPLANTED ); |
|
} |
|
|
|
void CDODPlayer::StatEvent_BombDefused( void ) |
|
{ |
|
m_StatProperty.IncrementPlayerClassStat( DODSTAT_BOMBSDEFUSED ); |
|
} |
|
|
|
void CDODPlayer::StatEvent_ScoredDomination( void ) |
|
{ |
|
m_StatProperty.IncrementPlayerClassStat( DODSTAT_DOMINATIONS ); |
|
} |
|
|
|
void CDODPlayer::StatEvent_ScoredRevenge( void ) |
|
{ |
|
m_StatProperty.IncrementPlayerClassStat( DODSTAT_REVENGES ); |
|
} |
|
|
|
void CDODPlayer::StatEvent_WeaponFired( DODWeaponID iWeaponID ) |
|
{ |
|
m_StatProperty.IncrementWeaponStat( iWeaponID, DODSTAT_SHOTS_FIRED ); |
|
} |
|
|
|
void CDODPlayer::StatEvent_WeaponHit( DODWeaponID iWeaponID, bool bWasHeadshot ) |
|
{ |
|
m_StatProperty.IncrementWeaponStat( iWeaponID, DODSTAT_SHOTS_HIT ); |
|
|
|
if ( bWasHeadshot ) |
|
{ |
|
m_StatProperty.IncrementWeaponStat( iWeaponID, DODSTAT_HEADSHOTS ); |
|
} |
|
} |
|
|
|
|
|
// CDODPlayerStatProperty |
|
|
|
void CDODPlayerStatProperty::SetClassAndTeamForThisLife( int iPlayerClass, int iTeam ) |
|
{ |
|
m_bRecordingStats = ( ( iTeam == TEAM_ALLIES || iTeam == TEAM_AXIS ) && ( iPlayerClass >= 0 && iPlayerClass <= NUM_DOD_PLAYERCLASSES ) ); |
|
|
|
m_iCurrentLifePlayerClass = iPlayerClass; |
|
m_iCurrentLifePlayerTeam = iTeam; |
|
} |
|
|
|
void CDODPlayerStatProperty::IncrementPlayerClassStat( DODStatType_t statType, int iValue /* = 1 */ ) |
|
{ |
|
if ( !m_bRecordingStats ) |
|
return; |
|
|
|
Assert( m_iCurrentLifePlayerClass >= 0 && m_iCurrentLifePlayerClass <= 5 ); |
|
|
|
m_PlayerStatsPerLife.m_iStat[statType] += iValue; |
|
} |
|
|
|
void CDODPlayerStatProperty::IncrementWeaponStat( DODWeaponID iWeaponID, DODStatType_t statType, int iValue /* = 1 */ ) |
|
{ |
|
if ( !m_bRecordingStats ) |
|
return; |
|
|
|
Assert( iWeaponID >= 0 && iWeaponID <= WEAPON_MAX ); |
|
m_WeaponStatsPerLife[iWeaponID].m_iStat[statType] += iValue; |
|
m_bWeaponStatsDirty[iWeaponID] = true; |
|
} |
|
|
|
void CDODPlayerStatProperty::ResetPerLifeStats( void ) |
|
{ |
|
Q_memset( &m_PlayerStatsPerLife, 0, sizeof(m_PlayerStatsPerLife) ); |
|
Q_memset( &m_WeaponStatsPerLife, 0, sizeof(m_WeaponStatsPerLife) ); |
|
Q_memset( &m_bWeaponStatsDirty, 0, sizeof(m_bWeaponStatsDirty) ); |
|
} |
|
|
|
void CDODPlayerStatProperty::SendStatsToPlayer( CDODPlayer *pPlayer ) |
|
{ |
|
if ( !m_bRecordingStats ) |
|
return; |
|
|
|
// make a bit field of all the stats we want to send (all with non-zero values) |
|
int iStat; |
|
int iSendPlayerBits = 0; |
|
for ( iStat = DODSTAT_FIRST; iStat < DODSTAT_MAX; iStat++ ) |
|
{ |
|
if ( m_PlayerStatsPerLife.m_iStat[iStat] > 0 ) |
|
{ |
|
iSendPlayerBits |= ( 1 << ( iStat - DODSTAT_FIRST ) ); |
|
} |
|
} |
|
|
|
CUtlVector<int> vecWeaponsToSend; |
|
|
|
// for every weapon that we have stats for, set a bit in the weapon mask |
|
for ( int iWeapon = WEAPON_NONE; iWeapon < WEAPON_MAX; iWeapon++ ) |
|
{ |
|
if ( m_bWeaponStatsDirty[iWeapon] ) |
|
{ |
|
vecWeaponsToSend.AddToTail( iWeapon ); |
|
} |
|
} |
|
|
|
if ( iSendPlayerBits == 0 && vecWeaponsToSend.Count() == 0 ) |
|
{ |
|
ResetPerLifeStats(); |
|
return; |
|
} |
|
|
|
iStat = DODSTAT_FIRST; |
|
|
|
CSingleUserRecipientFilter filter( (CBasePlayer *)pPlayer ); |
|
filter.MakeReliable(); |
|
|
|
UserMessageBegin( filter, "DODPlayerStatsUpdate" ); |
|
|
|
WRITE_BYTE( m_iCurrentLifePlayerClass ); // write the class |
|
WRITE_BYTE( m_iCurrentLifePlayerTeam ); |
|
|
|
WRITE_LONG( iSendPlayerBits ); // write the bit mask of which stats follow in the message |
|
|
|
// write all the non-zero stats according to the bit mask |
|
while ( iSendPlayerBits > 0 ) |
|
{ |
|
if ( iSendPlayerBits & 1 ) |
|
{ |
|
WRITE_LONG( m_PlayerStatsPerLife.m_iStat[iStat] ); |
|
} |
|
iSendPlayerBits >>= 1; |
|
iStat++; |
|
} |
|
|
|
// now the weapon bits |
|
// how many weapons |
|
WRITE_BYTE( vecWeaponsToSend.Count() ); |
|
|
|
int i; |
|
|
|
// send the weapons |
|
for ( i=0;i<vecWeaponsToSend.Count(); i++ ) |
|
{ |
|
WRITE_BYTE( vecWeaponsToSend.Element(i) ); |
|
} |
|
|
|
// for each weapon that we're sending stats for |
|
for ( i=0;i<vecWeaponsToSend.Count(); i++ ) |
|
{ |
|
int iWeapon = vecWeaponsToSend.Element(i); |
|
|
|
// what stats does iWeapon want to send? |
|
int iWeaponStatBits = 0; |
|
|
|
for ( iStat = DODSTAT_FIRST; iStat < DODSTAT_MAX; iStat++ ) |
|
{ |
|
if ( m_WeaponStatsPerLife[iWeapon].m_iStat[iStat] > 0 ) |
|
{ |
|
iWeaponStatBits |= ( 1 << ( iStat - DODSTAT_FIRST ) ); |
|
} |
|
} |
|
|
|
// send the mask of stats that we're sending for this weapon |
|
WRITE_LONG( iWeaponStatBits ); |
|
|
|
// send the stats |
|
iStat = DODSTAT_FIRST; |
|
|
|
// send that mask |
|
while ( iWeaponStatBits > 0 ) |
|
{ |
|
if ( iWeaponStatBits & 1 ) |
|
{ |
|
WRITE_LONG( m_WeaponStatsPerLife[iWeapon].m_iStat[iStat] ); |
|
} |
|
iWeaponStatBits >>= 1; |
|
iStat++; |
|
} |
|
} |
|
|
|
MessageEnd(); |
|
|
|
ResetPerLifeStats(); |
|
}
|
|
|