Counter Strike : Global Offensive Source Code
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.
 
 
 
 
 
 

577 lines
19 KiB

//===== 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<CPhysicsProp*>( 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<CBaseEntity*> 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;
}