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.
 
 
 
 
 
 

622 lines
20 KiB

//========= Copyright © 1996-2009, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================//
#include "cbase.h"
#include "baseprojectedentity_shared.h"
#if defined( GAME_DLL )
# include "baseprojector.h"
# include "info_placement_helper.h"
# include "portal_gamestats.h"
# include "weapon_portalgun_shared.h"
#else
typedef C_BaseProjectedEntity CBaseProjectedEntity;
#include "prediction.h"
#include "c_baseprojector.h"
#endif
// offset away from the projector, so the trace doesn't start in solid
#define PROJECTEDENTITY_TRACE_OFFSET 25.f
void UTil_ProjectedEntity_Trace_Filter( CTraceFilterSimpleClassnameList *traceFilter )
{
traceFilter->AddClassnameToIgnore( "prop_physics" );
traceFilter->AddClassnameToIgnore( "func_physbox" );
traceFilter->AddClassnameToIgnore( "simple_physics_brush" );
/*traceFilter->AddClassnameToIgnore( "prop_weighted_cube" );
traceFilter->AddClassnameToIgnore( "npc_portal_turret_floor" );
traceFilter->AddClassnameToIgnore( "prop_energy_ball" );
traceFilter->AddClassnameToIgnore( "npc_security_camera" );
traceFilter->AddClassnameToIgnore( "simple_physics_prop" );
traceFilter->AddClassnameToIgnore( "prop_ragdoll" );
traceFilter->AddClassnameToIgnore( "prop_glados_core" );
traceFilter->AddClassnameToIgnore( "player" );
traceFilter->AddClassnameToIgnore( "projected_wall_entity" );
traceFilter->AddClassnameToIgnore( "prop_paint_bomb" );
traceFilter->AddClassnameToIgnore( "prop_exploding_futbol" );
traceFilter->AddClassnameToIgnore( "prop_wall_projector" );
traceFilter->AddClassnameToIgnore( "projected_wall_entity" );
traceFilter->AddClassnameToIgnore( "projected_tractor_beam_entity" );
traceFilter->AddClassnameToIgnore( "trigger_tractorbeam" );
traceFilter->AddClassnameToIgnore( "physicsshadowclone" );
traceFilter->AddClassnameToIgnore( "prop_floor_button" );*/
}
//--------------------------------------------------------------------------------------------------
//
//--------------------------------------------------------------------------------------------------
bool CBaseProjectedEntity::IsHittingPortal( Vector* pOutOrigin, QAngle* pOutAngles, CPortal_Base2D** pOutPortal )
{
#if defined( GAME_DLL )
QAngle qAngles = GetLocalAngles();
#else
QAngle qAngles = GetNetworkAngles();
#endif
// Get current orientation
Vector vForward, vecRight, vecUp;
AngleVectors( qAngles, &vForward );
Vector mins, maxs;
GetProjectionExtents( mins, maxs );
#if defined( GAME_DLL )
Vector vStart = GetLocalOrigin();
#else
Vector vStart = GetNetworkOrigin();
#endif
Ray_t ray;
// Projected ents keep themselves off the walls slightly, so move this up double that ammount to make
// sure we hit any potential portal.
Vector rayPos = vStart + PROJECTEDENTITY_TRACE_OFFSET * vForward;// GetEndPoint() - 5.f * vForward; // back up the start pos of the ray a bit to make sure that we miss anything at the endpoint
ray.Init( rayPos, rayPos + ( vForward * MAX_TRACE_LENGTH ), mins, maxs );
float flPortalTraceFraction = 1.0f;
trace_t worldTrace;
CTraceFilterSimpleClassnameList traceFilter( this, COLLISION_GROUP_NONE );
UTil_ProjectedEntity_Trace_Filter( &traceFilter );
UTIL_TraceRay( ray, MASK_SOLID_BRUSHONLY, &traceFilter, &worldTrace );
CPortal_Base2D* pHitPortal = UTIL_Portal_FirstAlongRay( ray, flPortalTraceFraction );
if ( pOutOrigin )
{
*pOutOrigin = worldTrace.endpos;
}
if ( pOutAngles )
{
*pOutAngles = qAngles;
}
// trace hit world brush before hitting portal
// we need the threshold because the difference of the two fractions is very small in a valid case
float flTraceThreshold = 0.0001f;
if ( flPortalTraceFraction - worldTrace.fraction > flTraceThreshold )
{
return false;
}
if ( pOutPortal )
{
*pOutPortal = pHitPortal;
}
if ( !pHitPortal )
{
return false;
}
// We only care about portals that are currently linked (we don't project through anything else)
if ( pHitPortal->IsActivedAndLinked() == false )
return false;
float flIntersectionFraction = UTIL_IntersectRayWithPortal( ray, pHitPortal );
Vector vHitPoint = ray.m_Start + ray.m_Delta*flIntersectionFraction;
// Wall hit a portal, reorient and project on the other side
VMatrix matToLinked = pHitPortal->MatrixThisToLinked();
Vector vNewWallOrigin;
UTIL_Portal_PointTransform( matToLinked, vHitPoint, vNewWallOrigin );
QAngle vNewAngles;
UTIL_Portal_AngleTransform( matToLinked, qAngles, vNewAngles );
Vector vNewForward;
AngleVectors( vNewAngles, &vNewForward, NULL, NULL );
// Move far enough in front of the portal not to be co-planar
// (will cause traces to start solid and have z fighting issues on the renderable)
vNewWallOrigin += vNewForward * PROJECTION_END_POINT_EPSILON;
if ( pOutAngles )
{
*pOutAngles = vNewAngles;
}
if ( pOutOrigin )
{
*pOutOrigin = vNewWallOrigin;
}
return true;
}
#if defined( CLIENT_DLL )
ConVar cl_predict_projected_entities( "cl_predict_projected_entities", "1" );
#endif
//--------------------------------------------------------------------------------------------------
//
//--------------------------------------------------------------------------------------------------
void CBaseProjectedEntity::RecursiveProjection( bool bShouldSpawn, CBaseProjector *pParentProjector, CPortal_Base2D *pExitPortal, const Vector &vProjectOrigin, const QAngle &qProjectAngles, int iRemainingProjections, bool bDisablePlacementHelper )
{
#if defined( CLIENT_DLL )
if( !prediction->InPrediction() || !GetPredictable() )
return;
#endif
AddEffects( EF_NOINTERP );
#if 0
Vector vFlooredPosition; //HACKHACK: the inputs vary just ever so slightly from client/server. Hopefully flooring them will keep them in sync
vFlooredPosition.x = floor( vProjectOrigin.x * 512.0f ) / 512.0f;
vFlooredPosition.y = floor( vProjectOrigin.y * 512.0f ) / 512.0f;
vFlooredPosition.z = floor( vProjectOrigin.z * 512.0f ) / 512.0f;
#else
Vector vFlooredPosition = vProjectOrigin;
#endif
#if defined( GAME_DLL )
OnPreProjected();
#endif
Vector vOldOrigin = GetAbsOrigin();
QAngle qModAngles; //SendProxy_Angles will perform this operation on the angles, making them differ by either extremely small values, or 360 degrees (-90 == 270 angularly, but not from a precision standpoint)
qModAngles.x = anglemod( qProjectAngles.x );
qModAngles.y = anglemod( qProjectAngles.y );
qModAngles.z = anglemod( qProjectAngles.z );
#if defined( CLIENT_DLL )
Assert( bShouldSpawn == false );
SetNetworkOrigin( vFlooredPosition );
SetNetworkAngles( qModAngles );
#else
SetOwnerEntity( pParentProjector );
SetLocalOrigin( vFlooredPosition );
SetLocalAngles( qModAngles );
#endif
//EASY_DIFFPRINT( this, "CBaseProjectedEntity::RecursiveProjection() %f %f %f\n", XYZ( vFlooredPosition ) );
m_iMaxRemainingRecursions = iRemainingProjections;
#if defined( GAME_DLL )
if( bShouldSpawn )
{
DispatchSpawn( this );
}
else
#endif
{
FindProjectedEndpoints();
}
if( pExitPortal )
{
SetSourcePortal( pExitPortal );
#if defined( GAME_DLL ) && !defined( _GAMECONSOLE ) && !defined( NO_STEAM )
CWeaponPortalgun *pPortalGun = dynamic_cast<CWeaponPortalgun*>( pExitPortal->m_hPlacedBy.Get() );
if ( pPortalGun != NULL )
{
Vector vecForward, vecRight, vecUp;
AngleVectors( qModAngles, &vecForward, &vecRight, &vecUp );
g_PortalGameStats.Event_TractorBeam_Project( pExitPortal->m_ptOrigin, vecForward , ToPortalPlayer( pPortalGun->GetOwner() ) );
}
#endif
}
OnProjected();
#if defined( CLIENT_DLL )
if( cl_predict_projected_entities.GetBool() == false )
return;
#endif
// If this hits a portal, reorient through it.
// We create a new ent to do this.
if ( iRemainingProjections > 1 )
{
// If there is a portal within a small distance of our end point, reorient
CPortal_Base2D* pHitPortal = NULL;
Vector vNewProjectedEntityOrigin;
QAngle qNewProjectedEntityAngles;
bool bIsHittingPortal = IsHittingPortal( &vNewProjectedEntityOrigin, &qNewProjectedEntityAngles, &pHitPortal );
SetHitPortal( pHitPortal );
if ( bIsHittingPortal && pHitPortal && pHitPortal->IsActivedAndLinked() )
{
CPortal_Base2D *pNewExitPortal = pHitPortal->m_hLinkedPortal.Get();
bool bCreateNew = (m_hChildSegment.Get() == NULL);
#if defined( GAME_DLL ) //TODO: Set dormant on the client
if( bCreateNew )
{
m_hChildSegment = CreateNewProjectedEntity();
}
#else
if( !bCreateNew )
#endif
{
m_hChildSegment.Get()->RecursiveProjection( bCreateNew, pParentProjector, pNewExitPortal, vNewProjectedEntityOrigin, qNewProjectedEntityAngles, iRemainingProjections - 1, bDisablePlacementHelper );
}
}
// FIXME: Bring this back for DLC2
//else if ( engine->HasPaintmap() )
//{
// //TestForReflectPaint();
//}
else if( m_hChildSegment.Get() != NULL )
{
#if defined( GAME_DLL ) //TODO: Set dormant on the client
UTIL_Remove( m_hChildSegment.Get() );
m_hChildSegment = NULL;
#endif
}
}
#if defined( GAME_DLL )
if( !bDisablePlacementHelper )
{
m_bCreatePlacementHelper = true;
bool bCreatePlacement = m_hPlacementHelper.Get() == NULL;
if( bCreatePlacement )
{
m_hPlacementHelper = (CInfoPlacementHelper *) CreateEntityByName( "info_placement_helper" );
}
PlacePlacementHelper( m_hPlacementHelper );
if( bCreatePlacement )
{
DispatchSpawn( m_hPlacementHelper );
}
}
else
{
m_bCreatePlacementHelper = false;
}
PhysicsTouchTriggers( NULL );
#endif
// Projected entities should probably reflect in water because they are noticeable
AddEffects( EF_MARKED_FOR_FAST_REFLECTION );
}
//--------------------------------------------------------------------------------------------------
//
//--------------------------------------------------------------------------------------------------
void CBaseProjectedEntity::TestForProjectionChanges( void )
{
#if defined( CLIENT_DLL )
if( cl_predict_projected_entities.GetBool() == false )
return;
#endif
Vector vNewPosition;
QAngle qNewAngles;
CPortal_Base2D* pHitPortal = NULL;
bool bIsHittingPortal = IsHittingPortal( &vNewPosition, &qNewAngles, &pHitPortal );
CBaseProjectedEntity *pChild = m_hChildSegment.Get();
//if( pChild )
//{
// EASY_DIFFPRINT( pChild, "CBaseProjectedEntity::TestForProjectionChanges() %i %s %i", entindex(), bIsHittingPortal ? "true" : "false", pHitPortal ? pHitPortal->entindex() : -1 );
//}
// Lost the portal we were hitting: Fizzle all children
if ( !bIsHittingPortal || (pHitPortal && !pHitPortal->IsActivedAndLinked()) )
{
SetHitPortal( NULL );
#if defined( GAME_DLL ) //TODO: Set dormant on the client
float flDistSqr = GetEndPoint().DistToSqr( vNewPosition );
if ( flDistSqr > 0.1f )
{
FindProjectedEndpoints();
OnProjected();
}
#endif
// FIXME: Bring this back for DLC2
// check for reflect paint
/*if ( engine->HasPaintmap() )
{
TestForReflectPaint();
}*/
#if defined( GAME_DLL )
/*else*/ if( pChild )
{
UTIL_Remove( pChild );
m_hChildSegment = NULL;
}
#endif
}
#if defined( GAME_DLL )
else if( pHitPortal->IsActivedAndLinked() && (DidRedirectionPortalMove( pHitPortal ) || ((pChild == NULL) && (m_iMaxRemainingRecursions > 0))) )
#else
else if( pHitPortal->IsActivedAndLinked() && DidRedirectionPortalMove( pHitPortal ) && (pChild != NULL) )
#endif
{
#if defined( CLIENT_DLL )
if( GetPredictable() )
#endif
{
Vector vPrevStart = GetStartPoint();
Vector vPrevEnd = GetEndPoint();
FindProjectedEndpoints();
if( (vPrevStart != GetStartPoint()) || (vPrevEnd != GetEndPoint()) )
{
OnProjected();
}
SetHitPortal( pHitPortal );
}
//reproject child portal
bool bCreateNew = (pChild == NULL);
#if defined( GAME_DLL )
if( bCreateNew )
{
m_hChildSegment = pChild = CreateNewProjectedEntity();
}
#else
if( !bCreateNew && pChild->GetPredictable() )
#endif
{
pChild->RecursiveProjection( bCreateNew, (CBaseProjector *)GetOwnerEntity(), pHitPortal->m_hLinkedPortal.Get(), vNewPosition, qNewAngles, m_iMaxRemainingRecursions - 1, m_bCreatePlacementHelper );
}
}
#if defined( GAME_DLL ) //server propogates down the chain. Client evaluates each entity separately since it's not guaranteed to know about them all
else if( pChild )
{
pChild->TestForProjectionChanges();
}
#endif
}
//--------------------------------------------------------------------------------------------------
// Test for perturbation of the portal this projected entity is redirecting through
// if true, this entity's owning projector will rebuild all projected ents after this one
//--------------------------------------------------------------------------------------------------
bool CBaseProjectedEntity::DidRedirectionPortalMove( CPortal_Base2D* pPortal )
{
if ( !pPortal )
return true;
// remote portal must exist to project through
if ( pPortal->IsActivedAndLinked() == false )
return true;
if ( pPortal != m_hHitPortal.Get() )
return true;
CBaseProjectedEntity *pChild = m_hChildSegment;
if( !pChild )
return true;
// close portal moved
if ( VectorsAreEqual( pChild->m_vecSourcePortalRemoteCenter, pPortal->m_ptOrigin ) == false )
{
//EASY_DIFFPRINT( pChild, "CBaseProjectedEntity::DidRedirectionPortalMove() hit portal moved" );
return true;
}
// close portal rotated
if ( QAnglesAreEqual( pChild->m_vecSourcePortalRemoteAngle, pPortal->m_qAbsAngle ) == false )
{
return true;
}
//EASY_DIFFPRINT( pChild, "%f %f %f %f %f %f", XYZ( (Vector)pChild->m_vecSourcePortalCenter ), XYZ( pPortal->m_hLinkedPortal->m_ptOrigin ) );
// remote portal moved
if ( VectorsAreEqual( pChild->m_vecSourcePortalCenter, pPortal->m_hLinkedPortal->m_ptOrigin ) == false )
{
//EASY_DIFFPRINT( pChild, "CBaseProjectedEntity::DidRedirectionPortalMove() remote portal moved" );
return true;
}
// remote portal rotated
if ( QAnglesAreEqual( pChild->m_vecSourcePortalAngle, pPortal->m_hLinkedPortal->m_qAbsAngle ) == false )
{
return true;
}
return false;
}
//--------------------------------------------------------------------------------------------------
// Project from origin to solid in the direction of our forward
//--------------------------------------------------------------------------------------------------
void CBaseProjectedEntity::FindProjectedEndpoints( void )
{
#if defined( GAME_DLL )
QAngle qAngles = GetLocalAngles();
#else
QAngle qAngles = GetNetworkAngles();
#endif
// Get current orientation
Vector vecForward, vecRight, vecUp;
AngleVectors( qAngles, &vecForward, &vecRight, &vecUp );
Vector mins, maxs;
GetProjectionExtents( mins, maxs );
#if defined( GAME_DLL )
Vector vStart = GetLocalOrigin();
#else
Vector vStart = GetNetworkOrigin();
#endif
Vector vRayPos = vStart + PROJECTEDENTITY_TRACE_OFFSET * vecForward;
Ray_t ray;
ray.Init( vRayPos, vRayPos + vecForward*PROJECTOR_MAX_LENGTH, mins, maxs );
trace_t tr;
CTraceFilterSimpleClassnameList traceFilter( this, COLLISION_GROUP_NONE );
UTil_ProjectedEntity_Trace_Filter( &traceFilter );
UTIL_TraceRay( ray, MASK_SOLID_BRUSHONLY, &traceFilter, &tr );
// Should up the max trace dist if this hits
Assert ( tr.DidHit() );
m_vecStartPoint = vStart;
m_vecEndPoint = tr.endpos + tr.plane.normal * (PROJECTION_END_POINT_EPSILON); // Move a tiny bit off the hit surface so there's no physical overlap
}
//--------------------------------------------------------------------------------------------------
//
//--------------------------------------------------------------------------------------------------
void CBaseProjectedEntity::SetHitPortal( CPortal_Base2D* pPortal )
{
m_hHitPortal = pPortal;
if ( pPortal )
{
Assert( pPortal->IsActivedAndLinked() );
if ( pPortal->IsActivedAndLinked() )
{
#if defined( GAME_DLL )
// Listen for this portal to move
//pPortal->AddPortalEventListener( this );
#endif
}
}
}
CPortal_Base2D* CBaseProjectedEntity::GetHitPortal( void )
{
return m_hHitPortal.Get();
}
//--------------------------------------------------------------------------------------------------
//
//--------------------------------------------------------------------------------------------------
void CBaseProjectedEntity::SetSourcePortal( CPortal_Base2D* pPortal )
{
Assert( pPortal && pPortal->IsActivedAndLinked() );
m_hSourcePortal.Set( pPortal );
#if defined( CLIENT_DLL )
SetPredictionEligible( pPortal != NULL );
#endif
if( pPortal )
{
m_vecSourcePortalCenter = pPortal->m_ptOrigin;
m_vecSourcePortalRemoteCenter = pPortal->m_hLinkedPortal->m_ptOrigin;
m_vecSourcePortalAngle = pPortal->m_qAbsAngle;
m_vecSourcePortalRemoteAngle = pPortal->m_hLinkedPortal->m_qAbsAngle;
}
else
{
m_vecSourcePortalCenter = vec3_origin;
m_vecSourcePortalRemoteCenter = vec3_origin;
m_vecSourcePortalAngle = vec3_angle;
m_vecSourcePortalRemoteAngle = vec3_angle;
}
if( pPortal && pPortal->GetSimulatingPlayer() )
{
SetPlayerSimulated( pPortal->GetSimulatingPlayer() );
}
else
{
UnsetPlayerSimulated();
}
}
CPortal_Base2D* CBaseProjectedEntity::GetSourcePortal( void )
{
return m_hSourcePortal.Get();
}
//--------------------------------------------------------------------------------------------------
// Specify the extents to use for the projection trace
//--------------------------------------------------------------------------------------------------
void CBaseProjectedEntity::GetProjectionExtents( Vector &outMins, Vector &outMaxs )
{
outMins = outMaxs = vec3_origin;
}
void CBaseProjectedEntity::OnProjected( void )
{
AddEffects( EF_NOINTERP );
#if defined( CLIENT_DLL )
SetNetworkOrigin( GetStartPoint() );
PreDataChanged.vStartPoint = GetStartPoint();
PreDataChanged.vEndPoint = GetEndPoint();
PreDataChanged.qAngles = GetNetworkAngles();
#endif
}
void CBaseProjectedEntity::TestForReflectPaint( void )
{
Ray_t ray;
// make ray twice longer than the projected length, so the trace will actually hit something
ray.Init( GetStartPoint(), GetStartPoint() + 2.f * ( GetEndPoint() - GetStartPoint() ) );
CTraceFilterSimpleClassnameList traceFilter( this, COLLISION_GROUP_NONE );
UTil_ProjectedEntity_Trace_Filter( &traceFilter );
trace_t tr;
UTIL_ClearTrace( tr );
UTIL_TraceRay( ray, MASK_SOLID_BRUSHONLY, &traceFilter, &tr );
Vector vDir, vNewProjectedEntityOrigin;
if ( UTIL_Paint_Reflect( tr, vNewProjectedEntityOrigin, vDir ) )
{
// rotate psuedo up vector
Vector vOldDir, vOldUp;
GetVectors( &vOldDir, NULL, &vOldUp );
Vector vRotAxis = CrossProduct( vOldDir, vDir );
float flAngleBetween = RAD2DEG( acos( clamp( DotProduct( vOldDir, vDir ), -1.f, 1.f ) ) );
matrix3x4_t matRotation;
MatrixBuildRotationAboutAxis( vRotAxis, flAngleBetween, matRotation );
Vector vNewUp;
VectorRotate( vOldUp, matRotation, vNewUp );
QAngle qNewProjectedEntityAngles;
VectorAngles( vDir, vNewUp, qNewProjectedEntityAngles );
//reproject child portal
bool bCreateNew = (m_hChildSegment.Get() == NULL);
#if defined( GAME_DLL )
if( bCreateNew )
{
m_hChildSegment = CreateNewProjectedEntity();
}
#else
if( !bCreateNew )
#endif
{
m_hChildSegment.Get()->RecursiveProjection( bCreateNew, (CBaseProjector *)GetOwnerEntity(), NULL, vNewProjectedEntityOrigin, qNewProjectedEntityAngles, m_iMaxRemainingRecursions - 1, m_bCreatePlacementHelper );
}
}
#if defined( GAME_DLL )
else if ( m_hChildSegment.Get() != NULL )
{
UTIL_Remove( m_hChildSegment.Get() );
m_hChildSegment = NULL;
}
#endif
}