cstrike15_src/game/shared/portal/trigger_tractorbeam_shared.cpp
2025-06-04 03:22:50 +02:00

670 lines
19 KiB
C++

//========= Copyright © 1996-2009, Valve Corporation, All rights reserved. ============//
//
//=============================================================================//
#include "cbase.h"
#include "trigger_tractorbeam_shared.h"
#include "in_buttons.h"
#include "portal_util_shared.h"
#include <functional>
#ifdef CLIENT_DLL
#include "c_portal_player.h"
#include "c_paintblob.h"
#include "portal_mp_gamerules.h"
#else
#include "portal_player.h"
#include "npc_portal_turret_floor.h"
#include "prop_weightedcube.h"
#include "cpaintblob.h"
#endif
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
extern ConVar sv_gravity;
ConVar tbeam_air_ctrl_threshold( "tbeam_air_ctrl_threshold", "20", FCVAR_REPLICATED | FCVAR_CHEAT );
CTractorBeam_Manager g_TractorBeamManager;
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTrigger_TractorBeam::EndTouch( CBaseEntity *pOther )
{
#ifdef GAME_DLL
if ( !PassesTriggerFilters( pOther ) )
return;
#endif
BaseClass::EndTouch( pOther );
#ifdef GAME_DLL
if ( FClassnameIs( pOther, "npc_portal_turret_floor" ) )
{
CNPC_Portal_FloorTurret *pTurret = assert_cast< CNPC_Portal_FloorTurret* >( pOther );
if ( pTurret )
{
pTurret->OnExitedTractorBeam();
}
}
else if ( UTIL_IsReflectiveCube( pOther ) )
{
CPropWeightedCube *pCube = assert_cast< CPropWeightedCube* >( pOther );
if ( pCube )
{
pCube->OnExitedTractorBeam();
}
}
#endif
bool bTravelingToLinkedTBeam = false;
if ( pOther->IsPlayer() )
{
CPortal_Player *pPlayer = static_cast< CPortal_Player* >( pOther );
if ( pPlayer )
{
if ( m_hProxyEntity.Get() )
{
// Lets see if the player is going through a portal
Vector vExtents = pPlayer->GetPlayerMaxs() - pPlayer->GetPlayerMins();
for ( int i=0; i<CPortal_Base2D_Shared::AllPortals.Count(); ++i )
{
CPortal_Base2D *pPortal = CPortal_Base2D_Shared::AllPortals[i];
if ( pPortal && UTIL_IsBoxIntersectingPortal( pPlayer->WorldSpaceCenter(), vExtents, pPortal ) )
{
// Is the tbeam is pushing us into the portal that we're traveling through?
CPortal_Base2D *pPortal2 = IsReversed() ? m_hProxyEntity->GetSourcePortal() : m_hProxyEntity->GetHitPortal();
if ( pPortal == pPortal2 || pPortal->GetLinkedPortal() == pPortal2 )
{
bTravelingToLinkedTBeam = true;
break;
}
}
}
}
pPlayer->SetLeaveTractorBeam( this, bTravelingToLinkedTBeam );
}
#ifdef GAME_DLL
if ( m_sndPlayerInBeam )
{
CSoundEnvelopeController::GetController().SoundFadeOut( m_sndPlayerInBeam, 0.5f );
}
#endif
}
else
{
#ifdef GAME_DLL
triggerevent_t event;
if ( PhysGetTriggerEvent( &event, this ) && event.pObject && m_pController )
{
event.pObject->Wake();
m_pController->DetachObject( event.pObject );
}
#else
//Warning( "client EndTouch %f %s %s\n", gpGlobals->curtime, prediction->InPrediction() ? "true" : "false", prediction->IsFirstTimePredicted() ? "First" : "Repredict" );
if( m_pController )
{
IPhysicsObject *pPhysObject = pOther->VPhysicsGetObject();
if( pPhysObject )
{
m_pController->DetachObject( pPhysObject );
}
}
#endif
EntityBeamHistory_t& beamHistory = g_TractorBeamManager.GetHistoryFromEnt( pOther );
beamHistory.LeaveBeam( this );
#ifdef GAME_DLL
if ( beamHistory.m_beams.Count() && beamHistory.m_beams.Head().m_hBeamHandle != NULL )
{
static_cast< CTrigger_TractorBeam* >( beamHistory.m_beams.Head().m_hBeamHandle.Get() )->ForceAttachEntity( pOther );
}
#endif
}
}
void CTrigger_TractorBeam::UpdateBeam( const Vector& vStartPoint, const Vector& vEndPoint, float flLinearForce )
{
CBaseEntity *pOwner = GetOwnerEntity();
if ( !pOwner )
return;
#if defined( GAME_DLL )
// We want to touch everything...
AddSpawnFlags( SF_TRIGGER_ALLOW_ALL );
#endif
if ( flLinearForce < 0 )
{
flLinearForce = fabs(flLinearForce);
#if defined( GAME_DLL )
SetAsReversed( true );
#endif
SetDirection( vEndPoint, vStartPoint );
}
else
{
#if defined( GAME_DLL )
SetAsReversed( false );
#endif
SetDirection( vStartPoint, vEndPoint );
}
Vector vStart = GetStartPoint();
Vector vEnd = GetEndPoint();
// Get our local vectors
Vector vDir = ( vEnd - vStart );
float flLength = vDir.NormalizeInPlace();
QAngle qBeamAngles;
VectorAngles( vDir, qBeamAngles );
// Setup our base (unrotated) bounding box
const float flHalfWidth = GetBeamRadius();
Vector vMins( 0, -flHalfWidth, -flHalfWidth );
Vector vMaxs( flLength, flHalfWidth, flHalfWidth );
// Clear any current physobject we have assigned to us
IPhysicsObject *pObject = VPhysicsGetObject();
if ( pObject )
{
// Untouch everything
pObject->RemoveTrigger();
// Recreate static physics box
VPhysicsDestroyObject();
}
// Create our object
if ( physenv )
{
IPhysicsObject *pPhysicsObject = PhysModelCreateOBB( this, vMins, vMaxs, m_vStart, qBeamAngles, true );
VPhysicsSetObject( pPhysicsObject );
pPhysicsObject->BecomeTrigger();
// make sure origin and angle are todate to compute world surrounding bounds
SetAbsOrigin( m_vStart );
SetAbsAngles( qBeamAngles );
CollisionProp()->SetCollisionBounds( vMins, vMaxs );
CollisionProp()->SetSurroundingBoundsType( USE_OBB_COLLISION_BOUNDS );
#ifdef CLIENT_DLL
// need to add the entity to partition tree so we can trace against this on the client
Vector vWorldMins, vWorldMaxs;
CollisionProp()->WorldSpaceSurroundingBounds( &vWorldMins, &vWorldMaxs );
::partition->ElementMoved( CollisionProp()->GetPartitionHandle(), vWorldMins, vWorldMaxs );
#endif
}
#if defined( GAME_DLL )
// Setup our direction and force data
SetLinearForce( vDir, flLinearForce );
SetLinearLimit( flLinearForce * 0.5f );
#endif
#if defined( GAME_DLL )
// Now wake everything up
WakeTouchingObjects();
#endif
#if defined( CLIENT_DLL )
// Deal with particle changes
CreateParticles();
#endif // CLIENT_DLL
RemoveAllBlobsFromBeam();
m_nLastUpdateFrame = gpGlobals->framecount;
}
int CTrigger_TractorBeam::GetLastUpdateFrame() const
{
return m_nLastUpdateFrame;
}
//------------------------------------------------------------------------------
// Purpose:
//------------------------------------------------------------------------------
void CTrigger_TractorBeam::SetDirection( const Vector &vStart, const Vector &vEnd )
{
// Cache these points for the server / client
m_vStart = vStart;
m_vEnd = vEnd;
}
Vector CTrigger_TractorBeam::GetForceDirection() const
{
Vector vecForceDir = GetEndPoint() - GetStartPoint();
return vecForceDir.Normalized();
}
ConVar tbeam_allow_player_struggle( "tbeam_allow_player_struggle", "0", FCVAR_REPLICATED | FCVAR_CHEAT );
ConVar tbeam_prevent_players_from_colliding( "tbeam_prevent_players_from_colliding", "1", FCVAR_REPLICATED | FCVAR_CHEAT );
//------------------------------------------------------------------------------
// Apply the forces to the entity
//------------------------------------------------------------------------------
IMotionEvent::simresult_e CTrigger_TractorBeam::Simulate( IPhysicsMotionController *pController, IPhysicsObject *pObject, float deltaTime, Vector &linear, AngularImpulse &angular )
{
if ( m_bDisabled )
return SIM_NOTHING;
linear.Init();
angular.Init();
// Don't affect things held by the player
if ( !pObject || (pObject->GetGameFlags() & FVPHYSICS_PLAYER_HELD) )
return SIM_NOTHING;
// Get our actual game entity
CBaseEntity *pEntity = static_cast<CBaseEntity *>(pObject->GetGameData());
CalculateFrameMovement( pObject, pEntity, deltaTime, linear, angular );
return SIM_GLOBAL_ACCELERATION;
}
void CTrigger_TractorBeam::CalculateFrameMovement( IPhysicsObject *pObject, CBaseEntity *pEntity, float deltaTime, Vector &linear, AngularImpulse &angular )
{
// We want to influence entities to rest at our "middles" so that they pass through portals and are predictable.
// Here we perturb the position of the entity in question to move it towards the "middle line" of our beam.
Vector vWorldPos;
Vector vel;
AngularImpulse angVel;
if ( pObject )
{
pObject->GetPosition( &vWorldPos, NULL );
pObject->GetVelocity( &vel, &angVel );
}
else
{
vWorldPos = pEntity->WorldSpaceCenter();
vel = pEntity->GetAbsVelocity();
angVel.Init();
}
const bool bIsPlayer = pEntity->IsPlayer();
// Find the direction of travel
Vector vecForceDir = GetForceDirection();
float flLinearScale = 1.0f;
Vector vCenteringPos = vWorldPos + vel * deltaTime * 5.0f; // Use the position that we're heading toward to dampen oscelation as we approach the center
Vector vMidPoint;
float flPathPerc; // How far along the beam we are
CalcClosestPointOnLineSegment( vCenteringPos, m_vStart, m_vEnd, vMidPoint, &flPathPerc );
CBasePlayer *pPlayer = NULL;
if ( bIsPlayer )
{
pPlayer = ToBasePlayer( pEntity );
// Players need to decelerate when they reach the end of the trigger (unless they're going to go through a portal!)
bool bSourcePortal = m_hProxyEntity.Get() ? m_hProxyEntity->GetSourcePortal() != NULL : false;
bool bHitPortal = m_hProxyEntity.Get() ? m_hProxyEntity->GetHitPortal() != NULL : false;
bool bHeadingTowardsPortal = ( m_bReversed ) ? (bool) bSourcePortal : (bool) bHitPortal;
// Players also need to decelerate if they'll bump into each other
bool bMayBumpIntoOtherPlayerInTbeam = false;
float flPlayerBlockingPathPerc = 0.0f;
float flBlockedBeamLength = 0.0f;
if( tbeam_prevent_players_from_colliding.GetBool() && GameRules()->IsMultiplayer() )
{
CPortal_Player const* pOtherPlayer = assert_cast<CPortal_Player*>( UTIL_OtherPlayer( pPlayer ) );
if( pOtherPlayer && pOtherPlayer->GetTractorBeam() == this )
{
// Compute the percentage along the beam of the other player
Vector const vOtherCenteringPos = pOtherPlayer->WorldSpaceCenter() + pOtherPlayer->GetAbsVelocity() * deltaTime * 5.0f;
Vector vOtherClosest;
float flOtherPathPerc;
CalcClosestPointOnLineSegment( vOtherCenteringPos, m_vStart, m_vEnd, vOtherClosest, &flOtherPathPerc );
// This player might bump into the other if she is behind the other player.
// If they're both at the same point in the beam, slow one down temporarily.
bMayBumpIntoOtherPlayerInTbeam = flPathPerc == flOtherPathPerc ? pPlayer->entindex() > pOtherPlayer->entindex() : flPathPerc < flOtherPathPerc;
// Reuse the code below to slow down the player. Essentially, if both players are in
// the same beam, we're treating the player that's in front as the end point of the
// beam for the player that's behind.
if( bMayBumpIntoOtherPlayerInTbeam )
{
float const PLAYER_DISTANCE_BUFFER = 18.0f; // Approximately how far the gun can extend outside the AABB
Vector temp;
Vector const end = vOtherClosest - PLAYER_DISTANCE_BUFFER * vecForceDir;
CalcClosestPointOnLineSegment( vCenteringPos, m_vStart, end, temp, &flPlayerBlockingPathPerc );
flBlockedBeamLength = (end - m_vStart).Length();
}
}
}
if ( (bHeadingTowardsPortal == false && !m_bDisablePlayerMove) || bMayBumpIntoOtherPlayerInTbeam )
{
// Find the player's "width" in the direction travel (different when moving up/down to left/right)
Vector mins, maxs;
pEntity->CollisionProp()->NormalizedToCollisionSpace( vec3_origin, &mins );
pEntity->CollisionProp()->NormalizedToCollisionSpace( vecForceDir, &maxs );
float flRadius = ( maxs - mins ).Length();
// Find where we are along the length of the beam
float flBeamLength = bMayBumpIntoOtherPlayerInTbeam ? flBlockedBeamLength : VectorLength( (Vector)m_vEnd - (Vector)m_vStart );
float flEndPerc = ( flRadius / flBeamLength );
float flRemapPathPerc = bMayBumpIntoOtherPlayerInTbeam ? flPlayerBlockingPathPerc : flPathPerc;
// If we're within that last period, we need to scale down the linear force
if ( flRemapPathPerc >= flEndPerc )
{
flLinearScale = RemapValClamped( flRemapPathPerc, (1.0f-(flEndPerc*2.0f)), (1.0f-flEndPerc), 1.0f, 0.0f );
}
}
}
// Apply our constant directional force
VectorMA( linear, ((m_linearForce*2.0f) * flLinearScale), vecForceDir, linear );
// Deal with limiting velocity
if ( HasAirDensity() || HasLinearLimit() || HasLinearScale() || HasAngularLimit() || HasAngularScale() )
{
vel += linear * deltaTime; // account for gravity scale
Vector unitVel = vel;
Vector unitAngVel = angVel;
float speed = VectorNormalize( unitVel );
float angSpeed = VectorNormalize( unitAngVel );
float speedScale = 0.0;
float angSpeedScale = 0.0;
if ( HasAirDensity() && pObject )
{
float linearDrag = -0.5 * m_addAirDensity * pObject->CalculateLinearDrag( unitVel ) * deltaTime;
if ( linearDrag < -1 )
{
linearDrag = -1;
}
speedScale += linearDrag / deltaTime;
float angDrag = -0.5 * m_addAirDensity * pObject->CalculateAngularDrag( unitAngVel ) * deltaTime;
if ( angDrag < -1 )
{
angDrag = -1;
}
angSpeedScale += angDrag / deltaTime;
}
if ( HasLinearLimit() && speed > (m_linearLimit*flLinearScale) )
{
float flDeltaVel = ((GetLinearLimit()*flLinearScale)- speed) / deltaTime;
VectorMA( linear, flDeltaVel, unitVel, linear );
}
if ( HasAngularLimit() && angSpeed > m_angularLimit )
{
angular += ((m_angularLimit - angSpeed)/deltaTime) * unitAngVel;
}
if ( HasLinearScale() )
{
speedScale = ( (speedScale+1) * m_linearScale ) - 1;
}
if ( HasAngularScale() )
{
angSpeedScale = ( (angSpeedScale+1) * m_angularScale ) - 1;
}
linear += vel * speedScale;
angular += angVel * angSpeedScale;
}
// Push us slowly towards the center and keep us there
Vector vShoveDir = ( vMidPoint - vCenteringPos );
float flDistFromCenter = VectorNormalize( vShoveDir );
float flPerc = RemapValClamped( flDistFromCenter, 0, 32.0f, 0.0f, 1.0f );
if ( pEntity && bIsPlayer )
{
bool bPlayerAirControlling = false;
Vector vForward, vRight, vUp;
AngleVectors( pEntity->EyeAngles(), &vForward, &vRight, &vUp );
Vector vSubDir = vec3_origin;
float flAirControlMod = 1.0f;
const CUserCmd *ucmd = pPlayer->GetLastUserCommand();
if ( ucmd && !m_bDisablePlayerMove )
{
vSubDir = ( vForward * ucmd->forwardmove ) + ( vRight * ucmd->sidemove ) + ( vUp * ucmd->upmove );
bPlayerAirControlling = ( VectorNormalize( vSubDir ) > tbeam_air_ctrl_threshold.GetFloat() );
// Don't let them swim upstream
float flTravelDot = DotProduct( vSubDir, vecForceDir );
// This is an old deprecated way of letting the player move through tractor beams
if ( tbeam_allow_player_struggle.GetBool() )
{
if ( flTravelDot < 0.0f )
{
vSubDir -= flTravelDot * vecForceDir;
}
// If the beam is vertical, don't let players' velocities get limited by looking down
if ( fabs( vecForceDir.z ) > DOT_45DEGREE )
{
VectorNormalize( vSubDir );
}
}
else
{
// Clip the movement so they can't swim up or down the stream
vSubDir -= flTravelDot * vecForceDir;
if ( vSubDir.z > 0.0f && vCenteringPos.z - vMidPoint.z > 30.0f )
{
// Don't let them climb up above the tbeam
vSubDir.z = 0.0f;
}
// Find out how much we're going to throttle their movement speed based on their view direction
flAirControlMod = RemapValClamped( fabs(flTravelDot), 1.0f, DOT_30DEGREE, 0.0f, 1.0f );
}
}
if ( m_bDisablePlayerMove )
{
pPlayer->ForceButtons( IN_DUCK );
pPlayer->SetGroundEntity( NULL );
}
if ( bPlayerAirControlling )
{
if ( tbeam_allow_player_struggle.GetBool() )
{
linear += vSubDir * 64.0f / deltaTime;
}
else
{
Vector vShoveAdd = vShoveDir * ( ( 24.0f * flPerc ) / deltaTime ) * flLinearScale;
// If we don't allow them to struggle against the beam, then limit them
if ( tbeam_allow_player_struggle.GetBool() == false )
{
vShoveAdd += vSubDir * ( ( 84.0f / deltaTime ) * flAirControlMod );
}
linear += vShoveAdd;
}
}
else
{
linear += vShoveDir * ( ( 24.0f * flPerc ) / deltaTime ) * flLinearScale;
}
// Set the player up to move this way
if ( pEntity->GetFlags() & FL_ONGROUND )
{
pEntity->SetGroundEntity( NULL );
Vector origin = pEntity->GetAbsOrigin();
origin.z += 1.0f;
pEntity->SetAbsOrigin( origin );
pEntity->AddFlag( FL_BASEVELOCITY );
pEntity->SetBaseVelocity( Vector( 0, 0, 1000 ) );
linear.z += 5000.0f;
}
#if defined( DEBUG_MOTION_CONTROLLERS )
((CBasePlayer *)pEntity)->m_Debug_LinearAccel = linear;
#endif
}
else
{
linear += vShoveDir * ( ( 16.0f * flPerc ) / deltaTime );
linear.z -= (m_gravityScale-1) * sv_gravity.GetFloat();
}
}
float CTrigger_TractorBeam::GetLinearLimit()
{
if ( m_linearLimitTime == 0.0f )
return m_linearLimit;
float dt = gpGlobals->curtime - m_linearLimitStartTime;
if ( dt >= m_linearLimitTime )
{
m_linearLimitTime = 0.0;
return m_linearLimit;
}
dt /= m_linearLimitTime;
float flLimit = RemapVal( dt, 0.0f, 1.0f, m_linearLimitStart, m_linearLimit );
return flLimit;
}
struct ShouldDeleteBlob_t : std::unary_function< CPaintBlob*, bool >
{
inline bool operator()( const CPaintBlob* pBlob ) const
{
return pBlob->ShouldDeleteThis();
}
};
// Remove dead blobs from the list
void CTrigger_TractorBeam::RemoveDeadBlobs()
{
if ( m_blobs.Count() == 0 )
return;
CPaintBlob** begin = GetBegin( m_blobs );
CPaintBlob** end = GetEnd( m_blobs );
CPaintBlob** middle = std::partition( begin, end, ShouldDeleteBlob_t() );
int numRemoved = middle - begin;
m_blobs.RemoveMultipleFromHead( numRemoved );
}
struct IsBlobInSameBeam_t : std::unary_function< CPaintBlob*, bool >
{
IsBlobInSameBeam_t( CTrigger_TractorBeam* pBeam ) : m_pBeam( pBeam )
{
}
inline bool operator()( const CPaintBlob* pBlob ) const
{
return ( m_pBeam == pBlob->GetCurrentBeam() );
}
CTrigger_TractorBeam *m_pBeam;
};
// remove blobs that change to different beams
void CTrigger_TractorBeam::RemoveChangedBeamBlobs()
{
if ( m_blobs.Count() == 0 )
return;
CPaintBlob** begin = GetBegin( m_blobs );
CPaintBlob** end = GetEnd( m_blobs );
CPaintBlob** middle = std::partition( begin, end, IsBlobInSameBeam_t( this ) );
int numRemoved = end - middle;
m_blobs.RemoveMultipleFromTail( numRemoved );
}
void CTrigger_TractorBeam::RemoveAllBlobsFromBeam()
{
for ( int i=0; i<m_blobs.Count(); ++i )
{
m_blobs[i]->SetTractorBeam( NULL );
}
m_blobs.Purge();
}
void CTrigger_TractorBeam_Shared::RemoveDeadBlobsFromBeams()
{
for ( int i = 0; i < ITriggerTractorBeamAutoList::AutoList().Count(); ++i )
{
static_cast< CTrigger_TractorBeam* >( ITriggerTractorBeamAutoList::AutoList()[i] )->RemoveDeadBlobs();
}
}
void CTrigger_TractorBeam_Shared::RemoveBlobsFromPreviousBeams()
{
for ( int i = 0; i < ITriggerTractorBeamAutoList::AutoList().Count(); ++i )
{
static_cast< CTrigger_TractorBeam* >( ITriggerTractorBeamAutoList::AutoList()[i] )->RemoveChangedBeamBlobs();
}
}
bool EntityBeamHistory_t::IsDifferentBeam( CTrigger_TractorBeam* pNewBeam )
{
#ifdef CLIENT_DLL
// run the old behavior if PC client is playing against PS3 server
if ( PortalMPGameRules() && !PortalMPGameRules()->IsClientCrossplayingPCvsPC() )
{
if ( m_beams.Count() == 0 )
{
return true;
}
return m_beams.Head().m_hBeamHandle != pNewBeam;
}
#endif // CLIENT_DLL
int nLastUpdateFrame = pNewBeam->GetLastUpdateFrame();
for ( int i=0; i<m_beams.Count(); ++i )
{
if ( m_beams[i].m_hBeamHandle == pNewBeam )
{
return m_beams[i].m_nLastFrameUpdate != nLastUpdateFrame;
}
}
return true;
}