rendered paste body//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============////// Purpose: //// $NoKeywords: $//=============================================================================//#include "cbase.h"#include "beam_shared.h"#include "player.h"#include "gamerules.h"#include "basecombatweapon.h"#include "baseviewmodel.h"#include "vphysics/constraints.h"#include "physics.h"#include "in_buttons.h"#include "IEffects.h"#include "engine/IEngineSound.h"#include "ndebugoverlay.h"#include "physics_saverestore.h"#include "player_pickup.h"#include "soundemittersystem/isoundemittersystembase.h"// memdbgon must be the last include file in a .cpp file!!!#include "tier0/memdbgon.h"ConVar phys_gunmass("phys_gunmass", "1000000000000000000000000000");ConVar phys_gunvel("phys_gunvel", "10e10");ConVar phys_gunforce("phys_gunforce", "1000000" );ConVar phys_guntorque("phys_guntorque", "15e15" );ConVar phys_gunglueradius("phys_gunglueradius", "400" );ConVar _physguncurrenthealth("_physguncurrenthealth","100" );static int g_physgunBeam;#define PHYSGUN_BEAM_SPRITE "sprites/physbeam.vmt"#define MAX_PELLETS 16class 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 *pPhysics = pObject ? (pObject->VPhysicsGetObject()) : NULL; pPhysics->Sleep(); }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 ) {m_hObject->SetHealth(100); // Dirty Way to stop the object breaking causing the client to crash... 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; //Msg("Debugging Info for Model: " & pObject->GetModelName ); // Msg("-> MoveType: " + pObject->GetFlags() ); // Msg("-> AbsOrigin: " + pObject->GetAbsOrigin() ); // Msg("-> GetAbsVelocity: " + pObject->GetAbsVelocity().x + " " + pObject->GetAbsVelocity().y + " " + pObject->GetAbsVelocity().z ); // Msg("condump"); if ( pPhysics && pObject->GetMoveType() == MOVETYPE_VPHYSICS ) { pPhysics->EnableMotion(true); pObject->SetHealth ( 9999999 ); // Dirty Way to stop the object breaking causing the client to crash... 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( 9999999 );// 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; 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 ); 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 );// Start Freeze Prop on secondary code (Marine) Vector start = pOwner->Weapon_ShootPosition(); Vector end = start + forward * 16000; 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->GetMoveType() == MOVETYPE_VPHYSICS ){ IPhysicsObject *pPhysics = pHit ? (pHit->VPhysicsGetObject()) : NULL; pPhysics->EnableMotion(false); } } // End Freeze Prop on secondary code (Marine) //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( void )////{//// 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 ( engine->Cmd_Argc() >= 2 )//// {//// testType = atoi(engine->Cmd_Argv(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 );