//========= Copyright Valve Corporation,k All rights reserved. ============// // // // Note: This code integrated then adapted from TF: // //ValveGames/staging/src/game/server/tf/tf_pushentity.cpp // // $NoKeywords: $ //=============================================================================// #include "cbase.h" #include "pushentity.h" #include "cs_player.h" #include "collisionutils.h" #include "cs_gamerules.h" //#include "mathlib/mathlib.h" class CCSPhysicsPushEntities : public CPhysicsPushedEntities { public: DECLARE_CLASS( CCSPhysicsPushEntities, CPhysicsPushedEntities ); // Constructor/Destructor. CCSPhysicsPushEntities(); ~CCSPhysicsPushEntities(); protected: // Speculatively checks to see if all entities in this list can be pushed virtual bool SpeculativelyCheckRotPush( const RotatingPushMove_t &rotPushMove, CBaseEntity *pRoot ) OVERRIDE; virtual bool SpeculativelyCheckLinearPush( const Vector &vecAbsPush ) OVERRIDE; virtual void FinishRotPushedEntity( CBaseEntity *pPushedEntity, const RotatingPushMove_t &rotPushMove ) OVERRIDE; private: bool RotationPushCSPlayer( PhysicsPushedInfo_t &info, const Vector &vecAbsPush, const RotatingPushMove_t &rotPushMove, bool bRotationalPush, CBaseEntity *pRoot ); bool RotationCheckPush( PhysicsPushedInfo_t &info, bool bIgnoreTeammates ); bool LinearPushCSPlayer( PhysicsPushedInfo_t &info, const Vector &vecAbsPush, bool bRotationalPush ); bool LinearCheckPush( PhysicsPushedInfo_t &info, bool bIgnoreTeammates ); void EnsureValidPushWhileRiding( CBaseEntity *pBlocker, CBaseEntity *pPusher ); bool IsPlayerAABBIntersetingPusherOBB( CBaseEntity *pEntity, CBaseEntity *pRootEntity ); void MovePlayer( CBaseEntity *pBlocker, PhysicsPushedInfo_t &info, float flMoveScale, bool bPusherIsTrain, bool bIgnoreTeammates ); void FindNewPushDirection( Vector &vecCurrent, Vector &vecNormal, Vector &vecOutput ); float m_flPushDist; Vector m_vecPushVector; }; CCSPhysicsPushEntities s_CSPushedEntities; CPhysicsPushedEntities *g_pPushedEntities = &s_CSPushedEntities; //----------------------------------------------------------------------------- // Purpose: Constructor. //----------------------------------------------------------------------------- CCSPhysicsPushEntities::CCSPhysicsPushEntities() { } //----------------------------------------------------------------------------- // Purpose: Destructor. //----------------------------------------------------------------------------- CCSPhysicsPushEntities::~CCSPhysicsPushEntities() { } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CCSPhysicsPushEntities::SpeculativelyCheckRotPush( const RotatingPushMove_t &rotPushMove, CBaseEntity *pRoot ) { TM_ZONE_DEFAULT( TELEMETRY_LEVEL0 ); Vector vecAbsPush( 0.0f, 0.0f, 0.0f ); m_nBlocker = -1; int nMovedCount = m_rgMoved.Count(); for ( int i = ( nMovedCount - 1 ); i >= 0; --i ) { // Is the entity and CS Player? CCSPlayer *pCSPlayer = NULL; bool bPusherIsTrain = false; if ( m_rgMoved[i].m_pEntity && m_rgMoved[i].m_pEntity->IsPlayer() ) { pCSPlayer = ToCSPlayer( m_rgMoved[i].m_pEntity ); CBaseEntity* pPusher = m_rgPusher[ 0 ].m_pEntity->GetRootMoveParent(); bPusherIsTrain = pPusher && pPusher->IsBaseTrain(); } // Special code to move the player away from the func_train. // Only do this if it's a train pushing a player--otherwise use base class. if ( pCSPlayer && bPusherIsTrain ) { // Rotationally push the player! ComputeRotationalPushDirection( m_rgMoved[ i ].m_pEntity, rotPushMove, &vecAbsPush, pRoot ); RotationPushCSPlayer( m_rgMoved[i], vecAbsPush, rotPushMove, true, pRoot ); } else { // Keep this in sync with BaseClass::SpeculativelyCheckRotPush ComputeRotationalPushDirection( m_rgMoved[i].m_pEntity, rotPushMove, &vecAbsPush, pRoot ); if ( !SpeculativelyCheckPush( m_rgMoved[i], vecAbsPush, true, pRoot ) ) { m_nBlocker = i; return false; } } } return true; } //----------------------------------------------------------------------------- // Speculatively checks to see if all entities in this list can be pushed //----------------------------------------------------------------------------- bool CCSPhysicsPushEntities::SpeculativelyCheckLinearPush( const Vector &vecAbsPush ) { TM_ZONE_DEFAULT( TELEMETRY_LEVEL0 ); m_nBlocker = -1; int nMovedCount = m_rgMoved.Count(); for ( int i = ( nMovedCount - 1 ); i >= 0; --i ) { // Is the entity and CS Player? CCSPlayer *pCSPlayer = NULL; bool bPusherIsTrain = false; if ( m_rgMoved[i].m_pEntity && m_rgMoved[i].m_pEntity->IsPlayer() ) { pCSPlayer = ToCSPlayer( m_rgMoved[i].m_pEntity ); CBaseEntity* pPusher = m_rgPusher[0].m_pEntity->GetRootMoveParent(); bPusherIsTrain = pPusher && pPusher->IsBaseTrain(); } // Special code to move the player away from the func_train. // Only do this if it's a train pushing a player--otherwise use base class. if ( pCSPlayer && bPusherIsTrain ) { // Linearly push the player! LinearPushCSPlayer( m_rgMoved[i], vecAbsPush, false ); } else { // Keep this in sync with BaseClass::SpeculativelyCheckLinearPush if ( !SpeculativelyCheckPush( m_rgMoved[i], vecAbsPush, false, NULL ) ) { m_nBlocker = i; return false; } } } return true; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CCSPhysicsPushEntities::RotationPushCSPlayer( PhysicsPushedInfo_t &info, const Vector &vecAbsPush, const RotatingPushMove_t &rotPushMove, bool bRotationalPush, CBaseEntity* pRoot ) { const bool cbIgnoreTeammates = !CSGameRules() || !CSGameRules()->IsTeammateSolid(); Assert( cbIgnoreTeammates ); // This code doesn't behave if teammates are solid. // Clear out the collision entity so that if we early out we don't send bogus collision data to the physics system. info.m_Trace.m_pEnt = NULL; // Look into doing a full engine->CM_Clear( trace) // Get the player. CCSPlayer *pPlayer = ToCSPlayer( info.m_pEntity ); if ( !pPlayer ) return false; info.m_vecStartAbsOrigin = pPlayer->GetAbsOrigin(); // Get the player collision data. CCollisionProperty *pCollisionPlayer = info.m_pEntity->CollisionProp(); if ( !pCollisionPlayer ) return false; // Find the root object if in hierarchy. CBaseEntity *pRootEntity = m_rgPusher[0].m_pEntity->GetRootMoveParent(); if ( !pRootEntity ) return false; // This code doesn't at all match the code in AvoidPushawayProps, which is a bummer. It'd be // great if this just got rolled into that. Unfortunately, doing so would also require // making trains predictive--they are not currently. if ( !pPlayer->GetGroundEntity() || pPlayer->GetGroundEntity()->GetRootMoveParent() != pRootEntity ) { Vector vMinPushAway = vecAbsPush; m_flPushDist = VectorNormalize( vMinPushAway ); m_vecPushVector = vMinPushAway; Assert( !m_vecPushVector.IsZero() ); // Is our push vector legit? } else { SpeculativelyCheckPush( info, vecAbsPush, true, pRoot, cbIgnoreTeammates ); EnsureValidPushWhileRiding( pPlayer, pRootEntity ); } return RotationCheckPush( info, cbIgnoreTeammates ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CCSPhysicsPushEntities::RotationCheckPush( PhysicsPushedInfo_t &info, bool bIgnoreTeammates ) { // Get the blocking and pushing entities. CBaseEntity *pBlocker = info.m_pEntity; CBaseEntity *pRootEntity = m_rgPusher[0].m_pEntity->GetRootMoveParent(); if ( !pBlocker || !pRootEntity ) return true; int *pPusherHandles = ( int* )stackalloc( m_rgPusher.Count() * sizeof( int ) ); UnlinkPusherList( pPusherHandles ); for ( int iPushTry = 0; iPushTry < 3; ++iPushTry ) { MovePlayer( pBlocker, info, 0.35f, pRootEntity->IsBaseTrain(), bIgnoreTeammates ); if ( IsPushedPositionValid( pBlocker, bIgnoreTeammates ) ) break; } RelinkPusherList( pPusherHandles ); // Is the blocked ground the push entity? info.m_bPusherIsGround = false; if ( pBlocker->GetGroundEntity() && pBlocker->GetGroundEntity()->GetRootMoveParent() == m_rgPusher[0].m_pEntity ) { info.m_bPusherIsGround = true; } // 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. if ( !IsPushedPositionValid( pBlocker, bIgnoreTeammates ) ) { // Try again is the player is still blocked. DevMsg( 2, "Pushing rotation hard!\n" ); UnlinkPusherList( pPusherHandles ); MovePlayer( pBlocker, info, 1.0f, pRootEntity->IsBaseTrain(), bIgnoreTeammates ); RelinkPusherList( pPusherHandles ); } // The player will never stop a train from moving in CS. info.m_bBlocked = false; return true; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CCSPhysicsPushEntities::LinearPushCSPlayer( PhysicsPushedInfo_t &info, const Vector &vecAbsPush, bool bRotationalPush ) { const bool cbIgnoreTeammates = !CSGameRules() || !CSGameRules()->IsTeammateSolid(); Assert( cbIgnoreTeammates ); // This code doesn't behave if teammates are solid. // Clear out the collision entity so that if we early out we don't send bogus collision data to the physics system. info.m_Trace.m_pEnt = NULL; // Get the player. CCSPlayer *pPlayer = ToCSPlayer( info.m_pEntity ); if ( !pPlayer ) return false; info.m_vecStartAbsOrigin = pPlayer->GetAbsOrigin(); // Get the player collision data. CCollisionProperty *pCollisionPlayer = info.m_pEntity->CollisionProp(); if ( !pCollisionPlayer ) return false; // Find the root object if in hierarchy. CBaseEntity *pRootEntity = m_rgPusher[0].m_pEntity->GetRootMoveParent(); if ( !pRootEntity ) return false; // Get the pusher collision data. CCollisionProperty *pCollisionPusher = pRootEntity->CollisionProp(); if ( !pCollisionPusher ) return false; if ( !pPlayer->GetGroundEntity() || pPlayer->GetGroundEntity()->GetRootMoveParent() != pRootEntity ) { m_vecPushVector = vecAbsPush; m_flPushDist = VectorNormalize( m_vecPushVector ); } else { // Try to get the base class first. SpeculativelyCheckPush( info, vecAbsPush, false, NULL, cbIgnoreTeammates ); EnsureValidPushWhileRiding( pPlayer, pRootEntity ); } return LinearCheckPush( info, cbIgnoreTeammates ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CCSPhysicsPushEntities::LinearCheckPush( PhysicsPushedInfo_t &info, bool bIgnoreTeammates ) { // Get the blocking and pushing entities. CBaseEntity *pBlocker = info.m_pEntity; CBaseEntity *pRootEntity = m_rgPusher[0].m_pEntity->GetRootMoveParent(); if ( !pBlocker || !pRootEntity ) return true; // Unlink the pusher from the spatial partition and attempt a player move. int *pPusherHandles = ( int* )stackalloc( m_rgPusher.Count() * sizeof( int ) ); UnlinkPusherList( pPusherHandles ); MovePlayer( pBlocker, info, 1.0f, pRootEntity->IsBaseTrain(), bIgnoreTeammates ); RelinkPusherList( pPusherHandles ); // Is the pusher the ground entity the blocker is standing on? info.m_bPusherIsGround = false; if ( pBlocker->GetGroundEntity() && pBlocker->GetGroundEntity()->GetRootMoveParent() == m_rgPusher[0].m_pEntity ) { info.m_bPusherIsGround = true; } // 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. if ( !info.m_bPusherIsGround && !IsPushedPositionValid( pBlocker, bIgnoreTeammates ) ) { // Try again is the player is still blocked. DevMsg( 2, "Pushing linear hard!\n" ); UnlinkPusherList( pPusherHandles ); MovePlayer( pBlocker, info, 1.0f, pRootEntity->IsBaseTrain(), bIgnoreTeammates ); RelinkPusherList( pPusherHandles ); } // The player will never stop a train from moving in CS. info.m_bBlocked = false; return true; } //----------------------------------------------------------------------------- // Purpose: When riding atop a vehicle, try to ensure that the final push vector and // push distance will land us in a valid location. //----------------------------------------------------------------------------- void CCSPhysicsPushEntities::EnsureValidPushWhileRiding( CBaseEntity *pBlocker, CBaseEntity *pPusher ) { const bool cbIgnoreTeammates = !CSGameRules() || !CSGameRules()->IsTeammateSolid(); Assert( cbIgnoreTeammates ); // This code doesn't behave if teammates are solid. TM_ZONE_DEFAULT( TELEMETRY_LEVEL3 ); m_vecPushVector.Zero(); m_flPushDist = 0.0f; // Do we still have a collision? if ( IsPushedPositionValid( pBlocker, cbIgnoreTeammates ) ) return; const float cMaxDistToLookForPlacement = 72; // Try nudging them upwards a bit. if ( FindValidLocationUpwards( &m_flPushDist, pBlocker, cMaxDistToLookForPlacement, 1.1 ) ) { m_vecPushVector.z = 1.0f; } else { // Try to push the player backwards along the direction of travel of the vehicle (and also up a bit)? Vector vBackwardsAndUp( 0, 0, 1 ); Vector vTraceEndpoint = pPusher->GetAbsVelocity(); vTraceEndpoint.x = -vTraceEndpoint.x; vTraceEndpoint.y = -vTraceEndpoint.y; vTraceEndpoint.z = sqrt( vTraceEndpoint.x * vTraceEndpoint.x + vTraceEndpoint.y * vTraceEndpoint.y ); vTraceEndpoint = vTraceEndpoint.Normalized() * cMaxDistToLookForPlacement; vTraceEndpoint += pBlocker->GetAbsOrigin(); Vector vDelta; if ( FindValidLocationAlongVector( &vDelta, pBlocker, vTraceEndpoint, 1.1 ) ) { m_vecPushVector = vDelta; m_flPushDist = VectorNormalize( m_vecPushVector ); } else { Assert( !"Failed to find a location upwards or backwards for a blocker, sadness!" ); } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CCSPhysicsPushEntities::IsPlayerAABBIntersetingPusherOBB( CBaseEntity *pEntity, CBaseEntity *pRootEntity ) { // Get the player. CCSPlayer *pPlayer = ToCSPlayer( pEntity ); if ( !pPlayer ) return false; // Get the player collision data. CCollisionProperty *pCollisionPlayer = pEntity->CollisionProp(); if ( !pCollisionPlayer ) return false; // Get the pusher collision data. CCollisionProperty *pCollisionPusher = pRootEntity->CollisionProp(); if ( !pCollisionPusher ) return false; // Do we have a collision. return IsOBBIntersectingOBB( pCollisionPlayer->GetCollisionOrigin(), pCollisionPlayer->GetCollisionAngles(), pCollisionPlayer->OBBMins(), pCollisionPlayer->OBBMaxs(), pCollisionPusher->GetCollisionOrigin(), pCollisionPusher->GetCollisionAngles(), pCollisionPusher->OBBMins(), pCollisionPusher->OBBMaxs(), 0.0f ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CCSPhysicsPushEntities::FindNewPushDirection( Vector &vecCurrent, Vector &vecNormal, Vector &vecOutput ) { // Determine how far along plane to slide based on incoming direction. float flBackOff = DotProduct( vecCurrent, vecNormal ); for ( int iAxis = 0; iAxis < 3; ++iAxis ) { float flDelta = vecNormal[iAxis] * flBackOff; vecOutput[iAxis] = vecCurrent[iAxis] - flDelta; } // iterate once to make sure we aren't still moving through the plane float flAdjust = DotProduct( vecOutput, vecNormal ); if( flAdjust < 0.0f ) { vecOutput -= ( vecNormal * flAdjust ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CCSPhysicsPushEntities::MovePlayer( CBaseEntity *pBlocker, PhysicsPushedInfo_t &info, float flMoveScale, bool bPusherIsTrain, bool bIgnoreTeammates ) { // Find out how far we still need to move. float flFractionLeft = 1.0f; float flNewDist = m_flPushDist *flMoveScale; Vector vecPush = m_vecPushVector; // Find a new push vector. Vector vecStart = pBlocker->GetAbsOrigin(); Vector logVecStart = vecStart; Vector logVecEnd = vecStart; int iSteps = 0; vecStart.z += 4.0f; for ( int iTest = 0; iTest < 4; ++iTest ) { // Clear the trace entity. Vector vecEnd = pBlocker->GetAbsOrigin() + ( flNewDist * vecPush ); TraceBlockerEntity( pBlocker, vecStart, vecEnd, bIgnoreTeammates, &info.m_Trace ); if ( info.m_Trace.fraction > 0.0f ) { pBlocker->SetAbsOrigin( info.m_Trace.endpos ); logVecEnd = info.m_Trace.endpos; iSteps = iTest + 1; } if ( info.m_Trace.fraction == 1.0f || !info.m_Trace.m_pEnt ) break; // New test distance and position. flFractionLeft = 1.0f - info.m_Trace.fraction; flNewDist = flFractionLeft * flNewDist; flNewDist = flNewDist * ( 1.0f + ( 1.0f - fabs( info.m_Trace.plane.normal.Dot( vecPush ) ) ) ); // Find the new push direction. Vector vecTmp; FindNewPushDirection( vecPush, info.m_Trace.plane.normal, vecTmp ); VectorCopy( vecTmp, vecPush ); } Vector finalPushVec = logVecEnd - logVecStart; DevMsg( 2, "Pushed player by %.2f over %d steps (push vector: %.2f, %.2f, %.2f)\n", finalPushVec.Length(), iSteps, finalPushVec.x, finalPushVec.y, finalPushVec.z ); } //----------------------------------------------------------------------------- // Causes all entities in the list to touch triggers from their prev position //----------------------------------------------------------------------------- void CCSPhysicsPushEntities::FinishRotPushedEntity( CBaseEntity *pPushedEntity, const RotatingPushMove_t &rotPushMove ) { if ( !pPushedEntity->IsPlayer() ) { QAngle angles = pPushedEntity->GetAbsAngles(); // only rotate YAW with pushing. Freely rotateable entities should either use VPHYSICS // or be set up as children angles.y += rotPushMove.amove.y; pPushedEntity->SetAbsAngles( angles ); } }