3253 lines
101 KiB
C++
3253 lines
101 KiB
C++
//===== Copyright © Valve Corporation, All rights reserved. ======//
|
|
//
|
|
// Purpose:
|
|
//
|
|
//=============================================================================//
|
|
|
|
#include "cbase.h"
|
|
#include "portal_grabcontroller_shared.h"
|
|
#include "portal_player_shared.h"
|
|
#include "portal_player_shared.h"
|
|
#include "vphysics/friction.h"
|
|
#include "collisionutils.h"
|
|
|
|
#if defined ( CLIENT_DLL )
|
|
#include "c_portal_player.h"
|
|
#include "prediction.h"
|
|
#include "c_breakableprop.h"
|
|
#include "c_npc_portal_turret_floor.h"
|
|
typedef C_NPC_Portal_FloorTurret CNPC_Portal_FloorTurret;
|
|
#else
|
|
#include "player_pickup.h"
|
|
#include "portal_player.h"
|
|
#include "props.h"
|
|
#include "physics_saverestore.h"
|
|
#include "weapon_portalgun.h"
|
|
#include "npc_portal_turret_floor.h"
|
|
#endif // CLIENT_DLL
|
|
|
|
bool TestIntersectionVsHeldObjectCollide( CBaseEntity *pHeldObject, Vector vHeldObjectTestOrigin, CBaseEntity *pOther );
|
|
|
|
ConVar g_debug_physcannon( "g_debug_physcannon", "0", FCVAR_REPLICATED | FCVAR_CHEAT );
|
|
ConVar debug_viewmodel_grabcontroller( "debug_viewmodel_grabcontroller", "0", FCVAR_REPLICATED | FCVAR_CHEAT );
|
|
ConVar physcannon_maxmass( "physcannon_maxmass", "250", FCVAR_REPLICATED | FCVAR_CHEAT );
|
|
ConVar hide_gun_when_holding( "hide_gun_when_holding", "0", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY );
|
|
ConVar player_held_object_offset_up_cube( "player_held_object_offset_up_cube", "-10", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY, "offest along the up axis for held objects.", true, -100.0f, true, 100.0f );
|
|
ConVar player_held_object_offset_up_cube_vm( "player_held_object_offset_up_cube_vm", "-20", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY, "offest along the up axis for held objects.", true, -100.0f, true, 100.0f );
|
|
ConVar player_held_object_offset_up_sphere( "player_held_object_offset_up_sphere", "-15", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY, "offest along the up axis for held objects.", true, -100.0f, true, 100.0f );
|
|
ConVar player_held_object_look_down_adjustment( "player_held_object_look_down_adjustment", "20", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY, "Moves the box forward when looking down (viewmodel held object only.", true, 0.0f, true, 100.0f );
|
|
ConVar player_held_object_distance( "player_held_object_distance", "15", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY, "Distance from player for held objects.", true, 0.0f, true, 100.0f );
|
|
ConVar player_held_object_distance_vm( "player_held_object_distance_vm", "65", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY, "Distance from player for held objects.", true, 0.0f, true, 160.0f );
|
|
ConVar player_held_object_offset_up_turret_vm( "player_held_object_offset_up_turret_vm", "-20", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY, "Offset for held turrets", true, -100.0f, true, 100.0f );
|
|
ConVar player_held_object_distance_turret_vm( "player_held_object_distance_turret_vm", "100", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY, "Offset for held turrets", true, 0.0f, true, 200.0f );
|
|
ConVar player_held_object_min_distance( "player_held_object_min_distance", "50", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY, "Minimum distance from player for held objects (used by viewmodel held objects).", true, 0.0f, true, 100.0f );
|
|
ConVar player_hold_object_in_column( "player_hold_object_in_column", "1", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY, "Hold object along a fixed column in front of player\n" );
|
|
ConVar player_hold_column_max_size( "player_hold_column_max_size", "96", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY, "Furthest distance an object can be when held in colmn mode." );
|
|
ConVar player_held_object_max_knock_magnitude( "player_held_object_max_knock_magnitude", "30", FCVAR_REPLICATED | FCVAR_CHEAT, "For viewmodel grab controller, max velocity magnitude squared to apply to knocked objects." );
|
|
ConVar player_held_object_max_throw_magnitude( "player_held_object_max_throw_magnitude", "60", FCVAR_REPLICATED | FCVAR_CHEAT, "For viewmodel grab controller, max velocity magnitude squared to apply to knocked objects." );
|
|
|
|
ConVar player_held_object_use_view_model( "player_held_object_use_view_model", "-1", FCVAR_REPLICATED | FCVAR_CHEAT, "Use clone models in the view model instead of physics simulated grab controller." );
|
|
ConVar player_held_object_collide_with_player( "player_held_object_collide_with_player", "0", FCVAR_REPLICATED | FCVAR_CHEAT, "Should held objects collide with players" );
|
|
ConVar player_held_object_debug_error( "player_held_object_debug_error", "0", FCVAR_REPLICATED | FCVAR_CHEAT, "Spew information on dropping objects due to error" );
|
|
|
|
ConVar player_held_object_transform_bump_ray( "player_held_object_transform_bump_ray", "0", FCVAR_REPLICATED | FCVAR_CHEAT );
|
|
|
|
#if defined ( GAME_DLL )
|
|
ConVar test_for_vphysics_clips_when_dropping( "test_for_vphysics_clips_when_dropping", "1", FCVAR_CHEAT );
|
|
#endif
|
|
|
|
#if USE_SLOWTIME
|
|
extern ConVar slowtime_speed;
|
|
#endif // USE_SLOWTIME
|
|
|
|
static void MatrixOrthogonalize( matrix3x4_t &matrix, int column )
|
|
{
|
|
Vector columns[3];
|
|
int i;
|
|
|
|
for ( i = 0; i < 3; i++ )
|
|
{
|
|
MatrixGetColumn( matrix, i, columns[i] );
|
|
}
|
|
|
|
int index0 = column;
|
|
int index1 = (column+1)%3;
|
|
int index2 = (column+2)%3;
|
|
|
|
columns[index2] = CrossProduct( columns[index0], columns[index1] );
|
|
columns[index1] = CrossProduct( columns[index2], columns[index0] );
|
|
VectorNormalize( columns[index2] );
|
|
VectorNormalize( columns[index1] );
|
|
MatrixSetColumn( columns[index1], index1, matrix );
|
|
MatrixSetColumn( columns[index2], index2, matrix );
|
|
}
|
|
|
|
#define SIGN(x) ( (x) < 0 ? -1 : 1 )
|
|
|
|
static QAngle AlignAngles( const QAngle &angles, float cosineAlignAngle )
|
|
{
|
|
matrix3x4_t alignMatrix;
|
|
AngleMatrix( angles, alignMatrix );
|
|
|
|
// NOTE: Must align z first
|
|
for ( int j = 3; --j >= 0; )
|
|
{
|
|
Vector vec;
|
|
MatrixGetColumn( alignMatrix, j, vec );
|
|
for ( int i = 0; i < 3; i++ )
|
|
{
|
|
if ( fabs(vec[i]) > cosineAlignAngle )
|
|
{
|
|
vec[i] = SIGN(vec[i]);
|
|
vec[(i+1)%3] = 0;
|
|
vec[(i+2)%3] = 0;
|
|
MatrixSetColumn( vec, j, alignMatrix );
|
|
MatrixOrthogonalize( alignMatrix, j );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
QAngle out;
|
|
MatrixAngles( alignMatrix, out );
|
|
return out;
|
|
}
|
|
|
|
|
|
static void TraceCollideAgainstBBox( const CPhysCollide *pCollide, const Vector &start, const Vector &end, const QAngle &angles, const Vector &boxOrigin, const Vector &mins, const Vector &maxs, trace_t *ptr )
|
|
{
|
|
physcollision->TraceBox( boxOrigin, boxOrigin + (start-end), mins, maxs, pCollide, start, angles, ptr );
|
|
|
|
if ( ptr->DidHit() )
|
|
{
|
|
ptr->endpos = start * (1-ptr->fraction) + end * ptr->fraction;
|
|
ptr->startpos = start;
|
|
ptr->plane.dist = -ptr->plane.dist;
|
|
ptr->plane.normal *= -1;
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Computes a local matrix for the player clamped to valid carry ranges
|
|
//-----------------------------------------------------------------------------
|
|
// when looking level, hold bottom of object 8 inches below eye level
|
|
#define PLAYER_HOLD_LEVEL_EYES -8
|
|
|
|
// when looking down, hold bottom of object 0 inches from feet
|
|
#define PLAYER_HOLD_DOWN_FEET 2
|
|
|
|
// when looking up, hold bottom of object 24 inches above eye level
|
|
#define PLAYER_HOLD_UP_EYES 24
|
|
|
|
// use a +/-30 degree range for the entire range of motion of pitch
|
|
#define PLAYER_LOOK_PITCH_RANGE 30
|
|
|
|
// player can reach down 2ft below his feet (otherwise he'll hold the object above the bottom)
|
|
#define PLAYER_REACH_DOWN_DISTANCE 24
|
|
|
|
void ComputePlayerMatrix( CBasePlayer *pPlayer, matrix3x4_t &out )
|
|
{
|
|
if ( !pPlayer )
|
|
return;
|
|
|
|
QAngle angles = pPlayer->EyeAngles();
|
|
Vector origin = pPlayer->EyePosition();
|
|
|
|
// 0-360 / -180-180
|
|
//angles.x = init ? 0 : AngleDistance( angles.x, 0 );
|
|
//angles.x = clamp( angles.x, -PLAYER_LOOK_PITCH_RANGE, PLAYER_LOOK_PITCH_RANGE );
|
|
angles.x = 0;
|
|
|
|
float feet = pPlayer->GetAbsOrigin().z + pPlayer->WorldAlignMins().z;
|
|
float eyes = origin.z;
|
|
float zoffset = 0;
|
|
// moving up (negative pitch is up)
|
|
if ( angles.x < 0 )
|
|
{
|
|
zoffset = RemapVal( angles.x, 0, -PLAYER_LOOK_PITCH_RANGE, PLAYER_HOLD_LEVEL_EYES, PLAYER_HOLD_UP_EYES );
|
|
}
|
|
else
|
|
{
|
|
zoffset = RemapVal( angles.x, 0, PLAYER_LOOK_PITCH_RANGE, PLAYER_HOLD_LEVEL_EYES, PLAYER_HOLD_DOWN_FEET + (feet - eyes) );
|
|
}
|
|
origin.z += zoffset;
|
|
angles.x = 0;
|
|
AngleMatrix( angles, origin, out );
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#if !defined CLIENT_DLL
|
|
BEGIN_SIMPLE_DATADESC( game_shadowcontrol_params_t )
|
|
|
|
DEFINE_FIELD( targetPosition, FIELD_POSITION_VECTOR ),
|
|
DEFINE_FIELD( targetRotation, FIELD_VECTOR ),
|
|
DEFINE_FIELD( maxAngular, FIELD_FLOAT ),
|
|
DEFINE_FIELD( maxDampAngular, FIELD_FLOAT ),
|
|
DEFINE_FIELD( maxSpeed, FIELD_FLOAT ),
|
|
DEFINE_FIELD( maxDampSpeed, FIELD_FLOAT ),
|
|
DEFINE_FIELD( dampFactor, FIELD_FLOAT ),
|
|
DEFINE_FIELD( teleportDistance, FIELD_FLOAT ),
|
|
|
|
END_DATADESC()
|
|
#endif
|
|
|
|
|
|
void CGrabController::RotateObject( CBasePlayer *pPlayer, float fRotAboutUp, float fRotAboutRight, bool bUseWorldUpInsteadOfPlayerUp )
|
|
{
|
|
if( (fRotAboutRight == 0.0f) && (fRotAboutUp == 0.0f) )
|
|
return; //no actual rotation to do
|
|
|
|
if ( !m_bHasPreferredCarryAngles )
|
|
{
|
|
Vector right, up;
|
|
QAngle playerAngles = pPlayer->EyeAngles();
|
|
AngleVectors( playerAngles, NULL, &right, &up );
|
|
|
|
if( bUseWorldUpInsteadOfPlayerUp )
|
|
up.Init( 0.0f, 0.0f, 1.0f );
|
|
|
|
Quaternion qRotationAboutUp;
|
|
AxisAngleQuaternion( up, fRotAboutUp, qRotationAboutUp );
|
|
|
|
Quaternion qRotationAboutRight;
|
|
AxisAngleQuaternion( right, fRotAboutRight, qRotationAboutRight );
|
|
|
|
Quaternion qRotation;
|
|
QuaternionMult( qRotationAboutRight, qRotationAboutUp, qRotation );
|
|
|
|
matrix3x4_t tmp;
|
|
ComputePlayerMatrix( pPlayer, tmp );
|
|
|
|
QAngle qTemp = TransformAnglesToWorldSpace( m_attachedAnglesPlayerSpace, tmp );
|
|
Quaternion qExisting;
|
|
AngleQuaternion( qTemp, qExisting );
|
|
Quaternion qFinal;
|
|
QuaternionMult( qRotation, qExisting, qFinal );
|
|
|
|
QuaternionAngles( qFinal, qTemp );
|
|
m_attachedAnglesPlayerSpace = TransformAnglesToLocalSpace( qTemp, tmp );
|
|
}
|
|
}
|
|
|
|
#if !defined CLIENT_DLL
|
|
BEGIN_SIMPLE_DATADESC( CGrabController )
|
|
|
|
DEFINE_EMBEDDED( m_shadow ),
|
|
|
|
DEFINE_FIELD( m_timeToArrive, FIELD_FLOAT ),
|
|
DEFINE_FIELD( m_errorTime, FIELD_FLOAT ),
|
|
DEFINE_FIELD( m_error, FIELD_FLOAT ),
|
|
DEFINE_FIELD( m_contactAmount, FIELD_FLOAT ),
|
|
DEFINE_AUTO_ARRAY( m_savedRotDamping, FIELD_FLOAT ),
|
|
DEFINE_AUTO_ARRAY( m_savedMass, FIELD_FLOAT ),
|
|
DEFINE_FIELD( m_flLoadWeight, FIELD_FLOAT ),
|
|
DEFINE_FIELD( m_bCarriedEntityBlocksLOS, FIELD_BOOLEAN ),
|
|
DEFINE_FIELD( m_bIgnoreRelativePitch, FIELD_BOOLEAN ),
|
|
DEFINE_FIELD( m_attachedEntity, FIELD_EHANDLE ),
|
|
DEFINE_FIELD( m_angleAlignment, FIELD_FLOAT ),
|
|
DEFINE_FIELD( m_vecPreferredCarryAngles, FIELD_VECTOR ),
|
|
DEFINE_FIELD( m_bHasPreferredCarryAngles, FIELD_BOOLEAN ),
|
|
DEFINE_FIELD( m_flDistanceOffset, FIELD_FLOAT ),
|
|
DEFINE_FIELD( m_attachedAnglesPlayerSpace, FIELD_VECTOR ),
|
|
DEFINE_FIELD( m_attachedPositionObjectSpace, FIELD_VECTOR ),
|
|
DEFINE_FIELD( m_bAllowObjectOverhead, FIELD_BOOLEAN ),
|
|
DEFINE_FIELD( m_bWasDragEnabled, FIELD_BOOLEAN ),
|
|
DEFINE_FIELD( m_hHoldingPlayer, FIELD_EHANDLE ),
|
|
DEFINE_FIELD( m_preVMModeCollisionGroup, FIELD_INTEGER ),
|
|
DEFINE_FIELD( m_prePickupCollisionGroup, FIELD_INTEGER ),
|
|
DEFINE_FIELD( m_oldTransmitState, FIELD_INTEGER ),
|
|
DEFINE_FIELD( m_bOldShadowState, FIELD_BOOLEAN ),
|
|
DEFINE_FIELD( m_hOldLightingOrigin, FIELD_EHANDLE ),
|
|
DEFINE_FIELD( m_flAngleOffset, FIELD_FLOAT ),
|
|
DEFINE_FIELD( m_flLengthOffset, FIELD_FLOAT ),
|
|
DEFINE_FIELD( m_flTimeOffset, FIELD_FLOAT ),
|
|
|
|
// Physptrs can't be inside embedded classes
|
|
// DEFINE_PHYSPTR( m_controller ),
|
|
|
|
END_DATADESC()
|
|
#endif
|
|
|
|
const float DEFAULT_MAX_ANGULAR = 360.0f * 10.0f;
|
|
const float REDUCED_CARRY_MASS = 1.0f;
|
|
|
|
#if defined( GAME_DLL )
|
|
CGrabController::CGrabController( void )
|
|
#else
|
|
CGrabController::CGrabController( void ) : m_iv_predictedRenderOrigin( "CGrabController::m_iv_predictedRenderOrigin" )
|
|
#endif
|
|
{
|
|
m_shadow.dampFactor = 1.0;
|
|
m_shadow.teleportDistance = 0;
|
|
// make this controller really stiff!
|
|
m_shadow.maxSpeed = 1000;
|
|
m_shadow.maxAngular = DEFAULT_MAX_ANGULAR;
|
|
m_shadow.maxDampSpeed = m_shadow.maxSpeed*2;
|
|
m_shadow.maxDampAngular = m_shadow.maxAngular;
|
|
m_errorTime = 0;
|
|
m_error = 0;
|
|
m_attachedEntity = NULL;
|
|
m_vecPreferredCarryAngles = vec3_angle;
|
|
m_bHasPreferredCarryAngles = false;
|
|
m_flDistanceOffset = 0;
|
|
m_bOldUsingVMGrabState = false;
|
|
|
|
#if defined( CLIENT_DLL )
|
|
m_iv_predictedRenderOrigin.Setup( &m_vHeldObjectRenderOrigin, INTERPOLATE_LINEAR_ONLY );
|
|
m_iv_predictedRenderOrigin.SetInterpolationAmount( TICK_INTERVAL );
|
|
#endif
|
|
}
|
|
|
|
CGrabController::~CGrabController( void )
|
|
{
|
|
if ( !IsUsingVMGrab() )
|
|
{
|
|
DetachEntity( false );
|
|
}
|
|
}
|
|
|
|
#if !defined CLIENT_DLL
|
|
void CGrabController::OnRestore()
|
|
{
|
|
if ( m_controller )
|
|
{
|
|
m_controller->SetEventHandler( this );
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void CGrabController::SetTargetPosition( const Vector &target, const QAngle &targetOrientation, bool bIsTeleport /*= false*/ )
|
|
{
|
|
m_shadow.targetPosition = target;
|
|
m_shadow.targetRotation = targetOrientation;
|
|
|
|
#if defined ( CLIENT_DLL )
|
|
m_timeToArrive = gpGlobals->frametime;
|
|
#else
|
|
if( bIsTeleport && PhysIsFinalTick() == false )
|
|
m_timeToArrive = gpGlobals->interval_per_tick;
|
|
else
|
|
m_timeToArrive = UTIL_GetSimulationInterval();
|
|
#endif
|
|
|
|
if ( !IsUsingVMGrab() )
|
|
{
|
|
CBaseEntity *pAttached = GetAttached();
|
|
if ( pAttached )
|
|
{
|
|
#if defined( GAME_DLL )
|
|
((CPortal_Player *)m_hHoldingPlayer.Get())->m_GrabControllerPersistentVars.m_vLastTargetPosition = target;
|
|
IPhysicsObject *pObj = pAttached->VPhysicsGetObject();
|
|
|
|
if ( pObj != NULL )
|
|
{
|
|
pObj->Wake();
|
|
}
|
|
else
|
|
{
|
|
DetachEntity( false );
|
|
}
|
|
#else
|
|
//TODO
|
|
//NDebugOverlay::BoxAngles( target, pAttached->CollisionProp()->OBBMins(), pAttached->CollisionProp()->OBBMaxs(), targetOrientation, 0, 255, 0, 25, 0.0f );
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
void CGrabController::GetTargetPosition( Vector *target, QAngle *targetOrientation )
|
|
{
|
|
if ( target )
|
|
*target = m_shadow.targetPosition;
|
|
|
|
if ( targetOrientation )
|
|
*targetOrientation = m_shadow.targetRotation;
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Output : float
|
|
//-----------------------------------------------------------------------------
|
|
float CGrabController::ComputeError()
|
|
{
|
|
if ( m_bOldUsingVMGrabState != IsUsingVMGrab() )
|
|
{
|
|
// Just switched states... give it a chance to correct itself before snapping
|
|
m_bOldUsingVMGrabState = IsUsingVMGrab();
|
|
m_errorTime = 0.0f;
|
|
}
|
|
|
|
if ( IsUsingVMGrab() )
|
|
{
|
|
// Don't break due to distance when in the viewmodel
|
|
return 0;
|
|
}
|
|
|
|
#if defined ( CLIENT_DLL )
|
|
// Server decides when grab controllers break.
|
|
return 0;
|
|
#endif
|
|
if ( m_errorTime <= 0 )
|
|
return 0;
|
|
|
|
CBaseEntity *pAttached = GetAttached();
|
|
if ( pAttached )
|
|
{
|
|
Vector pos;
|
|
IPhysicsObject *pObj = pAttached->VPhysicsGetObject();
|
|
|
|
if ( pObj )
|
|
{
|
|
pObj->GetShadowPosition( &pos, NULL );
|
|
|
|
float error = (m_shadow.targetPosition - pos).Length();
|
|
if ( m_errorTime > 0 )
|
|
{
|
|
if ( m_errorTime > 1 )
|
|
{
|
|
m_errorTime = 1;
|
|
}
|
|
float speed = error / m_errorTime;
|
|
if ( speed > m_shadow.maxSpeed )
|
|
{
|
|
error *= 0.5;
|
|
}
|
|
m_error = (1-m_errorTime) * m_error + error * m_errorTime;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DevMsg( "Object attached to Physcannon has no physics object\n" );
|
|
DetachEntity( false );
|
|
return 9999; // force detach
|
|
}
|
|
}
|
|
|
|
if ( pAttached->IsEFlagSet( EFL_IS_BEING_LIFTED_BY_BARNACLE ) )
|
|
{
|
|
m_error *= 3.0f;
|
|
}
|
|
|
|
|
|
// If held across a portal but not looking at the portal multiply error
|
|
CPortal_Player *pPortalPlayer = (CPortal_Player *)GetPlayerHoldingEntity( pAttached );
|
|
Assert( pPortalPlayer );
|
|
if ( pPortalPlayer->IsHeldObjectOnOppositeSideOfPortal() )
|
|
{
|
|
Vector forward, right, up;
|
|
QAngle playerAngles = pPortalPlayer->EyeAngles();
|
|
|
|
float pitch = AngleDistance(playerAngles.x,0);
|
|
playerAngles.x = clamp( pitch, -75, 75 );
|
|
AngleVectors( playerAngles, &forward, &right, &up );
|
|
|
|
Vector start = pPortalPlayer->Weapon_ShootPosition();
|
|
|
|
// If the player is upside down then we need to hold the box closer to their feet.
|
|
if ( up.z < 0.0f )
|
|
start += pPortalPlayer->GetViewOffset() * up.z;
|
|
if ( right.z < 0.0f )
|
|
start += pPortalPlayer->GetViewOffset() * right.z;
|
|
|
|
Ray_t rayPortalTest;
|
|
rayPortalTest.Init( start, start + forward * 256.0f );
|
|
|
|
if ( UTIL_IntersectRayWithPortal( rayPortalTest, pPortalPlayer->GetHeldObjectPortal() ) < 0.0f )
|
|
{
|
|
bool bHoldRayCrossesHeldPortal = false;
|
|
|
|
CPortal_Base2D *pHeldPortal = pPortalPlayer->GetHeldObjectPortal();
|
|
if( pHeldPortal && pHeldPortal->IsActivedAndLinked() )
|
|
{
|
|
//bring target position into local space
|
|
Vector vLocalTarget = pHeldPortal->m_hLinkedPortal->m_matrixThisToLinked * m_shadow.targetPosition;
|
|
Ray_t lastChanceRay;
|
|
lastChanceRay.Init( start, vLocalTarget );
|
|
float fLastChanceCloser = 2.0f;
|
|
bHoldRayCrossesHeldPortal = ( UTIL_Portal_FirstAlongRay( lastChanceRay, fLastChanceCloser ) == pHeldPortal );
|
|
}
|
|
|
|
if( !bHoldRayCrossesHeldPortal )
|
|
{
|
|
m_error *= 2.5f;
|
|
if ( player_held_object_debug_error.GetBool() )
|
|
{
|
|
engine->Con_NPrintf( 22, "Multiplying error due to portals" );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we've given ourselves extra error distance to allow object-player penetration,
|
|
// multiply that error if some obstruction gets in between the player and the object
|
|
if ( player_held_object_collide_with_player.GetBool() == false )
|
|
{
|
|
trace_t tr;
|
|
Ray_t ray;
|
|
CTraceFilterSkipTwoEntities traceFilter( pPortalPlayer, pAttached, COLLISION_GROUP_NONE );
|
|
Vector vObjectCenter = pAttached->GetAbsOrigin();
|
|
if ( pPortalPlayer->IsHeldObjectOnOppositeSideOfPortal() )
|
|
{
|
|
Assert ( pPortalPlayer->GetHeldObjectPortal() && pPortalPlayer->GetHeldObjectPortal()->GetLinkedPortal() );
|
|
if ( pPortalPlayer->GetHeldObjectPortal() && pPortalPlayer->GetHeldObjectPortal()->GetLinkedPortal() )
|
|
{
|
|
Vector tmp;
|
|
UTIL_Portal_PointTransform( pPortalPlayer->GetHeldObjectPortal()->GetLinkedPortal()->MatrixThisToLinked(), vObjectCenter, tmp );
|
|
vObjectCenter = tmp;
|
|
}
|
|
}
|
|
ray.Init( pPortalPlayer->EyePosition(), vObjectCenter );
|
|
UTIL_Portal_TraceRay( ray, MASK_SOLID, &traceFilter, &tr, false );
|
|
if ( tr.DidHit() )
|
|
{
|
|
m_error *= 3.0f;
|
|
if ( player_held_object_debug_error.GetBool() )
|
|
{
|
|
engine->Con_NPrintf( 23, "Multiplying error from obstruction" );
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( player_held_object_debug_error.GetBool() )
|
|
{
|
|
engine->Con_NPrintf( 24, "Error: %f Time: %f", m_error, m_errorTime );
|
|
}
|
|
|
|
m_errorTime = 0;
|
|
|
|
return m_error;
|
|
}
|
|
|
|
|
|
#define MASS_SPEED_SCALE 60
|
|
#define MAX_MASS 40
|
|
|
|
void CGrabController::ComputeMaxSpeed( CBaseEntity *pEntity, IPhysicsObject *pPhysics )
|
|
{
|
|
m_shadow.maxSpeed = 1000;
|
|
m_shadow.maxAngular = DEFAULT_MAX_ANGULAR;
|
|
|
|
// Compute total mass...
|
|
float flMass = PhysGetEntityMass( pEntity );
|
|
float flMaxMass = physcannon_maxmass.GetFloat();
|
|
if ( flMass <= flMaxMass )
|
|
return;
|
|
|
|
float flLerpFactor = clamp( flMass, flMaxMass, 500.0f );
|
|
flLerpFactor = SimpleSplineRemapVal( flLerpFactor, flMaxMass, 500.0f, 0.0f, 1.0f );
|
|
|
|
float invMass = pPhysics->GetInvMass();
|
|
float invInertia = pPhysics->GetInvInertia().Length();
|
|
|
|
float invMaxMass = 1.0f / MAX_MASS;
|
|
float ratio = invMaxMass / invMass;
|
|
invMass = invMaxMass;
|
|
invInertia *= ratio;
|
|
|
|
float maxSpeed = invMass * MASS_SPEED_SCALE * 200;
|
|
float maxAngular = invInertia * MASS_SPEED_SCALE * 360;
|
|
|
|
m_shadow.maxSpeed = Lerp( flLerpFactor, m_shadow.maxSpeed, maxSpeed );
|
|
m_shadow.maxAngular = Lerp( flLerpFactor, m_shadow.maxAngular, maxAngular );
|
|
}
|
|
|
|
|
|
QAngle CGrabController::TransformAnglesToPlayerSpace( const QAngle &anglesIn, CBasePlayer *pPlayer )
|
|
{
|
|
if ( m_bIgnoreRelativePitch )
|
|
{
|
|
matrix3x4_t test;
|
|
QAngle angleTest = pPlayer->EyeAngles();
|
|
angleTest.x = 0;
|
|
AngleMatrix( angleTest, test );
|
|
return TransformAnglesToLocalSpace( anglesIn, test );
|
|
}
|
|
return TransformAnglesToLocalSpace( anglesIn, pPlayer->EntityToWorldTransform() );
|
|
}
|
|
|
|
QAngle CGrabController::TransformAnglesFromPlayerSpace( const QAngle &anglesIn, CBasePlayer *pPlayer )
|
|
{
|
|
if ( m_bIgnoreRelativePitch )
|
|
{
|
|
matrix3x4_t test;
|
|
QAngle angleTest = pPlayer->EyeAngles();
|
|
|
|
Vector vUp, vRight, vForward;
|
|
CPortal_Player* pPortalPlayer = ToPortalPlayer( pPlayer );
|
|
Vector vPlayerUp = pPortalPlayer->GetPortalPlayerLocalData().m_Up;
|
|
AngleVectors( angleTest, &vForward, &vRight, &vUp );
|
|
vForward -= vPlayerUp * DotProduct( vPlayerUp, vForward );
|
|
VectorAngles( vForward, vUp, angleTest );
|
|
|
|
AngleMatrix( angleTest, test );
|
|
return TransformAnglesToWorldSpace( anglesIn, test );
|
|
}
|
|
return TransformAnglesToWorldSpace( anglesIn, pPlayer->EntityToWorldTransform() );
|
|
}
|
|
|
|
|
|
Vector CGrabController::TransformVectorToPlayerSpace( const Vector &vectorIn, CBasePlayer *pPlayer )
|
|
{
|
|
Vector vRet;
|
|
VectorIRotate( vectorIn, pPlayer->EntityToWorldTransform(), vRet );
|
|
return vRet;
|
|
}
|
|
|
|
Vector CGrabController::TransformVectorFromPlayerSpace( const Vector &vectorIn, CBasePlayer *pPlayer )
|
|
{
|
|
Vector vRet;
|
|
VectorRotate( vectorIn, pPlayer->EntityToWorldTransform(), vRet );
|
|
return vRet;
|
|
}
|
|
|
|
|
|
void CGrabController::AttachEntity( CBasePlayer *pPlayer, CBaseEntity *pEntity, IPhysicsObject *pPhys, bool bIsMegaPhysCannon, const Vector &vGrabPosition, bool bUseGrabPosition )
|
|
{
|
|
// If any other player is holding it, they need to drop it now!
|
|
/*for ( int i = 1; i <= gpGlobals->maxClients; i++ )
|
|
{
|
|
CBasePlayer *pOtherPlayer = UTIL_PlayerByIndex( i );
|
|
if ( pOtherPlayer == NULL || pOtherPlayer == pPlayer )
|
|
continue;
|
|
|
|
pOtherPlayer->ForceDropOfCarriedPhysObjects();
|
|
}*/
|
|
|
|
// play the impact sound of the object hitting the player
|
|
// used as feedback to let the player know he picked up the object
|
|
m_hHoldingPlayer = pPlayer;
|
|
|
|
CPortal_Player *pPortalPlayer = (CPortal_Player *)pPlayer;
|
|
#if !defined CLIENT_DLL
|
|
if ( !pPortalPlayer->m_bSilentDropAndPickup )
|
|
{
|
|
int hitMaterial = pPhys->GetMaterialIndex();
|
|
int playerMaterial = pPlayer->VPhysicsGetObject() ? pPlayer->VPhysicsGetObject()->GetMaterialIndex() : hitMaterial;
|
|
PhysicsImpactSound( pPlayer, pPhys, CHAN_STATIC, hitMaterial, playerMaterial, 1.0, 64 );
|
|
}
|
|
#else
|
|
m_iv_predictedRenderOrigin.ClearHistory();
|
|
#endif
|
|
Vector position;
|
|
QAngle angles;
|
|
if( pPhys )
|
|
{
|
|
pPhys->GetPosition( &position, &angles );
|
|
}
|
|
else
|
|
{
|
|
position = pEntity->GetAbsOrigin();
|
|
angles = pEntity->GetAbsAngles();
|
|
}
|
|
|
|
// If it has a preferred orientation, use that instead.
|
|
Pickup_GetPreferredCarryAngles( pEntity, pPlayer, pPlayer->EntityToWorldTransform(), angles );
|
|
|
|
#if !defined CLIENT_DLL
|
|
pPortalPlayer->UpdateVMGrab( pEntity );
|
|
pPortalPlayer->SetUsingVMGrabState( pPortalPlayer->WantsVMGrab() );
|
|
#endif
|
|
|
|
CPortal_Base2D *pHeldObjectPortal = NULL;
|
|
if ( pPortalPlayer->IsHeldObjectOnOppositeSideOfPortal() )
|
|
{
|
|
pHeldObjectPortal = pPortalPlayer->GetHeldObjectPortal();
|
|
}
|
|
else
|
|
{
|
|
pHeldObjectPortal = static_cast< CPortal_Base2D* >( pPortalPlayer->m_hPortalThroughWhichGrabOccured.Get() );
|
|
}
|
|
|
|
if ( !IsUsingVMGrab() )
|
|
{
|
|
//Fix attachment orientation weirdness
|
|
if ( pHeldObjectPortal )
|
|
{
|
|
Vector vPlayerForward;
|
|
pPlayer->EyeVectors( &vPlayerForward );
|
|
|
|
Vector radial;
|
|
if( pPhys )
|
|
{
|
|
radial = physcollision->CollideGetExtent( pPhys->GetCollide(), vec3_origin, pEntity->GetAbsAngles(), -vPlayerForward );
|
|
}
|
|
else
|
|
{
|
|
vcollide_t *pVCollide = modelinfo->GetVCollide( pEntity->GetModelIndex() );
|
|
if( pVCollide && (pVCollide->solidCount > 0) )
|
|
{
|
|
radial = physcollision->CollideGetExtent( pVCollide->solids[0], vec3_origin, pEntity->GetAbsAngles(), -vPlayerForward );
|
|
}
|
|
else
|
|
{
|
|
radial = vec3_origin;
|
|
}
|
|
}
|
|
// The AABB "orients" to the surface, Length2D doesn't cut it.
|
|
// Instead, we flatten the hull along the up vector and take the length of
|
|
// the resulting vector.
|
|
CPortal_Player* pPortalPlayer = ToPortalPlayer( pPlayer );
|
|
const Vector& stickNormal = pPortalPlayer->GetPortalPlayerLocalData().m_StickNormal;
|
|
Vector player2d = 0.5f * ( pPortalPlayer->GetHullMaxs() - pPortalPlayer->GetHullMins() );
|
|
player2d -= DotProduct( stickNormal, player2d ) * stickNormal;
|
|
float playerRadius = player2d.Length();
|
|
float flDot = DotProduct( vPlayerForward, radial );
|
|
|
|
float radius = playerRadius + fabs( flDot );
|
|
|
|
float distance = 24 + ( radius * 2.0f );
|
|
|
|
//find out which portal the object is on the other side of....
|
|
Vector start = pPlayer->Weapon_ShootPosition();
|
|
Vector end = start + ( vPlayerForward * distance );
|
|
|
|
// If our end point hasn't gone into the portal yet we at least need to know what portal is in front of us
|
|
Ray_t rayPortalTest;
|
|
rayPortalTest.Init( start, start + vPlayerForward * 1024.0f );
|
|
|
|
int iPortalCount = CPortal_Base2D_Shared::AllPortals.Count();
|
|
if( iPortalCount != 0 )
|
|
{
|
|
CPortal_Base2D **pPortals = CPortal_Base2D_Shared::AllPortals.Base();
|
|
float fMinDist = 2.0f;
|
|
for( int i = 0; i != iPortalCount; ++i )
|
|
{
|
|
CPortal_Base2D *pTempPortal = pPortals[i];
|
|
if( pTempPortal->IsActive() &&
|
|
(pTempPortal->m_hLinkedPortal.Get() != NULL) )
|
|
{
|
|
float fDist = UTIL_IntersectRayWithPortal( rayPortalTest, pTempPortal );
|
|
if( (fDist >= 0.0f) && (fDist < fMinDist) )
|
|
{
|
|
fMinDist = fDist;
|
|
pHeldObjectPortal = pTempPortal;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( pHeldObjectPortal && pHeldObjectPortal->IsActivedAndLinked() )
|
|
{
|
|
UTIL_Portal_AngleTransform( pHeldObjectPortal->m_hLinkedPortal->MatrixThisToLinked(), angles, angles );
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( pHeldObjectPortal )
|
|
{
|
|
Vector vTransofrmedPos;
|
|
UTIL_Portal_PointTransform( pHeldObjectPortal->MatrixThisToLinked(), position, vTransofrmedPos );
|
|
position = vTransofrmedPos;
|
|
|
|
#if !defined CLIENT_DLL
|
|
pEntity->Teleport( &position, NULL, NULL );
|
|
#endif
|
|
}
|
|
}
|
|
|
|
VectorITransform( pEntity->WorldSpaceCenter(), pEntity->EntityToWorldTransform(), m_attachedPositionObjectSpace );
|
|
// ComputeMaxSpeed( pEntity, pPhys );
|
|
|
|
// Carried entities can never block LOS
|
|
m_bCarriedEntityBlocksLOS = pEntity->BlocksLOS();
|
|
pEntity->SetBlocksLOS( false );
|
|
if( m_controller )
|
|
{
|
|
physenv->DestroyMotionController( m_controller );
|
|
}
|
|
m_controller = physenv->CreateMotionController( this );
|
|
if( pPhys )
|
|
{
|
|
m_controller->AttachObject( pPhys, true );
|
|
}
|
|
// Don't do this, it's causing trouble with constraint solvers.
|
|
//m_controller->SetPriority( IPhysicsMotionController::HIGH_PRIORITY );
|
|
|
|
if( pPhys )
|
|
{
|
|
pPhys->Wake();
|
|
PhysSetGameFlags( pPhys, FVPHYSICS_PLAYER_HELD );
|
|
}
|
|
SetTargetPosition( position, angles );
|
|
m_attachedEntity = pEntity;
|
|
IPhysicsObject *pList[VPHYSICS_MAX_OBJECT_LIST_COUNT];
|
|
int count = pEntity->VPhysicsGetObjectList( pList, ARRAYSIZE(pList) );
|
|
m_flLoadWeight = 0;
|
|
float flFactor = count / 7.5f;
|
|
if ( flFactor < 1.0f )
|
|
{
|
|
flFactor = 1.0f;
|
|
}
|
|
#if !defined CLIENT_DLL
|
|
float damping = 10;
|
|
for ( int i = 0; i < count; i++ )
|
|
{
|
|
float mass = pList[i]->GetMass();
|
|
pList[i]->GetDamping( NULL, &m_savedRotDamping[i] );
|
|
m_flLoadWeight += mass;
|
|
m_savedMass[i] = mass;
|
|
|
|
// reduce the mass to prevent the player from adding crazy amounts of energy to the system
|
|
pList[i]->SetMass( REDUCED_CARRY_MASS / flFactor );
|
|
pList[i]->SetDamping( NULL, &damping );
|
|
}
|
|
#endif
|
|
|
|
// Give extra mass to the phys object we're actually picking up
|
|
if( pPhys )
|
|
{
|
|
pPhys->SetMass( REDUCED_CARRY_MASS );
|
|
m_bWasDragEnabled = pPhys->IsDragEnabled();
|
|
pPhys->EnableDrag( false );
|
|
}
|
|
|
|
m_errorTime = bIsMegaPhysCannon ? -1.5f : -1.0f; // 1 seconds until error starts accumulating
|
|
m_error = 0;
|
|
m_contactAmount = 0;
|
|
|
|
m_attachedAnglesPlayerSpace = TransformAnglesToPlayerSpace( angles, pPlayer );
|
|
if ( m_angleAlignment != 0 )
|
|
{
|
|
m_attachedAnglesPlayerSpace = AlignAngles( m_attachedAnglesPlayerSpace, m_angleAlignment );
|
|
}
|
|
|
|
// Ragdolls don't offset this way
|
|
VectorITransform( pEntity->WorldSpaceCenter(), pEntity->EntityToWorldTransform(), m_attachedPositionObjectSpace );
|
|
|
|
// If it's a prop, see if it has desired carry angles
|
|
#if !defined CLIENT_DLL
|
|
CPhysicsProp *pProp = dynamic_cast<CPhysicsProp *>(pEntity);
|
|
if ( pProp )
|
|
{
|
|
m_bHasPreferredCarryAngles = pProp->GetPropDataAngles( "preferred_carryangles", m_vecPreferredCarryAngles );
|
|
m_flDistanceOffset = pProp->GetCarryDistanceOffset();
|
|
}
|
|
else
|
|
{
|
|
m_bHasPreferredCarryAngles = false;
|
|
m_flDistanceOffset = 0;
|
|
}
|
|
#else
|
|
C_BreakableProp *pProp = dynamic_cast<C_BreakableProp *>(pEntity);
|
|
if( pProp )
|
|
{
|
|
m_bHasPreferredCarryAngles = (pProp->GetNetworkedPreferredPlayerCarryAngles().x < FLT_MAX);
|
|
m_flDistanceOffset = 0;//pProp->GetCarryDistanceOffset();
|
|
}
|
|
else
|
|
{
|
|
m_bHasPreferredCarryAngles = false;
|
|
m_flDistanceOffset = 0;
|
|
}
|
|
#endif
|
|
|
|
m_prePickupCollisionGroup = pEntity->GetCollisionGroup();
|
|
|
|
if ( player_held_object_collide_with_player.GetBool() == false )
|
|
{
|
|
pEntity->SetCollisionGroup( COLLISION_GROUP_PLAYER_HELD );
|
|
}
|
|
|
|
AttachEntityVM( pPlayer, pEntity, pPhys, bIsMegaPhysCannon, vGrabPosition, bUseGrabPosition );
|
|
}
|
|
|
|
static void ClampPhysicsVelocity( IPhysicsObject *pPhys, float linearLimit, float angularLimit, const Vector *pRelativeVelocity = NULL )
|
|
{
|
|
if( pRelativeVelocity == NULL )
|
|
pRelativeVelocity = &vec3_origin;
|
|
|
|
Vector vel;
|
|
AngularImpulse angVel;
|
|
pPhys->GetVelocity( &vel, &angVel );
|
|
vel -= *pRelativeVelocity;
|
|
float speed = VectorNormalize(vel);
|
|
float angSpeed = VectorNormalize(angVel);
|
|
speed = speed > linearLimit ? linearLimit : speed;
|
|
angSpeed = angSpeed > angularLimit ? angularLimit : angSpeed;
|
|
vel *= speed;
|
|
vel += *pRelativeVelocity;
|
|
angVel *= angSpeed;
|
|
pPhys->SetVelocity( &vel, &angVel );
|
|
}
|
|
|
|
bool CGrabController::DetachEntity( bool bClearVelocity )
|
|
{
|
|
CPortal_Player *pPortalPlayer = ToPortalPlayer( m_hHoldingPlayer.Get() );
|
|
#if defined( CLIENT_DLL )
|
|
m_iv_predictedRenderOrigin.ClearHistory();
|
|
#else
|
|
if( pPortalPlayer && !pPortalPlayer->m_bSilentDropAndPickup )
|
|
{
|
|
pPortalPlayer->m_GrabControllerPersistentVars.ResetOscillationWatch();
|
|
}
|
|
#endif
|
|
|
|
if ( IsUsingVMGrab() )
|
|
{
|
|
if ( !DetachEntityVM( bClearVelocity ) )
|
|
{
|
|
CBaseEntity *pEntity = GetAttached();
|
|
Assert ( pEntity );
|
|
if ( !pEntity )
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
#if !defined CLIENT_DLL
|
|
Assert(!PhysIsInCallback());
|
|
#endif
|
|
CBaseEntity *pEntity = GetAttached();
|
|
if ( pEntity )
|
|
{
|
|
// If we're not colliding with the player, refuse to drop when penetrating a player.
|
|
// (or anything else... trusting FindSafePlacementLocation to not give false positives..
|
|
// if this isn't the case we can change it to a straight object vs player check)
|
|
if ( player_held_object_collide_with_player.GetBool() == false )
|
|
{
|
|
if ( pPortalPlayer && !pPortalPlayer->m_bSilentDropAndPickup && !pPortalPlayer->IsForcingDrop() )
|
|
{
|
|
if ( TestIntersectionVsHeldObjectCollide( pEntity, pEntity->GetAbsOrigin(), m_hHoldingPlayer.Get() ) )
|
|
return false;
|
|
}
|
|
|
|
// Also, restore the original collision group
|
|
pEntity->SetCollisionGroup( m_prePickupCollisionGroup );
|
|
}
|
|
|
|
// Restore the LS blocking state
|
|
pEntity->SetBlocksLOS( m_bCarriedEntityBlocksLOS );
|
|
IPhysicsObject *pList[VPHYSICS_MAX_OBJECT_LIST_COUNT];
|
|
int count = pEntity->VPhysicsGetObjectList( pList, ARRAYSIZE(pList) );
|
|
for ( int i = 0; i < count; i++ )
|
|
{
|
|
IPhysicsObject *pPhys = pList[i];
|
|
if ( !pPhys )
|
|
continue;
|
|
|
|
// on the odd chance that it's gone to sleep while under anti-gravity
|
|
pPhys->EnableDrag( m_bWasDragEnabled );
|
|
pPhys->Wake();
|
|
#if !defined CLIENT_DLL
|
|
pPhys->SetMass( m_savedMass[i] );
|
|
pPhys->SetDamping( NULL, &m_savedRotDamping[i] );
|
|
#endif
|
|
PhysClearGameFlags( pPhys, FVPHYSICS_PLAYER_HELD );
|
|
if ( bClearVelocity )
|
|
{
|
|
PhysForceClearVelocity( pPhys );
|
|
}
|
|
else
|
|
{
|
|
//find the player owning this grab controller. So we can pass along their velocity to the clamping function as a relative velocity
|
|
CBasePlayer *pHoldingPlayer = NULL;
|
|
for( int i = 1; i <= gpGlobals->maxClients; ++i )
|
|
{
|
|
CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
|
|
if ( pPlayer )
|
|
{
|
|
if ( GetGrabControllerForPlayer( pPlayer ) == this )
|
|
{
|
|
pHoldingPlayer = pPlayer;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( pHoldingPlayer )
|
|
{
|
|
ClampPhysicsVelocity( pPhys, pHoldingPlayer->MaxSpeed() * 1.5f, 2.0f * 360.0f, &pHoldingPlayer->GetAbsVelocity() );
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
m_attachedEntity = NULL;
|
|
if ( physenv != NULL )
|
|
{
|
|
physenv->DestroyMotionController( m_controller );
|
|
}
|
|
else
|
|
{
|
|
Warning( "%s(%d): Trying to dereference NULL physenv.\n", __FILE__, __LINE__ );
|
|
}
|
|
m_controller = NULL;
|
|
m_hHoldingPlayer = NULL;
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool InContactWithHeavyObject( IPhysicsObject *pObject, float heavyMass )
|
|
{
|
|
bool contact = false;
|
|
IPhysicsFrictionSnapshot *pSnapshot = pObject->CreateFrictionSnapshot();
|
|
while ( pSnapshot->IsValid() )
|
|
{
|
|
IPhysicsObject *pOther = pSnapshot->GetObject( 1 );
|
|
if ( !pOther->IsMoveable() || pOther->GetMass() > heavyMass )
|
|
{
|
|
contact = true;
|
|
break;
|
|
}
|
|
pSnapshot->NextFrictionData();
|
|
}
|
|
pObject->DestroyFrictionSnapshot( pSnapshot );
|
|
return contact;
|
|
}
|
|
|
|
IMotionEvent::simresult_e CGrabController::Simulate( IPhysicsMotionController *pController, IPhysicsObject *pObject, float deltaTime, Vector &linear, AngularImpulse &angular )
|
|
{
|
|
game_shadowcontrol_params_t shadowParams = m_shadow;
|
|
shadowParams.maxSpeed += m_fPlayerSpeed; //fix for fast moving players being unable to hold objects.
|
|
|
|
if ( InContactWithHeavyObject( pObject, GetLoadWeight() ) )
|
|
{
|
|
m_contactAmount = Approach( 0.1f, m_contactAmount, deltaTime*2.0f );
|
|
}
|
|
else
|
|
{
|
|
m_contactAmount = Approach( 1.0f, m_contactAmount, deltaTime*2.0f );
|
|
}
|
|
shadowParams.maxAngular = m_shadow.maxAngular * m_contactAmount * m_contactAmount * m_contactAmount;
|
|
m_timeToArrive = pObject->ComputeShadowControl( shadowParams, m_timeToArrive, deltaTime );
|
|
|
|
#if defined DEBUG_SHADOW_CONTROLLER
|
|
shadowParams.SpewState();
|
|
#endif
|
|
|
|
// Slide along the current contact points to fix bouncing problems
|
|
Vector velocity;
|
|
AngularImpulse angVel;
|
|
pObject->GetVelocity( &velocity, &angVel );
|
|
PhysComputeSlideDirection( pObject, velocity, angVel, &velocity, &angVel, GetLoadWeight() );
|
|
pObject->SetVelocityInstantaneous( &velocity, NULL );
|
|
|
|
linear.Init();
|
|
angular.Init();
|
|
m_errorTime += deltaTime;
|
|
|
|
return SIM_LOCAL_ACCELERATION;
|
|
}
|
|
|
|
#if defined( CLIENT_DLL )
|
|
//Goals of this algorithm
|
|
//1. For a player carrying an object, keep up with their input to keep the grab controller feeling smooth
|
|
//2. For an object that collides with a complex spatial interaction on the server, settle in the same position on the server in a smooth (even if wrong) way.
|
|
void CGrabController::ClientApproachTarget( CBasePlayer *pOwnerPlayer )
|
|
{
|
|
CBaseEntity *pAttached = GetAttached();
|
|
C_PlayerHeldObjectClone *pClone = NULL;
|
|
|
|
if ( IsUsingVMGrab() )
|
|
{
|
|
if( pAttached == ((CPortal_Player *)pOwnerPlayer)->m_pHeldEntityClone )
|
|
{
|
|
//update clone as well as base object, clone first with this code, then swap out pAttached for base
|
|
pClone = ((C_PlayerHeldObjectClone *)pAttached);
|
|
pAttached = ((C_PlayerHeldObjectClone *)pAttached)->m_hOriginal;
|
|
|
|
Vector vPlayerEye, vPlayerForward, vPlayerRight, vPlayerUp;
|
|
pOwnerPlayer->EyePositionAndVectors( &vPlayerEye, &vPlayerForward, &vPlayerRight, &vPlayerUp );
|
|
Vector vEyeRelative = m_shadow.targetPosition - vPlayerEye;
|
|
pClone->m_vPlayerRelativeOrigin.x = vPlayerForward.Dot( vEyeRelative );
|
|
pClone->m_vPlayerRelativeOrigin.y = vPlayerRight.Dot( vEyeRelative );
|
|
pClone->m_vPlayerRelativeOrigin.z = vPlayerUp.Dot( vEyeRelative );
|
|
|
|
pClone->SetNetworkOrigin( m_shadow.targetPosition );
|
|
pClone->SetNetworkAngles( m_shadow.targetRotation );
|
|
pClone->SetAbsOrigin( m_shadow.targetPosition );
|
|
pClone->SetAbsAngles( m_shadow.targetRotation );
|
|
}
|
|
}
|
|
|
|
if( prediction->InPrediction() && pAttached && pAttached->GetPredictable() )
|
|
{
|
|
//Not accounting for visual stuttering, this is the ideal position based on the most recent data from the server
|
|
//Vector vServerTarget = m_shadow.targetPosition - TransformVectorFromPlayerSpace( ((CPortal_Player *)pOwnerPlayer)->m_vecCarriedObject_CurPosToTargetPos, pOwnerPlayer );
|
|
|
|
//The same idea as above, but trying to eliminate visual stuttering by ramping into changes coming down from the server, and producing consistent results on repredictions even if the networked offset changes under our feet
|
|
Vector vForwardServerTarget = m_shadow.targetPosition - TransformVectorFromPlayerSpace( ((CPortal_Player *)pOwnerPlayer)->m_vecCarriedObject_CurPosToTargetPos_Interpolated, pOwnerPlayer );
|
|
|
|
//filter out the player and every version of the held object
|
|
CTraceFilterSimpleList traceFilter( pAttached->GetCollisionGroup() );
|
|
traceFilter.AddEntityToIgnore( pOwnerPlayer );
|
|
traceFilter.AddEntityToIgnore( pAttached );
|
|
if( ((CPortal_Player *)pOwnerPlayer)->m_pHeldEntityClone )
|
|
{
|
|
traceFilter.AddEntityToIgnore( ((CPortal_Player *)pOwnerPlayer)->m_pHeldEntityClone );
|
|
}
|
|
if( ((CPortal_Player *)pOwnerPlayer)->m_pHeldEntityThirdpersonClone )
|
|
{
|
|
traceFilter.AddEntityToIgnore( ((CPortal_Player *)pOwnerPlayer)->m_pHeldEntityThirdpersonClone );
|
|
}
|
|
|
|
//For fluidity, there are 2 distinct cases for where to put the object.
|
|
//Case 1: If we think the swept space is collision-free, just put the object at the m_shadow targets. This is the most fluid movement
|
|
//Case 2: If we think there's something for physics on the server to interact with, incorporate vForwardServerTarget somehow. This can move back and forth between start and end position as network data catches up with prediction
|
|
|
|
Vector vFinalPos;
|
|
|
|
ICollideable *pCollideable = pAttached->GetCollideable();
|
|
if( pCollideable )
|
|
{
|
|
trace_t tr;
|
|
enginetrace->SweepCollideable( pCollideable, pAttached->GetNetworkOrigin(), vForwardServerTarget, vec3_angle, MASK_SOLID, &traceFilter, &tr );
|
|
|
|
if( tr.DidHit() )
|
|
{
|
|
vFinalPos = tr.endpos;
|
|
}
|
|
else
|
|
{
|
|
enginetrace->SweepCollideable( pCollideable, pAttached->GetNetworkOrigin(), m_shadow.targetPosition, vec3_angle, MASK_SOLID, &traceFilter, &tr );
|
|
vFinalPos = tr.endpos;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
vFinalPos = m_shadow.targetPosition - ((CPortal_Player *)pOwnerPlayer)->m_vecCarriedObject_CurPosToTargetPos_Interpolated;
|
|
}
|
|
|
|
|
|
if( prediction->IsFirstTimePredicted() )
|
|
{
|
|
if( bLastUpdateWasOnOppositeSideOfPortal != ((CPortal_Player *)pOwnerPlayer)->IsHeldObjectOnOppositeSideOfPortal() )
|
|
{
|
|
m_iv_predictedRenderOrigin.ClearHistory();
|
|
bLastUpdateWasOnOppositeSideOfPortal = ((CPortal_Player *)pOwnerPlayer)->IsHeldObjectOnOppositeSideOfPortal();
|
|
}
|
|
m_iv_predictedRenderOrigin.AddToHead( gpGlobals->curtime, &vFinalPos, false );
|
|
}
|
|
|
|
pAttached->SetNetworkOrigin( vFinalPos );
|
|
pAttached->SetAbsOrigin( vFinalPos );
|
|
m_vHeldObjectRenderOrigin = vFinalPos;
|
|
|
|
pAttached->SetAbsAngles( m_shadow.targetRotation - ((CPortal_Player *)pOwnerPlayer)->m_vecCarriedObject_CurAngToTargetAng_Interpolated );
|
|
}
|
|
}
|
|
|
|
const Vector &CGrabController::GetHeldObjectRenderOrigin( void )
|
|
{
|
|
float currentTime;
|
|
C_BasePlayer *pHoldingPlayer = (C_BasePlayer *)m_hHoldingPlayer.Get();
|
|
if( pHoldingPlayer )
|
|
{
|
|
currentTime = pHoldingPlayer->GetFinalPredictedTime();
|
|
currentTime -= TICK_INTERVAL;
|
|
currentTime += ( gpGlobals->interpolation_amount * TICK_INTERVAL );
|
|
}
|
|
else
|
|
{
|
|
currentTime = gpGlobals->curtime;
|
|
}
|
|
|
|
m_iv_predictedRenderOrigin.Interpolate( currentTime );
|
|
return m_vHeldObjectRenderOrigin;
|
|
}
|
|
#endif
|
|
|
|
#if !defined CLIENT_DLL
|
|
float CGrabController::GetSavedMass( IPhysicsObject *pObject )
|
|
{
|
|
CBaseEntity *pHeld = m_attachedEntity;
|
|
if ( pHeld )
|
|
{
|
|
if ( pObject->GetGameData() == (void*)pHeld )
|
|
{
|
|
IPhysicsObject *pList[VPHYSICS_MAX_OBJECT_LIST_COUNT];
|
|
int count = pHeld->VPhysicsGetObjectList( pList, ARRAYSIZE(pList) );
|
|
for ( int i = 0; i < count; i++ )
|
|
{
|
|
if ( pList[i] == pObject )
|
|
return m_savedMass[i];
|
|
}
|
|
}
|
|
}
|
|
return 0.0f;
|
|
}
|
|
#endif
|
|
|
|
|
|
void CGrabController::SetPortalPenetratingEntity( CBaseEntity *pPenetrated )
|
|
{
|
|
m_PenetratedEntity = pPenetrated;
|
|
}
|
|
|
|
#if !defined CLIENT_DLL
|
|
LINK_ENTITY_TO_CLASS( player_pickup, CPlayerPickupController );
|
|
//---------------------------------------------------------
|
|
// Save/Restore
|
|
//---------------------------------------------------------
|
|
BEGIN_DATADESC( CPlayerPickupController )
|
|
|
|
DEFINE_EMBEDDED( m_grabController ),
|
|
|
|
// Physptrs can't be inside embedded classes
|
|
DEFINE_PHYSPTR( m_grabController.m_controller ),
|
|
|
|
DEFINE_FIELD( m_pPlayer, FIELD_CLASSPTR ),
|
|
|
|
END_DATADESC()
|
|
#else
|
|
LINK_ENTITY_TO_CLASS_CLIENTONLY( player_pickup, CPlayerPickupController );
|
|
#endif
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : *pPlayer -
|
|
// *pObject -
|
|
//-----------------------------------------------------------------------------
|
|
void CPlayerPickupController::InitGrabController( CBasePlayer *pPlayer, CBaseEntity *pObject )
|
|
{
|
|
#if !defined CLIENT_DLL
|
|
// Holster player's weapon
|
|
if ( pPlayer->GetActiveWeapon() )
|
|
{
|
|
// Don't holster the portalgun
|
|
if ( hide_gun_when_holding.GetBool() == false && FClassnameIs( pPlayer->GetActiveWeapon(), "weapon_portalgun" ) )
|
|
{
|
|
CWeaponPortalgun *pPortalGun = (CWeaponPortalgun*)(pPlayer->GetActiveWeapon());
|
|
pPortalGun->OpenProngs( true );
|
|
}
|
|
else
|
|
{
|
|
if ( !pPlayer->GetActiveWeapon()->Holster() )
|
|
{
|
|
Shutdown();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
CPortal_Player *pOwner = ToPortalPlayer( pPlayer );
|
|
|
|
// If the target is debris, convert it to non-debris
|
|
if ( pObject->GetCollisionGroup() == COLLISION_GROUP_DEBRIS )
|
|
{
|
|
// Interactive debris converts back to debris when it comes to rest
|
|
pObject->SetCollisionGroup( COLLISION_GROUP_INTERACTIVE_DEBRIS );
|
|
}
|
|
|
|
// done so I'll go across level transitions with the player
|
|
SetParent( pPlayer );
|
|
GetGrabController().SetIgnorePitch( true );
|
|
GetGrabController().SetAngleAlignment( 0.866025403784 );
|
|
m_pPlayer = pPlayer;
|
|
IPhysicsObject *pPhysics = pObject->VPhysicsGetObject();
|
|
|
|
if ( !pOwner->m_bSilentDropAndPickup )
|
|
{
|
|
Pickup_OnPhysGunPickup( pObject, m_pPlayer, PICKED_UP_BY_PLAYER );
|
|
}
|
|
|
|
GetGrabController().AttachEntity( pPlayer, pObject, pPhysics, false, vec3_origin, false );
|
|
|
|
m_pPlayer->m_Local.m_iHideHUD |= HIDEHUD_WEAPONSELECTION;
|
|
m_pPlayer->SetUseEntity( this );
|
|
|
|
#if !defined ( CLIENT_DLL )
|
|
//pObject->DispatchUpdateTransmitState();
|
|
|
|
pOwner->m_hAttachedObject = pObject;
|
|
|
|
#endif
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : bool -
|
|
//-----------------------------------------------------------------------------
|
|
bool CPlayerPickupController::Shutdown( bool bThrown )
|
|
{
|
|
CBaseEntity *pObject = GetGrabController().GetAttached();
|
|
|
|
bool bClearVelocity = false;
|
|
if ( !bThrown && pObject && pObject->VPhysicsGetObject() && pObject->VPhysicsGetObject()->GetContactPoint(NULL,NULL) )
|
|
{
|
|
bClearVelocity = true;
|
|
}
|
|
|
|
if ( GetGrabController().DetachEntity( bClearVelocity ) == false )
|
|
return false;
|
|
|
|
if ( pObject != NULL )
|
|
{
|
|
if ( !ToPortalPlayer( m_pPlayer )->m_bSilentDropAndPickup )
|
|
{
|
|
Pickup_OnPhysGunDrop( pObject, m_pPlayer, bThrown ? THROWN_BY_PLAYER : DROPPED_BY_PLAYER );
|
|
}
|
|
}
|
|
CPortal_Player *pOwner = ToPortalPlayer( m_pPlayer );
|
|
|
|
if ( pOwner )
|
|
{
|
|
pOwner->SetUseEntity( NULL );
|
|
if ( !pOwner->m_bSilentDropAndPickup )
|
|
{
|
|
pOwner->SetHeldObjectOnOppositeSideOfPortal( false );
|
|
|
|
#if !defined CLIENT_DLL
|
|
if ( pOwner->GetActiveWeapon() )
|
|
{
|
|
float flAttackDelay = 0.5f;
|
|
|
|
#if USE_SLOWTIME
|
|
if ( pOwner->IsSlowingTime() )
|
|
{
|
|
flAttackDelay *= slowtime_speed.GetFloat();
|
|
}
|
|
#endif // USE_SLOWTIME
|
|
|
|
pOwner->SetNextAttack( gpGlobals->curtime + flAttackDelay );
|
|
CWeaponPortalgun *pPortalGun = (CWeaponPortalgun*)(m_pPlayer->GetActiveWeapon());
|
|
pPortalGun->DelayAttack( flAttackDelay );
|
|
|
|
if ( hide_gun_when_holding.GetBool() == false && FClassnameIs( m_pPlayer->GetActiveWeapon(), "weapon_portalgun" ) )
|
|
{
|
|
pPortalGun->OpenProngs( false );
|
|
}
|
|
else
|
|
{
|
|
if ( !pOwner->GetActiveWeapon()->Deploy() )
|
|
{
|
|
// We tried to restore the player's weapon, but we couldn't.
|
|
// This usually happens when they're holding an empty weapon that doesn't
|
|
// autoswitch away when out of ammo. Switch to next best weapon.
|
|
pOwner->SwitchToNextBestWeapon( NULL );
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
pOwner->m_Local.m_iHideHUD &= ~HIDEHUD_WEAPONSELECTION;
|
|
}
|
|
}
|
|
Remove();
|
|
|
|
#if !defined ( CLIENT_DLL )
|
|
if ( pObject != NULL )
|
|
{
|
|
pObject->DispatchUpdateTransmitState();
|
|
}
|
|
|
|
pOwner->m_hAttachedObject = NULL;
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CPlayerPickupController::UsePickupController( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
|
|
{
|
|
if ( ToBasePlayer(pActivator) == m_pPlayer )
|
|
{
|
|
CBaseEntity *pAttached = GetGrabController().GetAttached();
|
|
|
|
// UNDONE: Use vphysics stress to decide to drop objects
|
|
// UNDONE: Must fix case of forcing objects into the ground you're standing on (causes stress) before that will work
|
|
float flMaxError = ( player_held_object_collide_with_player.GetBool() ) ? ( 12 ) : ( 40 ); // Some magic numbers here... what we want to allow is the object moving from the desired position into the player before we start trying to break the hold
|
|
if ( !pAttached || useType == USE_OFF || GetGrabController().ComputeError() > flMaxError )
|
|
{
|
|
return Shutdown();
|
|
}
|
|
|
|
//Adrian: Oops, our object became motion disabled, let go!
|
|
IPhysicsObject *pPhys = pAttached->VPhysicsGetObject();
|
|
if ( pPhys && pPhys->IsMoveable() == false )
|
|
{
|
|
return Shutdown();
|
|
}
|
|
|
|
#if STRESS_TEST
|
|
vphysics_objectstress_t stress;
|
|
CalculateObjectStress( pPhys, pAttached, &stress );
|
|
if ( stress.exertedStress > 250 )
|
|
{
|
|
return Shutdown();
|
|
}
|
|
#endif
|
|
|
|
if ( useType == USE_SET )
|
|
{
|
|
// update position
|
|
if ( GetGrabController().UpdateObject( m_pPlayer, 12 ) == false )
|
|
{
|
|
Shutdown();
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : *pEnt -
|
|
// Output : Returns true on success, false on failure.
|
|
//-----------------------------------------------------------------------------
|
|
bool CPlayerPickupController::IsHoldingEntity( CBaseEntity *pEnt )
|
|
{
|
|
if ( pEnt )
|
|
{
|
|
return ( GetGrabController().GetAttached() == pEnt );
|
|
}
|
|
|
|
return ( GetGrabController().GetAttached() != 0 );
|
|
}
|
|
|
|
CGrabController &CPlayerPickupController::GetGrabController()
|
|
{
|
|
return m_grabController;
|
|
}
|
|
|
|
void PlayerPickupObject( CBasePlayer *pPlayer, CBaseEntity *pObject )
|
|
{
|
|
#if !defined ( CLIENT_DLL )
|
|
//Don't pick up if we don't have a phys object.
|
|
if ( pObject->VPhysicsGetObject() == NULL )
|
|
return;
|
|
#endif
|
|
|
|
CPlayerPickupController *pController = NULL;
|
|
#if !defined CLIENT_DLL
|
|
if ( pObject->GetBaseAnimating() && pObject->GetBaseAnimating()->IsDissolving() )
|
|
return;
|
|
|
|
pController = (CPlayerPickupController *)CreateEntityByName( "player_pickup" );
|
|
#endif
|
|
|
|
if ( !pController )
|
|
return;
|
|
|
|
pController->SetAbsOrigin( pObject->GetAbsOrigin() );
|
|
pController->SetAbsAngles( vec3_angle );
|
|
//pController->SetOwnerEntity( pPlayer );
|
|
|
|
pController->InitGrabController( pPlayer, pObject );
|
|
}
|
|
|
|
float CGrabController::GetObjectOffset( CBaseEntity *pEntity ) const
|
|
{
|
|
if ( IsUsingVMGrab() )
|
|
{
|
|
//return ( FClassnameIs( pEntity, "npc_personality_core") ) ? ( player_held_object_offset_up_sphere.GetFloat() ) : ( player_held_object_offset_up_vm.GetFloat() );
|
|
if ( FClassnameIs( pEntity, "npc_personality_core") )
|
|
{
|
|
return player_held_object_offset_up_sphere.GetFloat();
|
|
}
|
|
#ifdef GAME_DLL
|
|
else if ( FClassnameIs( pEntity, "prop_weighted_cube") ) // Argh. MP will always use this one.
|
|
#else // it keeps the object out of eyes but the
|
|
else if ( dynamic_cast<C_PlayerHeldObjectClone*>( pEntity ) != NULL ) // convar's name doesn't really show this behavior well.
|
|
#endif
|
|
{
|
|
return player_held_object_offset_up_cube_vm.GetFloat();
|
|
}
|
|
else if ( dynamic_cast<CNPC_Portal_FloorTurret*>( pEntity ) != NULL )
|
|
{
|
|
return player_held_object_offset_up_turret_vm.GetFloat();
|
|
}
|
|
else
|
|
{
|
|
return 0.0f;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( FClassnameIs( pEntity, "prop_weighted_cube") )
|
|
{
|
|
return player_held_object_offset_up_cube.GetFloat();
|
|
}
|
|
else
|
|
{
|
|
return 0.0f;
|
|
}
|
|
}
|
|
}
|
|
|
|
float CGrabController::GetObjectDistance( void ) const
|
|
{
|
|
if ( IsUsingVMGrab() )
|
|
{
|
|
Assert ( m_attachedEntity );
|
|
if ( m_attachedEntity && FClassnameIs( m_attachedEntity, "npc_portal_turret_floor" ) )
|
|
{
|
|
return player_held_object_distance_turret_vm.GetFloat();
|
|
}
|
|
else
|
|
{
|
|
return player_held_object_distance_vm.GetFloat();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return player_held_object_distance.GetFloat();
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
bool CGrabController::UpdateObject( CBasePlayer *pPlayer, float flError, bool bIsTeleport /*= false*/ )
|
|
{
|
|
CBaseEntity *pEntity = GetAttached();
|
|
|
|
if ( pEntity )
|
|
{
|
|
bool bWantsVMGrab = WantsVMGrab( pPlayer );
|
|
if ( IsUsingVMGrab() && !bWantsVMGrab )
|
|
{
|
|
DetachEntityVM( false );
|
|
}
|
|
else if ( !IsUsingVMGrab() && bWantsVMGrab )
|
|
{
|
|
AttachEntityVM( pPlayer, pEntity, pEntity->VPhysicsGetObject(), false, pEntity->GetAbsOrigin(), false );
|
|
}
|
|
|
|
if ( pEntity->GetMoveParent() != NULL && pEntity->GetMoveParent() != pPlayer )
|
|
{
|
|
// Parented to something else now! Detach!
|
|
DetachEntity( false );
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if ( IsUsingVMGrab() )
|
|
{
|
|
return UpdateObjectVM( pPlayer, flError );
|
|
}
|
|
|
|
m_fPlayerSpeed = pPlayer->GetAbsVelocity().Length();
|
|
|
|
CBaseEntity *pPenetratedEntity = m_PenetratedEntity.Get();
|
|
if( pPenetratedEntity )
|
|
{
|
|
//FindClosestPassableSpace( pPenetratedEntity, Vector( 0.0f, 0.0f, 1.0f ) );
|
|
IPhysicsObject *pPhysObject = pPenetratedEntity->VPhysicsGetObject();
|
|
if( pPhysObject )
|
|
pPhysObject->Wake();
|
|
|
|
m_PenetratedEntity = NULL; //assume we won
|
|
}
|
|
|
|
|
|
#if !defined ( CLIENT_DLL )
|
|
if ( !pEntity || ComputeError() > flError || pPlayer->GetGroundEntity() == pEntity || !pEntity->VPhysicsGetObject() )
|
|
{
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
#if !defined ( CLIENT_DLL )
|
|
IPhysicsObject *pPhys = pEntity->VPhysicsGetObject();
|
|
//Adrian: Oops, our object became motion disabled, let go!
|
|
if ( pPhys && pPhys->IsMoveable() == false )
|
|
{
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
Vector forward, right, up;
|
|
QAngle playerAngles = pPlayer->EyeAngles();
|
|
|
|
float pitch = AngleDistance(playerAngles.x,0);
|
|
if( !m_bAllowObjectOverhead )
|
|
{
|
|
playerAngles.x = clamp( pitch, -75, 75 );
|
|
}
|
|
else
|
|
{
|
|
playerAngles.x = clamp( pitch, -90, 75 );
|
|
}
|
|
|
|
AngleVectors( playerAngles, &forward, &right, &up );
|
|
|
|
Vector start = pPlayer->Weapon_ShootPosition();
|
|
|
|
// If the player is upside down then we need to hold the box closer to their feet.
|
|
if ( up.z < 0.0f )
|
|
start += pPlayer->GetViewOffset() * up.z;
|
|
if ( right.z < 0.0f )
|
|
start += pPlayer->GetViewOffset() * right.z;
|
|
|
|
CPortal_Player *pPortalPlayer = ToPortalPlayer( pPlayer );
|
|
|
|
|
|
bool bLookingAtHeldPortal = true;
|
|
CPortal_Base2D *pPortal = pPortalPlayer->GetHeldObjectPortal();
|
|
|
|
if ( !IsUsingVMGrab() )
|
|
{
|
|
// Find out if it's being held across a portal
|
|
if ( !pPortal )
|
|
{
|
|
// If the portal is invalid make sure we don't try to hold it across the portal
|
|
pPortalPlayer->SetHeldObjectOnOppositeSideOfPortal( false );
|
|
}
|
|
|
|
if ( pPortalPlayer->IsHeldObjectOnOppositeSideOfPortal() )
|
|
{
|
|
Ray_t rayPortalTest;
|
|
rayPortalTest.Init( start, start + forward * 1024.0f );
|
|
|
|
// Check if we're looking at the portal we're holding through
|
|
if ( pPortal )
|
|
{
|
|
if ( UTIL_IntersectRayWithPortal( rayPortalTest, pPortal ) < 0.0f )
|
|
{
|
|
bLookingAtHeldPortal = false;
|
|
}
|
|
}
|
|
// If our end point hasn't gone into the portal yet we at least need to know what portal is in front of us
|
|
else
|
|
{
|
|
int iPortalCount = CPortal_Base2D_Shared::AllPortals.Count();
|
|
if( iPortalCount != 0 )
|
|
{
|
|
CPortal_Base2D **pPortals = CPortal_Base2D_Shared::AllPortals.Base();
|
|
float fMinDist = 2.0f;
|
|
for( int i = 0; i != iPortalCount; ++i )
|
|
{
|
|
CPortal_Base2D *pTempPortal = pPortals[i];
|
|
if( pTempPortal->IsActive() &&
|
|
(pTempPortal->m_hLinkedPortal.Get() != NULL) )
|
|
{
|
|
float fDist = UTIL_IntersectRayWithPortal( rayPortalTest, pTempPortal );
|
|
if( (fDist >= 0.0f) && (fDist < fMinDist) )
|
|
{
|
|
fMinDist = fDist;
|
|
pPortal = pTempPortal;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pPortal = NULL;
|
|
}
|
|
}
|
|
|
|
#if defined( GAME_DLL )
|
|
pPortalPlayer->m_GrabControllerPersistentVars.m_hLookingThroughPortalLastUpdate = bLookingAtHeldPortal ? pPortal : NULL; //bLookingAtHeldPortal is true for cases where we're simply looking at a portal as well as holding across a portal
|
|
if( !pPortalPlayer->IsHeldObjectOnOppositeSideOfPortal() && (pPortal == pPortalPlayer->m_GrabControllerPersistentVars.m_hOscillationWatch) )
|
|
{
|
|
pPortalPlayer->m_GrabControllerPersistentVars.m_hOscillationWatch = NULL; //end oscillation watch if we actually look through the portal
|
|
}
|
|
#endif
|
|
|
|
QAngle qEntityAngles = pEntity->GetAbsAngles();
|
|
|
|
if ( !IsUsingVMGrab() )
|
|
{
|
|
if ( pPortal )
|
|
{
|
|
// If the portal isn't linked we need to drop the object
|
|
if ( !pPortal->m_hLinkedPortal.Get() )
|
|
{
|
|
pPlayer->ForceDropOfCarriedPhysObjects();
|
|
return false;
|
|
}
|
|
|
|
UTIL_Portal_AngleTransform( pPortal->m_hLinkedPortal->MatrixThisToLinked(), qEntityAngles, qEntityAngles );
|
|
}
|
|
}
|
|
|
|
// Now clamp a sphere of object radius at end to the player's bbox
|
|
#if 0
|
|
#if defined( GAME_DLL )
|
|
Vector radial = physcollision->CollideGetExtent( pPhys->GetCollide(), vec3_origin, qEntityAngles, -forward );
|
|
#else
|
|
Vector radial;
|
|
{
|
|
vcollide_t *pVCollide = modelinfo->GetVCollide( pEntity->GetModelIndex() );
|
|
if( pVCollide && (pVCollide->solidCount > 0) )
|
|
{
|
|
radial = physcollision->CollideGetExtent( pVCollide->solids[0], vec3_origin, qEntityAngles, -forward );
|
|
}
|
|
else
|
|
{
|
|
CCollisionProperty *pCollisionProp = pEntity->CollisionProp();
|
|
if( pCollisionProp )
|
|
{
|
|
pCollisionProp->CalcNearestPoint( start, &radial );
|
|
radial -= start;
|
|
}
|
|
else
|
|
{
|
|
radial = vec3_origin;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
// The AABB "orients" to the surface, Length2D doesn't cut it.
|
|
// Instead, we flatten the hull along the up vector and take the length of
|
|
// the resulting vector.
|
|
const Vector& stickNormal = pPortalPlayer->GetPortalPlayerLocalData().m_StickNormal;
|
|
Vector player2d = 0.5f * ( pPortalPlayer->GetHullMaxs() - pPortalPlayer->GetHullMins() );
|
|
player2d -= DotProduct( stickNormal, player2d ) * stickNormal;
|
|
float playerRadius = player2d.Length();
|
|
|
|
float radius = playerRadius + pEntity->BoundingRadius();
|
|
|
|
float distance = GetObjectDistance() + radius;
|
|
|
|
// Add the prop's distance offset
|
|
distance += m_flDistanceOffset;
|
|
|
|
pitch = AngleDistance(playerAngles.x,0);
|
|
|
|
float flUpOffset = RemapValClamped( fabs(pitch), 0.0f, 75.0f, 1.0f, 0.0f ) * GetObjectOffset( pEntity );
|
|
|
|
Vector end;
|
|
if ( player_hold_object_in_column.GetBool() )
|
|
{
|
|
Vector player2dForward = CrossProduct( stickNormal, right );
|
|
Vector point = start + ( player2dForward * distance );
|
|
float intersection = IntersectRayWithPlane( start, forward, player2dForward, -DotProduct( -player2dForward, point ) );
|
|
distance = clamp( intersection, GetObjectDistance(), player_hold_column_max_size.GetFloat() );
|
|
}
|
|
|
|
trace_t tr;
|
|
CTraceFilterSkipTwoEntities traceFilter( pPlayer, pEntity, COLLISION_GROUP_NONE );
|
|
Ray_t ray;
|
|
ray.Init( start, start + forward * distance + up * flUpOffset );
|
|
|
|
//enginetrace->TraceRay( ray, MASK_SOLID_BRUSHONLY, &traceFilter, &tr );
|
|
UTIL_Portal_TraceRay( ray, MASK_SOLID_BRUSHONLY, &traceFilter, &tr );
|
|
|
|
#if defined( GAME_DLL )
|
|
if( !tr.DidHit() )
|
|
{
|
|
pPortalPlayer->m_GrabControllerPersistentVars.m_hOscillationWatch = NULL; //end oscillation watch if we trace into open air
|
|
}
|
|
else if( pPortalPlayer->m_GrabControllerPersistentVars.m_hOscillationWatch.Get() != NULL )
|
|
{
|
|
//make sure it's also not obscenely far away from the oscillation watch portal
|
|
CPortal_Base2D *pOscillationPortal = pPortalPlayer->m_GrabControllerPersistentVars.m_hOscillationWatch.Get();
|
|
Vector vOscillationPortalToTraceHit = tr.endpos - pOscillationPortal->m_ptOrigin;
|
|
|
|
if( (fabs(pOscillationPortal->m_vRight.Dot(vOscillationPortalToTraceHit)) >= (pOscillationPortal->GetHalfWidth() * 2.0f)) ||
|
|
(fabs(pOscillationPortal->m_vUp.Dot(vOscillationPortalToTraceHit)) >= (pOscillationPortal->GetHalfHeight() * 2.0f)) ||
|
|
(fabs(pOscillationPortal->m_vForward.Dot(vOscillationPortalToTraceHit)) >= 5.0f) )
|
|
{
|
|
pPortalPlayer->m_GrabControllerPersistentVars.m_hOscillationWatch = NULL; //trace point has gone reasonably far from the portal
|
|
}
|
|
}
|
|
#endif
|
|
|
|
float flTraceDist = distance * tr.fraction;
|
|
if ( flTraceDist < radius )
|
|
flTraceDist = radius;
|
|
|
|
Vector direction = ray.m_Delta.Normalized();
|
|
end = start + ( direction * flTraceDist ) +
|
|
( up * flUpOffset );
|
|
|
|
Vector playerMins, playerMaxs, nearest;
|
|
pPlayer->CollisionProp()->WorldSpaceAABB( &playerMins, &playerMaxs );
|
|
Vector playerLine = pPlayer->CollisionProp()->WorldSpaceCenter();
|
|
float fHeight = pPortalPlayer->GetHullHeight();
|
|
Vector vUp = pPortalPlayer->GetPortalPlayerLocalData().m_Up;
|
|
// Note: We can't use pPlayer->CollisionProp()->WorldSpaceCenter() because our player's bbox doesn't rotate on stick power
|
|
playerLine = pPortalPlayer->EyePosition() - fHeight * vUp;
|
|
|
|
CalcClosestPointOnLine( end, playerLine + vUp * fHeight, playerLine - vUp * fHeight, nearest, NULL );
|
|
/*NDebugOverlay::Sphere( nearest, 10, 255, 0, 0, true, 0.1f );
|
|
NDebugOverlay::Sphere( playerLine, 10, 0, 0, 255, true, 0.1f );*/
|
|
|
|
//
|
|
//
|
|
//
|
|
|
|
// Trace down from the held object and see if we need to bump up off the floor
|
|
const float flHalfRadius = radius/2.0f;
|
|
Vector vecMaxs( flHalfRadius, flHalfRadius, 0 );
|
|
Vector vecMins = -vecMaxs;
|
|
Vector vecEndPos = end - Vector(0,0,flHalfRadius+1);
|
|
|
|
UTIL_ClearTrace( tr );
|
|
|
|
ray.Init( end + Vector(0,0,flHalfRadius+1), vecEndPos );
|
|
|
|
if ( !IsUsingVMGrab() && player_held_object_transform_bump_ray.GetBool() )
|
|
{
|
|
if ( pPortal != NULL && pPortalPlayer->IsHeldObjectOnOppositeSideOfPortal() )
|
|
{
|
|
VMatrix matThisToLinked = pPortal->MatrixThisToLinked();
|
|
Ray_t portalRay = ray;
|
|
UTIL_Portal_RayTransform( matThisToLinked, portalRay, ray );
|
|
}
|
|
}
|
|
|
|
|
|
UTIL_Portal_TraceRay( ray, MASK_SOLID_BRUSHONLY, &traceFilter, &tr );
|
|
|
|
// Hold onto our last frame because there's a case where moving through a portal creates
|
|
// a brief pop as you hit a solid "no man's land" while traversing the portal
|
|
static float flLastDelta = 0.0f;
|
|
|
|
if ( !tr.startsolid )
|
|
{
|
|
if ( tr.fraction < 1.0f )
|
|
{
|
|
flLastDelta = radius * (1.0f-tr.fraction);
|
|
}
|
|
else
|
|
{
|
|
flLastDelta = 0.0f;
|
|
}
|
|
}
|
|
|
|
end.z += flLastDelta;
|
|
|
|
if( !m_bAllowObjectOverhead )
|
|
{
|
|
Vector delta = end - nearest;
|
|
float len = VectorNormalize(delta);
|
|
if ( len < radius )
|
|
{
|
|
end = nearest + radius * delta;
|
|
}
|
|
}
|
|
|
|
#if !defined ( CLIENT_DLL )
|
|
// Send these down to the client
|
|
pPortalPlayer->m_vecCarriedObjectAngles = m_attachedAnglesPlayerSpace;
|
|
#endif
|
|
|
|
QAngle angles = TransformAnglesFromPlayerSpace( m_attachedAnglesPlayerSpace, pPlayer );
|
|
|
|
//Show overlays of radius
|
|
if ( g_debug_physcannon.GetBool() )
|
|
{
|
|
NDebugOverlay::Box( end, -Vector( 2,2,2 ), Vector(2,2,2), 0, 255, 0, true, 0 );
|
|
|
|
NDebugOverlay::Box( GetAttached()->WorldSpaceCenter(),
|
|
-Vector( radius, radius, radius),
|
|
Vector( radius, radius, radius ),
|
|
255, 0, 0,
|
|
true,
|
|
0.0f );
|
|
}
|
|
|
|
|
|
// If it has a preferred orientation, update to ensure we're still oriented correctly.
|
|
Pickup_GetPreferredCarryAngles( pEntity, pPlayer, pPlayer->EntityToWorldTransform(), angles );
|
|
|
|
// We may be holding a prop that has preferred carry angles
|
|
if ( m_bHasPreferredCarryAngles )
|
|
{
|
|
matrix3x4_t tmp;
|
|
ComputePlayerMatrix( pPlayer, tmp );
|
|
angles = TransformAnglesToWorldSpace( m_vecPreferredCarryAngles, tmp );
|
|
}
|
|
|
|
|
|
|
|
matrix3x4_t attachedToWorld;
|
|
Vector offset;
|
|
AngleMatrix( angles, attachedToWorld );
|
|
VectorRotate( m_attachedPositionObjectSpace, attachedToWorld, offset );
|
|
|
|
//if the player is moving pretty fast. Start moving the object more towards where they're going to be instead of where they are.
|
|
{
|
|
Vector vCurrentOffsetDirection = end - pPlayer->WorldSpaceCenter();
|
|
vCurrentOffsetDirection.NormalizeInPlace();
|
|
|
|
Vector vSpeedAddon = pPlayer->GetAbsVelocity() * (gpGlobals->interval_per_tick * MIN( m_fPlayerSpeed / m_shadow.maxSpeed, 1.0f) );
|
|
float fDot = vSpeedAddon.Dot( vCurrentOffsetDirection );
|
|
end += vCurrentOffsetDirection * MAX( 0.0f, fDot );
|
|
}
|
|
|
|
#if defined( GAME_DLL )
|
|
pPortalPlayer->m_GrabControllerPersistentVars.m_bLastUpdateWasForcedPull = false;
|
|
#endif
|
|
|
|
if ( !IsUsingVMGrab() )
|
|
{
|
|
// Translate hold position and angles across portal
|
|
if ( pPortalPlayer->IsHeldObjectOnOppositeSideOfPortal() )
|
|
{
|
|
CPortal_Base2D *pPortalLinked = pPortal->m_hLinkedPortal;
|
|
if ( pPortal && pPortal->IsActive() && pPortalLinked != NULL )
|
|
{
|
|
Vector vTeleportedPosition;
|
|
QAngle qTeleportedAngles;
|
|
|
|
bool bHoldRayCrossesHeldPortal;
|
|
if( !bLookingAtHeldPortal && pPortalPlayer->IsHeldObjectOnOppositeSideOfPortal() )
|
|
{
|
|
Ray_t lastChanceRay;
|
|
lastChanceRay.Init( start, end - offset );
|
|
float fLastChanceCloser = 2.0f;
|
|
bHoldRayCrossesHeldPortal = ( UTIL_Portal_FirstAlongRay( lastChanceRay, fLastChanceCloser ) == pPortalPlayer->GetHeldObjectPortal() );
|
|
}
|
|
else
|
|
{
|
|
bHoldRayCrossesHeldPortal = false;
|
|
}
|
|
|
|
if ( !bLookingAtHeldPortal && !bHoldRayCrossesHeldPortal && ( start - pPortal->GetAbsOrigin() ).Length() > distance - radius )
|
|
{
|
|
// Pull the object through the portal
|
|
Vector vPortalLinkedForward;
|
|
pPortalLinked->GetVectors( &vPortalLinkedForward, NULL, NULL );
|
|
vTeleportedPosition = pPortalLinked->GetAbsOrigin() - vPortalLinkedForward * ( 1.0f + offset.Length() );
|
|
qTeleportedAngles = pPortalLinked->GetAbsAngles();
|
|
#if defined( GAME_DLL )
|
|
pPortalPlayer->m_GrabControllerPersistentVars.m_bLastUpdateWasForcedPull = true;
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
// Translate hold position and angles across the portal
|
|
VMatrix matThisToLinked = pPortal->MatrixThisToLinked();
|
|
UTIL_Portal_PointTransform( matThisToLinked, end - offset, vTeleportedPosition );
|
|
UTIL_Portal_AngleTransform( matThisToLinked, angles, qTeleportedAngles );
|
|
}
|
|
|
|
SetTargetPosition( vTeleportedPosition, qTeleportedAngles, bIsTeleport );
|
|
pPortalPlayer->SetHeldObjectPortal( pPortal );
|
|
}
|
|
else
|
|
{
|
|
pPlayer->ForceDropOfCarriedPhysObjects();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
#if defined( GAME_DLL )
|
|
if( pPortalPlayer->m_GrabControllerPersistentVars.m_hOscillationWatch.Get() != NULL )
|
|
{
|
|
//prevent an oscillation bug where our target position makes the held object teleport through a portal this tick, then teleport right back the next tick because we're not looking at the portal
|
|
CPortal_Base2D *pOscillationPortal = pPortalPlayer->m_GrabControllerPersistentVars.m_hOscillationWatch;
|
|
Vector vTargetPosition = end - offset;
|
|
float fPlaneDist = pOscillationPortal->m_plane_Origin.normal.Dot( vTargetPosition ) - pOscillationPortal->m_plane_Origin.dist;
|
|
if( fPlaneDist < 1.0f )
|
|
{
|
|
end += pOscillationPortal->m_plane_Origin.normal * (1.0f - fPlaneDist); //bump out to a minimum of 1 inch in front of the portal plane
|
|
}
|
|
}
|
|
#endif
|
|
SetTargetPosition( end - offset, angles, bIsTeleport );
|
|
pPortalPlayer->SetHeldObjectPortal( NULL );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SetTargetPosition( end - offset, angles, bIsTeleport );
|
|
pPortalPlayer->SetHeldObjectPortal( NULL );
|
|
}
|
|
|
|
#if defined( GAME_DLL )
|
|
if( GetAttached() )
|
|
{
|
|
pPortalPlayer->m_vecCarriedObject_CurPosToTargetPos = TransformVectorToPlayerSpace( m_shadow.targetPosition - GetAttached()->GetAbsOrigin(), pPlayer );
|
|
pPortalPlayer->m_vecCarriedObject_CurAngToTargetAng = m_shadow.targetRotation - GetAttached()->GetAbsAngles();
|
|
}
|
|
#endif
|
|
|
|
|
|
return true;
|
|
}
|
|
|
|
bool PlayerPickupControllerIsHoldingEntity( CBaseEntity *pPickupControllerEntity, CBaseEntity *pHeldEntity )
|
|
{
|
|
CPlayerPickupController *pController = dynamic_cast<CPlayerPickupController *>(pPickupControllerEntity);
|
|
return pController ? pController->IsHoldingEntity( pHeldEntity ) : false;
|
|
}
|
|
|
|
void ShutdownPickupController( CBaseEntity *pPickupControllerEntity )
|
|
{
|
|
CPlayerPickupController *pController = dynamic_cast<CPlayerPickupController *>(pPickupControllerEntity);
|
|
pController->Shutdown( false );
|
|
}
|
|
|
|
CBaseEntity *GetPlayerHeldEntity( CBasePlayer *pPlayer )
|
|
{
|
|
CBaseEntity *pObject = NULL;
|
|
#if !defined ( CLIENT_DLL )
|
|
CPlayerPickupController *pPlayerPickupController = (CPlayerPickupController *)(pPlayer->GetUseEntity());
|
|
|
|
if ( pPlayerPickupController )
|
|
{
|
|
pObject = pPlayerPickupController->GetGrabController().GetAttached();
|
|
}
|
|
#else
|
|
CPortal_Player *pPortalPlayer = (CPortal_Player*)pPlayer;
|
|
Assert ( pPlayer );
|
|
if ( pPlayer )
|
|
{
|
|
pObject = pPortalPlayer->m_pHeldEntityClone;
|
|
if ( !pObject )
|
|
{
|
|
pObject = pPortalPlayer->GetAttachedObject();
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return pObject;
|
|
}
|
|
|
|
CBasePlayer *GetPlayerHoldingEntity( const CBaseEntity *pEntity )
|
|
{
|
|
for( int i = 1; i <= gpGlobals->maxClients; ++i )
|
|
{
|
|
CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
|
|
if ( pPlayer )
|
|
{
|
|
#if defined( GAME_DLL )
|
|
if ( GetPlayerHeldEntity( pPlayer ) == pEntity )
|
|
#else
|
|
if( (((CPortal_Player*)pPlayer)->GetAttachedObject() == pEntity) ||
|
|
(((CPortal_Player*)pPlayer)->m_pHeldEntityClone == pEntity) )
|
|
#endif
|
|
{
|
|
return pPlayer;
|
|
}
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
CGrabController *GetGrabControllerForPlayer( CBasePlayer *pPlayer )
|
|
{
|
|
CPlayerPickupController *pPlayerPickupController = (CPlayerPickupController *)(pPlayer->GetUseEntity());
|
|
if( pPlayerPickupController )
|
|
return &(pPlayerPickupController->GetGrabController());
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void RotatePlayerHeldObject( CBasePlayer *pPlayer, float fRotAboutUp, float fRotAboutRight, bool bUseWorldUpInsteadOfPlayerUp )
|
|
{
|
|
CPlayerPickupController *pPlayerPickupController = (CPlayerPickupController *)(pPlayer->GetUseEntity());
|
|
if( pPlayerPickupController )
|
|
pPlayerPickupController->GetGrabController().RotateObject( pPlayer, fRotAboutUp, fRotAboutRight, bUseWorldUpInsteadOfPlayerUp );
|
|
}
|
|
|
|
#if !defined CLIENT_DLL
|
|
void GetSavedParamsForCarriedPhysObject( CGrabController *pGrabController, IPhysicsObject *pObject, float *pSavedMassOut, float *pSavedRotationalDampingOut )
|
|
{
|
|
if ( !pGrabController || !pObject )
|
|
{
|
|
Assert ( 0 );
|
|
return;
|
|
}
|
|
|
|
CBaseEntity *pHeld = pGrabController->m_attachedEntity;
|
|
if( pHeld )
|
|
{
|
|
if( pObject->GetGameData() == (void*)pHeld )
|
|
{
|
|
IPhysicsObject *pList[VPHYSICS_MAX_OBJECT_LIST_COUNT];
|
|
int count = pHeld->VPhysicsGetObjectList( pList, ARRAYSIZE(pList) );
|
|
for ( int i = 0; i < count; i++ )
|
|
{
|
|
if ( pList[i] == pObject )
|
|
{
|
|
if( pSavedMassOut )
|
|
*pSavedMassOut = pGrabController->m_savedMass[i];
|
|
|
|
if( pSavedRotationalDampingOut )
|
|
*pSavedRotationalDampingOut = pGrabController->m_savedRotDamping[i];
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if( pSavedMassOut )
|
|
*pSavedMassOut = 0.0f;
|
|
|
|
if( pSavedRotationalDampingOut )
|
|
*pSavedRotationalDampingOut = 0.0f;
|
|
|
|
return;
|
|
}
|
|
#endif // !CLIENT_DLL
|
|
|
|
void UpdateGrabControllerTargetPosition( CBasePlayer *pPlayer, Vector *vPosition, QAngle *qAngles, bool bIsTeleport /*= true*/ )
|
|
{
|
|
CGrabController *pGrabController = GetGrabControllerForPlayer( pPlayer );
|
|
|
|
if ( !pGrabController )
|
|
return;
|
|
|
|
pGrabController->UpdateObject( pPlayer, 12, bIsTeleport );
|
|
pGrabController->GetTargetPosition( vPosition, qAngles );
|
|
}
|
|
|
|
|
|
#if !defined ( CLIENT_DLL )
|
|
float PlayerPickupGetHeldObjectMass( CBaseEntity *pPickupControllerEntity, IPhysicsObject *pHeldObject )
|
|
{
|
|
float mass = 0.0f;
|
|
CPlayerPickupController *pController = dynamic_cast<CPlayerPickupController *>(pPickupControllerEntity);
|
|
if ( pController )
|
|
{
|
|
CGrabController &grab = pController->GetGrabController();
|
|
mass = grab.GetSavedMass( pHeldObject );
|
|
}
|
|
return mass;
|
|
}
|
|
#endif
|
|
|
|
|
|
void GrabController_SetPortalPenetratingEntity( CGrabController *pController, CBaseEntity *pPenetrated )
|
|
{
|
|
pController->SetPortalPenetratingEntity( pPenetrated );
|
|
}
|
|
|
|
struct collidelist_t
|
|
{
|
|
const CPhysCollide *pCollide;
|
|
Vector origin;
|
|
QAngle angles;
|
|
};
|
|
|
|
// TODO: Blatantly stolen from triggers.cpp... should peel this out into an engine feature
|
|
bool TestIntersectionVsHeldObjectCollide( CBaseEntity *pHeldObject, Vector vHeldObjectTestOrigin, CBaseEntity *pOther )
|
|
{
|
|
if ( !pHeldObject || !pOther || !pHeldObject->VPhysicsGetObject() || !pOther->CollisionProp() )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
switch ( pOther->GetSolid() )
|
|
{
|
|
case SOLID_BBOX:
|
|
{
|
|
ICollideable *pCollide = pHeldObject->CollisionProp();
|
|
Ray_t ray;
|
|
trace_t tr;
|
|
ray.Init( pOther->GetAbsOrigin(), pOther->GetAbsOrigin(), pOther->WorldAlignMins(), pOther->WorldAlignMaxs() );
|
|
enginetrace->ClipRayToCollideable( ray, MASK_SOLID, pCollide, &tr );
|
|
|
|
if ( tr.startsolid )
|
|
return true;
|
|
}
|
|
break;
|
|
case SOLID_BSP:
|
|
case SOLID_VPHYSICS:
|
|
{
|
|
CPhysCollide *pHeldObjectVCollide = modelinfo->GetVCollide( pHeldObject->GetModelIndex() )->solids[0];
|
|
Assert( pHeldObjectVCollide );
|
|
|
|
CUtlVector<collidelist_t> collideList;
|
|
IPhysicsObject *pList[VPHYSICS_MAX_OBJECT_LIST_COUNT];
|
|
int physicsCount = pHeldObject->VPhysicsGetObjectList( pList, ARRAYSIZE(pList) );
|
|
if ( physicsCount )
|
|
{
|
|
for ( int i = 0; i < physicsCount; i++ )
|
|
{
|
|
const CPhysCollide *pCollide = pList[i]->GetCollide();
|
|
if ( pCollide )
|
|
{
|
|
collidelist_t element;
|
|
element.pCollide = pCollide;
|
|
pList[i]->GetPosition( &element.origin, &element.angles );
|
|
element.origin = element.origin - pHeldObject->GetAbsOrigin() + vHeldObjectTestOrigin;
|
|
collideList.AddToTail( element );
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
vcollide_t *pVCollide = modelinfo->GetVCollide( pHeldObject->GetModelIndex() );
|
|
if ( pVCollide && pVCollide->solidCount )
|
|
{
|
|
collidelist_t element;
|
|
element.pCollide = pVCollide->solids[0];
|
|
element.origin = vHeldObjectTestOrigin;
|
|
element.angles = pHeldObject->GetAbsAngles();
|
|
collideList.AddToTail( element );
|
|
}
|
|
}
|
|
for ( int i = collideList.Count()-1; i >= 0; --i )
|
|
{
|
|
const collidelist_t &element = collideList[i];
|
|
trace_t tr;
|
|
physcollision->TraceCollide( element.origin, element.origin, element.pCollide, element.angles, pHeldObjectVCollide, vHeldObjectTestOrigin, pHeldObject->GetAbsAngles(), &tr );
|
|
if ( tr.startsolid )
|
|
return true;
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool CGrabController::IsUsingVMGrab( CBasePlayer *pPlayer ) const
|
|
{
|
|
if ( pPlayer == NULL )
|
|
{
|
|
pPlayer = (CPortal_Player*)m_hHoldingPlayer.Get();
|
|
}
|
|
|
|
CPortal_Player *pPortalPlayer = (CPortal_Player*)pPlayer;
|
|
if ( pPortalPlayer )
|
|
{
|
|
return pPortalPlayer->IsUsingVMGrab();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool CGrabController::WantsVMGrab( CBasePlayer *pPlayer ) const
|
|
{
|
|
if ( pPlayer == NULL )
|
|
{
|
|
pPlayer = (CPortal_Player*)m_hHoldingPlayer.Get();
|
|
}
|
|
|
|
CPortal_Player *pPortalPlayer = (CPortal_Player*)pPlayer;
|
|
if ( pPortalPlayer )
|
|
{
|
|
return pPortalPlayer->WantsVMGrab();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
#if defined( GAME_DLL )
|
|
void CGrabController::CheckPortalOscillation( CPortal_Base2D *pWentThroughPortal, CBaseEntity *pTeleportingEntity, CPortal_Player *pHoldingPlayer )
|
|
{
|
|
if( pHoldingPlayer->m_GrabControllerPersistentVars.m_hLookingThroughPortalLastUpdate.Get() != NULL ) //oscillation case only happens (for now) when you aren't looking into the portal
|
|
return;
|
|
|
|
if( pHoldingPlayer->m_GrabControllerPersistentVars.m_bLastUpdateWasForcedPull )
|
|
{
|
|
pHoldingPlayer->m_GrabControllerPersistentVars.m_hOscillationWatch = pWentThroughPortal->GetLinkedPortal();
|
|
}
|
|
else
|
|
{
|
|
float fPlaneDist = pWentThroughPortal->m_plane_Origin.normal.Dot( pHoldingPlayer->m_GrabControllerPersistentVars.m_vLastTargetPosition ) - pWentThroughPortal->m_plane_Origin.dist;
|
|
|
|
if( fPlaneDist < 1.0f && fPlaneDist > -5.0f ) //magic numbers, "kinda near the plane"
|
|
{
|
|
//doublecheck that the target isn't within the quad
|
|
Vector vPortalToTargetPos = pHoldingPlayer->m_GrabControllerPersistentVars.m_vLastTargetPosition - pWentThroughPortal->m_ptOrigin;
|
|
if( (fabs(pWentThroughPortal->m_vRight.Dot(vPortalToTargetPos)) >= pWentThroughPortal->GetHalfWidth()) ||
|
|
(fabs(pWentThroughPortal->m_vUp.Dot(vPortalToTargetPos)) >= pWentThroughPortal->GetHalfHeight()) )
|
|
{
|
|
pHoldingPlayer->m_GrabControllerPersistentVars.m_hOscillationWatch = pWentThroughPortal; //keep an eye on target positions for a while
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if defined ( GAME_DLL )
|
|
bool TestRayVsFuncClipVPhysics( const Ray_t& ray )
|
|
{
|
|
for( int i = 0; i != GetVPhysicsClipList().Count(); ++i )
|
|
{
|
|
VPhysicsClipEntry_t &checkEntry = GetVPhysicsClipList()[i];
|
|
|
|
if ( checkEntry.hEnt == NULL )
|
|
continue;
|
|
|
|
IPhysicsObject* pPhys = checkEntry.hEnt.Get()->VPhysicsGetObject();
|
|
// Don't check collision against vphys_clips that are disabled
|
|
if( pPhys && !pPhys->IsCollisionEnabled() )
|
|
continue;
|
|
|
|
if ( IsBoxIntersectingRay( checkEntry.vAABBMins, checkEntry.vAABBMaxs, ray ) )
|
|
{
|
|
// If we hit the AABB, perform the more expensive test vs physics
|
|
trace_t tr;
|
|
enginetrace->ClipRayToEntity( ray, MASK_SOLID, checkEntry.hEnt, &tr );
|
|
if ( tr.DidHit() )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool TestHeldEntityVsFuncClipVPhysics( CBaseEntity* pEnt, Vector testOrg )
|
|
{
|
|
Assert ( pEnt );
|
|
if ( !pEnt )
|
|
return false;
|
|
|
|
for( int i = 0; i != GetVPhysicsClipList().Count(); ++i )
|
|
{
|
|
VPhysicsClipEntry_t &checkEntry = GetVPhysicsClipList()[i];
|
|
|
|
if ( checkEntry.hEnt.Get() )
|
|
{
|
|
IPhysicsObject* pPhys = checkEntry.hEnt.Get()->VPhysicsGetObject();
|
|
// Don't check collision against vphys_clips that are disabled
|
|
if ( pPhys && !pPhys->IsCollisionEnabled() )
|
|
continue;
|
|
}
|
|
|
|
CCollisionProperty* pColProp = pEnt->CollisionProp();
|
|
Vector vMins = pColProp->GetCollisionOrigin() + pColProp->OBBMins();
|
|
Vector vMaxs = pColProp->GetCollisionOrigin() + pColProp->OBBMaxs();
|
|
if ( IsBoxIntersectingBox( vMins, vMaxs, checkEntry.vAABBMins, checkEntry.vAABBMaxs ) )
|
|
{
|
|
// If we hit the AABB, perform the more expensive test vs physics
|
|
if ( TestIntersectionVsHeldObjectCollide( pEnt, testOrg, checkEntry.hEnt ) )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
// When dropping, try to place this non-solid held object in a valid spot.
|
|
bool CGrabController::FindSafePlacementLocation( Vector *pVecPosition, bool bFinalPass )
|
|
{
|
|
if ( !pVecPosition )
|
|
return false;
|
|
|
|
CBaseEntity *pEntity = GetAttached();
|
|
if ( !pEntity )
|
|
return false;
|
|
|
|
if ( !pEntity->VPhysicsGetObject() )
|
|
return false;
|
|
|
|
// HACK: Some map logic will steal the held object out of our hands by setting its parent and motion disabling it
|
|
// to let that work, we'll pretend its a safe spot when we get motion disabled.
|
|
if ( pEntity->VPhysicsGetObject()->IsMotionEnabled() == false )
|
|
return true;
|
|
|
|
CPortal_Player *pPlayer = (CPortal_Player*)GetPlayerHoldingEntity( pEntity );
|
|
Assert ( pPlayer );
|
|
if ( !pPlayer )
|
|
return false;
|
|
|
|
Vector vecDropPosition = *pVecPosition;
|
|
|
|
// TODO: Scan for appropriate places like buttons or on top of a nearby ledge.
|
|
|
|
// Assume the player is in a safe space, then trace outwards to held position from there.
|
|
trace_t tr;
|
|
CTraceFilterSkipTwoEntities traceFilter( pPlayer, pEntity, COLLISION_GROUP_NONE );
|
|
//CTraceFilterSimple traceFilter( pEntity, COLLISION_GROUP_NONE );
|
|
Vector vecStartPosition = pPlayer->Weapon_ShootPosition();
|
|
|
|
int placementMask = MASK_SOLID | (CONTENTS_DEBRIS);
|
|
|
|
// Hrm.. Trace entity for portal is taking the 'portal' trace over the world trace which will in some situations
|
|
// hit nothing when a world geo wall is in the way. Trace against the world only for now, and handle portals separately.
|
|
// BUG: cant use the real angles with sweepcollideable... might cause bugs detecting obstructions.
|
|
enginetrace->SweepCollideable( pEntity->GetCollideable(), vecDropPosition, vecStartPosition, vec3_angle, placementMask, &traceFilter, &tr );
|
|
|
|
if ( debug_viewmodel_grabcontroller.GetBool() )
|
|
{
|
|
NDebugOverlay::Line( vecStartPosition, vecDropPosition, 200, 0, 0, false, 10.0f );
|
|
}
|
|
|
|
if ( tr.startsolid )
|
|
{
|
|
if ( tr.allsolid )
|
|
{
|
|
if ( bFinalPass && GameRules() && GameRules()->IsMultiplayer() )
|
|
{
|
|
// Drop it in their face!
|
|
tr.fraction = 1.0f;
|
|
tr.endpos = vecStartPosition;
|
|
vecDropPosition = tr.endpos;
|
|
|
|
if ( debug_viewmodel_grabcontroller.GetBool() )
|
|
{
|
|
NDebugOverlay::BoxAngles( vecDropPosition, pEntity->CollisionProp()->OBBMins(), pEntity->CollisionProp()->OBBMaxs(), pEntity->CollisionProp()->GetCollisionAngles(), 255, 0, 0, 128, 10.0f );
|
|
//NDebugOverlay::BoxAngles( vecStartPosition, pEntity->CollisionProp()->OBBMins(), pEntity->CollisionProp()->OBBMaxs(), pEntity->CollisionProp()->GetCollisionAngles(), 255, 0, 0, 128, 10.0f );
|
|
NDebugOverlay::EntityBounds( pPlayer, 255, 0, 0, 128, 10.0f );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( debug_viewmodel_grabcontroller.GetBool() )
|
|
{
|
|
NDebugOverlay::BoxAngles( vecDropPosition, pEntity->CollisionProp()->OBBMins(), pEntity->CollisionProp()->OBBMaxs(), pEntity->CollisionProp()->GetCollisionAngles(), 255, 0, 0, 128, 10.0f );
|
|
//NDebugOverlay::BoxAngles( vecStartPosition, pEntity->CollisionProp()->OBBMins(), pEntity->CollisionProp()->OBBMaxs(), pEntity->CollisionProp()->GetCollisionAngles(), 255, 0, 0, 128, 10.0f );
|
|
NDebugOverlay::EntityBounds( pPlayer, 255, 0, 0, 128, 10.0f );
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
tr.fraction = tr.fractionleftsolid;
|
|
Vector vDelta = tr.endpos - tr.startpos;
|
|
tr.endpos = tr.startpos + vDelta * tr.fraction;
|
|
vecDropPosition = tr.endpos;
|
|
}
|
|
}
|
|
|
|
if ( debug_viewmodel_grabcontroller.GetBool() && tr.DidHit() )
|
|
{
|
|
Msg("Found obstruction between player and drop position.\n" );
|
|
}
|
|
|
|
// SPECIAL CASE FOR PORTALS
|
|
// For dropping through portals, test the original held distance for any portals
|
|
// that might be closer than whatever obstruction we hit.
|
|
Ray_t ray;
|
|
ray.Init( pPlayer->Weapon_ShootPosition(), pEntity->GetLocalOrigin() );
|
|
// BUG: No equivelant of 'sweepcollideable' through portals... Going to do a non-extruded ray
|
|
// to test for collision in between the box and a potential portal.
|
|
trace_t trTestObstructionsNearPortals;
|
|
enginetrace->TraceRay( ray, placementMask, &traceFilter, &trTestObstructionsNearPortals );
|
|
float flWallHitFraction = trTestObstructionsNearPortals.fraction + 0.01f;
|
|
CPortal_Base2D *pPortal = UTIL_Portal_FirstAlongRay( ray, flWallHitFraction );
|
|
bool bHeldOnOppositeSideOfPortal = false;
|
|
// If this held object should drop on the other side of the portal.
|
|
// Put the start and end position on the other side of the portal and run the rest of the
|
|
// safe placement checks.
|
|
if ( trTestObstructionsNearPortals.DidHit() && pPortal )
|
|
{
|
|
if ( debug_viewmodel_grabcontroller.GetBool() )
|
|
{
|
|
Msg("Found portal along drop position, teleporting drop position to the other side\n" );
|
|
}
|
|
// Make the held start point be on the other side of the portal
|
|
float flRayHitFraction = UTIL_IntersectRayWithPortal( ray, pPortal );
|
|
Vector vNewStart;
|
|
Vector vHitPoint = ray.m_Start + ray.m_Delta*flRayHitFraction;
|
|
UTIL_Portal_PointTransform( pPortal->MatrixThisToLinked(), vHitPoint, vNewStart );
|
|
vecStartPosition = vNewStart;
|
|
|
|
// Set the drop position to be on the other side of the portal
|
|
Vector vTemp;
|
|
UTIL_Portal_PointTransform( pPortal->MatrixThisToLinked(), pEntity->GetLocalOrigin(), vTemp );
|
|
vecDropPosition = vTemp;
|
|
|
|
// This flag is for any further portal-crossing specific modifications to the safe placement checks below
|
|
bHeldOnOppositeSideOfPortal = true;
|
|
}
|
|
else
|
|
{
|
|
if ( debug_viewmodel_grabcontroller.GetBool() )
|
|
{
|
|
Msg("Not reorienting through portal. Trace hit: %s Found Portal %s\n", (tr.DidHit())?("yes"):("no"), (pPortal)?("yes"):("no") );
|
|
}
|
|
}
|
|
|
|
#if !defined ( CLIENT_DLL )
|
|
#if 0
|
|
// If the player is in a portal environment, add this held object to it so it knows to trace properly
|
|
CPortal_Base2D *pPlayerNearPortal = pPlayer->m_hPortalEnvironment.Get();
|
|
CPortalSimulator *pSimulator = CPortalSimulator::GetSimulatorThatOwnsEntity( pEntity );
|
|
if ( pPlayerNearPortal && pSimulator == NULL )
|
|
{
|
|
Assert ( pPortal == NULL || pPortal == pPlayerNearPortal );
|
|
pPlayerNearPortal->m_PortalSimulator.TakeOwnershipOfEntity( pEntity );
|
|
pSimulator = pPlayerNearPortal->m_PortalSimulator.GetLinkedPortalSimulator();
|
|
}
|
|
#endif
|
|
#endif
|
|
const Vector& stickNormal = pPlayer->GetPortalPlayerLocalData().m_StickNormal;
|
|
|
|
// Move outside of the player's box.
|
|
if ( bHeldOnOppositeSideOfPortal == false )
|
|
{
|
|
Vector vecDisplacement = vecDropPosition - vecStartPosition;
|
|
//vecDisplacement.z = 0;
|
|
Vector forward, right, up;
|
|
pPlayer->GetVectors( &forward, &right, &up );
|
|
Vector vecPlayerBodyForward;
|
|
CrossProduct( stickNormal, right, vecPlayerBodyForward );
|
|
|
|
// Project displacement onto player forward
|
|
vecDisplacement = vecPlayerBodyForward * ( vecDisplacement.Dot( vecPlayerBodyForward ) );
|
|
|
|
Vector vecMoveDir = vecDisplacement.Normalized();
|
|
|
|
Vector radial;
|
|
|
|
if( pEntity->VPhysicsGetObject() )
|
|
{
|
|
radial = physcollision->CollideGetExtent( pEntity->VPhysicsGetObject()->GetCollide(), vec3_origin, pEntity->GetAbsAngles(), -forward );
|
|
}
|
|
else
|
|
{
|
|
vcollide_t *pVCollide = modelinfo->GetVCollide( pEntity->GetModelIndex() );
|
|
if( pVCollide && (pVCollide->solidCount > 0) )
|
|
{
|
|
radial = physcollision->CollideGetExtent( pVCollide->solids[0], vec3_origin, pEntity->GetAbsAngles(), -forward );
|
|
}
|
|
else
|
|
{
|
|
radial = vec3_origin;
|
|
}
|
|
}
|
|
|
|
float flPlayer2DBoundingRadius = 22.62f;
|
|
float flRadius = radial.Length();
|
|
float flOverlap = (flRadius + flPlayer2DBoundingRadius) - vecDisplacement.Length();
|
|
if ( flOverlap < 0 )
|
|
flOverlap = 0;
|
|
|
|
vecDropPosition += vecMoveDir * flOverlap;
|
|
|
|
//vecDropPosition += stickNormal * 5.0f;
|
|
|
|
if ( debug_viewmodel_grabcontroller.GetBool() )
|
|
{
|
|
Msg("Moving drop position out of player's box, move dir: %f %f %f move length %f\n", XYZ(vecMoveDir), flOverlap );
|
|
}
|
|
}
|
|
|
|
// One more stuck check, if we're still in solid then don't drop
|
|
pEntity->SetCollisionGroup( m_preVMModeCollisionGroup );
|
|
pEntity->CollisionRulesChanged();
|
|
//UTIL_TraceEntity( pEntity, vecDropPosition, vecDropPosition, placementMask, &tr );
|
|
//TEMP: Ignore portal environments... need to debug this, but right now it's extremley easy to drop the box
|
|
// through the world because it gets simulated in the wrong environment and stays there.
|
|
enginetrace->SweepCollideable( pEntity->GetCollideable(), vecDropPosition, vecDropPosition, vec3_angle, placementMask, &traceFilter, &tr );
|
|
pEntity->SetCollisionGroup( COLLISION_GROUP_DEBRIS_TRIGGER );
|
|
|
|
bool bStuckInSolid = false;
|
|
if ( tr.DidHit() && TestIntersectionVsHeldObjectCollide( pEntity, vecDropPosition, tr.m_pEnt ) )
|
|
{
|
|
bStuckInSolid = true;
|
|
}
|
|
|
|
#if defined ( GAME_DLL )
|
|
if ( bStuckInSolid == false && test_for_vphysics_clips_when_dropping.GetBool() )
|
|
{
|
|
Ray_t toDropPos;
|
|
toDropPos.Init( vecStartPosition, vecDropPosition );
|
|
if ( TestRayVsFuncClipVPhysics( ray ) || TestHeldEntityVsFuncClipVPhysics( pEntity, vecDropPosition ) )
|
|
{
|
|
bStuckInSolid = true;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
bool bSuccess = ( bStuckInSolid == false ) && ( enginetrace->PointOutsideWorld( vecDropPosition ) == false );
|
|
|
|
// Do one more valid placement check making sure this object can make it to the new drop position
|
|
UTIL_TraceLine( vecStartPosition, vecDropPosition, placementMask, &traceFilter, &tr );
|
|
|
|
// Place if we can make it to the spot, the spot is clear and is not outside the world.
|
|
bSuccess = bSuccess && !tr.DidHit();
|
|
|
|
if ( debug_viewmodel_grabcontroller.GetBool() )
|
|
{
|
|
if ( bSuccess )
|
|
{
|
|
Msg( "Object not stuck at drop position, dropping successfully.\n" );
|
|
}
|
|
else
|
|
{
|
|
Msg( "Object stuck at drop position %f %f %f, refusing to drop object\n", XYZ(vecDropPosition) );
|
|
}
|
|
|
|
//NDebugOverlay::EntityBounds( pEntity, (bSuccess)?(0):(255), (bSuccess)?(255):(0), 0, 128, 10.0f );
|
|
NDebugOverlay::BoxAngles( vecDropPosition, pEntity->CollisionProp()->OBBMins(), pEntity->CollisionProp()->OBBMaxs(), pEntity->CollisionProp()->GetCollisionAngles(), (bSuccess)?(0):(255), (bSuccess)?(255):(0), 0, 128, 10.0f );
|
|
//NDebugOverlay::BoxAngles( vecStartPosition, pEntity->CollisionProp()->OBBMins(), pEntity->CollisionProp()->OBBMaxs(), pEntity->CollisionProp()->GetCollisionAngles(), (bSuccess)?(0):(255), (bSuccess)?(255):(0), 0, 128, 10.0f );
|
|
NDebugOverlay::EntityBounds( pPlayer, (bSuccess)?(0):(255), (bSuccess)?(255):(0), 0, 128, 10.0f );
|
|
}
|
|
|
|
|
|
#if !defined ( CLIENT_DLL )
|
|
#if 0
|
|
// When we're done with the traces, remove the object from the portal simulator (if any).
|
|
// When it becomes solid again it will go about portal hole logic in the normal way
|
|
if ( pSimulator )
|
|
{
|
|
pSimulator->ReleaseOwnershipOfEntity( pEntity );
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
if ( bSuccess && pVecPosition )
|
|
*pVecPosition = vecDropPosition;
|
|
return bSuccess;
|
|
|
|
}
|
|
|
|
void CGrabController::AttachEntityVM( CBasePlayer *pPlayer, CBaseEntity *pEntity, IPhysicsObject *pPhys, bool bIsMegaPhysCannon, const Vector &vGrabPosition, bool bUseGrabPosition )
|
|
{
|
|
if ( !WantsVMGrab( pPlayer ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
CPortal_Player *pPortalPlayer = (CPortal_Player*)pPlayer;
|
|
pPortalPlayer->SetUsingVMGrabState( true );
|
|
|
|
Assert( pEntity && pPhys );
|
|
if ( !pEntity || !pPhys )
|
|
return;
|
|
|
|
//NOTE: This is really here just for picking up objects across portals...
|
|
// this will prevent them from teleporting across the world. Could handle this
|
|
// as a special case if for some reason we want to 'lerp' into the player's hands.
|
|
UpdateObject( pPlayer, 12.0f );
|
|
#if defined( CLIENT_DLL )
|
|
if( pEntity->GetPredictable() )
|
|
#endif
|
|
{
|
|
pEntity->Teleport( &m_shadow.targetPosition, &m_shadow.targetRotation, NULL );
|
|
}
|
|
|
|
#if !defined ( CLIENT_DLL )
|
|
m_preVMModeCollisionGroup = pEntity->GetCollisionGroup();
|
|
pEntity->SetCollisionGroup( COLLISION_GROUP_DEBRIS_TRIGGER );
|
|
m_oldTransmitState = pEntity->GetTransmitState();
|
|
pEntity->VPhysicsGetObject()->EnableCollisions( false );
|
|
pEntity->SetTransmitState( FL_EDICT_ALWAYS );
|
|
m_bOldShadowState = ( pEntity->GetEffects() & EF_NOSHADOW ) == false;
|
|
pEntity->AddEffects( EF_NOSHADOW );
|
|
if ( GameRules()->IsMultiplayer() )
|
|
{
|
|
pEntity->AddEffects( EF_NODRAW );
|
|
}
|
|
CBaseAnimating* pAnim = pEntity->GetBaseAnimating();
|
|
Assert ( pAnim );
|
|
if ( pAnim )
|
|
{
|
|
m_hOldLightingOrigin = pAnim->GetLightingOrigin();
|
|
pAnim->SetLightingOrigin( pPlayer );
|
|
}
|
|
#endif
|
|
}
|
|
|
|
bool CGrabController::DetachEntityVM( bool bClearVelocity )
|
|
{
|
|
#if defined ( CLIENT_DLL )
|
|
// Server is in charge of detaching... when it does it will network down
|
|
// the change in held object and the client will react then.
|
|
return false;
|
|
#else
|
|
CBaseEntity *pEntity = GetAttached();
|
|
Assert ( pEntity );
|
|
if ( !pEntity )
|
|
{
|
|
if( m_controller )
|
|
{
|
|
if ( physenv != NULL )
|
|
{
|
|
physenv->DestroyMotionController( m_controller );
|
|
}
|
|
else
|
|
{
|
|
Warning( "%s(%d): Trying to dereference NULL physenv.\n", __FILE__, __LINE__ );
|
|
}
|
|
m_controller = NULL;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
CPortal_Player *pPlayer = (CPortal_Player*)GetPlayerHoldingEntity( pEntity );
|
|
if ( !pPlayer )
|
|
{
|
|
Assert( 0 );
|
|
return false;
|
|
}
|
|
|
|
Vector vecDropPosition = pEntity->GetAbsOrigin();
|
|
|
|
bool bFoundSafePlacementLocation = FindSafePlacementLocation( &vecDropPosition );
|
|
|
|
if ( !bFoundSafePlacementLocation )
|
|
{
|
|
// Scoot it up and try again
|
|
Vector vScootFromOriginal = pEntity->GetAbsOrigin() + Vector( 0.0f, 0.0f, ( pEntity->CollisionProp()->OBBMaxs().z - pEntity->CollisionProp()->OBBMins().z ) * 0.5f );
|
|
bFoundSafePlacementLocation = FindSafePlacementLocation( &vScootFromOriginal );
|
|
|
|
if ( bFoundSafePlacementLocation )
|
|
{
|
|
vecDropPosition = vScootFromOriginal;
|
|
}
|
|
else
|
|
{
|
|
// One more try... scoot up more
|
|
vecDropPosition = pEntity->GetAbsOrigin() + Vector( 0.0f, 0.0f, pEntity->CollisionProp()->OBBMaxs().z - pEntity->CollisionProp()->OBBMins().z );
|
|
bFoundSafePlacementLocation = FindSafePlacementLocation( &vecDropPosition, true );
|
|
}
|
|
}
|
|
|
|
if ( !bFoundSafePlacementLocation )
|
|
{
|
|
if ( !pPlayer->IsForcingDrop() )
|
|
{
|
|
ShowDenyPlacement();
|
|
return false;
|
|
}
|
|
#if !defined ( CLIENT_DLL )
|
|
else if ( GameRules()->IsMultiplayer() && V_strcmp( "mp_coop_rat_maze", STRING( gpGlobals->mapname ) ) == 0 )
|
|
{
|
|
// 83939: We need to drop this (probably due to player death) but it's in solid.
|
|
CBaseEntity* pEntInSafePlace = gEntList.FindEntityByName( NULL, "target_spawn_ratmaze_box" );
|
|
if ( pEntInSafePlace )
|
|
{
|
|
vecDropPosition = pEntInSafePlace->GetAbsOrigin();
|
|
}
|
|
}
|
|
else if ( GameRules()->IsMultiplayer() && V_strcmp( "mp_coop_tbeam_maze", STRING( gpGlobals->mapname ) ) == 0 )
|
|
{
|
|
// 85025: ..aaaand another one.
|
|
CBaseEntity* pEntInSafePlace = gEntList.FindEntityByName( NULL, "dropper-proxy" );
|
|
if ( pEntInSafePlace )
|
|
{
|
|
vecDropPosition = pEntInSafePlace->GetAbsOrigin();
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
pPlayer->SetUsingVMGrabState( false );
|
|
|
|
pEntity->SetCollisionGroup( m_preVMModeCollisionGroup );
|
|
pEntity->VPhysicsGetObject()->EnableCollisions( true );
|
|
pEntity->SetTransmitState( m_oldTransmitState );
|
|
if ( m_bOldShadowState )
|
|
{
|
|
pEntity->RemoveEffects( EF_NOSHADOW );
|
|
}
|
|
if ( GameRules()->IsMultiplayer() )
|
|
{
|
|
pEntity->RemoveEffects( EF_NODRAW );
|
|
}
|
|
CBaseAnimating* pAnim = pEntity->GetBaseAnimating();
|
|
Assert ( pAnim );
|
|
if ( pAnim )
|
|
{
|
|
pAnim->SetLightingOrigin( m_hOldLightingOrigin );
|
|
}
|
|
|
|
IPhysicsObject *pPhys = pEntity->VPhysicsGetObject();
|
|
if ( !pPhys )
|
|
{
|
|
Assert( 0 );
|
|
return false;
|
|
}
|
|
|
|
Vector vVelocity;
|
|
pPhys->GetVelocity( &vVelocity, NULL );
|
|
pEntity->Teleport( &vecDropPosition, NULL, &vVelocity );
|
|
pEntity->SetLocalVelocity( vec3_origin );
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
#if defined( CLIENT_DLL )
|
|
void CGrabController::DetachUnknownEntity( void )
|
|
{
|
|
if( m_controller )
|
|
{
|
|
physenv->DestroyMotionController( m_controller );
|
|
m_controller = NULL;
|
|
}
|
|
m_attachedEntity = NULL;
|
|
}
|
|
#endif
|
|
|
|
bool CGrabController::UpdateObjectVM( CBasePlayer *pPlayer, float flError )
|
|
{
|
|
CBaseEntity *pEnt = GetAttached();
|
|
CPortal_Player *pPortalPlayer = (CPortal_Player*)pPlayer;
|
|
m_hHoldingPlayer = pPortalPlayer;
|
|
|
|
#if defined( GAME_DLL )
|
|
if( pPortalPlayer )
|
|
{
|
|
pPortalPlayer->m_GrabControllerPersistentVars.ResetOscillationWatch();
|
|
}
|
|
#endif
|
|
|
|
Assert ( pEnt );
|
|
if ( !pEnt )
|
|
{
|
|
return false;
|
|
}
|
|
#if !defined ( CLIENT_DLL )
|
|
CBaseAnimating *pAnimating = (CBaseAnimating*)pEnt;
|
|
if ( pAnimating->IsDissolving() )
|
|
{
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
QAngle playerAngles = pPlayer->EyeAngles();
|
|
Vector forward, right, up;
|
|
AngleVectors( playerAngles, &forward, &right, &up );
|
|
Vector start = pPlayer->Weapon_ShootPosition();
|
|
|
|
float pitch = AngleDistance(playerAngles.x,0);
|
|
float flUpOffset = RemapValClamped( fabs(pitch), 0.0f, 75.0f, 1.0f, 0.0f ) * GetObjectOffset( pEnt );
|
|
// As we look down, hold the box more forward so it doesn't go under our 'feet'
|
|
// Also doing this for 'up' so we can see the ceiling
|
|
float scale = RemapValClamped( fabs(pitch), 0.0f, 75.0f, 0.0f, 1.0f );
|
|
float flHoldDistAdjust = player_held_object_look_down_adjustment.GetFloat() * scale;
|
|
const Vector& stickNormal = pPortalPlayer->GetPortalPlayerLocalData().m_StickNormal;
|
|
Vector vecPlayerBodyForward;
|
|
CrossProduct( stickNormal, right, vecPlayerBodyForward );
|
|
|
|
|
|
Vector vHoldOffset = forward * GetObjectDistance() + // Move a bit out of our face
|
|
vecPlayerBodyForward * flHoldDistAdjust + // As we look down move forward so it doesnt appear 'under' us
|
|
up * flUpOffset; // move down out of our face
|
|
|
|
// Run some traces to have a faux reaction to physical objects in the world
|
|
Ray_t ray;
|
|
ray.Init( start, start + vHoldOffset );
|
|
trace_t tr;
|
|
#if defined ( CLIENT_DLL )
|
|
|
|
//filter out the player and every version of this object
|
|
CTraceFilterSimpleList traceFilter( COLLISION_GROUP_DEBRIS );
|
|
{
|
|
traceFilter.AddEntityToIgnore( pPlayer );
|
|
traceFilter.AddEntityToIgnore( pEnt );
|
|
if( ((CPortal_Player *)pPlayer)->m_pHeldEntityClone )
|
|
{
|
|
if( (((CPortal_Player *)pPlayer)->m_pHeldEntityClone == pEnt) && //this ent is the held clone
|
|
((C_PlayerHeldObjectClone *)pEnt)->m_hOriginal ) //clone has original
|
|
{
|
|
traceFilter.AddEntityToIgnore( ((C_PlayerHeldObjectClone *)pEnt)->m_hOriginal );
|
|
}
|
|
else
|
|
{
|
|
traceFilter.AddEntityToIgnore( ((CPortal_Player *)pPlayer)->m_pHeldEntityClone );
|
|
}
|
|
}
|
|
if( ((CPortal_Player *)pPlayer)->m_pHeldEntityThirdpersonClone )
|
|
{
|
|
traceFilter.AddEntityToIgnore( ((CPortal_Player *)pPlayer)->m_pHeldEntityThirdpersonClone );
|
|
}
|
|
}
|
|
#else
|
|
CTraceFilterSkipTwoEntities traceFilter( pPlayer, pEnt, COLLISION_GROUP_DEBRIS );
|
|
#endif
|
|
UTIL_Portal_TraceRay( ray, MASK_SOLID, &traceFilter, &tr );
|
|
|
|
// reduce some of the forward held distance
|
|
float offsetscale = tr.fraction;
|
|
Vector vOffsetDir = vHoldOffset.Normalized();
|
|
float flOffsetDist = vHoldOffset.Length();
|
|
float scaledLength = flOffsetDist * offsetscale;
|
|
if ( scaledLength < player_held_object_min_distance.GetFloat() )
|
|
{
|
|
scaledLength = player_held_object_min_distance.GetFloat();
|
|
}
|
|
|
|
vHoldOffset = vOffsetDir * scaledLength;
|
|
|
|
Vector end = start + vHoldOffset;
|
|
|
|
#if !defined ( CLIENT_DLL )
|
|
// Send these down to the client
|
|
pPortalPlayer->m_vecCarriedObjectAngles = m_attachedAnglesPlayerSpace;
|
|
#endif
|
|
|
|
QAngle angles = TransformAnglesFromPlayerSpace( m_attachedAnglesPlayerSpace, pPlayer );
|
|
|
|
matrix3x4_t eyeMatrix;
|
|
AngleMatrix( pPlayer->EyeAngles(), eyeMatrix );
|
|
|
|
// If it has a preferred orientation, update to ensure we're still oriented correctly.
|
|
Pickup_GetPreferredCarryAngles( pEnt, pPlayer, eyeMatrix, angles );
|
|
|
|
// We may be holding a prop that has preferred carry angles
|
|
if ( m_bHasPreferredCarryAngles )
|
|
{
|
|
matrix3x4_t tmp;
|
|
ComputePlayerMatrix( pPlayer, tmp );
|
|
angles = TransformAnglesToWorldSpace( m_vecPreferredCarryAngles, tmp );
|
|
}
|
|
|
|
|
|
#if !defined ( CLIENT_DLL )
|
|
AngularImpulse angImpulse;
|
|
Vector vel;
|
|
IPhysicsObject *pPhys = pEnt->VPhysicsGetObject();
|
|
if ( !pPhys )
|
|
{
|
|
return false;
|
|
}
|
|
pPhys->GetVelocity( &vel, &angImpulse );
|
|
pEnt->SetLocalVelocity( vel );
|
|
|
|
// Don't let anything change the transmit state back to PVS_CHECK or we'll
|
|
// start disappearing when going through portals or standing near walls.
|
|
// HACK: This isn't ideal... maybe add a test in baseentity::UpdateTransmitState? Hard to do this for any
|
|
// possible held object and guarentee it'll get called because they dont all chain to base
|
|
pEnt->SetTransmitState( FL_EDICT_ALWAYS );
|
|
#endif
|
|
|
|
// If we use-denied, wiggle a bit to give feedback
|
|
if ( m_flAngleOffset > 0 || m_flLengthOffset > 0 )
|
|
{
|
|
float flOutput = sin( 20 * ( gpGlobals->curtime + m_flTimeOffset ) );
|
|
float flCurAngleOffset = m_flAngleOffset * flOutput;
|
|
float flCurLengthOffset = m_flLengthOffset * flOutput;
|
|
angles.y += flCurAngleOffset;
|
|
Vector vHoldDir = vHoldOffset.Normalized();
|
|
end += vHoldDir * flCurLengthOffset;
|
|
|
|
float flDecay = ExponentialDecay( 0.5, 0.1, gpGlobals->frametime );
|
|
m_flAngleOffset *= flDecay;
|
|
m_flLengthOffset *= flDecay;
|
|
|
|
if ( m_flAngleOffset < 0.1f )
|
|
m_flAngleOffset = 0;
|
|
|
|
if ( m_flLengthOffset < 0.1f )
|
|
m_flLengthOffset = 0;
|
|
}
|
|
|
|
|
|
SetTargetPosition( end, angles );
|
|
|
|
// Keep the object with this player even if they're moving too fast
|
|
m_fPlayerSpeed = pPlayer->GetAbsVelocity().Length();
|
|
|
|
PushNearbyTurrets();
|
|
|
|
return true;
|
|
}
|
|
|
|
void CGrabController::PushNearbyTurrets( void )
|
|
{
|
|
CTraceFilterSkipTwoEntities traceFilter( m_hHoldingPlayer, m_attachedEntity, COLLISION_GROUP_NONE );
|
|
trace_t tr;
|
|
enginetrace->SweepCollideable( m_attachedEntity->GetCollideable(), m_hHoldingPlayer->EyePosition(), m_shadow.targetPosition, vec3_angle, MASK_SOLID, &traceFilter, &tr );
|
|
|
|
if ( tr.m_pEnt && FClassnameIs( tr.m_pEnt, "npc_portal_turret_floor" ) )
|
|
{
|
|
// Apply fake force to feign 'knocking over' the turret.
|
|
Vector vHeldObjectVelocity = m_attachedEntity->GetAbsVelocity();
|
|
|
|
// held objects 'snap' to position so their velocities can be incredibly high.
|
|
// cap it at some sane max amount;
|
|
float flMaxMag = player_held_object_max_knock_magnitude.GetFloat();
|
|
|
|
if ( vHeldObjectVelocity.LengthSqr() > flMaxMag*flMaxMag )
|
|
{
|
|
vHeldObjectVelocity.NormalizeInPlace();
|
|
vHeldObjectVelocity *= flMaxMag;
|
|
}
|
|
|
|
tr.m_pEnt->ApplyAbsVelocityImpulse( vHeldObjectVelocity );
|
|
}
|
|
}
|
|
|
|
|
|
void CGrabController::ShowDenyPlacement( void )
|
|
{
|
|
m_flAngleOffset = 10.0f;
|
|
m_flLengthOffset = 5.0f;
|
|
// Always start at (well, close to) sin( x*pi ) so we don't pop positions/angles
|
|
m_flTimeOffset = -fmod( gpGlobals->curtime, 180.0f );
|
|
}
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// C_PlayerHeldObjectClone
|
|
//////////////////////////////////////////////////////////////////////////
|
|
#if defined ( CLIENT_DLL )
|
|
LINK_ENTITY_TO_CLASS_CLIENTONLY( player_held_object_clone, C_PlayerHeldObjectClone );
|
|
|
|
C_PlayerHeldObjectClone::~C_PlayerHeldObjectClone()
|
|
{
|
|
DestroyShadow();
|
|
DestroyModelInstance();
|
|
VPhysicsDestroyObject();
|
|
Term();
|
|
}
|
|
|
|
bool C_PlayerHeldObjectClone::InitClone( C_BaseEntity *pObject, C_BasePlayer *pPlayer, bool bIsViewModel, C_PlayerHeldObjectClone *pVMToFollow )
|
|
{
|
|
if ( !pObject )
|
|
return false;
|
|
|
|
m_hPlayer = pPlayer;
|
|
m_hOriginal = pObject;
|
|
m_pVMToFollow = pVMToFollow;
|
|
|
|
const char *pModelName = modelinfo->GetModelName( pObject->GetModel() );
|
|
|
|
Assert ( pModelName );
|
|
|
|
if ( !pModelName )
|
|
return false;
|
|
|
|
if ( InitializeAsClientEntity( pModelName, bIsViewModel ) == false )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
m_bCanUseFastPath = false;
|
|
|
|
SetAbsOrigin( pObject->GetAbsOrigin() );
|
|
|
|
// This isn't needed... The old grab code assumes a physics object so
|
|
// while we want to have the option of both types we need a 'dummy' object.
|
|
solid_t tmpSolid;
|
|
PhysModelParseSolid( tmpSolid, this, GetModelIndex() );
|
|
SetSolid( SOLID_VPHYSICS );
|
|
m_pPhysicsObject = VPhysicsInitShadow( false, false, &tmpSolid );
|
|
|
|
if ( m_pPhysicsObject )
|
|
{
|
|
PhysSetGameFlags( m_pPhysicsObject, FVPHYSICS_PLAYER_HELD );
|
|
m_pPhysicsObject->EnableCollisions( !bIsViewModel );
|
|
SetCollisionGroup( COLLISION_GROUP_DEBRIS_TRIGGER );
|
|
}
|
|
|
|
if ( !bIsViewModel )
|
|
{
|
|
// make ghost renderables
|
|
SetMoveType( MOVETYPE_CUSTOM );
|
|
}
|
|
else
|
|
{
|
|
// Let the non view model cast the shadow
|
|
AddEffects( EF_NOSHADOW );
|
|
}
|
|
|
|
const model_t *mod = GetModel();
|
|
if ( mod )
|
|
{
|
|
Vector mins, maxs;
|
|
modelinfo->GetModelBounds( mod, mins, maxs );
|
|
SetCollisionBounds( mins, maxs );
|
|
}
|
|
|
|
OnDataChanged( DATA_UPDATE_CREATED );
|
|
CollisionProp()->UpdatePartition();
|
|
CreateShadow();
|
|
|
|
CreateModelInstance();
|
|
pObject->SnatchModelInstance( this );
|
|
|
|
m_nOldSkin = pObject->GetSkin();
|
|
SetSkin( m_nOldSkin );
|
|
|
|
m_bOnOppositeSideOfPortal = false;
|
|
|
|
SetNextClientThink( CLIENT_THINK_ALWAYS );
|
|
|
|
return true;
|
|
}
|
|
|
|
void C_PlayerHeldObjectClone::ClientThink()
|
|
{
|
|
if ( m_hOriginal.Get() )
|
|
{
|
|
if ( m_nOldSkin != m_hOriginal->GetSkin() )
|
|
{
|
|
m_nOldSkin = m_hOriginal->GetSkin();
|
|
SetSkin( m_nOldSkin );
|
|
}
|
|
}
|
|
|
|
if ( m_pVMToFollow )
|
|
{
|
|
SetAbsOrigin( m_pVMToFollow->GetAbsOrigin() );
|
|
SetAbsAngles( m_pVMToFollow->GetAbsAngles() );
|
|
|
|
m_bOnOppositeSideOfPortal = false;
|
|
|
|
CPortal_Player *pPlayer = (CPortal_Player *)m_hPlayer.Get();
|
|
|
|
if ( pPlayer )
|
|
{
|
|
Vector vForward;
|
|
m_hPlayer->EyeVectors( &vForward, NULL, NULL );
|
|
vForward.z = 0.0f;
|
|
VectorNormalize( vForward );
|
|
|
|
Vector vEyePos = m_hPlayer->EyePosition();
|
|
Vector vStart( vEyePos.x, vEyePos.y, GetAbsOrigin().z );
|
|
|
|
Ray_t ray;
|
|
ray.Init( vStart, GetAbsOrigin() );
|
|
float fCloser = 2.0f;
|
|
CPortal_Base2D *pPortal = UTIL_Portal_FirstAlongRay( ray, fCloser );
|
|
if( pPortal == NULL )
|
|
{
|
|
pPortal = pPlayer->m_hPortalEnvironment;
|
|
}
|
|
ray.Init( vStart + vForward * 20.f, GetAbsOrigin(), GetCollideable()->OBBMins(), GetCollideable()->OBBMaxs() );
|
|
|
|
CTraceFilterSkipTwoEntities filter;
|
|
filter.SetPassEntity( m_hPlayer );
|
|
filter.SetPassEntity2( m_hOriginal );
|
|
|
|
trace_t tr;
|
|
UTIL_Portal_TraceRay_With( pPortal, ray, MASK_SOLID, &filter, &tr );
|
|
|
|
if ( tr.startsolid )
|
|
{
|
|
tr.endpos = m_hPlayer->EyePosition();
|
|
}
|
|
|
|
Vector vNewPos = tr.endpos;
|
|
float fZDiff = GetAbsOrigin().z - vNewPos.z;
|
|
|
|
if ( fZDiff > 0.0f && tr.plane.normal.z > -0.5f || fZDiff < 0.0f && tr.plane.normal.z < 0.5f )
|
|
{
|
|
vNewPos.z += fZDiff * 0.5f; // Move us halfway to the intended z pos
|
|
}
|
|
|
|
if ( pPortal )
|
|
{
|
|
Vector vTracePos = vNewPos;
|
|
|
|
// Make sure the center has passed through portal
|
|
ray.Init( m_hPlayer->EyePosition(), vTracePos );
|
|
pPortal = UTIL_Portal_TraceRay( ray, MASK_SOLID, &filter, &tr );
|
|
if ( pPortal )
|
|
{
|
|
m_bOnOppositeSideOfPortal = true;
|
|
|
|
QAngle angTransformed;
|
|
UTIL_Portal_AngleTransform( pPortal->m_matrixThisToLinked, GetAbsAngles(), angTransformed );
|
|
SetAbsAngles( angTransformed );
|
|
|
|
UTIL_Portal_PointTransform( pPortal->m_matrixThisToLinked, vTracePos, vNewPos );
|
|
}
|
|
}
|
|
|
|
SetAbsOrigin( vNewPos );
|
|
}
|
|
}
|
|
else //!m_pVMToFollow
|
|
{
|
|
C_BasePlayer *pOwner = m_hPlayer;
|
|
if( pOwner )
|
|
{
|
|
Vector vInterpolatedOrigin, vForward, vUp, vRight;
|
|
pOwner->EyePositionAndVectors( &vInterpolatedOrigin, &vForward, &vRight, &vUp );
|
|
vInterpolatedOrigin += vForward * m_vPlayerRelativeOrigin.x;
|
|
vInterpolatedOrigin += vRight * m_vPlayerRelativeOrigin.y;
|
|
vInterpolatedOrigin += vUp * m_vPlayerRelativeOrigin.z;
|
|
SetAbsOrigin( vInterpolatedOrigin );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
bool C_PlayerHeldObjectClone::OnInternalDrawModel( ClientModelRenderInfo_t *pInfo )
|
|
{
|
|
Assert ( m_hPlayer.Get() );
|
|
if ( m_hPlayer.Get() )
|
|
{
|
|
pInfo->pLightingOrigin = &(m_hPlayer->GetAbsOrigin());
|
|
}
|
|
return true;
|
|
}
|
|
|
|
int C_PlayerHeldObjectClone::DrawModel( int flags, const RenderableInstance_t &instance )
|
|
{
|
|
if ( IsRenderingWithViewModels() )
|
|
{
|
|
if ( m_hPlayer.Get() && m_hPlayer.Get() == C_BasePlayer::GetLocalPlayer() )
|
|
{
|
|
if ( g_pPortalRender->GetViewRecursionLevel() > 0 )
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( m_hPlayer.Get() && (m_hPlayer.Get() == C_BasePlayer::GetLocalPlayer()) && !m_hPlayer->ShouldDrawLocalPlayer() )
|
|
{
|
|
if ( g_pPortalRender->GetViewRecursionLevel() < 1 )
|
|
{
|
|
if ( !m_bOnOppositeSideOfPortal )
|
|
return 0;
|
|
}
|
|
else if ( g_pPortalRender->GetViewRecursionLevel() < 2 )
|
|
{
|
|
if ( m_bOnOppositeSideOfPortal )
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
return BaseClass::DrawModel( flags, instance );
|
|
}
|
|
|
|
#if 0
|
|
void C_PlayerHeldObjectClone::GetColorModulation( float* color )
|
|
{
|
|
if( m_pVMToFollow )
|
|
{
|
|
color[0] = 0.0f;
|
|
color[1] = 0.0f;
|
|
color[2] = 1.0f;
|
|
}
|
|
else
|
|
{
|
|
color[0] = 0.0f;
|
|
color[1] = 1.0f;
|
|
color[2] = 0.0f;
|
|
return;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
|
|
bool C_PlayerHeldObjectClone::HasPreferredCarryAnglesForPlayer( CBasePlayer *pPlayer )
|
|
{
|
|
if ( m_hOriginal.Get() )
|
|
{
|
|
IPlayerPickupVPhysics *pPickupPhys = dynamic_cast<IPlayerPickupVPhysics *>(m_hOriginal.Get());
|
|
if ( pPickupPhys )
|
|
{
|
|
return pPickupPhys->HasPreferredCarryAnglesForPlayer( pPlayer );
|
|
}
|
|
}
|
|
|
|
return CDefaultPlayerPickupVPhysics::HasPreferredCarryAnglesForPlayer( pPlayer );
|
|
}
|
|
|
|
QAngle C_PlayerHeldObjectClone::PreferredCarryAngles( void )
|
|
{
|
|
if ( m_hOriginal.Get() )
|
|
{
|
|
IPlayerPickupVPhysics *pPickupPhys = dynamic_cast<IPlayerPickupVPhysics *>(m_hOriginal.Get());
|
|
if ( pPickupPhys )
|
|
{
|
|
return pPickupPhys->PreferredCarryAngles();
|
|
}
|
|
}
|
|
|
|
return CDefaultPlayerPickupVPhysics::PreferredCarryAngles();
|
|
}
|
|
|
|
#endif
|
|
|
|
|