sqwarmed/sdk_src/game/server/physgun.cpp

1524 lines
41 KiB
C++

//========= Copyright © 1996-2005, 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 = Plat_FloatTime();
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 += Plat_FloatTime() - 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 );