aboutsummaryrefslogtreecommitdiff
path: root/mp/src/game/server/physgun.cpp
diff options
context:
space:
mode:
authorJørgen P. Tjernø <[email protected]>2013-12-02 19:31:46 -0800
committerJørgen P. Tjernø <[email protected]>2013-12-02 19:46:31 -0800
commitf56bb35301836e56582a575a75864392a0177875 (patch)
treede61ddd39de3e7df52759711950b4c288592f0dc /mp/src/game/server/physgun.cpp
parentMark some more files as text. (diff)
downloadsource-sdk-2013-f56bb35301836e56582a575a75864392a0177875.tar.xz
source-sdk-2013-f56bb35301836e56582a575a75864392a0177875.zip
Fix line endings. WHAMMY.
Diffstat (limited to 'mp/src/game/server/physgun.cpp')
-rw-r--r--mp/src/game/server/physgun.cpp3046
1 files changed, 1523 insertions, 1523 deletions
diff --git a/mp/src/game/server/physgun.cpp b/mp/src/game/server/physgun.cpp
index 3ed9d1ed..21324bae 100644
--- a/mp/src/game/server/physgun.cpp
+++ b/mp/src/game/server/physgun.cpp
@@ -1,1523 +1,1523 @@
-//========= 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 );
+//========= 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 );