//===== Copyright © 1996-2009, Valve Corporation, All rights reserved. ======// // //===========================================================================// #include "cbase.h" #include "paint_power_user.h" #ifdef CLIENT_DLL #include "c_projectedwallentity.h" #include "c_portal_player.h" #include "c_physicsprop.h" #define CPhysicsProp C_PhysicsProp #else #include "projectedwallentity.h" #include "portal_player.h" #include "props.h" // for CPhysicsProp def extern void WallPainted( int colorIndex, int nSegment, CBaseEntity *pWall ); #endif // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" #if defined( GAME_DLL ) ConVar wall_debug_time("wall_debug_time", "5.f"); ConVar wall_debug("wall_debug", "0"); #endif ConVar debug_paintable_projected_wall("debug_paintable_projected_wall", "0", FCVAR_REPLICATED); ConVar sv_thinnerprojectedwalls( "sv_thinnerprojectedwalls", "0", FCVAR_CHEAT | FCVAR_REPLICATED ); void CProjectedWallEntity::Touch( CBaseEntity* pOther ) { //Check if the touched entity is a paint power user IPaintPowerUser* pPowerUser = dynamic_cast< IPaintPowerUser* >( pOther ); if( engine->HasPaintmap() && pPowerUser ) { //Get the up vector of the wall Vector vecWallUp; #if defined( GAME_DLL ) AngleVectors( GetLocalAngles(), NULL, NULL, &vecWallUp ); #else AngleVectors( GetNetworkAngles(), NULL, NULL, &vecWallUp ); #endif const trace_t& trace = BaseClass::GetTouchTrace(); float flDot = DotProduct( vecWallUp, trace.plane.normal ); //Get the segment of the wall that the power user touched Vector vecWorldSpaceCenter = pOther->WorldSpaceCenter(); Vector vecTouchPoint = UTIL_ProjectPointOntoPlane( vecWorldSpaceCenter, trace.plane ); const int nSegment = ComputeSegmentIndex( vecTouchPoint ); if( nSegment >= m_nNumSegments ) { return; } //Get the paint power at the current segment PaintPowerType power = m_PaintPowers[nSegment]; if( debug_paintable_projected_wall.GetBool() ) { DevMsg( "Segment: %d, Power: %d\n", nSegment, power ); } // We dont want to give power to the user if they're touching the side of a projected wall if( !CloseEnough( flDot, 0.0f ) ) { pPowerUser->AddSurfacePaintPowerInfo( PaintPowerInfo_t( trace.plane.normal, trace.endpos, this, power ) ); } } } int CProjectedWallEntity::ComputeSegmentIndex( const Vector& vWorldPositionOnWall ) const { const Vector& startPoint = m_vecStartPoint; const Vector& endPoint = m_vecEndPoint; const Vector wallVector = endPoint - startPoint; const Vector contactOffset = vWorldPositionOnWall - startPoint; const float distance = DotProduct( wallVector, contactOffset ) / m_flLength; Assert( distance + 0.5f > 0.0f && distance < m_flLength + 0.5f ); return clamp( distance / m_flSegmentLength, 0, m_nNumSegments - 1 ); } PaintPowerType CProjectedWallEntity::GetPaintPowerAtPoint( const Vector& worldContactPt ) const { return m_PaintPowers[ComputeSegmentIndex(worldContactPt)]; } void CProjectedWallEntity::Paint( PaintPowerType type, const Vector& worldContactPt ) { const int nSegment = ComputeSegmentIndex( worldContactPt ); if( nSegment < m_PaintPowers.Count() ) { m_PaintPowers[nSegment] = type; #ifndef CLIENT_DLL //Send the event to the client WallPainted( type, nSegment, this ); #endif } } void CProjectedWallEntity::CleansePaint() { for( int i = 0; i < m_nNumSegments; ++i ) { // come back to this - MTW /* #if defined( CLIENT_DLL ) if ( m_PaintPowers[i] != NO_POWER && m_PaintParticles[i] && m_PaintParticles[i]->IsValid() ) { ParticleProp()->StopEmissionAndDestroyImmediately( m_PaintParticles[i] ); } m_PaintParticles[i] = NULL; #endif */ m_PaintPowers[i] = NO_POWER; } } class CProjectorCollideList : public IEntityEnumerator { public: CProjectorCollideList( Ray_t *pRay, CProjectedWallEntity* pIgnoreEntity, int nContentsMask ) : m_Entities( 0, 32 ), m_pIgnoreEntity( pIgnoreEntity ), m_nContentsMask( nContentsMask ), m_pRay(pRay) {} virtual bool EnumEntity( IHandleEntity *pHandleEntity ) { // Don't bother with the ignore entity. if ( pHandleEntity == m_pIgnoreEntity ) return true; Assert( pHandleEntity ); if ( !pHandleEntity ) return true; #if defined( GAME_DLL ) CBaseEntity *pEntity = gEntList.GetBaseEntity( pHandleEntity->GetRefEHandle() ); #else CBaseEntity *pEntity = C_BaseEntity::Instance( pHandleEntity->GetRefEHandle() ); #endif Assert( pEntity ); if ( !pEntity ) return true; // Only interested in physics objects, the player and turret npcs if ( pEntity->IsPlayer() || dynamic_cast( pEntity ) ) { Ray_t ray; ray.Init( pEntity->GetAbsOrigin(), pEntity->GetAbsOrigin(), pEntity->CollisionProp()->OBBMins(), pEntity->CollisionProp()->OBBMaxs() ) ; trace_t tr; m_pIgnoreEntity->TestCollision( ray, MASK_SHOT, tr ); // add if their AABB is overlapping the collideable if ( tr.DidHit() ) { m_Entities.AddToTail( pEntity ); } } return true; } CUtlVector m_Entities; private: CProjectedWallEntity *m_pIgnoreEntity; int m_nContentsMask; Ray_t *m_pRay; }; void CProjectedWallEntity::DisplaceObstructingEntity( CBaseEntity *pEntity, bool bIgnoreStuck ) { #if defined( GAME_DLL ) Vector vOrigin = GetLocalOrigin(); #else Vector vOrigin = GetNetworkOrigin(); #endif Vector vWallForward, vWallRight, vWallUp; GetVectors( &vWallForward, &vWallRight, &vWallUp ); Ray_t ray; Vector vLength = GetLengthVector(); Vector vWallSweptBoxMins, vWallSweptBoxMaxs; GetExtents( vWallSweptBoxMins, vWallSweptBoxMaxs ); ray.Init( vOrigin, vOrigin + vWallForward*vLength.Length(), vWallSweptBoxMins, vWallSweptBoxMaxs ); CTraceFilterOnlyHitThis filter( pEntity ); trace_t tr; UTIL_TraceRay( ray, MASK_ALL, &filter, &tr ); if ( tr.DidHit() ) { DisplaceObstructingEntity( pEntity, vOrigin, vWallUp, vWallRight, bIgnoreStuck ); } } //----------------------------------------------------------------------------- // Purpose: Attempts to smooth over the frequent physics-stuck properties of the wall. //----------------------------------------------------------------------------- void CProjectedWallEntity::DisplaceObstructingEntities( void ) { // Walls size needs to be set up before we test for obstructing objects Assert ( !VectorsAreEqual( m_vWorldSpace_WallMins, m_vWorldSpace_WallMaxs ) ); #if defined( GAME_DLL ) Vector vOrigin = GetLocalOrigin(); #else Vector vOrigin = GetNetworkOrigin(); #endif Vector vWallForward, vWallRight, vWallUp; GetVectors( &vWallForward, &vWallRight, &vWallUp ); Ray_t ray; Vector vLength = GetLengthVector(); Vector vWallSweptBoxMins, vWallSweptBoxMaxs; GetExtents( vWallSweptBoxMins, vWallSweptBoxMaxs ); ray.Init( vOrigin, vOrigin + vWallForward*vLength.Length(), vWallSweptBoxMins, vWallSweptBoxMaxs ); CProjectorCollideList enumerator( &ray, this, MASK_SHOT ); enginetrace->EnumerateEntities( ray, false, &enumerator ); for( int iEntity = enumerator.m_Entities.Count(); --iEntity >= 0; ) { CBaseEntity *pEntity = enumerator.m_Entities[iEntity]; DisplaceObstructingEntity( pEntity, vOrigin, vWallUp, vWallRight, false ); } } CEG_NOINLINE void CProjectedWallEntity::DisplaceObstructingEntity( CBaseEntity *pEntity, const Vector &vOrigin, const Vector &vWallUp, const Vector &vWallRight, bool bIgnoreStuck ) { #ifdef CLIENT_DLL if ( !pEntity->GetPredictable() ) return; #endif Vector vObstructionMaxs = pEntity->CollisionProp()->OBBMins(); Vector vObstructionMins = pEntity->CollisionProp()->OBBMaxs(); Vector vNewPos = pEntity->GetAbsOrigin(); QAngle vNewAngles = pEntity->GetAbsAngles(); Vector vNewVel = pEntity->GetAbsVelocity(); // TODO: // - get 8 corner points out of the OBB // - find max distances PointVSPlane for both sides of the plane // - use the least distance of the two maxs as distance to push off the entity in the direction of normal of the greater max Vector vEntForward, vEntRight, vEntUp; AngleVectors( pEntity->CollisionProp()->GetCollisionAngles(), &vEntForward, &vEntRight, &vEntUp ); Vector ptOBBCenter = pEntity->CollisionProp()->GetCollisionOrigin() + pEntity->CollisionProp()->OBBCenter(); Vector vExtents = ( vObstructionMaxs - vObstructionMins ) * 0.5f; vEntForward *= vExtents.x; vEntRight *= vExtents.y; vEntUp *= vExtents.z; Vector ptOBB[8]; ptOBB[0] = ptOBBCenter - vEntForward - vEntRight - vEntUp; ptOBB[1] = ptOBBCenter - vEntForward - vEntRight + vEntUp; ptOBB[2] = ptOBBCenter - vEntForward + vEntRight + vEntUp; ptOBB[3] = ptOBBCenter - vEntForward + vEntRight - vEntUp; ptOBB[4] = ptOBBCenter + vEntForward - vEntRight - vEntUp; ptOBB[5] = ptOBBCenter + vEntForward - vEntRight + vEntUp; ptOBB[6] = ptOBBCenter + vEntForward + vEntRight + vEntUp; ptOBB[7] = ptOBBCenter + vEntForward + vEntRight - vEntUp; #if defined( GAME_DLL ) if ( wall_debug.GetBool() ) { NDebugOverlay::Sphere( ptOBBCenter, 5, 255, 0, 0, true, wall_debug_time.GetFloat() ); for ( int i=0; i<8; ++i ) { NDebugOverlay::Sphere( ptOBB[i], 2, 255, 0, 0, true, wall_debug_time.GetFloat() ); } NDebugOverlay::VertArrow( GetAbsOrigin(), GetAbsOrigin() + 50.f*vWallUp, 2, 255, 0, 0, 128, true, wall_debug_time.GetFloat() ); } CEG_PROTECT_MEMBER_FUNCTION( CProjectedWallEntity_DisplaceObstructingEntity ); #endif VPlane plWallPlane( vWallUp, DotProduct( vWallUp, vOrigin ) ); float flFrontMax = 0.f; float flBackMax = 0.f; Vector vFrontMaxPos, vBackMaxPos; for ( int i=0; i<8; ++i ) { float flDistToPlane = fabsf( plWallPlane.DistTo( ptOBB[i] ) ); if ( plWallPlane.GetPointSide( ptOBB[i] ) == SIDE_FRONT ) { if ( flDistToPlane > flFrontMax ) { flFrontMax = flDistToPlane; vFrontMaxPos = ptOBB[i]; } } else { if ( flDistToPlane > flBackMax ) { flBackMax = flDistToPlane; vBackMaxPos = ptOBB[i]; } } } // always try to push the entity up or down along Z-axis if the wall is horizontal (walkable) // else push the entity to the side of the bridge in the direction of bridge UP vector projected onto XY-plane float flHalfWallWidth = m_flWidth / 2.f; Vector side1 = vOrigin + flHalfWallWidth * vWallRight; Vector side2 = vOrigin - flHalfWallWidth * vWallRight; Vector vBumpAxis; float flBumpAmount; float flInvBumpAmount; if ( m_bIsHorizontal ) { vBumpAxis = Vector( 0, 0, 1 ); // compute the bump amount float flDot = fabs( clamp( DotProduct( vWallUp, vBumpAxis ), -1.f, 1.f ) ); Assert( flDot != 0.0f ); flBumpAmount = MIN( flBackMax / flDot, MAX( fabs( DotProduct( side1 - vBackMaxPos, vBumpAxis ) ), fabs( DotProduct( side2 - vBackMaxPos, vBumpAxis ) ) ) ); flInvBumpAmount = MIN( flFrontMax / flDot, MAX( fabs( DotProduct( side1 - vFrontMaxPos, vBumpAxis ) ), fabs( DotProduct( side2 - vFrontMaxPos, vBumpAxis ) ) ) ); if ( vWallUp.z < 0.f ) { V_swap( flBumpAmount, flInvBumpAmount ); } } else { vBumpAxis = Vector( vWallUp.x, vWallUp.y, 0.f ); VectorNormalize( vBumpAxis ); // compute the bump amount float flDot = fabs( clamp( DotProduct( vWallUp, vBumpAxis ), -1.f, 1.f ) ); Assert( flDot != 0.0f ); flBumpAmount = MIN( flBackMax / flDot, MAX( fabs( DotProduct( side1 - vBackMaxPos, vBumpAxis ) ), fabs( DotProduct( side2 - vBackMaxPos, vBumpAxis ) ) ) ); flInvBumpAmount = MIN( flFrontMax / flDot, MAX( fabs( DotProduct( side1 - vFrontMaxPos, vBumpAxis ) ), fabs( DotProduct( side2 - vFrontMaxPos, vBumpAxis ) ) ) ); #if defined( GAME_DLL ) if ( wall_debug.GetBool() ) { // front side push NDebugOverlay::VertArrow( vFrontMaxPos - flFrontMax * vWallUp, vFrontMaxPos, 2, 255, 0, 0, 255, true, wall_debug_time.GetFloat() ); NDebugOverlay::VertArrow( vFrontMaxPos, vFrontMaxPos - flInvBumpAmount * vBumpAxis, 2, 255, 0, 0, 255, true, wall_debug_time.GetFloat() ); // back side push NDebugOverlay::VertArrow( vBackMaxPos + flBackMax * vWallUp, vBackMaxPos, 2, 0, 0, 255, 255, true, wall_debug_time.GetFloat() ); NDebugOverlay::VertArrow( vBackMaxPos, vBackMaxPos + flBumpAmount * vBumpAxis, 2, 0, 0, 255, 255, true, wall_debug_time.GetFloat() ); } #endif // push in the negative direction of the projected normal if ( flFrontMax < flBackMax ) { VectorNegate( vBumpAxis ); V_swap( flBumpAmount, flInvBumpAmount ); } } // add epsilon flBumpAmount += 0.1f; flInvBumpAmount += 0.1f; vNewPos += flBumpAmount * vBumpAxis; #if defined( GAME_DLL ) if ( wall_debug.GetBool() ) { Vector vPosOffset = vNewPos - pEntity->GetAbsOrigin(); NDebugOverlay::Sphere( ptOBBCenter + vPosOffset, 5, 0, 0, 255, true, wall_debug_time.GetFloat() ); for ( int i=0; i<8; ++i ) { NDebugOverlay::Sphere( ptOBB[i] + vPosOffset, 2, 0, 0, 255, true, wall_debug_time.GetFloat() ); } NDebugOverlay::BoxAngles( pEntity->GetAbsOrigin() , vObstructionMins, vObstructionMaxs, pEntity->CollisionProp()->GetCollisionAngles(), 0, 255, 255, 64, wall_debug_time.GetFloat() ); NDebugOverlay::BoxAngles( vNewPos , vObstructionMins, vObstructionMaxs, pEntity->CollisionProp()->GetCollisionAngles(), 255, 255, 0, 64, wall_debug_time.GetFloat() ); } #endif // check if the entity gets stuck at the new pos CTraceFilterSimple filter( pEntity, COLLISION_GROUP_NONE ); trace_t stuckTrace; enginetrace->SweepCollideable( pEntity->GetCollideable(), vNewPos, vNewPos, vNewAngles, MASK_SOLID, &filter, &stuckTrace ); // If safe, teleport. Otherwise, we're better off being stuck by the wall. if ( !stuckTrace.startsolid || bIgnoreStuck ) { //EASY_DIFFPRINT( this, "CProjectedWallEntity::DisplaceObstructingEntities() teleport up" ); // TODO: Some smoothing of the player's view or effect when this happens? pEntity->Teleport( &vNewPos, &vNewAngles, &vNewVel ); return; } // the entity got stuck with horizontal bridge else if ( pEntity->IsPlayer() && m_bIsHorizontal ) { // if success, move on CPortal_Player* pPlayer = ToPortalPlayer( pEntity ); Assert ( pPlayer ); if ( !pPlayer ) return; // 1. If player wasn't crouching already, try to crouch and move player up if ( !pPlayer->m_Local.m_bDucked ) { // If ducking stops the intersection, force them to duck TracePlayerBoxAgainstCollidables( stuckTrace, pPlayer, vNewPos, vNewPos, VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX ); if ( !stuckTrace.startsolid ) { //EASY_DIFFPRINT( this, "CProjectedWallEntity::DisplaceObstructingEntities() force duck up" ); pPlayer->ForceDuckThisFrame(); pPlayer->Teleport( &vNewPos, &vNewAngles, &vNewVel ); #if defined( GAME_DLL ) if ( wall_debug.GetBool() ) { NDebugOverlay::BoxAngles( vNewPos , VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX, pEntity->CollisionProp()->GetCollisionAngles(), 255, 0, 0, 64, wall_debug_time.GetFloat() ); } #endif return; } } // 2. If pushing up failed, try to move the player to the opposite side vNewPos = pEntity->GetAbsOrigin() - flInvBumpAmount * vBumpAxis; if ( !pPlayer->m_Local.m_bDucked ) { TracePlayerBoxAgainstCollidables( stuckTrace, pPlayer, vNewPos, vNewPos, VEC_HULL_MIN, VEC_HULL_MAX ); if ( !stuckTrace.startsolid ) { //EASY_DIFFPRINT( this, "CProjectedWallEntity::DisplaceObstructingEntities() teleport down" ); pPlayer->Teleport( &vNewPos, &vNewAngles, &vNewVel ); #if defined( GAME_DLL ) if ( wall_debug.GetBool() ) { NDebugOverlay::BoxAngles( vNewPos , VEC_HULL_MIN, VEC_HULL_MAX, pEntity->CollisionProp()->GetCollisionAngles(), 255, 0, 0, 64, wall_debug_time.GetFloat() ); } #endif return; } // check duck vNewPos += 36.f * Vector( 0, 0, 1 ); TracePlayerBoxAgainstCollidables( stuckTrace, pPlayer, vNewPos, vNewPos, VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX ); if ( !stuckTrace.startsolid ) { //EASY_DIFFPRINT( this, "CProjectedWallEntity::DisplaceObstructingEntities() force duck down" ); pPlayer->ForceDuckThisFrame(); pPlayer->Teleport( &vNewPos, &vNewAngles, &vNewVel ); #if defined( GAME_DLL ) if ( wall_debug.GetBool() ) { NDebugOverlay::BoxAngles( vNewPos , VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX, pEntity->CollisionProp()->GetCollisionAngles(), 255, 0, 0, 64, wall_debug_time.GetFloat() ); } #endif return; } } else { TracePlayerBoxAgainstCollidables( stuckTrace, pPlayer, vNewPos, vNewPos, VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX ); if ( !stuckTrace.startsolid ) { //EASY_DIFFPRINT( this, "CProjectedWallEntity::DisplaceObstructingEntities() double duck down" ); pPlayer->ForceDuckThisFrame(); pPlayer->Teleport( &vNewPos, &vNewAngles, &vNewVel ); #if defined( GAME_DLL ) if ( wall_debug.GetBool() ) { NDebugOverlay::BoxAngles( vNewPos , VEC_HULL_MIN, VEC_HULL_MAX, pEntity->CollisionProp()->GetCollisionAngles(), 255, 0, 0, 64, wall_debug_time.GetFloat() ); } #endif return; } } } // player stuck in not-horizontal bridge OR entity is stuck in a bridge else { vNewPos = pEntity->GetAbsOrigin() - flInvBumpAmount * vBumpAxis; UTIL_ClearTrace( stuckTrace ); enginetrace->SweepCollideable( pEntity->GetCollideable(), vNewPos, vNewPos, vNewAngles, MASK_SOLID, &filter, &stuckTrace ); if ( !stuckTrace.startsolid || bIgnoreStuck ) { pEntity->Teleport( &vNewPos, &vNewAngles, &vNewVel ); #if defined( GAME_DLL ) if ( wall_debug.GetBool() ) { NDebugOverlay::BoxAngles( vNewPos , vObstructionMins, vObstructionMaxs, pEntity->CollisionProp()->GetCollisionAngles(), 255, 0, 0, 64, wall_debug_time.GetFloat() ); } STEAMWORKS_SELFCHECK(); #endif return; } #if defined( GAME_DLL ) if ( wall_debug.GetBool() ) { NDebugOverlay::BoxAngles( vNewPos , vObstructionMins, vObstructionMaxs, pEntity->CollisionProp()->GetCollisionAngles(), 255, 0, 0, 64, wall_debug_time.GetFloat() ); } #endif } // The entity is stuck at super rare case if we get here. { AssertMsg( 0, "Rare case for the entity getting stuck with projected bridge. Investigate at CProjectedWallEntity::DisplaceObstructingEntities()." ); } } void CProjectedWallEntity::GetExtents( Vector &outMins, Vector &outMaxs, float flWidthScale ) { // Get current orientation Vector vecForward, vecRight, vecUp; #if defined( GAME_DLL ) QAngle qAngles = GetLocalAngles(); #else QAngle qAngles = GetNetworkAngles(); #endif AngleVectors( qAngles, &vecForward, &vecRight, &vecUp ); #if defined( GAME_DLL ) && !defined( _PS3 ) // we're assuming it's oblong, and that height is the larger COMPILE_TIME_ASSERT( WALL_PROJECTOR_THICKNESS > WALL_PROJECTOR_HEIGHT ); #endif // Set up mins/maxes to trace along float flHalfHeight = m_flHeight / 2.f; float flHalfWidth = ( m_flWidth * flWidthScale ) / 2.f; Vector vTmpExtent1 = ( -vecForward * FLT_EPSILON ) - ( vecUp * flHalfHeight ) - ( vecRight * flHalfWidth ); Vector vTmpExtent2 = ( vecForward * FLT_EPSILON ) + ( vecUp * flHalfHeight ) + ( vecRight * flHalfWidth ); // align the mins and maxs Vector vWallSweptBoxMins, vWallSweptBoxMaxs; VectorMin( vTmpExtent1, vTmpExtent2, vWallSweptBoxMins ); VectorMax( vTmpExtent1, vTmpExtent2, vWallSweptBoxMaxs ); outMins = vWallSweptBoxMins; outMaxs = vWallSweptBoxMaxs; }