mirror of
https://github.com/alliedmodders/hl2sdk.git
synced 2024-12-23 01:59:43 +08:00
3686 lines
104 KiB
C++
3686 lines
104 KiB
C++
//========= Copyright Valve Corporation, All rights reserved. ============//
|
|
//
|
|
// Purpose: Physics cannon
|
|
//
|
|
//=============================================================================//
|
|
|
|
#include "cbase.h"
|
|
|
|
#ifdef CLIENT_DLL
|
|
#include "c_hl2mp_player.h"
|
|
#include "vcollide_parse.h"
|
|
#include "engine/ivdebugoverlay.h"
|
|
#include "iviewrender_beams.h"
|
|
#include "beamdraw.h"
|
|
#include "c_te_effect_dispatch.h"
|
|
#include "model_types.h"
|
|
#include "clienteffectprecachesystem.h"
|
|
#include "fx_interpvalue.h"
|
|
#else
|
|
#include "hl2mp_player.h"
|
|
#include "soundent.h"
|
|
#include "ndebugoverlay.h"
|
|
#include "ai_basenpc.h"
|
|
#include "player_pickup.h"
|
|
#include "physics_prop_ragdoll.h"
|
|
#include "globalstate.h"
|
|
#include "props.h"
|
|
#include "te_effect_dispatch.h"
|
|
#include "util.h"
|
|
#endif
|
|
|
|
#include "gamerules.h"
|
|
#include "soundenvelope.h"
|
|
#include "engine/IEngineSound.h"
|
|
#include "physics.h"
|
|
#include "in_buttons.h"
|
|
#include "IEffects.h"
|
|
#include "shake.h"
|
|
#include "beam_shared.h"
|
|
#include "Sprite.h"
|
|
#include "weapon_physcannon.h"
|
|
#include "physics_saverestore.h"
|
|
#include "movevars_shared.h"
|
|
#include "weapon_hl2mpbasehlmpcombatweapon.h"
|
|
#include "vphysics/friction.h"
|
|
#include "debugoverlay_shared.h"
|
|
|
|
// memdbgon must be the last include file in a .cpp file!!!
|
|
#include "tier0/memdbgon.h"
|
|
|
|
#define SPRITE_SCALE 128.0f
|
|
|
|
static const char *s_pWaitForUpgradeContext = "WaitForUpgrade";
|
|
|
|
ConVar g_debug_physcannon( "g_debug_physcannon", "0", FCVAR_REPLICATED | FCVAR_CHEAT );
|
|
|
|
ConVar physcannon_minforce( "physcannon_minforce", "700", FCVAR_REPLICATED | FCVAR_CHEAT );
|
|
ConVar physcannon_maxforce( "physcannon_maxforce", "1500", FCVAR_REPLICATED | FCVAR_CHEAT );
|
|
ConVar physcannon_maxmass( "physcannon_maxmass", "250", FCVAR_REPLICATED | FCVAR_CHEAT );
|
|
ConVar physcannon_tracelength( "physcannon_tracelength", "250", FCVAR_REPLICATED | FCVAR_CHEAT );
|
|
ConVar physcannon_chargetime("physcannon_chargetime", "2", FCVAR_REPLICATED | FCVAR_CHEAT );
|
|
ConVar physcannon_pullforce( "physcannon_pullforce", "4000", FCVAR_REPLICATED | FCVAR_CHEAT );
|
|
ConVar physcannon_cone( "physcannon_cone", "0.97", FCVAR_REPLICATED | FCVAR_CHEAT );
|
|
ConVar physcannon_ball_cone( "physcannon_ball_cone", "0.997", FCVAR_REPLICATED | FCVAR_CHEAT );
|
|
ConVar player_throwforce( "player_throwforce", "1000", FCVAR_REPLICATED | FCVAR_CHEAT );
|
|
|
|
#ifndef CLIENT_DLL
|
|
extern ConVar hl2_normspeed;
|
|
extern ConVar hl2_walkspeed;
|
|
#endif
|
|
|
|
#define PHYSCANNON_BEAM_SPRITE "sprites/orangelight1.vmt"
|
|
#define PHYSCANNON_BEAM_SPRITE_NOZ "sprites/orangelight1_noz.vmt"
|
|
#define PHYSCANNON_GLOW_SPRITE "sprites/glow04_noz"
|
|
#define PHYSCANNON_ENDCAP_SPRITE "sprites/orangeflare1"
|
|
#define PHYSCANNON_CENTER_GLOW "sprites/orangecore1"
|
|
#define PHYSCANNON_BLAST_SPRITE "sprites/orangecore2"
|
|
|
|
#ifdef CLIENT_DLL
|
|
|
|
//Precahce the effects
|
|
CLIENTEFFECT_REGISTER_BEGIN( PrecacheEffectPhysCannon )
|
|
CLIENTEFFECT_MATERIAL( "sprites/orangelight1" )
|
|
CLIENTEFFECT_MATERIAL( "sprites/orangelight1_noz" )
|
|
CLIENTEFFECT_MATERIAL( PHYSCANNON_GLOW_SPRITE )
|
|
CLIENTEFFECT_MATERIAL( PHYSCANNON_ENDCAP_SPRITE )
|
|
CLIENTEFFECT_MATERIAL( PHYSCANNON_CENTER_GLOW )
|
|
CLIENTEFFECT_MATERIAL( PHYSCANNON_BLAST_SPRITE )
|
|
CLIENTEFFECT_REGISTER_END()
|
|
|
|
#endif // CLIENT_DLL
|
|
|
|
#ifndef CLIENT_DLL
|
|
|
|
void PhysCannonBeginUpgrade( CBaseAnimating *pAnim )
|
|
{
|
|
|
|
}
|
|
|
|
bool PlayerHasMegaPhysCannon( void )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool PhysCannonAccountableForObject( CBaseCombatWeapon *pPhysCannon, CBaseEntity *pObject )
|
|
{
|
|
// BRJ: FIXME! This can't be implemented trivially, so I'm leaving it to Steve or Adrian
|
|
Assert( 0 );
|
|
return false;
|
|
}
|
|
|
|
#endif
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
// this will hit skip the pass entity, but not anything it owns
|
|
// (lets player grab own grenades)
|
|
class CTraceFilterNoOwnerTest : public CTraceFilterSimple
|
|
{
|
|
public:
|
|
DECLARE_CLASS( CTraceFilterNoOwnerTest, CTraceFilterSimple );
|
|
|
|
CTraceFilterNoOwnerTest( const IHandleEntity *passentity, int collisionGroup )
|
|
: CTraceFilterSimple( NULL, collisionGroup ), m_pPassNotOwner(passentity)
|
|
{
|
|
}
|
|
|
|
virtual bool ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask )
|
|
{
|
|
if ( pHandleEntity != m_pPassNotOwner )
|
|
return BaseClass::ShouldHitEntity( pHandleEntity, contentsMask );
|
|
|
|
return false;
|
|
}
|
|
|
|
protected:
|
|
const IHandleEntity *m_pPassNotOwner;
|
|
};
|
|
|
|
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
|
|
|
|
static 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:
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// derive from this so we can add save/load data to it
|
|
struct game_shadowcontrol_params_t : public hlshadowcontrol_params_t
|
|
{
|
|
DECLARE_SIMPLE_DATADESC();
|
|
};
|
|
|
|
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()
|
|
|
|
//-----------------------------------------------------------------------------
|
|
class CGrabController : public IMotionEvent
|
|
{
|
|
public:
|
|
|
|
CGrabController( void );
|
|
~CGrabController( void );
|
|
void AttachEntity( CBasePlayer *pPlayer, CBaseEntity *pEntity, IPhysicsObject *pPhys, bool bIsMegaPhysCannon, const Vector &vGrabPosition, bool bUseGrabPosition );
|
|
void DetachEntity( bool bClearVelocity );
|
|
void OnRestore();
|
|
|
|
bool UpdateObject( CBasePlayer *pPlayer, float flError );
|
|
|
|
void SetTargetPosition( const Vector &target, const QAngle &targetOrientation );
|
|
float ComputeError();
|
|
float GetLoadWeight( void ) const { return m_flLoadWeight; }
|
|
void SetAngleAlignment( float alignAngleCosine ) { m_angleAlignment = alignAngleCosine; }
|
|
void SetIgnorePitch( bool bIgnore ) { m_bIgnoreRelativePitch = bIgnore; }
|
|
QAngle TransformAnglesToPlayerSpace( const QAngle &anglesIn, CBasePlayer *pPlayer );
|
|
QAngle TransformAnglesFromPlayerSpace( const QAngle &anglesIn, CBasePlayer *pPlayer );
|
|
|
|
CBaseEntity *GetAttached() { return (CBaseEntity *)m_attachedEntity; }
|
|
|
|
IMotionEvent::simresult_e Simulate( IPhysicsMotionController *pController, IPhysicsObject *pObject, float deltaTime, Vector &linear, AngularImpulse &angular );
|
|
float GetSavedMass( IPhysicsObject *pObject );
|
|
|
|
QAngle m_attachedAnglesPlayerSpace;
|
|
Vector m_attachedPositionObjectSpace;
|
|
|
|
private:
|
|
// Compute the max speed for an attached object
|
|
void ComputeMaxSpeed( CBaseEntity *pEntity, IPhysicsObject *pPhysics );
|
|
|
|
game_shadowcontrol_params_t m_shadow;
|
|
float m_timeToArrive;
|
|
float m_errorTime;
|
|
float m_error;
|
|
float m_contactAmount;
|
|
float m_angleAlignment;
|
|
bool m_bCarriedEntityBlocksLOS;
|
|
bool m_bIgnoreRelativePitch;
|
|
|
|
float m_flLoadWeight;
|
|
float m_savedRotDamping[VPHYSICS_MAX_OBJECT_LIST_COUNT];
|
|
float m_savedMass[VPHYSICS_MAX_OBJECT_LIST_COUNT];
|
|
EHANDLE m_attachedEntity;
|
|
QAngle m_vecPreferredCarryAngles;
|
|
bool m_bHasPreferredCarryAngles;
|
|
|
|
|
|
IPhysicsMotionController *m_controller;
|
|
int m_frameCount;
|
|
friend class CWeaponPhysCannon;
|
|
};
|
|
|
|
const float DEFAULT_MAX_ANGULAR = 360.0f * 10.0f;
|
|
const float REDUCED_CARRY_MASS = 1.0f;
|
|
|
|
CGrabController::CGrabController( void )
|
|
{
|
|
m_shadow.dampFactor = 1.0;
|
|
m_shadow.teleportDistance = 0;
|
|
m_errorTime = 0;
|
|
m_error = 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_attachedEntity = NULL;
|
|
m_vecPreferredCarryAngles = vec3_angle;
|
|
m_bHasPreferredCarryAngles = false;
|
|
}
|
|
|
|
CGrabController::~CGrabController( void )
|
|
{
|
|
DetachEntity( false );
|
|
}
|
|
|
|
void CGrabController::OnRestore()
|
|
{
|
|
if ( m_controller )
|
|
{
|
|
m_controller->SetEventHandler( this );
|
|
}
|
|
}
|
|
|
|
void CGrabController::SetTargetPosition( const Vector &target, const QAngle &targetOrientation )
|
|
{
|
|
m_shadow.targetPosition = target;
|
|
m_shadow.targetRotation = targetOrientation;
|
|
|
|
m_timeToArrive = gpGlobals->frametime;
|
|
|
|
CBaseEntity *pAttached = GetAttached();
|
|
if ( pAttached )
|
|
{
|
|
IPhysicsObject *pObj = pAttached->VPhysicsGetObject();
|
|
|
|
if ( pObj != NULL )
|
|
{
|
|
pObj->Wake();
|
|
}
|
|
else
|
|
{
|
|
DetachEntity( false );
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Output : float
|
|
//-----------------------------------------------------------------------------
|
|
float CGrabController::ComputeError()
|
|
{
|
|
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;
|
|
}
|
|
|
|
m_errorTime = 0;
|
|
|
|
return m_error;
|
|
}
|
|
|
|
|
|
#define MASS_SPEED_SCALE 60
|
|
#define MAX_MASS 40
|
|
|
|
void CGrabController::ComputeMaxSpeed( CBaseEntity *pEntity, IPhysicsObject *pPhysics )
|
|
{
|
|
#ifndef CLIENT_DLL
|
|
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 );
|
|
#endif
|
|
}
|
|
|
|
|
|
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();
|
|
angleTest.x = 0;
|
|
AngleMatrix( angleTest, test );
|
|
return TransformAnglesToWorldSpace( anglesIn, test );
|
|
}
|
|
return TransformAnglesToWorldSpace( anglesIn, pPlayer->EntityToWorldTransform() );
|
|
}
|
|
|
|
|
|
void CGrabController::AttachEntity( CBasePlayer *pPlayer, CBaseEntity *pEntity, IPhysicsObject *pPhys, bool bIsMegaPhysCannon, const Vector &vGrabPosition, bool bUseGrabPosition )
|
|
{
|
|
// play the impact sound of the object hitting the player
|
|
// used as feedback to let the player know he picked up the object
|
|
#ifndef CLIENT_DLL
|
|
PhysicsImpactSound( pPlayer, pPhys, CHAN_STATIC, pPhys->GetMaterialIndex(), pPlayer->VPhysicsGetObject()->GetMaterialIndex(), 1.0, 64 );
|
|
#endif
|
|
Vector position;
|
|
QAngle angles;
|
|
pPhys->GetPosition( &position, &angles );
|
|
// If it has a preferred orientation, use that instead.
|
|
#ifndef CLIENT_DLL
|
|
Pickup_GetPreferredCarryAngles( pEntity, pPlayer, pPlayer->EntityToWorldTransform(), angles );
|
|
#endif
|
|
|
|
// ComputeMaxSpeed( pEntity, pPhys );
|
|
|
|
// Carried entities can never block LOS
|
|
m_bCarriedEntityBlocksLOS = pEntity->BlocksLOS();
|
|
pEntity->SetBlocksLOS( false );
|
|
m_controller = physenv->CreateMotionController( this );
|
|
m_controller->AttachObject( pPhys, true );
|
|
// Don't do this, it's causing trouble with constraint solvers.
|
|
//m_controller->SetPriority( IPhysicsMotionController::HIGH_PRIORITY );
|
|
|
|
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 damping = 10;
|
|
float flFactor = count / 7.5f;
|
|
if ( flFactor < 1.0f )
|
|
{
|
|
flFactor = 1.0f;
|
|
}
|
|
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 );
|
|
}
|
|
|
|
// Give extra mass to the phys object we're actually picking up
|
|
pPhys->SetMass( REDUCED_CARRY_MASS );
|
|
pPhys->EnableDrag( false );
|
|
|
|
m_errorTime = -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 );
|
|
}
|
|
|
|
VectorITransform( pEntity->WorldSpaceCenter(), pEntity->EntityToWorldTransform(), m_attachedPositionObjectSpace );
|
|
|
|
#ifndef CLIENT_DLL
|
|
// If it's a prop, see if it has desired carry angles
|
|
CPhysicsProp *pProp = dynamic_cast<CPhysicsProp *>(pEntity);
|
|
if ( pProp )
|
|
{
|
|
m_bHasPreferredCarryAngles = pProp->GetPropDataAngles( "preferred_carryangles", m_vecPreferredCarryAngles );
|
|
}
|
|
else
|
|
{
|
|
m_bHasPreferredCarryAngles = false;
|
|
}
|
|
#else
|
|
|
|
m_bHasPreferredCarryAngles = false;
|
|
#endif
|
|
|
|
}
|
|
|
|
static void ClampPhysicsVelocity( IPhysicsObject *pPhys, float linearLimit, float angularLimit )
|
|
{
|
|
Vector vel;
|
|
AngularImpulse angVel;
|
|
pPhys->GetVelocity( &vel, &angVel );
|
|
float speed = VectorNormalize(vel) - linearLimit;
|
|
float angSpeed = VectorNormalize(angVel) - angularLimit;
|
|
speed = speed < 0 ? 0 : -speed;
|
|
angSpeed = angSpeed < 0 ? 0 : -angSpeed;
|
|
vel *= speed;
|
|
angVel *= angSpeed;
|
|
pPhys->AddVelocity( &vel, &angVel );
|
|
}
|
|
|
|
void CGrabController::DetachEntity( bool bClearVelocity )
|
|
{
|
|
CBaseEntity *pEntity = GetAttached();
|
|
if ( pEntity )
|
|
{
|
|
// 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( true );
|
|
pPhys->Wake();
|
|
pPhys->SetMass( m_savedMass[i] );
|
|
pPhys->SetDamping( NULL, &m_savedRotDamping[i] );
|
|
PhysClearGameFlags( pPhys, FVPHYSICS_PLAYER_HELD );
|
|
if ( bClearVelocity )
|
|
{
|
|
PhysForceClearVelocity( pPhys );
|
|
}
|
|
else
|
|
{
|
|
#ifndef CLIENT_DLL
|
|
ClampPhysicsVelocity( pPhys, hl2_normspeed.GetFloat() * 1.5f, 2.0f * 360.0f );
|
|
#endif
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
m_attachedEntity = NULL;
|
|
if ( physenv )
|
|
{
|
|
physenv->DestroyMotionController( m_controller );
|
|
}
|
|
m_controller = NULL;
|
|
}
|
|
|
|
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;
|
|
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;
|
|
#ifndef CLIENT_DLL
|
|
m_timeToArrive = pObject->ComputeShadowControl( shadowParams, m_timeToArrive, deltaTime );
|
|
#else
|
|
m_timeToArrive = pObject->ComputeShadowControl( shadowParams, (TICK_INTERVAL*2), deltaTime );
|
|
#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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Player pickup controller
|
|
//-----------------------------------------------------------------------------
|
|
|
|
class CPlayerPickupController : public CBaseEntity
|
|
{
|
|
DECLARE_CLASS( CPlayerPickupController, CBaseEntity );
|
|
public:
|
|
void Init( CBasePlayer *pPlayer, CBaseEntity *pObject );
|
|
void Shutdown( bool bThrown = false );
|
|
bool OnControls( CBaseEntity *pControls ) { return true; }
|
|
void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
|
|
void OnRestore()
|
|
{
|
|
m_grabController.OnRestore();
|
|
}
|
|
void VPhysicsUpdate( IPhysicsObject *pPhysics ){}
|
|
void VPhysicsShadowUpdate( IPhysicsObject *pPhysics ) {}
|
|
|
|
bool IsHoldingEntity( CBaseEntity *pEnt );
|
|
CGrabController &GetGrabController() { return m_grabController; }
|
|
|
|
private:
|
|
CGrabController m_grabController;
|
|
CBasePlayer *m_pPlayer;
|
|
};
|
|
|
|
LINK_ENTITY_TO_CLASS( player_pickup, CPlayerPickupController );
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : *pPlayer -
|
|
// *pObject -
|
|
//-----------------------------------------------------------------------------
|
|
void CPlayerPickupController::Init( CBasePlayer *pPlayer, CBaseEntity *pObject )
|
|
{
|
|
#ifndef CLIENT_DLL
|
|
// Holster player's weapon
|
|
if ( pPlayer->GetActiveWeapon() )
|
|
{
|
|
if ( !pPlayer->GetActiveWeapon()->Holster() )
|
|
{
|
|
Shutdown();
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
CHL2MP_Player *pOwner = (CHL2MP_Player *)ToBasePlayer( pPlayer );
|
|
if ( pOwner )
|
|
{
|
|
pOwner->EnableSprint( false );
|
|
}
|
|
|
|
// 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 );
|
|
m_grabController.SetIgnorePitch( true );
|
|
m_grabController.SetAngleAlignment( DOT_30DEGREE );
|
|
m_pPlayer = pPlayer;
|
|
IPhysicsObject *pPhysics = pObject->VPhysicsGetObject();
|
|
Pickup_OnPhysGunPickup( pObject, m_pPlayer );
|
|
|
|
m_grabController.AttachEntity( pPlayer, pObject, pPhysics, false, vec3_origin, false );
|
|
|
|
m_pPlayer->m_Local.m_iHideHUD |= HIDEHUD_WEAPONSELECTION;
|
|
m_pPlayer->SetUseEntity( this );
|
|
#endif
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : bool -
|
|
//-----------------------------------------------------------------------------
|
|
void CPlayerPickupController::Shutdown( bool bThrown )
|
|
{
|
|
#ifndef CLIENT_DLL
|
|
CBaseEntity *pObject = m_grabController.GetAttached();
|
|
|
|
bool bClearVelocity = false;
|
|
if ( !bThrown && pObject && pObject->VPhysicsGetObject() && pObject->VPhysicsGetObject()->GetContactPoint(NULL,NULL) )
|
|
{
|
|
bClearVelocity = true;
|
|
}
|
|
|
|
m_grabController.DetachEntity( bClearVelocity );
|
|
|
|
if ( pObject != NULL )
|
|
{
|
|
Pickup_OnPhysGunDrop( pObject, m_pPlayer, bThrown ? THROWN_BY_PLAYER : DROPPED_BY_PLAYER );
|
|
}
|
|
|
|
if ( m_pPlayer )
|
|
{
|
|
CHL2MP_Player *pOwner = (CHL2MP_Player *)ToBasePlayer( m_pPlayer );
|
|
if ( pOwner )
|
|
{
|
|
pOwner->EnableSprint( true );
|
|
}
|
|
|
|
m_pPlayer->SetUseEntity( NULL );
|
|
if ( m_pPlayer->GetActiveWeapon() )
|
|
{
|
|
if ( !m_pPlayer->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.
|
|
m_pPlayer->SwitchToNextBestWeapon( NULL );
|
|
}
|
|
}
|
|
|
|
m_pPlayer->m_Local.m_iHideHUD &= ~HIDEHUD_WEAPONSELECTION;
|
|
}
|
|
Remove();
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
void CPlayerPickupController::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
|
|
{
|
|
if ( ToBasePlayer(pActivator) == m_pPlayer )
|
|
{
|
|
CBaseEntity *pAttached = m_grabController.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
|
|
if ( !pAttached || useType == USE_OFF || (m_pPlayer->m_nButtons & IN_ATTACK2) || m_grabController.ComputeError() > 12 )
|
|
{
|
|
Shutdown();
|
|
return;
|
|
}
|
|
|
|
//Adrian: Oops, our object became motion disabled, let go!
|
|
IPhysicsObject *pPhys = pAttached->VPhysicsGetObject();
|
|
if ( pPhys && pPhys->IsMoveable() == false )
|
|
{
|
|
Shutdown();
|
|
return;
|
|
}
|
|
|
|
#if STRESS_TEST
|
|
vphysics_objectstress_t stress;
|
|
CalculateObjectStress( pPhys, pAttached, &stress );
|
|
if ( stress.exertedStress > 250 )
|
|
{
|
|
Shutdown();
|
|
return;
|
|
}
|
|
#endif
|
|
// +ATTACK will throw phys objects
|
|
if ( m_pPlayer->m_nButtons & IN_ATTACK )
|
|
{
|
|
Shutdown( true );
|
|
Vector vecLaunch;
|
|
m_pPlayer->EyeVectors( &vecLaunch );
|
|
// JAY: Scale this with mass because some small objects really go flying
|
|
float massFactor = clamp( pPhys->GetMass(), 0.5, 15 );
|
|
massFactor = RemapVal( massFactor, 0.5, 15, 0.5, 4 );
|
|
vecLaunch *= player_throwforce.GetFloat() * massFactor;
|
|
|
|
pPhys->ApplyForceCenter( vecLaunch );
|
|
AngularImpulse aVel = RandomAngularImpulse( -10, 10 ) * massFactor;
|
|
pPhys->ApplyTorqueCenter( aVel );
|
|
return;
|
|
}
|
|
|
|
if ( useType == USE_SET )
|
|
{
|
|
// update position
|
|
m_grabController.UpdateObject( m_pPlayer, 12 );
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : *pEnt -
|
|
// Output : Returns true on success, false on failure.
|
|
//-----------------------------------------------------------------------------
|
|
bool CPlayerPickupController::IsHoldingEntity( CBaseEntity *pEnt )
|
|
{
|
|
return ( m_grabController.GetAttached() == pEnt );
|
|
}
|
|
|
|
void PlayerPickupObject( CBasePlayer *pPlayer, CBaseEntity *pObject )
|
|
{
|
|
|
|
#ifndef CLIENT_DLL
|
|
|
|
//Don't pick up if we don't have a phys object.
|
|
if ( pObject->VPhysicsGetObject() == NULL )
|
|
return;
|
|
|
|
CPlayerPickupController *pController = (CPlayerPickupController *)CBaseEntity::Create( "player_pickup", pObject->GetAbsOrigin(), vec3_angle, pPlayer );
|
|
|
|
if ( !pController )
|
|
return;
|
|
|
|
pController->Init( pPlayer, pObject );
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
// CInterpolatedValue class
|
|
//----------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
#ifdef CLIENT_DLL
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
// CPhysCannonEffect class
|
|
//----------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
class CPhysCannonEffect
|
|
{
|
|
public:
|
|
CPhysCannonEffect( void ) : m_vecColor( 255, 255, 255 ), m_bVisible( true ), m_nAttachment( -1 ) {};
|
|
|
|
void SetAttachment( int attachment ) { m_nAttachment = attachment; }
|
|
int GetAttachment( void ) const { return m_nAttachment; }
|
|
|
|
void SetVisible( bool visible = true ) { m_bVisible = visible; }
|
|
int IsVisible( void ) const { return m_bVisible; }
|
|
|
|
void SetColor( const Vector &color ) { m_vecColor = color; }
|
|
const Vector &GetColor( void ) const { return m_vecColor; }
|
|
|
|
bool SetMaterial( const char *materialName )
|
|
{
|
|
m_hMaterial.Init( materialName, TEXTURE_GROUP_CLIENT_EFFECTS );
|
|
return ( m_hMaterial != NULL );
|
|
}
|
|
|
|
CMaterialReference &GetMaterial( void ) { return m_hMaterial; }
|
|
|
|
CInterpolatedValue &GetAlpha( void ) { return m_Alpha; }
|
|
CInterpolatedValue &GetScale( void ) { return m_Scale; }
|
|
|
|
private:
|
|
CInterpolatedValue m_Alpha;
|
|
CInterpolatedValue m_Scale;
|
|
|
|
Vector m_vecColor;
|
|
bool m_bVisible;
|
|
int m_nAttachment;
|
|
CMaterialReference m_hMaterial;
|
|
};
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
// CPhysCannonEffectBeam class
|
|
//----------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
class CPhysCannonEffectBeam
|
|
{
|
|
public:
|
|
CPhysCannonEffectBeam( void ) : m_pBeam( NULL ) {};
|
|
|
|
~CPhysCannonEffectBeam( void )
|
|
{
|
|
Release();
|
|
}
|
|
|
|
void Release( void )
|
|
{
|
|
if ( m_pBeam != NULL )
|
|
{
|
|
m_pBeam->flags = 0;
|
|
m_pBeam->die = gpGlobals->curtime - 1;
|
|
|
|
m_pBeam = NULL;
|
|
}
|
|
}
|
|
|
|
void Init( int startAttachment, int endAttachment, CBaseEntity *pEntity, bool firstPerson )
|
|
{
|
|
if ( m_pBeam != NULL )
|
|
return;
|
|
|
|
BeamInfo_t beamInfo;
|
|
|
|
beamInfo.m_pStartEnt = pEntity;
|
|
beamInfo.m_nStartAttachment = startAttachment;
|
|
beamInfo.m_pEndEnt = pEntity;
|
|
beamInfo.m_nEndAttachment = endAttachment;
|
|
beamInfo.m_nType = TE_BEAMPOINTS;
|
|
beamInfo.m_vecStart = vec3_origin;
|
|
beamInfo.m_vecEnd = vec3_origin;
|
|
|
|
beamInfo.m_pszModelName = ( firstPerson ) ? PHYSCANNON_BEAM_SPRITE_NOZ : PHYSCANNON_BEAM_SPRITE;
|
|
|
|
beamInfo.m_flHaloScale = 0.0f;
|
|
beamInfo.m_flLife = 0.0f;
|
|
|
|
if ( firstPerson )
|
|
{
|
|
beamInfo.m_flWidth = 0.0f;
|
|
beamInfo.m_flEndWidth = 4.0f;
|
|
}
|
|
else
|
|
{
|
|
beamInfo.m_flWidth = 0.5f;
|
|
beamInfo.m_flEndWidth = 2.0f;
|
|
}
|
|
|
|
beamInfo.m_flFadeLength = 0.0f;
|
|
beamInfo.m_flAmplitude = 16;
|
|
beamInfo.m_flBrightness = 255.0;
|
|
beamInfo.m_flSpeed = 150.0f;
|
|
beamInfo.m_nStartFrame = 0.0;
|
|
beamInfo.m_flFrameRate = 30.0;
|
|
beamInfo.m_flRed = 255.0;
|
|
beamInfo.m_flGreen = 255.0;
|
|
beamInfo.m_flBlue = 255.0;
|
|
beamInfo.m_nSegments = 8;
|
|
beamInfo.m_bRenderable = true;
|
|
beamInfo.m_nFlags = FBEAM_FOREVER;
|
|
|
|
m_pBeam = beams->CreateBeamEntPoint( beamInfo );
|
|
}
|
|
|
|
void SetVisible( bool state = true )
|
|
{
|
|
if ( m_pBeam == NULL )
|
|
return;
|
|
|
|
m_pBeam->brightness = ( state ) ? 255.0f : 0.0f;
|
|
}
|
|
|
|
private:
|
|
Beam_t *m_pBeam;
|
|
};
|
|
|
|
#endif
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
// CWeaponPhysCannon class
|
|
//----------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
#ifdef CLIENT_DLL
|
|
#define CWeaponPhysCannon C_WeaponPhysCannon
|
|
#endif
|
|
|
|
class CWeaponPhysCannon : public CBaseHL2MPCombatWeapon
|
|
{
|
|
public:
|
|
DECLARE_CLASS( CWeaponPhysCannon, CBaseHL2MPCombatWeapon );
|
|
|
|
DECLARE_NETWORKCLASS();
|
|
DECLARE_PREDICTABLE();
|
|
|
|
CWeaponPhysCannon( void );
|
|
|
|
void Drop( const Vector &vecVelocity );
|
|
void Precache();
|
|
virtual void OnRestore();
|
|
virtual void StopLoopingSounds();
|
|
virtual void UpdateOnRemove(void);
|
|
void PrimaryAttack();
|
|
void SecondaryAttack();
|
|
void WeaponIdle();
|
|
void ItemPreFrame();
|
|
void ItemPostFrame();
|
|
|
|
void ForceDrop( void );
|
|
bool DropIfEntityHeld( CBaseEntity *pTarget ); // Drops its held entity if it matches the entity passed in
|
|
CGrabController &GetGrabController() { return m_grabController; }
|
|
|
|
bool CanHolster( void );
|
|
bool Holster( CBaseCombatWeapon *pSwitchingTo = NULL );
|
|
bool Deploy( void );
|
|
|
|
bool HasAnyAmmo( void ) { return true; }
|
|
|
|
virtual void SetViewModel( void );
|
|
virtual const char *GetShootSound( int iIndex ) const;
|
|
|
|
#ifndef CLIENT_DLL
|
|
CNetworkQAngle ( m_attachedAnglesPlayerSpace );
|
|
#else
|
|
QAngle m_attachedAnglesPlayerSpace;
|
|
#endif
|
|
|
|
CNetworkVector ( m_attachedPositionObjectSpace );
|
|
|
|
CNetworkHandle( CBaseEntity, m_hAttachedObject );
|
|
|
|
EHANDLE m_hOldAttachedObject;
|
|
|
|
protected:
|
|
enum FindObjectResult_t
|
|
{
|
|
OBJECT_FOUND = 0,
|
|
OBJECT_NOT_FOUND,
|
|
OBJECT_BEING_DETACHED,
|
|
};
|
|
|
|
void DoEffect( int effectType, Vector *pos = NULL );
|
|
|
|
void OpenElements( void );
|
|
void CloseElements( void );
|
|
|
|
// Pickup and throw objects.
|
|
bool CanPickupObject( CBaseEntity *pTarget );
|
|
void CheckForTarget( void );
|
|
|
|
#ifndef CLIENT_DLL
|
|
bool AttachObject( CBaseEntity *pObject, const Vector &vPosition );
|
|
FindObjectResult_t FindObject( void );
|
|
CBaseEntity *FindObjectInCone( const Vector &vecOrigin, const Vector &vecDir, float flCone );
|
|
#endif // !CLIENT_DLL
|
|
|
|
void UpdateObject( void );
|
|
void DetachObject( bool playSound = true, bool wasLaunched = false );
|
|
void LaunchObject( const Vector &vecDir, float flForce );
|
|
void StartEffects( void ); // Initialize all sprites and beams
|
|
void StopEffects( bool stopSound = true ); // Hide all effects temporarily
|
|
void DestroyEffects( void ); // Destroy all sprites and beams
|
|
|
|
// Punt objects - this is pointing at an object in the world and applying a force to it.
|
|
void PuntNonVPhysics( CBaseEntity *pEntity, const Vector &forward, trace_t &tr );
|
|
void PuntVPhysics( CBaseEntity *pEntity, const Vector &forward, trace_t &tr );
|
|
|
|
// Velocity-based throw common to punt and launch code.
|
|
void ApplyVelocityBasedForce( CBaseEntity *pEntity, const Vector &forward );
|
|
|
|
// Physgun effects
|
|
void DoEffectClosed( void );
|
|
void DoEffectReady( void );
|
|
void DoEffectHolding( void );
|
|
void DoEffectLaunch( Vector *pos );
|
|
void DoEffectNone( void );
|
|
void DoEffectIdle( void );
|
|
|
|
// Trace length
|
|
float TraceLength();
|
|
|
|
// Sprite scale factor
|
|
float SpriteScaleFactor();
|
|
|
|
float GetLoadPercentage();
|
|
CSoundPatch *GetMotorSound( void );
|
|
|
|
void DryFire( void );
|
|
void PrimaryFireEffect( void );
|
|
|
|
#ifndef CLIENT_DLL
|
|
// What happens when the physgun picks up something
|
|
void Physgun_OnPhysGunPickup( CBaseEntity *pEntity, CBasePlayer *pOwner, PhysGunPickup_t reason );
|
|
#endif // !CLIENT_DLL
|
|
|
|
#ifdef CLIENT_DLL
|
|
|
|
enum EffectType_t
|
|
{
|
|
PHYSCANNON_CORE = 0,
|
|
|
|
PHYSCANNON_BLAST,
|
|
|
|
PHYSCANNON_GLOW1, // Must be in order!
|
|
PHYSCANNON_GLOW2,
|
|
PHYSCANNON_GLOW3,
|
|
PHYSCANNON_GLOW4,
|
|
PHYSCANNON_GLOW5,
|
|
PHYSCANNON_GLOW6,
|
|
|
|
PHYSCANNON_ENDCAP1, // Must be in order!
|
|
PHYSCANNON_ENDCAP2,
|
|
PHYSCANNON_ENDCAP3, // Only used in third-person!
|
|
|
|
NUM_PHYSCANNON_PARAMETERS // Must be last!
|
|
};
|
|
|
|
#define NUM_GLOW_SPRITES ((CWeaponPhysCannon::PHYSCANNON_GLOW6-CWeaponPhysCannon::PHYSCANNON_GLOW1)+1)
|
|
#define NUM_ENDCAP_SPRITES ((CWeaponPhysCannon::PHYSCANNON_ENDCAP3-CWeaponPhysCannon::PHYSCANNON_ENDCAP1)+1)
|
|
|
|
#define NUM_PHYSCANNON_BEAMS 3
|
|
|
|
virtual int DrawModel( int flags );
|
|
virtual void ViewModelDrawn( C_BaseViewModel *pBaseViewModel );
|
|
virtual bool IsTransparent( void );
|
|
virtual void OnDataChanged( DataUpdateType_t type );
|
|
virtual void ClientThink( void );
|
|
|
|
void ManagePredictedObject( void );
|
|
void DrawEffects( void );
|
|
void GetEffectParameters( EffectType_t effectID, color32 &color, float &scale, IMaterial **pMaterial, Vector &vecAttachment );
|
|
void DrawEffectSprite( EffectType_t effectID );
|
|
inline bool IsEffectVisible( EffectType_t effectID );
|
|
void UpdateElementPosition( void );
|
|
|
|
// We need to render opaque and translucent pieces
|
|
RenderGroup_t GetRenderGroup( void ) { return RENDER_GROUP_TWOPASS; }
|
|
|
|
CInterpolatedValue m_ElementParameter; // Used to interpolate the position of the articulated elements
|
|
CPhysCannonEffect m_Parameters[NUM_PHYSCANNON_PARAMETERS]; // Interpolated parameters for the effects
|
|
CPhysCannonEffectBeam m_Beams[NUM_PHYSCANNON_BEAMS]; // Beams
|
|
|
|
int m_nOldEffectState; // Used for parity checks
|
|
bool m_bOldOpen; // Used for parity checks
|
|
|
|
void NotifyShouldTransmit( ShouldTransmitState_t state );
|
|
|
|
#endif // CLIENT_DLL
|
|
|
|
int m_nChangeState; // For delayed state change of elements
|
|
float m_flCheckSuppressTime; // Amount of time to suppress the checking for targets
|
|
bool m_flLastDenySoundPlayed; // Debounce for deny sound
|
|
int m_nAttack2Debounce;
|
|
|
|
CNetworkVar( bool, m_bActive );
|
|
CNetworkVar( int, m_EffectState ); // Current state of the effects on the gun
|
|
CNetworkVar( bool, m_bOpen );
|
|
|
|
bool m_bResetOwnerEntity;
|
|
|
|
float m_flElementDebounce;
|
|
|
|
CSoundPatch *m_sndMotor; // Whirring sound for the gun
|
|
|
|
CGrabController m_grabController;
|
|
|
|
float m_flRepuntObjectTime;
|
|
EHANDLE m_hLastPuntedObject;
|
|
|
|
private:
|
|
CWeaponPhysCannon( const CWeaponPhysCannon & );
|
|
|
|
#ifndef CLIENT_DLL
|
|
DECLARE_ACTTABLE();
|
|
#endif
|
|
};
|
|
|
|
IMPLEMENT_NETWORKCLASS_ALIASED( WeaponPhysCannon, DT_WeaponPhysCannon )
|
|
|
|
BEGIN_NETWORK_TABLE( CWeaponPhysCannon, DT_WeaponPhysCannon )
|
|
#ifdef CLIENT_DLL
|
|
RecvPropBool( RECVINFO( m_bActive ) ),
|
|
RecvPropEHandle( RECVINFO( m_hAttachedObject ) ),
|
|
RecvPropVector( RECVINFO( m_attachedPositionObjectSpace ) ),
|
|
RecvPropFloat( RECVINFO( m_attachedAnglesPlayerSpace[0] ) ),
|
|
RecvPropFloat( RECVINFO( m_attachedAnglesPlayerSpace[1] ) ),
|
|
RecvPropFloat( RECVINFO( m_attachedAnglesPlayerSpace[2] ) ),
|
|
RecvPropInt( RECVINFO( m_EffectState ) ),
|
|
RecvPropBool( RECVINFO( m_bOpen ) ),
|
|
#else
|
|
SendPropBool( SENDINFO( m_bActive ) ),
|
|
SendPropEHandle( SENDINFO( m_hAttachedObject ) ),
|
|
SendPropVector(SENDINFO( m_attachedPositionObjectSpace ), -1, SPROP_COORD),
|
|
SendPropAngle( SENDINFO_VECTORELEM(m_attachedAnglesPlayerSpace, 0 ), 11 ),
|
|
SendPropAngle( SENDINFO_VECTORELEM(m_attachedAnglesPlayerSpace, 1 ), 11 ),
|
|
SendPropAngle( SENDINFO_VECTORELEM(m_attachedAnglesPlayerSpace, 2 ), 11 ),
|
|
SendPropInt( SENDINFO( m_EffectState ) ),
|
|
SendPropBool( SENDINFO( m_bOpen ) ),
|
|
#endif
|
|
END_NETWORK_TABLE()
|
|
|
|
#ifdef CLIENT_DLL
|
|
BEGIN_PREDICTION_DATA( CWeaponPhysCannon )
|
|
DEFINE_PRED_FIELD( m_EffectState, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ),
|
|
DEFINE_PRED_FIELD( m_bOpen, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ),
|
|
END_PREDICTION_DATA()
|
|
#endif
|
|
|
|
LINK_ENTITY_TO_CLASS( weapon_physcannon, CWeaponPhysCannon );
|
|
PRECACHE_WEAPON_REGISTER( weapon_physcannon );
|
|
|
|
#ifndef CLIENT_DLL
|
|
|
|
acttable_t CWeaponPhysCannon::m_acttable[] =
|
|
{
|
|
{ ACT_HL2MP_IDLE, ACT_HL2MP_IDLE_PHYSGUN, false },
|
|
{ ACT_HL2MP_RUN, ACT_HL2MP_RUN_PHYSGUN, false },
|
|
{ ACT_HL2MP_IDLE_CROUCH, ACT_HL2MP_IDLE_CROUCH_PHYSGUN, false },
|
|
{ ACT_HL2MP_WALK_CROUCH, ACT_HL2MP_WALK_CROUCH_PHYSGUN, false },
|
|
{ ACT_HL2MP_GESTURE_RANGE_ATTACK, ACT_HL2MP_GESTURE_RANGE_ATTACK_PHYSGUN, false },
|
|
{ ACT_HL2MP_GESTURE_RELOAD, ACT_HL2MP_GESTURE_RELOAD_PHYSGUN, false },
|
|
{ ACT_HL2MP_JUMP, ACT_HL2MP_JUMP_PHYSGUN, false },
|
|
};
|
|
|
|
IMPLEMENT_ACTTABLE(CWeaponPhysCannon);
|
|
|
|
#endif
|
|
|
|
|
|
enum
|
|
{
|
|
ELEMENT_STATE_NONE = -1,
|
|
ELEMENT_STATE_OPEN,
|
|
ELEMENT_STATE_CLOSED,
|
|
};
|
|
|
|
enum
|
|
{
|
|
EFFECT_NONE,
|
|
EFFECT_CLOSED,
|
|
EFFECT_READY,
|
|
EFFECT_HOLDING,
|
|
EFFECT_LAUNCH,
|
|
};
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Constructor
|
|
//-----------------------------------------------------------------------------
|
|
CWeaponPhysCannon::CWeaponPhysCannon( void )
|
|
{
|
|
m_bOpen = false;
|
|
m_nChangeState = ELEMENT_STATE_NONE;
|
|
m_flCheckSuppressTime = 0.0f;
|
|
m_EffectState = (int)EFFECT_NONE;
|
|
m_flLastDenySoundPlayed = false;
|
|
|
|
#ifdef CLIENT_DLL
|
|
m_nOldEffectState = EFFECT_NONE;
|
|
m_bOldOpen = false;
|
|
#endif
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Precache
|
|
//-----------------------------------------------------------------------------
|
|
void CWeaponPhysCannon::Precache( void )
|
|
{
|
|
PrecacheModel( PHYSCANNON_BEAM_SPRITE );
|
|
PrecacheModel( PHYSCANNON_BEAM_SPRITE_NOZ );
|
|
|
|
PrecacheScriptSound( "Weapon_PhysCannon.HoldSound" );
|
|
|
|
BaseClass::Precache();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Restore
|
|
//-----------------------------------------------------------------------------
|
|
void CWeaponPhysCannon::OnRestore()
|
|
{
|
|
BaseClass::OnRestore();
|
|
m_grabController.OnRestore();
|
|
|
|
// Tracker 8106: Physcannon effects disappear through level transition, so
|
|
// just recreate any effects here
|
|
if ( m_EffectState != EFFECT_NONE )
|
|
{
|
|
DoEffect( m_EffectState, NULL );
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// On Remove
|
|
//-----------------------------------------------------------------------------
|
|
void CWeaponPhysCannon::UpdateOnRemove(void)
|
|
{
|
|
DestroyEffects( );
|
|
BaseClass::UpdateOnRemove();
|
|
}
|
|
|
|
#ifdef CLIENT_DLL
|
|
void CWeaponPhysCannon::OnDataChanged( DataUpdateType_t type )
|
|
{
|
|
BaseClass::OnDataChanged( type );
|
|
|
|
if ( type == DATA_UPDATE_CREATED )
|
|
{
|
|
SetNextClientThink( CLIENT_THINK_ALWAYS );
|
|
|
|
C_BaseAnimating::AutoAllowBoneAccess boneaccess( true, false );
|
|
StartEffects();
|
|
}
|
|
|
|
if ( GetOwner() == NULL )
|
|
{
|
|
if ( m_hAttachedObject )
|
|
{
|
|
m_hAttachedObject->VPhysicsDestroyObject();
|
|
}
|
|
|
|
if ( m_hOldAttachedObject )
|
|
{
|
|
m_hOldAttachedObject->VPhysicsDestroyObject();
|
|
}
|
|
}
|
|
|
|
// Update effect state when out of parity with the server
|
|
if ( m_nOldEffectState != m_EffectState )
|
|
{
|
|
DoEffect( m_EffectState );
|
|
m_nOldEffectState = m_EffectState;
|
|
}
|
|
|
|
// Update element state when out of parity
|
|
if ( m_bOldOpen != m_bOpen )
|
|
{
|
|
if ( m_bOpen )
|
|
{
|
|
m_ElementParameter.InitFromCurrent( 1.0f, 0.2f, INTERP_SPLINE );
|
|
}
|
|
else
|
|
{
|
|
m_ElementParameter.InitFromCurrent( 0.0f, 0.5f, INTERP_SPLINE );
|
|
}
|
|
|
|
m_bOldOpen = (bool) m_bOpen;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Sprite scale factor
|
|
//-----------------------------------------------------------------------------
|
|
inline float CWeaponPhysCannon::SpriteScaleFactor()
|
|
{
|
|
return 1.0f;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Output : Returns true on success, false on failure.
|
|
//-----------------------------------------------------------------------------
|
|
bool CWeaponPhysCannon::Deploy( void )
|
|
{
|
|
CloseElements();
|
|
DoEffect( EFFECT_READY );
|
|
|
|
bool bReturn = BaseClass::Deploy();
|
|
|
|
m_flNextSecondaryAttack = m_flNextPrimaryAttack = gpGlobals->curtime;
|
|
|
|
CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
|
|
|
|
if ( pOwner )
|
|
{
|
|
pOwner->SetNextAttack( gpGlobals->curtime );
|
|
}
|
|
|
|
return bReturn;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CWeaponPhysCannon::SetViewModel( void )
|
|
{
|
|
BaseClass::SetViewModel();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Force the cannon to drop anything it's carrying
|
|
//-----------------------------------------------------------------------------
|
|
void CWeaponPhysCannon::ForceDrop( void )
|
|
{
|
|
CloseElements();
|
|
DetachObject();
|
|
StopEffects();
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Drops its held entity if it matches the entity passed in
|
|
// Input : *pTarget -
|
|
// Output : Returns true on success, false on failure.
|
|
//-----------------------------------------------------------------------------
|
|
bool CWeaponPhysCannon::DropIfEntityHeld( CBaseEntity *pTarget )
|
|
{
|
|
if ( pTarget == NULL )
|
|
return false;
|
|
|
|
CBaseEntity *pHeld = m_grabController.GetAttached();
|
|
|
|
if ( pHeld == NULL )
|
|
return false;
|
|
|
|
if ( pHeld == pTarget )
|
|
{
|
|
ForceDrop();
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CWeaponPhysCannon::Drop( const Vector &vecVelocity )
|
|
{
|
|
ForceDrop();
|
|
|
|
#ifndef CLIENT_DLL
|
|
UTIL_Remove( this );
|
|
#endif
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
bool CWeaponPhysCannon::CanHolster( void )
|
|
{
|
|
//Don't holster this weapon if we're holding onto something
|
|
if ( m_bActive )
|
|
return false;
|
|
|
|
return BaseClass::CanHolster();
|
|
};
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Output : Returns true on success, false on failure.
|
|
//-----------------------------------------------------------------------------
|
|
bool CWeaponPhysCannon::Holster( CBaseCombatWeapon *pSwitchingTo )
|
|
{
|
|
//Don't holster this weapon if we're holding onto something
|
|
if ( m_bActive )
|
|
return false;
|
|
|
|
ForceDrop();
|
|
DestroyEffects();
|
|
|
|
return BaseClass::Holster( pSwitchingTo );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CWeaponPhysCannon::DryFire( void )
|
|
{
|
|
SendWeaponAnim( ACT_VM_PRIMARYATTACK );
|
|
|
|
WeaponSound( EMPTY );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CWeaponPhysCannon::PrimaryFireEffect( void )
|
|
{
|
|
CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
|
|
|
|
if ( pOwner == NULL )
|
|
return;
|
|
|
|
pOwner->ViewPunch( QAngle(-6, SharedRandomInt( "physcannonfire", -2,2) ,0) );
|
|
|
|
#ifndef CLIENT_DLL
|
|
color32 white = { 245, 245, 255, 32 };
|
|
UTIL_ScreenFade( pOwner, white, 0.1f, 0.0f, FFADE_IN );
|
|
#endif
|
|
|
|
WeaponSound( SINGLE );
|
|
}
|
|
|
|
#define MAX_KNOCKBACK_FORCE 128
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Punt non-physics
|
|
//-----------------------------------------------------------------------------
|
|
void CWeaponPhysCannon::PuntNonVPhysics( CBaseEntity *pEntity, const Vector &forward, trace_t &tr )
|
|
{
|
|
if ( m_hLastPuntedObject == pEntity && gpGlobals->curtime < m_flRepuntObjectTime )
|
|
return;
|
|
|
|
#ifndef CLIENT_DLL
|
|
CTakeDamageInfo info;
|
|
|
|
info.SetAttacker( GetOwner() );
|
|
info.SetInflictor( this );
|
|
info.SetDamage( 1.0f );
|
|
info.SetDamageType( DMG_CRUSH | DMG_PHYSGUN );
|
|
info.SetDamageForce( forward ); // Scale?
|
|
info.SetDamagePosition( tr.endpos );
|
|
|
|
m_hLastPuntedObject = pEntity;
|
|
m_flRepuntObjectTime = gpGlobals->curtime + 0.5f;
|
|
|
|
pEntity->DispatchTraceAttack( info, forward, &tr );
|
|
|
|
ApplyMultiDamage();
|
|
|
|
//Explosion effect
|
|
DoEffect( EFFECT_LAUNCH, &tr.endpos );
|
|
#endif
|
|
|
|
PrimaryFireEffect();
|
|
SendWeaponAnim( ACT_VM_SECONDARYATTACK );
|
|
|
|
m_nChangeState = ELEMENT_STATE_CLOSED;
|
|
m_flElementDebounce = gpGlobals->curtime + 0.5f;
|
|
m_flCheckSuppressTime = gpGlobals->curtime + 0.25f;
|
|
}
|
|
|
|
|
|
#ifndef CLIENT_DLL
|
|
//-----------------------------------------------------------------------------
|
|
// What happens when the physgun picks up something
|
|
//-----------------------------------------------------------------------------
|
|
void CWeaponPhysCannon::Physgun_OnPhysGunPickup( CBaseEntity *pEntity, CBasePlayer *pOwner, PhysGunPickup_t reason )
|
|
{
|
|
// If the target is debris, convert it to non-debris
|
|
if ( pEntity->GetCollisionGroup() == COLLISION_GROUP_DEBRIS )
|
|
{
|
|
// Interactive debris converts back to debris when it comes to rest
|
|
pEntity->SetCollisionGroup( COLLISION_GROUP_INTERACTIVE_DEBRIS );
|
|
}
|
|
|
|
Pickup_OnPhysGunPickup( pEntity, pOwner, reason );
|
|
}
|
|
#endif
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Punt vphysics
|
|
//-----------------------------------------------------------------------------
|
|
void CWeaponPhysCannon::PuntVPhysics( CBaseEntity *pEntity, const Vector &vecForward, trace_t &tr )
|
|
{
|
|
CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
|
|
|
|
|
|
if ( m_hLastPuntedObject == pEntity && gpGlobals->curtime < m_flRepuntObjectTime )
|
|
return;
|
|
|
|
m_hLastPuntedObject = pEntity;
|
|
m_flRepuntObjectTime = gpGlobals->curtime + 0.5f;
|
|
|
|
#ifndef CLIENT_DLL
|
|
CTakeDamageInfo info;
|
|
|
|
Vector forward = vecForward;
|
|
|
|
info.SetAttacker( GetOwner() );
|
|
info.SetInflictor( this );
|
|
info.SetDamage( 0.0f );
|
|
info.SetDamageType( DMG_PHYSGUN );
|
|
pEntity->DispatchTraceAttack( info, forward, &tr );
|
|
ApplyMultiDamage();
|
|
|
|
|
|
if ( Pickup_OnAttemptPhysGunPickup( pEntity, pOwner, PUNTED_BY_CANNON ) )
|
|
{
|
|
IPhysicsObject *pList[VPHYSICS_MAX_OBJECT_LIST_COUNT];
|
|
int listCount = pEntity->VPhysicsGetObjectList( pList, ARRAYSIZE(pList) );
|
|
if ( !listCount )
|
|
{
|
|
//FIXME: Do we want to do this if there's no physics object?
|
|
Physgun_OnPhysGunPickup( pEntity, pOwner, PUNTED_BY_CANNON );
|
|
DryFire();
|
|
return;
|
|
}
|
|
|
|
if( forward.z < 0 )
|
|
{
|
|
//reflect, but flatten the trajectory out a bit so it's easier to hit standing targets
|
|
forward.z *= -0.65f;
|
|
}
|
|
|
|
// NOTE: Do this first to enable motion (if disabled) - so forces will work
|
|
// Tell the object it's been punted
|
|
Physgun_OnPhysGunPickup( pEntity, pOwner, PUNTED_BY_CANNON );
|
|
|
|
// don't push vehicles that are attached to the world via fixed constraints
|
|
// they will just wiggle...
|
|
if ( (pList[0]->GetGameFlags() & FVPHYSICS_CONSTRAINT_STATIC) && pEntity->GetServerVehicle() )
|
|
{
|
|
forward.Init();
|
|
}
|
|
|
|
if ( !Pickup_ShouldPuntUseLaunchForces( pEntity, PHYSGUN_FORCE_PUNTED ) )
|
|
{
|
|
int i;
|
|
|
|
// limit mass to avoid punting REALLY huge things
|
|
float totalMass = 0;
|
|
for ( i = 0; i < listCount; i++ )
|
|
{
|
|
totalMass += pList[i]->GetMass();
|
|
}
|
|
float maxMass = 250;
|
|
IServerVehicle *pVehicle = pEntity->GetServerVehicle();
|
|
if ( pVehicle )
|
|
{
|
|
maxMass *= 2.5; // 625 for vehicles
|
|
}
|
|
float mass = MIN(totalMass, maxMass); // max 250kg of additional force
|
|
|
|
// Put some spin on the object
|
|
for ( i = 0; i < listCount; i++ )
|
|
{
|
|
const float hitObjectFactor = 0.5f;
|
|
const float otherObjectFactor = 1.0f - hitObjectFactor;
|
|
// Must be light enough
|
|
float ratio = pList[i]->GetMass() / totalMass;
|
|
if ( pList[i] == pEntity->VPhysicsGetObject() )
|
|
{
|
|
ratio += hitObjectFactor;
|
|
ratio = MIN(ratio,1.0f);
|
|
}
|
|
else
|
|
{
|
|
ratio *= otherObjectFactor;
|
|
}
|
|
pList[i]->ApplyForceCenter( forward * 15000.0f * ratio );
|
|
pList[i]->ApplyForceOffset( forward * mass * 600.0f * ratio, tr.endpos );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ApplyVelocityBasedForce( pEntity, vecForward );
|
|
}
|
|
}
|
|
|
|
#endif
|
|
// Add recoil
|
|
QAngle recoil = QAngle( random->RandomFloat( 1.0f, 2.0f ), random->RandomFloat( -1.0f, 1.0f ), 0 );
|
|
pOwner->ViewPunch( recoil );
|
|
|
|
//Explosion effect
|
|
DoEffect( EFFECT_LAUNCH, &tr.endpos );
|
|
|
|
PrimaryFireEffect();
|
|
SendWeaponAnim( ACT_VM_SECONDARYATTACK );
|
|
|
|
m_nChangeState = ELEMENT_STATE_CLOSED;
|
|
m_flElementDebounce = gpGlobals->curtime + 0.5f;
|
|
m_flCheckSuppressTime = gpGlobals->curtime + 0.25f;
|
|
|
|
// Don't allow the gun to regrab a thrown object!!
|
|
m_flNextSecondaryAttack = m_flNextPrimaryAttack = gpGlobals->curtime + 0.5f;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Applies velocity-based forces to throw the entity. This code is
|
|
// called from both punt and launch carried code.
|
|
// ASSUMES: that pEntity is a vphysics entity.
|
|
// Input : -
|
|
//-----------------------------------------------------------------------------
|
|
void CWeaponPhysCannon::ApplyVelocityBasedForce( CBaseEntity *pEntity, const Vector &forward )
|
|
{
|
|
#ifndef CLIENT_DLL
|
|
IPhysicsObject *pPhysicsObject = pEntity->VPhysicsGetObject();
|
|
Assert(pPhysicsObject); // Shouldn't ever get here with a non-vphysics object.
|
|
if (!pPhysicsObject)
|
|
return;
|
|
|
|
float flForceMax = physcannon_maxforce.GetFloat();
|
|
float flForce = flForceMax;
|
|
|
|
float mass = pPhysicsObject->GetMass();
|
|
if (mass > 100)
|
|
{
|
|
mass = MIN(mass, 1000);
|
|
float flForceMin = physcannon_minforce.GetFloat();
|
|
flForce = SimpleSplineRemapVal(mass, 100, 600, flForceMax, flForceMin);
|
|
}
|
|
|
|
Vector vVel = forward * flForce;
|
|
// FIXME: Josh needs to put a real value in for PHYSGUN_FORCE_PUNTED
|
|
AngularImpulse aVel = Pickup_PhysGunLaunchAngularImpulse( pEntity, PHYSGUN_FORCE_PUNTED );
|
|
|
|
pPhysicsObject->AddVelocity( &vVel, &aVel );
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Trace length
|
|
//-----------------------------------------------------------------------------
|
|
float CWeaponPhysCannon::TraceLength()
|
|
{
|
|
return physcannon_tracelength.GetFloat();
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//
|
|
// This mode is a toggle. Primary fire one time to pick up a physics object.
|
|
// With an object held, click primary fire again to drop object.
|
|
//-----------------------------------------------------------------------------
|
|
void CWeaponPhysCannon::PrimaryAttack( void )
|
|
{
|
|
if( m_flNextPrimaryAttack > gpGlobals->curtime )
|
|
return;
|
|
|
|
CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
|
|
|
|
if ( pOwner == NULL )
|
|
return;
|
|
|
|
if( m_bActive )
|
|
{
|
|
// Punch the object being held!!
|
|
Vector forward;
|
|
pOwner->EyeVectors( &forward );
|
|
|
|
// Validate the item is within punt range
|
|
CBaseEntity *pHeld = m_grabController.GetAttached();
|
|
Assert( pHeld != NULL );
|
|
|
|
if ( pHeld != NULL )
|
|
{
|
|
float heldDist = ( pHeld->WorldSpaceCenter() - pOwner->WorldSpaceCenter() ).Length();
|
|
|
|
if ( heldDist > physcannon_tracelength.GetFloat() )
|
|
{
|
|
// We can't punt this yet
|
|
DryFire();
|
|
return;
|
|
}
|
|
}
|
|
|
|
LaunchObject( forward, physcannon_maxforce.GetFloat() );
|
|
|
|
PrimaryFireEffect();
|
|
SendWeaponAnim( ACT_VM_SECONDARYATTACK );
|
|
return;
|
|
}
|
|
|
|
// If not active, just issue a physics punch in the world.
|
|
m_flNextPrimaryAttack = gpGlobals->curtime + 0.5f;
|
|
|
|
Vector forward;
|
|
pOwner->EyeVectors( &forward );
|
|
|
|
// NOTE: Notice we're *not* using the mega tracelength here
|
|
// when you have the mega cannon. Punting has shorter range.
|
|
Vector start, end;
|
|
start = pOwner->Weapon_ShootPosition();
|
|
float flPuntDistance = physcannon_tracelength.GetFloat();
|
|
VectorMA( start, flPuntDistance, forward, end );
|
|
|
|
CTraceFilterNoOwnerTest filter( pOwner, COLLISION_GROUP_NONE );
|
|
trace_t tr;
|
|
UTIL_TraceHull( start, end, -Vector(8,8,8), Vector(8,8,8), MASK_SHOT|CONTENTS_GRATE, &filter, &tr );
|
|
bool bValid = true;
|
|
CBaseEntity *pEntity = tr.m_pEnt;
|
|
if ( tr.fraction == 1 || !tr.m_pEnt || tr.m_pEnt->IsEFlagSet( EFL_NO_PHYSCANNON_INTERACTION ) )
|
|
{
|
|
bValid = false;
|
|
}
|
|
else if ( (pEntity->GetMoveType() != MOVETYPE_VPHYSICS) && ( pEntity->m_takedamage == DAMAGE_NO ) )
|
|
{
|
|
bValid = false;
|
|
}
|
|
|
|
// If the entity we've hit is invalid, try a traceline instead
|
|
if ( !bValid )
|
|
{
|
|
UTIL_TraceLine( start, end, MASK_SHOT|CONTENTS_GRATE, &filter, &tr );
|
|
if ( tr.fraction == 1 || !tr.m_pEnt || tr.m_pEnt->IsEFlagSet( EFL_NO_PHYSCANNON_INTERACTION ) )
|
|
{
|
|
// Play dry-fire sequence
|
|
DryFire();
|
|
return;
|
|
}
|
|
|
|
pEntity = tr.m_pEnt;
|
|
}
|
|
|
|
// See if we hit something
|
|
if ( pEntity->GetMoveType() != MOVETYPE_VPHYSICS )
|
|
{
|
|
if ( pEntity->m_takedamage == DAMAGE_NO )
|
|
{
|
|
DryFire();
|
|
return;
|
|
}
|
|
|
|
if( GetOwner()->IsPlayer() )
|
|
{
|
|
// Don't let the player zap any NPC's except regular antlions and headcrabs.
|
|
if( pEntity->IsPlayer() )
|
|
{
|
|
DryFire();
|
|
return;
|
|
}
|
|
}
|
|
|
|
PuntNonVPhysics( pEntity, forward, tr );
|
|
}
|
|
else
|
|
{
|
|
if ( pEntity->VPhysicsIsFlesh( ) )
|
|
{
|
|
DryFire();
|
|
return;
|
|
}
|
|
PuntVPhysics( pEntity, forward, tr );
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Click secondary attack whilst holding an object to hurl it.
|
|
//-----------------------------------------------------------------------------
|
|
void CWeaponPhysCannon::SecondaryAttack( void )
|
|
{
|
|
#ifndef CLIENT_DLL
|
|
if ( m_flNextSecondaryAttack > gpGlobals->curtime )
|
|
return;
|
|
|
|
CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
|
|
|
|
if ( pOwner == NULL )
|
|
return;
|
|
|
|
// See if we should drop a held item
|
|
if ( ( m_bActive ) && ( pOwner->m_afButtonPressed & IN_ATTACK2 ) )
|
|
{
|
|
// Drop the held object
|
|
m_flNextPrimaryAttack = gpGlobals->curtime + 0.5;
|
|
m_flNextSecondaryAttack = gpGlobals->curtime + 0.5;
|
|
|
|
DetachObject();
|
|
|
|
DoEffect( EFFECT_READY );
|
|
|
|
SendWeaponAnim( ACT_VM_PRIMARYATTACK );
|
|
}
|
|
else
|
|
{
|
|
// Otherwise pick it up
|
|
FindObjectResult_t result = FindObject();
|
|
switch ( result )
|
|
{
|
|
case OBJECT_FOUND:
|
|
WeaponSound( SPECIAL1 );
|
|
SendWeaponAnim( ACT_VM_PRIMARYATTACK );
|
|
m_flNextSecondaryAttack = gpGlobals->curtime + 0.5f;
|
|
|
|
// We found an object. Debounce the button
|
|
m_nAttack2Debounce |= pOwner->m_nButtons;
|
|
break;
|
|
|
|
case OBJECT_NOT_FOUND:
|
|
m_flNextSecondaryAttack = gpGlobals->curtime + 0.1f;
|
|
CloseElements();
|
|
break;
|
|
|
|
case OBJECT_BEING_DETACHED:
|
|
m_flNextSecondaryAttack = gpGlobals->curtime + 0.01f;
|
|
break;
|
|
}
|
|
|
|
DoEffect( EFFECT_HOLDING );
|
|
}
|
|
#endif
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CWeaponPhysCannon::WeaponIdle( void )
|
|
{
|
|
if ( HasWeaponIdleTimeElapsed() )
|
|
{
|
|
if ( m_bActive )
|
|
{
|
|
//Shake when holding an item
|
|
SendWeaponAnim( ACT_VM_RELOAD );
|
|
}
|
|
else
|
|
{
|
|
//Otherwise idle simply
|
|
SendWeaponAnim( ACT_VM_IDLE );
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifndef CLIENT_DLL
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : *pObject -
|
|
//-----------------------------------------------------------------------------
|
|
bool CWeaponPhysCannon::AttachObject( CBaseEntity *pObject, const Vector &vPosition )
|
|
{
|
|
|
|
if ( m_bActive )
|
|
return false;
|
|
|
|
if ( CanPickupObject( pObject ) == false )
|
|
return false;
|
|
|
|
m_grabController.SetIgnorePitch( false );
|
|
m_grabController.SetAngleAlignment( 0 );
|
|
|
|
IPhysicsObject *pPhysics = pObject->VPhysicsGetObject();
|
|
|
|
// Must be valid
|
|
if ( !pPhysics )
|
|
return false;
|
|
|
|
CHL2MP_Player *pOwner = (CHL2MP_Player *)ToBasePlayer( GetOwner() );
|
|
|
|
m_bActive = true;
|
|
if( pOwner )
|
|
{
|
|
// NOTE: This can change the mass; so it must be done before max speed setting
|
|
Physgun_OnPhysGunPickup( pObject, pOwner, PICKED_UP_BY_CANNON );
|
|
}
|
|
|
|
// NOTE :This must happen after OnPhysGunPickup because that can change the mass
|
|
m_grabController.AttachEntity( pOwner, pObject, pPhysics, false, vPosition, false );
|
|
m_hAttachedObject = pObject;
|
|
m_attachedPositionObjectSpace = m_grabController.m_attachedPositionObjectSpace;
|
|
m_attachedAnglesPlayerSpace = m_grabController.m_attachedAnglesPlayerSpace;
|
|
|
|
m_bResetOwnerEntity = false;
|
|
|
|
if ( m_hAttachedObject->GetOwnerEntity() == NULL )
|
|
{
|
|
m_hAttachedObject->SetOwnerEntity( pOwner );
|
|
m_bResetOwnerEntity = true;
|
|
}
|
|
|
|
/* if( pOwner )
|
|
{
|
|
pOwner->EnableSprint( false );
|
|
|
|
float loadWeight = ( 1.0f - GetLoadPercentage() );
|
|
float maxSpeed = hl2_walkspeed.GetFloat() + ( ( hl2_normspeed.GetFloat() - hl2_walkspeed.GetFloat() ) * loadWeight );
|
|
|
|
//Msg( "Load perc: %f -- Movement speed: %f/%f\n", loadWeight, maxSpeed, hl2_normspeed.GetFloat() );
|
|
pOwner->SetMaxSpeed( maxSpeed );
|
|
}*/
|
|
|
|
// Don't drop again for a slight delay, in case they were pulling objects near them
|
|
m_flNextSecondaryAttack = gpGlobals->curtime + 0.4f;
|
|
|
|
DoEffect( EFFECT_HOLDING );
|
|
OpenElements();
|
|
|
|
if ( GetMotorSound() )
|
|
{
|
|
(CSoundEnvelopeController::GetController()).Play( GetMotorSound(), 0.0f, 50 );
|
|
(CSoundEnvelopeController::GetController()).SoundChangePitch( GetMotorSound(), 100, 0.5f );
|
|
(CSoundEnvelopeController::GetController()).SoundChangeVolume( GetMotorSound(), 0.8f, 0.5f );
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
}
|
|
|
|
CWeaponPhysCannon::FindObjectResult_t CWeaponPhysCannon::FindObject( void )
|
|
{
|
|
CBasePlayer *pPlayer = ToBasePlayer( GetOwner() );
|
|
|
|
Assert( pPlayer );
|
|
if ( pPlayer == NULL )
|
|
return OBJECT_NOT_FOUND;
|
|
|
|
Vector forward;
|
|
pPlayer->EyeVectors( &forward );
|
|
|
|
// Setup our positions
|
|
Vector start = pPlayer->Weapon_ShootPosition();
|
|
float testLength = TraceLength() * 4.0f;
|
|
Vector end = start + forward * testLength;
|
|
|
|
// Try to find an object by looking straight ahead
|
|
trace_t tr;
|
|
CTraceFilterNoOwnerTest filter( pPlayer, COLLISION_GROUP_NONE );
|
|
UTIL_TraceLine( start, end, MASK_SHOT|CONTENTS_GRATE, &filter, &tr );
|
|
|
|
// Try again with a hull trace
|
|
if ( ( tr.fraction == 1.0 ) || ( tr.m_pEnt == NULL ) || ( tr.m_pEnt->IsWorld() ) )
|
|
{
|
|
UTIL_TraceHull( start, end, -Vector(4,4,4), Vector(4,4,4), MASK_SHOT|CONTENTS_GRATE, &filter, &tr );
|
|
}
|
|
|
|
CBaseEntity *pEntity = tr.m_pEnt ? tr.m_pEnt->GetRootMoveParent() : NULL;
|
|
bool bAttach = false;
|
|
bool bPull = false;
|
|
|
|
// If we hit something, pick it up or pull it
|
|
if ( ( tr.fraction != 1.0f ) && ( tr.m_pEnt ) && ( tr.m_pEnt->IsWorld() == false ) )
|
|
{
|
|
// Attempt to attach if within range
|
|
if ( tr.fraction <= 0.25f )
|
|
{
|
|
bAttach = true;
|
|
}
|
|
else if ( tr.fraction > 0.25f )
|
|
{
|
|
bPull = true;
|
|
}
|
|
}
|
|
|
|
// Find anything within a general cone in front
|
|
CBaseEntity *pConeEntity = NULL;
|
|
|
|
if (!bAttach && !bPull)
|
|
{
|
|
pConeEntity = FindObjectInCone( start, forward, physcannon_cone.GetFloat() );
|
|
}
|
|
|
|
if ( pConeEntity )
|
|
{
|
|
pEntity = pConeEntity;
|
|
|
|
// If the object is near, grab it. Else, pull it a bit.
|
|
if ( pEntity->WorldSpaceCenter().DistToSqr( start ) <= (testLength * testLength) )
|
|
{
|
|
bAttach = true;
|
|
}
|
|
else
|
|
{
|
|
bPull = true;
|
|
}
|
|
}
|
|
|
|
if ( CanPickupObject( pEntity ) == false )
|
|
{
|
|
// Make a noise to signify we can't pick this up
|
|
if ( !m_flLastDenySoundPlayed )
|
|
{
|
|
m_flLastDenySoundPlayed = true;
|
|
WeaponSound( SPECIAL3 );
|
|
}
|
|
|
|
return OBJECT_NOT_FOUND;
|
|
}
|
|
|
|
// Check to see if the object is constrained + needs to be ripped off...
|
|
CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
|
|
if ( !Pickup_OnAttemptPhysGunPickup( pEntity, pOwner, PICKED_UP_BY_CANNON ) )
|
|
return OBJECT_BEING_DETACHED;
|
|
|
|
if ( bAttach )
|
|
{
|
|
return AttachObject( pEntity, tr.endpos ) ? OBJECT_FOUND : OBJECT_NOT_FOUND;
|
|
}
|
|
|
|
if ( !bPull )
|
|
return OBJECT_NOT_FOUND;
|
|
|
|
// FIXME: This needs to be run through the CanPickupObject logic
|
|
IPhysicsObject *pObj = pEntity->VPhysicsGetObject();
|
|
if ( !pObj )
|
|
return OBJECT_NOT_FOUND;
|
|
|
|
// If we're too far, simply start to pull the object towards us
|
|
Vector pullDir = start - pEntity->WorldSpaceCenter();
|
|
VectorNormalize( pullDir );
|
|
pullDir *= physcannon_pullforce.GetFloat();
|
|
|
|
float mass = PhysGetEntityMass( pEntity );
|
|
if ( mass < 50.0f )
|
|
{
|
|
pullDir *= (mass + 0.5) * (1/50.0f);
|
|
}
|
|
|
|
// Nudge it towards us
|
|
pObj->ApplyForceCenter( pullDir );
|
|
return OBJECT_NOT_FOUND;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
CBaseEntity *CWeaponPhysCannon::FindObjectInCone( const Vector &vecOrigin, const Vector &vecDir, float flCone )
|
|
{
|
|
// Find the nearest physics-based item in a cone in front of me.
|
|
CBaseEntity *list[256];
|
|
float flNearestDist = TraceLength() + 1.0;
|
|
Vector mins = vecOrigin - Vector( flNearestDist, flNearestDist, flNearestDist );
|
|
Vector maxs = vecOrigin + Vector( flNearestDist, flNearestDist, flNearestDist );
|
|
|
|
CBaseEntity *pNearest = NULL;
|
|
|
|
int count = UTIL_EntitiesInBox( list, 256, mins, maxs, 0 );
|
|
for( int i = 0 ; i < count ; i++ )
|
|
{
|
|
if ( !list[ i ]->VPhysicsGetObject() )
|
|
continue;
|
|
|
|
// Closer than other objects
|
|
Vector los = ( list[ i ]->WorldSpaceCenter() - vecOrigin );
|
|
float flDist = VectorNormalize( los );
|
|
if( flDist >= flNearestDist )
|
|
continue;
|
|
|
|
// Cull to the cone
|
|
if ( DotProduct( los, vecDir ) <= flCone )
|
|
continue;
|
|
|
|
// Make sure it isn't occluded!
|
|
trace_t tr;
|
|
CTraceFilterNoOwnerTest filter( GetOwner(), COLLISION_GROUP_NONE );
|
|
UTIL_TraceLine( vecOrigin, list[ i ]->WorldSpaceCenter(), MASK_SHOT|CONTENTS_GRATE, &filter, &tr );
|
|
if( tr.m_pEnt == list[ i ] )
|
|
{
|
|
flNearestDist = flDist;
|
|
pNearest = list[ i ];
|
|
}
|
|
}
|
|
|
|
return pNearest;
|
|
}
|
|
|
|
#endif
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
bool CGrabController::UpdateObject( CBasePlayer *pPlayer, float flError )
|
|
{
|
|
CBaseEntity *pEntity = GetAttached();
|
|
if ( !pEntity )
|
|
return false;
|
|
if ( ComputeError() > flError )
|
|
return false;
|
|
if ( pPlayer->GetGroundEntity() == pEntity )
|
|
return false;
|
|
if (!pEntity->VPhysicsGetObject() )
|
|
return false;
|
|
|
|
//Adrian: Oops, our object became motion disabled, let go!
|
|
IPhysicsObject *pPhys = pEntity->VPhysicsGetObject();
|
|
if ( pPhys && pPhys->IsMoveable() == false )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if ( m_frameCount == gpGlobals->framecount )
|
|
{
|
|
return true;
|
|
}
|
|
m_frameCount = gpGlobals->framecount;
|
|
Vector forward, right, up;
|
|
QAngle playerAngles = pPlayer->EyeAngles();
|
|
|
|
float pitch = AngleDistance(playerAngles.x,0);
|
|
playerAngles.x = clamp( pitch, -75, 75 );
|
|
AngleVectors( playerAngles, &forward, &right, &up );
|
|
|
|
// Now clamp a sphere of object radius at end to the player's bbox
|
|
Vector radial = physcollision->CollideGetExtent( pPhys->GetCollide(), vec3_origin, pEntity->GetAbsAngles(), -forward );
|
|
Vector player2d = pPlayer->CollisionProp()->OBBMaxs();
|
|
float playerRadius = player2d.Length2D();
|
|
float flDot = DotProduct( forward, radial );
|
|
|
|
float radius = playerRadius + fabs( flDot );
|
|
|
|
float distance = 24 + ( radius * 2.0f );
|
|
|
|
Vector start = pPlayer->Weapon_ShootPosition();
|
|
Vector end = start + ( forward * distance );
|
|
|
|
trace_t tr;
|
|
CTraceFilterSkipTwoEntities traceFilter( pPlayer, pEntity, COLLISION_GROUP_NONE );
|
|
Ray_t ray;
|
|
ray.Init( start, end );
|
|
enginetrace->TraceRay( ray, MASK_SOLID_BRUSHONLY, &traceFilter, &tr );
|
|
|
|
if ( tr.fraction < 0.5 )
|
|
{
|
|
end = start + forward * (radius*0.5f);
|
|
}
|
|
else if ( tr.fraction <= 1.0f )
|
|
{
|
|
end = start + forward * ( distance - radius );
|
|
}
|
|
|
|
Vector playerMins, playerMaxs, nearest;
|
|
pPlayer->CollisionProp()->WorldSpaceAABB( &playerMins, &playerMaxs );
|
|
Vector playerLine = pPlayer->CollisionProp()->WorldSpaceCenter();
|
|
CalcClosestPointOnLine( end, playerLine+Vector(0,0,playerMins.z), playerLine+Vector(0,0,playerMaxs.z), nearest, NULL );
|
|
|
|
Vector delta = end - nearest;
|
|
float len = VectorNormalize(delta);
|
|
if ( len < radius )
|
|
{
|
|
end = nearest + radius * delta;
|
|
}
|
|
|
|
QAngle angles = TransformAnglesFromPlayerSpace( m_attachedAnglesPlayerSpace, pPlayer );
|
|
|
|
//Show overlays of radius
|
|
if ( g_debug_physcannon.GetBool() )
|
|
{
|
|
|
|
#ifdef CLIENT_DLL
|
|
|
|
debugoverlay->AddBoxOverlay( end, -Vector( 2,2,2 ), Vector(2,2,2), angles, 0, 255, 255, true, 0 );
|
|
|
|
debugoverlay->AddBoxOverlay( GetAttached()->WorldSpaceCenter(),
|
|
-Vector( radius, radius, radius),
|
|
Vector( radius, radius, radius ),
|
|
angles,
|
|
255, 255, 0,
|
|
true,
|
|
0.0f );
|
|
|
|
#else
|
|
|
|
NDebugOverlay::Box( end, -Vector( 2,2,2 ), Vector(2,2,2), 0, 255, 0, true, 0 );
|
|
|
|
NDebugOverlay::Box( GetAttached()->WorldSpaceCenter(),
|
|
-Vector( radius+5, radius+5, radius+5),
|
|
Vector( radius+5, radius+5, radius+5 ),
|
|
255, 0, 0,
|
|
true,
|
|
0.0f );
|
|
#endif
|
|
}
|
|
|
|
#ifndef CLIENT_DLL
|
|
// 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 );
|
|
}
|
|
|
|
#endif
|
|
|
|
matrix3x4_t attachedToWorld;
|
|
Vector offset;
|
|
AngleMatrix( angles, attachedToWorld );
|
|
VectorRotate( m_attachedPositionObjectSpace, attachedToWorld, offset );
|
|
|
|
SetTargetPosition( end - offset, angles );
|
|
|
|
return true;
|
|
}
|
|
|
|
void CWeaponPhysCannon::UpdateObject( void )
|
|
{
|
|
CBasePlayer *pPlayer = ToBasePlayer( GetOwner() );
|
|
Assert( pPlayer );
|
|
|
|
float flError = 12;
|
|
if ( !m_grabController.UpdateObject( pPlayer, flError ) )
|
|
{
|
|
DetachObject();
|
|
return;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
void CWeaponPhysCannon::DetachObject( bool playSound, bool wasLaunched )
|
|
{
|
|
#ifndef CLIENT_DLL
|
|
if ( m_bActive == false )
|
|
return;
|
|
|
|
CHL2MP_Player *pOwner = (CHL2MP_Player *)ToBasePlayer( GetOwner() );
|
|
if( pOwner != NULL )
|
|
{
|
|
pOwner->EnableSprint( true );
|
|
pOwner->SetMaxSpeed( hl2_normspeed.GetFloat() );
|
|
}
|
|
|
|
CBaseEntity *pObject = m_grabController.GetAttached();
|
|
|
|
m_grabController.DetachEntity( wasLaunched );
|
|
|
|
if ( pObject != NULL )
|
|
{
|
|
Pickup_OnPhysGunDrop( pObject, pOwner, wasLaunched ? LAUNCHED_BY_CANNON : DROPPED_BY_CANNON );
|
|
}
|
|
|
|
// Stop our looping sound
|
|
if ( GetMotorSound() )
|
|
{
|
|
(CSoundEnvelopeController::GetController()).SoundChangeVolume( GetMotorSound(), 0.0f, 1.0f );
|
|
(CSoundEnvelopeController::GetController()).SoundChangePitch( GetMotorSound(), 50, 1.0f );
|
|
}
|
|
|
|
if ( pObject && m_bResetOwnerEntity == true )
|
|
{
|
|
pObject->SetOwnerEntity( NULL );
|
|
}
|
|
|
|
m_bActive = false;
|
|
m_hAttachedObject = NULL;
|
|
|
|
|
|
if ( playSound )
|
|
{
|
|
//Play the detach sound
|
|
WeaponSound( MELEE_MISS );
|
|
}
|
|
|
|
#else
|
|
|
|
m_grabController.DetachEntity( wasLaunched );
|
|
|
|
if ( m_hAttachedObject )
|
|
{
|
|
m_hAttachedObject->VPhysicsDestroyObject();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
#ifdef CLIENT_DLL
|
|
void CWeaponPhysCannon::ManagePredictedObject( void )
|
|
{
|
|
CBaseEntity *pAttachedObject = m_hAttachedObject.Get();
|
|
|
|
if ( m_hAttachedObject )
|
|
{
|
|
// NOTE :This must happen after OnPhysGunPickup because that can change the mass
|
|
if ( pAttachedObject != GetGrabController().GetAttached() )
|
|
{
|
|
IPhysicsObject *pPhysics = pAttachedObject->VPhysicsGetObject();
|
|
|
|
if ( pPhysics == NULL )
|
|
{
|
|
solid_t tmpSolid;
|
|
PhysModelParseSolid( tmpSolid, m_hAttachedObject, pAttachedObject->GetModelIndex() );
|
|
|
|
pAttachedObject->VPhysicsInitNormal( SOLID_VPHYSICS, 0, false, &tmpSolid );
|
|
}
|
|
|
|
pPhysics = pAttachedObject->VPhysicsGetObject();
|
|
|
|
if ( pPhysics )
|
|
{
|
|
m_grabController.SetIgnorePitch( false );
|
|
m_grabController.SetAngleAlignment( 0 );
|
|
|
|
GetGrabController().AttachEntity( ToBasePlayer( GetOwner() ), pAttachedObject, pPhysics, false, vec3_origin, false );
|
|
GetGrabController().m_attachedPositionObjectSpace = m_attachedPositionObjectSpace;
|
|
GetGrabController().m_attachedAnglesPlayerSpace = m_attachedAnglesPlayerSpace;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( m_hOldAttachedObject && m_hOldAttachedObject->VPhysicsGetObject() )
|
|
{
|
|
GetGrabController().DetachEntity( false );
|
|
|
|
m_hOldAttachedObject->VPhysicsDestroyObject();
|
|
}
|
|
}
|
|
|
|
m_hOldAttachedObject = m_hAttachedObject;
|
|
}
|
|
|
|
#endif
|
|
|
|
#ifdef CLIENT_DLL
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Update the pose parameter for the gun
|
|
//-----------------------------------------------------------------------------
|
|
void CWeaponPhysCannon::UpdateElementPosition( void )
|
|
{
|
|
CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
|
|
|
|
float flElementPosition = m_ElementParameter.Interp( gpGlobals->curtime );
|
|
|
|
if ( ShouldDrawUsingViewModel() )
|
|
{
|
|
if ( pOwner != NULL )
|
|
{
|
|
CBaseViewModel *vm = pOwner->GetViewModel();
|
|
|
|
if ( vm != NULL )
|
|
{
|
|
vm->SetPoseParameter( "active", flElementPosition );
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SetPoseParameter( "active", flElementPosition );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Think function for the client
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void CWeaponPhysCannon::ClientThink( void )
|
|
{
|
|
// Update our elements visually
|
|
UpdateElementPosition();
|
|
|
|
// Update our effects
|
|
DoEffectIdle();
|
|
}
|
|
|
|
#endif // CLIENT_DLL
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
void CWeaponPhysCannon::ItemPreFrame()
|
|
{
|
|
BaseClass::ItemPreFrame();
|
|
|
|
#ifdef CLIENT_DLL
|
|
C_BasePlayer *localplayer = C_BasePlayer::GetLocalPlayer();
|
|
|
|
if ( localplayer && !localplayer->IsObserver() )
|
|
ManagePredictedObject();
|
|
#endif
|
|
|
|
// Update the object if the weapon is switched on.
|
|
if( m_bActive )
|
|
{
|
|
UpdateObject();
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CWeaponPhysCannon::CheckForTarget( void )
|
|
{
|
|
#ifndef CLIENT_DLL
|
|
//See if we're suppressing this
|
|
if ( m_flCheckSuppressTime > gpGlobals->curtime )
|
|
return;
|
|
|
|
// holstered
|
|
if ( IsEffectActive( EF_NODRAW ) )
|
|
return;
|
|
|
|
CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
|
|
|
|
if ( pOwner == NULL )
|
|
return;
|
|
|
|
if ( m_bActive )
|
|
return;
|
|
|
|
Vector aimDir;
|
|
pOwner->EyeVectors( &aimDir );
|
|
|
|
Vector startPos = pOwner->Weapon_ShootPosition();
|
|
Vector endPos;
|
|
VectorMA( startPos, TraceLength(), aimDir, endPos );
|
|
|
|
trace_t tr;
|
|
UTIL_TraceHull( startPos, endPos, -Vector(4,4,4), Vector(4,4,4), MASK_SHOT|CONTENTS_GRATE, pOwner, COLLISION_GROUP_NONE, &tr );
|
|
|
|
if ( ( tr.fraction != 1.0f ) && ( tr.m_pEnt != NULL ) )
|
|
{
|
|
// FIXME: Try just having the elements always open when pointed at a physics object
|
|
if ( CanPickupObject( tr.m_pEnt ) || Pickup_ForcePhysGunOpen( tr.m_pEnt, pOwner ) )
|
|
// if ( ( tr.m_pEnt->VPhysicsGetObject() != NULL ) && ( tr.m_pEnt->GetMoveType() == MOVETYPE_VPHYSICS ) )
|
|
{
|
|
m_nChangeState = ELEMENT_STATE_NONE;
|
|
OpenElements();
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Close the elements after a delay to prevent overact state switching
|
|
if ( ( m_flElementDebounce < gpGlobals->curtime ) && ( m_nChangeState == ELEMENT_STATE_NONE ) )
|
|
{
|
|
m_nChangeState = ELEMENT_STATE_CLOSED;
|
|
m_flElementDebounce = gpGlobals->curtime + 0.5f;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Idle effect (pulsing)
|
|
//-----------------------------------------------------------------------------
|
|
void CWeaponPhysCannon::DoEffectIdle( void )
|
|
{
|
|
#ifdef CLIENT_DLL
|
|
|
|
StartEffects();
|
|
|
|
//if ( ShouldDrawUsingViewModel() )
|
|
{
|
|
// Turn on the glow sprites
|
|
for ( int i = PHYSCANNON_GLOW1; i < (PHYSCANNON_GLOW1+NUM_GLOW_SPRITES); i++ )
|
|
{
|
|
m_Parameters[i].GetScale().SetAbsolute( random->RandomFloat( 0.075f, 0.05f ) * SPRITE_SCALE );
|
|
m_Parameters[i].GetAlpha().SetAbsolute( random->RandomInt( 24, 32 ) );
|
|
}
|
|
|
|
// Turn on the glow sprites
|
|
for ( int i = PHYSCANNON_ENDCAP1; i < (PHYSCANNON_ENDCAP1+NUM_ENDCAP_SPRITES); i++ )
|
|
{
|
|
m_Parameters[i].GetScale().SetAbsolute( random->RandomFloat( 3, 5 ) );
|
|
m_Parameters[i].GetAlpha().SetAbsolute( random->RandomInt( 200, 255 ) );
|
|
}
|
|
|
|
if ( m_EffectState != EFFECT_HOLDING )
|
|
{
|
|
// Turn beams off
|
|
m_Beams[0].SetVisible( false );
|
|
m_Beams[1].SetVisible( false );
|
|
m_Beams[2].SetVisible( false );
|
|
}
|
|
}
|
|
/*
|
|
else
|
|
{
|
|
// Turn on the glow sprites
|
|
for ( int i = PHYSCANNON_GLOW1; i < (PHYSCANNON_GLOW1+NUM_GLOW_SPRITES); i++ )
|
|
{
|
|
m_Parameters[i].GetScale().SetAbsolute( random->RandomFloat( 0.075f, 0.05f ) * SPRITE_SCALE );
|
|
m_Parameters[i].GetAlpha().SetAbsolute( random->RandomInt( 24, 32 ) );
|
|
}
|
|
|
|
// Turn on the glow sprites
|
|
for ( i = PHYSCANNON_ENDCAP1; i < (PHYSCANNON_ENDCAP1+NUM_ENDCAP_SPRITES); i++ )
|
|
{
|
|
m_Parameters[i].GetScale().SetAbsolute( random->RandomFloat( 3, 5 ) );
|
|
m_Parameters[i].GetAlpha().SetAbsolute( random->RandomInt( 200, 255 ) );
|
|
}
|
|
|
|
if ( m_EffectState != EFFECT_HOLDING )
|
|
{
|
|
// Turn beams off
|
|
m_Beams[0].SetVisible( false );
|
|
m_Beams[1].SetVisible( false );
|
|
m_Beams[2].SetVisible( false );
|
|
}
|
|
}
|
|
*/
|
|
#endif
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
void CWeaponPhysCannon::ItemPostFrame()
|
|
{
|
|
CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
|
|
if ( pOwner == NULL )
|
|
{
|
|
// We found an object. Debounce the button
|
|
m_nAttack2Debounce = 0;
|
|
return;
|
|
}
|
|
|
|
//Check for object in pickup range
|
|
if ( m_bActive == false )
|
|
{
|
|
CheckForTarget();
|
|
|
|
if ( ( m_flElementDebounce < gpGlobals->curtime ) && ( m_nChangeState != ELEMENT_STATE_NONE ) )
|
|
{
|
|
if ( m_nChangeState == ELEMENT_STATE_OPEN )
|
|
{
|
|
OpenElements();
|
|
}
|
|
else if ( m_nChangeState == ELEMENT_STATE_CLOSED )
|
|
{
|
|
CloseElements();
|
|
}
|
|
|
|
m_nChangeState = ELEMENT_STATE_NONE;
|
|
}
|
|
}
|
|
|
|
// NOTE: Attack2 will be considered to be pressed until the first item is picked up.
|
|
int nAttack2Mask = pOwner->m_nButtons & (~m_nAttack2Debounce);
|
|
if ( nAttack2Mask & IN_ATTACK2 )
|
|
{
|
|
SecondaryAttack();
|
|
}
|
|
else
|
|
{
|
|
// Reset our debouncer
|
|
m_flLastDenySoundPlayed = false;
|
|
|
|
if ( m_bActive == false )
|
|
{
|
|
DoEffect( EFFECT_READY );
|
|
}
|
|
}
|
|
|
|
if (( pOwner->m_nButtons & IN_ATTACK2 ) == 0 )
|
|
{
|
|
m_nAttack2Debounce = 0;
|
|
}
|
|
|
|
if ( pOwner->m_nButtons & IN_ATTACK )
|
|
{
|
|
PrimaryAttack();
|
|
}
|
|
else
|
|
{
|
|
WeaponIdle();
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
#define PHYSCANNON_DANGER_SOUND_RADIUS 128
|
|
|
|
void CWeaponPhysCannon::LaunchObject( const Vector &vecDir, float flForce )
|
|
{
|
|
CBaseEntity *pObject = m_grabController.GetAttached();
|
|
|
|
if ( !(m_hLastPuntedObject == pObject && gpGlobals->curtime < m_flRepuntObjectTime) )
|
|
{
|
|
// FIRE!!!
|
|
if( pObject != NULL )
|
|
{
|
|
DetachObject( false, true );
|
|
|
|
m_hLastPuntedObject = pObject;
|
|
m_flRepuntObjectTime = gpGlobals->curtime + 0.5f;
|
|
|
|
// Launch
|
|
ApplyVelocityBasedForce( pObject, vecDir );
|
|
|
|
// Don't allow the gun to regrab a thrown object!!
|
|
m_flNextSecondaryAttack = m_flNextPrimaryAttack = gpGlobals->curtime + 0.5;
|
|
|
|
Vector center = pObject->WorldSpaceCenter();
|
|
|
|
//Do repulse effect
|
|
DoEffect( EFFECT_LAUNCH, ¢er );
|
|
|
|
m_hAttachedObject = NULL;
|
|
m_bActive = false;
|
|
}
|
|
}
|
|
|
|
// Stop our looping sound
|
|
if ( GetMotorSound() )
|
|
{
|
|
(CSoundEnvelopeController::GetController()).SoundChangeVolume( GetMotorSound(), 0.0f, 1.0f );
|
|
(CSoundEnvelopeController::GetController()).SoundChangePitch( GetMotorSound(), 50, 1.0f );
|
|
}
|
|
|
|
//Close the elements and suppress checking for a bit
|
|
m_nChangeState = ELEMENT_STATE_CLOSED;
|
|
m_flElementDebounce = gpGlobals->curtime + 0.1f;
|
|
m_flCheckSuppressTime = gpGlobals->curtime + 0.25f;
|
|
}
|
|
|
|
bool UTIL_IsCombineBall( CBaseEntity *pEntity );
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : *pTarget -
|
|
// Output : Returns true on success, false on failure.
|
|
//-----------------------------------------------------------------------------
|
|
bool CWeaponPhysCannon::CanPickupObject( CBaseEntity *pTarget )
|
|
{
|
|
#ifndef CLIENT_DLL
|
|
if ( pTarget == NULL )
|
|
return false;
|
|
|
|
if ( pTarget->GetBaseAnimating() && pTarget->GetBaseAnimating()->IsDissolving() )
|
|
return false;
|
|
|
|
if ( pTarget->IsEFlagSet( EFL_NO_PHYSCANNON_INTERACTION ) )
|
|
return false;
|
|
|
|
CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
|
|
|
|
if ( pOwner && pOwner->GetGroundEntity() == pTarget )
|
|
return false;
|
|
|
|
if ( pTarget->VPhysicsIsFlesh( ) )
|
|
return false;
|
|
|
|
IPhysicsObject *pObj = pTarget->VPhysicsGetObject();
|
|
|
|
if ( pObj && pObj->GetGameFlags() & FVPHYSICS_PLAYER_HELD )
|
|
return false;
|
|
|
|
if ( UTIL_IsCombineBall( pTarget ) )
|
|
{
|
|
return CBasePlayer::CanPickupObject( pTarget, 0, 0 );
|
|
}
|
|
|
|
return CBasePlayer::CanPickupObject( pTarget, physcannon_maxmass.GetFloat(), 0 );
|
|
#else
|
|
return false;
|
|
#endif
|
|
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CWeaponPhysCannon::OpenElements( void )
|
|
{
|
|
if ( m_bOpen )
|
|
return;
|
|
|
|
WeaponSound( SPECIAL2 );
|
|
|
|
CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
|
|
|
|
if ( pOwner == NULL )
|
|
return;
|
|
|
|
SendWeaponAnim( ACT_VM_IDLE );
|
|
|
|
m_bOpen = true;
|
|
|
|
DoEffect( EFFECT_READY );
|
|
|
|
#ifdef CLIENT
|
|
// Element prediction
|
|
m_ElementParameter.InitFromCurrent( 1.0f, 0.2f, INTERP_SPLINE );
|
|
m_bOldOpen = true;
|
|
#endif
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CWeaponPhysCannon::CloseElements( void )
|
|
{
|
|
if ( m_bOpen == false )
|
|
return;
|
|
|
|
WeaponSound( MELEE_HIT );
|
|
|
|
CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
|
|
|
|
if ( pOwner == NULL )
|
|
return;
|
|
|
|
SendWeaponAnim( ACT_VM_IDLE );
|
|
|
|
m_bOpen = false;
|
|
|
|
if ( GetMotorSound() )
|
|
{
|
|
(CSoundEnvelopeController::GetController()).SoundChangeVolume( GetMotorSound(), 0.0f, 1.0f );
|
|
(CSoundEnvelopeController::GetController()).SoundChangePitch( GetMotorSound(), 50, 1.0f );
|
|
}
|
|
|
|
DoEffect( EFFECT_CLOSED );
|
|
|
|
#ifdef CLIENT
|
|
// Element prediction
|
|
m_ElementParameter.InitFromCurrent( 0.0f, 0.5f, INTERP_SPLINE );
|
|
m_bOldOpen = false;
|
|
#endif
|
|
}
|
|
|
|
#define PHYSCANNON_MAX_MASS 500
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Output : float
|
|
//-----------------------------------------------------------------------------
|
|
float CWeaponPhysCannon::GetLoadPercentage( void )
|
|
{
|
|
float loadWeight = m_grabController.GetLoadWeight();
|
|
loadWeight /= physcannon_maxmass.GetFloat();
|
|
loadWeight = clamp( loadWeight, 0.0f, 1.0f );
|
|
return loadWeight;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Output : CSoundPatch
|
|
//-----------------------------------------------------------------------------
|
|
CSoundPatch *CWeaponPhysCannon::GetMotorSound( void )
|
|
{
|
|
if ( m_sndMotor == NULL )
|
|
{
|
|
CPASAttenuationFilter filter( this );
|
|
|
|
m_sndMotor = (CSoundEnvelopeController::GetController()).SoundCreate( filter, entindex(), CHAN_STATIC, "Weapon_PhysCannon.HoldSound", ATTN_NORM );
|
|
}
|
|
|
|
return m_sndMotor;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Shuts down sounds
|
|
//-----------------------------------------------------------------------------
|
|
void CWeaponPhysCannon::StopLoopingSounds()
|
|
{
|
|
if ( m_sndMotor != NULL )
|
|
{
|
|
(CSoundEnvelopeController::GetController()).SoundDestroy( m_sndMotor );
|
|
m_sndMotor = NULL;
|
|
}
|
|
|
|
#ifndef CLIENT_DLL
|
|
BaseClass::StopLoopingSounds();
|
|
#endif
|
|
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CWeaponPhysCannon::DestroyEffects( void )
|
|
{
|
|
#ifdef CLIENT_DLL
|
|
|
|
// Free our beams
|
|
m_Beams[0].Release();
|
|
m_Beams[1].Release();
|
|
m_Beams[2].Release();
|
|
|
|
#endif
|
|
|
|
// Stop everything
|
|
StopEffects();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CWeaponPhysCannon::StopEffects( bool stopSound )
|
|
{
|
|
// Turn off our effect state
|
|
DoEffect( EFFECT_NONE );
|
|
|
|
#ifndef CLIENT_DLL
|
|
//Shut off sounds
|
|
if ( stopSound && GetMotorSound() != NULL )
|
|
{
|
|
(CSoundEnvelopeController::GetController()).SoundFadeOut( GetMotorSound(), 0.1f );
|
|
}
|
|
#endif // !CLIENT_DLL
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CWeaponPhysCannon::StartEffects( void )
|
|
{
|
|
#ifdef CLIENT_DLL
|
|
|
|
// ------------------------------------------
|
|
// Core
|
|
// ------------------------------------------
|
|
|
|
if ( m_Parameters[PHYSCANNON_CORE].GetMaterial() == NULL )
|
|
{
|
|
m_Parameters[PHYSCANNON_CORE].GetScale().Init( 0.0f, 1.0f, 0.1f );
|
|
m_Parameters[PHYSCANNON_CORE].GetAlpha().Init( 255.0f, 255.0f, 0.1f );
|
|
m_Parameters[PHYSCANNON_CORE].SetAttachment( 1 );
|
|
|
|
if ( m_Parameters[PHYSCANNON_CORE].SetMaterial( PHYSCANNON_CENTER_GLOW ) == false )
|
|
{
|
|
// This means the texture was not found
|
|
Assert( 0 );
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------
|
|
// Blast
|
|
// ------------------------------------------
|
|
|
|
if ( m_Parameters[PHYSCANNON_BLAST].GetMaterial() == NULL )
|
|
{
|
|
m_Parameters[PHYSCANNON_BLAST].GetScale().Init( 0.0f, 1.0f, 0.1f );
|
|
m_Parameters[PHYSCANNON_BLAST].GetAlpha().Init( 255.0f, 255.0f, 0.1f );
|
|
m_Parameters[PHYSCANNON_BLAST].SetAttachment( 1 );
|
|
m_Parameters[PHYSCANNON_BLAST].SetVisible( false );
|
|
|
|
if ( m_Parameters[PHYSCANNON_BLAST].SetMaterial( PHYSCANNON_BLAST_SPRITE ) == false )
|
|
{
|
|
// This means the texture was not found
|
|
Assert( 0 );
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------
|
|
// Glows
|
|
// ------------------------------------------
|
|
|
|
const char *attachNamesGlowThirdPerson[NUM_GLOW_SPRITES] =
|
|
{
|
|
"fork1m",
|
|
"fork1t",
|
|
"fork2m",
|
|
"fork2t",
|
|
"fork3m",
|
|
"fork3t",
|
|
};
|
|
|
|
const char *attachNamesGlow[NUM_GLOW_SPRITES] =
|
|
{
|
|
"fork1b",
|
|
"fork1m",
|
|
"fork1t",
|
|
"fork2b",
|
|
"fork2m",
|
|
"fork2t"
|
|
};
|
|
|
|
//Create the glow sprites
|
|
for ( int i = PHYSCANNON_GLOW1; i < (PHYSCANNON_GLOW1+NUM_GLOW_SPRITES); i++ )
|
|
{
|
|
if ( m_Parameters[i].GetMaterial() != NULL )
|
|
continue;
|
|
|
|
m_Parameters[i].GetScale().SetAbsolute( 0.05f * SPRITE_SCALE );
|
|
m_Parameters[i].GetAlpha().SetAbsolute( 64.0f );
|
|
|
|
// Different for different views
|
|
if ( ShouldDrawUsingViewModel() )
|
|
{
|
|
m_Parameters[i].SetAttachment( LookupAttachment( attachNamesGlow[i-PHYSCANNON_GLOW1] ) );
|
|
}
|
|
else
|
|
{
|
|
m_Parameters[i].SetAttachment( LookupAttachment( attachNamesGlowThirdPerson[i-PHYSCANNON_GLOW1] ) );
|
|
}
|
|
m_Parameters[i].SetColor( Vector( 255, 128, 0 ) );
|
|
|
|
if ( m_Parameters[i].SetMaterial( PHYSCANNON_GLOW_SPRITE ) == false )
|
|
{
|
|
// This means the texture was not found
|
|
Assert( 0 );
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------
|
|
// End caps
|
|
// ------------------------------------------
|
|
|
|
const char *attachNamesEndCap[NUM_ENDCAP_SPRITES] =
|
|
{
|
|
"fork1t",
|
|
"fork2t",
|
|
"fork3t"
|
|
};
|
|
|
|
//Create the glow sprites
|
|
for ( int i = PHYSCANNON_ENDCAP1; i < (PHYSCANNON_ENDCAP1+NUM_ENDCAP_SPRITES); i++ )
|
|
{
|
|
if ( m_Parameters[i].GetMaterial() != NULL )
|
|
continue;
|
|
|
|
m_Parameters[i].GetScale().SetAbsolute( 0.05f * SPRITE_SCALE );
|
|
m_Parameters[i].GetAlpha().SetAbsolute( 255.0f );
|
|
m_Parameters[i].SetAttachment( LookupAttachment( attachNamesEndCap[i-PHYSCANNON_ENDCAP1] ) );
|
|
m_Parameters[i].SetVisible( false );
|
|
|
|
if ( m_Parameters[i].SetMaterial( PHYSCANNON_ENDCAP_SPRITE ) == false )
|
|
{
|
|
// This means the texture was not found
|
|
Assert( 0 );
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Closing effects
|
|
//-----------------------------------------------------------------------------
|
|
void CWeaponPhysCannon::DoEffectClosed( void )
|
|
{
|
|
|
|
#ifdef CLIENT_DLL
|
|
|
|
// Turn off the end-caps
|
|
for ( int i = PHYSCANNON_ENDCAP1; i < (PHYSCANNON_ENDCAP1+NUM_ENDCAP_SPRITES); i++ )
|
|
{
|
|
m_Parameters[i].SetVisible( false );
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Ready effects
|
|
//-----------------------------------------------------------------------------
|
|
void CWeaponPhysCannon::DoEffectReady( void )
|
|
{
|
|
|
|
#ifdef CLIENT_DLL
|
|
|
|
// Special POV case
|
|
if ( ShouldDrawUsingViewModel() )
|
|
{
|
|
//Turn on the center sprite
|
|
m_Parameters[PHYSCANNON_CORE].GetScale().InitFromCurrent( 14.0f, 0.2f );
|
|
m_Parameters[PHYSCANNON_CORE].GetAlpha().InitFromCurrent( 128.0f, 0.2f );
|
|
m_Parameters[PHYSCANNON_CORE].SetVisible();
|
|
}
|
|
else
|
|
{
|
|
//Turn off the center sprite
|
|
m_Parameters[PHYSCANNON_CORE].GetScale().InitFromCurrent( 8.0f, 0.2f );
|
|
m_Parameters[PHYSCANNON_CORE].GetAlpha().InitFromCurrent( 0.0f, 0.2f );
|
|
m_Parameters[PHYSCANNON_CORE].SetVisible();
|
|
}
|
|
|
|
// Turn on the glow sprites
|
|
for ( int i = PHYSCANNON_GLOW1; i < (PHYSCANNON_GLOW1+NUM_GLOW_SPRITES); i++ )
|
|
{
|
|
m_Parameters[i].GetScale().InitFromCurrent( 0.4f * SPRITE_SCALE, 0.2f );
|
|
m_Parameters[i].GetAlpha().InitFromCurrent( 64.0f, 0.2f );
|
|
m_Parameters[i].SetVisible();
|
|
}
|
|
|
|
// Turn on the glow sprites
|
|
for ( int i = PHYSCANNON_ENDCAP1; i < (PHYSCANNON_ENDCAP1+NUM_ENDCAP_SPRITES); i++ )
|
|
{
|
|
m_Parameters[i].SetVisible( false );
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Holding effects
|
|
//-----------------------------------------------------------------------------
|
|
void CWeaponPhysCannon::DoEffectHolding( void )
|
|
{
|
|
|
|
#ifdef CLIENT_DLL
|
|
|
|
if ( ShouldDrawUsingViewModel() )
|
|
{
|
|
// Scale up the center sprite
|
|
m_Parameters[PHYSCANNON_CORE].GetScale().InitFromCurrent( 16.0f, 0.2f );
|
|
m_Parameters[PHYSCANNON_CORE].GetAlpha().InitFromCurrent( 255.0f, 0.1f );
|
|
m_Parameters[PHYSCANNON_CORE].SetVisible();
|
|
|
|
// Prepare for scale up
|
|
m_Parameters[PHYSCANNON_BLAST].SetVisible( false );
|
|
|
|
// Turn on the glow sprites
|
|
for ( int i = PHYSCANNON_GLOW1; i < (PHYSCANNON_GLOW1+NUM_GLOW_SPRITES); i++ )
|
|
{
|
|
m_Parameters[i].GetScale().InitFromCurrent( 0.5f * SPRITE_SCALE, 0.2f );
|
|
m_Parameters[i].GetAlpha().InitFromCurrent( 64.0f, 0.2f );
|
|
m_Parameters[i].SetVisible();
|
|
}
|
|
|
|
// Turn on the glow sprites
|
|
// NOTE: The last glow is left off for first-person
|
|
for ( int i = PHYSCANNON_ENDCAP1; i < (PHYSCANNON_ENDCAP1+NUM_ENDCAP_SPRITES-1); i++ )
|
|
{
|
|
m_Parameters[i].SetVisible();
|
|
}
|
|
|
|
// Create our beams
|
|
CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
|
|
CBaseEntity *pBeamEnt = pOwner->GetViewModel();
|
|
|
|
// Setup the beams
|
|
m_Beams[0].Init( LookupAttachment( "fork1t" ), 1, pBeamEnt, true );
|
|
m_Beams[1].Init( LookupAttachment( "fork2t" ), 1, pBeamEnt, true );
|
|
|
|
// Set them visible
|
|
m_Beams[0].SetVisible();
|
|
m_Beams[1].SetVisible();
|
|
}
|
|
else
|
|
{
|
|
// Scale up the center sprite
|
|
m_Parameters[PHYSCANNON_CORE].GetScale().InitFromCurrent( 14.0f, 0.2f );
|
|
m_Parameters[PHYSCANNON_CORE].GetAlpha().InitFromCurrent( 255.0f, 0.1f );
|
|
m_Parameters[PHYSCANNON_CORE].SetVisible();
|
|
|
|
// Prepare for scale up
|
|
m_Parameters[PHYSCANNON_BLAST].SetVisible( false );
|
|
|
|
// Turn on the glow sprites
|
|
for ( int i = PHYSCANNON_GLOW1; i < (PHYSCANNON_GLOW1+NUM_GLOW_SPRITES); i++ )
|
|
{
|
|
m_Parameters[i].GetScale().InitFromCurrent( 0.5f * SPRITE_SCALE, 0.2f );
|
|
m_Parameters[i].GetAlpha().InitFromCurrent( 64.0f, 0.2f );
|
|
m_Parameters[i].SetVisible();
|
|
}
|
|
|
|
// Turn on the glow sprites
|
|
for ( int i = PHYSCANNON_ENDCAP1; i < (PHYSCANNON_ENDCAP1+NUM_ENDCAP_SPRITES); i++ )
|
|
{
|
|
m_Parameters[i].SetVisible();
|
|
}
|
|
|
|
// Setup the beams
|
|
m_Beams[0].Init( LookupAttachment( "fork1t" ), 1, this, false );
|
|
m_Beams[1].Init( LookupAttachment( "fork2t" ), 1, this, false );
|
|
m_Beams[2].Init( LookupAttachment( "fork3t" ), 1, this, false );
|
|
|
|
// Set them visible
|
|
m_Beams[0].SetVisible();
|
|
m_Beams[1].SetVisible();
|
|
m_Beams[2].SetVisible();
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Launch effects
|
|
//-----------------------------------------------------------------------------
|
|
void CWeaponPhysCannon::DoEffectLaunch( Vector *pos )
|
|
{
|
|
CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
|
|
if ( pOwner == NULL )
|
|
return;
|
|
|
|
Vector endPos;
|
|
Vector shotDir;
|
|
|
|
// See if we need to predict this position
|
|
if ( pos == NULL )
|
|
{
|
|
// Hit an entity if we're holding one
|
|
if ( m_hAttachedObject )
|
|
{
|
|
endPos = m_hAttachedObject->WorldSpaceCenter();
|
|
|
|
shotDir = endPos - pOwner->Weapon_ShootPosition();
|
|
VectorNormalize( shotDir );
|
|
}
|
|
else
|
|
{
|
|
// Otherwise try and find the right spot
|
|
endPos = pOwner->Weapon_ShootPosition();
|
|
pOwner->EyeVectors( &shotDir );
|
|
|
|
trace_t tr;
|
|
UTIL_TraceLine( endPos, endPos + ( shotDir * MAX_TRACE_LENGTH ), MASK_SHOT, pOwner, COLLISION_GROUP_NONE, &tr );
|
|
|
|
endPos = tr.endpos;
|
|
shotDir = endPos - pOwner->Weapon_ShootPosition();
|
|
VectorNormalize( shotDir );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Use what is supplied
|
|
endPos = *pos;
|
|
shotDir = ( endPos - pOwner->Weapon_ShootPosition() );
|
|
VectorNormalize( shotDir );
|
|
}
|
|
|
|
// End hit
|
|
CPVSFilter filter( endPos );
|
|
|
|
// Don't send this to the owning player, they already had it predicted
|
|
if ( IsPredicted() )
|
|
{
|
|
filter.UsePredictionRules();
|
|
}
|
|
|
|
// Do an impact hit
|
|
CEffectData data;
|
|
data.m_vOrigin = endPos;
|
|
#ifdef CLIENT_DLL
|
|
data.m_hEntity = GetRefEHandle();
|
|
#else
|
|
data.m_nEntIndex = entindex();
|
|
#endif
|
|
|
|
te->DispatchEffect( filter, 0.0, data.m_vOrigin, "PhyscannonImpact", data );
|
|
|
|
#ifdef CLIENT_DLL
|
|
|
|
//Turn on the blast sprite and scale
|
|
m_Parameters[PHYSCANNON_BLAST].GetScale().Init( 8.0f, 64.0f, 0.1f );
|
|
m_Parameters[PHYSCANNON_BLAST].GetAlpha().Init( 255.0f, 0.0f, 0.2f );
|
|
m_Parameters[PHYSCANNON_BLAST].SetVisible();
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Shutdown for the weapon when it's holstered
|
|
//-----------------------------------------------------------------------------
|
|
void CWeaponPhysCannon::DoEffectNone( void )
|
|
{
|
|
#ifdef CLIENT_DLL
|
|
|
|
//Turn off main glows
|
|
m_Parameters[PHYSCANNON_CORE].SetVisible( false );
|
|
m_Parameters[PHYSCANNON_BLAST].SetVisible( false );
|
|
|
|
for ( int i = PHYSCANNON_GLOW1; i < (PHYSCANNON_GLOW1+NUM_GLOW_SPRITES); i++ )
|
|
{
|
|
m_Parameters[i].SetVisible( false );
|
|
}
|
|
|
|
// Turn on the glow sprites
|
|
for ( int i = PHYSCANNON_ENDCAP1; i < (PHYSCANNON_ENDCAP1+NUM_ENDCAP_SPRITES); i++ )
|
|
{
|
|
m_Parameters[i].SetVisible( false );
|
|
}
|
|
|
|
m_Beams[0].SetVisible( false );
|
|
m_Beams[1].SetVisible( false );
|
|
m_Beams[2].SetVisible( false );
|
|
|
|
#endif
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : effectType -
|
|
//-----------------------------------------------------------------------------
|
|
void CWeaponPhysCannon::DoEffect( int effectType, Vector *pos )
|
|
{
|
|
m_EffectState = effectType;
|
|
|
|
#ifdef CLIENT_DLL
|
|
// Save predicted state
|
|
m_nOldEffectState = m_EffectState;
|
|
#endif
|
|
|
|
switch( effectType )
|
|
{
|
|
case EFFECT_CLOSED:
|
|
DoEffectClosed( );
|
|
break;
|
|
|
|
case EFFECT_READY:
|
|
DoEffectReady( );
|
|
break;
|
|
|
|
case EFFECT_HOLDING:
|
|
DoEffectHolding();
|
|
break;
|
|
|
|
case EFFECT_LAUNCH:
|
|
DoEffectLaunch( pos );
|
|
break;
|
|
|
|
default:
|
|
case EFFECT_NONE:
|
|
DoEffectNone();
|
|
break;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : iIndex -
|
|
// Output : const char
|
|
//-----------------------------------------------------------------------------
|
|
const char *CWeaponPhysCannon::GetShootSound( int iIndex ) const
|
|
{
|
|
return BaseClass::GetShootSound( iIndex );
|
|
}
|
|
|
|
#ifdef CLIENT_DLL
|
|
|
|
extern void FormatViewModelAttachment( Vector &vOrigin, bool bInverse );
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Gets the complete list of values needed to render an effect from an
|
|
// effect parameter
|
|
//-----------------------------------------------------------------------------
|
|
void CWeaponPhysCannon::GetEffectParameters( EffectType_t effectID, color32 &color, float &scale, IMaterial **pMaterial, Vector &vecAttachment )
|
|
{
|
|
const float dt = gpGlobals->curtime;
|
|
|
|
// Get alpha
|
|
float alpha = m_Parameters[effectID].GetAlpha().Interp( dt );
|
|
|
|
// Get scale
|
|
scale = m_Parameters[effectID].GetScale().Interp( dt );
|
|
|
|
// Get material
|
|
*pMaterial = (IMaterial *) m_Parameters[effectID].GetMaterial();
|
|
|
|
// Setup the color
|
|
color.r = (int) m_Parameters[effectID].GetColor().x;
|
|
color.g = (int) m_Parameters[effectID].GetColor().y;
|
|
color.b = (int) m_Parameters[effectID].GetColor().z;
|
|
color.a = (int) alpha;
|
|
|
|
// Setup the attachment
|
|
int attachment = m_Parameters[effectID].GetAttachment();
|
|
QAngle angles;
|
|
|
|
// Format for first-person
|
|
if ( ShouldDrawUsingViewModel() )
|
|
{
|
|
CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
|
|
|
|
if ( pOwner != NULL )
|
|
{
|
|
pOwner->GetViewModel()->GetAttachment( attachment, vecAttachment, angles );
|
|
::FormatViewModelAttachment( vecAttachment, true );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
GetAttachment( attachment, vecAttachment, angles );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Whether or not an effect is set to display
|
|
//-----------------------------------------------------------------------------
|
|
bool CWeaponPhysCannon::IsEffectVisible( EffectType_t effectID )
|
|
{
|
|
return m_Parameters[effectID].IsVisible();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Draws the effect sprite, given an effect parameter ID
|
|
//-----------------------------------------------------------------------------
|
|
void CWeaponPhysCannon::DrawEffectSprite( EffectType_t effectID )
|
|
{
|
|
color32 color;
|
|
float scale;
|
|
IMaterial *pMaterial;
|
|
Vector vecAttachment;
|
|
|
|
// Don't draw invisible effects
|
|
if ( IsEffectVisible( effectID ) == false )
|
|
return;
|
|
|
|
// Get all of our parameters
|
|
GetEffectParameters( effectID, color, scale, &pMaterial, vecAttachment );
|
|
|
|
// Msg( "Scale: %.2f\tAlpha: %.2f\n", scale, alpha );
|
|
|
|
// Don't render fully translucent objects
|
|
if ( color.a <= 0.0f )
|
|
return;
|
|
|
|
// Draw the sprite
|
|
CMatRenderContextPtr pRenderContext( materials );
|
|
pRenderContext->Bind( pMaterial, this );
|
|
DrawSprite( vecAttachment, scale, scale, color );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Render our third-person effects
|
|
//-----------------------------------------------------------------------------
|
|
void CWeaponPhysCannon::DrawEffects( void )
|
|
{
|
|
// Draw the core effects
|
|
DrawEffectSprite( PHYSCANNON_CORE );
|
|
DrawEffectSprite( PHYSCANNON_BLAST );
|
|
|
|
// Draw the glows
|
|
for ( int i = PHYSCANNON_GLOW1; i < (PHYSCANNON_GLOW1+NUM_GLOW_SPRITES); i++ )
|
|
{
|
|
DrawEffectSprite( (EffectType_t) i );
|
|
}
|
|
|
|
// Draw the endcaps
|
|
for ( int i = PHYSCANNON_ENDCAP1; i < (PHYSCANNON_ENDCAP1+NUM_ENDCAP_SPRITES); i++ )
|
|
{
|
|
DrawEffectSprite( (EffectType_t) i );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Third-person function call to render world model
|
|
//-----------------------------------------------------------------------------
|
|
int CWeaponPhysCannon::DrawModel( int flags )
|
|
{
|
|
// Only render these on the transparent pass
|
|
if ( flags & STUDIO_TRANSPARENCY )
|
|
{
|
|
DrawEffects();
|
|
return 1;
|
|
}
|
|
|
|
// Only do this on the opaque pass
|
|
return BaseClass::DrawModel( flags );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: First-person function call after viewmodel has been drawn
|
|
//-----------------------------------------------------------------------------
|
|
void CWeaponPhysCannon::ViewModelDrawn( C_BaseViewModel *pBaseViewModel )
|
|
{
|
|
// Render our effects
|
|
DrawEffects();
|
|
|
|
// Pass this back up
|
|
BaseClass::ViewModelDrawn( pBaseViewModel );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: We are always considered transparent
|
|
//-----------------------------------------------------------------------------
|
|
bool CWeaponPhysCannon::IsTransparent( void )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CWeaponPhysCannon::NotifyShouldTransmit( ShouldTransmitState_t state )
|
|
{
|
|
BaseClass::NotifyShouldTransmit(state);
|
|
|
|
if ( state == SHOULDTRANSMIT_END )
|
|
{
|
|
DoEffect( EFFECT_NONE );
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// EXTERNAL API
|
|
//-----------------------------------------------------------------------------
|
|
void PhysCannonForceDrop( CBaseCombatWeapon *pActiveWeapon, CBaseEntity *pOnlyIfHoldingThis )
|
|
{
|
|
CWeaponPhysCannon *pCannon = dynamic_cast<CWeaponPhysCannon *>(pActiveWeapon);
|
|
if ( pCannon )
|
|
{
|
|
if ( pOnlyIfHoldingThis )
|
|
{
|
|
pCannon->DropIfEntityHeld( pOnlyIfHoldingThis );
|
|
}
|
|
else
|
|
{
|
|
pCannon->ForceDrop();
|
|
}
|
|
}
|
|
}
|
|
|
|
bool PlayerPickupControllerIsHoldingEntity( CBaseEntity *pPickupControllerEntity, CBaseEntity *pHeldEntity )
|
|
{
|
|
CPlayerPickupController *pController = dynamic_cast<CPlayerPickupController *>(pPickupControllerEntity);
|
|
|
|
return pController ? pController->IsHoldingEntity( pHeldEntity ) : false;
|
|
}
|
|
|
|
|
|
float PhysCannonGetHeldObjectMass( CBaseCombatWeapon *pActiveWeapon, IPhysicsObject *pHeldObject )
|
|
{
|
|
float mass = 0.0f;
|
|
CWeaponPhysCannon *pCannon = dynamic_cast<CWeaponPhysCannon *>(pActiveWeapon);
|
|
if ( pCannon )
|
|
{
|
|
CGrabController &grab = pCannon->GetGrabController();
|
|
mass = grab.GetSavedMass( pHeldObject );
|
|
}
|
|
|
|
return mass;
|
|
}
|
|
|
|
CBaseEntity *PhysCannonGetHeldEntity( CBaseCombatWeapon *pActiveWeapon )
|
|
{
|
|
CWeaponPhysCannon *pCannon = dynamic_cast<CWeaponPhysCannon *>(pActiveWeapon);
|
|
if ( pCannon )
|
|
{
|
|
CGrabController &grab = pCannon->GetGrabController();
|
|
return grab.GetAttached();
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
#ifdef CLIENT_DLL
|
|
|
|
extern void FX_GaussExplosion( const Vector &pos, const Vector &dir, int type );
|
|
|
|
void CallbackPhyscannonImpact( const CEffectData &data )
|
|
{
|
|
C_BaseEntity *pEnt = data.GetEntity();
|
|
if ( pEnt == NULL )
|
|
return;
|
|
|
|
Vector vecAttachment;
|
|
QAngle vecAngles;
|
|
|
|
C_BaseCombatWeapon *pWeapon = dynamic_cast<C_BaseCombatWeapon *>(pEnt);
|
|
|
|
if ( pWeapon == NULL )
|
|
return;
|
|
|
|
pWeapon->GetAttachment( 1, vecAttachment, vecAngles );
|
|
|
|
Vector dir = ( data.m_vOrigin - vecAttachment );
|
|
VectorNormalize( dir );
|
|
|
|
// Do special first-person fix-up
|
|
if ( pWeapon->GetOwner() == CBasePlayer::GetLocalPlayer() )
|
|
{
|
|
// Translate the attachment entity to the viewmodel
|
|
C_BasePlayer *pPlayer = dynamic_cast<C_BasePlayer *>(pWeapon->GetOwner());
|
|
|
|
if ( pPlayer )
|
|
{
|
|
pEnt = pPlayer->GetViewModel();
|
|
}
|
|
|
|
// Format attachment for first-person view!
|
|
::FormatViewModelAttachment( vecAttachment, true );
|
|
|
|
// Explosions at the impact point
|
|
FX_GaussExplosion( data.m_vOrigin, -dir, 0 );
|
|
|
|
// Draw a beam
|
|
BeamInfo_t beamInfo;
|
|
|
|
beamInfo.m_pStartEnt = pEnt;
|
|
beamInfo.m_nStartAttachment = 1;
|
|
beamInfo.m_pEndEnt = NULL;
|
|
beamInfo.m_nEndAttachment = -1;
|
|
beamInfo.m_vecStart = vec3_origin;
|
|
beamInfo.m_vecEnd = data.m_vOrigin;
|
|
beamInfo.m_pszModelName = PHYSCANNON_BEAM_SPRITE;
|
|
beamInfo.m_flHaloScale = 0.0f;
|
|
beamInfo.m_flLife = 0.1f;
|
|
beamInfo.m_flWidth = 12.0f;
|
|
beamInfo.m_flEndWidth = 4.0f;
|
|
beamInfo.m_flFadeLength = 0.0f;
|
|
beamInfo.m_flAmplitude = 0;
|
|
beamInfo.m_flBrightness = 255.0;
|
|
beamInfo.m_flSpeed = 0.0f;
|
|
beamInfo.m_nStartFrame = 0.0;
|
|
beamInfo.m_flFrameRate = 30.0;
|
|
beamInfo.m_flRed = 255.0;
|
|
beamInfo.m_flGreen = 255.0;
|
|
beamInfo.m_flBlue = 255.0;
|
|
beamInfo.m_nSegments = 16;
|
|
beamInfo.m_bRenderable = true;
|
|
beamInfo.m_nFlags = FBEAM_ONLYNOISEONCE;
|
|
|
|
beams->CreateBeamEntPoint( beamInfo );
|
|
}
|
|
else
|
|
{
|
|
// Explosion at the starting point
|
|
FX_GaussExplosion( vecAttachment, dir, 0 );
|
|
}
|
|
}
|
|
|
|
DECLARE_CLIENT_EFFECT( "PhyscannonImpact", CallbackPhyscannonImpact );
|
|
|
|
#endif
|