diff options
| author | Jørgen P. Tjernø <[email protected]> | 2013-12-02 19:31:46 -0800 |
|---|---|---|
| committer | Jørgen P. Tjernø <[email protected]> | 2013-12-02 19:46:31 -0800 |
| commit | f56bb35301836e56582a575a75864392a0177875 (patch) | |
| tree | de61ddd39de3e7df52759711950b4c288592f0dc /mp/src/game/server/physgun.cpp | |
| parent | Mark some more files as text. (diff) | |
| download | source-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.cpp | 3046 |
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 ); |