From f56bb35301836e56582a575a75864392a0177875 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20P=2E=20Tjern=C3=B8?= Date: Mon, 2 Dec 2013 19:31:46 -0800 Subject: Fix line endings. WHAMMY. --- mp/src/game/server/physgun.cpp | 3046 ++++++++++++++++++++-------------------- 1 file changed, 1523 insertions(+), 1523 deletions(-) (limited to 'mp/src/game/server/physgun.cpp') 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 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_pelletCountIsInert() ) - { - 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 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_pelletCountIsInert() ) + { + 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 ); -- cgit v1.2.3