1524 lines
39 KiB
C++
1524 lines
39 KiB
C++
//========= Copyright Valve Corporation, All rights reserved. ============//
|
|
//
|
|
// Purpose:
|
|
//
|
|
// $NoKeywords: $
|
|
//=============================================================================//
|
|
|
|
#include "cbase.h"
|
|
#include "beam_shared.h"
|
|
#include "player.h"
|
|
#include "gamerules.h"
|
|
#include "basecombatweapon.h"
|
|
#include "baseviewmodel.h"
|
|
#include "vphysics/constraints.h"
|
|
#include "physics.h"
|
|
#include "in_buttons.h"
|
|
#include "IEffects.h"
|
|
#include "engine/IEngineSound.h"
|
|
#include "ndebugoverlay.h"
|
|
#include "physics_saverestore.h"
|
|
#include "player_pickup.h"
|
|
#include "SoundEmitterSystem/isoundemittersystembase.h"
|
|
|
|
// memdbgon must be the last include file in a .cpp file!!!
|
|
#include "tier0/memdbgon.h"
|
|
|
|
ConVar phys_gunmass("phys_gunmass", "200");
|
|
ConVar phys_gunvel("phys_gunvel", "400");
|
|
ConVar phys_gunforce("phys_gunforce", "5e5" );
|
|
ConVar phys_guntorque("phys_guntorque", "100" );
|
|
ConVar phys_gunglueradius("phys_gunglueradius", "128" );
|
|
|
|
static int g_physgunBeam;
|
|
#define PHYSGUN_BEAM_SPRITE "sprites/physbeam.vmt"
|
|
|
|
#define MAX_PELLETS 16
|
|
|
|
class CWeaponGravityGun;
|
|
|
|
class CGravityPellet : public CBaseAnimating
|
|
{
|
|
DECLARE_CLASS( CGravityPellet, CBaseAnimating );
|
|
public:
|
|
DECLARE_DATADESC();
|
|
|
|
~CGravityPellet();
|
|
void Precache()
|
|
{
|
|
SetModelName( MAKE_STRING( "models/weapons/glueblob.mdl" ) );
|
|
PrecacheModel( STRING( GetModelName() ) );
|
|
BaseClass::Precache();
|
|
}
|
|
void Spawn()
|
|
{
|
|
Precache();
|
|
SetModel( STRING( GetModelName() ) );
|
|
SetSolid( SOLID_NONE );
|
|
SetMoveType( MOVETYPE_NONE );
|
|
AddEffects( EF_NOSHADOW );
|
|
SetRenderColor( 255, 0, 0 );
|
|
m_isInert = false;
|
|
}
|
|
|
|
bool IsInert()
|
|
{
|
|
return m_isInert;
|
|
}
|
|
|
|
bool MakeConstraint( CBaseEntity *pObject )
|
|
{
|
|
IPhysicsObject *pReference = g_PhysWorldObject;
|
|
if ( GetMoveParent() )
|
|
{
|
|
pReference = GetMoveParent()->VPhysicsGetObject();
|
|
}
|
|
IPhysicsObject *pAttached = pObject->VPhysicsGetObject();
|
|
if ( !pReference || !pAttached )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
constraint_fixedparams_t fixed;
|
|
fixed.Defaults();
|
|
fixed.InitWithCurrentObjectState( pReference, pAttached );
|
|
|
|
m_pConstraint = physenv->CreateFixedConstraint( pReference, pAttached, NULL, fixed );
|
|
m_pConstraint->SetGameData( (void *)this );
|
|
|
|
MakeInert();
|
|
return true;
|
|
}
|
|
|
|
void MakeInert()
|
|
{
|
|
SetRenderColor( 64, 64, 128 );
|
|
m_isInert = true;
|
|
}
|
|
|
|
void InputOnBreak( inputdata_t &inputdata )
|
|
{
|
|
UTIL_Remove(this);
|
|
}
|
|
|
|
IPhysicsConstraint *m_pConstraint;
|
|
bool m_isInert;
|
|
};
|
|
|
|
LINK_ENTITY_TO_CLASS(gravity_pellet, CGravityPellet);
|
|
PRECACHE_REGISTER(gravity_pellet);
|
|
|
|
BEGIN_DATADESC( CGravityPellet )
|
|
|
|
DEFINE_PHYSPTR( m_pConstraint ),
|
|
DEFINE_FIELD( m_isInert, FIELD_BOOLEAN ),
|
|
// physics system will fire this input if the constraint breaks due to physics
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "ConstraintBroken", InputOnBreak ),
|
|
|
|
END_DATADESC()
|
|
|
|
|
|
CGravityPellet::~CGravityPellet()
|
|
{
|
|
if ( m_pConstraint )
|
|
{
|
|
physenv->DestroyConstraint( m_pConstraint );
|
|
}
|
|
}
|
|
|
|
class CGravControllerPoint : public IMotionEvent
|
|
{
|
|
DECLARE_SIMPLE_DATADESC();
|
|
|
|
public:
|
|
CGravControllerPoint( void );
|
|
~CGravControllerPoint( void );
|
|
void AttachEntity( CBaseEntity *pEntity, IPhysicsObject *pPhys, const Vector &position );
|
|
void DetachEntity( void );
|
|
void SetMaxVelocity( float maxVel )
|
|
{
|
|
m_maxVel = maxVel;
|
|
}
|
|
void SetTargetPosition( const Vector &target )
|
|
{
|
|
m_targetPosition = target;
|
|
if ( m_attachedEntity == NULL )
|
|
{
|
|
m_worldPosition = target;
|
|
}
|
|
m_timeToArrive = gpGlobals->frametime;
|
|
}
|
|
|
|
void SetAutoAlign( const Vector &localDir, const Vector &localPos, const Vector &worldAlignDir, const Vector &worldAlignPos )
|
|
{
|
|
m_align = true;
|
|
m_localAlignNormal = -localDir;
|
|
m_localAlignPosition = localPos;
|
|
m_targetAlignNormal = worldAlignDir;
|
|
m_targetAlignPosition = worldAlignPos;
|
|
}
|
|
|
|
void ClearAutoAlign()
|
|
{
|
|
m_align = false;
|
|
}
|
|
|
|
IMotionEvent::simresult_e Simulate( IPhysicsMotionController *pController, IPhysicsObject *pObject, float deltaTime, Vector &linear, AngularImpulse &angular );
|
|
Vector m_localPosition;
|
|
Vector m_targetPosition;
|
|
Vector m_worldPosition;
|
|
Vector m_localAlignNormal;
|
|
Vector m_localAlignPosition;
|
|
Vector m_targetAlignNormal;
|
|
Vector m_targetAlignPosition;
|
|
bool m_align;
|
|
float m_saveDamping;
|
|
float m_maxVel;
|
|
float m_maxAcceleration;
|
|
Vector m_maxAngularAcceleration;
|
|
EHANDLE m_attachedEntity;
|
|
QAngle m_targetRotation;
|
|
float m_timeToArrive;
|
|
|
|
IPhysicsMotionController *m_controller;
|
|
};
|
|
|
|
|
|
BEGIN_SIMPLE_DATADESC( CGravControllerPoint )
|
|
|
|
DEFINE_FIELD( m_localPosition, FIELD_VECTOR ),
|
|
DEFINE_FIELD( m_targetPosition, FIELD_POSITION_VECTOR ),
|
|
DEFINE_FIELD( m_worldPosition, FIELD_POSITION_VECTOR ),
|
|
DEFINE_FIELD( m_localAlignNormal, FIELD_VECTOR ),
|
|
DEFINE_FIELD( m_localAlignPosition, FIELD_VECTOR ),
|
|
DEFINE_FIELD( m_targetAlignNormal, FIELD_VECTOR ),
|
|
DEFINE_FIELD( m_targetAlignPosition, FIELD_POSITION_VECTOR ),
|
|
DEFINE_FIELD( m_align, FIELD_BOOLEAN ),
|
|
DEFINE_FIELD( m_saveDamping, FIELD_FLOAT ),
|
|
DEFINE_FIELD( m_maxVel, FIELD_FLOAT ),
|
|
DEFINE_FIELD( m_maxAcceleration, FIELD_FLOAT ),
|
|
DEFINE_FIELD( m_maxAngularAcceleration, FIELD_VECTOR ),
|
|
DEFINE_FIELD( m_attachedEntity, FIELD_EHANDLE ),
|
|
DEFINE_FIELD( m_targetRotation, FIELD_VECTOR ),
|
|
DEFINE_FIELD( m_timeToArrive, FIELD_FLOAT ),
|
|
|
|
// Physptrs can't be saved in embedded classes... this is to silence classcheck
|
|
// DEFINE_PHYSPTR( m_controller ),
|
|
|
|
END_DATADESC()
|
|
|
|
|
|
CGravControllerPoint::CGravControllerPoint( void )
|
|
{
|
|
m_attachedEntity = NULL;
|
|
}
|
|
|
|
CGravControllerPoint::~CGravControllerPoint( void )
|
|
{
|
|
DetachEntity();
|
|
}
|
|
|
|
|
|
void CGravControllerPoint::AttachEntity( CBaseEntity *pEntity, IPhysicsObject *pPhys, const Vector &position )
|
|
{
|
|
m_attachedEntity = pEntity;
|
|
pPhys->WorldToLocal( &m_localPosition, position );
|
|
m_worldPosition = position;
|
|
pPhys->GetDamping( NULL, &m_saveDamping );
|
|
float damping = 2;
|
|
pPhys->SetDamping( NULL, &damping );
|
|
m_controller = physenv->CreateMotionController( this );
|
|
m_controller->AttachObject( pPhys, true );
|
|
m_controller->SetPriority( IPhysicsMotionController::HIGH_PRIORITY );
|
|
SetTargetPosition( position );
|
|
m_maxAcceleration = phys_gunforce.GetFloat() * pPhys->GetInvMass();
|
|
m_targetRotation = pEntity->GetAbsAngles();
|
|
float torque = phys_guntorque.GetFloat();
|
|
m_maxAngularAcceleration = torque * pPhys->GetInvInertia();
|
|
}
|
|
|
|
void CGravControllerPoint::DetachEntity( void )
|
|
{
|
|
CBaseEntity *pEntity = m_attachedEntity;
|
|
if ( pEntity )
|
|
{
|
|
IPhysicsObject *pPhys = pEntity->VPhysicsGetObject();
|
|
if ( pPhys )
|
|
{
|
|
// on the odd chance that it's gone to sleep while under anti-gravity
|
|
pPhys->Wake();
|
|
pPhys->SetDamping( NULL, &m_saveDamping );
|
|
}
|
|
}
|
|
m_attachedEntity = NULL;
|
|
physenv->DestroyMotionController( m_controller );
|
|
m_controller = NULL;
|
|
|
|
// UNDONE: Does this help the networking?
|
|
m_targetPosition = vec3_origin;
|
|
m_worldPosition = vec3_origin;
|
|
}
|
|
|
|
void AxisAngleQAngle( const Vector &axis, float angle, QAngle &outAngles )
|
|
{
|
|
// map back to HL rotation axes
|
|
outAngles.z = axis.x * angle;
|
|
outAngles.x = axis.y * angle;
|
|
outAngles.y = axis.z * angle;
|
|
}
|
|
|
|
IMotionEvent::simresult_e CGravControllerPoint::Simulate( IPhysicsMotionController *pController, IPhysicsObject *pObject, float deltaTime, Vector &linear, AngularImpulse &angular )
|
|
{
|
|
Vector vel;
|
|
AngularImpulse angVel;
|
|
|
|
float fracRemainingSimTime = 1.0;
|
|
if ( m_timeToArrive > 0 )
|
|
{
|
|
fracRemainingSimTime *= deltaTime / m_timeToArrive;
|
|
if ( fracRemainingSimTime > 1 )
|
|
{
|
|
fracRemainingSimTime = 1;
|
|
}
|
|
}
|
|
|
|
m_timeToArrive -= deltaTime;
|
|
if ( m_timeToArrive < 0 )
|
|
{
|
|
m_timeToArrive = 0;
|
|
}
|
|
|
|
float invDeltaTime = (1.0f / deltaTime);
|
|
Vector world;
|
|
pObject->LocalToWorld( &world, m_localPosition );
|
|
m_worldPosition = world;
|
|
pObject->GetVelocity( &vel, &angVel );
|
|
//pObject->GetVelocityAtPoint( world, &vel );
|
|
float damping = 1.0;
|
|
world += vel * deltaTime * damping;
|
|
Vector delta = (m_targetPosition - world) * fracRemainingSimTime * invDeltaTime;
|
|
Vector alignDir;
|
|
linear = vec3_origin;
|
|
angular = vec3_origin;
|
|
|
|
if ( m_align )
|
|
{
|
|
QAngle angles;
|
|
Vector origin;
|
|
Vector axis;
|
|
AngularImpulse torque;
|
|
|
|
pObject->GetShadowPosition( &origin, &angles );
|
|
// align local normal to target normal
|
|
VMatrix tmp = SetupMatrixOrgAngles( origin, angles );
|
|
Vector worldNormal = tmp.VMul3x3( m_localAlignNormal );
|
|
axis = CrossProduct( worldNormal, m_targetAlignNormal );
|
|
float trig = VectorNormalize(axis);
|
|
float alignRotation = RAD2DEG(asin(trig));
|
|
axis *= alignRotation;
|
|
if ( alignRotation < 10 )
|
|
{
|
|
float dot = DotProduct( worldNormal, m_targetAlignNormal );
|
|
// probably 180 degrees off
|
|
if ( dot < 0 )
|
|
{
|
|
if ( worldNormal.x < 0.5 )
|
|
{
|
|
axis.Init(10,0,0);
|
|
}
|
|
else
|
|
{
|
|
axis.Init(0,0,10);
|
|
}
|
|
alignRotation = 10;
|
|
}
|
|
}
|
|
|
|
// Solve for the rotation around the target normal (at the local align pos) that will
|
|
// move the grabbed spot to the destination.
|
|
Vector worldRotCenter = tmp.VMul4x3( m_localAlignPosition );
|
|
Vector rotSrc = world - worldRotCenter;
|
|
Vector rotDest = m_targetPosition - worldRotCenter;
|
|
|
|
// Get a basis in the plane perpendicular to m_targetAlignNormal
|
|
Vector srcN = rotSrc;
|
|
VectorNormalize( srcN );
|
|
Vector tangent = CrossProduct( srcN, m_targetAlignNormal );
|
|
float len = VectorNormalize( tangent );
|
|
|
|
// needs at least ~5 degrees, or forget rotation (0.08 ~= sin(5))
|
|
if ( len > 0.08 )
|
|
{
|
|
Vector binormal = CrossProduct( m_targetAlignNormal, tangent );
|
|
|
|
// Now project the src & dest positions into that plane
|
|
Vector planeSrc( DotProduct( rotSrc, tangent ), DotProduct( rotSrc, binormal ), 0 );
|
|
Vector planeDest( DotProduct( rotDest, tangent ), DotProduct( rotDest, binormal ), 0 );
|
|
|
|
float rotRadius = VectorNormalize( planeSrc );
|
|
float destRadius = VectorNormalize( planeDest );
|
|
if ( rotRadius > 0.1 )
|
|
{
|
|
if ( destRadius < rotRadius )
|
|
{
|
|
destRadius = rotRadius;
|
|
}
|
|
//float ratio = rotRadius / destRadius;
|
|
float angleSrc = atan2( planeSrc.y, planeSrc.x );
|
|
float angleDest = atan2( planeDest.y, planeDest.x );
|
|
float angleDiff = angleDest - angleSrc;
|
|
angleDiff = RAD2DEG(angleDiff);
|
|
axis += m_targetAlignNormal * angleDiff;
|
|
//world = m_targetPosition;// + rotDest * (1-ratio);
|
|
// NDebugOverlay::Line( worldRotCenter, worldRotCenter-m_targetAlignNormal*50, 255, 0, 0, false, 0.1 );
|
|
// NDebugOverlay::Line( worldRotCenter, worldRotCenter+tangent*50, 0, 255, 0, false, 0.1 );
|
|
// NDebugOverlay::Line( worldRotCenter, worldRotCenter+binormal*50, 0, 0, 255, false, 0.1 );
|
|
}
|
|
}
|
|
|
|
torque = WorldToLocalRotation( tmp, axis, 1 );
|
|
torque *= fracRemainingSimTime * invDeltaTime;
|
|
torque -= angVel * 1.0; // damping
|
|
for ( int i = 0; i < 3; i++ )
|
|
{
|
|
if ( torque[i] > 0 )
|
|
{
|
|
if ( torque[i] > m_maxAngularAcceleration[i] )
|
|
torque[i] = m_maxAngularAcceleration[i];
|
|
}
|
|
else
|
|
{
|
|
if ( torque[i] < -m_maxAngularAcceleration[i] )
|
|
torque[i] = -m_maxAngularAcceleration[i];
|
|
}
|
|
}
|
|
torque *= invDeltaTime;
|
|
angular += torque;
|
|
// Calculate an acceleration that pulls the object toward the constraint
|
|
// When you're out of alignment, don't pull very hard
|
|
float factor = fabsf(alignRotation);
|
|
if ( factor < 5 )
|
|
{
|
|
factor = clamp( factor, 0, 5 ) * (1/5);
|
|
alignDir = m_targetAlignPosition - worldRotCenter;
|
|
// Limit movement to the part along m_targetAlignNormal if worldRotCenter is on the backside of
|
|
// of the target plane (one inch epsilon)!
|
|
float planeForward = DotProduct( alignDir, m_targetAlignNormal );
|
|
if ( planeForward > 1 )
|
|
{
|
|
alignDir = m_targetAlignNormal * planeForward;
|
|
}
|
|
Vector accel = alignDir * invDeltaTime * fracRemainingSimTime * (1-factor) * 0.20 * invDeltaTime;
|
|
float mag = accel.Length();
|
|
if ( mag > m_maxAcceleration )
|
|
{
|
|
accel *= (m_maxAcceleration/mag);
|
|
}
|
|
linear += accel;
|
|
}
|
|
linear -= vel*damping*invDeltaTime;
|
|
// UNDONE: Factor in the change in worldRotCenter due to applied torque!
|
|
}
|
|
else
|
|
{
|
|
// clamp future velocity to max speed
|
|
Vector nextVel = delta + vel;
|
|
float nextSpeed = nextVel.Length();
|
|
if ( nextSpeed > m_maxVel )
|
|
{
|
|
nextVel *= (m_maxVel / nextSpeed);
|
|
delta = nextVel - vel;
|
|
}
|
|
|
|
delta *= invDeltaTime;
|
|
|
|
float linearAccel = delta.Length();
|
|
if ( linearAccel > m_maxAcceleration )
|
|
{
|
|
delta *= m_maxAcceleration / linearAccel;
|
|
}
|
|
|
|
Vector accel;
|
|
AngularImpulse angAccel;
|
|
pObject->CalculateForceOffset( delta, world, &accel, &angAccel );
|
|
|
|
linear += accel;
|
|
angular += angAccel;
|
|
}
|
|
|
|
return SIM_GLOBAL_ACCELERATION;
|
|
}
|
|
|
|
|
|
struct pelletlist_t
|
|
{
|
|
DECLARE_SIMPLE_DATADESC();
|
|
|
|
Vector localNormal; // normal in parent space
|
|
CHandle<CGravityPellet> pellet;
|
|
EHANDLE parent;
|
|
};
|
|
|
|
class CWeaponGravityGun : public CBaseCombatWeapon
|
|
{
|
|
DECLARE_DATADESC();
|
|
|
|
public:
|
|
DECLARE_CLASS( CWeaponGravityGun, CBaseCombatWeapon );
|
|
|
|
CWeaponGravityGun();
|
|
void Spawn( void );
|
|
void OnRestore( void );
|
|
void Precache( void );
|
|
|
|
void PrimaryAttack( void );
|
|
void SecondaryAttack( void );
|
|
void WeaponIdle( void );
|
|
void ItemPostFrame( void );
|
|
virtual bool Holster( CBaseCombatWeapon *pSwitchingTo )
|
|
{
|
|
EffectDestroy();
|
|
return BaseClass::Holster();
|
|
}
|
|
|
|
bool Reload( void );
|
|
void Equip( CBaseCombatCharacter *pOwner )
|
|
{
|
|
// add constraint ammo
|
|
pOwner->SetAmmoCount( MAX_PELLETS, m_iSecondaryAmmoType );
|
|
BaseClass::Equip( pOwner );
|
|
}
|
|
void Drop(const Vector &vecVelocity)
|
|
{
|
|
CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
|
|
pOwner->SetAmmoCount( 0, m_iSecondaryAmmoType );
|
|
// destroy all constraints
|
|
BaseClass::Drop(vecVelocity);
|
|
}
|
|
|
|
bool HasAnyAmmo( void );
|
|
|
|
void AttachObject( CBaseEntity *pEdict, const Vector& start, const Vector &end, float distance );
|
|
void DetachObject( void );
|
|
|
|
void EffectCreate( void );
|
|
void EffectUpdate( void );
|
|
void EffectDestroy( void );
|
|
|
|
void SoundCreate( void );
|
|
void SoundDestroy( void );
|
|
void SoundStop( void );
|
|
void SoundStart( void );
|
|
void SoundUpdate( void );
|
|
void AddPellet( CGravityPellet *pPellet, CBaseEntity *pParent, const Vector &surfaceNormal );
|
|
void DeleteActivePellets();
|
|
void SortPelletsForObject( CBaseEntity *pObject );
|
|
void SetObjectPelletsColor( int r, int g, int b );
|
|
void CreatePelletAttraction( float radius, CBaseEntity *pObject );
|
|
IPhysicsObject *GetPelletPhysObject( int pelletIndex );
|
|
void GetPelletWorldCoords( int pelletIndex, Vector *worldPos, Vector *worldNormal )
|
|
{
|
|
if ( worldPos )
|
|
{
|
|
*worldPos = m_activePellets[pelletIndex].pellet->GetAbsOrigin();
|
|
}
|
|
if ( worldNormal )
|
|
{
|
|
if ( m_activePellets[pelletIndex].parent )
|
|
{
|
|
EntityMatrix tmp;
|
|
tmp.InitFromEntity( m_activePellets[pelletIndex].parent );
|
|
*worldNormal = tmp.LocalToWorldRotation( m_activePellets[pelletIndex].localNormal );
|
|
}
|
|
else
|
|
{
|
|
*worldNormal = m_activePellets[pelletIndex].localNormal;
|
|
}
|
|
}
|
|
}
|
|
|
|
int ObjectCaps( void )
|
|
{
|
|
int caps = BaseClass::ObjectCaps();
|
|
if ( m_active )
|
|
{
|
|
caps |= FCAP_DIRECTIONAL_USE;
|
|
}
|
|
return caps;
|
|
}
|
|
|
|
CBaseEntity *GetBeamEntity();
|
|
|
|
DECLARE_SERVERCLASS();
|
|
|
|
private:
|
|
CNetworkVar( int, m_active );
|
|
bool m_useDown;
|
|
EHANDLE m_hObject;
|
|
float m_distance;
|
|
float m_movementLength;
|
|
float m_lastYaw;
|
|
int m_soundState;
|
|
CNetworkVar( int, m_viewModelIndex );
|
|
Vector m_originalObjectPosition;
|
|
|
|
CGravControllerPoint m_gravCallback;
|
|
pelletlist_t m_activePellets[MAX_PELLETS];
|
|
int m_pelletCount;
|
|
int m_objectPelletCount;
|
|
|
|
int m_pelletHeld;
|
|
int m_pelletAttract;
|
|
float m_glueTime;
|
|
CNetworkVar( bool, m_glueTouching );
|
|
};
|
|
|
|
IMPLEMENT_SERVERCLASS_ST( CWeaponGravityGun, DT_WeaponGravityGun )
|
|
SendPropVector( SENDINFO_NAME(m_gravCallback.m_targetPosition, m_targetPosition), -1, SPROP_COORD ),
|
|
SendPropVector( SENDINFO_NAME(m_gravCallback.m_worldPosition, m_worldPosition), -1, SPROP_COORD ),
|
|
SendPropInt( SENDINFO(m_active), 1, SPROP_UNSIGNED ),
|
|
SendPropInt( SENDINFO(m_glueTouching), 1, SPROP_UNSIGNED ),
|
|
SendPropModelIndex( SENDINFO(m_viewModelIndex) ),
|
|
END_SEND_TABLE()
|
|
|
|
LINK_ENTITY_TO_CLASS( weapon_physgun, CWeaponGravityGun );
|
|
PRECACHE_WEAPON_REGISTER(weapon_physgun);
|
|
|
|
//---------------------------------------------------------
|
|
// Save/Restore
|
|
//---------------------------------------------------------
|
|
BEGIN_SIMPLE_DATADESC( pelletlist_t )
|
|
|
|
DEFINE_FIELD( localNormal, FIELD_VECTOR ),
|
|
DEFINE_FIELD( pellet, FIELD_EHANDLE ),
|
|
DEFINE_FIELD( parent, FIELD_EHANDLE ),
|
|
|
|
END_DATADESC()
|
|
|
|
BEGIN_DATADESC( CWeaponGravityGun )
|
|
|
|
DEFINE_FIELD( m_active, FIELD_INTEGER ),
|
|
DEFINE_FIELD( m_useDown, FIELD_BOOLEAN ),
|
|
DEFINE_FIELD( m_hObject, FIELD_EHANDLE ),
|
|
DEFINE_FIELD( m_distance, FIELD_FLOAT ),
|
|
DEFINE_FIELD( m_movementLength, FIELD_FLOAT ),
|
|
DEFINE_FIELD( m_lastYaw, FIELD_FLOAT ),
|
|
DEFINE_FIELD( m_soundState, FIELD_INTEGER ),
|
|
DEFINE_FIELD( m_viewModelIndex, FIELD_INTEGER ),
|
|
DEFINE_FIELD( m_originalObjectPosition, FIELD_POSITION_VECTOR ),
|
|
DEFINE_EMBEDDED( m_gravCallback ),
|
|
// Physptrs can't be saved in embedded classes..
|
|
DEFINE_PHYSPTR( m_gravCallback.m_controller ),
|
|
DEFINE_EMBEDDED_AUTO_ARRAY( m_activePellets ),
|
|
DEFINE_FIELD( m_pelletCount, FIELD_INTEGER ),
|
|
DEFINE_FIELD( m_objectPelletCount, FIELD_INTEGER ),
|
|
DEFINE_FIELD( m_pelletHeld, FIELD_INTEGER ),
|
|
DEFINE_FIELD( m_pelletAttract, FIELD_INTEGER ),
|
|
DEFINE_FIELD( m_glueTime, FIELD_TIME ),
|
|
DEFINE_FIELD( m_glueTouching, FIELD_BOOLEAN ),
|
|
|
|
END_DATADESC()
|
|
|
|
|
|
enum physgun_soundstate { SS_SCANNING, SS_LOCKEDON };
|
|
enum physgun_soundIndex { SI_LOCKEDON = 0, SI_SCANNING = 1, SI_LIGHTOBJECT = 2, SI_HEAVYOBJECT = 3, SI_ON, SI_OFF };
|
|
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
|
|
CWeaponGravityGun::CWeaponGravityGun()
|
|
{
|
|
m_active = false;
|
|
m_bFiresUnderwater = true;
|
|
m_pelletAttract = -1;
|
|
m_pelletHeld = -1;
|
|
}
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
void CWeaponGravityGun::Spawn( )
|
|
{
|
|
BaseClass::Spawn();
|
|
// SetModel( GetWorldModel() );
|
|
|
|
FallInit();
|
|
}
|
|
|
|
void CWeaponGravityGun::OnRestore( void )
|
|
{
|
|
BaseClass::OnRestore();
|
|
|
|
if ( m_gravCallback.m_controller )
|
|
{
|
|
m_gravCallback.m_controller->SetEventHandler( &m_gravCallback );
|
|
}
|
|
}
|
|
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
void CWeaponGravityGun::Precache( void )
|
|
{
|
|
BaseClass::Precache();
|
|
|
|
g_physgunBeam = PrecacheModel(PHYSGUN_BEAM_SPRITE);
|
|
|
|
PrecacheScriptSound( "Weapon_Physgun.Scanning" );
|
|
PrecacheScriptSound( "Weapon_Physgun.LockedOn" );
|
|
PrecacheScriptSound( "Weapon_Physgun.Scanning" );
|
|
PrecacheScriptSound( "Weapon_Physgun.LightObject" );
|
|
PrecacheScriptSound( "Weapon_Physgun.HeavyObject" );
|
|
}
|
|
|
|
void CWeaponGravityGun::EffectCreate( void )
|
|
{
|
|
EffectUpdate();
|
|
m_active = true;
|
|
}
|
|
|
|
|
|
void CWeaponGravityGun::EffectUpdate( void )
|
|
{
|
|
Vector start, angles, forward, right;
|
|
trace_t tr;
|
|
|
|
CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
|
|
if ( !pOwner )
|
|
return;
|
|
|
|
m_viewModelIndex = pOwner->entindex();
|
|
// Make sure I've got a view model
|
|
CBaseViewModel *vm = pOwner->GetViewModel();
|
|
if ( vm )
|
|
{
|
|
m_viewModelIndex = vm->entindex();
|
|
}
|
|
|
|
pOwner->EyeVectors( &forward, &right, NULL );
|
|
|
|
start = pOwner->Weapon_ShootPosition();
|
|
Vector end = start + forward * 4096;
|
|
|
|
UTIL_TraceLine( start, end, MASK_SHOT, pOwner, COLLISION_GROUP_NONE, &tr );
|
|
end = tr.endpos;
|
|
float distance = tr.fraction * 4096;
|
|
if ( tr.fraction != 1 )
|
|
{
|
|
// too close to the player, drop the object
|
|
if ( distance < 36 )
|
|
{
|
|
DetachObject();
|
|
return;
|
|
}
|
|
}
|
|
|
|
if ( m_hObject == NULL && tr.DidHitNonWorldEntity() )
|
|
{
|
|
CBaseEntity *pEntity = tr.m_pEnt;
|
|
// inform the object what was hit
|
|
ClearMultiDamage();
|
|
pEntity->DispatchTraceAttack( CTakeDamageInfo( pOwner, pOwner, 0, DMG_PHYSGUN ), forward, &tr );
|
|
ApplyMultiDamage();
|
|
AttachObject( pEntity, start, tr.endpos, distance );
|
|
m_lastYaw = pOwner->EyeAngles().y;
|
|
}
|
|
|
|
// Add the incremental player yaw to the target transform
|
|
matrix3x4_t curMatrix, incMatrix, nextMatrix;
|
|
AngleMatrix( m_gravCallback.m_targetRotation, curMatrix );
|
|
AngleMatrix( QAngle(0,pOwner->EyeAngles().y - m_lastYaw,0), incMatrix );
|
|
ConcatTransforms( incMatrix, curMatrix, nextMatrix );
|
|
MatrixAngles( nextMatrix, m_gravCallback.m_targetRotation );
|
|
m_lastYaw = pOwner->EyeAngles().y;
|
|
|
|
CBaseEntity *pObject = m_hObject;
|
|
if ( pObject )
|
|
{
|
|
if ( m_useDown )
|
|
{
|
|
if ( pOwner->m_afButtonPressed & IN_USE )
|
|
{
|
|
m_useDown = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( pOwner->m_afButtonPressed & IN_USE )
|
|
{
|
|
m_useDown = true;
|
|
}
|
|
}
|
|
|
|
if ( m_useDown )
|
|
{
|
|
pOwner->SetPhysicsFlag( PFLAG_DIROVERRIDE, true );
|
|
if ( pOwner->m_nButtons & IN_FORWARD )
|
|
{
|
|
m_distance = UTIL_Approach( 1024, m_distance, gpGlobals->frametime * 100 );
|
|
}
|
|
if ( pOwner->m_nButtons & IN_BACK )
|
|
{
|
|
m_distance = UTIL_Approach( 40, m_distance, gpGlobals->frametime * 100 );
|
|
}
|
|
}
|
|
|
|
if ( pOwner->m_nButtons & IN_WEAPON1 )
|
|
{
|
|
m_distance = UTIL_Approach( 1024, m_distance, m_distance * 0.1 );
|
|
}
|
|
if ( pOwner->m_nButtons & IN_WEAPON2 )
|
|
{
|
|
m_distance = UTIL_Approach( 40, m_distance, m_distance * 0.1 );
|
|
}
|
|
|
|
// Send the object a physics damage message (0 damage). Some objects interpret this
|
|
// as something else being in control of their physics temporarily.
|
|
pObject->TakeDamage( CTakeDamageInfo( this, pOwner, 0, DMG_PHYSGUN ) );
|
|
|
|
Vector newPosition = start + forward * m_distance;
|
|
// 24 is a little larger than 16 * sqrt(2) (extent of player bbox)
|
|
// HACKHACK: We do this so we can "ignore" the player and the object we're manipulating
|
|
// If we had a filter for tracelines, we could simply filter both ents and start from "start"
|
|
Vector awayfromPlayer = start + forward * 24;
|
|
|
|
UTIL_TraceLine( start, awayfromPlayer, MASK_SOLID, pOwner, COLLISION_GROUP_NONE, &tr );
|
|
if ( tr.fraction == 1 )
|
|
{
|
|
UTIL_TraceLine( awayfromPlayer, newPosition, MASK_SOLID, pObject, COLLISION_GROUP_NONE, &tr );
|
|
Vector dir = tr.endpos - newPosition;
|
|
float distance = VectorNormalize(dir);
|
|
float maxDist = m_gravCallback.m_maxVel * gpGlobals->frametime;
|
|
if ( distance > maxDist )
|
|
{
|
|
newPosition += dir * maxDist;
|
|
}
|
|
else
|
|
{
|
|
newPosition = tr.endpos;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
newPosition = tr.endpos;
|
|
}
|
|
|
|
CreatePelletAttraction( phys_gunglueradius.GetFloat(), pObject );
|
|
|
|
// If I'm looking more than 20 degrees away from the glue point, then give up
|
|
// This lets the player "gesture" for the glue to let go.
|
|
Vector pelletDir = m_gravCallback.m_worldPosition - start;
|
|
VectorNormalize(pelletDir);
|
|
if ( DotProduct( pelletDir, forward ) < 0.939 ) // 0.939 ~= cos(20deg)
|
|
{
|
|
// lose attach for 2 seconds if you're too far away
|
|
m_glueTime = gpGlobals->curtime + 1;
|
|
}
|
|
|
|
if ( m_pelletHeld >= 0 && gpGlobals->curtime > m_glueTime )
|
|
{
|
|
CGravityPellet *pPelletAttract = m_activePellets[m_pelletAttract].pellet;
|
|
|
|
g_pEffects->Sparks( pPelletAttract->GetAbsOrigin() );
|
|
}
|
|
|
|
m_gravCallback.SetTargetPosition( newPosition );
|
|
Vector dir = (newPosition - pObject->GetLocalOrigin());
|
|
m_movementLength = dir.Length();
|
|
}
|
|
else
|
|
{
|
|
m_gravCallback.SetTargetPosition( end );
|
|
}
|
|
if ( m_pelletHeld >= 0 && gpGlobals->curtime > m_glueTime )
|
|
{
|
|
Vector worldNormal, worldPos;
|
|
GetPelletWorldCoords( m_pelletAttract, &worldPos, &worldNormal );
|
|
|
|
m_gravCallback.SetAutoAlign( m_activePellets[m_pelletHeld].localNormal, m_activePellets[m_pelletHeld].pellet->GetLocalOrigin(), worldNormal, worldPos );
|
|
}
|
|
else
|
|
{
|
|
m_gravCallback.ClearAutoAlign();
|
|
}
|
|
}
|
|
|
|
void CWeaponGravityGun::SoundCreate( void )
|
|
{
|
|
m_soundState = SS_SCANNING;
|
|
SoundStart();
|
|
}
|
|
|
|
|
|
void CWeaponGravityGun::SoundDestroy( void )
|
|
{
|
|
SoundStop();
|
|
}
|
|
|
|
|
|
void CWeaponGravityGun::SoundStop( void )
|
|
{
|
|
switch( m_soundState )
|
|
{
|
|
case SS_SCANNING:
|
|
GetOwner()->StopSound( "Weapon_Physgun.Scanning" );
|
|
break;
|
|
case SS_LOCKEDON:
|
|
GetOwner()->StopSound( "Weapon_Physgun.Scanning" );
|
|
GetOwner()->StopSound( "Weapon_Physgun.LockedOn" );
|
|
GetOwner()->StopSound( "Weapon_Physgun.LightObject" );
|
|
GetOwner()->StopSound( "Weapon_Physgun.HeavyObject" );
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: returns the linear fraction of value between low & high (0.0 - 1.0) * scale
|
|
// e.g. UTIL_LineFraction( 1.5, 1, 2, 1 ); will return 0.5 since 1.5 is
|
|
// halfway between 1 and 2
|
|
// Input : value - a value between low & high (clamped)
|
|
// low - the value that maps to zero
|
|
// high - the value that maps to "scale"
|
|
// scale - the output scale
|
|
// Output : parametric fraction between low & high
|
|
//-----------------------------------------------------------------------------
|
|
static float UTIL_LineFraction( float value, float low, float high, float scale )
|
|
{
|
|
if ( value < low )
|
|
value = low;
|
|
if ( value > high )
|
|
value = high;
|
|
|
|
float delta = high - low;
|
|
if ( delta == 0 )
|
|
return 0;
|
|
|
|
return scale * (value-low) / delta;
|
|
}
|
|
|
|
void CWeaponGravityGun::SoundStart( void )
|
|
{
|
|
CPASAttenuationFilter filter( GetOwner() );
|
|
filter.MakeReliable();
|
|
|
|
switch( m_soundState )
|
|
{
|
|
case SS_SCANNING:
|
|
{
|
|
EmitSound( filter, GetOwner()->entindex(), "Weapon_Physgun.Scanning" );
|
|
}
|
|
break;
|
|
case SS_LOCKEDON:
|
|
{
|
|
// BUGBUG - If you start a sound with a pitch of 100, the pitch shift doesn't work!
|
|
|
|
EmitSound( filter, GetOwner()->entindex(), "Weapon_Physgun.LockedOn" );
|
|
EmitSound( filter, GetOwner()->entindex(), "Weapon_Physgun.Scanning" );
|
|
EmitSound( filter, GetOwner()->entindex(), "Weapon_Physgun.LightObject" );
|
|
EmitSound( filter, GetOwner()->entindex(), "Weapon_Physgun.HeavyObject" );
|
|
}
|
|
break;
|
|
}
|
|
// volume, att, flags, pitch
|
|
}
|
|
|
|
void CWeaponGravityGun::SoundUpdate( void )
|
|
{
|
|
int newState;
|
|
|
|
if ( m_hObject )
|
|
newState = SS_LOCKEDON;
|
|
else
|
|
newState = SS_SCANNING;
|
|
|
|
if ( newState != m_soundState )
|
|
{
|
|
SoundStop();
|
|
m_soundState = newState;
|
|
SoundStart();
|
|
}
|
|
|
|
switch( m_soundState )
|
|
{
|
|
case SS_SCANNING:
|
|
break;
|
|
case SS_LOCKEDON:
|
|
{
|
|
CPASAttenuationFilter filter( GetOwner() );
|
|
filter.MakeReliable();
|
|
|
|
float height = m_hObject->GetAbsOrigin().z - m_originalObjectPosition.z;
|
|
|
|
// go from pitch 90 to 150 over a height of 500
|
|
int pitch = 90 + (int)UTIL_LineFraction( height, 0, 500, 60 );
|
|
|
|
CSoundParameters params;
|
|
if ( GetParametersForSound( "Weapon_Physgun.LockedOn", params, NULL ) )
|
|
{
|
|
EmitSound_t ep( params );
|
|
ep.m_nFlags = SND_CHANGE_VOL | SND_CHANGE_PITCH;
|
|
ep.m_nPitch = pitch;
|
|
|
|
EmitSound( filter, GetOwner()->entindex(), ep );
|
|
}
|
|
|
|
// attenutate the movement sounds over 200 units of movement
|
|
float distance = UTIL_LineFraction( m_movementLength, 0, 200, 1.0 );
|
|
|
|
// blend the "mass" sounds between 50 and 500 kg
|
|
IPhysicsObject *pPhys = m_hObject->VPhysicsGetObject();
|
|
|
|
float fade = UTIL_LineFraction( pPhys->GetMass(), 50, 500, 1.0 );
|
|
|
|
if ( GetParametersForSound( "Weapon_Physgun.LightObject", params, NULL ) )
|
|
{
|
|
EmitSound_t ep( params );
|
|
ep.m_nFlags = SND_CHANGE_VOL;
|
|
ep.m_flVolume = fade * distance;
|
|
|
|
EmitSound( filter, GetOwner()->entindex(), ep );
|
|
}
|
|
|
|
if ( GetParametersForSound( "Weapon_Physgun.HeavyObject", params, NULL ) )
|
|
{
|
|
EmitSound_t ep( params );
|
|
ep.m_nFlags = SND_CHANGE_VOL;
|
|
ep.m_flVolume = (1.0 - fade) * distance;
|
|
|
|
EmitSound( filter, GetOwner()->entindex(), ep );
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
void CWeaponGravityGun::AddPellet( CGravityPellet *pPellet, CBaseEntity *pAttach, const Vector &surfaceNormal )
|
|
{
|
|
Assert(m_pelletCount<MAX_PELLETS);
|
|
|
|
m_activePellets[m_pelletCount].localNormal = surfaceNormal;
|
|
if ( pAttach )
|
|
{
|
|
EntityMatrix tmp;
|
|
tmp.InitFromEntity( pAttach );
|
|
m_activePellets[m_pelletCount].localNormal = tmp.WorldToLocalRotation( surfaceNormal );
|
|
}
|
|
m_activePellets[m_pelletCount].pellet = pPellet;
|
|
m_activePellets[m_pelletCount].parent = pAttach;
|
|
m_pelletCount++;
|
|
}
|
|
|
|
void CWeaponGravityGun::SortPelletsForObject( CBaseEntity *pObject )
|
|
{
|
|
m_objectPelletCount = 0;
|
|
for ( int i = 0; i < m_pelletCount; i++ )
|
|
{
|
|
// move pellets attached to the active object to the front of the list
|
|
if ( m_activePellets[i].parent == pObject && !m_activePellets[i].pellet->IsInert() )
|
|
{
|
|
if ( i != 0 )
|
|
{
|
|
pelletlist_t tmp = m_activePellets[m_objectPelletCount];
|
|
m_activePellets[m_objectPelletCount] = m_activePellets[i];
|
|
m_activePellets[i] = tmp;
|
|
}
|
|
m_objectPelletCount++;
|
|
}
|
|
}
|
|
|
|
SetObjectPelletsColor( 192, 255, 192 );
|
|
}
|
|
|
|
void CWeaponGravityGun::SetObjectPelletsColor( int r, int g, int b )
|
|
{
|
|
color32 color;
|
|
color.r = r;
|
|
color.g = g;
|
|
color.b = b;
|
|
color.a = 255;
|
|
|
|
for ( int i = 0; i < m_objectPelletCount; i++ )
|
|
{
|
|
CGravityPellet *pPellet = m_activePellets[i].pellet;
|
|
if ( !pPellet || pPellet->IsInert() )
|
|
continue;
|
|
|
|
pPellet->m_clrRender = color;
|
|
}
|
|
}
|
|
|
|
CBaseEntity *CWeaponGravityGun::GetBeamEntity()
|
|
{
|
|
CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
|
|
if ( !pOwner )
|
|
return NULL;
|
|
|
|
// Make sure I've got a view model
|
|
CBaseViewModel *vm = pOwner->GetViewModel();
|
|
if ( vm )
|
|
return vm;
|
|
|
|
return pOwner;
|
|
}
|
|
|
|
void CWeaponGravityGun::DeleteActivePellets()
|
|
{
|
|
CBaseEntity *pEnt = GetBeamEntity();
|
|
|
|
for ( int i = 0; i < m_pelletCount; i++ )
|
|
{
|
|
CGravityPellet *pPellet = m_activePellets[i].pellet;
|
|
if ( !pPellet )
|
|
continue;
|
|
|
|
Vector forward;
|
|
AngleVectors( pPellet->GetAbsAngles(), &forward );
|
|
g_pEffects->Dust( pPellet->GetAbsOrigin(), forward, 32, 30 );
|
|
|
|
// UNDONE: Probably should just do this client side
|
|
CBeam *pBeam = CBeam::BeamCreate( PHYSGUN_BEAM_SPRITE, 1.5 );
|
|
pBeam->PointEntInit( pPellet->GetAbsOrigin(), pEnt );
|
|
pBeam->SetEndAttachment( 1 );
|
|
pBeam->SetBrightness( 255 );
|
|
pBeam->SetColor( 255, 0, 0 );
|
|
pBeam->RelinkBeam();
|
|
pBeam->LiveForTime( 0.1 );
|
|
|
|
UTIL_Remove( pPellet );
|
|
}
|
|
m_pelletCount = 0;
|
|
}
|
|
|
|
void CWeaponGravityGun::CreatePelletAttraction( float radius, CBaseEntity *pObject )
|
|
{
|
|
int nearPellet = -1;
|
|
int objectPellet = -1;
|
|
float best = radius*radius;
|
|
// already have a pellet, check for in range
|
|
if ( m_pelletAttract >= 0 )
|
|
{
|
|
Vector attract, held;
|
|
GetPelletWorldCoords( m_pelletAttract, &attract, NULL );
|
|
GetPelletWorldCoords( m_pelletHeld, &held, NULL );
|
|
float dist = (attract - held).Length();
|
|
if ( dist < radius * 2 )
|
|
{
|
|
nearPellet = m_pelletAttract;
|
|
objectPellet = m_pelletHeld;
|
|
best = dist * dist;
|
|
}
|
|
}
|
|
|
|
if ( nearPellet < 0 )
|
|
{
|
|
|
|
for ( int i = 0; i < m_objectPelletCount; i++ )
|
|
{
|
|
CGravityPellet *pPellet = m_activePellets[i].pellet;
|
|
if ( !pPellet )
|
|
continue;
|
|
for ( int j = m_objectPelletCount; j < m_pelletCount; j++ )
|
|
{
|
|
CGravityPellet *pTest = m_activePellets[j].pellet;
|
|
if ( !pTest )
|
|
continue;
|
|
|
|
if ( pTest->IsInert() )
|
|
continue;
|
|
float distSqr = (pTest->GetAbsOrigin() - pPellet->GetAbsOrigin()).LengthSqr();
|
|
if ( distSqr < best )
|
|
{
|
|
Vector worldPos, worldNormal;
|
|
GetPelletWorldCoords( j, &worldPos, &worldNormal );
|
|
// don't attract backside pellets (unless current pellet - prevent oscillation)
|
|
float dist = DotProduct( worldPos, worldNormal );
|
|
if ( m_pelletAttract == j || DotProduct( pPellet->GetAbsOrigin(), worldNormal ) - dist >= 0 )
|
|
{
|
|
best = distSqr;
|
|
nearPellet = j;
|
|
objectPellet = i;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
m_glueTouching = false;
|
|
if ( nearPellet < 0 || objectPellet < 0 )
|
|
{
|
|
m_pelletAttract = -1;
|
|
m_pelletHeld = -1;
|
|
return;
|
|
}
|
|
|
|
if ( nearPellet != m_pelletAttract || objectPellet != m_pelletHeld )
|
|
{
|
|
m_glueTime = gpGlobals->curtime;
|
|
|
|
m_pelletAttract = nearPellet;
|
|
m_pelletHeld = objectPellet;
|
|
}
|
|
|
|
// check for bonding
|
|
if ( best < 3*3 )
|
|
{
|
|
// This makes the pull towards the pellet stop getting stronger since some part of
|
|
// the object is touching
|
|
m_glueTouching = true;
|
|
}
|
|
}
|
|
|
|
|
|
IPhysicsObject *CWeaponGravityGun::GetPelletPhysObject( int pelletIndex )
|
|
{
|
|
if ( pelletIndex < 0 )
|
|
return NULL;
|
|
|
|
CBaseEntity *pEntity = m_activePellets[pelletIndex].parent;
|
|
if ( pEntity )
|
|
return pEntity->VPhysicsGetObject();
|
|
|
|
return g_PhysWorldObject;
|
|
}
|
|
|
|
void CWeaponGravityGun::EffectDestroy( void )
|
|
{
|
|
m_active = false;
|
|
SoundStop();
|
|
|
|
DetachObject();
|
|
}
|
|
|
|
void CWeaponGravityGun::DetachObject( void )
|
|
{
|
|
m_pelletHeld = -1;
|
|
m_pelletAttract = -1;
|
|
m_glueTouching = false;
|
|
SetObjectPelletsColor( 255, 0, 0 );
|
|
m_objectPelletCount = 0;
|
|
|
|
if ( m_hObject )
|
|
{
|
|
CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
|
|
Pickup_OnPhysGunDrop( m_hObject, pOwner, DROPPED_BY_CANNON );
|
|
|
|
m_gravCallback.DetachEntity();
|
|
m_hObject = NULL;
|
|
}
|
|
}
|
|
|
|
void CWeaponGravityGun::AttachObject( CBaseEntity *pObject, const Vector& start, const Vector &end, float distance )
|
|
{
|
|
m_hObject = pObject;
|
|
m_useDown = false;
|
|
IPhysicsObject *pPhysics = pObject ? (pObject->VPhysicsGetObject()) : NULL;
|
|
if ( pPhysics && pObject->GetMoveType() == MOVETYPE_VPHYSICS )
|
|
{
|
|
m_distance = distance;
|
|
|
|
m_gravCallback.AttachEntity( pObject, pPhysics, end );
|
|
float mass = pPhysics->GetMass();
|
|
Msg( "Object mass: %.2f lbs (%.2f kg)\n", kg2lbs(mass), mass );
|
|
float vel = phys_gunvel.GetFloat();
|
|
if ( mass > phys_gunmass.GetFloat() )
|
|
{
|
|
vel = (vel*phys_gunmass.GetFloat())/mass;
|
|
}
|
|
m_gravCallback.SetMaxVelocity( vel );
|
|
// Msg( "Object mass: %.2f lbs (%.2f kg) %f %f %f\n", kg2lbs(mass), mass, pObject->GetAbsOrigin().x, pObject->GetAbsOrigin().y, pObject->GetAbsOrigin().z );
|
|
// Msg( "ANG: %f %f %f\n", pObject->GetAbsAngles().x, pObject->GetAbsAngles().y, pObject->GetAbsAngles().z );
|
|
|
|
m_originalObjectPosition = pObject->GetAbsOrigin();
|
|
|
|
m_pelletAttract = -1;
|
|
m_pelletHeld = -1;
|
|
|
|
pPhysics->Wake();
|
|
SortPelletsForObject( pObject );
|
|
|
|
CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
|
|
if( pOwner )
|
|
{
|
|
Pickup_OnPhysGunPickup( pObject, pOwner );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_hObject = NULL;
|
|
}
|
|
}
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
void CWeaponGravityGun::PrimaryAttack( void )
|
|
{
|
|
if ( !m_active )
|
|
{
|
|
SendWeaponAnim( ACT_VM_PRIMARYATTACK );
|
|
EffectCreate();
|
|
SoundCreate();
|
|
}
|
|
else
|
|
{
|
|
EffectUpdate();
|
|
SoundUpdate();
|
|
}
|
|
}
|
|
|
|
void CWeaponGravityGun::SecondaryAttack( void )
|
|
{
|
|
m_flNextSecondaryAttack = gpGlobals->curtime + 0.1;
|
|
if ( m_active )
|
|
{
|
|
EffectDestroy();
|
|
SoundDestroy();
|
|
return;
|
|
}
|
|
|
|
CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
|
|
Assert( pOwner );
|
|
|
|
if ( pOwner->GetAmmoCount(m_iSecondaryAmmoType) <= 0 )
|
|
return;
|
|
|
|
m_viewModelIndex = pOwner->entindex();
|
|
// Make sure I've got a view model
|
|
CBaseViewModel *vm = pOwner->GetViewModel();
|
|
if ( vm )
|
|
{
|
|
m_viewModelIndex = vm->entindex();
|
|
}
|
|
|
|
Vector forward;
|
|
pOwner->EyeVectors( &forward );
|
|
|
|
Vector start = pOwner->Weapon_ShootPosition();
|
|
Vector end = start + forward * 4096;
|
|
|
|
trace_t tr;
|
|
UTIL_TraceLine( start, end, MASK_SHOT, pOwner, COLLISION_GROUP_NONE, &tr );
|
|
if ( tr.fraction == 1.0 || (tr.surface.flags & SURF_SKY) )
|
|
return;
|
|
|
|
CBaseEntity *pHit = tr.m_pEnt;
|
|
|
|
if ( pHit->entindex() == 0 )
|
|
{
|
|
pHit = NULL;
|
|
}
|
|
else
|
|
{
|
|
// if the object has no physics object, or isn't a physprop or brush entity, then don't glue
|
|
if ( !pHit->VPhysicsGetObject() || pHit->GetMoveType() != MOVETYPE_VPHYSICS )
|
|
return;
|
|
}
|
|
|
|
QAngle angles;
|
|
WeaponSound( SINGLE );
|
|
pOwner->RemoveAmmo( 1, m_iSecondaryAmmoType );
|
|
|
|
VectorAngles( tr.plane.normal, angles );
|
|
Vector endPoint = tr.endpos + tr.plane.normal;
|
|
CGravityPellet *pPellet = (CGravityPellet *)CBaseEntity::Create( "gravity_pellet", endPoint, angles, this );
|
|
if ( pHit )
|
|
{
|
|
pPellet->SetParent( pHit );
|
|
}
|
|
AddPellet( pPellet, pHit, tr.plane.normal );
|
|
|
|
// UNDONE: Probably should just do this client side
|
|
CBaseEntity *pEnt = GetBeamEntity();
|
|
CBeam *pBeam = CBeam::BeamCreate( PHYSGUN_BEAM_SPRITE, 1.5 );
|
|
pBeam->PointEntInit( endPoint, pEnt );
|
|
pBeam->SetEndAttachment( 1 );
|
|
pBeam->SetBrightness( 255 );
|
|
pBeam->SetColor( 255, 0, 0 );
|
|
pBeam->RelinkBeam();
|
|
pBeam->LiveForTime( 0.1 );
|
|
|
|
}
|
|
|
|
void CWeaponGravityGun::WeaponIdle( void )
|
|
{
|
|
if ( HasWeaponIdleTimeElapsed() )
|
|
{
|
|
SendWeaponAnim( ACT_VM_IDLE );
|
|
if ( m_active )
|
|
{
|
|
CBaseEntity *pObject = m_hObject;
|
|
// pellet is touching object, so glue it
|
|
if ( pObject && m_glueTouching )
|
|
{
|
|
CGravityPellet *pPellet = m_activePellets[m_pelletAttract].pellet;
|
|
if ( pPellet->MakeConstraint( pObject ) )
|
|
{
|
|
WeaponSound( SPECIAL1 );
|
|
m_flNextPrimaryAttack = gpGlobals->curtime + 0.75;
|
|
m_activePellets[m_pelletHeld].pellet->MakeInert();
|
|
}
|
|
}
|
|
|
|
EffectDestroy();
|
|
SoundDestroy();
|
|
}
|
|
}
|
|
}
|
|
|
|
void CWeaponGravityGun::ItemPostFrame( void )
|
|
{
|
|
CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
|
|
if (!pOwner)
|
|
return;
|
|
|
|
if ( pOwner->m_afButtonPressed & IN_ATTACK2 )
|
|
{
|
|
SecondaryAttack();
|
|
}
|
|
else if ( pOwner->m_nButtons & IN_ATTACK )
|
|
{
|
|
PrimaryAttack();
|
|
}
|
|
else if ( pOwner->m_afButtonPressed & IN_RELOAD )
|
|
{
|
|
Reload();
|
|
}
|
|
// -----------------------
|
|
// No buttons down
|
|
// -----------------------
|
|
else
|
|
{
|
|
WeaponIdle( );
|
|
return;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Output : Returns true on success, false on failure.
|
|
//-----------------------------------------------------------------------------
|
|
bool CWeaponGravityGun::HasAnyAmmo( void )
|
|
{
|
|
//Always report that we have ammo
|
|
return true;
|
|
}
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
bool CWeaponGravityGun::Reload( void )
|
|
{
|
|
CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
|
|
|
|
if ( pOwner->GetAmmoCount(m_iSecondaryAmmoType) != MAX_PELLETS )
|
|
{
|
|
pOwner->SetAmmoCount( MAX_PELLETS, m_iSecondaryAmmoType );
|
|
DeleteActivePellets();
|
|
WeaponSound( RELOAD );
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
#define NUM_COLLISION_TESTS 2500
|
|
void CC_CollisionTest( const CCommand &args )
|
|
{
|
|
if ( !physenv )
|
|
return;
|
|
|
|
Msg( "Testing collision system\n" );
|
|
int i;
|
|
CBaseEntity *pSpot = gEntList.FindEntityByClassname( NULL, "info_player_start");
|
|
Vector start = pSpot->GetAbsOrigin();
|
|
static Vector *targets = NULL;
|
|
static bool first = true;
|
|
static float test[2] = {1,1};
|
|
if ( first )
|
|
{
|
|
targets = new Vector[NUM_COLLISION_TESTS];
|
|
float radius = 0;
|
|
float theta = 0;
|
|
float phi = 0;
|
|
for ( i = 0; i < NUM_COLLISION_TESTS; i++ )
|
|
{
|
|
radius += NUM_COLLISION_TESTS * 123.123;
|
|
radius = fabs(fmod(radius, 128));
|
|
theta += NUM_COLLISION_TESTS * 76.76;
|
|
theta = fabs(fmod(theta, DEG2RAD(360)));
|
|
phi += NUM_COLLISION_TESTS * 1997.99;
|
|
phi = fabs(fmod(phi, DEG2RAD(180)));
|
|
|
|
float st, ct, sp, cp;
|
|
SinCos( theta, &st, &ct );
|
|
SinCos( phi, &sp, &cp );
|
|
|
|
targets[i].x = radius * ct * sp;
|
|
targets[i].y = radius * st * sp;
|
|
targets[i].z = radius * cp;
|
|
|
|
// make the trace 1024 units long
|
|
Vector dir = targets[i] - start;
|
|
VectorNormalize(dir);
|
|
targets[i] = start + dir * 1024;
|
|
}
|
|
first = false;
|
|
}
|
|
|
|
//Vector results[NUM_COLLISION_TESTS];
|
|
|
|
int testType = 0;
|
|
if ( args.ArgC() >= 2 )
|
|
{
|
|
testType = atoi( args[1] );
|
|
}
|
|
float duration = 0;
|
|
Vector size[2];
|
|
size[0].Init(0,0,0);
|
|
size[1].Init(16,16,16);
|
|
unsigned int dots = 0;
|
|
|
|
for ( int j = 0; j < 2; j++ )
|
|
{
|
|
float startTime = engine->Time();
|
|
if ( testType == 1 )
|
|
{
|
|
const CPhysCollide *pCollide = g_PhysWorldObject->GetCollide();
|
|
trace_t tr;
|
|
|
|
for ( i = 0; i < NUM_COLLISION_TESTS; i++ )
|
|
{
|
|
physcollision->TraceBox( start, targets[i], -size[j], size[j], pCollide, vec3_origin, vec3_angle, &tr );
|
|
dots += physcollision->ReadStat(0);
|
|
//results[i] = tr.endpos;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
testType = 0;
|
|
CBaseEntity *pWorld = GetContainingEntity( INDEXENT(0) );
|
|
trace_t tr;
|
|
|
|
for ( i = 0; i < NUM_COLLISION_TESTS; i++ )
|
|
{
|
|
UTIL_TraceModel( start, targets[i], -size[j], size[j], pWorld, COLLISION_GROUP_NONE, &tr );
|
|
//results[i] = tr.endpos;
|
|
}
|
|
}
|
|
|
|
duration += engine->Time() - startTime;
|
|
}
|
|
test[testType] = duration;
|
|
Msg("%d collisions in %.2f ms (%u dots)\n", NUM_COLLISION_TESTS, duration*1000, dots );
|
|
Msg("Current speed ratio: %.2fX BSP:JGJK\n", test[1] / test[0] );
|
|
#if 0
|
|
int red = 255, green = 0, blue = 0;
|
|
for ( i = 0; i < NUM_COLLISION_TESTS; i++ )
|
|
{
|
|
NDebugOverlay::Line( start, results[i], red, green, blue, false, 2 );
|
|
}
|
|
#endif
|
|
}
|
|
static ConCommand collision_test("collision_test", CC_CollisionTest, "Tests collision system", FCVAR_CHEAT );
|