Team Fortress 2 Source Code as on 22/4/2020
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

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: Player for HL1.
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================//
  7. #include "cbase.h"
  8. #include "dod_player.h"
  9. #include "dod_gamerules.h"
  10. #include "team.h" //GetGlobalTeam
  11. #include "in_buttons.h"
  12. #include "dod_shareddefs.h"
  13. #include "dod_cvars.h"
  14. #include "weapon_dodbase.h"
  15. #include "weapon_dodbasegrenade.h"
  16. #include "weapon_dodbaserpg.h"
  17. #include "weapon_dodbipodgun.h"
  18. #include "dod_basegrenade.h"
  19. #include "dod_ammo_box.h"
  20. #include "effect_dispatch_data.h"
  21. #include "movehelper_server.h"
  22. #include "tier0/vprof.h"
  23. #include "te_effect_dispatch.h"
  24. #include "vphysics/player_controller.h"
  25. #include <KeyValues.h>
  26. #include "engine/IEngineSound.h"
  27. #include "studio.h"
  28. #include "dod_viewmodel.h"
  29. #include "info_camera_link.h"
  30. #include "dod_team.h"
  31. #include "dod_control_point.h"
  32. #include "dod_location.h"
  33. #include "viewport_panel_names.h"
  34. #include "obstacle_pushaway.h"
  35. #include "datacache/imdlcache.h"
  36. #include "bone_setup.h"
  37. #include "dod_baserocket.h"
  38. #include "dod_basegrenade.h"
  39. #include "dod_bombtarget.h"
  40. #include "collisionutils.h"
  41. #include "gamestats.h"
  42. #include "gameinterface.h"
  43. #include "holiday_gift.h"
  44. // memdbgon must be the last include file in a .cpp file!!!
  45. #include "tier0/memdbgon.h"
  46. #pragma warning( disable: 4355 ) // disables ' 'this' : used in base member initializer list'
  47. 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." );
  48. 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." );
  49. EHANDLE g_pLastAlliesSpawn;
  50. EHANDLE g_pLastAxisSpawn;
  51. int g_iLastAlliesSpawnIndex = 0;
  52. int g_iLastAxisSpawnIndex = 0;
  53. ConVar dod_playerstatetransitions( "dod_playerstatetransitions", "-2", FCVAR_CHEAT, "dod_playerstatetransitions <ent index or -1 for all>. Show player state transitions." );
  54. ConVar dod_debugdamage( "dod_debugdamage", "0", FCVAR_CHEAT );
  55. ConVar mp_bandage_heal_amount( "mp_bandage_heal_amount",
  56. "40",
  57. FCVAR_GAMEDLL,
  58. "How much health to give after a successful bandage\n",
  59. true, 0, false, 0 );
  60. ConVar dod_freezecam( "dod_freezecam", "1", FCVAR_REPLICATED | FCVAR_ARCHIVE, "show players a freezecam shot of their killer on death" );
  61. extern ConVar spec_freeze_time;
  62. extern ConVar spec_freeze_traveltime;
  63. #define DOD_PUSHAWAY_THINK_CONTEXT "DODPushawayThink"
  64. #define DOD_DEAFEN_CONTEXT "DeafenContext"
  65. int g_iCurrentLifeID = 0;
  66. int GetNextLifeID( void )
  67. {
  68. return ++g_iCurrentLifeID;
  69. }
  70. // -------------------------------------------------------------------------------- //
  71. // Classes
  72. // -------------------------------------------------------------------------------- //
  73. class CPhysicsPlayerCallback : public IPhysicsPlayerControllerEvent
  74. {
  75. public:
  76. int ShouldMoveTo( IPhysicsObject *pObject, const Vector &position )
  77. {
  78. CDODPlayer *pPlayer = (CDODPlayer *)pObject->GetGameData();
  79. if ( pPlayer )
  80. {
  81. if ( pPlayer->TouchedPhysics() )
  82. {
  83. return 0;
  84. }
  85. }
  86. return 1;
  87. }
  88. };
  89. static CPhysicsPlayerCallback playerCallback;
  90. // -------------------------------------------------------------------------------- //
  91. // Ragdoll entities.
  92. // -------------------------------------------------------------------------------- //
  93. class CDODRagdoll : public CBaseAnimatingOverlay
  94. {
  95. public:
  96. DECLARE_CLASS( CDODRagdoll, CBaseAnimatingOverlay );
  97. DECLARE_SERVERCLASS();
  98. // Transmit ragdolls to everyone.
  99. virtual int UpdateTransmitState()
  100. {
  101. return SetTransmitState( FL_EDICT_ALWAYS );
  102. }
  103. public:
  104. // In case the client has the player entity, we transmit the player index.
  105. // In case the client doesn't have it, we transmit the player's model index, origin, and angles
  106. // so they can create a ragdoll in the right place.
  107. CNetworkHandle( CBaseEntity, m_hPlayer ); // networked entity handle
  108. CNetworkVector( m_vecRagdollVelocity );
  109. CNetworkVector( m_vecRagdollOrigin );
  110. };
  111. LINK_ENTITY_TO_CLASS( dod_ragdoll, CDODRagdoll );
  112. IMPLEMENT_SERVERCLASS_ST_NOBASE( CDODRagdoll, DT_DODRagdoll )
  113. SendPropVector( SENDINFO(m_vecRagdollOrigin), -1, SPROP_COORD ),
  114. SendPropEHandle( SENDINFO( m_hPlayer ) ),
  115. SendPropModelIndex( SENDINFO( m_nModelIndex ) ),
  116. SendPropInt ( SENDINFO(m_nForceBone), 8, 0 ),
  117. SendPropVector ( SENDINFO(m_vecForce), -1, SPROP_NOSCALE ),
  118. SendPropVector( SENDINFO( m_vecRagdollVelocity ) )
  119. END_SEND_TABLE()
  120. // -------------------------------------------------------------------------------- //
  121. // Player animation event. Sent to the client when a player fires, jumps, reloads, etc..
  122. // -------------------------------------------------------------------------------- //
  123. class CTEPlayerAnimEvent : public CBaseTempEntity
  124. {
  125. public:
  126. DECLARE_CLASS( CTEPlayerAnimEvent, CBaseTempEntity );
  127. DECLARE_SERVERCLASS();
  128. CTEPlayerAnimEvent( const char *name ) : CBaseTempEntity( name )
  129. {
  130. }
  131. CNetworkHandle( CBasePlayer, m_hPlayer );
  132. CNetworkVar( int, m_iEvent );
  133. CNetworkVar( int, m_nData );
  134. };
  135. IMPLEMENT_SERVERCLASS_ST_NOBASE( CTEPlayerAnimEvent, DT_TEPlayerAnimEvent )
  136. SendPropEHandle( SENDINFO( m_hPlayer ) ),
  137. SendPropInt( SENDINFO( m_iEvent ), Q_log2( PLAYERANIMEVENT_COUNT ) + 1, SPROP_UNSIGNED ),
  138. SendPropInt( SENDINFO( m_nData ), 32 )
  139. END_SEND_TABLE()
  140. static CTEPlayerAnimEvent g_TEPlayerAnimEvent( "PlayerAnimEvent" );
  141. void TE_PlayerAnimEvent( CBasePlayer *pPlayer, PlayerAnimEvent_t event, int nData )
  142. {
  143. CPVSFilter filter( (const Vector&)pPlayer->EyePosition() );
  144. g_TEPlayerAnimEvent.m_hPlayer = pPlayer;
  145. g_TEPlayerAnimEvent.m_iEvent = event;
  146. g_TEPlayerAnimEvent.m_nData = nData;
  147. g_TEPlayerAnimEvent.Create( filter, 0 );
  148. }
  149. //-----------------------------------------------------------------------------
  150. // Purpose: Filters updates to a variable so that only non-local players see
  151. // the changes. This is so we can send a low-res origin to non-local players
  152. // while sending a hi-res one to the local player.
  153. // Input : *pVarData -
  154. // *pOut -
  155. // objectID -
  156. //-----------------------------------------------------------------------------
  157. void* SendProxy_SendNonLocalDataTable( const SendProp *pProp, const void *pStruct, const void *pVarData, CSendProxyRecipients *pRecipients, int objectID )
  158. {
  159. pRecipients->SetAllRecipients();
  160. pRecipients->ClearRecipient( objectID - 1 );
  161. return ( void * )pVarData;
  162. }
  163. // -------------------------------------------------------------------------------- //
  164. // Tables.
  165. // -------------------------------------------------------------------------------- //
  166. LINK_ENTITY_TO_CLASS( player, CDODPlayer );
  167. PRECACHE_REGISTER(player);
  168. // CDODPlayerShared Data Tables
  169. //=============================
  170. // specific to the local player
  171. BEGIN_SEND_TABLE_NOBASE( CDODPlayerShared, DT_DODSharedLocalPlayerExclusive )
  172. SendPropInt( SENDINFO( m_iPlayerClass), 4 ),
  173. SendPropInt( SENDINFO( m_iDesiredPlayerClass ), 4 ),
  174. SendPropFloat( SENDINFO( m_flDeployedYawLimitLeft ) ),
  175. SendPropFloat( SENDINFO( m_flDeployedYawLimitRight ) ),
  176. SendPropInt( SENDINFO( m_iCPIndex ), 4 ),
  177. SendPropArray3( SENDINFO_ARRAY3( m_bPlayerDominated ), SendPropBool( SENDINFO_ARRAY( m_bPlayerDominated ) ) ),
  178. SendPropArray3( SENDINFO_ARRAY3( m_bPlayerDominatingMe ), SendPropBool( SENDINFO_ARRAY( m_bPlayerDominatingMe ) ) ),
  179. END_SEND_TABLE()
  180. // main table
  181. BEGIN_SEND_TABLE_NOBASE( CDODPlayerShared, DT_DODPlayerShared )
  182. SendPropFloat( SENDINFO( m_flStamina ), 0, SPROP_NOSCALE | SPROP_CHANGES_OFTEN ),
  183. SendPropTime( SENDINFO( m_flSlowedUntilTime ) ),
  184. SendPropBool( SENDINFO( m_bProne ) ),
  185. SendPropBool( SENDINFO( m_bIsSprinting ) ),
  186. SendPropTime( SENDINFO( m_flGoProneTime ) ),
  187. SendPropTime( SENDINFO( m_flUnProneTime ) ),
  188. SendPropTime( SENDINFO( m_flDeployChangeTime ) ),
  189. SendPropTime( SENDINFO( m_flDeployedHeight ) ),
  190. SendPropBool( SENDINFO( m_bPlanting ) ),
  191. SendPropBool( SENDINFO( m_bDefusing ) ),
  192. SendPropDataTable( "dodsharedlocaldata", 0, &REFERENCE_SEND_TABLE(DT_DODSharedLocalPlayerExclusive), SendProxy_SendLocalDataTable ),
  193. END_SEND_TABLE()
  194. // CDODPlayer Data Tables
  195. //=========================
  196. extern void SendProxy_Origin( const SendProp *pProp, const void *pStruct, const void *pData, DVariant *pOut, int iElement, int objectID );
  197. // specific to the local player
  198. BEGIN_SEND_TABLE_NOBASE( CDODPlayer, DT_DODLocalPlayerExclusive )
  199. // send a hi-res origin to the local player for use in prediction
  200. SendPropVector (SENDINFO(m_vecOrigin), -1, SPROP_NOSCALE|SPROP_CHANGES_OFTEN, 0.0f, HIGH_DEFAULT, SendProxy_Origin ),
  201. SendPropFloat( SENDINFO(m_flStunDuration), 0, SPROP_NOSCALE ),
  202. SendPropFloat( SENDINFO(m_flStunMaxAlpha), 0, SPROP_NOSCALE ),
  203. SendPropInt( SENDINFO( m_iProgressBarDuration ), 4, SPROP_UNSIGNED ),
  204. SendPropFloat( SENDINFO( m_flProgressBarStartTime ), 0, SPROP_NOSCALE ),
  205. END_SEND_TABLE()
  206. // all players except the local player
  207. BEGIN_SEND_TABLE_NOBASE( CDODPlayer, DT_DODNonLocalPlayerExclusive )
  208. // send a lo-res origin to other players
  209. SendPropVector (SENDINFO(m_vecOrigin), -1, SPROP_COORD|SPROP_CHANGES_OFTEN, 0.0f, HIGH_DEFAULT, SendProxy_Origin ),
  210. END_SEND_TABLE()
  211. // main table
  212. IMPLEMENT_SERVERCLASS_ST( CDODPlayer, DT_DODPlayer )
  213. SendPropExclude( "DT_BaseAnimating", "m_flPoseParameter" ),
  214. SendPropExclude( "DT_BaseAnimating", "m_flPlaybackRate" ),
  215. SendPropExclude( "DT_BaseAnimating", "m_nSequence" ),
  216. SendPropExclude( "DT_BaseAnimating", "m_nNewSequenceParity" ),
  217. SendPropExclude( "DT_BaseAnimating", "m_nResetEventsParity" ),
  218. SendPropExclude( "DT_BaseEntity", "m_angRotation" ),
  219. SendPropExclude( "DT_BaseAnimatingOverlay", "overlay_vars" ),
  220. // dod_playeranimstate and clientside animation takes care of these on the client
  221. SendPropExclude( "DT_ServerAnimationData" , "m_flCycle" ),
  222. SendPropExclude( "DT_AnimTimeMustBeFirst" , "m_flAnimTime" ),
  223. // We need to send a hi-res origin to the local player to avoid prediction errors sliding along walls
  224. SendPropExclude( "DT_BaseEntity", "m_vecOrigin" ),
  225. // Data that only gets sent to the local player.
  226. SendPropDataTable( SENDINFO_DT( m_Shared ), &REFERENCE_SEND_TABLE( DT_DODPlayerShared ) ),
  227. // Data that only gets sent to the local player.
  228. SendPropDataTable( "dodlocaldata", 0, &REFERENCE_SEND_TABLE(DT_DODLocalPlayerExclusive), SendProxy_SendLocalDataTable ),
  229. SendPropDataTable( "dodnonlocaldata", 0, &REFERENCE_SEND_TABLE(DT_DODNonLocalPlayerExclusive), SendProxy_SendNonLocalDataTable ),
  230. SendPropAngle( SENDINFO_VECTORELEM(m_angEyeAngles, 0), 13, SPROP_CHANGES_OFTEN ),
  231. SendPropAngle( SENDINFO_VECTORELEM(m_angEyeAngles, 1), 13, SPROP_CHANGES_OFTEN ),
  232. SendPropEHandle( SENDINFO( m_hRagdoll ) ),
  233. SendPropBool( SENDINFO( m_bSpawnInterpCounter ) ),
  234. SendPropInt( SENDINFO( m_iAchievementAwardsMask ), NUM_ACHIEVEMENT_AWARDS, SPROP_UNSIGNED ),
  235. END_SEND_TABLE()
  236. BEGIN_DATADESC( CDODPlayer )
  237. DEFINE_THINKFUNC( PushawayThink ),
  238. DEFINE_THINKFUNC( DeafenThink )
  239. END_DATADESC()
  240. // -------------------------------------------------------------------------------- //
  241. void cc_CreatePredictionError_f()
  242. {
  243. CBaseEntity *pEnt = CBaseEntity::Instance( 1 );
  244. pEnt->SetAbsOrigin( pEnt->GetAbsOrigin() + Vector( 63, 0, 0 ) );
  245. }
  246. ConCommand cc_CreatePredictionError( "CreatePredictionError", cc_CreatePredictionError_f, "Create a prediction error", FCVAR_CHEAT );
  247. bool PlayerStatLessFunc( const int &lhs, const int &rhs )
  248. {
  249. return lhs < rhs;
  250. }
  251. // -------------------------------------------------------------------------------- //
  252. // CDODPlayer implementation.
  253. // -------------------------------------------------------------------------------- //
  254. CDODPlayer::CDODPlayer()
  255. #if !defined(NO_STEAM)
  256. : m_CallbackGSStatsReceived( this, &CDODPlayer::OnGSStatsReceived )
  257. #endif
  258. {
  259. m_PlayerAnimState = CreatePlayerAnimState( this );
  260. m_Shared.Init( this );
  261. UseClientSideAnimation();
  262. m_angEyeAngles.Init();
  263. SetViewOffset( DOD_PLAYER_VIEW_OFFSET );
  264. m_pCurStateInfo = NULL; // no state yet
  265. m_lifeState = LIFE_DEAD; // Start "dead".
  266. m_Shared.SetPlayerClass( PLAYERCLASS_UNDEFINED );
  267. m_bTeamChanged = false;
  268. // Clear the signals
  269. m_signals.Update();
  270. m_fHandleSignalsTime = 0.0f;
  271. // autokick
  272. m_flLastMovement = gpGlobals->curtime;
  273. m_flNextVoice = gpGlobals->curtime;
  274. m_flNextHandSignal = gpGlobals->curtime;
  275. m_flNextTimeCheck = gpGlobals->curtime;
  276. m_bAutoReload = true;
  277. // Init our hint system
  278. Hints()->Init( this, NUM_HINTS, g_pszHintMessages );
  279. Hints()->RegisterHintTimer( HINT_USE_MELEE, 60 );
  280. Hints()->RegisterHintTimer( HINT_USE_ZOOM, 30 );
  281. Hints()->RegisterHintTimer( HINT_USE_IRON_SIGHTS, 60 );
  282. Hints()->RegisterHintTimer( HINT_USE_SEMI_AUTO, 60 );
  283. Hints()->RegisterHintTimer( HINT_USE_SPRINT, 120 );
  284. Hints()->RegisterHintTimer( HINT_USE_DEPLOY, 30 );
  285. Hints()->RegisterHintTimer( HINT_USE_PRIME, 2 );
  286. memset( &m_WeaponStats, 0, sizeof(weaponstat_t) );
  287. m_KilledPlayers.SetLessFunc( PlayerStatLessFunc );
  288. m_KilledByPlayers.SetLessFunc( PlayerStatLessFunc );
  289. m_iNumAreaDefenses = 0;
  290. m_iNumAreaCaptures = 0;
  291. m_iNumBonusRoundKills = 0;
  292. memset( &m_flTimePlayedPerClass_Allies, 0, sizeof(m_flTimePlayedPerClass_Allies) );
  293. memset( &m_flTimePlayedPerClass_Axis, 0, sizeof(m_flTimePlayedPerClass_Axis) );
  294. m_flLastClassChangeTime = gpGlobals->curtime;
  295. Q_memset( iNumKilledByUnanswered, 0, sizeof( iNumKilledByUnanswered ) );
  296. m_iAchievementAwardsMask = 0;
  297. m_hLastDroppedWeapon = NULL;
  298. m_hLastDroppedAmmoBox = NULL;
  299. m_flTimeAsClassAccumulator = 0;
  300. }
  301. CDODPlayer::~CDODPlayer()
  302. {
  303. m_PlayerAnimState->Release();
  304. }
  305. CDODPlayer *CDODPlayer::CreatePlayer( const char *className, edict_t *ed )
  306. {
  307. CDODPlayer::s_PlayerEdict = ed;
  308. return (CDODPlayer*)CreateEntityByName( className );
  309. }
  310. void CDODPlayer::PrecachePlayerModel( const char *szPlayerModel )
  311. {
  312. PrecacheModel( szPlayerModel );
  313. // Strange numbers, but gotten from the actual max bounds of our
  314. // player models
  315. const static Vector mins( -13.9, -30.1, -12.1 );
  316. const static Vector maxs( 30.9, 30.1, 73.1 );
  317. // Moved to pure_server_minimal.txt
  318. // engine->ForceModelBounds( szPlayerModel, mins, maxs );
  319. }
  320. void CDODPlayer::Precache()
  321. {
  322. PrecachePlayerModel( DOD_PLAYERMODEL_AXIS_RIFLEMAN );
  323. PrecachePlayerModel( DOD_PLAYERMODEL_AXIS_ASSAULT );
  324. PrecachePlayerModel( DOD_PLAYERMODEL_AXIS_SUPPORT );
  325. PrecachePlayerModel( DOD_PLAYERMODEL_AXIS_SNIPER );
  326. PrecachePlayerModel( DOD_PLAYERMODEL_AXIS_MG );
  327. PrecachePlayerModel( DOD_PLAYERMODEL_AXIS_ROCKET );
  328. PrecachePlayerModel( DOD_PLAYERMODEL_US_RIFLEMAN );
  329. PrecachePlayerModel( DOD_PLAYERMODEL_US_ASSAULT );
  330. PrecachePlayerModel( DOD_PLAYERMODEL_US_SUPPORT );
  331. PrecachePlayerModel( DOD_PLAYERMODEL_US_SNIPER );
  332. PrecachePlayerModel( DOD_PLAYERMODEL_US_MG );
  333. PrecachePlayerModel( DOD_PLAYERMODEL_US_ROCKET );
  334. /// Moved to pure_server_minimal.txt
  335. // engine->ForceSimpleMaterial( "materials/models/player/american/american_body.vmt" );
  336. // engine->ForceSimpleMaterial( "materials/models/player/american/american_gear.vmt" );
  337. // engine->ForceSimpleMaterial( "materials/models/player/german/german_body.vmt" );
  338. // engine->ForceSimpleMaterial( "materials/models/player/german/german_gear.vmt" );
  339. UTIL_PrecacheOther( "dod_ammo_box" );
  340. PrecacheScriptSound( "Player.FlashlightOn" );
  341. PrecacheScriptSound( "Player.FlashlightOff" );
  342. PrecacheScriptSound( "Player.FreezeCam" );
  343. PrecacheScriptSound( "Camera.SnapShot" );
  344. PrecacheScriptSound( "Achievement.Earned" );
  345. PrecacheScriptSound( "Game.Revenge" );
  346. PrecacheScriptSound( "Game.Domination" );
  347. PrecacheScriptSound( "Game.Nemesis" );
  348. PrecacheModel ( "sprites/glow01.vmt" );
  349. BaseClass::Precache();
  350. }
  351. //-----------------------------------------------------------------------------
  352. // Purpose: Allow pre-frame adjustments on the player
  353. //-----------------------------------------------------------------------------
  354. ConVar sv_runcmds( "sv_runcmds", "1" );
  355. void CDODPlayer::PlayerRunCommand( CUserCmd *ucmd, IMoveHelper *moveHelper )
  356. {
  357. VPROF( "CDODPlayer::PlayerRunCommand" );
  358. if ( !sv_runcmds.GetInt() )
  359. return;
  360. // don't run commands in the future
  361. if ( !IsEngineThreaded() &&
  362. ( ucmd->tick_count > (gpGlobals->tickcount + sv_max_usercmd_future_ticks.GetInt()) ) )
  363. {
  364. DevMsg( "Client cmd out of sync (delta %i).\n", ucmd->tick_count - gpGlobals->tickcount );
  365. return;
  366. }
  367. BaseClass::PlayerRunCommand( ucmd, moveHelper );
  368. }
  369. //-----------------------------------------------------------------------------
  370. // Purpose: Simulates a single frame of movement for a player
  371. //-----------------------------------------------------------------------------
  372. void CDODPlayer::RunPlayerMove( const QAngle& viewangles, float forwardmove, float sidemove, float upmove, unsigned short buttons, byte impulse, float frametime )
  373. {
  374. CUserCmd cmd;
  375. // Store off the globals.. they're gonna get whacked
  376. float flOldFrametime = gpGlobals->frametime;
  377. float flOldCurtime = gpGlobals->curtime;
  378. float flTimeBase = gpGlobals->curtime + gpGlobals->frametime - frametime;
  379. this->SetTimeBase( flTimeBase );
  380. CUserCmd lastUserCmd = *GetLastUserCommand();
  381. Q_memset( &cmd, 0, sizeof( cmd ) );
  382. MoveHelperServer()->SetHost( this );
  383. this->PlayerRunCommand( &cmd, MoveHelperServer() );
  384. this->SetLastUserCommand( cmd );
  385. // Clear out any fixangle that has been set
  386. this->pl.fixangle = FIXANGLE_NONE;
  387. // Restore the globals..
  388. gpGlobals->frametime = flOldFrametime;
  389. gpGlobals->curtime = flOldCurtime;
  390. }
  391. void CDODPlayer::InitialSpawn( void )
  392. {
  393. BaseClass::InitialSpawn();
  394. State_Enter( STATE_WELCOME );
  395. }
  396. void CDODPlayer::Spawn()
  397. {
  398. // Get rid of the progress bar...
  399. SetProgressBarTime( 0 );
  400. SetModel( DOD_PLAYERMODEL_US_RIFLEMAN ); //temporarily
  401. BaseClass::Spawn();
  402. AddFlag( FL_ONGROUND ); // set the player on the ground at the start of the round.
  403. m_Shared.SetStamina( 100 );
  404. m_bTeamChanged = false;
  405. ClearCapAreaIndex();
  406. m_bHasGenericAmmo = true;
  407. m_bSlowedByHit = false;
  408. m_flIdleTime = gpGlobals->curtime + random->RandomFloat( 20, 30 );
  409. InitProne();
  410. InitSprinting();
  411. // update this counter, used to not interp players when they spawn
  412. m_bSpawnInterpCounter = !m_bSpawnInterpCounter;
  413. EmitSound( "Player.Spawn" );
  414. SetContextThink( &CDODPlayer::PushawayThink, gpGlobals->curtime + PUSHAWAY_THINK_INTERVAL, DOD_PUSHAWAY_THINK_CONTEXT );
  415. m_Shared.SetCPIndex( -1 );
  416. ResetBleeding();
  417. // Our own version of remove decals, as we can't pass entity messages to
  418. // the target's base class.
  419. EntityMessageBegin( this );
  420. WRITE_BYTE( DOD_PLAYER_REMOVE_DECALS );
  421. MessageEnd();
  422. if ( m_Shared.PlayerClass() != PLAYERCLASS_UNDEFINED )
  423. {
  424. Hints()->StartHintTimer( HINT_USE_SPRINT );
  425. }
  426. // Get a unique "life id" used for tracking stats
  427. m_iLifeID = GetNextLifeID();
  428. SetDefusing( NULL );
  429. SetPlanting( NULL );
  430. m_iLastBlockAreaIndex = -1;
  431. m_iLastBlockCapAttempt = -1;
  432. if ( DODGameRules()->IsBombingTeam( GetTeamNumber() ) )
  433. {
  434. Hints()->HintMessage( HINT_BOMB_PLANT_MAP );
  435. }
  436. //CollisionProp()->SetSurroundingBoundsType( USE_GAME_CODE );
  437. // Reset per-life achievement counts
  438. HandleHeadshotAchievement( 0 );
  439. HandleDeployedMGKillCount( 0 );
  440. HandleEnemyWeaponsAchievement( 0 );
  441. ResetComboWeaponKill();
  442. RecalculateAchievementAwardsMask();
  443. // If we're spawning as a non-player team, flush the stats
  444. int iTeam = GetTeamNumber();
  445. if ( iTeam != TEAM_ALLIES && iTeam != TEAM_AXIS )
  446. {
  447. StatEvent_UploadStats();
  448. }
  449. m_StatProperty.SetClassAndTeamForThisLife( m_Shared.PlayerClass(), GetTeamNumber() );
  450. }
  451. void CDODPlayer::PlayerDeathThink()
  452. {
  453. //overridden, do nothing
  454. }
  455. void CDODPlayer::CreateRagdollEntity()
  456. {
  457. // If we already have a ragdoll, don't make another one.
  458. CDODRagdoll *pRagdoll = dynamic_cast< CDODRagdoll* >( m_hRagdoll.Get() );
  459. if( pRagdoll )
  460. {
  461. // MATTTODO: put ragdolls in a queue to disappear
  462. // for now remove the old one ..
  463. UTIL_Remove( pRagdoll );
  464. pRagdoll = NULL;
  465. }
  466. if ( !pRagdoll )
  467. {
  468. // and create a new one
  469. pRagdoll = dynamic_cast< CDODRagdoll* >( CreateEntityByName( "dod_ragdoll" ) );
  470. }
  471. if ( pRagdoll )
  472. {
  473. pRagdoll->m_hPlayer = this;
  474. pRagdoll->m_vecRagdollOrigin = GetAbsOrigin();
  475. pRagdoll->m_vecRagdollVelocity = GetAbsVelocity();
  476. pRagdoll->m_nModelIndex = m_nModelIndex;
  477. pRagdoll->m_nForceBone = m_nForceBone;
  478. pRagdoll->m_vecForce = m_vecTotalBulletForce;
  479. }
  480. // ragdolls will be removed on round restart automatically
  481. m_hRagdoll = pRagdoll;
  482. }
  483. // Called when a player is disconnecting
  484. void CDODPlayer::DestroyRagdoll( void )
  485. {
  486. CDODRagdoll *pRagdoll = dynamic_cast< CDODRagdoll* >( m_hRagdoll.Get() );
  487. if( pRagdoll )
  488. {
  489. UTIL_Remove( pRagdoll );
  490. }
  491. }
  492. void CDODPlayer::Event_Killed( const CTakeDamageInfo &info )
  493. {
  494. // allow bots to react
  495. // TheBots->OnEvent( EVENT_PLAYER_DIED, this, info.GetAttacker() );
  496. CBaseEntity *pInflictor = info.GetInflictor();
  497. CBaseEntity *pKiller = info.GetAttacker();
  498. CDODPlayer *pScorer = ToDODPlayer( DODGameRules()->GetDeathScorer( pKiller, pInflictor ) );
  499. // Do this before we drop the victim's weapons to check the streak
  500. if ( GetDeployedKillStreak() >= ACHIEVEMENT_MG_STREAK_IS_DOMINATING )
  501. {
  502. if ( pScorer && pScorer->GetTeamNumber() != GetTeamNumber() && DODGameRules()->State_Get() == STATE_RND_RUNNING )
  503. {
  504. pScorer->AwardAchievement( ACHIEVEMENT_DOD_KILL_DOMINATING_MG );
  505. }
  506. }
  507. // see if this was a long range kill
  508. // distance from scorer to inflictor > 1200
  509. if ( pScorer && GetTeamNumber() != pScorer->GetTeamNumber() && pInflictor != pScorer )
  510. {
  511. const char *killer_weapon_name = STRING( pInflictor->m_iClassname );
  512. if ( strncmp( killer_weapon_name, "rocket_", 7 ) == 0 )
  513. {
  514. float flDist = ( pScorer->GetAbsOrigin() - pInflictor->GetAbsOrigin() ).Length();
  515. if ( flDist > ACHIEVEMENT_LONG_RANGE_ROCKET_DIST && DODGameRules()->State_Get() == STATE_RND_RUNNING )
  516. {
  517. bool bIsSniperZoomed = false;
  518. CWeaponDODBase *pWeapon = GetActiveDODWeapon();
  519. if( pWeapon && ( pWeapon->GetDODWpnData().m_WeaponType == WPN_TYPE_SNIPER ) )
  520. {
  521. CWeaponDODBaseGun *pGun = static_cast<CWeaponDODBaseGun *>( pWeapon );
  522. bIsSniperZoomed = pGun->IsSniperZoomed();
  523. }
  524. // victim must be deployed sniper or deployed mg
  525. if ( bIsSniperZoomed || m_Shared.IsInMGDeploy() )
  526. {
  527. pScorer->AwardAchievement( ACHIEVEMENT_DOD_LONG_RANGE_ROCKET );
  528. }
  529. }
  530. }
  531. }
  532. DropPrimaryWeapon();
  533. if ( DODGameRules()->GetPresentDropChance() >= RandomFloat() )
  534. {
  535. Vector vForward, vRight, vUp;
  536. AngleVectors( EyeAngles(), &vForward, &vRight, &vUp );
  537. CHolidayGift::Create( WorldSpaceCenter(), GetAbsAngles(), EyeAngles(), GetAbsVelocity(), this );
  538. }
  539. FlashlightTurnOff();
  540. // Just in case the progress bar is on screen, kill it.
  541. SetProgressBarTime( 0 );
  542. // turn off our cap area index
  543. HandleSignals();
  544. //if we have a primed grenade, drop it
  545. CWeaponDODBase *pWpn = GetActiveDODWeapon();
  546. if( pWpn && pWpn->GetDODWpnData().m_WeaponType == WPN_TYPE_GRENADE )
  547. {
  548. CWeaponDODBaseGrenade *pGrenade = dynamic_cast<CWeaponDODBaseGrenade *>(pWpn);
  549. if( pGrenade && pGrenade->IsArmed() )
  550. {
  551. pGrenade->DropGrenade();
  552. }
  553. }
  554. if ( pScorer && pScorer != this && pScorer->GetTeamNumber() != GetTeamNumber() )
  555. {
  556. if ( m_bIsPlanting && m_pPlantTarget )
  557. {
  558. CDODBombTarget *pTarget = m_pPlantTarget;
  559. Assert( pTarget );
  560. if ( pTarget )
  561. pTarget->PlantBlocked( pScorer );
  562. IGameEvent *event = gameeventmanager->CreateEvent( "dod_kill_planter" );
  563. if ( event )
  564. {
  565. event->SetInt( "userid", pScorer->GetUserID() );
  566. event->SetInt( "victimid", GetUserID() );
  567. gameeventmanager->FireEvent( event );
  568. }
  569. }
  570. if ( m_bIsDefusing && m_pDefuseTarget && pScorer->GetTeamNumber() != GetTeamNumber() )
  571. {
  572. // Re-enable if we want to give a defend point to someone who kills a defuser
  573. //CDODBombTarget *pTarget = m_pDefuseTarget;
  574. //pTarget->DefuseBlocked( pScorer );
  575. IGameEvent *event = gameeventmanager->CreateEvent( "dod_kill_defuser" );
  576. if ( event )
  577. {
  578. event->SetInt( "userid", pScorer->GetUserID() );
  579. event->SetInt( "victimid", GetUserID() );
  580. gameeventmanager->FireEvent( event );
  581. }
  582. }
  583. }
  584. SetDefusing( NULL );
  585. SetPlanting( NULL );
  586. // show killer in death cam mode
  587. // chopped down version of SetObserverTarget without the team check
  588. if( info.GetAttacker() && info.GetAttacker()->IsPlayer() )
  589. {
  590. // set new target
  591. m_hObserverTarget.Set( info.GetAttacker() );
  592. // reset fov to default
  593. SetFOV( this, 0 );
  594. }
  595. else
  596. m_hObserverTarget.Set( NULL );
  597. //update damage info with our accumulated physics force
  598. CTakeDamageInfo subinfo = info;
  599. subinfo.SetDamageForce( m_vecTotalBulletForce );
  600. // Note: since we're dead, it won't draw us on the client, but we don't set EF_NODRAW
  601. // because we still want to transmit to the clients in our PVS.
  602. CreateRagdollEntity();
  603. State_Transition( STATE_DEATH_ANIM ); // Transition into the dying state.
  604. BaseClass::Event_Killed( subinfo );
  605. OutputDamageGiven();
  606. OutputDamageTaken();
  607. ResetDamageCounters();
  608. // stop any voice command or pain sounds that we may be making
  609. CPASFilter filter( WorldSpaceCenter() );
  610. EmitSound( filter, entindex(), "Voice.StopSounds" );
  611. ResetBleeding();
  612. m_flStunDuration = 0.0f;
  613. m_flStunMaxAlpha = 0;
  614. // cancel deafen think
  615. SetContextThink( NULL, 0.0, DOD_DEAFEN_CONTEXT );
  616. // cancel deafen dsp
  617. CSingleUserRecipientFilter user( this );
  618. enginesound->SetPlayerDSP( user, 0, false );
  619. CDODPlayer *pAttacker = ToDODPlayer( info.GetAttacker() );
  620. if( pAttacker && pAttacker->IsPlayer() )
  621. {
  622. // find the weapon id of the weapon
  623. DODWeaponID weaponID = WEAPON_NONE;
  624. CBaseEntity *pInflictor = info.GetInflictor();
  625. if ( pInflictor == pAttacker )
  626. {
  627. CWeaponDODBase *pWpn = pAttacker->GetActiveDODWeapon();
  628. if ( pWpn )
  629. {
  630. if ( info.GetDamageCustom() & MELEE_DMG_SECONDARYATTACK )
  631. weaponID = pWpn->GetAltWeaponID();
  632. else
  633. weaponID = pWpn->GetStatsWeaponID();
  634. }
  635. }
  636. else
  637. {
  638. Assert( pInflictor );
  639. const char *weaponName = STRING( pInflictor->m_iClassname );
  640. if ( Q_strncmp( weaponName, "rocket_", 7 ) == 0 )
  641. {
  642. CDODBaseRocket *pRocket = dynamic_cast< CDODBaseRocket *>( pInflictor );
  643. if ( pRocket )
  644. {
  645. weaponID = pRocket->GetEmitterWeaponID();
  646. }
  647. }
  648. else if ( Q_strncmp( weaponName, "grenade_", 8 ) == 0 )
  649. {
  650. CDODBaseGrenade *pGren = dynamic_cast< CDODBaseGrenade *>( pInflictor );
  651. if ( pGren )
  652. {
  653. weaponID = pGren->GetEmitterWeaponID();
  654. }
  655. }
  656. }
  657. IGameEvent * event = gameeventmanager->CreateEvent( "dod_stats_player_killed" );
  658. if ( event )
  659. {
  660. event->SetInt( "attacker", pAttacker->GetUserID() );
  661. event->SetInt( "victim", GetUserID() );
  662. event->SetInt( "weapon", weaponID );
  663. gameeventmanager->FireEvent( event );
  664. }
  665. if ( DODGameRules()->IsInBonusRound() )
  666. {
  667. pAttacker->Stats_BonusRoundKill();
  668. }
  669. }
  670. m_bIsDefusing = false;
  671. m_bIsPlanting = false;
  672. if ( pScorer != this )
  673. {
  674. StatEvent_WasKilled();
  675. }
  676. StatEvent_UploadStats();
  677. }
  678. bool CDODPlayer::ShouldInstantRespawn( void )
  679. {
  680. CUtlVector<EHANDLE> *pSpawnPoints = DODGameRules()->GetSpawnPointListForTeam( GetTeamNumber() );
  681. if ( !pSpawnPoints )
  682. return false;
  683. const float flMaxDistSqr = ( 500*500 );
  684. Vector vecOrigin = GetAbsOrigin();
  685. int i;
  686. int count = pSpawnPoints->Count();
  687. for ( i=0;i<count;i++ )
  688. {
  689. EHANDLE handle = pSpawnPoints->Element(i);
  690. if ( handle )
  691. {
  692. CSpawnPoint *point = dynamic_cast<CSpawnPoint *>( handle.Get() );
  693. if ( point && !point->IsDisabled() )
  694. {
  695. // range
  696. Vector vecDist = point->GetAbsOrigin() - vecOrigin;
  697. if ( vecDist.LengthSqr() > flMaxDistSqr )
  698. {
  699. continue;
  700. }
  701. // los
  702. if ( FVisible( point ) )
  703. {
  704. return true;
  705. }
  706. }
  707. }
  708. }
  709. return false;
  710. }
  711. //-----------------------------------------------------------------------------
  712. // Purpose:
  713. //-----------------------------------------------------------------------------
  714. void CDODPlayer::InitVCollision( const Vector &vecAbsOrigin, const Vector &vecAbsVelocity )
  715. {
  716. if ( 0 ) //if ( !sv_turbophysics.GetBool() )
  717. {
  718. BaseClass::InitVCollision( vecAbsOrigin, vecAbsVelocity );
  719. // Setup the HL2 specific callback.
  720. GetPhysicsController()->SetEventHandler( &playerCallback );
  721. }
  722. }
  723. void CDODPlayer::VPhysicsShadowUpdate( IPhysicsObject *pPhysics )
  724. {
  725. if ( 0 ) //if ( !sv_turbophysics.GetBool() )
  726. {
  727. if ( !CanMove() )
  728. return;
  729. BaseClass::VPhysicsShadowUpdate( pPhysics );
  730. }
  731. }
  732. void CDODPlayer::PushawayThink()
  733. {
  734. // Push physics props out of our way.
  735. PerformObstaclePushaway( this );
  736. SetNextThink( gpGlobals->curtime + PUSHAWAY_THINK_INTERVAL, DOD_PUSHAWAY_THINK_CONTEXT );
  737. }
  738. void CDODPlayer::CheatImpulseCommands( int iImpulse )
  739. {
  740. switch( iImpulse )
  741. {
  742. case 101:
  743. {
  744. if( sv_cheats->GetBool() )
  745. {
  746. extern int gEvilImpulse101;
  747. gEvilImpulse101 = true;
  748. GiveAmmo( 1000, DOD_AMMO_COLT );
  749. GiveAmmo( 1000, DOD_AMMO_P38 );
  750. GiveAmmo( 1000, DOD_AMMO_C96 );
  751. GiveAmmo( 1000, DOD_AMMO_GARAND );
  752. GiveAmmo( 1000, DOD_AMMO_K98 );
  753. GiveAmmo( 1000, DOD_AMMO_M1CARBINE );
  754. GiveAmmo( 1000, DOD_AMMO_SPRING );
  755. GiveAmmo( 1000, DOD_AMMO_SUBMG );
  756. GiveAmmo( 1000, DOD_AMMO_BAR );
  757. GiveAmmo( 1000, DOD_AMMO_30CAL );
  758. GiveAmmo( 1000, DOD_AMMO_MG42 );
  759. GiveAmmo( 1000, DOD_AMMO_ROCKET );
  760. gEvilImpulse101 = false;
  761. }
  762. }
  763. break;
  764. default:
  765. {
  766. BaseClass::CheatImpulseCommands( iImpulse );
  767. }
  768. }
  769. }
  770. void CDODPlayer::SetupVisibility( CBaseEntity *pViewEntity, unsigned char *pvs, int pvssize )
  771. {
  772. BaseClass::SetupVisibility( pViewEntity, pvs, pvssize );
  773. int area = pViewEntity ? pViewEntity->NetworkProp()->AreaNum() : NetworkProp()->AreaNum();
  774. PointCameraSetupVisibility( this, area, pvs, pvssize );
  775. }
  776. void CDODPlayer::DoAnimationEvent( PlayerAnimEvent_t event, int nData )
  777. {
  778. m_PlayerAnimState->DoAnimationEvent( event, nData );
  779. TE_PlayerAnimEvent( this, event, nData ); // Send to any clients who can see this guy.
  780. }
  781. CBaseEntity *CDODPlayer::GiveNamedItem( const char *pszName, int iSubType )
  782. {
  783. EHANDLE pent;
  784. pent = CreateEntityByName(pszName);
  785. if ( pent == NULL )
  786. {
  787. Msg( "NULL Ent in GiveNamedItem!\n" );
  788. return NULL;
  789. }
  790. pent->SetLocalOrigin( GetLocalOrigin() );
  791. pent->AddSpawnFlags( SF_NORESPAWN );
  792. if ( iSubType )
  793. {
  794. CBaseCombatWeapon *pWeapon = dynamic_cast<CBaseCombatWeapon*>( (CBaseEntity*)pent );
  795. if ( pWeapon )
  796. {
  797. pWeapon->SetSubType( iSubType );
  798. }
  799. }
  800. DispatchSpawn( pent );
  801. if ( pent != NULL && !(pent->IsMarkedForDeletion()) )
  802. {
  803. pent->Touch( this );
  804. }
  805. return pent;
  806. }
  807. extern ConVar flashlight;
  808. //-----------------------------------------------------------------------------
  809. //-----------------------------------------------------------------------------
  810. int CDODPlayer::FlashlightIsOn( void )
  811. {
  812. return IsEffectActive( EF_DIMLIGHT );
  813. }
  814. //-----------------------------------------------------------------------------
  815. //-----------------------------------------------------------------------------
  816. void CDODPlayer::FlashlightTurnOn( void )
  817. {
  818. if( flashlight.GetInt() > 0 && IsAlive() )
  819. {
  820. AddEffects( EF_DIMLIGHT );
  821. EmitSound( "Player.FlashlightOn" );
  822. }
  823. }
  824. //-----------------------------------------------------------------------------
  825. //-----------------------------------------------------------------------------
  826. void CDODPlayer::FlashlightTurnOff( void )
  827. {
  828. if( IsEffectActive(EF_DIMLIGHT) )
  829. {
  830. RemoveEffects( EF_DIMLIGHT );
  831. if( m_iHealth > 0 )
  832. {
  833. EmitSound( "Player.FlashlightOff" );
  834. }
  835. }
  836. }
  837. void CDODPlayer::PostThink()
  838. {
  839. BaseClass::PostThink();
  840. if( m_Shared.IsProne() )
  841. {
  842. SetCollisionBounds( VEC_PRONE_HULL_MIN, VEC_PRONE_HULL_MAX );
  843. }
  844. if ( gpGlobals->curtime > m_fHandleSignalsTime )
  845. {
  846. m_fHandleSignalsTime = gpGlobals->curtime + 0.1;
  847. HandleSignals();
  848. }
  849. QAngle angles = GetLocalAngles();
  850. angles[PITCH] = 0;
  851. SetLocalAngles( angles );
  852. // Store the eye angles pitch so the client can compute its animation state correctly.
  853. m_angEyeAngles = EyeAngles();
  854. m_PlayerAnimState->Update( m_angEyeAngles[YAW], m_angEyeAngles[PITCH] );
  855. }
  856. void CDODPlayer::HandleCommand_Voice( const char *pcmd )
  857. {
  858. // string should be formatted as "voice_gogogo"
  859. // go through our list of voice commands and if we find it,
  860. // emit a voice command
  861. int i = 0;
  862. while( g_VoiceCommands[i].pszCommandName != NULL )
  863. {
  864. if( Q_strcmp( pcmd, g_VoiceCommands[i].pszCommandName ) == 0 )
  865. {
  866. VoiceCommand( i );
  867. break;
  868. }
  869. i++;
  870. }
  871. }
  872. void CDODPlayer::VoiceCommand( int iVoiceCommand )
  873. {
  874. //no voices in observer or when dead
  875. if ( !IsAlive() || IsObserver() )
  876. return;
  877. //no voices when game is over
  878. if (g_fGameOver)
  879. return;
  880. if (m_flNextVoice > gpGlobals->curtime )
  881. return;
  882. // Emit the voice sound
  883. CPASFilter filter( WorldSpaceCenter() );
  884. // Get the country text to fill into the sound name
  885. char *pszCountry = "US";
  886. if( GetTeamNumber() == TEAM_AXIS )
  887. {
  888. pszCountry = "German";
  889. }
  890. char szSound[128];
  891. Q_snprintf( szSound, sizeof(szSound), "Voice.%s_%s", pszCountry, g_VoiceCommands[iVoiceCommand].pszSoundName );
  892. EmitSound( filter, entindex(), szSound );
  893. // Don't show the subtitle to the other team
  894. int oppositeTeam = ( GetTeamNumber() == TEAM_ALLIES ) ? TEAM_AXIS : TEAM_ALLIES;
  895. filter.RemoveRecipientsByTeam( GetGlobalDODTeam(oppositeTeam) );
  896. // further reduce the voice command subtitle radius
  897. float flDist;
  898. float flMaxDist = 1900;
  899. Vector vecEmitOrigin = GetAbsOrigin();
  900. int i;
  901. for ( i=1; i<= MAX_PLAYERS; i++ )
  902. {
  903. CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
  904. if ( !pPlayer )
  905. continue;
  906. flDist = ( pPlayer->GetAbsOrigin() - vecEmitOrigin ).Length2D();
  907. if ( flDist > flMaxDist )
  908. filter.RemoveRecipient( pPlayer );
  909. }
  910. // Send a subtitle to anyone in the PAS
  911. UserMessageBegin( filter, "VoiceSubtitle" );
  912. WRITE_BYTE( entindex() );
  913. WRITE_BYTE( GetTeamNumber() );
  914. WRITE_BYTE( iVoiceCommand );
  915. MessageEnd();
  916. PlayerAnimEvent_t iHandSignal = g_VoiceCommands[iVoiceCommand].iHandSignal;
  917. if( iHandSignal != PLAYERANIMEVENT_HS_NONE )
  918. DoAnimationEvent( iHandSignal );
  919. m_flNextVoice = gpGlobals->curtime + 1.0;
  920. }
  921. void CDODPlayer::HandleCommand_HandSignal( const char *pcmd )
  922. {
  923. // string should be formatted as "signal_yes"
  924. int i = 0;
  925. while( g_HandSignals[i].pszCommandName != NULL )
  926. {
  927. if( Q_strcmp( pcmd, g_HandSignals[i].pszCommandName ) == 0 )
  928. {
  929. HandSignal( i );
  930. break;
  931. }
  932. i++;
  933. }
  934. }
  935. void CDODPlayer::HandSignal( int iSignal )
  936. {
  937. //no hand signals in observer or when dead
  938. if ( !IsAlive() || IsObserver() )
  939. return;
  940. //or when game is over
  941. if (g_fGameOver)
  942. return;
  943. if (m_flNextHandSignal > gpGlobals->curtime )
  944. return;
  945. int oppositeTeam = ( GetTeamNumber() == TEAM_ALLIES ) ? TEAM_AXIS : TEAM_ALLIES;
  946. // Emit the voice sound
  947. CRecipientFilter filter;
  948. filter.AddRecipientsByPVS( WorldSpaceCenter() );
  949. filter.RemoveRecipientsByTeam( GetGlobalTeam(oppositeTeam) );
  950. // Send a subtitle to anyone in the PAS
  951. UserMessageBegin( filter, "HandSignalSubtitle" );
  952. WRITE_BYTE( entindex() );
  953. WRITE_BYTE( iSignal );
  954. MessageEnd();
  955. DoAnimationEvent( g_HandSignals[iSignal].iHandSignal );
  956. m_flNextHandSignal = gpGlobals->curtime + 1.0;
  957. }
  958. void CDODPlayer::DropGenericAmmo( void )
  959. {
  960. if ( !IsAlive() )
  961. return;
  962. if( m_bHasGenericAmmo == false )
  963. return;
  964. Vector vForward, vRight, vUp;
  965. AngleVectors( EyeAngles(), &vForward, &vRight, &vUp );
  966. CAmmoBox *pAmmo = CAmmoBox::Create( WorldSpaceCenter(), vec3_angle, this, GetTeamNumber() );
  967. pAmmo->ApplyAbsVelocityImpulse( vForward * 300 + vUp * 100 );
  968. m_hLastDroppedAmmoBox = pAmmo;
  969. m_bHasGenericAmmo = false;
  970. }
  971. void CDODPlayer::ReturnGenericAmmo( void )
  972. {
  973. //play pickup sound
  974. CPASFilter filter( WorldSpaceCenter() );
  975. EmitSound( filter, entindex(), "BaseCombatCharacter.AmmoPickup" );
  976. //allow them to drop generic ammo again
  977. m_bHasGenericAmmo = true;
  978. }
  979. bool CDODPlayer::GiveGenericAmmo( void )
  980. {
  981. //give primary weapon ammo
  982. CWeaponDODBase *pWpn = (CWeaponDODBase *)Weapon_GetSlot( WPN_SLOT_PRIMARY );
  983. if( pWpn )
  984. {
  985. int nAmmoType = pWpn->GetPrimaryAmmoType();
  986. int iClipSize = pWpn->GetDODWpnData().iMaxClip1;
  987. int iAmmoPickupClips = pWpn->GetDODWpnData().m_iAmmoPickupClips;
  988. if( GiveAmmo( iAmmoPickupClips * iClipSize, nAmmoType, false ) > 0 )
  989. {
  990. //some ammo was picked up, consume the ammo box
  991. //play pickup sound
  992. CPASFilter filter( WorldSpaceCenter() );
  993. EmitSound( filter, entindex(), "BaseCombatCharacter.AmmoPickup" );
  994. return true;
  995. }
  996. }
  997. return false;
  998. }
  999. ConVar dod_friendlyfiresafezone( "dod_friendlyfiresafezone",
  1000. "100",
  1001. FCVAR_ARCHIVE,
  1002. "Units around a player where they will not damage teammates, even if FF is on",
  1003. true, 0, false, 0 );
  1004. void CDODPlayer::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator )
  1005. {
  1006. bool bTakeDamage = true;
  1007. CBasePlayer *pAttacker = (CBasePlayer*)ToBasePlayer( info.GetAttacker() );
  1008. bool bFriendlyFire = DODGameRules()->IsFriendlyFireOn();
  1009. if ( pAttacker && ( GetTeamNumber() == pAttacker->GetTeamNumber() ) )
  1010. {
  1011. bTakeDamage = bFriendlyFire;
  1012. // Create a FF-free zone around players for melee and bullets
  1013. if ( bFriendlyFire && ( info.GetDamageType() & (DMG_SLASH | DMG_BULLET | DMG_CLUB) ) )
  1014. {
  1015. float flDist = dod_friendlyfiresafezone.GetFloat();
  1016. Vector vecDist = pAttacker->WorldSpaceCenter() - WorldSpaceCenter();
  1017. if ( vecDist.LengthSqr() < ( flDist*flDist ) )
  1018. {
  1019. bTakeDamage = false;
  1020. }
  1021. }
  1022. }
  1023. if ( m_takedamage != DAMAGE_YES )
  1024. return;
  1025. m_LastHitGroup = ptr->hitgroup;
  1026. m_LastDamageType = info.GetDamageType();
  1027. Assert( ptr->hitgroup != HITGROUP_GENERIC );
  1028. m_nForceBone = ptr->physicsbone; //Save this bone for ragdoll
  1029. float flDamage = info.GetDamage();
  1030. float flOriginalDmg = flDamage;
  1031. bool bHeadShot = false;
  1032. //if its an enemy OR ff is on.
  1033. if( bTakeDamage )
  1034. {
  1035. m_Shared.SetSlowedTime( 0.5f );
  1036. }
  1037. char *szHitbox = NULL;
  1038. if( info.GetDamageType() & DMG_BLAST )
  1039. {
  1040. // don't do hitgroup specific grenade damage
  1041. flDamage *= 1.0;
  1042. szHitbox = "dmg_blast";
  1043. }
  1044. else
  1045. {
  1046. switch ( ptr->hitgroup )
  1047. {
  1048. case HITGROUP_HEAD:
  1049. {
  1050. flDamage *= 2.5; //regular head shot multiplier
  1051. if( bTakeDamage )
  1052. {
  1053. Vector dir = vecDir;
  1054. VectorNormalize(dir);
  1055. if ( info.GetDamageType() & DMG_CLUB )
  1056. dir *= 800;
  1057. else
  1058. dir *= 400;
  1059. dir.z += 100; // add some lift so it pops better.
  1060. PopHelmet( dir, ptr->endpos );
  1061. }
  1062. szHitbox = "head";
  1063. bHeadShot = true;
  1064. }
  1065. break;
  1066. case HITGROUP_CHEST:
  1067. szHitbox = "chest";
  1068. break;
  1069. case HITGROUP_STOMACH:
  1070. szHitbox = "stomach";
  1071. break;
  1072. case HITGROUP_LEFTARM:
  1073. flDamage *= 0.75;
  1074. szHitbox = "left arm";
  1075. break;
  1076. case HITGROUP_RIGHTARM:
  1077. flDamage *= 0.75;
  1078. szHitbox = "right arm";
  1079. break;
  1080. case HITGROUP_LEFTLEG:
  1081. case HITGROUP_RIGHTLEG:
  1082. {
  1083. //we are slowed for 2 full seconds if we get a leg hit
  1084. if( bTakeDamage )
  1085. {
  1086. //m_bSlowedByHit = true;
  1087. //m_flUnslowTime = gpGlobals->curtime + 2;
  1088. m_Shared.SetSlowedTime( 2.0f );
  1089. }
  1090. flDamage *= 0.75;
  1091. szHitbox = "leg";
  1092. }
  1093. break;
  1094. case HITGROUP_GENERIC:
  1095. szHitbox = "(error - hit generic)";
  1096. break;
  1097. default:
  1098. szHitbox = "(error - hit default)";
  1099. break;
  1100. }
  1101. }
  1102. if ( bTakeDamage )
  1103. {
  1104. if( dod_debugdamage.GetInt() )
  1105. {
  1106. char buf[256];
  1107. Q_snprintf( buf, sizeof(buf), "%s hit %s in the %s hitgroup for %f damage ( %f base damage ) ( %s now has %d health )\n",
  1108. pAttacker->GetPlayerName(),
  1109. GetPlayerName(),
  1110. szHitbox,
  1111. flDamage,
  1112. flOriginalDmg,
  1113. GetPlayerName(),
  1114. GetHealth() - (int)flDamage );
  1115. // print to server
  1116. UTIL_LogPrintf( "%s", buf );
  1117. // print to injured
  1118. ClientPrint( this, HUD_PRINTCONSOLE, buf );
  1119. // print to shooter
  1120. if ( pAttacker )
  1121. ClientPrint( pAttacker, HUD_PRINTCONSOLE, buf );
  1122. }
  1123. // Since this code only runs on the server, make sure it shows the tempents it creates.
  1124. CDisablePredictionFiltering disabler;
  1125. // This does smaller splotches on the guy and splats blood on the world.
  1126. TraceBleed( flDamage, vecDir, ptr, info.GetDamageType() );
  1127. CEffectData data;
  1128. data.m_vOrigin = ptr->endpos;
  1129. data.m_vNormal = vecDir * -1;
  1130. data.m_flScale = 4;
  1131. data.m_fFlags = FX_BLOODSPRAY_ALL;
  1132. data.m_nEntIndex = ptr->m_pEnt ? ptr->m_pEnt->entindex() : 0;
  1133. data.m_flMagnitude = flDamage;
  1134. DispatchEffect( "dodblood", data );
  1135. CTakeDamageInfo subInfo = info;
  1136. subInfo.SetDamage( flDamage );
  1137. AddMultiDamage( subInfo, this );
  1138. }
  1139. }
  1140. bool CDODPlayer::DropActiveWeapon( void )
  1141. {
  1142. CWeaponDODBase *pWeapon = GetActiveDODWeapon();
  1143. if( pWeapon )
  1144. {
  1145. if( pWeapon->CanDrop() )
  1146. {
  1147. DODWeaponDrop( pWeapon, true );
  1148. return true;
  1149. }
  1150. else
  1151. {
  1152. int iWeaponType = pWeapon->GetDODWpnData().m_WeaponType;
  1153. if( iWeaponType == WPN_TYPE_BAZOOKA || iWeaponType == WPN_TYPE_MG )
  1154. {
  1155. // they are deployed, cannot drop
  1156. Hints()->HintMessage( "#game_cannot_drop_while" );
  1157. }
  1158. else
  1159. {
  1160. // must be a weapon type that cannot be dropped
  1161. Hints()->HintMessage( "#game_cannot_drop" );
  1162. }
  1163. return false;
  1164. }
  1165. }
  1166. return false;
  1167. }
  1168. CWeaponDODBase* CDODPlayer::GetActiveDODWeapon() const
  1169. {
  1170. return dynamic_cast< CWeaponDODBase* >( GetActiveWeapon() );
  1171. }
  1172. void CDODPlayer::PreThink()
  1173. {
  1174. BaseClass::PreThink();
  1175. if ( m_afButtonLast != m_nButtons )
  1176. m_flLastMovement = gpGlobals->curtime;
  1177. if ( g_fGameOver )
  1178. return;
  1179. State_PreThink();
  1180. //Reset bullet force accumulator, only lasts one frame, for ragdoll forces from multiple shots
  1181. m_vecTotalBulletForce = vec3_origin;
  1182. if ( mp_autokick.GetInt() && !IsBot() && !IsHLTV() )
  1183. {
  1184. if ( m_flLastMovement + mp_autokick.GetInt()*60 < gpGlobals->curtime )
  1185. {
  1186. UTIL_ClientPrintAll( HUD_PRINTCONSOLE, "#Game_idle_kick", GetPlayerName() );
  1187. engine->ServerCommand( UTIL_VarArgs( "kickid %d\n", GetUserID() ) );
  1188. m_flLastMovement = gpGlobals->curtime;
  1189. }
  1190. }
  1191. }
  1192. bool CDODPlayer::DropPrimaryWeapon( void )
  1193. {
  1194. CWeaponDODBase *pWeapon = (CWeaponDODBase *)Weapon_GetSlot( WPN_SLOT_PRIMARY );
  1195. if( pWeapon )
  1196. {
  1197. DODWeaponDrop( pWeapon, false );
  1198. return true;
  1199. }
  1200. return false;
  1201. }
  1202. bool CDODPlayer::DODWeaponDrop( CBaseCombatWeapon *pWeapon, bool bThrowForward )
  1203. {
  1204. bool bSuccess = false;
  1205. if ( pWeapon )
  1206. {
  1207. Vector vForward;
  1208. AngleVectors( EyeAngles(), &vForward, NULL, NULL );
  1209. Vector vTossPos = WorldSpaceCenter();
  1210. if( bThrowForward )
  1211. vTossPos = vTossPos + vForward * 64;
  1212. Weapon_Drop( pWeapon, &vTossPos, NULL );
  1213. pWeapon->SetSolidFlags( FSOLID_NOT_STANDABLE | FSOLID_TRIGGER | FSOLID_USE_TRIGGER_BOUNDS );
  1214. pWeapon->SetMoveCollide( MOVECOLLIDE_FLY_BOUNCE );
  1215. CWeaponDODBase *pDODWeapon = dynamic_cast< CWeaponDODBase* >( pWeapon );
  1216. if( pDODWeapon )
  1217. {
  1218. pDODWeapon->SetDieThink(true);
  1219. pDODWeapon->SetWeaponModelIndex( pDODWeapon->GetDODWpnData().szWorldModel );
  1220. //Find out the index of the ammo type
  1221. int iAmmoIndex = pDODWeapon->GetPrimaryAmmoType();
  1222. //If it has an ammo type, find out how much the player has
  1223. if( iAmmoIndex != -1 )
  1224. {
  1225. int iAmmoToDrop = GetAmmoCount( iAmmoIndex );
  1226. //Add this much to the dropped weapon
  1227. pDODWeapon->SetExtraAmmoCount( iAmmoToDrop );
  1228. //Remove all ammo of this type from the player
  1229. SetAmmoCount( 0, iAmmoIndex );
  1230. }
  1231. }
  1232. //=========================================
  1233. // Teleport the weapon to the player's hand
  1234. //=========================================
  1235. int iBIndex = -1;
  1236. int iWeaponBoneIndex = -1;
  1237. CStudioHdr *hdr = pWeapon->GetModelPtr();
  1238. // If I have a hand, set the weapon position to my hand bone position.
  1239. if ( hdr && hdr->numbones() > 0 )
  1240. {
  1241. // Assume bone zero is the root
  1242. for ( iWeaponBoneIndex = 0; iWeaponBoneIndex < hdr->numbones(); ++iWeaponBoneIndex )
  1243. {
  1244. iBIndex = LookupBone( hdr->pBone( iWeaponBoneIndex )->pszName() );
  1245. // Found one!
  1246. if ( iBIndex != -1 )
  1247. {
  1248. break;
  1249. }
  1250. }
  1251. if ( iWeaponBoneIndex == hdr->numbones() )
  1252. return true;
  1253. if ( iBIndex == -1 )
  1254. {
  1255. iBIndex = LookupBone( "ValveBiped.Bip01_R_Hand" );
  1256. }
  1257. }
  1258. else
  1259. {
  1260. iBIndex = LookupBone( "ValveBiped.Bip01_R_Hand" );
  1261. }
  1262. if ( iBIndex != -1)
  1263. {
  1264. Vector origin;
  1265. QAngle angles;
  1266. matrix3x4_t transform;
  1267. // Get the transform for the weapon bonetoworldspace in the NPC
  1268. GetBoneTransform( iBIndex, transform );
  1269. // find offset of root bone from origin in local space
  1270. // Make sure we're detached from hierarchy before doing this!!!
  1271. pWeapon->StopFollowingEntity();
  1272. pWeapon->SetAbsOrigin( Vector( 0, 0, 0 ) );
  1273. pWeapon->SetAbsAngles( QAngle( 0, 0, 0 ) );
  1274. pWeapon->InvalidateBoneCache();
  1275. matrix3x4_t rootLocal;
  1276. pWeapon->GetBoneTransform( iWeaponBoneIndex, rootLocal );
  1277. // invert it
  1278. matrix3x4_t rootInvLocal;
  1279. MatrixInvert( rootLocal, rootInvLocal );
  1280. matrix3x4_t weaponMatrix;
  1281. ConcatTransforms( transform, rootInvLocal, weaponMatrix );
  1282. MatrixAngles( weaponMatrix, angles, origin );
  1283. // trace the bounding box of the weapon in the desired position
  1284. trace_t tr;
  1285. Vector mins, maxs;
  1286. // not exactly correct bounds, we haven't rotated them to match the attachment
  1287. pWeapon->CollisionProp()->WorldSpaceSurroundingBounds( &mins, &maxs );
  1288. UTIL_TraceHull( WorldSpaceCenter(), origin, mins, maxs, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr );
  1289. if ( tr.fraction < 1.0 )
  1290. origin = WorldSpaceCenter();
  1291. pWeapon->Teleport( &origin, &angles, NULL );
  1292. //Have to teleport the physics object as well
  1293. IPhysicsObject *pWeaponPhys = pWeapon->VPhysicsGetObject();
  1294. if( pWeaponPhys )
  1295. {
  1296. Vector vPos;
  1297. QAngle vAngles;
  1298. pWeaponPhys->GetPosition( &vPos, &vAngles );
  1299. pWeaponPhys->SetPosition( vPos, angles, true );
  1300. AngularImpulse angImp(0,0,0);
  1301. Vector vecAdd = GetAbsVelocity();
  1302. pWeaponPhys->AddVelocity( &vecAdd, &angImp );
  1303. }
  1304. m_hLastDroppedWeapon = pWeapon;
  1305. }
  1306. if ( !GetActiveWeapon() )
  1307. {
  1308. // we haven't auto-switched to anything usable
  1309. // switch to the first weapon we find, even if its empty
  1310. CBaseCombatWeapon *pCheck;
  1311. for ( int i = 0 ; i < WeaponCount(); ++i )
  1312. {
  1313. pCheck = GetWeapon( i );
  1314. if ( !pCheck || !pCheck->CanDeploy() )
  1315. {
  1316. continue;
  1317. }
  1318. Weapon_Switch( pCheck );
  1319. break;
  1320. }
  1321. }
  1322. bSuccess = true;
  1323. }
  1324. return bSuccess;
  1325. }
  1326. extern int g_iHelmetModels[NUM_HELMETS];
  1327. void CDODPlayer::PopHelmet( Vector vecDir, Vector vecForceOrigin )
  1328. {
  1329. int iPlayerClass = m_Shared.PlayerClass();
  1330. CDODTeam *pTeam = GetGlobalDODTeam( GetTeamNumber() );
  1331. const CDODPlayerClassInfo &pClassInfo = pTeam->GetPlayerClassInfo( iPlayerClass );
  1332. // See if they already lost their helmet
  1333. if( GetBodygroup( BODYGROUP_HELMET ) == pClassInfo.m_iHairGroup )
  1334. return;
  1335. // Nope.. take it off
  1336. SetBodygroup( BODYGROUP_HELMET, pClassInfo.m_iHairGroup );
  1337. // Add the velocity of the player
  1338. vecDir += GetAbsVelocity();
  1339. //CDisablePredictionFiltering disabler;
  1340. EntityMessageBegin( this, true );
  1341. WRITE_BYTE( DOD_PLAYER_POP_HELMET );
  1342. WRITE_VEC3COORD( vecDir );
  1343. WRITE_VEC3COORD( vecForceOrigin );
  1344. WRITE_SHORT( g_iHelmetModels[pClassInfo.m_iDropHelmet] );
  1345. MessageEnd();
  1346. }
  1347. bool CDODPlayer::BumpWeapon( CBaseCombatWeapon *pBaseWeapon )
  1348. {
  1349. CWeaponDODBase *pWeapon = dynamic_cast< CWeaponDODBase* >( pBaseWeapon );
  1350. if ( !pWeapon )
  1351. {
  1352. Assert( false );
  1353. return false;
  1354. }
  1355. CBaseCombatCharacter *pOwner = pWeapon->GetOwner();
  1356. // Can I have this weapon type?
  1357. if ( pOwner || !Weapon_CanUse( pWeapon ) || !g_pGameRules->CanHavePlayerItem( this, pWeapon ) )
  1358. {
  1359. extern int gEvilImpulse101;
  1360. if ( gEvilImpulse101 )
  1361. {
  1362. UTIL_Remove( pWeapon );
  1363. }
  1364. return false;
  1365. }
  1366. // Don't let the player fetch weapons through walls
  1367. if( !pWeapon->FVisible( this ) && !(GetFlags() & FL_NOTARGET) )
  1368. {
  1369. return false;
  1370. }
  1371. /*
  1372. CBaseCombatWeapon *pOwnedWeapon = Weapon_OwnsThisType( pWeapon->GetClassname() );
  1373. if( pOwnedWeapon != NULL && ( pWeapon->GetWpnData().iFlags & ITEM_FLAG_EXHAUSTIBLE ) )
  1374. {
  1375. // its an item that we can hold several of, and use up
  1376. // Just give one ammo
  1377. if( GiveAmmo( 1, pOwnedWeapon->GetPrimaryAmmoType(), true ) > 0 )
  1378. return true;
  1379. else
  1380. return false;
  1381. }
  1382. */
  1383. // ----------------------------------------
  1384. // If I already have it just take the ammo
  1385. // ----------------------------------------
  1386. if (Weapon_OwnsThisType( pWeapon->GetClassname(), pWeapon->GetSubType()))
  1387. {
  1388. if( Weapon_EquipAmmoOnly( pWeapon ) )
  1389. {
  1390. // Only remove me if I have no ammo left
  1391. if ( pWeapon->HasPrimaryAmmo() )
  1392. return false;
  1393. UTIL_Remove( pWeapon );
  1394. return true;
  1395. }
  1396. else
  1397. {
  1398. return false;
  1399. }
  1400. }
  1401. pWeapon->CheckRespawn();
  1402. pWeapon->AddSolidFlags( FSOLID_NOT_SOLID );
  1403. pWeapon->AddEffects( EF_NODRAW );
  1404. Weapon_Equip( pWeapon );
  1405. int iExtraAmmo = pWeapon->GetExtraAmmoCount();
  1406. if( iExtraAmmo )
  1407. {
  1408. //Find out the index of the ammo
  1409. int iAmmoIndex = pWeapon->GetPrimaryAmmoType();
  1410. if( iAmmoIndex != -1 )
  1411. {
  1412. //Remove the extra ammo from the weapon
  1413. pWeapon->SetExtraAmmoCount(0);
  1414. //Give it to the player
  1415. SetAmmoCount( iExtraAmmo, iAmmoIndex );
  1416. }
  1417. }
  1418. return true;
  1419. }
  1420. bool CDODPlayer::HandleCommand_JoinClass( int iClass )
  1421. {
  1422. Assert( GetTeamNumber() != TEAM_SPECTATOR );
  1423. Assert( GetTeamNumber() != TEAM_UNASSIGNED );
  1424. if( GetTeamNumber() == TEAM_SPECTATOR )
  1425. return false;
  1426. if( iClass == PLAYERCLASS_UNDEFINED )
  1427. return false; //they typed in something weird
  1428. int iOldPlayerClass = m_Shared.DesiredPlayerClass();
  1429. // See if we're joining the class we already are
  1430. if( iClass == iOldPlayerClass )
  1431. return true;
  1432. if( !DODGameRules()->IsPlayerClassOnTeam( iClass, GetTeamNumber() ) )
  1433. return false;
  1434. const char *classname = DODGameRules()->GetPlayerClassName( iClass, GetTeamNumber() );
  1435. if( DODGameRules()->CanPlayerJoinClass( this, iClass ) )
  1436. {
  1437. m_Shared.SetDesiredPlayerClass( iClass ); //real class value is set when the player spawns
  1438. if( State_Get() == STATE_PICKINGCLASS )
  1439. State_Transition( STATE_OBSERVER_MODE );
  1440. if( iClass == PLAYERCLASS_RANDOM )
  1441. {
  1442. if( IsAlive() )
  1443. {
  1444. ClientPrint(this, HUD_PRINTTALK, "#game_respawn_asrandom" );
  1445. }
  1446. else
  1447. {
  1448. ClientPrint(this, HUD_PRINTTALK, "#game_spawn_asrandom" );
  1449. }
  1450. }
  1451. else
  1452. {
  1453. if( IsAlive() )
  1454. {
  1455. ClientPrint(this, HUD_PRINTTALK, "#game_respawn_as", classname );
  1456. }
  1457. else
  1458. {
  1459. ClientPrint(this, HUD_PRINTTALK, "#game_spawn_as", classname );
  1460. }
  1461. }
  1462. IGameEvent * event = gameeventmanager->CreateEvent( "player_changeclass" );
  1463. if ( event )
  1464. {
  1465. event->SetInt( "userid", GetUserID() );
  1466. event->SetInt( "class", iClass );
  1467. gameeventmanager->FireEvent( event );
  1468. }
  1469. TallyLatestTimePlayedPerClass( GetTeamNumber(), iOldPlayerClass );
  1470. // if we're near a respawn point and can see it, just spawn us right away
  1471. if ( ShouldInstantRespawn() )
  1472. {
  1473. DODRespawn();
  1474. m_fNextSuicideTime = gpGlobals->curtime + 1.0;
  1475. // if we dropped our primary weapon, and noone else picked it up, destroy it
  1476. CBaseCombatWeapon *pWeapon = dynamic_cast<CBaseCombatWeapon *>( m_hLastDroppedWeapon.Get() );
  1477. if ( pWeapon )
  1478. {
  1479. if ( !pWeapon->GetOwner() )
  1480. {
  1481. UTIL_Remove( m_hLastDroppedWeapon.Get() );
  1482. }
  1483. }
  1484. // if we dropped our primary weapon, and noone else picked it up, destroy it
  1485. CAmmoBox *pAmmo = dynamic_cast<CAmmoBox *>( m_hLastDroppedAmmoBox.Get() );
  1486. if ( pAmmo )
  1487. {
  1488. UTIL_Remove( pAmmo );
  1489. }
  1490. }
  1491. }
  1492. else
  1493. {
  1494. ClientPrint(this, HUD_PRINTTALK, "#game_class_limit", classname );
  1495. ShowClassSelectMenu();
  1496. }
  1497. // if they missed the last wave while choosing class
  1498. // then restart it now
  1499. DODGameRules()->CreateOrJoinRespawnWave( this );
  1500. return true;
  1501. }
  1502. void CDODPlayer::ShowClassSelectMenu()
  1503. {
  1504. if ( GetTeamNumber() == TEAM_ALLIES )
  1505. {
  1506. ShowViewPortPanel( PANEL_CLASS_ALLIES );
  1507. }
  1508. else if ( GetTeamNumber() == TEAM_AXIS )
  1509. {
  1510. ShowViewPortPanel( PANEL_CLASS_AXIS );
  1511. }
  1512. }
  1513. void CDODPlayer::CheckRotateIntroCam( void )
  1514. {
  1515. // Update whatever intro camera it's at.
  1516. if( m_pIntroCamera && (gpGlobals->curtime >= m_fIntroCamTime) )
  1517. {
  1518. MoveToNextIntroCamera();
  1519. }
  1520. }
  1521. int CDODPlayer::GetHealthAsString( char *pDest, int iDestSize )
  1522. {
  1523. Q_snprintf( pDest, iDestSize, "%d", GetHealth() );
  1524. return Q_strlen(pDest);
  1525. }
  1526. int CDODPlayer::GetLastPlayerIDAsString( char *pDest, int iDestSize )
  1527. {
  1528. Q_snprintf( pDest, iDestSize, "last player id'd" );
  1529. return Q_strlen(pDest);
  1530. }
  1531. int CDODPlayer::GetClosestPlayerHealthAsString( char *pDest, int iDestSize )
  1532. {
  1533. Q_snprintf( pDest, iDestSize, "some guy's health" );
  1534. return Q_strlen(pDest);
  1535. }
  1536. int CDODPlayer::GetPlayerClassAsString( char *pDest, int iDestSize )
  1537. {
  1538. int team = GetTeamNumber();
  1539. if( team == TEAM_ALLIES || team == TEAM_AXIS )
  1540. {
  1541. const char *pszClassName = DODGameRules()->GetPlayerClassName( m_Shared.PlayerClass(), GetTeamNumber() );
  1542. Q_snprintf( pDest, iDestSize, "%s", pszClassName );
  1543. return Q_strlen(pDest);
  1544. }
  1545. else
  1546. {
  1547. pDest[0] = '\0';
  1548. return 0;
  1549. }
  1550. }
  1551. int CDODPlayer::GetNearestLocationAsString( char *pDest, int iDestSize )
  1552. {
  1553. float flMinDist = FLT_MAX;
  1554. float flDist;
  1555. const char *pLocationName = "";
  1556. CBaseEntity *pEnt = gEntList.FindEntityByClassname( NULL, "dod_control_point" );
  1557. while( pEnt )
  1558. {
  1559. Vector vecDelta = GetAbsOrigin() - pEnt->GetAbsOrigin();
  1560. flDist = vecDelta.Length();
  1561. if( flDist < flMinDist )
  1562. {
  1563. CControlPoint *pPoint = static_cast< CControlPoint* >( pEnt );
  1564. pLocationName = pPoint->GetName();
  1565. flMinDist = flDist;
  1566. }
  1567. pEnt = gEntList.FindEntityByClassname( pEnt, "dod_control_point" );
  1568. }
  1569. pEnt = gEntList.FindEntityByClassname( NULL, "dod_location" );
  1570. while( pEnt )
  1571. {
  1572. Vector vecDelta = GetAbsOrigin() - pEnt->GetAbsOrigin();
  1573. flDist = vecDelta.Length();
  1574. if( flDist < flMinDist )
  1575. {
  1576. CDODLocation *pPoint = static_cast< CDODLocation* >( pEnt );
  1577. pLocationName = pPoint->GetName();
  1578. flMinDist = flDist;
  1579. }
  1580. pEnt = gEntList.FindEntityByClassname( pEnt, "dod_location" );
  1581. }
  1582. Q_snprintf( pDest, iDestSize, "%s", pLocationName );
  1583. return Q_strlen(pDest);
  1584. }
  1585. int CDODPlayer::GetTimeleftAsString( char *pDest, int iDestSize )
  1586. {
  1587. if( !DODGameRules()->IsGameUnderTimeLimit() )
  1588. {
  1589. Q_snprintf( pDest, iDestSize, "-:--" );
  1590. return 4;
  1591. }
  1592. else
  1593. {
  1594. int iTimeLeft = DODGameRules()->GetTimeLeft();
  1595. if ( iTimeLeft < 0 )
  1596. {
  1597. Q_snprintf( pDest, iDestSize, "0:00" );
  1598. return 4;
  1599. }
  1600. int iMinutes = iTimeLeft / 60;
  1601. int iSeconds = iTimeLeft % 60;
  1602. Q_snprintf( pDest, iDestSize, "%d:%.02d", iMinutes, iSeconds );
  1603. return Q_strlen(pDest);
  1604. }
  1605. }
  1606. // Copy the result into pDest
  1607. // return value is number of chars copied
  1608. int CDODPlayer::GetStringForEscapeSequence( char c, char *pDest, int iDestSize )
  1609. {
  1610. int iCharsCopied = 0;
  1611. switch( c )
  1612. {
  1613. case 't':
  1614. iCharsCopied = GetTimeleftAsString( pDest, iDestSize );
  1615. break;
  1616. case 'h':
  1617. iCharsCopied = GetHealthAsString( pDest, iDestSize );
  1618. break;
  1619. case 'l':
  1620. iCharsCopied = GetNearestLocationAsString( pDest, iDestSize );
  1621. break;
  1622. case 'c':
  1623. iCharsCopied = GetPlayerClassAsString( pDest, iDestSize );
  1624. break;
  1625. /*case 'i':
  1626. iCharsCopied = GetLastPlayerIDAsString( pDest, iDestSize );
  1627. break;
  1628. case 'r':
  1629. iCharsCopied = GetClosestPlayerHealthAsString( pDest, iDestSize );
  1630. break;
  1631. */
  1632. default:
  1633. iCharsCopied = 0;
  1634. break;
  1635. }
  1636. return iCharsCopied;
  1637. }
  1638. void CDODPlayer::CheckChatText( char *p, int bufsize )
  1639. {
  1640. //Look for escape sequences and replace
  1641. char *buf = new char[bufsize];
  1642. int pos = 0;
  1643. // Parse say text for escape sequences
  1644. for ( char *pSrc = p; pSrc != NULL && *pSrc != 0 && pos < bufsize-1; pSrc++ )
  1645. {
  1646. if ( *pSrc == '%' )
  1647. {
  1648. char szSubst[32];
  1649. int iResult = GetStringForEscapeSequence( *(pSrc+1), szSubst, sizeof(szSubst) );
  1650. if( iResult )
  1651. {
  1652. buf[pos] = '\0';
  1653. Q_strncat( buf, szSubst, bufsize, COPY_ALL_CHARACTERS );
  1654. pos = MIN( pos + Q_strlen(szSubst), bufsize-1 );
  1655. pSrc++;
  1656. }
  1657. else
  1658. {
  1659. //just copy in the '%'
  1660. buf[pos] = *pSrc;
  1661. pos++;
  1662. }
  1663. }
  1664. else
  1665. {
  1666. // copy each char across
  1667. buf[pos] = *pSrc;
  1668. pos++;
  1669. }
  1670. }
  1671. buf[pos] = '\0';
  1672. // copy buf back into p
  1673. Q_strncpy( p, buf, bufsize );
  1674. delete[] buf;
  1675. const char *pReadyCheck = p;
  1676. DODGameRules()->CheckChatForReadySignal( this, pReadyCheck );
  1677. }
  1678. bool CDODPlayer::ClientCommand( const CCommand &args )
  1679. {
  1680. const char *pcmd = args[0];
  1681. if ( FStrEq( pcmd, "jointeam" ) )
  1682. {
  1683. if ( args.ArgC() < 2 )
  1684. {
  1685. Warning( "Player sent bad jointeam syntax\n" );
  1686. }
  1687. int iTeam = atoi( args[1] );
  1688. HandleCommand_JoinTeam( iTeam );
  1689. return true;
  1690. }
  1691. else if( !Q_strncmp( pcmd, "cls_", 4 ) )
  1692. {
  1693. CDODTeam *pTeam = GetGlobalDODTeam( GetTeamNumber() );
  1694. Assert( pTeam );
  1695. int iClassIndex = PLAYERCLASS_UNDEFINED;
  1696. if( pTeam->IsClassOnTeam( pcmd, iClassIndex ) )
  1697. {
  1698. HandleCommand_JoinClass( iClassIndex );
  1699. }
  1700. else
  1701. {
  1702. DevMsg( "player tried to join a class that isn't on this team ( %s )\n", pcmd );
  1703. ShowClassSelectMenu();
  1704. }
  1705. return true;
  1706. }
  1707. else if ( FStrEq( pcmd, "spectate" ) )
  1708. {
  1709. // instantly join spectators
  1710. HandleCommand_JoinTeam( TEAM_SPECTATOR );
  1711. return true;
  1712. }
  1713. else if ( FStrEq( pcmd, "joingame" ) )
  1714. {
  1715. // player just closed MOTD dialog
  1716. if ( m_iPlayerState == STATE_WELCOME )
  1717. {
  1718. State_Transition( STATE_PICKINGTEAM );
  1719. }
  1720. return true;
  1721. }
  1722. else if ( FStrEq( pcmd, "joinclass" ) )
  1723. {
  1724. if ( args.ArgC() < 2 )
  1725. {
  1726. Warning( "Player sent bad joinclass syntax\n" );
  1727. }
  1728. int iClass = atoi( args[1] );
  1729. HandleCommand_JoinClass( iClass );
  1730. return true;
  1731. }
  1732. else if ( FStrEq( pcmd, "dropammo" ) )
  1733. {
  1734. DropGenericAmmo();
  1735. return true;
  1736. }
  1737. else if ( FStrEq( pcmd, "drop" ) )
  1738. {
  1739. DropActiveWeapon();
  1740. return true;
  1741. }
  1742. else if( !Q_strncmp( pcmd, "voice_", 6 ) )
  1743. {
  1744. HandleCommand_Voice( pcmd );
  1745. return true;
  1746. }
  1747. else if( !Q_strncmp( pcmd, "signal_", 7 ) )
  1748. {
  1749. HandleCommand_HandSignal( pcmd );
  1750. return true;
  1751. }
  1752. else if ( FStrEq( pcmd, "extendfreeze" ) )
  1753. {
  1754. m_flDeathTime += 2.0f;
  1755. return true;
  1756. }
  1757. #if defined ( DEBUG )
  1758. else if ( FStrEq( pcmd, "debug" ) )
  1759. {
  1760. DODGameRules()->WriteStatsFile( "1.xml" );
  1761. return true;
  1762. }
  1763. else if ( FStrEq( pcmd, "test_stun" ) )
  1764. {
  1765. Vector vecForward;
  1766. AngleVectors( EyeAngles(), &vecForward );
  1767. trace_t tr;
  1768. UTIL_TraceLine( EyePosition(), EyePosition() + vecForward * 1000, MASK_SOLID, NULL, &tr );
  1769. float flStunAmount = 100;
  1770. float flStunRadius = 100;
  1771. if ( args.ArgC() > 1 )
  1772. {
  1773. flStunAmount = atof( args[1] );
  1774. }
  1775. if ( args.ArgC() > 2 )
  1776. {
  1777. flStunRadius = atof( args[2] );
  1778. }
  1779. if ( tr.fraction < 1.0 )
  1780. {
  1781. CTakeDamageInfo info( this, this, vec3_origin, tr.endpos, flStunAmount, DMG_STUN );
  1782. DODGameRules()->RadiusStun( info, tr.endpos, flStunRadius );
  1783. }
  1784. return true;
  1785. }
  1786. else if ( FStrEq( pcmd, "switch_prim" ) )
  1787. {
  1788. CWeaponDODBase *pWpn = (CWeaponDODBase *)Weapon_GetSlot( WPN_SLOT_PRIMARY );
  1789. Weapon_Switch( pWpn );
  1790. return true;
  1791. }
  1792. else if ( FStrEq( pcmd, "switch_sec" ) )
  1793. {
  1794. CWeaponDODBase *pWpn = (CWeaponDODBase *)Weapon_GetSlot( WPN_SLOT_SECONDARY );
  1795. Weapon_Switch( pWpn );
  1796. return true;
  1797. }
  1798. else if ( FStrEq( pcmd, "switch_melee" ) )
  1799. {
  1800. CWeaponDODBase *pWpn = (CWeaponDODBase *)Weapon_GetSlot( WPN_SLOT_MELEE );
  1801. Weapon_Switch( pWpn );
  1802. return true;
  1803. }
  1804. else if ( FStrEq( pcmd, "switch_gren" ) )
  1805. {
  1806. CWeaponDODBase *pWpn = (CWeaponDODBase *)Weapon_GetSlot( WPN_SLOT_GRENADES );
  1807. Weapon_Switch( pWpn );
  1808. return true;
  1809. }
  1810. else if ( FStrEq( pcmd, "switch_tnt" ) )
  1811. {
  1812. CWeaponDODBase *pWpn = (CWeaponDODBase *)Weapon_GetSlot( WPN_SLOT_BOMB );
  1813. Weapon_Switch( pWpn );
  1814. return true;
  1815. }
  1816. else if ( FStrEq( pcmd, "nextwpn" ) )
  1817. {
  1818. CBaseCombatWeapon *pWpn = GetActiveWeapon();
  1819. SwitchToNextBestWeapon( pWpn );
  1820. return true;
  1821. }
  1822. else if ( FStrEq( pcmd, "smoke" ) )
  1823. {
  1824. CWeaponDODBase *pWpn = (CWeaponDODBase *)Weapon_GetSlot( 3 );
  1825. Weapon_Switch( pWpn );
  1826. return true;
  1827. }
  1828. else if ( FStrEq( pcmd, "resetents" ) )
  1829. {
  1830. DODGameRules()->CleanUpMap();
  1831. return true;
  1832. }
  1833. else if( FStrEq( pcmd, "pop" ) )
  1834. {
  1835. Vector vecDir(0,0,200);
  1836. if ( args.ArgC() > 1 )
  1837. {
  1838. vecDir.z = atof( args[1] );
  1839. }
  1840. PopHelmet( vecDir, vec3_origin );
  1841. return true;
  1842. }
  1843. else if ( FStrEq( pcmd, "bandage" ) )
  1844. {
  1845. if ( sv_cheats->GetBool() )
  1846. {
  1847. Bandage();
  1848. }
  1849. return true;
  1850. }
  1851. else if ( FStrEq( pcmd, "printstats" ) )
  1852. {
  1853. PrintLifetimeStats();
  1854. return true;
  1855. }
  1856. #endif //_DEBUG
  1857. return BaseClass::ClientCommand( args );
  1858. }
  1859. // returns true if the selection has been handled and the player's menu
  1860. // can be closed...false if the menu should be displayed again
  1861. bool CDODPlayer::HandleCommand_JoinTeam( int team )
  1862. {
  1863. CDODGameRules *mp = DODGameRules();
  1864. int iOldTeam = GetTeamNumber();
  1865. int iOldPlayerClass = m_Shared.DesiredPlayerClass();
  1866. if ( !GetGlobalTeam( team ) )
  1867. {
  1868. Warning( "HandleCommand_JoinTeam( %d ) - invalid team index.\n", team );
  1869. return false;
  1870. }
  1871. // If we already died and changed teams once, deny
  1872. if( m_bTeamChanged && team != TEAM_SPECTATOR && iOldTeam != TEAM_SPECTATOR )
  1873. {
  1874. ClientPrint( this, HUD_PRINTCENTER, "game_switch_teams_once" );
  1875. return true;
  1876. }
  1877. if ( team == TEAM_UNASSIGNED )
  1878. {
  1879. // Attempt to auto-select a team, may set team to T, CT or SPEC
  1880. team = mp->SelectDefaultTeam();
  1881. if ( team == TEAM_UNASSIGNED )
  1882. {
  1883. // still team unassigned, try to kick a bot if possible
  1884. ClientPrint( this, HUD_PRINTTALK, "#All_Teams_Full" );
  1885. team = TEAM_SPECTATOR;
  1886. }
  1887. }
  1888. if ( team == iOldTeam )
  1889. return true; // we wouldn't change the team
  1890. if ( mp->TeamFull( team ) )
  1891. {
  1892. if ( team == TEAM_ALLIES )
  1893. {
  1894. ClientPrint( this, HUD_PRINTTALK, "#Allies_Full" );
  1895. }
  1896. else if ( team == TEAM_AXIS )
  1897. {
  1898. ClientPrint( this, HUD_PRINTTALK, "#Axis_Full" );
  1899. }
  1900. ShowViewPortPanel( PANEL_TEAM );
  1901. return false;
  1902. }
  1903. if ( team == TEAM_SPECTATOR )
  1904. {
  1905. // Prevent this if the cvar is set
  1906. if ( !mp_allowspectators.GetInt() && !IsHLTV() )
  1907. {
  1908. ClientPrint( this, HUD_PRINTTALK, "#Cannot_Be_Spectator" );
  1909. ShowViewPortPanel( PANEL_TEAM );
  1910. return false;
  1911. }
  1912. ChangeTeam( TEAM_SPECTATOR );
  1913. return true;
  1914. }
  1915. // If the code gets this far, the team is not TEAM_UNASSIGNED
  1916. // Player is switching to a new team (It is possible to switch to the
  1917. // same team just to choose a new appearance)
  1918. if (mp->TeamStacked( team, GetTeamNumber() ))//players are allowed to change to their own team so they can just change their model
  1919. {
  1920. // The specified team is full
  1921. ClientPrint(
  1922. this,
  1923. HUD_PRINTCENTER,
  1924. ( team == TEAM_ALLIES ) ? "#Allies_full" : "#Axis_full" );
  1925. ShowViewPortPanel( PANEL_TEAM );
  1926. return false;
  1927. }
  1928. // Show the appropriate Choose Appearance menu
  1929. // This must come before ClientKill() for CheckWinConditions() to function properly
  1930. // Switch their actual team...
  1931. ChangeTeam( team );
  1932. DODGameRules()->CreateOrJoinRespawnWave( this );
  1933. // Force them to choose a new class
  1934. m_Shared.SetDesiredPlayerClass( PLAYERCLASS_UNDEFINED );
  1935. m_Shared.SetPlayerClass( PLAYERCLASS_UNDEFINED );
  1936. TallyLatestTimePlayedPerClass( iOldTeam, iOldPlayerClass );
  1937. return true;
  1938. }
  1939. void CDODPlayer::State_Transition( DODPlayerState newState )
  1940. {
  1941. State_Leave();
  1942. State_Enter( newState );
  1943. }
  1944. void CDODPlayer::State_Enter( DODPlayerState newState )
  1945. {
  1946. m_iPlayerState = newState;
  1947. m_pCurStateInfo = State_LookupInfo( newState );
  1948. if ( dod_playerstatetransitions.GetInt() == -1 || dod_playerstatetransitions.GetInt() == entindex() )
  1949. {
  1950. if ( m_pCurStateInfo )
  1951. Msg( "ShowStateTransitions: entering '%s'\n", m_pCurStateInfo->m_pStateName );
  1952. else
  1953. Msg( "ShowStateTransitions: entering #%d\n", newState );
  1954. }
  1955. // Initialize the new state.
  1956. if ( m_pCurStateInfo && m_pCurStateInfo->pfnEnterState )
  1957. (this->*m_pCurStateInfo->pfnEnterState)();
  1958. }
  1959. void CDODPlayer::State_Leave()
  1960. {
  1961. if ( m_pCurStateInfo && m_pCurStateInfo->pfnLeaveState )
  1962. {
  1963. (this->*m_pCurStateInfo->pfnLeaveState)();
  1964. }
  1965. }
  1966. void CDODPlayer::State_PreThink()
  1967. {
  1968. if ( m_pCurStateInfo && m_pCurStateInfo->pfnPreThink )
  1969. {
  1970. (this->*m_pCurStateInfo->pfnPreThink)();
  1971. }
  1972. }
  1973. CDODPlayerStateInfo* CDODPlayer::State_LookupInfo( DODPlayerState state )
  1974. {
  1975. // This table MUST match the
  1976. static CDODPlayerStateInfo playerStateInfos[] =
  1977. {
  1978. { STATE_ACTIVE, "STATE_ACTIVE", &CDODPlayer::State_Enter_ACTIVE, NULL, &CDODPlayer::State_PreThink_ACTIVE },
  1979. { STATE_WELCOME, "STATE_WELCOME", &CDODPlayer::State_Enter_WELCOME, NULL, &CDODPlayer::State_PreThink_WELCOME },
  1980. { STATE_PICKINGTEAM, "STATE_PICKINGTEAM", &CDODPlayer::State_Enter_PICKINGTEAM, NULL, &CDODPlayer::State_PreThink_PICKING },
  1981. { STATE_PICKINGCLASS, "STATE_PICKINGCLASS", &CDODPlayer::State_Enter_PICKINGCLASS, NULL, &CDODPlayer::State_PreThink_PICKING },
  1982. { STATE_DEATH_ANIM, "STATE_DEATH_ANIM", &CDODPlayer::State_Enter_DEATH_ANIM, NULL, &CDODPlayer::State_PreThink_DEATH_ANIM },
  1983. { STATE_OBSERVER_MODE, "STATE_OBSERVER_MODE", &CDODPlayer::State_Enter_OBSERVER_MODE, NULL, &CDODPlayer::State_PreThink_OBSERVER_MODE }
  1984. };
  1985. for ( int i=0; i < ARRAYSIZE( playerStateInfos ); i++ )
  1986. {
  1987. if ( playerStateInfos[i].m_iPlayerState == state )
  1988. return &playerStateInfos[i];
  1989. }
  1990. return NULL;
  1991. }
  1992. void CDODPlayer::PhysObjectSleep()
  1993. {
  1994. IPhysicsObject *pObj = VPhysicsGetObject();
  1995. if ( pObj )
  1996. pObj->Sleep();
  1997. }
  1998. void CDODPlayer::PhysObjectWake()
  1999. {
  2000. IPhysicsObject *pObj = VPhysicsGetObject();
  2001. if ( pObj )
  2002. pObj->Wake();
  2003. }
  2004. void CDODPlayer::State_Enter_WELCOME()
  2005. {
  2006. SetMoveType( MOVETYPE_NONE );
  2007. AddSolidFlags( FSOLID_NOT_SOLID );
  2008. PhysObjectSleep();
  2009. // Show info panel
  2010. if ( IsBot() )
  2011. {
  2012. // If they want to auto join a team for debugging, pretend they clicked the button.
  2013. CCommand args;
  2014. args.Tokenize( "joingame" );
  2015. ClientCommand( args );
  2016. }
  2017. else
  2018. {
  2019. const ConVar *hostname = cvar->FindVar( "hostname" );
  2020. const char *title = (hostname) ? hostname->GetString() : "MESSAGE OF THE DAY";
  2021. KeyValues *data = new KeyValues("data");
  2022. data->SetString( "title", title ); // info panel title
  2023. data->SetString( "type", "1" ); // show userdata from stringtable entry
  2024. data->SetString( "msg", "motd" ); // use this stringtable entry
  2025. data->SetInt( "cmd", TEXTWINDOW_CMD_CHANGETEAM ); // exec this command if panel closed
  2026. data->SetBool( "unload", sv_motd_unload_on_dismissal.GetBool() );
  2027. ShowViewPortPanel( PANEL_INFO, true, data );
  2028. data->deleteThis();
  2029. }
  2030. }
  2031. void CDODPlayer::State_PreThink_WELCOME()
  2032. {
  2033. // Verify some state.
  2034. Assert( GetMoveType() == MOVETYPE_NONE );
  2035. Assert( IsSolidFlagSet( FSOLID_NOT_SOLID ) );
  2036. Assert( GetAbsVelocity().Length() == 0 );
  2037. CheckRotateIntroCam();
  2038. }
  2039. void CDODPlayer::State_Enter_PICKINGTEAM()
  2040. {
  2041. ShowViewPortPanel( PANEL_TEAM );
  2042. }
  2043. void CDODPlayer::State_PreThink_PICKING()
  2044. {
  2045. }
  2046. void CDODPlayer::State_Enter_DEATH_ANIM()
  2047. {
  2048. if ( HasWeapons() )
  2049. {
  2050. // we drop the guns here because weapons that have an area effect and can kill their user
  2051. // will sometimes crash coming back from CBasePlayer::Killed() if they kill their owner because the
  2052. // player class sometimes is freed. It's safer to manipulate the weapons once we know
  2053. // we aren't calling into any of their code anymore through the player pointer.
  2054. PackDeadPlayerItems();
  2055. }
  2056. // Used for a timer.
  2057. m_flDeathTime = gpGlobals->curtime;
  2058. StartObserverMode( OBS_MODE_DEATHCAM ); // go to observer mode
  2059. RemoveEffects( EF_NODRAW ); // still draw player body
  2060. DODGameRules()->CreateOrJoinRespawnWave( this );
  2061. m_bAbortFreezeCam = false;
  2062. m_bPlayedFreezeCamSound = false;
  2063. }
  2064. #define DOD_DEATH_ANIMATION_TIME 1.6f
  2065. #define DOD_FREEZECAM_LENGTH 3.4f
  2066. void CDODPlayer::State_PreThink_DEATH_ANIM()
  2067. {
  2068. // If the anim is done playing, go to the next state (waiting for a keypress to
  2069. // either respawn the guy or put him into observer mode).
  2070. if ( GetFlags() & FL_ONGROUND )
  2071. {
  2072. float flForward = GetAbsVelocity().Length() - 20;
  2073. if (flForward <= 0)
  2074. {
  2075. SetAbsVelocity( vec3_origin );
  2076. }
  2077. else
  2078. {
  2079. Vector vAbsVel = GetAbsVelocity();
  2080. VectorNormalize( vAbsVel );
  2081. vAbsVel *= flForward;
  2082. SetAbsVelocity( vAbsVel );
  2083. }
  2084. }
  2085. if ( dod_freezecam.GetBool() )
  2086. {
  2087. // important! freeze end time must match DEATH_CAM_TIME
  2088. // or else players will start missing respawn waves.
  2089. float flFreezeEnd = (m_flDeathTime + DOD_DEATH_ANIMATION_TIME + DOD_FREEZECAM_LENGTH );
  2090. if ( !m_bPlayedFreezeCamSound && GetObserverTarget() && GetObserverTarget() != this )
  2091. {
  2092. // Start the sound so that it ends at the freezecam lock on time
  2093. float flFreezeSoundLength = 0.3;
  2094. float flFreezeSoundTime = m_flDeathTime + DOD_DEATH_ANIMATION_TIME - flFreezeSoundLength + 0.4f /* travel time */;
  2095. if ( gpGlobals->curtime >= flFreezeSoundTime )
  2096. {
  2097. CSingleUserRecipientFilter filter( this );
  2098. EmitSound_t params;
  2099. params.m_flSoundTime = 0;
  2100. params.m_pSoundName = "Player.FreezeCam";
  2101. EmitSound( filter, entindex(), params );
  2102. m_bPlayedFreezeCamSound = true;
  2103. }
  2104. }
  2105. if ( gpGlobals->curtime >= (m_flDeathTime + DOD_DEATH_ANIMATION_TIME ) ) // allow 2 seconds death animation / death cam
  2106. {
  2107. if ( GetObserverTarget() && GetObserverTarget() != this )
  2108. {
  2109. if ( !m_bAbortFreezeCam && gpGlobals->curtime < flFreezeEnd )
  2110. {
  2111. if ( GetObserverMode() != OBS_MODE_FREEZECAM )
  2112. {
  2113. StartObserverMode( OBS_MODE_FREEZECAM );
  2114. PhysObjectSleep();
  2115. }
  2116. return;
  2117. }
  2118. }
  2119. if ( GetObserverMode() == OBS_MODE_FREEZECAM )
  2120. {
  2121. // If we're in freezecam, and we want out, abort.
  2122. if ( m_bAbortFreezeCam )
  2123. {
  2124. m_lifeState = LIFE_DEAD;
  2125. StopAnimation();
  2126. IncrementInterpolationFrame();
  2127. if ( GetMoveType() != MOVETYPE_NONE && (GetFlags() & FL_ONGROUND) )
  2128. SetMoveType( MOVETYPE_NONE );
  2129. State_Transition( STATE_OBSERVER_MODE );
  2130. }
  2131. }
  2132. // Don't allow anyone to respawn until freeze time is over, even if they're not
  2133. // in freezecam. This prevents players skipping freezecam to spawn faster.
  2134. if ( gpGlobals->curtime >= flFreezeEnd )
  2135. {
  2136. m_lifeState = LIFE_DEAD;
  2137. StopAnimation();
  2138. IncrementInterpolationFrame();
  2139. if ( GetMoveType() != MOVETYPE_NONE && (GetFlags() & FL_ONGROUND) )
  2140. SetMoveType( MOVETYPE_NONE );
  2141. State_Transition( STATE_OBSERVER_MODE );
  2142. }
  2143. }
  2144. }
  2145. else
  2146. {
  2147. if ( gpGlobals->curtime >= (m_flDeathTime + DEATH_CAM_TIME ) ) // allow x seconds death animation / death cam
  2148. {
  2149. m_lifeState = LIFE_DEAD;
  2150. StopAnimation();
  2151. IncrementInterpolationFrame();
  2152. if ( GetMoveType() != MOVETYPE_NONE && (GetFlags() & FL_ONGROUND) )
  2153. SetMoveType( MOVETYPE_NONE );
  2154. // Disabled death cam!
  2155. //StartReplayMode( 8, 8, entindex() );
  2156. State_Transition( STATE_OBSERVER_MODE );
  2157. }
  2158. }
  2159. }
  2160. //-----------------------------------------------------------------------------
  2161. // Purpose:
  2162. //-----------------------------------------------------------------------------
  2163. void CDODPlayer::AttemptToExitFreezeCam( void )
  2164. {
  2165. float flFreezeTravelTime = (m_flDeathTime + DOD_DEATH_ANIMATION_TIME ) + 0.4 /*spec_freeze_traveltime.GetFloat()*/ + 0.5;
  2166. if ( gpGlobals->curtime < flFreezeTravelTime )
  2167. return;
  2168. m_bAbortFreezeCam = true;
  2169. }
  2170. void CDODPlayer::State_Enter_OBSERVER_MODE()
  2171. {
  2172. // Always start a spectator session in chase mode
  2173. m_iObserverLastMode = OBS_MODE_CHASE;
  2174. if( m_hObserverTarget == NULL )
  2175. {
  2176. // find a new observer target
  2177. CheckObserverSettings();
  2178. }
  2179. // Change our observer target to the player nearest us
  2180. CTeam *pTeam = GetGlobalTeam( TEAM_ALLIES );
  2181. CBasePlayer *pPlayer;
  2182. Vector localOrigin = GetAbsOrigin();
  2183. Vector targetOrigin;
  2184. float flMinDist = FLT_MAX;
  2185. float flDist;
  2186. for ( int i=0;i<pTeam->GetNumPlayers();i++ )
  2187. {
  2188. pPlayer = pTeam->GetPlayer(i);
  2189. if ( !pPlayer )
  2190. continue;
  2191. if ( !IsValidObserverTarget(pPlayer) )
  2192. continue;
  2193. targetOrigin = pPlayer->GetAbsOrigin();
  2194. flDist = ( targetOrigin - localOrigin ).Length();
  2195. if ( flDist < flMinDist )
  2196. {
  2197. m_hObserverTarget.Set( pPlayer );
  2198. flMinDist = flDist;
  2199. }
  2200. }
  2201. if ( GetTeamNumber() == TEAM_ALLIES || GetTeamNumber() == TEAM_AXIS )
  2202. {
  2203. // we are entering spec while playing ( not on TEAM_SPECTATOR )
  2204. Hints()->HintMessage( HINT_CLASSMENU, true );
  2205. }
  2206. StartObserverMode( m_iObserverLastMode );
  2207. PhysObjectSleep();
  2208. }
  2209. void CDODPlayer::State_PreThink_OBSERVER_MODE()
  2210. {
  2211. // Make sure nobody has changed any of our state.
  2212. Assert( m_takedamage == DAMAGE_NO );
  2213. Assert( IsSolidFlagSet( FSOLID_NOT_SOLID ) );
  2214. // Must be dead.
  2215. Assert( m_lifeState == LIFE_DEAD );
  2216. Assert( pl.deadflag );
  2217. }
  2218. void CDODPlayer::State_Enter_PICKINGCLASS()
  2219. {
  2220. // go to spec mode, if dying keep deathcam
  2221. if ( GetObserverMode() == OBS_MODE_DEATHCAM )
  2222. {
  2223. StartObserverMode( OBS_MODE_DEATHCAM );
  2224. }
  2225. else
  2226. {
  2227. StartObserverMode( OBS_MODE_ROAMING );
  2228. }
  2229. PhysObjectSleep();
  2230. ShowClassSelectMenu();
  2231. }
  2232. void CDODPlayer::State_Enter_ACTIVE()
  2233. {
  2234. SetMoveType( MOVETYPE_WALK );
  2235. RemoveSolidFlags( FSOLID_NOT_SOLID );
  2236. m_Local.m_iHideHUD = 0;
  2237. PhysObjectWake();
  2238. }
  2239. void CDODPlayer::State_PreThink_ACTIVE()
  2240. {
  2241. return;
  2242. }
  2243. //-----------------------------------------------------------------------------
  2244. // Purpose: Initialize prone at spawn.
  2245. //-----------------------------------------------------------------------------
  2246. void CDODPlayer::InitProne( void )
  2247. {
  2248. m_Shared.SetProne( false, true );
  2249. }
  2250. void CDODPlayer::InitSprinting( void )
  2251. {
  2252. m_Shared.SetSprinting( false );
  2253. }
  2254. //-----------------------------------------------------------------------------
  2255. // Purpose: Returns whether or not we are allowed to sprint now.
  2256. //-----------------------------------------------------------------------------
  2257. bool CDODPlayer::CanSprint()
  2258. {
  2259. return (
  2260. //!IsWalking() && // Not if we're walking
  2261. !( m_Local.m_bDucked && !m_Local.m_bDucking ) && // Nor if we're ducking
  2262. (GetWaterLevel() != 3) ); // Certainly not underwater
  2263. }
  2264. void CDODPlayer::MoveToNextIntroCamera()
  2265. {
  2266. m_pIntroCamera = gEntList.FindEntityByClassname( m_pIntroCamera, "point_viewcontrol" );
  2267. // if m_pIntroCamera is NULL we just were at end of list, start searching from start again
  2268. if(!m_pIntroCamera)
  2269. m_pIntroCamera = gEntList.FindEntityByClassname(m_pIntroCamera, "point_viewcontrol");
  2270. if( !m_pIntroCamera ) //if there are no cameras find a spawn point and black out the screen
  2271. {
  2272. DODGameRules()->GetPlayerSpawnSpot( this );
  2273. SetAbsAngles( QAngle( 0, 0, 0 ) );
  2274. m_pIntroCamera = NULL; // never update again
  2275. }
  2276. else
  2277. {
  2278. Vector vIntroCamera = m_pIntroCamera->GetAbsOrigin();
  2279. QAngle CamAngles = m_pIntroCamera->GetAbsAngles();
  2280. UTIL_SetSize( this, vec3_origin, vec3_origin );
  2281. SetAbsOrigin( vIntroCamera );
  2282. SetAbsAngles( CamAngles );
  2283. SnapEyeAngles( CamAngles );
  2284. SetViewOffset( vec3_origin );
  2285. m_fIntroCamTime = gpGlobals->curtime + 6;
  2286. }
  2287. }
  2288. CBaseEntity *CDODPlayer::SelectSpawnSpot( CUtlVector<EHANDLE> *pSpawnPoints, int &iLastSpawnIndex )
  2289. {
  2290. Assert( pSpawnPoints );
  2291. int iNumSpawns = pSpawnPoints->Count();
  2292. CBaseEntity *pSpot = NULL;
  2293. for ( int i=0;i<iNumSpawns;i++ )
  2294. {
  2295. int testIndex = ( iLastSpawnIndex + i ) % iNumSpawns;
  2296. EHANDLE handle = pSpawnPoints->Element(testIndex);
  2297. if ( !handle )
  2298. continue;
  2299. pSpot = handle.Get();
  2300. if ( !pSpot )
  2301. continue;
  2302. if( g_pGameRules->IsSpawnPointValid( pSpot, this ) )
  2303. {
  2304. if ( pSpot->GetAbsOrigin() == Vector( 0, 0, 0 ) )
  2305. {
  2306. continue;
  2307. }
  2308. // if so, go to pSpot
  2309. iLastSpawnIndex = testIndex;
  2310. return pSpot;
  2311. }
  2312. }
  2313. DevMsg("CDODPlayer::SelectSpawnSpot: couldn't find valid spawn point.\n");
  2314. // If we get here, all spawn points are blocked.
  2315. // Try the 4 locations around each spawn point
  2316. Assert( pSpot );
  2317. Vector vecForward, vecRight, vecUp;
  2318. Vector mins = VEC_HULL_MIN;
  2319. Vector maxs = VEC_HULL_MAX;
  2320. float flHalfWidth = maxs.x;
  2321. float flTestDist = 3 * flHalfWidth;
  2322. for ( int i=0;i<iNumSpawns;i++ )
  2323. {
  2324. int testIndex = ( iLastSpawnIndex + i ) % iNumSpawns;
  2325. EHANDLE handle = pSpawnPoints->Element(testIndex);
  2326. if ( !handle )
  2327. continue;
  2328. pSpot = handle.Get();
  2329. if ( !pSpot )
  2330. continue;
  2331. // we know this spot is blocked, but check to the N, E, S, W of it.
  2332. // if we find a clear spot, create a new spawn point there, copied from pSpot
  2333. AngleVectors( pSpot->GetAbsAngles(), &vecForward, &vecRight, &vecUp );
  2334. for( int i=0;i<4;i++ )
  2335. {
  2336. Vector origin = pSpot->GetAbsOrigin();
  2337. switch( i )
  2338. {
  2339. case 0: origin += vecForward * flTestDist; break;
  2340. case 1: origin += vecRight * flTestDist; break;
  2341. case 2: origin -= vecForward * flTestDist; break;
  2342. case 3: origin -= vecRight * flTestDist; break;
  2343. }
  2344. Vector vTestMins = origin + mins;
  2345. Vector vTestMaxs = origin + maxs;
  2346. if ( UTIL_IsSpaceEmpty( this, vTestMins, vTestMaxs ) )
  2347. {
  2348. QAngle spotAngles = pSpot->GetAbsAngles();
  2349. // make a new spawnpoint so we don't have to do this a bunch of times
  2350. pSpot = CreateEntityByName( pSpot->GetClassname() );
  2351. pSpot->SetAbsOrigin( origin );
  2352. pSpot->SetAbsAngles( spotAngles );
  2353. // delete it in a while so we don't accumulate entities
  2354. pSpot->SetThink( &CBaseEntity::SUB_Remove );
  2355. pSpot->SetNextThink( gpGlobals->curtime + 0.5 );
  2356. Assert( pSpot );
  2357. iLastSpawnIndex = 0;
  2358. return pSpot;
  2359. }
  2360. }
  2361. }
  2362. // if we get here, we're really screwed. Spawning the person is going to stick them
  2363. // into someone or something, but we really tried hard, I swear.
  2364. Assert( !"We should never be here" );
  2365. return NULL;
  2366. }
  2367. CBaseEntity* CDODPlayer::EntSelectSpawnPoint()
  2368. {
  2369. CBaseEntity *pSpot = NULL;
  2370. switch( GetTeamNumber() )
  2371. {
  2372. case TEAM_ALLIES:
  2373. {
  2374. CUtlVector<EHANDLE> *pSpawnList = DODGameRules()->GetSpawnPointListForTeam( TEAM_ALLIES );
  2375. pSpot = SelectSpawnSpot( pSpawnList, g_iLastAlliesSpawnIndex );
  2376. }
  2377. break;
  2378. case TEAM_AXIS:
  2379. {
  2380. CUtlVector<EHANDLE> *pSpawnList = DODGameRules()->GetSpawnPointListForTeam( TEAM_AXIS );
  2381. pSpot = SelectSpawnSpot( pSpawnList, g_iLastAxisSpawnIndex );
  2382. }
  2383. break;
  2384. case TEAM_SPECTATOR:
  2385. case TEAM_UNASSIGNED:
  2386. default:
  2387. {
  2388. pSpot = CBaseEntity::Instance( INDEXENT(0) );
  2389. }
  2390. break;
  2391. }
  2392. if ( !pSpot )
  2393. {
  2394. Warning( "PutClientInServer: no valid spawns on level\n" );
  2395. return CBaseEntity::Instance( INDEXENT(0) );
  2396. }
  2397. return pSpot;
  2398. }
  2399. //-----------------------------------------------------------------------------
  2400. // Purpose: Put the player in the specified team
  2401. //-----------------------------------------------------------------------------
  2402. void CDODPlayer::ChangeTeam( int iTeamNum )
  2403. {
  2404. if ( !GetGlobalTeam( iTeamNum ) )
  2405. {
  2406. Warning( "CDODPlayer::ChangeTeam( %d ) - invalid team index.\n", iTeamNum );
  2407. return;
  2408. }
  2409. int iOldTeam = GetTeamNumber();
  2410. // if this is our current team, just abort
  2411. if ( iTeamNum == iOldTeam )
  2412. return;
  2413. m_bTeamChanged = true;
  2414. // do the team change:
  2415. BaseClass::ChangeTeam( iTeamNum );
  2416. // update client state
  2417. if ( iTeamNum == TEAM_UNASSIGNED )
  2418. {
  2419. State_Transition( STATE_OBSERVER_MODE );
  2420. }
  2421. else if ( iTeamNum == TEAM_SPECTATOR )
  2422. {
  2423. RemoveAllItems( true );
  2424. State_Transition( STATE_OBSERVER_MODE );
  2425. StatEvent_UploadStats();
  2426. m_StatProperty.SetClassAndTeamForThisLife( -1, TEAM_SPECTATOR );
  2427. }
  2428. else // active player
  2429. {
  2430. if ( !IsDead() )
  2431. {
  2432. // Kill player if switching teams while alive
  2433. CommitSuicide();
  2434. // add 1 to frags to balance out the 1 subtracted for killing yourself
  2435. // IncrementFragCount( 1 );
  2436. }
  2437. if( iOldTeam == TEAM_SPECTATOR )
  2438. SetMoveType( MOVETYPE_NONE );
  2439. // Put up the class selection menu.
  2440. State_Transition( STATE_PICKINGCLASS );
  2441. }
  2442. RemoveNemesisRelationships();
  2443. }
  2444. void CDODPlayer::DODRespawn( void )
  2445. {
  2446. // if it's a forced respawn, accumulate the stats now
  2447. if ( IsAlive() )
  2448. {
  2449. StatEvent_UploadStats();
  2450. }
  2451. RemoveAllItems(true);
  2452. StopObserverMode();
  2453. State_Transition( STATE_ACTIVE );
  2454. Spawn();
  2455. m_nButtons = 0;
  2456. SetNextThink( TICK_NEVER_THINK );
  2457. OutputDamageGiven();
  2458. OutputDamageTaken();
  2459. ResetDamageCounters();
  2460. }
  2461. bool CDODPlayer::IsReadyToPlay( void )
  2462. {
  2463. bool bResult = ( ( GetTeamNumber() == TEAM_ALLIES || GetTeamNumber() == TEAM_AXIS ) &&
  2464. m_Shared.DesiredPlayerClass() != PLAYERCLASS_UNDEFINED );
  2465. return bResult;
  2466. }
  2467. //-----------------------------------------------------------------------------
  2468. // Purpose: Returns whether or not we can switch to the given weapon.
  2469. // Input : pWeapon -
  2470. //-----------------------------------------------------------------------------
  2471. bool CDODPlayer::Weapon_CanSwitchTo( CBaseCombatWeapon *pWeapon )
  2472. {
  2473. #if !defined( CLIENT_DLL )
  2474. IServerVehicle *pVehicle = GetVehicle();
  2475. #else
  2476. IClientVehicle *pVehicle = GetVehicle();
  2477. #endif
  2478. if (pVehicle && !UsingStandardWeaponsInVehicle())
  2479. return false;
  2480. if ( !pWeapon->CanDeploy() )
  2481. return false;
  2482. if ( GetActiveWeapon() )
  2483. {
  2484. if ( !GetActiveWeapon()->CanHolster() )
  2485. return false;
  2486. }
  2487. return true;
  2488. }
  2489. void CDODPlayer::SetScore( int score )
  2490. {
  2491. m_iScore = score;
  2492. }
  2493. void CDODPlayer::AddScore( int num )
  2494. {
  2495. int n = MAX( 0, num );
  2496. m_iScore += n;
  2497. }
  2498. void CDODPlayer::HandleSignals( void )
  2499. {
  2500. int changed = m_signals.Update();
  2501. int state = m_signals.GetState();
  2502. if ( changed & SIGNAL_CAPTUREAREA )
  2503. {
  2504. if ( state & SIGNAL_CAPTUREAREA )
  2505. {
  2506. //do nothing
  2507. }
  2508. else
  2509. {
  2510. SetCapAreaIndex(-1);
  2511. SetCPIndex(-1);
  2512. m_iCapAreaIconIndex = -1;
  2513. }
  2514. }
  2515. }
  2516. void CDODPlayer::SetCapAreaIndex( int index )
  2517. {
  2518. m_iCapAreaIndex = index;
  2519. }
  2520. int CDODPlayer::GetCapAreaIndex( void )
  2521. {
  2522. return m_iCapAreaIndex;
  2523. }
  2524. void CDODPlayer::SetCPIndex( int index )
  2525. {
  2526. m_Shared.SetCPIndex( index );
  2527. }
  2528. int CDODPlayer::OnTakeDamage_Alive( const CTakeDamageInfo &info )
  2529. {
  2530. // set damage type sustained
  2531. m_bitsDamageType |= info.GetDamageType();
  2532. int iInitialHealth = m_iHealth;
  2533. if ( !CBaseCombatCharacter::OnTakeDamage_Alive( info ) )
  2534. return 0;
  2535. IGameEvent * event = gameeventmanager->CreateEvent( "player_hurt" );
  2536. if ( event )
  2537. {
  2538. event->SetInt("userid", GetUserID() );
  2539. event->SetInt("health", MAX(0, m_iHealth) );
  2540. event->SetInt("damage", info.GetDamage() );
  2541. event->SetInt("hitgroup", m_LastHitGroup );
  2542. CBaseEntity *attacker = info.GetAttacker();
  2543. const char *weaponName = "";
  2544. DODWeaponID weaponID = WEAPON_NONE;
  2545. if ( attacker->IsPlayer() )
  2546. {
  2547. CBasePlayer *player = ToBasePlayer( attacker );
  2548. event->SetInt("attacker", player->GetUserID() ); // hurt by other player
  2549. // ff damage
  2550. // no hint for bomb explosions, it was their own fault!
  2551. CDODPlayer *pDODPlayer = ToDODPlayer( player );
  2552. if( pDODPlayer->GetTeamNumber() == GetTeamNumber() && pDODPlayer != this && !FBitSet( info.GetDamageType(), DMG_BOMB ) )
  2553. {
  2554. pDODPlayer->Hints()->HintMessage( HINT_FRIEND_INJURED );
  2555. }
  2556. CBaseEntity *pInflictor = info.GetInflictor();
  2557. if ( pInflictor )
  2558. {
  2559. if ( pInflictor == pDODPlayer )
  2560. {
  2561. // If the inflictor is the killer, then it must be their current weapon doing the damage
  2562. if ( pDODPlayer->GetActiveWeapon() )
  2563. {
  2564. CWeaponDODBase *pWeapon = pDODPlayer->GetActiveDODWeapon();
  2565. weaponName = pWeapon->GetClassname();
  2566. if ( info.GetDamageCustom() & MELEE_DMG_SECONDARYATTACK )
  2567. weaponID = pWeapon->GetAltWeaponID();
  2568. else
  2569. weaponID = pWeapon->GetStatsWeaponID();
  2570. }
  2571. }
  2572. else
  2573. {
  2574. weaponName = STRING( pInflictor->m_iClassname ); // it's just that easy
  2575. }
  2576. }
  2577. }
  2578. else
  2579. {
  2580. event->SetInt("attacker", 0 ); // hurt by "world"
  2581. }
  2582. if ( strncmp( weaponName, "weapon_", 7 ) == 0 )
  2583. {
  2584. weaponName += 7;
  2585. }
  2586. else if ( strncmp( weaponName, "rocket_", 7 ) == 0 )
  2587. {
  2588. weaponName += 7;
  2589. CDODBaseRocket *pRocket = dynamic_cast< CDODBaseRocket *>( info.GetInflictor() );
  2590. if ( pRocket )
  2591. {
  2592. weaponID = pRocket->GetEmitterWeaponID();
  2593. }
  2594. }
  2595. else if ( strncmp( weaponName, "grenade_", 8 ) == 0 )
  2596. {
  2597. weaponName += 8;
  2598. CDODBaseGrenade *pGren = dynamic_cast< CDODBaseGrenade *>( info.GetInflictor() );
  2599. if ( pGren )
  2600. {
  2601. weaponID = pGren->GetEmitterWeaponID();
  2602. }
  2603. }
  2604. else if ( Q_stricmp( weaponName, "dod_bomb_target" ) == 0 )
  2605. {
  2606. weaponID = WEAPON_NONE;
  2607. }
  2608. event->SetString( "weapon", weaponName );
  2609. event->SetInt( "priority", 5 );
  2610. gameeventmanager->FireEvent( event );
  2611. #ifndef CLIENT_DLL
  2612. IGameEvent * stats_event = gameeventmanager->CreateEvent( "dod_stats_player_damage" );
  2613. if ( stats_event && attacker->IsPlayer() && weaponID != WEAPON_NONE )
  2614. {
  2615. CBasePlayer *pAttacker = ToBasePlayer( attacker );
  2616. int iDamage = ( info.GetDamage() + 0.5f ); // round to nearest integer
  2617. stats_event->SetInt( "attacker", pAttacker->GetUserID() );
  2618. stats_event->SetInt( "victim", GetUserID() );
  2619. stats_event->SetInt( "weapon", weaponID );
  2620. stats_event->SetInt( "damage", iDamage );
  2621. // damage_given is the amount of damage applied, not to exceed how much life we have
  2622. stats_event->SetInt( "damage_given", MIN( iDamage, iInitialHealth ) );
  2623. CBaseEntity *pInflictor = info.GetInflictor();
  2624. float flDist = ( pInflictor->GetAbsOrigin() - GetAbsOrigin() ).Length();
  2625. stats_event->SetFloat( "distance", flDist );
  2626. stats_event->SetInt("hitgroup", m_LastHitGroup );
  2627. gameeventmanager->FireEvent( stats_event );
  2628. }
  2629. #endif //CLIENT_DLL
  2630. }
  2631. return 1;
  2632. }
  2633. ConVar dod_explosionforcescale( "dod_explosionforcescale", "1.0", FCVAR_CHEAT );
  2634. ConVar dod_bulletforcescale( "dod_bulletforcescale", "1.0", FCVAR_CHEAT );
  2635. int CDODPlayer::OnTakeDamage( const CTakeDamageInfo &inputInfo )
  2636. {
  2637. CTakeDamageInfo info = inputInfo;
  2638. CBaseEntity *pInflictor = info.GetInflictor();
  2639. if ( !pInflictor )
  2640. return 0;
  2641. if ( GetMoveType() == MOVETYPE_NOCLIP )
  2642. return 0;
  2643. // if the player's team does not match the inflictor's team
  2644. // the player has changed teams between when they started the attack
  2645. if( pInflictor->GetTeamNumber() != TEAM_UNASSIGNED &&
  2646. info.GetAttacker() != NULL &&
  2647. pInflictor->GetTeamNumber() != info.GetAttacker()->GetTeamNumber() )
  2648. {
  2649. info.SetDamage( 0 );
  2650. info.SetDamageType( 0 );
  2651. }
  2652. bool bFriendlyFire = DODGameRules()->IsFriendlyFireOn();
  2653. if ( bFriendlyFire ||
  2654. info.GetAttacker()->GetTeamNumber() != GetTeamNumber() ||
  2655. pInflictor == this ||
  2656. info.GetAttacker() == this ||
  2657. info.GetDamageType() & DMG_BOMB )
  2658. {
  2659. // Do special stun damage effect
  2660. if ( info.GetDamageType() & DMG_STUN )
  2661. {
  2662. OnDamageByStun( info );
  2663. return 0;
  2664. }
  2665. CDODPlayer *pPlayer = ToDODPlayer( info.GetAttacker() );
  2666. // keep track of amount of damage last sustained
  2667. m_lastDamageAmount = info.GetDamage();
  2668. m_LastDamageType = info.GetDamageType();
  2669. if( !FBitSet( info.GetDamageType(), DMG_FALL ) )
  2670. Pain();
  2671. float flForceScale = 1.0;
  2672. // Do special explosion damage effect
  2673. if ( info.GetDamageType() & DMG_BLAST )
  2674. {
  2675. OnDamagedByExplosion( info );
  2676. flForceScale = dod_explosionforcescale.GetFloat();
  2677. }
  2678. if( info.GetDamageType() & DMG_BULLET )
  2679. {
  2680. flForceScale = dod_bulletforcescale.GetFloat();
  2681. }
  2682. // round up!
  2683. int iDamage = (int)( info.GetDamage() + 0.5 );
  2684. if ( pPlayer )
  2685. {
  2686. // Record for the shooter
  2687. pPlayer->RecordDamageGiven( this, iDamage );
  2688. // And for the victim
  2689. RecordDamageTaken( pPlayer, iDamage );
  2690. }
  2691. else
  2692. {
  2693. RecordWorldDamageTaken( iDamage );
  2694. }
  2695. m_vecTotalBulletForce += info.GetDamageForce() * flForceScale;
  2696. CSingleUserRecipientFilter user( this );
  2697. user.MakeReliable();
  2698. UserMessageBegin( user, "Damage" );
  2699. WRITE_BYTE( (int)info.GetDamage() );
  2700. WRITE_LONG( info.GetDamageType() );
  2701. WRITE_VEC3COORD( info.GetInflictor()->WorldSpaceCenter() );
  2702. MessageEnd();
  2703. gamestats->Event_PlayerDamage( this, info );
  2704. return CBaseCombatCharacter::OnTakeDamage( info );
  2705. }
  2706. else
  2707. {
  2708. return 0;
  2709. }
  2710. }
  2711. void CDODPlayer::Pain( void )
  2712. {
  2713. if ( m_LastDamageType & DMG_CLUB)
  2714. {
  2715. EmitSound( "Player.MajorPain" );
  2716. }
  2717. else if ( m_LastDamageType & DMG_BLAST )
  2718. {
  2719. EmitSound( "Player.MajorPain" );
  2720. }
  2721. else
  2722. {
  2723. EmitSound( "Player.MinorPain" );
  2724. }
  2725. }
  2726. void CDODPlayer::DeathSound( const CTakeDamageInfo &info )
  2727. {
  2728. if ( m_LastDamageType & DMG_CLUB )
  2729. {
  2730. EmitSound( "Player.MegaPain" );
  2731. }
  2732. else if ( m_LastDamageType & DMG_BLAST )
  2733. {
  2734. EmitSound( "Player.MegaPain" );
  2735. }
  2736. else if ( m_LastHitGroup == HITGROUP_HEAD )
  2737. {
  2738. EmitSound( "Player.DeathHeadShot" );
  2739. }
  2740. else
  2741. {
  2742. EmitSound( "Player.MinorPain" );
  2743. }
  2744. }
  2745. void CDODPlayer::OnDamagedByExplosion( const CTakeDamageInfo &info )
  2746. {
  2747. if ( info.GetDamage() >= 30.0f )
  2748. {
  2749. SetContextThink( &CDODPlayer::DeafenThink, gpGlobals->curtime + 0.3, DOD_DEAFEN_CONTEXT );
  2750. // The blast will naturally blow the temp ent helmet away
  2751. PopHelmet( info.GetDamagePosition(), info.GetDamageForce() );
  2752. }
  2753. }
  2754. ConVar dod_stun_min_pitch( "dod_stun_min_pitch", "30", FCVAR_CHEAT );
  2755. ConVar dod_stun_max_pitch( "dod_stun_max_pitch", "50", FCVAR_CHEAT );
  2756. ConVar dod_stun_min_yaw( "dod_stun_min_yaw", "120", FCVAR_CHEAT );
  2757. ConVar dod_stun_max_yaw( "dod_stun_max_yaw", "150", FCVAR_CHEAT );
  2758. ConVar dod_stun_min_roll( "dod_stun_min_roll", "15", FCVAR_CHEAT );
  2759. ConVar dod_stun_max_roll( "dod_stun_max_roll", "30", FCVAR_CHEAT );
  2760. void CDODPlayer::OnDamageByStun( const CTakeDamageInfo &info )
  2761. {
  2762. DevMsg( 2, "took %.1f stun damage\n", info.GetDamage() );
  2763. float flPercent = ( info.GetDamage() / 100.0f );
  2764. m_flStunDuration = 0.0; // mark it as dirty so it transmits, incase we get the same value twice
  2765. m_flStunDuration = 4.0f * flPercent;
  2766. m_flStunMaxAlpha = 255;
  2767. float flPitch = flPercent * RandomFloat( dod_stun_min_pitch.GetFloat(), dod_stun_max_pitch.GetFloat() ) *
  2768. ( (RandomInt( 0, 1 )) ? 1 : -1 );
  2769. float flYaw = flPercent * RandomFloat( dod_stun_min_yaw.GetFloat(), dod_stun_max_yaw.GetFloat() )
  2770. * ( (RandomInt( 0, 1 )) ? 1 : -1 );
  2771. float flRoll = flPercent * RandomFloat( dod_stun_min_roll.GetFloat(), dod_stun_max_roll.GetFloat() )
  2772. * ( (RandomInt( 0, 1 )) ? 1 : -1 );
  2773. DevMsg( 2, "punch: pitch %.1f yaw %.1f roll %.1f\n", flPitch, flYaw, flRoll );
  2774. SetPunchAngle( QAngle( flPitch, flYaw, flRoll ) );
  2775. }
  2776. void CDODPlayer::DeafenThink( void )
  2777. {
  2778. int effect = random->RandomInt( 32, 34 );
  2779. CSingleUserRecipientFilter user( this );
  2780. enginesound->SetPlayerDSP( user, effect, false );
  2781. }
  2782. //=======================================================
  2783. // Remember this amount of damage that we dealt for stats
  2784. //=======================================================
  2785. void CDODPlayer::RecordDamageGiven( CDODPlayer *pVictim, int iDamageGiven )
  2786. {
  2787. if ( iDamageGiven <= 0 )
  2788. return;
  2789. FOR_EACH_LL( m_DamageGivenList, i )
  2790. {
  2791. if ( pVictim->GetLifeID() == m_DamageGivenList[i]->GetLifeID() )
  2792. {
  2793. m_DamageGivenList[i]->AddDamage( iDamageGiven );
  2794. return;
  2795. }
  2796. }
  2797. CDamageRecord *record = new CDamageRecord( pVictim->GetPlayerName(), pVictim->GetLifeID(), iDamageGiven );
  2798. int tail = m_DamageGivenList.AddToTail();
  2799. m_DamageGivenList[tail] = record;
  2800. }
  2801. //=======================================================
  2802. // Remember this amount of damage that we took for stats
  2803. //=======================================================
  2804. void CDODPlayer::RecordDamageTaken( CDODPlayer *pAttacker, int iDamageTaken )
  2805. {
  2806. if ( iDamageTaken <= 0 )
  2807. return;
  2808. FOR_EACH_LL( m_DamageTakenList, i )
  2809. {
  2810. if ( pAttacker->GetLifeID() == m_DamageTakenList[i]->GetLifeID() )
  2811. {
  2812. m_DamageTakenList[i]->AddDamage( iDamageTaken );
  2813. return;
  2814. }
  2815. }
  2816. CDamageRecord *record = new CDamageRecord( pAttacker->GetPlayerName(), pAttacker->GetLifeID(), iDamageTaken );
  2817. int tail = m_DamageTakenList.AddToTail();
  2818. m_DamageTakenList[tail] = record;
  2819. }
  2820. void CDODPlayer::RecordWorldDamageTaken( int iDamageTaken )
  2821. {
  2822. if ( iDamageTaken <= 0 )
  2823. return;
  2824. FOR_EACH_LL( m_DamageTakenList, i )
  2825. {
  2826. if ( m_DamageTakenList[i]->GetLifeID() == 0 )
  2827. {
  2828. m_DamageTakenList[i]->AddDamage( iDamageTaken );
  2829. return;
  2830. }
  2831. }
  2832. CDamageRecord *record = new CDamageRecord( "world", 0, iDamageTaken );
  2833. int tail = m_DamageTakenList.AddToTail();
  2834. m_DamageTakenList[tail] = record;
  2835. }
  2836. //=======================================================
  2837. // Reset our damage given and taken counters
  2838. //=======================================================
  2839. void CDODPlayer::ResetDamageCounters()
  2840. {
  2841. m_DamageGivenList.PurgeAndDeleteElements();
  2842. m_DamageTakenList.PurgeAndDeleteElements();
  2843. }
  2844. //=======================================================
  2845. // Output the damage that we dealt to other players
  2846. //=======================================================
  2847. void CDODPlayer::OutputDamageTaken( void )
  2848. {
  2849. bool bPrintHeader = true;
  2850. CDamageRecord *pRecord;
  2851. char buf[64];
  2852. int msg_dest = HUD_PRINTCONSOLE;
  2853. FOR_EACH_LL( m_DamageTakenList, i )
  2854. {
  2855. if( bPrintHeader )
  2856. {
  2857. ClientPrint( this, msg_dest, "Player: %s1 - Damage Taken\n", GetPlayerName() );
  2858. ClientPrint( this, msg_dest, "-------------------------\n" );
  2859. bPrintHeader = false;
  2860. }
  2861. pRecord = m_DamageTakenList[i];
  2862. if( pRecord )
  2863. {
  2864. Q_snprintf( buf, sizeof(buf), "( life ID %i ) - %d in %d %s",
  2865. pRecord->GetLifeID(),
  2866. pRecord->GetDamage(),
  2867. pRecord->GetNumHits(),
  2868. ( pRecord->GetNumHits() == 1 ) ? "hit" : "hits" );
  2869. ClientPrint( this, msg_dest, "Damage Taken from \"%s1\" - %s2\n", pRecord->GetPlayerName(), buf );
  2870. }
  2871. }
  2872. }
  2873. //=======================================================
  2874. // Output the damage that we took from other players
  2875. //=======================================================
  2876. void CDODPlayer::OutputDamageGiven( void )
  2877. {
  2878. bool bPrintHeader = true;
  2879. CDamageRecord *pRecord;
  2880. char buf[64];
  2881. int msg_dest = HUD_PRINTCONSOLE;
  2882. FOR_EACH_LL( m_DamageGivenList, i )
  2883. {
  2884. if( bPrintHeader )
  2885. {
  2886. ClientPrint( this, msg_dest, "Player: %s1 - Damage Given\n", GetPlayerName() );
  2887. ClientPrint( this, msg_dest, "-------------------------\n" );
  2888. bPrintHeader = false;
  2889. }
  2890. pRecord = m_DamageGivenList[i];
  2891. if( pRecord )
  2892. {
  2893. Q_snprintf( buf, sizeof(buf), "( life ID %i ) - %d in %d %s",
  2894. pRecord->GetLifeID(),
  2895. pRecord->GetDamage(),
  2896. pRecord->GetNumHits(),
  2897. ( pRecord->GetNumHits() == 1 ) ? "hit" : "hits" );
  2898. ClientPrint( this, msg_dest, "Damage Given to \"%s1\" - %s2\n", pRecord->GetPlayerName(), buf );
  2899. }
  2900. }
  2901. }
  2902. // Sets the player's speed - returns true if successful
  2903. bool CDODPlayer::SetSpeed( int speed )
  2904. {
  2905. DevMsg( 2, "Changing max speed to %d\n", speed );
  2906. SetMaxSpeed( (float)speed );
  2907. return true;
  2908. }
  2909. void CDODPlayer::CreateViewModel( int index /*=0*/ )
  2910. {
  2911. Assert( index >= 0 && index < MAX_VIEWMODELS );
  2912. if ( GetViewModel( index ) )
  2913. return;
  2914. CDODViewModel *vm = ( CDODViewModel * )CreateEntityByName( "dod_viewmodel" );
  2915. if ( vm )
  2916. {
  2917. vm->SetAbsOrigin( GetAbsOrigin() );
  2918. vm->SetOwner( this );
  2919. vm->SetIndex( index );
  2920. DispatchSpawn( vm );
  2921. vm->FollowEntity( this, false );
  2922. m_hViewModel.Set( index, vm );
  2923. }
  2924. }
  2925. void CDODPlayer::ResetScores( void )
  2926. {
  2927. ResetFragCount();
  2928. ResetDeathCount();
  2929. SetScore(0);
  2930. Q_memset( iNumKilledByUnanswered, 0, sizeof( iNumKilledByUnanswered ) );
  2931. }
  2932. bool CDODPlayer::SetObserverMode(int mode)
  2933. {
  2934. // DoD forces mp_forcecamera to be team only
  2935. mp_forcecamera.SetValue( OBS_ALLOW_TEAM );
  2936. if ( mode < OBS_MODE_NONE || mode > OBS_MODE_ROAMING )
  2937. return false;
  2938. // Skip over OBS_MODE_ROAMING for dead players
  2939. if( GetTeamNumber() > TEAM_SPECTATOR )
  2940. {
  2941. if( mode == OBS_MODE_ROAMING )
  2942. mode = OBS_MODE_IN_EYE;
  2943. }
  2944. if ( m_iObserverMode > OBS_MODE_DEATHCAM )
  2945. {
  2946. // remember mode if we were really spectating before
  2947. m_iObserverLastMode = m_iObserverMode;
  2948. }
  2949. m_iObserverMode = mode;
  2950. switch ( mode )
  2951. {
  2952. case OBS_MODE_NONE:
  2953. case OBS_MODE_FIXED :
  2954. case OBS_MODE_DEATHCAM :
  2955. SetFOV( this, 0 ); // Reset FOV
  2956. SetViewOffset( vec3_origin );
  2957. SetMoveType( MOVETYPE_NONE );
  2958. break;
  2959. case OBS_MODE_CHASE :
  2960. case OBS_MODE_IN_EYE :
  2961. // udpate FOV and viewmodels
  2962. SetObserverTarget( m_hObserverTarget );
  2963. SetMoveType( MOVETYPE_OBSERVER );
  2964. break;
  2965. case OBS_MODE_ROAMING :
  2966. SetFOV( this, 0 ); // Reset FOV
  2967. SetObserverTarget( m_hObserverTarget );
  2968. SetViewOffset( vec3_origin );
  2969. SetMoveType( MOVETYPE_OBSERVER );
  2970. break;
  2971. }
  2972. CheckObserverSettings();
  2973. return true;
  2974. }
  2975. extern ConVar sv_alltalk;
  2976. bool CDODPlayer::CanHearChatFrom( CBasePlayer *pPlayer )
  2977. {
  2978. // can always hear the console
  2979. if ( !pPlayer )
  2980. return true;
  2981. // teammates can always hear each other if alltalk is on
  2982. if ( sv_alltalk.GetBool() == true )
  2983. return true;
  2984. // can hear dead people in bonus round and at round end
  2985. if ( DODGameRules()->IsInBonusRound() || DODGameRules()->State_Get() == STATE_GAME_OVER )
  2986. return true;
  2987. // alive players cannot hear dead players
  2988. if ( IsAlive() && !pPlayer->IsAlive() && !( pPlayer->GetTeamNumber() == TEAM_SPECTATOR ) )
  2989. {
  2990. return false;
  2991. }
  2992. return true;
  2993. }
  2994. void CDODPlayer::ResetBleeding( void )
  2995. {
  2996. SetBandager( NULL );
  2997. }
  2998. void CDODPlayer::Bandage( void )
  2999. {
  3000. ResetBleeding();
  3001. TakeHealth( mp_bandage_heal_amount.GetFloat(), 0 );
  3002. // change helmet to bandages
  3003. }
  3004. void CDODPlayer::SetBandager( CDODPlayer *pPlayer )
  3005. {
  3006. m_hBandager = pPlayer;
  3007. }
  3008. bool CDODPlayer::IsBeingBandaged( void )
  3009. {
  3010. return ( m_hBandager.Get() != NULL );
  3011. }
  3012. void CDODPlayer::CommitSuicide( bool bExplode /*= false*/, bool bForce /*= false*/ )
  3013. {
  3014. CommitSuicide( vec3_origin, false, bForce );
  3015. }
  3016. void CDODPlayer::CommitSuicide( const Vector &vecForce, bool bExplode /*= false*/, bool bForce /*= false*/ )
  3017. {
  3018. // If they're suiciding in the spawn, its most likely they're changing class
  3019. // ( or possible suiciding to avoid being killed in the endround )
  3020. // On linux the suicide option sometimes arrives before the joinclass so just
  3021. // reject it here
  3022. if ( ShouldInstantRespawn() )
  3023. return;
  3024. if( !IsAlive() )
  3025. return;
  3026. // prevent suiciding too often
  3027. if ( m_fNextSuicideTime > gpGlobals->curtime )
  3028. return;
  3029. // don't let them suicide for 5 seconds after suiciding
  3030. m_fNextSuicideTime = gpGlobals->curtime + 5;
  3031. // have the player kill themselves
  3032. CTakeDamageInfo info( this, this, 0, DMG_NEVERGIB );
  3033. Event_Killed( info );
  3034. Event_Dying( info );
  3035. }
  3036. bool CDODPlayer::StartReplayMode( float fDelay, float fDuration, int iEntity )
  3037. {
  3038. if ( !BaseClass::StartReplayMode( fDelay, fDuration, iEntity ) )
  3039. return false;
  3040. CSingleUserRecipientFilter filter( this );
  3041. filter.MakeReliable();
  3042. UserMessageBegin( filter, "KillCam" );
  3043. WRITE_BYTE( OBS_MODE_IN_EYE );
  3044. if ( m_hObserverTarget.Get() )
  3045. {
  3046. WRITE_BYTE( m_hObserverTarget.Get()->entindex() ); // first target
  3047. WRITE_BYTE( entindex() ); //second target
  3048. }
  3049. else
  3050. {
  3051. WRITE_BYTE( entindex() ); // first target
  3052. WRITE_BYTE( 0 ); //second target
  3053. }
  3054. MessageEnd();
  3055. ClientPrint( this, HUD_PRINTCENTER, "Kill Cam Replay" );
  3056. return true;
  3057. }
  3058. void CDODPlayer::StopReplayMode()
  3059. {
  3060. BaseClass::StopReplayMode();
  3061. CSingleUserRecipientFilter filter( this );
  3062. filter.MakeReliable();
  3063. UserMessageBegin( filter, "KillCam" );
  3064. WRITE_BYTE( OBS_MODE_NONE );
  3065. WRITE_BYTE( 0 );
  3066. WRITE_BYTE( 0 );
  3067. MessageEnd();
  3068. }
  3069. void CDODPlayer::PickUpWeapon( CWeaponDODBase *pWeapon )
  3070. {
  3071. // if we have a primary weapon and we are allowed to drop it, drop it and
  3072. // pick up the one we +used
  3073. CWeaponDODBase *pCurrentPrimaryWpn = (CWeaponDODBase *)Weapon_GetSlot( WPN_SLOT_PRIMARY );
  3074. // drop primary if we can
  3075. if( pCurrentPrimaryWpn )
  3076. {
  3077. if ( pCurrentPrimaryWpn->CanDrop() == false )
  3078. {
  3079. return;
  3080. }
  3081. DODWeaponDrop( pCurrentPrimaryWpn, true );
  3082. }
  3083. // pick up the new one
  3084. if ( BumpWeapon( pWeapon ) )
  3085. {
  3086. pWeapon->OnPickedUp( this );
  3087. }
  3088. }
  3089. //-----------------------------------------------------------------------------
  3090. // Purpose: Override setup bones so that is uses the render angles from
  3091. // the DOD animation state to setup the hitboxes.
  3092. //-----------------------------------------------------------------------------
  3093. void CDODPlayer::SetupBones( matrix3x4_t *pBoneToWorld, int boneMask )
  3094. {
  3095. VPROF_BUDGET( "CBaseAnimating::SetupBones", VPROF_BUDGETGROUP_SERVER_ANIM );
  3096. // Set the mdl cache semaphore.
  3097. MDLCACHE_CRITICAL_SECTION();
  3098. // Get the studio header.
  3099. Assert( GetModelPtr() );
  3100. CStudioHdr *pStudioHdr = GetModelPtr( );
  3101. Vector pos[MAXSTUDIOBONES];
  3102. Quaternion q[MAXSTUDIOBONES];
  3103. // Adjust hit boxes based on IK driven offset.
  3104. Vector adjOrigin = GetAbsOrigin() + Vector( 0, 0, m_flEstIkOffset );
  3105. // FIXME: pass this into Studio_BuildMatrices to skip transforms
  3106. CBoneBitList boneComputed;
  3107. if ( m_pIk )
  3108. {
  3109. m_iIKCounter++;
  3110. m_pIk->Init( pStudioHdr, GetAbsAngles(), adjOrigin, gpGlobals->curtime, m_iIKCounter, boneMask );
  3111. GetSkeleton( pStudioHdr, pos, q, boneMask );
  3112. m_pIk->UpdateTargets( pos, q, pBoneToWorld, boneComputed );
  3113. CalculateIKLocks( gpGlobals->curtime );
  3114. m_pIk->SolveDependencies( pos, q, pBoneToWorld, boneComputed );
  3115. }
  3116. else
  3117. {
  3118. GetSkeleton( pStudioHdr, pos, q, boneMask );
  3119. }
  3120. CBaseAnimating *pParent = dynamic_cast< CBaseAnimating* >( GetMoveParent() );
  3121. if ( pParent )
  3122. {
  3123. // We're doing bone merging, so do special stuff here.
  3124. CBoneCache *pParentCache = pParent->GetBoneCache();
  3125. if ( pParentCache )
  3126. {
  3127. BuildMatricesWithBoneMerge(
  3128. pStudioHdr,
  3129. m_PlayerAnimState->GetRenderAngles(),
  3130. adjOrigin,
  3131. pos,
  3132. q,
  3133. pBoneToWorld,
  3134. pParent,
  3135. pParentCache );
  3136. return;
  3137. }
  3138. }
  3139. Studio_BuildMatrices(
  3140. pStudioHdr,
  3141. m_PlayerAnimState->GetRenderAngles(),
  3142. adjOrigin,
  3143. pos,
  3144. q,
  3145. -1,
  3146. GetModelScale(), // Scaling
  3147. pBoneToWorld,
  3148. boneMask );
  3149. }
  3150. extern ConVar sv_debug_player_use;
  3151. extern float IntervalDistance( float x, float x0, float x1 );
  3152. //-----------------------------------------------------------------------------
  3153. // Purpose: Find which ents are higher priority for receiving our +use
  3154. //-----------------------------------------------------------------------------
  3155. int CDODPlayer::GetPriorityForPickUpEnt( CBaseEntity *pEnt )
  3156. {
  3157. CDODBombTarget *pBombTarget = dynamic_cast< CDODBombTarget *>( pEnt );
  3158. if ( pBombTarget )
  3159. {
  3160. // its a bomb target for planting or defusing, most important
  3161. return 20;
  3162. }
  3163. CDODBaseGrenade *pGren = dynamic_cast< CDODBaseGrenade *>( pEnt );
  3164. if ( pGren )
  3165. {
  3166. // its a grenade, high priority
  3167. return 10;
  3168. }
  3169. CWeaponDODBase *pWeapon = dynamic_cast< CWeaponDODBase *>( pEnt );
  3170. if ( pWeapon )
  3171. {
  3172. // weapons are medium priority
  3173. return 5;
  3174. }
  3175. return 0;
  3176. }
  3177. //-----------------------------------------------------------------------------
  3178. // Purpose: like regular FindUseEntity, but picks up grenades before other things
  3179. //-----------------------------------------------------------------------------
  3180. CBaseEntity *CDODPlayer::FindUseEntity()
  3181. {
  3182. Vector forward, up;
  3183. EyeVectors( &forward, NULL, &up );
  3184. trace_t tr;
  3185. // Search for objects in a sphere (tests for entities that are not solid, yet still useable)
  3186. Vector searchCenter = EyePosition();
  3187. // NOTE: Some debris objects are useable too, so hit those as well
  3188. // A button, etc. can be made out of clip brushes, make sure it's +useable via a traceline, too.
  3189. int useableContents = MASK_SOLID | CONTENTS_DEBRIS | CONTENTS_PLAYERCLIP;
  3190. UTIL_TraceLine( searchCenter, searchCenter + forward * 1024, useableContents, this, COLLISION_GROUP_NONE, &tr );
  3191. // try the hit entity if there is one, or the ground entity if there isn't.
  3192. CBaseEntity *pNearest = NULL;
  3193. CBaseEntity *pObject = tr.m_pEnt;
  3194. // UNDONE: Might be faster to just fold this range into the sphere query
  3195. int count = 0;
  3196. const int NUM_TANGENTS = 7;
  3197. while ( !IsUseableEntity(pObject, 0) && count < NUM_TANGENTS)
  3198. {
  3199. // trace a box at successive angles down
  3200. // 45 deg, 30 deg, 20 deg, 15 deg, 10 deg, -10, -15
  3201. const float tangents[NUM_TANGENTS] = { 1, 0.57735026919f, 0.3639702342f, 0.267949192431f, 0.1763269807f, -0.1763269807f, -0.267949192431f };
  3202. Vector down = forward - tangents[count]*up;
  3203. VectorNormalize(down);
  3204. UTIL_TraceHull( searchCenter, searchCenter + down * 72, -Vector(16,16,16), Vector(16,16,16), useableContents, this, COLLISION_GROUP_NONE, &tr );
  3205. pObject = tr.m_pEnt;
  3206. count++;
  3207. }
  3208. float nearestDot = CONE_90_DEGREES;
  3209. if ( IsUseableEntity(pObject, 0) )
  3210. {
  3211. Vector delta = tr.endpos - tr.startpos;
  3212. float centerZ = CollisionProp()->WorldSpaceCenter().z;
  3213. delta.z = IntervalDistance( tr.endpos.z, centerZ + CollisionProp()->OBBMins().z, centerZ + CollisionProp()->OBBMaxs().z );
  3214. float dist = delta.Length();
  3215. if ( dist < PLAYER_USE_RADIUS )
  3216. {
  3217. if ( sv_debug_player_use.GetBool() )
  3218. {
  3219. NDebugOverlay::Line( searchCenter, tr.endpos, 0, 255, 0, true, 30 );
  3220. NDebugOverlay::Cross3D( tr.endpos, 16, 0, 255, 0, true, 30 );
  3221. }
  3222. pNearest = pObject;
  3223. nearestDot = 0;
  3224. }
  3225. }
  3226. // check ground entity first
  3227. // if you've got a useable ground entity, then shrink the cone of this search to 45 degrees
  3228. // otherwise, search out in a 90 degree cone (hemisphere)
  3229. if ( GetGroundEntity() && IsUseableEntity(GetGroundEntity(), FCAP_USE_ONGROUND) )
  3230. {
  3231. pNearest = GetGroundEntity();
  3232. nearestDot = CONE_45_DEGREES;
  3233. }
  3234. int iHighestPriority = GetPriorityForPickUpEnt( pObject );
  3235. for ( CEntitySphereQuery sphere( searchCenter, PLAYER_USE_RADIUS ); ( pObject = sphere.GetCurrentEntity() ) != NULL; sphere.NextEntity() )
  3236. {
  3237. if ( !pObject )
  3238. continue;
  3239. if ( !IsUseableEntity( pObject, FCAP_USE_IN_RADIUS ) )
  3240. continue;
  3241. // see if it's more roughly in front of the player than previous guess
  3242. Vector point;
  3243. pObject->CollisionProp()->CalcNearestPoint( searchCenter, &point );
  3244. Vector dir = point - searchCenter;
  3245. VectorNormalize(dir);
  3246. float dot = DotProduct( dir, forward );
  3247. // Need to be looking at the object more or less
  3248. if ( dot < 0.8 )
  3249. continue;
  3250. //NEW FOR DOD
  3251. // if this entity is higher priority than previous ent, use this one
  3252. int iPriority = GetPriorityForPickUpEnt( pObject );
  3253. // if higher priority, always use
  3254. // within the same priority, use the closer one
  3255. if ( ( iPriority > iHighestPriority ) || ( iPriority == iHighestPriority && dot > nearestDot ) )
  3256. {
  3257. // Since this has purely been a radius search to this point, we now
  3258. // make sure the object isn't behind glass or a grate.
  3259. trace_t trCheckOccluded;
  3260. UTIL_TraceLine( searchCenter, point, useableContents, this, COLLISION_GROUP_NONE, &trCheckOccluded );
  3261. if ( trCheckOccluded.fraction == 1.0 || trCheckOccluded.m_pEnt == pObject )
  3262. {
  3263. pNearest = pObject;
  3264. nearestDot = dot;
  3265. iHighestPriority = iPriority;
  3266. }
  3267. }
  3268. }
  3269. if ( sv_debug_player_use.GetBool() )
  3270. {
  3271. if ( !pNearest )
  3272. {
  3273. NDebugOverlay::Line( searchCenter, tr.endpos, 255, 0, 0, true, 30 );
  3274. NDebugOverlay::Cross3D( tr.endpos, 16, 255, 0, 0, true, 30 );
  3275. }
  3276. else
  3277. {
  3278. NDebugOverlay::Box( pNearest->WorldSpaceCenter(), Vector(-8, -8, -8), Vector(8, 8, 8), 0, 255, 0, true, 30 );
  3279. }
  3280. }
  3281. // Special check for bomb targets, whose radius for +use is larger than other entities
  3282. if ( pNearest == NULL )
  3283. {
  3284. CBaseEntity *pEnt = NULL;
  3285. float flBestDist = FLT_MAX;
  3286. // If we didn't find anything, check to see if the bomb target is close enough to use.
  3287. // This is done separately since there might be something blocking our LOS to it
  3288. // but we might want to use it anyway if it's close enough.
  3289. while( ( pEnt = gEntList.FindEntityByClassname( pEnt, "dod_bomb_target" ) ) != NULL )
  3290. {
  3291. CDODBombTarget *pTarget = static_cast<CDODBombTarget *>( pEnt );
  3292. if ( !pTarget->CanPlantHere( this ) )
  3293. continue;
  3294. Vector pos = WorldSpaceCenter();
  3295. float flDist = ( pos - pTarget->GetAbsOrigin() ).Length();
  3296. Vector toBomb = pTarget->GetAbsOrigin() - EyePosition();
  3297. toBomb.NormalizeInPlace();
  3298. if ( DotProduct( forward, toBomb ) < 0.8 )
  3299. {
  3300. continue;
  3301. }
  3302. // if we are looking directly at a bomb target and it is within our radius, that automatically wins
  3303. if ( flDist < flBestDist && flDist < DOD_BOMB_PLANT_RADIUS )
  3304. {
  3305. flBestDist = flDist;
  3306. pNearest = pTarget;
  3307. }
  3308. }
  3309. }
  3310. return pNearest;
  3311. }
  3312. void CDODPlayer::PrintLifetimeStats( void )
  3313. {
  3314. unsigned int i;
  3315. Msg( "\nWeapon Stats\n================\n" );
  3316. Msg( " (shots) (hits) (damage) (avg.dist) (kills) (hits taken) (dmg taken) (times killed) (accuracy)\n" );
  3317. // ( "* thompson 4 3 110 89.8 1 1 0 0 75.0
  3318. //Msg( "*%9s %2i %2i %3i %5.1f %2i %2i %3i %2i %3.1f\n",
  3319. for ( i=0;i<WEAPON_MAX;i++ )
  3320. {
  3321. if ( m_WeaponStats[i].m_iNumShotsTaken > 0 )
  3322. {
  3323. Msg( "* %10s (%2i) %6i %6i %8i %9.1f %7i %9i %9i %9i %9.1f\n",
  3324. WeaponIDToAlias( i ), i,
  3325. m_WeaponStats[i].m_iNumShotsTaken,
  3326. m_WeaponStats[i].m_iNumShotsHit,
  3327. m_WeaponStats[i].m_iTotalDamageGiven,
  3328. m_WeaponStats[i].m_flAverageHitDistance,
  3329. m_WeaponStats[i].m_iNumKills,
  3330. m_WeaponStats[i].m_iNumHitsTaken,
  3331. m_WeaponStats[i].m_iTotalDamageTaken,
  3332. m_WeaponStats[i].m_iTimesKilled,
  3333. 100.0 * ( (float)m_WeaponStats[i].m_iNumShotsHit / (float)m_WeaponStats[i].m_iNumShotsTaken ) );
  3334. }
  3335. }
  3336. Msg( "\nPlayer Stats\n================\n" );
  3337. for( i=0;i<m_KilledPlayers.Count();i++ )
  3338. {
  3339. CBasePlayer *pPlayer = UTIL_PlayerByUserId( m_KilledPlayers[i].m_iUserID );
  3340. const char *pName = pPlayer ? pPlayer->GetPlayerName() : m_KilledByPlayers[i].m_szPlayerName;
  3341. Msg( "* Killed Player '%s' %i times ( %i damage )\n",
  3342. pName,
  3343. m_KilledPlayers[i].m_iKills,
  3344. m_KilledPlayers[i].m_iTotalDamage );
  3345. }
  3346. Msg( "\n" );
  3347. for( i=0;i<m_KilledByPlayers.Count();i++ )
  3348. {
  3349. CBasePlayer *pPlayer = UTIL_PlayerByUserId( m_KilledByPlayers[i].m_iUserID );
  3350. Assert( pPlayer && "If this fails, the player has disconnected. Fix this!" );
  3351. const char *pName = pPlayer ? pPlayer->GetPlayerName() : m_KilledByPlayers[i].m_szPlayerName;
  3352. Msg( "* Player '%s' killed you %i times ( %i damage )\n",
  3353. pName,
  3354. m_KilledByPlayers[i].m_iKills,
  3355. m_KilledByPlayers[i].m_iTotalDamage );
  3356. }
  3357. Msg( "\n" );
  3358. Msg( "* Areas Captured: %i\n", m_iNumAreaCaptures );
  3359. Msg( "* Areas Defended: %i\n", m_iNumAreaDefenses );
  3360. Msg( "* Num Bonus Round Kills: %i\n", m_iNumBonusRoundKills );
  3361. Msg( "\n" );
  3362. // time spent as each class
  3363. Msg( "Time spent as each class:\n" );
  3364. // Make sure to tally the latest time
  3365. int playerclass = m_Shared.DesiredPlayerClass();
  3366. //evil, re-map -2 to 6 so it goes on the end of the array
  3367. if ( playerclass == PLAYERCLASS_RANDOM )
  3368. playerclass = 6;
  3369. /*
  3370. m_flTimePlayedPerClass[playerclass] += ( gpGlobals->curtime - m_flLastClassChangeTime );
  3371. m_flLastClassChangeTime = gpGlobals->curtime;
  3372. Msg( "* Rifleman: %.0f\n", m_flTimePlayedPerClass[0] );
  3373. Msg( "* Assault: %.0f\n", m_flTimePlayedPerClass[1] );
  3374. Msg( "* Support: %.0f\n", m_flTimePlayedPerClass[2] );
  3375. Msg( "* Sniper: %.0f\n", m_flTimePlayedPerClass[3] );
  3376. Msg( "* MG: %.0f\n", m_flTimePlayedPerClass[4] );
  3377. Msg( "* Rocket: %.0f\n", m_flTimePlayedPerClass[5] );
  3378. Msg( "* Random: %.0f\n", m_flTimePlayedPerClass[6] );
  3379. Msg( "\n" );
  3380. Msg( "Total time connected %.0f\n", ( gpGlobals->curtime - m_flConnectTime ) );
  3381. */
  3382. }
  3383. void CDODPlayer::Stats_WeaponFired( int weaponID )
  3384. {
  3385. // increment shots taken for this weapon
  3386. m_WeaponStats[weaponID].m_iNumShotsTaken++;
  3387. DODGameRules()->Stats_WeaponFired( weaponID );
  3388. StatEvent_WeaponFired( (DODWeaponID)weaponID );
  3389. }
  3390. void CDODPlayer::Stats_WeaponHit( CDODPlayer *pVictim, int weaponID, int iDamage, int iDamageGiven, int hitgroup, float flHitDistance )
  3391. {
  3392. // distance
  3393. float flTotalHitDistance = m_WeaponStats[weaponID].m_flAverageHitDistance * m_WeaponStats[weaponID].m_iNumShotsHit;
  3394. flTotalHitDistance += flHitDistance;
  3395. m_WeaponStats[weaponID].m_flAverageHitDistance = flTotalHitDistance / ( m_WeaponStats[weaponID].m_iNumShotsHit + 1 );
  3396. // damage
  3397. m_WeaponStats[weaponID].m_iTotalDamageGiven += iDamageGiven;
  3398. // hitgroup
  3399. m_WeaponStats[weaponID].m_iBodygroupsHit[hitgroup]++;
  3400. // shots hit
  3401. m_WeaponStats[weaponID].m_iNumShotsHit++;
  3402. int userID = pVictim->GetUserID();
  3403. // add total damage to the player record
  3404. int lookup = m_KilledPlayers.Find( userID );
  3405. if ( lookup == m_KilledPlayers.InvalidIndex() )
  3406. {
  3407. // make a new one
  3408. playerstat_t p;
  3409. Q_strncpy( p.m_szPlayerName, pVictim->GetPlayerName(), MAX_PLAYER_NAME_LENGTH );
  3410. p.m_iUserID = userID;
  3411. p.m_iKills = 0;
  3412. p.m_iTotalDamage = iDamageGiven;
  3413. m_KilledPlayers.Insert( userID, p );
  3414. }
  3415. else
  3416. {
  3417. m_KilledPlayers[lookup].m_iTotalDamage += iDamageGiven;
  3418. }
  3419. DODGameRules()->Stats_WeaponHit( weaponID, flHitDistance );
  3420. StatEvent_WeaponHit( (DODWeaponID)weaponID, ( hitgroup == HITGROUP_HEAD ) );
  3421. }
  3422. void CDODPlayer::Stats_HitByWeapon( CDODPlayer *pAttacker, int weaponID, int iDamage, int iDamageGiven, int hitgroup )
  3423. {
  3424. // damage
  3425. m_WeaponStats[weaponID].m_iTotalDamageTaken += iDamageGiven;
  3426. // hitgroup
  3427. m_WeaponStats[weaponID].m_iHitInBodygroups[hitgroup]++;
  3428. // shots hit
  3429. m_WeaponStats[weaponID].m_iNumHitsTaken++;
  3430. int userID = pAttacker->GetUserID();
  3431. // add total damage to the player record
  3432. int lookup = m_KilledByPlayers.Find( userID );
  3433. if ( lookup == m_KilledByPlayers.InvalidIndex() )
  3434. {
  3435. // make a new one
  3436. playerstat_t p;
  3437. Q_strncpy( p.m_szPlayerName, pAttacker->GetPlayerName(), MAX_PLAYER_NAME_LENGTH );
  3438. p.m_iUserID = userID;
  3439. p.m_iKills = 0;
  3440. p.m_iTotalDamage = iDamageGiven;
  3441. m_KilledByPlayers.Insert( userID, p );
  3442. }
  3443. else
  3444. {
  3445. m_KilledByPlayers[lookup].m_iTotalDamage += iDamageGiven;
  3446. }
  3447. }
  3448. void CDODPlayer::Stats_KilledPlayer( CDODPlayer *pVictim, int weaponID )
  3449. {
  3450. m_WeaponStats[weaponID].m_iNumKills++;
  3451. int userID = pVictim->GetUserID();
  3452. // add a kill to the player record
  3453. int lookup = m_KilledPlayers.Find( userID );
  3454. if ( lookup == m_KilledPlayers.InvalidIndex() )
  3455. {
  3456. // make a new one
  3457. playerstat_t p;
  3458. Q_strncpy( p.m_szPlayerName, pVictim->GetPlayerName(), MAX_PLAYER_NAME_LENGTH );
  3459. p.m_iUserID = userID;
  3460. p.m_iKills = 1;
  3461. p.m_iTotalDamage = 0;
  3462. m_KilledPlayers.Insert( userID, p );
  3463. }
  3464. else
  3465. {
  3466. m_KilledPlayers[lookup].m_iKills++;
  3467. }
  3468. // Gamerules records kills per team
  3469. DODGameRules()->Stats_PlayerKill( GetTeamNumber(), m_Shared.PlayerClass() );
  3470. m_iPerRoundKills++;
  3471. StatEvent_KilledPlayer( (DODWeaponID)weaponID );
  3472. }
  3473. void CDODPlayer::Stats_KilledByPlayer( CDODPlayer *pAttacker, int weaponID )
  3474. {
  3475. m_WeaponStats[weaponID].m_iTimesKilled++;
  3476. int userID = pAttacker->GetUserID();
  3477. // add a kill to the player record
  3478. int lookup = m_KilledByPlayers.Find( userID );
  3479. if ( lookup == m_KilledByPlayers.InvalidIndex() )
  3480. {
  3481. // make a new one
  3482. playerstat_t p;
  3483. Q_strncpy( p.m_szPlayerName, pAttacker->GetPlayerName(), MAX_PLAYER_NAME_LENGTH );
  3484. p.m_iUserID = userID;
  3485. p.m_iKills = 1;
  3486. p.m_iTotalDamage = 0;
  3487. m_KilledByPlayers.Insert( userID, p );
  3488. }
  3489. else
  3490. {
  3491. m_KilledByPlayers[lookup].m_iKills++;
  3492. }
  3493. }
  3494. void CDODPlayer::Stats_AreaDefended()
  3495. {
  3496. // map count
  3497. m_iNumAreaDefenses++;
  3498. // round count
  3499. m_iPerRoundDefenses++;
  3500. }
  3501. void CDODPlayer::Stats_AreaCaptured()
  3502. {
  3503. // map count
  3504. m_iNumAreaCaptures++;
  3505. // round count
  3506. m_iPerRoundCaptures++;
  3507. }
  3508. void CDODPlayer::Stats_BonusRoundKill()
  3509. {
  3510. m_iNumBonusRoundKills++;
  3511. }
  3512. void CDODPlayer::Stats_BombDetonated()
  3513. {
  3514. m_iPerRoundBombsDetonated++;
  3515. }
  3516. void CDODPlayer::NoteWeaponFired()
  3517. {
  3518. Assert( m_pCurrentCommand );
  3519. if( m_pCurrentCommand )
  3520. {
  3521. m_iLastWeaponFireUsercmd = m_pCurrentCommand->command_number;
  3522. }
  3523. }
  3524. bool CDODPlayer::WantsLagCompensationOnEntity( const CBasePlayer *pPlayer, const CUserCmd *pCmd, const CBitVec<MAX_EDICTS> *pEntityTransmitBits ) const
  3525. {
  3526. // No need to lag compensate at all if we're not attacking in this command and
  3527. // we haven't attacked recently.
  3528. if ( !( pCmd->buttons & IN_ATTACK ) && (pCmd->command_number - m_iLastWeaponFireUsercmd > 5) )
  3529. return false;
  3530. return BaseClass::WantsLagCompensationOnEntity( pPlayer, pCmd, pEntityTransmitBits );
  3531. }
  3532. void CDODPlayer::TallyLatestTimePlayedPerClass( int iOldTeam, int iOldPlayerClass )
  3533. {
  3534. // Tally the time we spent in the last class, for stats purposes
  3535. if ( iOldPlayerClass != PLAYERCLASS_UNDEFINED && ( iOldTeam == TEAM_ALLIES || iOldTeam == TEAM_AXIS ) )
  3536. {
  3537. // store random in the last slot
  3538. if ( iOldPlayerClass == PLAYERCLASS_RANDOM )
  3539. iOldPlayerClass = 6;
  3540. Assert( iOldPlayerClass >= 0 && iOldPlayerClass <= 6 );
  3541. Assert( iOldTeam == TEAM_ALLIES || iOldTeam == TEAM_AXIS );
  3542. if ( iOldTeam == TEAM_ALLIES )
  3543. {
  3544. m_flTimePlayedPerClass_Allies[iOldPlayerClass] += ( gpGlobals->curtime - m_flLastClassChangeTime );
  3545. }
  3546. else if ( iOldTeam == TEAM_AXIS )
  3547. {
  3548. m_flTimePlayedPerClass_Axis[iOldPlayerClass] += ( gpGlobals->curtime - m_flLastClassChangeTime );
  3549. }
  3550. }
  3551. m_flLastClassChangeTime = gpGlobals->curtime;
  3552. }
  3553. void CDODPlayer::ResetProgressBar( void )
  3554. {
  3555. SetProgressBarTime( 0 );
  3556. }
  3557. void CDODPlayer::SetProgressBarTime( int barTime )
  3558. {
  3559. m_iProgressBarDuration = barTime;
  3560. m_flProgressBarStartTime = this->m_flSimulationTime;
  3561. }
  3562. void CDODPlayer::SetDefusing( CDODBombTarget *pTarget )
  3563. {
  3564. bool bIsDefusing = ( pTarget != NULL );
  3565. if ( bIsDefusing && !m_bIsDefusing )
  3566. {
  3567. // start defuse sound
  3568. EmitSound( "Weapon_C4.Disarm" );
  3569. m_Shared.SetDefusing( true );
  3570. }
  3571. else if ( !bIsDefusing && m_bIsDefusing )
  3572. {
  3573. // stop defuse sound
  3574. StopSound( "Weapon_C4.Disarm" );
  3575. m_Shared.SetDefusing( false );
  3576. }
  3577. m_bIsDefusing = bIsDefusing;
  3578. m_pDefuseTarget = pTarget;
  3579. }
  3580. void CDODPlayer::SetPlanting( CDODBombTarget *pTarget )
  3581. {
  3582. bool bIsPlanting = ( pTarget != NULL );
  3583. if ( bIsPlanting && !m_bIsPlanting )
  3584. {
  3585. // start defuse sound
  3586. EmitSound( "Weapon_C4.Plant" );
  3587. m_Shared.SetPlanting( true );
  3588. }
  3589. else if ( !bIsPlanting && m_bIsPlanting )
  3590. {
  3591. // stop defuse sound
  3592. StopSound( "Weapon_C4.Plant" );
  3593. m_Shared.SetPlanting( false );
  3594. }
  3595. m_bIsPlanting = bIsPlanting;
  3596. m_pPlantTarget = pTarget;
  3597. }
  3598. void CDODPlayer::StoreCaptureBlock( int iAreaIndex, int iCapAttempt )
  3599. {
  3600. m_iLastBlockAreaIndex = iAreaIndex;
  3601. m_iLastBlockCapAttempt = iCapAttempt;
  3602. }
  3603. // The capture attempt number that was taking place the last time we blocked a capture
  3604. int CDODPlayer::GetLastBlockCapAttempt( void )
  3605. {
  3606. return m_iLastBlockCapAttempt;
  3607. }
  3608. // The index of the area where we last blocked a capture
  3609. int CDODPlayer::GetLastBlockAreaIndex( void )
  3610. {
  3611. return m_iLastBlockCapAttempt;
  3612. }
  3613. // NOTE! This is unused, unless we reenable our collision bounds to use USE_GAME_CODE
  3614. // - This extends our trigger bounds out a long ways and breaks many things, so only
  3615. // do this if you know what you're doing.
  3616. void CDODPlayer::ComputeWorldSpaceSurroundingBox( Vector *pVecWorldMins, Vector *pVecWorldMaxs )
  3617. {
  3618. m_Shared.ComputeWorldSpaceSurroundingBox( pVecWorldMins, pVecWorldMaxs );
  3619. }
  3620. // Called when we fire a bullet, with the number of headshots we hit
  3621. void CDODPlayer::HandleHeadshotAchievement( int iNumHeadshots )
  3622. {
  3623. if ( iNumHeadshots <= 0 )
  3624. {
  3625. SetPerLifeCounterKV( "headshots", 0 );
  3626. }
  3627. else
  3628. {
  3629. if ( DODGameRules()->State_Get() != STATE_RND_RUNNING )
  3630. return;
  3631. int iHeadshots = GetPerLifeCounterKV( "headshots" ) + iNumHeadshots;
  3632. if ( iHeadshots >= ACHIEVEMENT_NUM_CONSECUTIVE_HEADSHOTS )
  3633. {
  3634. AwardAchievement( ACHIEVEMENT_DOD_CONSECUTIVE_HEADSHOTS );
  3635. }
  3636. SetPerLifeCounterKV( "headshots", iHeadshots );
  3637. }
  3638. }
  3639. void CDODPlayer::HandleDeployedMGKillCount( int iNumDeployedKills )
  3640. {
  3641. if ( iNumDeployedKills <= 0 )
  3642. {
  3643. SetPerLifeCounterKV( "deployed_mg_kills", 0 );
  3644. }
  3645. else
  3646. {
  3647. if ( DODGameRules()->State_Get() != STATE_RND_RUNNING )
  3648. return;
  3649. int iKillsFromPos = GetPerLifeCounterKV( "deployed_mg_kills" ) + iNumDeployedKills;
  3650. if ( iKillsFromPos >= ACHIEVEMENT_MG_STREAK_IS_DOMINATING )
  3651. {
  3652. AwardAchievement( ACHIEVEMENT_DOD_MG_POSITION_STREAK );
  3653. }
  3654. SetPerLifeCounterKV( "deployed_mg_kills", iKillsFromPos );
  3655. }
  3656. }
  3657. int CDODPlayer::GetDeployedKillStreak( void )
  3658. {
  3659. return GetPerLifeCounterKV( "deployed_mg_kills" );
  3660. }
  3661. void CDODPlayer::HandleEnemyWeaponsAchievement( int iNumEnemyWpnKills )
  3662. {
  3663. if ( iNumEnemyWpnKills <= 0 )
  3664. {
  3665. SetPerLifeCounterKV( "enemy_wpn_kills", 0 );
  3666. }
  3667. else
  3668. {
  3669. if ( DODGameRules()->State_Get() != STATE_RND_RUNNING )
  3670. return;
  3671. int iKills = GetPerLifeCounterKV( "enemy_wpn_kills" ) + iNumEnemyWpnKills;
  3672. if ( iKills >= ACHIEVEMENT_NUM_ENEMY_WPN_KILLS )
  3673. {
  3674. AwardAchievement( ACHIEVEMENT_DOD_USE_ENEMY_WEAPONS );
  3675. }
  3676. SetPerLifeCounterKV( "enemy_wpn_kills", iKills );
  3677. }
  3678. }
  3679. void CDODPlayer::ResetComboWeaponKill( void )
  3680. {
  3681. SetPerLifeCounterKV( "combo_wpn_mask", 0 );
  3682. }
  3683. void CDODPlayer::HandleComboWeaponKill( int iWeaponType )
  3684. {
  3685. if ( DODGameRules()->State_Get() != STATE_RND_RUNNING )
  3686. return;
  3687. int iMask = GetPerLifeCounterKV( "combo_wpn_mask" );
  3688. iMask |= iWeaponType;
  3689. SetPerLifeCounterKV( "combo_wpn_mask", iMask );
  3690. const int iRequiredMask = ( WPN_TYPE_MG | WPN_TYPE_SNIPER | WPN_TYPE_RIFLE | WPN_TYPE_SUBMG );
  3691. const int iExplosiveMask = ( WPN_TYPE_GRENADE | WPN_TYPE_RIFLEGRENADE );
  3692. if ( ( iMask & iRequiredMask ) == iRequiredMask ) // must have all these bits set
  3693. {
  3694. if ( ( iMask & iExplosiveMask ) > 0 ) // has one of these bits set
  3695. {
  3696. AwardAchievement( ACHIEVEMENT_DOD_WEAPON_MASTERY );
  3697. }
  3698. }
  3699. }
  3700. void CDODPlayer::PlayUseDenySound()
  3701. {
  3702. EmitSound( "Player.UseDeny" );
  3703. }
  3704. //-----------------------------------------------------------------------------
  3705. // Purpose: Removes all nemesis relationships between this player and others
  3706. //-----------------------------------------------------------------------------
  3707. void CDODPlayer::RemoveNemesisRelationships()
  3708. {
  3709. for ( int i = 1 ; i <= gpGlobals->maxClients ; i++ )
  3710. {
  3711. CDODPlayer *pPlayer = ToDODPlayer( UTIL_PlayerByIndex( i ) );
  3712. if ( pPlayer && pPlayer != this )
  3713. {
  3714. // we are no longer dominating anyone
  3715. m_Shared.SetPlayerDominated( pPlayer, false );
  3716. Q_memset( iNumKilledByUnanswered, 0, sizeof( iNumKilledByUnanswered ) );
  3717. // noone is dominating us anymore
  3718. pPlayer->m_Shared.SetPlayerDominated( this, false );
  3719. pPlayer->iNumKilledByUnanswered[entindex()] = 0;
  3720. }
  3721. }
  3722. }
  3723. //-----------------------------------------------------------------------------
  3724. // Purpose: Called when we get a new achievement, recalc our awards
  3725. //-----------------------------------------------------------------------------
  3726. void CDODPlayer::OnAchievementEarned( int iAchievement )
  3727. {
  3728. BaseClass::OnAchievementEarned( iAchievement );
  3729. RecalculateAchievementAwardsMask();
  3730. }
  3731. //-----------------------------------------------------------------------------
  3732. // Purpose: Determine what achievement awards this player has
  3733. //-----------------------------------------------------------------------------
  3734. void CDODPlayer::RecalculateAchievementAwardsMask( void )
  3735. {
  3736. #if !defined(NO_STEAM)
  3737. if ( steamgameserverapicontext->SteamGameServerStats() )
  3738. {
  3739. CSteamID steamIDForPlayer;
  3740. if ( GetSteamID( &steamIDForPlayer ) && steamIDForPlayer.BIndividualAccount() )
  3741. {
  3742. steamgameserverapicontext->SteamGameServerStats()->RequestUserStats( steamIDForPlayer );
  3743. }
  3744. }
  3745. #endif
  3746. }
  3747. #if !defined(NO_STEAM)
  3748. //-----------------------------------------------------------------------------
  3749. // Purpose: Called when Steam returns a user's stats
  3750. //-----------------------------------------------------------------------------
  3751. void CDODPlayer::OnGSStatsReceived( GSStatsReceived_t *pResponse )
  3752. {
  3753. if ( pResponse->m_eResult != k_EResultOK )
  3754. return;
  3755. if ( pResponse->m_steamIDUser == GetSteamIDAsUInt64() )
  3756. {
  3757. for ( int i = 1; i < NUM_ACHIEVEMENT_AWARDS; i++ )
  3758. {
  3759. bool bUnlocked;
  3760. if ( steamgameserverapicontext->SteamGameServerStats()->GetUserAchievement( pResponse->m_steamIDUser, g_pszAchievementAwards[i], &bUnlocked ) )
  3761. {
  3762. if ( bUnlocked )
  3763. {
  3764. m_iAchievementAwardsMask |= (1<<i);
  3765. break;
  3766. }
  3767. }
  3768. }
  3769. }
  3770. }
  3771. #endif
  3772. void CDODPlayer::StatEvent_UploadStats( void )
  3773. {
  3774. int iSecondsAlive = (int)( gpGlobals->curtime - m_flTimeAsClassAccumulator );
  3775. m_StatProperty.IncrementPlayerClassStat( DODSTAT_PLAYTIME, iSecondsAlive );
  3776. m_StatProperty.SendStatsToPlayer( this );
  3777. m_flTimeAsClassAccumulator = gpGlobals->curtime;
  3778. }
  3779. void CDODPlayer::StatEvent_KilledPlayer( DODWeaponID iKillingWeapon )
  3780. {
  3781. m_StatProperty.IncrementPlayerClassStat( DODSTAT_KILLS );
  3782. m_StatProperty.IncrementWeaponStat( iKillingWeapon, DODSTAT_KILLS );
  3783. }
  3784. void CDODPlayer::StatEvent_WasKilled( void )
  3785. {
  3786. m_StatProperty.IncrementPlayerClassStat( DODSTAT_DEATHS );
  3787. }
  3788. void CDODPlayer::StatEvent_RoundWin( void )
  3789. {
  3790. m_StatProperty.IncrementPlayerClassStat( DODSTAT_ROUNDSWON );
  3791. }
  3792. void CDODPlayer::StatEvent_RoundLoss( void )
  3793. {
  3794. m_StatProperty.IncrementPlayerClassStat( DODSTAT_ROUNDSLOST );
  3795. }
  3796. void CDODPlayer::StatEvent_PointCaptured( void )
  3797. {
  3798. m_StatProperty.IncrementPlayerClassStat( DODSTAT_CAPTURES );
  3799. }
  3800. void CDODPlayer::StatEvent_CaptureBlocked( void )
  3801. {
  3802. m_StatProperty.IncrementPlayerClassStat( DODSTAT_BLOCKS );
  3803. }
  3804. void CDODPlayer::StatEvent_BombPlanted( void )
  3805. {
  3806. m_StatProperty.IncrementPlayerClassStat( DODSTAT_BOMBSPLANTED );
  3807. }
  3808. void CDODPlayer::StatEvent_BombDefused( void )
  3809. {
  3810. m_StatProperty.IncrementPlayerClassStat( DODSTAT_BOMBSDEFUSED );
  3811. }
  3812. void CDODPlayer::StatEvent_ScoredDomination( void )
  3813. {
  3814. m_StatProperty.IncrementPlayerClassStat( DODSTAT_DOMINATIONS );
  3815. }
  3816. void CDODPlayer::StatEvent_ScoredRevenge( void )
  3817. {
  3818. m_StatProperty.IncrementPlayerClassStat( DODSTAT_REVENGES );
  3819. }
  3820. void CDODPlayer::StatEvent_WeaponFired( DODWeaponID iWeaponID )
  3821. {
  3822. m_StatProperty.IncrementWeaponStat( iWeaponID, DODSTAT_SHOTS_FIRED );
  3823. }
  3824. void CDODPlayer::StatEvent_WeaponHit( DODWeaponID iWeaponID, bool bWasHeadshot )
  3825. {
  3826. m_StatProperty.IncrementWeaponStat( iWeaponID, DODSTAT_SHOTS_HIT );
  3827. if ( bWasHeadshot )
  3828. {
  3829. m_StatProperty.IncrementWeaponStat( iWeaponID, DODSTAT_HEADSHOTS );
  3830. }
  3831. }
  3832. // CDODPlayerStatProperty
  3833. void CDODPlayerStatProperty::SetClassAndTeamForThisLife( int iPlayerClass, int iTeam )
  3834. {
  3835. m_bRecordingStats = ( ( iTeam == TEAM_ALLIES || iTeam == TEAM_AXIS ) && ( iPlayerClass >= 0 && iPlayerClass <= NUM_DOD_PLAYERCLASSES ) );
  3836. m_iCurrentLifePlayerClass = iPlayerClass;
  3837. m_iCurrentLifePlayerTeam = iTeam;
  3838. }
  3839. void CDODPlayerStatProperty::IncrementPlayerClassStat( DODStatType_t statType, int iValue /* = 1 */ )
  3840. {
  3841. if ( !m_bRecordingStats )
  3842. return;
  3843. Assert( m_iCurrentLifePlayerClass >= 0 && m_iCurrentLifePlayerClass <= 5 );
  3844. m_PlayerStatsPerLife.m_iStat[statType] += iValue;
  3845. }
  3846. void CDODPlayerStatProperty::IncrementWeaponStat( DODWeaponID iWeaponID, DODStatType_t statType, int iValue /* = 1 */ )
  3847. {
  3848. if ( !m_bRecordingStats )
  3849. return;
  3850. Assert( iWeaponID >= 0 && iWeaponID <= WEAPON_MAX );
  3851. m_WeaponStatsPerLife[iWeaponID].m_iStat[statType] += iValue;
  3852. m_bWeaponStatsDirty[iWeaponID] = true;
  3853. }
  3854. void CDODPlayerStatProperty::ResetPerLifeStats( void )
  3855. {
  3856. Q_memset( &m_PlayerStatsPerLife, 0, sizeof(m_PlayerStatsPerLife) );
  3857. Q_memset( &m_WeaponStatsPerLife, 0, sizeof(m_WeaponStatsPerLife) );
  3858. Q_memset( &m_bWeaponStatsDirty, 0, sizeof(m_bWeaponStatsDirty) );
  3859. }
  3860. void CDODPlayerStatProperty::SendStatsToPlayer( CDODPlayer *pPlayer )
  3861. {
  3862. if ( !m_bRecordingStats )
  3863. return;
  3864. // make a bit field of all the stats we want to send (all with non-zero values)
  3865. int iStat;
  3866. int iSendPlayerBits = 0;
  3867. for ( iStat = DODSTAT_FIRST; iStat < DODSTAT_MAX; iStat++ )
  3868. {
  3869. if ( m_PlayerStatsPerLife.m_iStat[iStat] > 0 )
  3870. {
  3871. iSendPlayerBits |= ( 1 << ( iStat - DODSTAT_FIRST ) );
  3872. }
  3873. }
  3874. CUtlVector<int> vecWeaponsToSend;
  3875. // for every weapon that we have stats for, set a bit in the weapon mask
  3876. for ( int iWeapon = WEAPON_NONE; iWeapon < WEAPON_MAX; iWeapon++ )
  3877. {
  3878. if ( m_bWeaponStatsDirty[iWeapon] )
  3879. {
  3880. vecWeaponsToSend.AddToTail( iWeapon );
  3881. }
  3882. }
  3883. if ( iSendPlayerBits == 0 && vecWeaponsToSend.Count() == 0 )
  3884. {
  3885. ResetPerLifeStats();
  3886. return;
  3887. }
  3888. iStat = DODSTAT_FIRST;
  3889. CSingleUserRecipientFilter filter( (CBasePlayer *)pPlayer );
  3890. filter.MakeReliable();
  3891. UserMessageBegin( filter, "DODPlayerStatsUpdate" );
  3892. WRITE_BYTE( m_iCurrentLifePlayerClass ); // write the class
  3893. WRITE_BYTE( m_iCurrentLifePlayerTeam );
  3894. WRITE_LONG( iSendPlayerBits ); // write the bit mask of which stats follow in the message
  3895. // write all the non-zero stats according to the bit mask
  3896. while ( iSendPlayerBits > 0 )
  3897. {
  3898. if ( iSendPlayerBits & 1 )
  3899. {
  3900. WRITE_LONG( m_PlayerStatsPerLife.m_iStat[iStat] );
  3901. }
  3902. iSendPlayerBits >>= 1;
  3903. iStat++;
  3904. }
  3905. // now the weapon bits
  3906. // how many weapons
  3907. WRITE_BYTE( vecWeaponsToSend.Count() );
  3908. int i;
  3909. // send the weapons
  3910. for ( i=0;i<vecWeaponsToSend.Count(); i++ )
  3911. {
  3912. WRITE_BYTE( vecWeaponsToSend.Element(i) );
  3913. }
  3914. // for each weapon that we're sending stats for
  3915. for ( i=0;i<vecWeaponsToSend.Count(); i++ )
  3916. {
  3917. int iWeapon = vecWeaponsToSend.Element(i);
  3918. // what stats does iWeapon want to send?
  3919. int iWeaponStatBits = 0;
  3920. for ( iStat = DODSTAT_FIRST; iStat < DODSTAT_MAX; iStat++ )
  3921. {
  3922. if ( m_WeaponStatsPerLife[iWeapon].m_iStat[iStat] > 0 )
  3923. {
  3924. iWeaponStatBits |= ( 1 << ( iStat - DODSTAT_FIRST ) );
  3925. }
  3926. }
  3927. // send the mask of stats that we're sending for this weapon
  3928. WRITE_LONG( iWeaponStatBits );
  3929. // send the stats
  3930. iStat = DODSTAT_FIRST;
  3931. // send that mask
  3932. while ( iWeaponStatBits > 0 )
  3933. {
  3934. if ( iWeaponStatBits & 1 )
  3935. {
  3936. WRITE_LONG( m_WeaponStatsPerLife[iWeapon].m_iStat[iStat] );
  3937. }
  3938. iWeaponStatBits >>= 1;
  3939. iStat++;
  3940. }
  3941. }
  3942. MessageEnd();
  3943. ResetPerLifeStats();
  3944. }