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.

1437 lines
42 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================//
  7. #include "cbase.h"
  8. #include "tf_passtime_ball.h"
  9. #include "tf_passtime_logic.h"
  10. #include "passtime_ballcontroller.h"
  11. #include "passtime_convars.h"
  12. #include "passtime_game_events.h"
  13. #include "func_passtime_no_ball_zone.h"
  14. #include "tf_shareddefs.h"
  15. #include "tf_player.h"
  16. #include "vcollide_parse.h"
  17. #include "SpriteTrail.h"
  18. #include "soundenvelope.h"
  19. #include "soundent.h"
  20. #include "tf_gamerules.h"
  21. #include "inetchannelinfo.h"
  22. #include "tf_gamestats.h"
  23. #include "tf_team.h"
  24. #include "tier0/memdbgon.h"
  25. //-----------------------------------------------------------------------------
  26. static const float s_flPickupDist = 1000.f;
  27. static const float s_flBlockDist = 30.0f;
  28. static const float s_flClearDist = 50.0f;
  29. static const char *s_pHalloweenBallModel = "models/passtime/ball/passtime_ball_halloween.mdl";
  30. //-----------------------------------------------------------------------------
  31. static objectparams_t SBallVPhysicsObjectParams()
  32. {
  33. objectparams_t params = g_PhysDefaultObjectParams;
  34. params.mass = tf_passtime_ball_mass.GetFloat();
  35. params.dragCoefficient = tf_passtime_ball_drag_coefficient.GetFloat();
  36. params.damping = tf_passtime_ball_damping_scale.GetFloat();
  37. params.rotdamping = tf_passtime_ball_rotdamping_scale.GetFloat();
  38. params.inertia = tf_passtime_ball_inertia_scale.GetFloat();
  39. return params;
  40. }
  41. //-----------------------------------------------------------------------------
  42. // CBallPlayerToucher exists because we need the ball to touch both players and
  43. // triggers. If the ball has FSOLID_TRIGGER, it will touch players but not
  44. // triggers. And if it doesn't have that, it will touch triggers but not players.
  45. // So this is a hack (there's probably a right way to do this) so the ball can
  46. // just be solid and touch triggers, and this will touch players.
  47. class CBallPlayerToucher : public CBaseEntity
  48. {
  49. public:
  50. DECLARE_CLASS( CBallPlayerToucher, CBaseEntity );
  51. CBallPlayerToucher() : m_pBall( 0 ) {}
  52. //-----------------------------------------------------------------------------
  53. virtual void Spawn() OVERRIDE
  54. {
  55. // NOTE: this used to create its own vphysics sphere, but it turns out that
  56. // the engine totally ignores it.
  57. SetCollisionGroup( COLLISION_GROUP_PROJECTILE );
  58. SetModelIndex( m_pBall->GetModelIndex() );
  59. SetMoveType( MOVETYPE_NONE ); // DIFFERENT
  60. m_takedamage = DAMAGE_NO;
  61. SetNextThink( TICK_NEVER_THINK );
  62. m_iHealth = 0;
  63. m_iMaxHealth = 1;
  64. VPhysicsInitNormal( SOLID_NONE, 0, false );
  65. SetSolid( SOLID_VPHYSICS );
  66. SetSolidFlags( FSOLID_TRIGGER );
  67. SetMoveType( MOVETYPE_NONE ); // DIFFERENT
  68. SetParent( m_pBall );
  69. SetLocalOrigin( Vector( 0,0,0 ) );
  70. SetLocalAngles( QAngle( 0,0,0 ) );
  71. SetTransmitState( FL_EDICT_DONTSEND );
  72. AddEffects( EF_NODRAW );
  73. SetTouch( &CBallPlayerToucher::OnTouch );
  74. }
  75. //-----------------------------------------------------------------------------
  76. bool ShouldCollide( int iCollisionGroup, int iContentsMask ) const OVERRIDE
  77. {
  78. NOTE_UNUSED( iContentsMask );
  79. return iCollisionGroup == COLLISION_GROUP_PLAYER_MOVEMENT;
  80. }
  81. private:
  82. friend class CPasstimeBall;
  83. CPasstimeBall *m_pBall;
  84. void OnTouch( CBaseEntity *pOther )
  85. {
  86. m_pBall->OnTouch( pOther );
  87. }
  88. };
  89. LINK_ENTITY_TO_CLASS( _ballplayertoucher, CBallPlayerToucher );
  90. //-----------------------------------------------------------------------------
  91. IMPLEMENT_SERVERCLASS_ST( CPasstimeBall, DT_PasstimeBall )
  92. SendPropInt(SENDINFO(m_iCollisionCount)),
  93. SendPropEHandle(SENDINFO(m_hHomingTarget)),
  94. SendPropEHandle(SENDINFO(m_hCarrier)),
  95. SendPropEHandle(SENDINFO(m_hPrevCarrier)),
  96. END_SEND_TABLE()
  97. //-----------------------------------------------------------------------------
  98. LINK_ENTITY_TO_CLASS( passtime_ball, CPasstimeBall );
  99. PRECACHE_REGISTER( passtime_ball );
  100. CTFPlayer *CPasstimeBall::GetCarrier() const { return m_hCarrier; }
  101. CTFPlayer *CPasstimeBall::GetPrevCarrier() const { return m_hPrevCarrier; }
  102. //-----------------------------------------------------------------------------
  103. CPasstimeBall::CPasstimeBall()
  104. {
  105. m_bLeftOwner = false;
  106. m_pHumLoop = 0;
  107. m_pBeepLoop = 0;
  108. m_pPlayerToucher = 0;
  109. m_flLastTeamChangeTime = 0;
  110. m_flBeginCarryTime = 0;
  111. m_flIdleRespawnTime = 0;
  112. m_bTrailActive = false;
  113. }
  114. //-----------------------------------------------------------------------------
  115. void CPasstimeBall::Precache()
  116. {
  117. PrecacheModel( "passtime/passtime_balltrail_red.vmt" );
  118. PrecacheModel( "passtime/passtime_balltrail_blu.vmt" );
  119. PrecacheModel( "passtime/passtime_balltrail_unassigned.vmt" );
  120. if ( TFGameRules() && TFGameRules()->IsHolidayActive( kHoliday_Halloween ) )
  121. {
  122. PrecacheModel( s_pHalloweenBallModel );
  123. }
  124. else
  125. {
  126. PrecacheModel( tf_passtime_ball_model.GetString() );
  127. }
  128. PrecacheScriptSound( "Passtime.BallSmack" );
  129. PrecacheScriptSound( "Passtime.BallGet" );
  130. PrecacheScriptSound( "Passtime.BallIdle" );
  131. PrecacheScriptSound( "Passtime.BallHoming" );
  132. BaseClass::Precache();
  133. }
  134. //-----------------------------------------------------------------------------
  135. CTFPlayer *CPasstimeBall::GetThrower() const
  136. {
  137. return m_hThrower.Get();
  138. }
  139. //-----------------------------------------------------------------------------
  140. void CPasstimeBall::SetThrower( CTFPlayer *pPlayer )
  141. {
  142. m_hThrower = pPlayer;
  143. if ( !pPlayer )
  144. {
  145. ChangeTeam( TEAM_UNASSIGNED );
  146. }
  147. else
  148. {
  149. ChangeTeam( pPlayer->GetTeamNumber() );
  150. }
  151. }
  152. //-----------------------------------------------------------------------------
  153. unsigned int CPasstimeBall::PhysicsSolidMaskForEntity() const
  154. {
  155. return MASK_PLAYERSOLID; // must include CONTENT_PLAYERCLIP
  156. }
  157. //-----------------------------------------------------------------------------
  158. int CPasstimeBall::GetCollisionCount() const { return m_iCollisionCount; }
  159. //-----------------------------------------------------------------------------
  160. int CPasstimeBall::GetCarryDuration() const
  161. {
  162. return ( (m_flBeginCarryTime > 0) && (m_flBeginCarryTime < gpGlobals->curtime) )
  163. ? (gpGlobals->curtime - m_flBeginCarryTime)
  164. : 0;
  165. }
  166. //-----------------------------------------------------------------------------
  167. static const char *GetTrailEffectForTeam( int iTeam )
  168. {
  169. switch ( iTeam )
  170. {
  171. case TF_TEAM_RED: return "passtime/passtime_balltrail_red.vmt";
  172. case TF_TEAM_BLUE: return "passtime/passtime_balltrail_blu.vmt";
  173. default: return "passtime/passtime_balltrail_unassigned.vmt";
  174. };
  175. }
  176. //-----------------------------------------------------------------------------
  177. void CPasstimeBall::ChangeTeam( int iTeam )
  178. {
  179. // this isn't really the right place for this stats code, but its function
  180. // is directly dependent on m_flLastTeamChangeTime so I wanted to keep it
  181. // here to help avoid bugs creeping in.
  182. // NOTE you can't rely on m_hCarrier being valid or correct here, the order
  183. // of operations on calling ChangeTeam isn't stable between all the
  184. // different places where it's called.
  185. float flElapsedTimeOnThisTeam = gpGlobals->curtime - m_flLastTeamChangeTime;
  186. if ( TFGameRules() && TFGameRules()->IsPasstimeMode() && g_pPasstimeLogic )
  187. {
  188. gamerules_roundstate_t state = TFGameRules()->State_Get();
  189. if ( ((state == GR_STATE_RND_RUNNING) || (state == GR_STATE_STALEMATE) || (state == GR_STATE_TEAM_WIN)) && (flElapsedTimeOnThisTeam > 0) )
  190. {
  191. int nElapsedTimeOnThisTeam = MAX( 0, Float2Int( flElapsedTimeOnThisTeam ) );
  192. if ( GetTeamNumber() == TEAM_UNASSIGNED )
  193. {
  194. CTF_GameStats.m_passtimeStats.summary.nBallNeutralSec += nElapsedTimeOnThisTeam;
  195. }
  196. else
  197. {
  198. CTF_GameStats.m_passtimeStats.summary.nTotalCarrySec += nElapsedTimeOnThisTeam;
  199. }
  200. CTFPlayer *pPlayer = GetThrower();
  201. if ( !pPlayer ) pPlayer = GetCarrier(); // this happens when the round ends or player dies or something
  202. if ( pPlayer )
  203. {
  204. CTFTeam *pPlayerTeam = GetGlobalTFTeam( pPlayer->GetTeamNumber() );
  205. CTFTeam *pPlayerEnemyTeam = GetGlobalTFTeam( GetEnemyTeam( pPlayer->GetTeamNumber() ) );
  206. // NOTE: if the ball carrier switches teams and suicides, this will incorrectly
  207. // attribute the time to the wrong team, but I don't care.
  208. if ( pPlayerTeam->GetFlagCaptures() > pPlayerEnemyTeam->GetFlagCaptures() )
  209. {
  210. CTF_GameStats.m_passtimeStats.summary.nTotalWinningTeamBallCarrySec += Float2Int( flElapsedTimeOnThisTeam );
  211. }
  212. else if ( pPlayerTeam->GetFlagCaptures() < pPlayerEnemyTeam->GetFlagCaptures() )
  213. {
  214. CTF_GameStats.m_passtimeStats.summary.nTotalLosingTeamBallCarrySec += Float2Int( flElapsedTimeOnThisTeam );
  215. }
  216. }
  217. }
  218. }
  219. m_flLastTeamChangeTime = gpGlobals->curtime;
  220. BaseClass::ChangeTeam( iTeam );
  221. // teams: TEAM_UNASSIGNED, spectator, TF_TEAM_RED, TF_TEAM_BLUE
  222. // skins: red, blu, unassigned
  223. // NOTE: skins are in this order because we use the same model as the weapon viewmodel
  224. // and m_bHasTeamSkins_Viewmodel expects them in this order
  225. const int skinForTeam[] = { 2, 2, 0, 1 };
  226. iTeam = GetTeamNumber(); // paranoia; set by BaseClass::ChangeTeam
  227. Assert( iTeam >= 0 && iTeam < 4 );
  228. if ( iTeam >= 0 && iTeam < 4 ) // paranoia
  229. {
  230. m_nSkin = skinForTeam[iTeam];
  231. }
  232. if ( m_bTrailActive )
  233. {
  234. const char *pszTrailEffectName = GetTrailEffectForTeam( iTeam );
  235. m_pTrail->SetModel( pszTrailEffectName );
  236. }
  237. if ( iTeam == TEAM_UNASSIGNED )
  238. {
  239. // NOTE: don't call SetThrower here, it'll be recursive.
  240. m_hThrower = 0;
  241. }
  242. }
  243. //-----------------------------------------------------------------------------
  244. bool CPasstimeBall::CreateModelCollider()
  245. {
  246. solid_t tmpSolid;
  247. PhysModelParseSolid( tmpSolid, this, GetModelIndex() );
  248. tmpSolid.params = SBallVPhysicsObjectParams();
  249. tmpSolid.params.pGameData = static_cast<void *>( this );
  250. auto *pPhysObj = VPhysicsInitNormal( SOLID_VPHYSICS, 0, false, &tmpSolid );
  251. if ( !pPhysObj )
  252. {
  253. return false;
  254. }
  255. SetSolidFlags( FSOLID_NOT_STANDABLE );
  256. AddFlag( FL_GRENADE ); // required for airblast deflection to work
  257. pPhysObj->Wake();
  258. return true;
  259. }
  260. //-----------------------------------------------------------------------------
  261. void CPasstimeBall::CreateSphereCollider()
  262. {
  263. // NOTE: calling VPhysicsInitNormal(SOLID_BBOX) doesn't work right.
  264. // Not calling SetSolid after also doesn't work right.
  265. // In order for CreateSphereObject to work and not crash, you must do
  266. // VPhysicsInitNormal( SOLID_NONE followed by SetSolid(whatever)
  267. // Seems like VPHYSICS or BBOX do the same thing.
  268. // Must have FSOLID_TRIGGER to touch players. Unfortunately, triggers can't trigger triggers.
  269. VPhysicsInitNormal( SOLID_NONE, 0, false );
  270. SetSolid( SOLID_VPHYSICS );
  271. SetSolidFlags( FSOLID_NOT_STANDABLE );
  272. AddFlag( FL_GRENADE ); // required for airblast deflection to work
  273. auto params = SBallVPhysicsObjectParams();
  274. params.pGameData = static_cast<void *>( this );
  275. const float flBallRadius = tf_passtime_ball_sphere_radius.GetFloat();
  276. const float flFourThirdsPi = 4.1888f;
  277. params.volume = flFourThirdsPi * (flBallRadius*flBallRadius*flBallRadius);
  278. const int iPhysMat = physprops->GetSurfaceIndex("passtime_ball");
  279. IPhysicsObject *pPhysObj = physenv->CreateSphereObject( flBallRadius, iPhysMat, GetAbsOrigin(), GetAbsAngles(), &params, false );
  280. VPhysicsSetObject( pPhysObj );
  281. SetMoveType( MOVETYPE_VPHYSICS );
  282. pPhysObj->Wake();
  283. }
  284. //-----------------------------------------------------------------------------
  285. void CPasstimeBall::Spawn()
  286. {
  287. // not sure why this has to come first, but iirc it does.
  288. SetCollisionGroup( COLLISION_GROUP_NONE );
  289. // === CBaseProp::Spawn
  290. const char *pszModelName = (char*) STRING( GetModelName() );
  291. if ( !pszModelName || !*pszModelName )
  292. {
  293. if ( TFGameRules() && TFGameRules()->IsHolidayActive( kHoliday_Halloween ) )
  294. {
  295. pszModelName = s_pHalloweenBallModel;
  296. }
  297. else
  298. {
  299. pszModelName = tf_passtime_ball_model.GetString();
  300. }
  301. }
  302. PrecacheModel( pszModelName );
  303. Precache();
  304. SetModel( pszModelName );
  305. SetMoveType( MOVETYPE_PUSH );
  306. m_takedamage = DAMAGE_NO;
  307. SetNextThink( TICK_NEVER_THINK );
  308. m_flAnimTime = gpGlobals->curtime;
  309. m_flPlaybackRate = 0.0f;
  310. SetCycle( 0 );
  311. // === CBreakableProp::Spawn
  312. m_flFadeScale = 1;
  313. m_iHealth = 0;
  314. m_takedamage = tf_passtime_ball_takedamage.GetBool()
  315. ? DAMAGE_EVENTS_ONLY
  316. : DAMAGE_NO;
  317. m_iMaxHealth = 1;
  318. // === CPhysicsProp::Spawn
  319. if( IsMarkedForDeletion() )
  320. {
  321. return;
  322. }
  323. m_pPlayerToucher = CreateEntityByName( "_ballplayertoucher" );
  324. ((CBallPlayerToucher*)m_pPlayerToucher)->m_pBall = this;
  325. DispatchSpawn( m_pPlayerToucher );
  326. if ( tf_passtime_ball_sphere_collision.GetBool() || !CreateModelCollider() )
  327. {
  328. CreateSphereCollider();
  329. }
  330. // === My spawn
  331. m_flLastTeamChangeTime = gpGlobals->curtime;
  332. m_flBeginCarryTime = -1;
  333. ResetTrail();
  334. ChangeTeam( TEAM_UNASSIGNED );
  335. if ( TFGameRules()->IsPasstimeMode() )
  336. {
  337. // TODO the ball used to be functional in non-wasabi maps, but I haven't maintained it
  338. SetThink( &CPasstimeBall::DefaultThink );
  339. SetNextThink( gpGlobals->curtime );
  340. SetTransmitState( FL_EDICT_ALWAYS );
  341. m_playerSeek.SetIsEnabled( true );
  342. }
  343. m_flLastCollisionTime = gpGlobals->curtime;
  344. m_flAirtimeDistance = 0;
  345. m_eState = STATE_OUT_OF_PLAY;
  346. }
  347. //-----------------------------------------------------------------------------
  348. void CPasstimeBall::SetIdleRespawnTime()
  349. {
  350. auto *pTimer = TFGameRules()->GetActiveRoundTimer();
  351. if ( !pTimer ) return;
  352. auto ts = pTimer->GetTimerState();
  353. auto grs = TFGameRules()->State_Get();
  354. m_flIdleRespawnTime = ((grs == GR_STATE_RND_RUNNING) && (ts == RT_STATE_NORMAL))
  355. ? (gpGlobals->curtime + tf_passtime_ball_reset_time.GetFloat())
  356. : 0;
  357. }
  358. //-----------------------------------------------------------------------------
  359. void CPasstimeBall::DisableIdleRespawnTime()
  360. {
  361. m_flIdleRespawnTime = 0;
  362. }
  363. //-----------------------------------------------------------------------------
  364. bool CPasstimeBall::ShouldCollide( int iCollisionGroup, int iContentsMask ) const
  365. {
  366. // note: returning false for COLLISION_GROUP_PLAYER_MOVEMENT means the ball won't
  367. // stop player movement. the only real visible effect when this function doesn't
  368. // return false for COLLISION_GROUP_PLAYER_MOVEMENT is that the ball is unable
  369. // to impart physics forces on itself when a player blocks it, since the player
  370. // will set velocity to zero due to being "stuck" on the ball, even though the
  371. // ball won't actually prevent the player from moving through it.
  372. return (iCollisionGroup != COLLISION_GROUP_PLAYER_MOVEMENT);
  373. }
  374. //-----------------------------------------------------------------------------
  375. void CPasstimeBall::ResetTrail()
  376. {
  377. // ideally this would just drop all of the existing trail points instead of
  378. // re-creating all the entities, but I couldn't find a clean way to do it in
  379. // a reasonable amount of time.
  380. HideTrail();
  381. const char *pszTrailEffect = GetTrailEffectForTeam( GetTeamNumber() );
  382. Vector origin = GetAbsOrigin();
  383. float flStartRadius = tf_passtime_ball_sphere_radius.GetFloat() * 2;
  384. float flEndRadius = tf_passtime_ball_sphere_radius.GetFloat() * 3;
  385. m_pTrail = CSpriteTrail::SpriteTrailCreate( pszTrailEffect, origin, true );
  386. m_pTrail->SetAttachment( this, 0 );
  387. m_pTrail->SetTransmit( true ); // this actually controls whether the attachment parent receives it
  388. m_pTrail->SetTransparency( kRenderTransAlpha, 255, 255, 255, 200, kRenderFxNone );
  389. m_pTrail->SetStartWidth( flStartRadius );
  390. m_pTrail->SetEndWidth( flEndRadius );
  391. m_pTrail->SetTextureResolution( 1 );
  392. m_pTrail->SetLifeTime( 3.0f );
  393. m_bTrailActive = true;
  394. }
  395. //-----------------------------------------------------------------------------
  396. void CPasstimeBall::HideTrail()
  397. {
  398. // ideally this would just hide the existing trails instead of deleting
  399. // them all, but I couldn't find a clean way to do it in a reasonable
  400. // amount of time.
  401. if ( !m_bTrailActive )
  402. {
  403. return;
  404. }
  405. // this is sometimes called from a physics callback (reset trail on collision)
  406. // so use PhysCallbackRemove instead of UTIL_Remove
  407. PhysCallbackRemove( m_pTrail->NetworkProp() );
  408. m_pTrail = nullptr;
  409. m_bTrailActive = false;
  410. }
  411. //-----------------------------------------------------------------------------
  412. CPasstimeBall::~CPasstimeBall()
  413. {
  414. // trail is automatically removed because it's a child
  415. // m_pPlayerToucher is automatically removed because it's a child
  416. if ( m_pHumLoop )
  417. {
  418. CSoundEnvelopeController::GetController().SoundDestroy( m_pHumLoop );
  419. }
  420. if ( m_pBeepLoop )
  421. {
  422. CSoundEnvelopeController::GetController().SoundDestroy( m_pBeepLoop );
  423. }
  424. }
  425. //-----------------------------------------------------------------------------
  426. // OnBecomeNotCarried: common boilerplate between SetStateFree/OutOfPlay
  427. void CPasstimeBall::OnBecomeNotCarried()
  428. {
  429. CTFPlayer *pCarrier = m_hCarrier;
  430. //
  431. // Carrier management and events
  432. //
  433. if ( pCarrier && pCarrier->m_Shared.HasPasstimeBall() )
  434. {
  435. pCarrier->m_Shared.SetHasPasstimeBall( false );
  436. pCarrier->m_Shared.RemoveCond( TF_COND_SPEED_BOOST, true );
  437. pCarrier->m_Shared.RemoveCond( TF_COND_PASSTIME_INTERCEPTION, true );
  438. pCarrier->TeamFortress_SetSpeed();
  439. PasstimeGameEvents::BallFree( pCarrier->entindex() ).Fire();
  440. }
  441. //
  442. // Stats
  443. //
  444. if( m_flBeginCarryTime > 0 )
  445. {
  446. int nClass = pCarrier->GetPlayerClass()->GetClassIndex();
  447. int nCarrySec = MAX( 0, Float2Int( gpGlobals->curtime - m_flBeginCarryTime ) );
  448. CTF_GameStats.m_passtimeStats.classes[ nClass].nTotalCarrySec += nCarrySec;
  449. m_flBeginCarryTime = -1;
  450. }
  451. //
  452. // Reset various tracking and counters
  453. //
  454. m_iCollisionCount = 0;
  455. m_flAirtimeDistance = 0;
  456. m_flLastCollisionTime = gpGlobals->curtime;
  457. m_bLeftOwner = false;
  458. //m_playerSeek.SetIsEnabled( false ); // TODO: seek will re-enable itself
  459. SetParent( 0 );
  460. }
  461. //-----------------------------------------------------------------------------
  462. void CPasstimeBall::SetStateFree()
  463. {
  464. if ( BOutOfPlay() )
  465. {
  466. // this is a hack to prevent the out-of-play time from counting in the stats
  467. m_flLastTeamChangeTime = gpGlobals->curtime;
  468. }
  469. //
  470. // Change state
  471. //
  472. m_eState = STATE_FREE;
  473. OnBecomeNotCarried();
  474. //
  475. // Make interactive
  476. //
  477. DisableIdleRespawnTime();
  478. RemoveEffects( EF_NODRAW );
  479. m_pPlayerToucher->RemoveSolidFlags( FSOLID_NOT_SOLID );
  480. m_pPlayerToucher->SetSolid( SOLID_VPHYSICS );
  481. m_takedamage = tf_passtime_ball_takedamage.GetBool() ? DAMAGE_EVENTS_ONLY : DAMAGE_NO;
  482. SetMoveType( MOVETYPE_VPHYSICS );
  483. SetSolid( SOLID_VPHYSICS );
  484. SetSolidFlags( FSOLID_NOT_STANDABLE );
  485. SetThrower( m_hCarrier );
  486. TFGameRules()->SetObjectiveObserverTarget( this );
  487. VPhysicsGetObject()->EnableGravity( true );
  488. VPhysicsGetObject()->Wake();
  489. //
  490. // Trail management
  491. //
  492. if ( !m_bTrailActive )
  493. {
  494. // create trails if there aren't any
  495. ResetTrail();
  496. }
  497. //
  498. // Sounds
  499. //
  500. if ( !m_pHumLoop )
  501. {
  502. CReliableBroadcastRecipientFilter filter;
  503. m_pHumLoop = CSoundEnvelopeController::GetController().SoundCreate(
  504. filter, entindex(), "Passtime.BallIdle" );
  505. CSoundEnvelopeController::GetController().Play( m_pHumLoop, 1, PITCH_NORM );
  506. }
  507. //
  508. // Bookeeping
  509. //
  510. if ( m_hCarrier )
  511. {
  512. m_hPrevCarrier = m_hCarrier;
  513. }
  514. m_hCarrier = 0;
  515. }
  516. //-----------------------------------------------------------------------------
  517. bool CPasstimeBall::BOutOfPlay() const { return m_eState == STATE_OUT_OF_PLAY; }
  518. //-----------------------------------------------------------------------------
  519. void CPasstimeBall::SetStateOutOfPlay()
  520. {
  521. // This can be called redundantly during RespawnBall
  522. if ( BOutOfPlay() )
  523. {
  524. return;
  525. }
  526. // this is a hack to make sure the carrier stats are captured because
  527. // ChangeTeam updates some stats and may not be called at end of round.
  528. ChangeTeam( TEAM_UNASSIGNED );
  529. //
  530. // Change state
  531. //
  532. m_eState = STATE_OUT_OF_PLAY;
  533. OnBecomeNotCarried();
  534. //
  535. // Make noninteractive
  536. //
  537. DisableIdleRespawnTime();
  538. AddEffects( EF_NODRAW );
  539. m_pPlayerToucher->AddSolidFlags( FSOLID_NOT_SOLID );
  540. m_pPlayerToucher->SetSolid( SOLID_NONE );
  541. m_takedamage = DAMAGE_NO;
  542. SetMoveType( MOVETYPE_NONE );
  543. SetSolid( SOLID_NONE );
  544. SetSolidFlags( FSOLID_NOT_SOLID );
  545. SetThrower( 0 );
  546. TFGameRules()->SetObjectiveObserverTarget( 0 );
  547. VPhysicsGetObject()->EnableGravity( false );
  548. //
  549. // Trail management
  550. //
  551. HideTrail();
  552. //
  553. // Sounds
  554. //
  555. if ( m_pHumLoop )
  556. {
  557. CSoundEnvelopeController::GetController().SoundDestroy( m_pHumLoop );
  558. m_pHumLoop = 0;
  559. }
  560. if ( m_pBeepLoop )
  561. {
  562. CSoundEnvelopeController::GetController().SoundDestroy( m_pBeepLoop );
  563. m_pBeepLoop = 0;
  564. }
  565. //
  566. // Bookeeping
  567. //
  568. if ( m_hCarrier )
  569. {
  570. m_hPrevCarrier = m_hCarrier;
  571. }
  572. m_hCarrier = 0;
  573. }
  574. //-----------------------------------------------------------------------------
  575. void CPasstimeBall::SetStateCarried( CTFPlayer *pCarrier )
  576. {
  577. // this can be called when m_eState==STATE_CARRIED when the ball is being
  578. // directly transferred between players.
  579. m_eState = STATE_CARRIED;
  580. Assert( pCarrier );
  581. if ( !pCarrier )
  582. {
  583. SetStateOutOfPlay();
  584. return;
  585. }
  586. //
  587. // Carrier management and events
  588. // FIXME move all of the event handling for ball events into CTFPasstimeLogic
  589. //
  590. Assert( !pCarrier->m_Shared.HasPasstimeBall() );
  591. pCarrier->RemoveInvisibility();
  592. pCarrier->RemoveDisguise();
  593. pCarrier->EndClassSpecialSkill(); // abort demo charge
  594. pCarrier->m_Shared.SetHasPasstimeBall( true );
  595. if ( pCarrier != m_hPrevCarrier )
  596. {
  597. pCarrier->m_Shared.AddCond( TF_COND_SPEED_BOOST, tf_passtime_speedboost_on_get_ball_time.GetFloat() );
  598. // Limit points by time so we can't just throw back and forth a ton for points.
  599. // FIXME awarding points here and also in passtime_logic?
  600. if ( gpGlobals->realtime - g_pPasstimeLogic->GetLastPassTime(pCarrier) > 6.0f ) // FIXME literal balance value
  601. {
  602. CTF_GameStats.Event_PlayerAwardBonusPoints(pCarrier, 0, 5); // FIXME literal balance value
  603. g_pPasstimeLogic->SetLastPassTime(pCarrier);
  604. }
  605. }
  606. pCarrier->TeamFortress_SetSpeed();
  607. //
  608. // Adjust things common to all states
  609. //
  610. DisableIdleRespawnTime();
  611. AddEffects( EF_NODRAW );
  612. m_iCollisionCount = 0;
  613. m_flAirtimeDistance = 0;
  614. m_flLastCollisionTime = gpGlobals->curtime;
  615. m_bLeftOwner = false;
  616. //m_playerSeek.SetIsEnabled( false ); // TODO: seek will re-enable itself
  617. m_pPlayerToucher->AddSolidFlags( FSOLID_NOT_SOLID );
  618. m_pPlayerToucher->SetSolid( SOLID_NONE );
  619. m_takedamage = DAMAGE_NO;
  620. SetMoveType( MOVETYPE_NONE );
  621. SetParent( pCarrier, pCarrier->LookupAttachment( "effect_hand_R" ) );
  622. SetSolid( SOLID_NONE );
  623. SetSolidFlags( FSOLID_NOT_SOLID );
  624. TFGameRules()->SetObjectiveObserverTarget( pCarrier );
  625. VPhysicsGetObject()->EnableGravity( false );
  626. //
  627. // Unique to this state
  628. //
  629. m_bTouchedSinceSpawn = true;
  630. SetLocalOrigin( Vector( 0,0,0 ) ); // because SetParent(pCarrier)
  631. //
  632. // Sounds
  633. //
  634. EmitSound( "Passtime.BallGet" );
  635. if ( m_pHumLoop )
  636. {
  637. CSoundEnvelopeController::GetController().SoundDestroy( m_pHumLoop );
  638. m_pHumLoop = 0;
  639. }
  640. if ( m_pBeepLoop )
  641. {
  642. CSoundEnvelopeController::GetController().SoundDestroy( m_pBeepLoop );
  643. m_pBeepLoop = 0;
  644. }
  645. //
  646. // Stats
  647. //
  648. m_flBeginCarryTime = gpGlobals->curtime;
  649. //
  650. // Bookeeping
  651. //
  652. if ( m_hCarrier )
  653. {
  654. m_hPrevCarrier = m_hCarrier;
  655. }
  656. m_hCarrier = pCarrier;
  657. ChangeTeam( pCarrier->GetTeamNumber() );
  658. }
  659. //-----------------------------------------------------------------------------
  660. void CPasstimeBall::MoveToSpawner( const Vector &pos )
  661. {
  662. MoveTo( pos, Vector( 0,0,0 ) );
  663. m_bTouchedSinceSpawn = false;
  664. m_hPrevCarrier = 0;
  665. }
  666. //-----------------------------------------------------------------------------
  667. bool CPasstimeBall::IsDeflectable()
  668. {
  669. return m_eState == STATE_FREE;
  670. }
  671. //-----------------------------------------------------------------------------
  672. int CPasstimeBall::UpdateTransmitState()
  673. {
  674. if ( !TFGameRules()->IsPasstimeMode() )
  675. {
  676. return BaseClass::UpdateTransmitState();
  677. }
  678. return SetTransmitState(FL_EDICT_ALWAYS);
  679. }
  680. //-----------------------------------------------------------------------------
  681. void CPasstimeBall::MoveTo( const Vector &pos, const Vector &vecVel )
  682. {
  683. // NOTE: using Teleport() causes some weird interpolation errors
  684. // because it handles it specially as a "teleport list" etc
  685. SetAbsOrigin( pos );
  686. SetAbsVelocity( vecVel );
  687. SetAbsAngles( QAngle( 0, 0, 0 ) );
  688. IPhysicsObject *pPhys = VPhysicsGetObject();
  689. pPhys->SetPosition( pos, QAngle( 0, 0, 0 ), true );
  690. Vector fwd = vecVel.Normalized();
  691. AngularImpulse angular( fwd.x * 0, fwd.y * 0, fwd.z * 1 ); // TODO
  692. pPhys->SetVelocity( &vecVel, &angular );
  693. PhysicsTouchTriggers();
  694. m_vecPrevOrigin = pos; // used for tracking pass distance
  695. CPasstimeBallController::BallSpawned( this );
  696. }
  697. //-----------------------------------------------------------------------------
  698. bool CPasstimeBall::BShouldPanicRespawn() const
  699. {
  700. if ( !TFGameRules()
  701. || ( TFGameRules()->State_Get() != GR_STATE_RND_RUNNING )
  702. || ( m_eState != STATE_FREE ) )
  703. {
  704. return false;
  705. }
  706. if ( ( m_flIdleRespawnTime > 0 ) && ( m_flIdleRespawnTime < gpGlobals->curtime ) )
  707. {
  708. return true;
  709. }
  710. return ( enginetrace->GetPointContents( GetAbsOrigin() ) == CONTENTS_SOLID );
  711. }
  712. //-----------------------------------------------------------------------------
  713. void CPasstimeBall::DefaultThink()
  714. {
  715. UpdateLagCompensationHistory();
  716. if( IsMarkedForDeletion() || !g_pPasstimeLogic )
  717. {
  718. return;
  719. }
  720. SetNextThink( gpGlobals->curtime );
  721. if ( BShouldPanicRespawn() )
  722. {
  723. g_pPasstimeLogic->RespawnBall();
  724. return;
  725. }
  726. //
  727. // Eject the ball if the carrier isn't allowed to carry it
  728. //
  729. CTFPlayer *pCarrier = m_hCarrier;
  730. if ( pCarrier )
  731. {
  732. HudNotification_t ejectReason;
  733. if ( !g_pPasstimeLogic->BCanPlayerPickUpBall( pCarrier, &ejectReason ) )
  734. {
  735. if ( ejectReason && TFGameRules() )
  736. {
  737. CSingleUserReliableRecipientFilter filter( pCarrier );
  738. TFGameRules()->SendHudNotification( filter, ejectReason );
  739. }
  740. g_pPasstimeLogic->EjectBall( pCarrier, pCarrier );
  741. SetIdleRespawnTime(); // have to do this here because need to guarantee it happens for no ball zones
  742. EmitSound( "Passtime.BallDropped");
  743. return;
  744. }
  745. }
  746. //
  747. // Track airtime and apply controllers
  748. //
  749. if ( m_eState == STATE_FREE )
  750. {
  751. {
  752. Vector vecOrigin = GetAbsOrigin();
  753. m_flAirtimeDistance += vecOrigin.DistTo( m_vecPrevOrigin );
  754. m_vecPrevOrigin = vecOrigin;
  755. }
  756. IPhysicsObject *pPhysObj = VPhysicsGetObject();
  757. Vector vecVel;
  758. pPhysObj->GetVelocity( &vecVel, 0 );
  759. SetAbsVelocity( vecVel );
  760. // this is a hack to work around some issues where GetAbsVelocity was just
  761. // returning some huge value. this seems to fix it, so something is probably fubar in physics :/
  762. // hopefully just related to using the sphere collider that nothing else uses.
  763. pPhysObj->Wake(); // NEVER SLEEP
  764. //m_playerSeek.SetIsEnabled( !m_bTouchedSinceSpawn );
  765. CPasstimeBallController::ApplyTo( this );
  766. }
  767. }
  768. //-----------------------------------------------------------------------------
  769. extern ConVar sv_maxunlag;
  770. void CPasstimeBall::UpdateLagCompensationHistory()
  771. {
  772. // adapted from CLagCompensationManager::FrameUpdatePostEntityThink
  773. Assert( m_lagCompensationHistory.Count() < 1000 ); // insanity check
  774. m_flLagCompensationTeleportDistanceSqr = 64*64;
  775. // remove tail records that are too old
  776. int tailIndex = m_lagCompensationHistory.Tail();
  777. int flDeadtime = gpGlobals->curtime - sv_maxunlag.GetFloat();
  778. while ( m_lagCompensationHistory.IsValidIndex( tailIndex ) )
  779. {
  780. LagRecord &tail = m_lagCompensationHistory.Element( tailIndex );
  781. // if tail is within limits, stop
  782. if ( tail.flSimulationTime >= flDeadtime )
  783. break;
  784. // remove tail, get new tail
  785. m_lagCompensationHistory.Remove( tailIndex );
  786. tailIndex = m_lagCompensationHistory.Tail();
  787. }
  788. // check if head has same simulation time
  789. if ( m_lagCompensationHistory.Count() > 0 )
  790. {
  791. LagRecord &head = m_lagCompensationHistory.Element( m_lagCompensationHistory.Head() );
  792. // check if player changed simulation time since last time updated
  793. if ( head.flSimulationTime >= GetSimulationTime() )
  794. return; // don't add new entry for same or older time
  795. }
  796. // add new record to player track
  797. LagRecord &record = m_lagCompensationHistory.Element( m_lagCompensationHistory.AddToHead() );
  798. record.flSimulationTime = GetSimulationTime();
  799. record.vecOrigin = GetAbsOrigin();
  800. }
  801. //-----------------------------------------------------------------------------
  802. void CPasstimeBall::StartLagCompensation( CBasePlayer *player, CUserCmd *cmd )
  803. {
  804. m_bLagCompensationNeedsRestore = false; // set to true if it actually backtracks
  805. if ( m_lagCompensationHistory.Count() <= 0 )
  806. return;
  807. // adapted from CLagCompensationManager::StartLagCompensation
  808. int targettick = cmd->tick_count;
  809. {
  810. // correct is the amout of time we have to correct game time
  811. float correct = 0.0f;
  812. INetChannelInfo *nci = engine->GetPlayerNetInfo( player->entindex() );
  813. if ( nci )
  814. {
  815. // add network latency
  816. correct+= nci->GetLatency( FLOW_OUTGOING );
  817. }
  818. // calc number of view interpolation ticks - 1
  819. int lerpTicks = TIME_TO_TICKS( player->m_fLerpTime );
  820. // add view interpolation latency see C_BaseEntity::GetInterpolationAmount()
  821. correct += TICKS_TO_TIME( lerpTicks );
  822. // check bouns [0,sv_maxunlag]
  823. correct = clamp( correct, 0.0f, sv_maxunlag.GetFloat() );
  824. // correct tick send by player
  825. targettick = cmd->tick_count - lerpTicks;
  826. // calc difference between tick send by player and our latency based tick
  827. float deltaTime = correct - TICKS_TO_TIME(gpGlobals->tickcount - targettick);
  828. if ( fabs( deltaTime ) > 0.2f )
  829. {
  830. // difference between cmd time and latency is too big > 200ms, use time correction based on latency
  831. // DevMsg("StartLagCompensation: delta too big (%.3f)\n", deltaTime );
  832. targettick = gpGlobals->tickcount - TIME_TO_TICKS( correct );
  833. }
  834. }
  835. // copied from BacktrackPlayer
  836. Vector org;
  837. float flTargetTime = TICKS_TO_TIME( targettick );
  838. {
  839. int curr = m_lagCompensationHistory.Head();
  840. LagRecord *prevRecord = 0;
  841. LagRecord *record = 0;
  842. Vector prevOrg = GetAbsOrigin();
  843. // Walk context looking for any invalidating pEvent
  844. while( m_lagCompensationHistory.IsValidIndex(curr) )
  845. {
  846. // remember last record
  847. prevRecord = record;
  848. // get next record
  849. record = &m_lagCompensationHistory.Element( curr );
  850. Vector delta = record->vecOrigin - prevOrg;
  851. if ( delta.Length2DSqr() > m_flLagCompensationTeleportDistanceSqr )
  852. {
  853. // lost track, too much difference
  854. return;
  855. }
  856. // did we find a context smaller than target time ?
  857. if ( record->flSimulationTime <= flTargetTime )
  858. break; // hurra, stop
  859. prevOrg = record->vecOrigin;
  860. // go one step back
  861. curr = m_lagCompensationHistory.Next( curr );
  862. }
  863. Assert( record );
  864. if ( !record )
  865. {
  866. return; // that should never happen
  867. }
  868. float frac = 0.0f;
  869. if ( prevRecord &&
  870. (record->flSimulationTime < flTargetTime) &&
  871. (record->flSimulationTime < prevRecord->flSimulationTime) )
  872. {
  873. // we didn't find the exact time but have a valid previous record
  874. // so interpolate between these two records;
  875. Assert( prevRecord->flSimulationTime > record->flSimulationTime );
  876. Assert( flTargetTime < prevRecord->flSimulationTime );
  877. // calc fraction between both records
  878. frac = ( flTargetTime - record->flSimulationTime ) /
  879. ( prevRecord->flSimulationTime - record->flSimulationTime );
  880. Assert( frac > 0 && frac < 1 ); // should never extrapolate
  881. org = Lerp( frac, record->vecOrigin, prevRecord->vecOrigin );
  882. }
  883. else
  884. {
  885. // we found the exact record or no other record to interpolate with
  886. // just copy these values since they are the best we have
  887. org = record->vecOrigin;
  888. }
  889. }
  890. Vector orgdiff = GetAbsOrigin() - org;
  891. m_lagCompensationRestore.flSimulationTime = GetSimulationTime();
  892. m_lagCompensationRestore.vecOrigin = GetAbsOrigin();
  893. SetAbsOrigin( org );
  894. SetSimulationTime( flTargetTime );
  895. m_bLagCompensationNeedsRestore = true;
  896. }
  897. //-----------------------------------------------------------------------------
  898. void CPasstimeBall::FinishLagCompensation( CBasePlayer *player )
  899. {
  900. // adapted from CLagCompensationManager::BacktrackPlayer
  901. if ( !m_bLagCompensationNeedsRestore )
  902. {
  903. return;
  904. }
  905. SetAbsOrigin( m_lagCompensationRestore.vecOrigin ); // this is probably not correct?
  906. SetSimulationTime( m_lagCompensationRestore.flSimulationTime );
  907. }
  908. //-----------------------------------------------------------------------------
  909. bool CPasstimeBall::BIgnorePlayer( CTFPlayer *pPlayer )
  910. {
  911. // NOTE: it's possible to be !alive and !dead at the same time
  912. if ( !pPlayer || !pPlayer->IsAlive() )
  913. {
  914. return true;
  915. }
  916. if ( !m_bLeftOwner && (pPlayer == GetThrower()) )
  917. {
  918. const float flDist = CalcDistanceToAABB(
  919. pPlayer->WorldAlignMins(),
  920. pPlayer->WorldAlignMaxs(),
  921. GetAbsOrigin() - pPlayer->GetAbsOrigin() );
  922. m_bLeftOwner = flDist > s_flClearDist;
  923. return !m_bLeftOwner;
  924. }
  925. else
  926. {
  927. m_bLeftOwner = true;
  928. return false;
  929. }
  930. }
  931. //-----------------------------------------------------------------------------
  932. void CPasstimeBall::TouchPlayer( CTFPlayer *pPlayer )
  933. {
  934. if ( !TFGameRules() )
  935. {
  936. return;
  937. }
  938. //
  939. // Is this player close enough to hit it?
  940. // TODO is this still necessary since we use actual physics touching now?
  941. //
  942. {
  943. const Vector& vecMyOrigin = GetAbsOrigin();
  944. const Vector& vecOtherOrigin = pPlayer->GetAbsOrigin();
  945. const Vector vecOtherHead = vecOtherOrigin + Vector( 0, 0, pPlayer->BoundingRadius() + 8 );
  946. float t = 0;
  947. const float flDist = CalcDistanceToLineSegment( vecMyOrigin, vecOtherOrigin, vecOtherHead, &t );
  948. if ( (flDist > s_flBlockDist) && (flDist > s_flPickupDist) )
  949. {
  950. return;
  951. }
  952. }
  953. const bool bSameTeam = GetThrower() && (pPlayer->GetTeamNumber() == GetThrower()->GetTeamNumber());
  954. //
  955. // Can this player get the ball?
  956. //
  957. bool bCanPickUp = false;
  958. {
  959. HudNotification_t cantPickUpReason;
  960. bCanPickUp = g_pPasstimeLogic->BCanPlayerPickUpBall( pPlayer, &cantPickUpReason );
  961. if ( cantPickUpReason )
  962. {
  963. CSingleUserReliableRecipientFilter filter( pPlayer );
  964. TFGameRules()->SendHudNotification( filter, cantPickUpReason );
  965. }
  966. }
  967. if ( bCanPickUp )
  968. {
  969. m_bTouchedSinceSpawn = true;
  970. g_pPasstimeLogic->OnPlayerTouchBall( pPlayer, this );
  971. }
  972. else if ( !bSameTeam )
  973. {
  974. // can't pick it up and not on the same team = block
  975. // NOTE: BlockDamage has to come after BlockReflect in order for
  976. // the reflection to work right. BlockDamage might apply a force
  977. // to the player, which will taint the reflection vector.
  978. // NOTE: because some of these functions might change the ball's
  979. // velocity, get it once and then pass it to each.
  980. IPhysicsObject* pPhysObj = VPhysicsGetObject();
  981. Vector vecBallVel;
  982. pPhysObj->GetVelocity( &vecBallVel, 0 );
  983. BlockReflect( pPlayer, pPlayer->GetAbsOrigin(), vecBallVel );
  984. BlockDamage( pPlayer, vecBallVel );
  985. if ( GetThrower() )
  986. {
  987. // ball was in flight
  988. PasstimeGameEvents::BallBlocked( GetThrower()->entindex(), pPlayer->entindex() ).Fire();
  989. }
  990. CPasstimeBallController::DisableOn( this );
  991. m_iCollisionCount++;
  992. SetThrower( 0 );
  993. m_flAirtimeDistance = 0;
  994. m_flLastCollisionTime = gpGlobals->curtime;
  995. }
  996. }
  997. //-----------------------------------------------------------------------------
  998. void CPasstimeBall::BlockReflect( CTFPlayer *pPlayer, const Vector& vecBallOrigin, const Vector& vecBallVel )
  999. {
  1000. if ( m_hBlocker == pPlayer )
  1001. {
  1002. // this helps prevent the ball from getting stuck inside players
  1003. return;
  1004. }
  1005. m_hBlocker = pPlayer;
  1006. const Vector vecMyOrigin = GetAbsOrigin();
  1007. Vector vecBallDir = vecBallVel;
  1008. vecBallDir.z = 0;
  1009. const float flBallSpeed = vecBallDir.NormalizeInPlace();
  1010. Vector vecReflectVel = vecMyOrigin - vecBallOrigin;
  1011. vecReflectVel.z = 0;
  1012. vecReflectVel.NormalizeInPlace();
  1013. vecReflectVel = vecReflectVel.Cross( vecBallDir );
  1014. vecReflectVel.NormalizeInPlace();
  1015. vecReflectVel = vecBallDir.Cross( vecReflectVel );
  1016. vecReflectVel.NormalizeInPlace();
  1017. vecReflectVel -= vecBallDir;
  1018. vecReflectVel *= flBallSpeed / 2.0f;
  1019. vecReflectVel += pPlayer->GetAbsVelocity();
  1020. AngularImpulse spin(0,0,0);
  1021. SetAbsVelocity( vecReflectVel );
  1022. VPhysicsGetObject()->SetVelocity( &vecReflectVel, &spin );
  1023. if ( flBallSpeed > 300 )
  1024. {
  1025. EmitSound( "Passtime.BallSmack" );
  1026. }
  1027. }
  1028. //-----------------------------------------------------------------------------
  1029. void CPasstimeBall::BlockDamage( CTFPlayer *pPlayer, const Vector& vecBallVel )
  1030. {
  1031. const float flSpeed = vecBallVel.Length();
  1032. const float flDamageSpeed = 1000;
  1033. pPlayer->m_Shared.OnSpyTouchedByEnemy();
  1034. if ( flSpeed >= flDamageSpeed )
  1035. {
  1036. CTakeDamageInfo di;
  1037. di.SetAttacker( GetThrower() );
  1038. di.SetDamage( 1 );
  1039. di.SetDamageType( DMG_CLUB );
  1040. di.SetInflictor( this );
  1041. di.SetDamagePosition( GetAbsOrigin() );
  1042. di.SetDamageForce( vecBallVel ); // needs to be set to nonzero
  1043. if ( flSpeed > 1200 )
  1044. {
  1045. di.AddDamageType( DMG_CRITICAL );
  1046. }
  1047. pPlayer->TakeDamage( di );
  1048. }
  1049. }
  1050. //-----------------------------------------------------------------------------
  1051. static bool IsGroundCollision( int index, const gamevcollisionevent_t *pEvent )
  1052. {
  1053. // this little arcane incantation stolen from somewhere else
  1054. const int otherindex = !index;
  1055. IPhysicsObject *pPhysObj = pEvent->pObjects[otherindex];
  1056. CBaseEntity *pOther = static_cast<CBaseEntity *>(pPhysObj->GetGameData());
  1057. if ( !pOther || !pEvent->pInternalData )
  1058. {
  1059. return false; // paranoia
  1060. }
  1061. Vector vecNormal;
  1062. pEvent->pInternalData->GetSurfaceNormal( vecNormal );
  1063. return Vector( 0, 0, 1 ).Dot( vecNormal ) < -0.7f; // why is this backwards?
  1064. }
  1065. //-----------------------------------------------------------------------------
  1066. void CPasstimeBall::OnTouch( CBaseEntity *pOther )
  1067. {
  1068. // If two players touch the ball in the same frame inside the physics system,
  1069. // the ball will get a touch callback for both regardless of what happens
  1070. // in response to the first call (i.e. it's just iterating a contact list).
  1071. // This catches the case where the ball was already picked up this frame.
  1072. if ( !TFGameRules()->IsPasstimeMode() || (m_eState != STATE_FREE) )
  1073. {
  1074. return;
  1075. }
  1076. CTFPlayer *pPlayer = ToTFPlayer( pOther );
  1077. if ( !BIgnorePlayer( pPlayer ) )
  1078. {
  1079. TouchPlayer( pPlayer );
  1080. }
  1081. }
  1082. //-----------------------------------------------------------------------------
  1083. void CPasstimeBall::VPhysicsCollision( int index, gamevcollisionevent_t *pEvent )
  1084. {
  1085. BaseClass::VPhysicsCollision( index, pEvent );
  1086. if ( !TFGameRules()->IsPasstimeMode() )
  1087. {
  1088. return;
  1089. }
  1090. if ( g_pPasstimeLogic && (g_pPasstimeLogic->GetBall() == this)
  1091. && g_pPasstimeLogic->OnBallCollision( this, index, pEvent )
  1092. && IsGroundCollision( index, pEvent ) )
  1093. {
  1094. OnCollision();
  1095. }
  1096. CPasstimeBallController::BallCollision( this, index, pEvent );
  1097. m_hBlocker.Term();
  1098. }
  1099. //-----------------------------------------------------------------------------
  1100. void CPasstimeBall::OnCollision()
  1101. {
  1102. m_flAirtimeDistance = 0;
  1103. m_flLastCollisionTime = gpGlobals->curtime;
  1104. ++m_iCollisionCount;
  1105. if ( m_iCollisionCount == 1 )
  1106. {
  1107. SetThrower( 0 );
  1108. if ( m_bTouchedSinceSpawn )
  1109. {
  1110. SetIdleRespawnTime();
  1111. }
  1112. }
  1113. m_hBlocker.Term();
  1114. }
  1115. //-----------------------------------------------------------------------------
  1116. int CPasstimeBall::OnTakeDamage( const CTakeDamageInfo &info )
  1117. {
  1118. if ( !tf_passtime_ball_takedamage.GetBool() )
  1119. {
  1120. // this can happen if the cvar is disabled after the ball has spawned
  1121. return 0;
  1122. }
  1123. if ( !m_bTouchedSinceSpawn && (GetCollisionCount() == 0) )
  1124. {
  1125. ++CTF_GameStats.m_passtimeStats.summary.nTotalBallSpawnShots;
  1126. }
  1127. if ( TFGameRules()->IsPasstimeMode() )
  1128. {
  1129. CPasstimeBallController::BallDamaged( this );
  1130. CPasstimeBallController::DisableOn( this );
  1131. OnCollision();
  1132. }
  1133. if ( IPhysicsObject* pPhysObj = VPhysicsGetObject() )
  1134. {
  1135. pPhysObj->EnableMotion( true );
  1136. pPhysObj->ApplyForceOffset( info.GetDamageForce().Normalized() * tf_passtime_ball_takedamage_force.GetFloat(), GetAbsOrigin() );
  1137. }
  1138. return 0;
  1139. }
  1140. //-----------------------------------------------------------------------------
  1141. void CPasstimeBall::Deflected(CBaseEntity *pDeflectedBy, Vector& vecDir )
  1142. {
  1143. NOTE_UNUSED( pDeflectedBy );
  1144. IPhysicsObject* pPhysObj = VPhysicsGetObject();
  1145. if ( !pPhysObj )
  1146. {
  1147. return;
  1148. }
  1149. // WeaponBase::DeflectEntity will redirect the velocity with the same flSpeed,
  1150. // which means that a stationary ball won't move since it has 0 flSpeed. this
  1151. // will just make sure the velocity is what it should be
  1152. // vecDir points from the point under the player's crosshair to the ball's origin.
  1153. // this will make ball deflection work just like rockets, except the velocity
  1154. // is normalized instead of just being whatever magnitude it was before deflection.
  1155. Vector vecVel = -vecDir * tf_passtime_ball_takedamage_force.GetFloat();
  1156. pPhysObj->SetVelocity( &vecVel, 0 );
  1157. if ( TFGameRules()->IsPasstimeMode() )
  1158. {
  1159. ++CTF_GameStats.m_passtimeStats.summary.nTotalBallDeflects;
  1160. // stop passing, etc
  1161. CPasstimeBallController::DisableOn( this );
  1162. // count as a collision
  1163. OnCollision();
  1164. }
  1165. }
  1166. //-----------------------------------------------------------------------------
  1167. //static
  1168. CPasstimeBall *CPasstimeBall::Create( Vector vecPosition, QAngle angles )
  1169. {
  1170. // mostly copied from CreatePhysicsToy
  1171. MDLCACHE_CRITICAL_SECTION();
  1172. MDLHandle_t hMdl = mdlcache->FindMDL( tf_passtime_ball_model.GetString() );
  1173. Assert( hMdl != MDLHANDLE_INVALID );
  1174. if( hMdl == MDLHANDLE_INVALID )
  1175. {
  1176. return 0;
  1177. }
  1178. studiohdr_t *pStudioHdr = mdlcache->GetStudioHdr( hMdl );
  1179. Assert( pStudioHdr );
  1180. if( !pStudioHdr )
  1181. {
  1182. return 0;
  1183. }
  1184. // i don't know what this "allow precache" stuff does,
  1185. // i copied it from other code and forgot to note where it was
  1186. bool oldAllowPrecache = CBaseEntity::IsPrecacheAllowed();
  1187. CBaseEntity::SetAllowPrecache( true );
  1188. CPasstimeBall *pBall = dynamic_cast< CPasstimeBall* >( CreateEntityByName( "passtime_ball" ) );
  1189. char pszBuf[512];
  1190. Q_snprintf( pszBuf, sizeof( pszBuf ), "%.10f %.10f %.10f", vecPosition.x, vecPosition.y, vecPosition.z );
  1191. pBall->KeyValue( "origin", pszBuf );
  1192. Q_snprintf( pszBuf, sizeof( pszBuf ), "%.10f %.10f %.10f", angles.x, angles.y, angles.z );
  1193. pBall->KeyValue( "angles", pszBuf );
  1194. pBall->KeyValue( "fademindist", "-1" );
  1195. pBall->KeyValue( "fademaxdist", "0" );
  1196. pBall->KeyValue( "fadescale", "1" );
  1197. DispatchSpawn( pBall );
  1198. pBall->Activate();
  1199. CBaseEntity::SetAllowPrecache( oldAllowPrecache );
  1200. mdlcache->Release( hMdl );
  1201. return pBall;
  1202. }
  1203. //-----------------------------------------------------------------------------
  1204. void CPasstimeBall::SetHomingTarget( CTFPlayer *pPlayer )
  1205. {
  1206. m_hHomingTarget = pPlayer;
  1207. if ( m_hHomingTarget )
  1208. {
  1209. if ( !m_pBeepLoop )
  1210. {
  1211. CReliableBroadcastRecipientFilter filter;
  1212. m_pBeepLoop = CSoundEnvelopeController::GetController().SoundCreate(
  1213. filter, entindex(), "Passtime.BallHoming" );
  1214. CSoundEnvelopeController::GetController().Play( m_pBeepLoop, 1, PITCH_NORM );
  1215. }
  1216. }
  1217. else
  1218. {
  1219. if ( m_pBeepLoop )
  1220. {
  1221. CSoundEnvelopeController::GetController().SoundDestroy( m_pBeepLoop );
  1222. m_pBeepLoop = 0;
  1223. }
  1224. }
  1225. }
  1226. //-----------------------------------------------------------------------------
  1227. CTFPlayer *CPasstimeBall::GetHomingTarget() const
  1228. {
  1229. return m_hHomingTarget;
  1230. }
  1231. //-----------------------------------------------------------------------------
  1232. float CPasstimeBall::GetAirtimeSec() const
  1233. {
  1234. return MAX( 0, gpGlobals->curtime - m_flLastCollisionTime );
  1235. }
  1236. //-----------------------------------------------------------------------------
  1237. float CPasstimeBall::GetAirtimeDistance() const
  1238. {
  1239. return m_flAirtimeDistance;
  1240. }