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.

481 lines
16 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. //
  4. // $NoKeywords: $
  5. //=============================================================================//
  6. #include "cbase.h"
  7. #include "pushentity.h"
  8. #include "tf_player.h"
  9. #include "collisionutils.h"
  10. #include "tf_gamerules.h"
  11. #include "func_respawnroom.h"
  12. //#include "mathlib/mathlib.h"
  13. class CTFPhysicsPushEntities : public CPhysicsPushedEntities
  14. {
  15. public:
  16. DECLARE_CLASS( CTFPhysicsPushEntities, CPhysicsPushedEntities );
  17. // Constructor/Destructor.
  18. CTFPhysicsPushEntities();
  19. ~CTFPhysicsPushEntities();
  20. protected:
  21. // Speculatively checks to see if all entities in this list can be pushed
  22. virtual bool SpeculativelyCheckRotPush( const RotatingPushMove_t &rotPushMove, CBaseEntity *pRoot );
  23. virtual bool SpeculativelyCheckLinearPush( const Vector &vecAbsPush );
  24. virtual void FinishRotPushedEntity( CBaseEntity *pPushedEntity, const RotatingPushMove_t &rotPushMove );
  25. private:
  26. bool RotationPushTFPlayer( PhysicsPushedInfo_t &info, const Vector &vecAbsPush, const RotatingPushMove_t &rotPushMove, bool bRotationalPush );
  27. bool RotationCheckPush( PhysicsPushedInfo_t &info );
  28. bool LinearPushTFPlayer( PhysicsPushedInfo_t &info, const Vector &vecAbsPush, bool bRotationalPush );
  29. bool LinearCheckPush( PhysicsPushedInfo_t &info );
  30. bool IsPlayerAABBIntersetingPusherOBB( CBaseEntity *pEntity, CBaseEntity *pRootEntity );
  31. void MovePlayer( CBaseEntity *pBlocker, PhysicsPushedInfo_t &info, float flMoveScale, bool bPusherIsTrain );
  32. void FindNewPushDirection( Vector &vecCurrent, Vector &vecNormal, Vector &vecOutput );
  33. float m_flPushDist;
  34. Vector m_vecPushVector;
  35. };
  36. CTFPhysicsPushEntities s_TFPushedEntities;
  37. CPhysicsPushedEntities *g_pPushedEntities = &s_TFPushedEntities;
  38. //-----------------------------------------------------------------------------
  39. // Purpose: Constructor.
  40. //-----------------------------------------------------------------------------
  41. CTFPhysicsPushEntities::CTFPhysicsPushEntities()
  42. {
  43. }
  44. //-----------------------------------------------------------------------------
  45. // Purpose: Destructor.
  46. //-----------------------------------------------------------------------------
  47. CTFPhysicsPushEntities::~CTFPhysicsPushEntities()
  48. {
  49. }
  50. //-----------------------------------------------------------------------------
  51. // Purpose:
  52. //-----------------------------------------------------------------------------
  53. bool CTFPhysicsPushEntities::SpeculativelyCheckRotPush( const RotatingPushMove_t &rotPushMove, CBaseEntity *pRoot )
  54. {
  55. // Only do this for "payload" or "escort" maps.
  56. if ( !( TFGameRules()->GetGameType() == TF_GAMETYPE_ESCORT ) )
  57. return BaseClass::SpeculativelyCheckRotPush( rotPushMove, pRoot );
  58. Vector vecAbsPush( 0.0f, 0.0f, 0.0f );
  59. m_nBlocker = -1;
  60. int nMovedCount = m_rgMoved.Count();
  61. for ( int i = ( nMovedCount - 1 ); i >= 0; --i )
  62. {
  63. // Is the entity and TF Player?
  64. CTFPlayer *pTFPlayer = NULL;
  65. if ( m_rgMoved[i].m_pEntity && m_rgMoved[i].m_pEntity->IsPlayer() )
  66. {
  67. pTFPlayer = ToTFPlayer( m_rgMoved[i].m_pEntity );
  68. }
  69. // Special code to move the player away from the func_train.
  70. if ( pTFPlayer )
  71. {
  72. // Rotationally push the player!
  73. RotationPushTFPlayer( m_rgMoved[i], vecAbsPush, rotPushMove, true );
  74. }
  75. else
  76. {
  77. ComputeRotationalPushDirection( m_rgMoved[i].m_pEntity, rotPushMove, &vecAbsPush, pRoot );
  78. if (!SpeculativelyCheckPush( m_rgMoved[i], vecAbsPush, true ))
  79. {
  80. m_nBlocker = i;
  81. return false;
  82. }
  83. }
  84. }
  85. return true;
  86. }
  87. //-----------------------------------------------------------------------------
  88. // Speculatively checks to see if all entities in this list can be pushed
  89. //-----------------------------------------------------------------------------
  90. bool CTFPhysicsPushEntities::SpeculativelyCheckLinearPush( const Vector &vecAbsPush )
  91. {
  92. // Only do this for "payload" or "escort" maps.
  93. if ( !( TFGameRules()->GetGameType() == TF_GAMETYPE_ESCORT ) )
  94. return BaseClass::SpeculativelyCheckLinearPush( vecAbsPush );
  95. m_nBlocker = -1;
  96. int nMovedCount = m_rgMoved.Count();
  97. for ( int i = ( nMovedCount - 1 ); i >= 0; --i )
  98. {
  99. // Is the entity and TF Player?
  100. CTFPlayer *pTFPlayer = NULL;
  101. if ( m_rgMoved[i].m_pEntity && m_rgMoved[i].m_pEntity->IsPlayer() )
  102. {
  103. pTFPlayer = ToTFPlayer( m_rgMoved[i].m_pEntity );
  104. }
  105. // Special code to move the player away from the func_train.
  106. if ( pTFPlayer )
  107. {
  108. // Linearly push the player!
  109. LinearPushTFPlayer( m_rgMoved[i], vecAbsPush, false );
  110. }
  111. else
  112. {
  113. if (!SpeculativelyCheckPush( m_rgMoved[i], vecAbsPush, false ))
  114. {
  115. m_nBlocker = i;
  116. return false;
  117. }
  118. }
  119. }
  120. return true;
  121. }
  122. //-----------------------------------------------------------------------------
  123. // Purpose:
  124. //-----------------------------------------------------------------------------
  125. bool CTFPhysicsPushEntities::RotationPushTFPlayer( PhysicsPushedInfo_t &info, const Vector &vecAbsPush, const RotatingPushMove_t &rotPushMove, bool bRotationalPush )
  126. {
  127. // Clear out the collision entity so that if we early out we don't send bogus collision data to the physics system.
  128. info.m_Trace.m_pEnt = NULL;
  129. // Look into doing a full engine->CM_Clear( trace)
  130. // Get the player.
  131. CTFPlayer *pPlayer = ToTFPlayer( info.m_pEntity );
  132. if ( !pPlayer )
  133. return false;
  134. info.m_vecStartAbsOrigin = pPlayer->GetAbsOrigin();
  135. // Get the player collision data.
  136. CCollisionProperty *pCollisionPlayer = info.m_pEntity->CollisionProp();
  137. if ( !pCollisionPlayer )
  138. return false;
  139. // Find the root object if in hierarchy.
  140. CBaseEntity *pRootEntity = m_rgPusher[0].m_pEntity->GetRootMoveParent();
  141. if ( !pRootEntity )
  142. return false;
  143. // Get the pusher collision data.
  144. CCollisionProperty *pCollisionPusher = pRootEntity->CollisionProp();
  145. if ( !pCollisionPusher )
  146. return false;
  147. // Do we have a collision.
  148. if ( !IsOBBIntersectingOBB( pCollisionPlayer->GetCollisionOrigin(), pCollisionPlayer->GetCollisionAngles(), pCollisionPlayer->OBBMins(), pCollisionPlayer->OBBMaxs(),
  149. pCollisionPusher->GetCollisionOrigin(), pCollisionPusher->GetCollisionAngles(), pCollisionPusher->OBBMins(), pCollisionPusher->OBBMaxs(),
  150. 0.0f ) )
  151. return false;
  152. // For speed use spheres to approximate push distance.
  153. Vector vecPlayerOrigin = pCollisionPlayer->GetCollisionOrigin();
  154. float flPlayerRadius = pCollisionPlayer->BoundingRadius();
  155. Vector vecPusherOrigin = pCollisionPusher->GetCollisionOrigin();
  156. float flPusherRadius = pCollisionPusher->BoundingRadius();
  157. Vector vecDeltaOrigin;
  158. VectorSubtract( vecPlayerOrigin, vecPusherOrigin, vecDeltaOrigin );
  159. float flRadiusTotal = flPlayerRadius + flPusherRadius;
  160. float flLength = vecDeltaOrigin.Length();
  161. float flDistanceDelta = fabs( flRadiusTotal - flLength );
  162. // Put special code in if we are riding the pusher - only push upward.
  163. if ( pPlayer->GetGroundEntity() == pRootEntity )
  164. {
  165. // Set the push direction and distance.
  166. m_vecPushVector.Init( 0.0f, 0.0f, 1.0f );
  167. if ( rotPushMove.amove[0] != 0.0f )
  168. {
  169. m_flPushDist = fabs( tan( DEG2RAD( rotPushMove.amove[0] ) ) * flPusherRadius );
  170. float flPushAdd = m_flPushDist * 0.1f;
  171. m_flPushDist += flPushAdd;
  172. }
  173. else
  174. {
  175. m_flPushDist = 0.0f;
  176. }
  177. }
  178. else
  179. {
  180. // Set the push direction and distance.
  181. m_vecPushVector = vecDeltaOrigin;
  182. m_vecPushVector.NormalizeInPlace();
  183. m_flPushDist = flDistanceDelta;
  184. float flPushAdd = m_flPushDist * 0.1f;
  185. m_flPushDist += flPushAdd;
  186. }
  187. return RotationCheckPush( info );
  188. }
  189. //-----------------------------------------------------------------------------
  190. // Purpose:
  191. //-----------------------------------------------------------------------------
  192. bool CTFPhysicsPushEntities::RotationCheckPush( PhysicsPushedInfo_t &info )
  193. {
  194. // Get the blocking and pushing entities.
  195. CBaseEntity *pBlocker = info.m_pEntity;
  196. CBaseEntity *pRootEntity = m_rgPusher[0].m_pEntity->GetRootMoveParent();
  197. if ( !pBlocker || !pRootEntity )
  198. return true;
  199. int *pPusherHandles = ( int* )stackalloc( m_rgPusher.Count() * sizeof( int ) );
  200. UnlinkPusherList( pPusherHandles );
  201. for ( int iPushTry = 0; iPushTry < 3; ++iPushTry )
  202. {
  203. MovePlayer( pBlocker, info, 0.35f, pRootEntity->IsBaseTrain() );
  204. if ( !IsPlayerAABBIntersetingPusherOBB( pBlocker, pRootEntity ) )
  205. break;
  206. }
  207. RelinkPusherList( pPusherHandles );
  208. // Is the blocked ground the push entity?
  209. info.m_bPusherIsGround = false;
  210. if ( pBlocker->GetGroundEntity() && pBlocker->GetGroundEntity()->GetRootMoveParent() == m_rgPusher[0].m_pEntity )
  211. {
  212. info.m_bPusherIsGround = true;
  213. }
  214. // Check to see if the player is in a good spot and attempt a move again if not - but only if it isn't being ridden on.
  215. if ( IsPlayerAABBIntersetingPusherOBB( pBlocker, pRootEntity ) )
  216. {
  217. // Try again is the player is still blocked.
  218. // DevMsg( 1, "Pushing rotation hard!\n" );
  219. UnlinkPusherList( pPusherHandles );
  220. MovePlayer( pBlocker, info, 1.0f, pRootEntity->IsBaseTrain() );
  221. RelinkPusherList( pPusherHandles );
  222. }
  223. // The player will never stop a train from moving in TF.
  224. info.m_bBlocked = false;
  225. return true;
  226. }
  227. //-----------------------------------------------------------------------------
  228. // Purpose:
  229. //-----------------------------------------------------------------------------
  230. bool CTFPhysicsPushEntities::LinearPushTFPlayer( PhysicsPushedInfo_t &info, const Vector &vecAbsPush, bool bRotationalPush )
  231. {
  232. // Clear out the collision entity so that if we early out we don't send bogus collision data to the physics system.
  233. info.m_Trace.m_pEnt = NULL;
  234. // Get the player.
  235. CTFPlayer *pPlayer = ToTFPlayer( info.m_pEntity );
  236. if ( !pPlayer )
  237. return false;
  238. info.m_vecStartAbsOrigin = pPlayer->GetAbsOrigin();
  239. // Get the player collision data.
  240. CCollisionProperty *pCollisionPlayer = info.m_pEntity->CollisionProp();
  241. if ( !pCollisionPlayer )
  242. return false;
  243. // Find the root object if in hierarchy.
  244. CBaseEntity *pRootEntity = m_rgPusher[0].m_pEntity->GetRootMoveParent();
  245. if ( !pRootEntity )
  246. return false;
  247. // Get the pusher collision data.
  248. CCollisionProperty *pCollisionPusher = pRootEntity->CollisionProp();
  249. if ( !pCollisionPusher )
  250. return false;
  251. // Do we have a collision.
  252. if ( !IsOBBIntersectingOBB( pCollisionPlayer->GetCollisionOrigin(), pCollisionPlayer->GetCollisionAngles(), pCollisionPlayer->OBBMins(), pCollisionPlayer->OBBMaxs(),
  253. pCollisionPusher->GetCollisionOrigin(), pCollisionPusher->GetCollisionAngles(), pCollisionPusher->OBBMins(), pCollisionPusher->OBBMaxs(),
  254. 0.0f ) )
  255. return false;
  256. if ( pPlayer->GetGroundEntity() == pRootEntity )
  257. {
  258. m_vecPushVector = vecAbsPush;
  259. m_flPushDist = VectorNormalize( m_vecPushVector );
  260. }
  261. else
  262. {
  263. m_vecPushVector = vecAbsPush;
  264. m_flPushDist = VectorNormalize( m_vecPushVector );
  265. m_vecPushVector.z = 0.0f;
  266. VectorNormalize( m_vecPushVector );
  267. }
  268. return LinearCheckPush( info );
  269. }
  270. //-----------------------------------------------------------------------------
  271. // Purpose:
  272. //-----------------------------------------------------------------------------
  273. bool CTFPhysicsPushEntities::LinearCheckPush( PhysicsPushedInfo_t &info )
  274. {
  275. // Get the blocking and pushing entities.
  276. CBaseEntity *pBlocker = info.m_pEntity;
  277. CBaseEntity *pRootEntity = m_rgPusher[0].m_pEntity->GetRootMoveParent();
  278. if ( !pBlocker || !pRootEntity )
  279. return true;
  280. // Unlink the pusher from the spatial partition and attempt a player move.
  281. int *pPusherHandles = ( int* )stackalloc( m_rgPusher.Count() * sizeof( int ) );
  282. UnlinkPusherList( pPusherHandles );
  283. MovePlayer( pBlocker, info, 1.0f, pRootEntity->IsBaseTrain() );
  284. RelinkPusherList( pPusherHandles );
  285. // Is the pusher the ground entity the blocker is standing on?
  286. info.m_bPusherIsGround = false;
  287. if ( pBlocker->GetGroundEntity() && pBlocker->GetGroundEntity()->GetRootMoveParent() == m_rgPusher[0].m_pEntity )
  288. {
  289. info.m_bPusherIsGround = true;
  290. }
  291. // Check to see if the player is in a good spot and attempt a move again if not - but only if it isn't being ridden on.
  292. if ( !info.m_bPusherIsGround && !IsPushedPositionValid( pBlocker ) )
  293. {
  294. // Try again is the player is still blocked.
  295. // DevMsg( 1, "Pushing linear hard!\n" );
  296. UnlinkPusherList( pPusherHandles );
  297. MovePlayer( pBlocker, info, 1.0f, pRootEntity->IsBaseTrain() );
  298. RelinkPusherList( pPusherHandles );
  299. }
  300. // The player will never stop a train from moving in TF.
  301. info.m_bBlocked = false;
  302. return true;
  303. }
  304. //-----------------------------------------------------------------------------
  305. // Purpose:
  306. //-----------------------------------------------------------------------------
  307. bool CTFPhysicsPushEntities::IsPlayerAABBIntersetingPusherOBB( CBaseEntity *pEntity, CBaseEntity *pRootEntity )
  308. {
  309. // Get the player.
  310. CTFPlayer *pPlayer = ToTFPlayer( pEntity );
  311. if ( !pPlayer )
  312. return false;
  313. // Get the player collision data.
  314. CCollisionProperty *pCollisionPlayer = pEntity->CollisionProp();
  315. if ( !pCollisionPlayer )
  316. return false;
  317. // Get the pusher collision data.
  318. CCollisionProperty *pCollisionPusher = pRootEntity->CollisionProp();
  319. if ( !pCollisionPusher )
  320. return false;
  321. // Do we have a collision.
  322. return IsOBBIntersectingOBB( pCollisionPlayer->GetCollisionOrigin(), pCollisionPlayer->GetCollisionAngles(), pCollisionPlayer->OBBMins(), pCollisionPlayer->OBBMaxs(),
  323. pCollisionPusher->GetCollisionOrigin(), pCollisionPusher->GetCollisionAngles(), pCollisionPusher->OBBMins(), pCollisionPusher->OBBMaxs(),
  324. 0.0f );
  325. }
  326. //-----------------------------------------------------------------------------
  327. // Purpose:
  328. //-----------------------------------------------------------------------------
  329. void CTFPhysicsPushEntities::FindNewPushDirection( Vector &vecCurrent, Vector &vecNormal, Vector &vecOutput )
  330. {
  331. // Determine how far along plane to slide based on incoming direction.
  332. float flBackOff = DotProduct( vecCurrent, vecNormal );
  333. for ( int iAxis = 0; iAxis < 3; ++iAxis )
  334. {
  335. float flDelta = vecNormal[iAxis] * flBackOff;
  336. vecOutput[iAxis] = vecCurrent[iAxis] - flDelta;
  337. }
  338. // iterate once to make sure we aren't still moving through the plane
  339. float flAdjust = DotProduct( vecOutput, vecNormal );
  340. if( flAdjust < 0.0f )
  341. {
  342. vecOutput -= ( vecNormal * flAdjust );
  343. }
  344. }
  345. //-----------------------------------------------------------------------------
  346. // Purpose:
  347. //-----------------------------------------------------------------------------
  348. void CTFPhysicsPushEntities::MovePlayer( CBaseEntity *pBlocker, PhysicsPushedInfo_t &info, float flMoveScale, bool bPusherIsTrain )
  349. {
  350. // Find out how far we still need to move.
  351. float flFractionLeft = 1.0f;
  352. float flNewDist = m_flPushDist *flMoveScale;
  353. Vector vecPush = m_vecPushVector;
  354. // Find a new push vector.
  355. Vector vecStart = pBlocker->GetAbsOrigin();
  356. vecStart.z += 4.0f;
  357. for ( int iTest = 0; iTest < 4; ++iTest )
  358. {
  359. // Clear the trace entity.
  360. Vector vecEnd = pBlocker->GetAbsOrigin() + ( flNewDist * vecPush );
  361. UTIL_TraceEntity( pBlocker, vecStart, vecEnd, MASK_PLAYERSOLID, NULL, COLLISION_GROUP_PLAYER_MOVEMENT, &info.m_Trace );
  362. // we don't want trains pushing enemy players through a respawn room visualizer
  363. if ( bPusherIsTrain && pBlocker->IsPlayer() )
  364. {
  365. if ( PointsCrossRespawnRoomVisualizer( vecStart, info.m_Trace.endpos, pBlocker->GetTeamNumber() ) )
  366. {
  367. CTFPlayer *pTFPlayer = ToTFPlayer( pBlocker );
  368. if ( pTFPlayer )
  369. {
  370. pTFPlayer->CommitSuicide( true, true );
  371. return;
  372. }
  373. }
  374. }
  375. if ( info.m_Trace.fraction > 0.0f )
  376. {
  377. pBlocker->SetAbsOrigin( info.m_Trace.endpos );
  378. }
  379. if ( info.m_Trace.fraction == 1.0f || !info.m_Trace.m_pEnt )
  380. break;
  381. // New test distance and position.
  382. flFractionLeft = 1.0f - info.m_Trace.fraction;
  383. flNewDist = flFractionLeft * flNewDist;
  384. flNewDist = flNewDist * ( 1.0f + ( 1.0f - fabs( info.m_Trace.plane.normal.Dot( vecPush ) ) ) );
  385. // Find the new push direction.
  386. Vector vecTmp;
  387. FindNewPushDirection( vecPush, info.m_Trace.plane.normal, vecTmp );
  388. VectorCopy( vecTmp, vecPush );
  389. }
  390. }
  391. //-----------------------------------------------------------------------------
  392. // Causes all entities in the list to touch triggers from their prev position
  393. //-----------------------------------------------------------------------------
  394. void CTFPhysicsPushEntities::FinishRotPushedEntity( CBaseEntity *pPushedEntity, const RotatingPushMove_t &rotPushMove )
  395. {
  396. // Only do this for "payload" or "escort" maps.
  397. if ( !( TFGameRules()->GetGameType() == TF_GAMETYPE_ESCORT ) )
  398. return BaseClass::FinishRotPushedEntity( pPushedEntity, rotPushMove );
  399. if ( !pPushedEntity->IsPlayer() )
  400. {
  401. QAngle angles = pPushedEntity->GetAbsAngles();
  402. // only rotate YAW with pushing. Freely rotateable entities should either use VPHYSICS
  403. // or be set up as children
  404. angles.y += rotPushMove.amove.y;
  405. pPushedEntity->SetAbsAngles( angles );
  406. }
  407. }