|
|
//========= 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 );
|