diff --git a/game/jbmod/scripts/vscripts/gamemodes/sandbox.nut b/game/jbmod/scripts/vscripts/gamemodes/sandbox.nut index d9b73bfc35c..0a7912746b0 100644 --- a/game/jbmod/scripts/vscripts/gamemodes/sandbox.nut +++ b/game/jbmod/scripts/vscripts/gamemodes/sandbox.nut @@ -31,5 +31,5 @@ IncludeScript( "gamemodes/base.nut" ); function OnPlayerSpawn( player ) { player.EquipSuit(); - player.GiveItem( "weapon_physcannon" ); + player.GiveItem( "weapon_physgun" ); } diff --git a/game/jbmod/sound/weapons/flaregun/impact.wav b/game/jbmod/sound/weapons/flaregun/impact.wav new file mode 100644 index 00000000000..96a91c7146e Binary files /dev/null and b/game/jbmod/sound/weapons/flaregun/impact.wav differ diff --git a/game/jbmod/sound/weapons/physgun_loop1.wav b/game/jbmod/sound/weapons/physgun_loop1.wav new file mode 100644 index 00000000000..378c82e1d8b Binary files /dev/null and b/game/jbmod/sound/weapons/physgun_loop1.wav differ diff --git a/game/jbmod/sound/weapons/physgun_loop2.wav b/game/jbmod/sound/weapons/physgun_loop2.wav new file mode 100644 index 00000000000..d7bcde268b3 Binary files /dev/null and b/game/jbmod/sound/weapons/physgun_loop2.wav differ diff --git a/game/jbmod/sound/weapons/physgun_loop3.wav b/game/jbmod/sound/weapons/physgun_loop3.wav new file mode 100644 index 00000000000..59d79872f5b Binary files /dev/null and b/game/jbmod/sound/weapons/physgun_loop3.wav differ diff --git a/game/jbmod/sound/weapons/physgun_loop4.wav b/game/jbmod/sound/weapons/physgun_loop4.wav new file mode 100644 index 00000000000..0c9f544ad1c Binary files /dev/null and b/game/jbmod/sound/weapons/physgun_loop4.wav differ diff --git a/game/jbmod/sound/weapons/physgun_off.wav b/game/jbmod/sound/weapons/physgun_off.wav new file mode 100644 index 00000000000..c8c8a7cd883 Binary files /dev/null and b/game/jbmod/sound/weapons/physgun_off.wav differ diff --git a/game/jbmod/sound/weapons/physgun_on.wav b/game/jbmod/sound/weapons/physgun_on.wav new file mode 100644 index 00000000000..928cd3a7e26 Binary files /dev/null and b/game/jbmod/sound/weapons/physgun_on.wav differ diff --git a/src/game/client/client_jbmod.vpc b/src/game/client/client_jbmod.vpc index 1d4fa4da637..c5ff838a0ab 100644 --- a/src/game/client/client_jbmod.vpc +++ b/src/game/client/client_jbmod.vpc @@ -187,6 +187,7 @@ $Project "Client (JBMod)" $File "$SRCDIR\game\shared\jbmod\weapon_stunstick.cpp" $File "$SRCDIR\game\shared\jbmod\weapon_scripted.cpp" $File "$SRCDIR\game\shared\jbmod\weapon_scripted.h" + $File "jbmod\c_weapon_physgun.cpp" } $Folder "UI" diff --git a/src/game/client/jbmod/c_weapon_physgun.cpp b/src/game/client/jbmod/c_weapon_physgun.cpp new file mode 100644 index 00000000000..a6e19902952 --- /dev/null +++ b/src/game/client/jbmod/c_weapon_physgun.cpp @@ -0,0 +1,181 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//===========================================================================// + +#include "cbase.h" +#include "hud.h" +#include "in_buttons.h" +#include "beamdraw.h" +#include "c_weapon__stubs.h" +#include "clienteffectprecachesystem.h" +#include "c_jbmod_player.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +CLIENTEFFECT_REGISTER_BEGIN( PrecacheEffectGravityGun ) +CLIENTEFFECT_MATERIAL( "sprites/physbeam" ) +CLIENTEFFECT_REGISTER_END() + +class C_BeamQuadratic : public CDefaultClientRenderable +{ +public: + C_BeamQuadratic(); + void Update( C_BaseEntity *pOwner ); + + // IClientRenderable + virtual const Vector &GetRenderOrigin( void ) { return m_worldPosition; } + virtual const QAngle &GetRenderAngles( void ) { return vec3_angle; } + virtual const matrix3x4_t &RenderableToWorldTransform(); + virtual bool ShouldDraw( void ) { return true; } + virtual bool IsTransparent( void ) { return true; } + virtual bool ShouldReceiveProjectedTextures( int flags ) { return false; } + virtual int DrawModel( int flags ); + + // Returns the bounds relative to the origin (render bounds) + virtual void GetRenderBounds( Vector &mins, Vector &maxs ) + { + // bogus. But it should draw if you can see the end point + mins.Init( -99999, -99999, -99999 ); + maxs.Init( 99999, 99999, 99999 ); + } + + C_BaseEntity *m_pOwner; + Vector m_targetPosition; + Vector m_worldPosition; + int m_active; + int m_glueTouching; +}; + + +class C_WeaponGravityGun : public C_BaseCombatWeapon +{ + DECLARE_CLASS( C_WeaponGravityGun, C_BaseCombatWeapon ); +public: + C_WeaponGravityGun() {} + + DECLARE_CLIENTCLASS(); + DECLARE_PREDICTABLE(); + + int KeyInput( int down, ButtonCode_t keynum, const char *pszCurrentBinding ) + { + if ( gHUD.m_iKeyBits & IN_ATTACK ) + { + switch ( keynum ) + { + case MOUSE_WHEEL_UP: + gHUD.m_iKeyBits |= IN_WEAPON1; + return 0; + + case MOUSE_WHEEL_DOWN: + gHUD.m_iKeyBits |= IN_WEAPON2; + return 0; + } + } + + // Allow engine to process + return BaseClass::KeyInput( down, keynum, pszCurrentBinding ); + } + + void OnDataChanged( DataUpdateType_t updateType ) + { + BaseClass::OnDataChanged( updateType ); + m_beam.Update( this ); + } + + void UpdateOnRemove( void ) + { + m_beam.m_active = false; + m_beam.Update( this ); + + BaseClass::UpdateOnRemove(); + } + +private: + C_WeaponGravityGun( const C_WeaponGravityGun & ); + + C_BeamQuadratic m_beam; +}; + +STUB_WEAPON_CLASS_IMPLEMENT( weapon_physgun, C_WeaponGravityGun ); + +IMPLEMENT_CLIENTCLASS_DT( C_WeaponGravityGun, DT_WeaponGravityGun, CWeaponGravityGun ) +RecvPropVector( RECVINFO_NAME( m_beam.m_targetPosition, m_targetPosition ) ), +RecvPropVector( RECVINFO_NAME( m_beam.m_worldPosition, m_worldPosition ) ), +RecvPropInt( RECVINFO_NAME( m_beam.m_active, m_active ) ), +RecvPropInt( RECVINFO_NAME( m_beam.m_glueTouching, m_glueTouching ) ), +END_RECV_TABLE() + +C_BeamQuadratic::C_BeamQuadratic() +{ + m_pOwner = NULL; +} + +void C_BeamQuadratic::Update( C_BaseEntity *pOwner ) +{ + m_pOwner = pOwner; + if ( m_active ) + { + if ( m_hRenderHandle == INVALID_CLIENT_RENDER_HANDLE ) + { + ClientLeafSystem()->AddRenderable( this, RENDER_GROUP_TRANSLUCENT_ENTITY ); + } + else + { + ClientLeafSystem()->RenderableChanged( m_hRenderHandle ); + } + } + else if ( !m_active && m_hRenderHandle != INVALID_CLIENT_RENDER_HANDLE ) + { + ClientLeafSystem()->RemoveRenderable( m_hRenderHandle ); + } +} + + +int C_BeamQuadratic::DrawModel( int ) +{ + Vector points[3]; + QAngle tmpAngle; + + if ( !m_active ) + return 0; + + C_JBMod_Player *pOwner = ToJBModPlayer( m_pOwner->GetOwnerEntity() ); + C_BaseEntity *pEnt = pOwner->GetRenderedWeaponModel(); + if ( !pEnt ) + return 0; + pEnt->GetAttachment( 1, points[0], tmpAngle ); + + points[1] = 0.5 * ( m_targetPosition + points[0] ); + + // a little noise 11t & 13t should be somewhat non-periodic looking + //points[1].z += 4*sin( gpGlobals->curtime*11 ) + 5*cos( gpGlobals->curtime*13 ); + points[2] = m_worldPosition; + + IMaterial *pMat = materials->FindMaterial( "sprites/physbeam", TEXTURE_GROUP_CLIENT_EFFECTS ); + Vector color; + if ( m_glueTouching ) + { + color.Init( 1, 0, 0 ); + } + else + { + color.Init( 1, 1, 1 ); + } + + float scrollOffset = gpGlobals->curtime - (int)gpGlobals->curtime; + CMatRenderContextPtr pRenderContext( materials ); + pRenderContext->Bind( pMat ); + DrawBeamQuadratic( points[0], points[1], points[2], 13, color, scrollOffset ); + return 1; +} + +const matrix3x4_t &C_BeamQuadratic::RenderableToWorldTransform() +{ + static matrix3x4_t mat; + AngleMatrix( GetRenderAngles(), GetRenderOrigin(), mat ); + return mat; +} diff --git a/src/game/server/jbmod/weapon_physgun.cpp b/src/game/server/jbmod/weapon_physgun.cpp new file mode 100644 index 00000000000..da82a27ed4d --- /dev/null +++ b/src/game/server/jbmod/weapon_physgun.cpp @@ -0,0 +1,1717 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "beam_shared.h" +#include "player.h" +#include "jbmod_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" +#include "ai_basenpc.h" +#include "physics_prop_ragdoll.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +ConVar phys_gunmass( "phys_gunmass", "200", FCVAR_REPLICATED | FCVAR_NOTIFY ); +ConVar phys_gunvel( "phys_gunvel", "400", FCVAR_REPLICATED | FCVAR_NOTIFY ); +ConVar phys_gunforce( "phys_gunforce", "20e20", FCVAR_REPLICATED | FCVAR_NOTIFY ); +ConVar phys_guntorque( "phys_guntorque", "100", FCVAR_REPLICATED | FCVAR_NOTIFY ); +ConVar phys_gunglueradius( "phys_gunglueradius", "128", FCVAR_REPLICATED | FCVAR_NOTIFY ); +ConVar phys_gunjank( "phys_gunjank", "0", FCVAR_REPLICATED | FCVAR_NOTIFY ); + +static int g_physgunBeam; +#define PHYSGUN_BEAM_SPRITE "sprites/physbeam.vmt" + +#define MAX_PELLETS 512 +#define MAX_FREEZE 2048 + +class CWeaponGravityGun; + +class CGravityPellet : public CBaseAnimating +{ + DECLARE_CLASS( CGravityPellet, CBaseAnimating ); +public: + DECLARE_DATADESC(); + + ~CGravityPellet(); + void Precache() + { + SetModelName( MAKE_STRING( "models/weapons/w_bugbait.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; + m_pPair = NULL; + } + + bool IsInert() + { + return m_isInert; + } + + bool MakeConstraint( CBaseEntity *pObject, CGravityPellet *pHeld, CGravityPellet *pBond ) + { + 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 ); + + pHeld->m_pPair = pBond; + pBond->m_pPair = pHeld; + + MakeInert(); + return true; + } + + void MakeActive() + { + SetRenderColor( 255, 0, 0 ); + m_isInert = false; + } + + void MakeInert() + { + SetRenderColor( 64, 64, 128 ); + m_isInert = true; + } + + void InputOnBreak( inputdata_t &inputdata ) + { + if ( m_pPair ) + { + UTIL_Remove( m_pPair ); + m_pPair = NULL; + } + UTIL_Remove( this ); + } + + IPhysicsConstraint *m_pConstraint; + bool m_isInert; + CGravityPellet *m_pPair; +}; + +LINK_ENTITY_TO_CLASS( gravity_pellet, CGravityPellet ); +PRECACHE_REGISTER( gravity_pellet ); + +BEGIN_DATADESC( CGravityPellet ) + +DEFINE_PHYSPTR( m_pConstraint ), +DEFINE_FIELD( m_isInert, FIELD_BOOLEAN ), +DEFINE_FIELD( m_pPair, FIELD_CLASSPTR ), +// 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_pPair ) + { + m_pPair->m_pPair = NULL; + } + if ( m_pConstraint ) + { + physenv->DestroyConstraint( m_pConstraint ); + } +} + +class CGravControllerPoint : public IMotionEvent +{ + DECLARE_SIMPLE_DATADESC(); + +public: + CGravControllerPoint( void ); + ~CGravControllerPoint( void ); + void AttachEntity( CBasePlayer *pPlayer, 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; + m_shadow.targetPosition = target; + if ( m_attachedEntity == NULL ) + { + m_worldPosition = target; + } + m_timeToArrive = gpGlobals->frametime; + + if ( m_attachedEntity != NULL ) + { + CBaseEntity *pEntity = m_attachedEntity; + if ( pEntity ) + { + IPhysicsObject *pObject = pEntity->VPhysicsGetObject(); + if ( pObject ) + { + pObject->Wake(); + + QAngle angles; + Vector origin; + pObject->GetShadowPosition( &origin, &angles ); + Vector localorigin; + pObject->LocalToWorld( &localorigin, m_localPosition ); + Vector diff = origin - localorigin; + + m_shadow.targetPosition = target + diff; + } + } + } + } + void SetTargetOrientation( const QAngle &targetOrientation ) + { + m_targetRotation = targetOrientation; + m_shadow.targetRotation = targetOrientation; + m_timeToArrive = gpGlobals->frametime; + } + void SetTarget( const Vector &target, const QAngle &targetOrientation ) + { + m_targetPosition = target; + m_targetRotation = targetOrientation; + m_shadow.targetPosition = target; + m_shadow.targetRotation = targetOrientation; + 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; + float m_contactAmount; + hlshadowcontrol_params_t m_shadow; + + 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_shadow.dampFactor = 1.0; + m_shadow.teleportDistance = 0; + m_shadow.maxSpeed = 1000; + m_shadow.maxAngular = 360.0f * 10.0f; + m_shadow.maxDampSpeed = m_shadow.maxSpeed * 2; + m_shadow.maxDampAngular = m_shadow.maxAngular; + + m_attachedEntity = NULL; + m_contactAmount = 0.0f; +} + +CGravControllerPoint::~CGravControllerPoint( void ) +{ + DetachEntity(); +} + + +void CGravControllerPoint::AttachEntity( CBasePlayer *pPlayer, CBaseEntity *pEntity, IPhysicsObject *pPhys, const Vector &position ) +{ + if ( phys_gunjank.GetFloat() == 0 ) + { + Vector vposition; + QAngle vangles; + pPhys->GetPosition( &vposition, &vangles ); + Pickup_GetPreferredCarryAngles( pEntity, pPlayer, pPlayer->EntityToWorldTransform(), vangles ); + m_controller = physenv->CreateMotionController( this ); + m_controller->AttachObject( pPhys, true ); + m_attachedEntity = pEntity; + pPhys->Wake(); + PhysSetGameFlags( pPhys, FVPHYSICS_PLAYER_HELD ); + SetTarget( vposition, vangles ); + pPhys->EnableCollisions( true ); + m_contactAmount = 0; + pPhys->WorldToLocal( &m_localPosition, position ); + m_worldPosition = position; + m_maxAcceleration = phys_gunforce.GetFloat() * pPhys->GetInvMass(); + float torque = phys_guntorque.GetFloat(); + m_maxAngularAcceleration = torque * pPhys->GetInvInertia(); + } + else + { + 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 ); + SetTarget( position, pEntity->GetAbsAngles() ); + m_maxAcceleration = phys_gunforce.GetFloat() * pPhys->GetInvMass(); + 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 ) + { + if ( phys_gunjank.GetFloat() == 0 ) + { + pPhys->EnableCollisions( true ); + PhysClearGameFlags( pPhys, FVPHYSICS_PLAYER_HELD ); + Vector vel; + AngularImpulse angVel; + pPhys->GetVelocity( &vel, &angVel ); + float speed = VectorNormalize( vel ) - ( 190.0f * 1.5f ); + float angSpeed = VectorNormalize( angVel ) - ( 2.0f * 360.0f ); + speed = speed < 0 ? 0 : -speed; + angSpeed = angSpeed < 0 ? 0 : -angSpeed; + vel *= speed; + angVel *= angSpeed; + pPhys->AddVelocity( &vel, &angVel ); + } + + // 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 ) +{ + if ( phys_gunjank.GetFloat() == 0 ) + { + hlshadowcontrol_params_t shadowParams = m_shadow; + m_contactAmount = Approach( 1.0f, m_contactAmount, deltaTime * 2.0f ); + shadowParams.maxAngular = m_shadow.maxAngular * m_contactAmount * m_contactAmount * m_contactAmount; + m_timeToArrive = pObject->ComputeShadowControl( shadowParams, m_timeToArrive, deltaTime ); + Vector vel; + AngularImpulse angVel; + pObject->GetVelocity( &vel, &angVel ); + PhysComputeSlideDirection( pObject, vel, angVel, &vel, &angVel, 0.0f ); + pObject->SetVelocityInstantaneous( &vel, NULL ); + Vector world; + pObject->LocalToWorld( &world, m_localPosition ); + m_worldPosition = world; + linear.Init(); + angular.Init(); + return SIM_GLOBAL_ACCELERATION; + } + else + { + 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; +}; + +struct freezelist_t +{ + DECLARE_SIMPLE_DATADESC(); + + EHANDLE parent; + IPhysicsObject *physobject; +}; + +class CWeaponGravityGun : public CBaseCombatWeapon +{ + DECLARE_DATADESC(); + +public: + DECLARE_CLASS( CWeaponGravityGun, CBaseCombatWeapon ); + + CWeaponGravityGun(); + void Spawn( void ); + void OnRestore( void ); + void UpdateOnRemove( 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_iPrimaryAmmoType ); + BaseClass::Equip( pOwner ); + } + void Drop( const Vector &vecVelocity ) + { + CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); + pOwner->SetAmmoCount( 0, m_iPrimaryAmmoType ); + // 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 FreezeObject( void ); + void UnfreezeObjects( void ); + void SortFreezeList( void ); + void AddPellet( CGravityPellet *pPellet, CBaseEntity *pParent, const Vector &surfaceNormal ); + void DeleteActivePellets(); + void SortPelletsForObject( CBaseEntity *pObject ); + void SortPelletList( void ); + 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; + Vector m_originalObjectPosition; + + CGravControllerPoint m_gravCallback; + pelletlist_t m_activePellets[MAX_PELLETS]; + freezelist_t m_activeFreeze[MAX_FREEZE]; + int m_pelletCount; + int m_freezeCount; + int m_objectPelletCount; + + int m_pelletHeld; + int m_pelletAttract; + float m_glueTime; + CNetworkVar( bool, m_glueTouching ); + + DECLARE_ACTTABLE(); +}; + +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 ), +END_SEND_TABLE() + +acttable_t CWeaponGravityGun::m_acttable[] = +{ + { ACT_HL2MP_IDLE, ACT_HL2MP_IDLE_PHYSGUN, false }, + { ACT_HL2MP_RUN, ACT_HL2MP_RUN_PHYSGUN, false }, + { ACT_HL2MP_IDLE_CROUCH, ACT_HL2MP_IDLE_CROUCH_PHYSGUN, false }, + { ACT_HL2MP_WALK_CROUCH, ACT_HL2MP_WALK_CROUCH_PHYSGUN, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK, ACT_HL2MP_GESTURE_RANGE_ATTACK_PHYSGUN, false }, + { ACT_HL2MP_GESTURE_RELOAD, ACT_HL2MP_GESTURE_RELOAD_PHYSGUN, false }, + { ACT_HL2MP_JUMP, ACT_HL2MP_JUMP_PHYSGUN, false }, +}; +IMPLEMENT_ACTTABLE( CWeaponGravityGun ); + +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_SIMPLE_DATADESC( freezelist_t ) + +DEFINE_FIELD( parent, FIELD_EHANDLE ), +DEFINE_PHYSPTR( physobject ), + +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_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_EMBEDDED_AUTO_ARRAY( m_activeFreeze ), +DEFINE_FIELD( m_pelletCount, FIELD_INTEGER ), +DEFINE_FIELD( m_freezeCount, 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::UpdateOnRemove( void ) +{ + if ( m_active ) + SoundStop(); + m_active = false; + + BaseClass::UpdateOnRemove(); +} + + +//========================================================= +//========================================================= +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; + + 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(); // FIXME(JBMOD): This causes crashes + 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 + if ( phys_gunjank.GetFloat() > 0 ) + { + 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 ( phys_gunjank.GetFloat() == 0 ) + { + if ( pOwner->m_afButtonPressed & IN_SPEED ) + pObject->VPhysicsGetObject()->EnableCollisions( false ); + if ( pOwner->m_afButtonReleased & IN_SPEED ) + pObject->VPhysicsGetObject()->EnableCollisions( true ); + if ( ( pOwner->m_nButtons | pOwner->m_afButtonPressed | pOwner->m_afButtonReleased ) & IN_WEAPON1 ) + m_distance = UTIL_Approach( 1024, m_distance, m_distance * 0.1 ); + if ( ( pOwner->m_nButtons | pOwner->m_afButtonPressed | pOwner->m_afButtonReleased ) & IN_WEAPON2 ) + m_distance = UTIL_Approach( 40, m_distance, m_distance * 0.1 ); + } + else { + if ( m_useDown && 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 ); + } + + 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() ); + } + + if ( phys_gunjank.GetFloat() == 0 ) + { + IPhysicsObject *pPhys = pObject->VPhysicsGetObject(); + if ( pPhys ) + { + Vector world; + pPhys->LocalToWorld( &world, m_gravCallback.m_localPosition ); + } + } + + 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(); + } + + NetworkStateChanged(); +} + +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 = 1.0; + if ( pPhys != NULL ) + 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 ) +{ + SortPelletList(); + m_objectPelletCount = 0; + for ( int i = 0; i < m_pelletCount; i++ ) + { + if ( !m_activePellets[i].pellet ) + continue; + // 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( 0, 255, 0 ); +} + +void CWeaponGravityGun::SortPelletList( void ) +{ + m_pelletCount = 0; + for ( int j = 0, i = 0; j < MAX_PELLETS; j++ ) + { + pelletlist_t tmp = m_activePellets[j]; + if ( !tmp.pellet ) + { + i++; + continue; + } + m_activePellets[j - i] = tmp; + m_pelletCount++; + } +} + +void CWeaponGravityGun::SortFreezeList( void ) +{ + m_freezeCount = 0; + for ( int j = 0, i = 0; j < MAX_FREEZE; j++ ) + { + freezelist_t tmp = m_activeFreeze[j]; + if ( !tmp.physobject || !tmp.parent ) + { + i++; + continue; + } + m_activeFreeze[j - i] = tmp; + m_freezeCount++; + } +} + +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() +{ + CJBMod_Player *pOwner = ToJBModPlayer( GetOwner() ); + if ( !pOwner ) + return NULL; + + CBaseCombatWeapon *pWeapon = pOwner->GetActiveWeapon(); + if ( pWeapon ) + return pWeapon; + + 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->EnergySplash( pPellet->GetAbsOrigin(), forward ); + + // 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( 0, 255, 0 ); + pBeam->RelinkBeam(); + pBeam->LiveForTime( 0.1 ); + g_pEffects->Sparks( pPellet->GetAbsOrigin() ); + + UTIL_Remove( pPellet ); + } + m_pelletCount = 0; +} + +void CWeaponGravityGun::CreatePelletAttraction( float radius, CBaseEntity *pObject ) +{ + if ( m_pelletHeld != -1 ) + { + if ( !m_activePellets[m_pelletHeld].pellet.IsValid() || (CBaseEntity *)m_activePellets[m_pelletHeld].pellet == NULL ) + { + m_pelletHeld = -1; + m_pelletAttract = -1; + return; + } + } + if ( m_pelletAttract != -1 ) + { + if ( !m_activePellets[m_pelletAttract].pellet.IsValid() || (CBaseEntity *)m_activePellets[m_pelletAttract].pellet == NULL ) + { + m_pelletHeld = -1; + m_pelletAttract = -1; + return; + } + } + + 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 ) +{ + CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); + if ( !pOwner ) + return; + + m_useDown = false; + IPhysicsObject *pPhysics = pObject ? ( pObject->VPhysicsGetObject() ) : NULL; + if ( pPhysics && pObject->GetMoveType() == MOVETYPE_VPHYSICS ) + { + pPhysics->EnableMotion( true ); + SortFreezeList(); + + if ( pObject->GetCollisionGroup() == COLLISION_GROUP_DEBRIS ) + pObject->SetCollisionGroup( COLLISION_GROUP_INTERACTIVE_DEBRIS ); + + if ( pObject->IsNPC() && !pObject->IsEFlagSet( EFL_NO_MEGAPHYSCANNON_RAGDOLL ) && pObject->MyNPCPointer()->CanBecomeRagdoll() ) + { + Assert( pObject->MyNPCPointer()->CanBecomeRagdoll() ); + CTakeDamageInfo info( GetOwner(), GetOwner(), 1.0f, DMG_GENERIC ); + CBaseEntity *pRagdoll = CreateServerRagdoll( pObject->MyNPCPointer(), 0, info, COLLISION_GROUP_INTERACTIVE_DEBRIS, true ); + PhysSetEntityGameFlags( pRagdoll, FVPHYSICS_NO_SELF_COLLISIONS ); + pRagdoll->SetCollisionBounds( pObject->CollisionProp()->OBBMins(), pObject->CollisionProp()->OBBMaxs() ); + CTakeDamageInfo ragdollInfo( GetOwner(), GetOwner(), 10000.0, DMG_PHYSGUN | DMG_REMOVENORAGDOLL ); + pObject->TakeDamage( ragdollInfo ); + pObject = pRagdoll; + pPhysics = pRagdoll->VPhysicsGetObject(); + } + + m_hObject = pObject; + m_distance = distance; + + m_gravCallback.AttachEntity( pOwner, pObject, pPhysics, end ); + float mass = pPhysics->GetMass(); + 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 ); + + 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_iPrimaryAmmoType ) <= 0 ) + return; + + 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_iPrimaryAmmoType ); + + 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 ); + g_pEffects->Sparks( pPellet->GetAbsOrigin() ); + +} + +void CWeaponGravityGun::FreezeObject() +{ + if ( m_active && m_hObject ) + { + // FIXME(JBMOD): Don't freeze vehicles for now + CBaseEntity *pObject = m_hObject; + if ( pObject->GetServerVehicle() != NULL ) + { + if ( Q_strcmp( STRING( pObject->m_iClassname ), "prop_vehicle_airboat" ) && Q_strcmp( STRING( pObject->m_iClassname ), "prop_vehicle_prisoner_pod" ) ) + return; + } + + IPhysicsObject *pPhysicsObject = m_hObject->VPhysicsGetObject(); + + if ( pPhysicsObject ) { + m_activeFreeze[m_freezeCount].parent = m_hObject; + m_activeFreeze[m_freezeCount].physobject = pPhysicsObject; + m_freezeCount++; + + pPhysicsObject->EnableMotion( false ); + + m_flNextPrimaryAttack = gpGlobals->curtime + 0.75; + EffectDestroy(); + SoundDestroy(); + } + } +} + +void CWeaponGravityGun::UnfreezeObjects() +{ + SortFreezeList(); + for ( int i = 0; i < m_freezeCount; i++ ) + { + CBaseEntity *pObject = m_activeFreeze[i].parent; + IPhysicsObject *pPhysicsObject = m_activeFreeze[i].physobject; + if ( pObject && pObject->VPhysicsGetObject() && pPhysicsObject ) { + pPhysicsObject->Wake(); + bool motion = pPhysicsObject->IsMotionEnabled(); + if ( motion ) {} + pPhysicsObject->EnableMotion( true ); + } + } + m_freezeCount = 0; +} + +void CWeaponGravityGun::WeaponIdle( void ) +{ + 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, m_activePellets[m_pelletHeld].pellet, m_activePellets[m_pelletAttract].pellet ) ) + { + 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 ( m_active && ( pOwner->m_afButtonPressed & IN_ATTACK2 ) ) + { + FreezeObject(); + } + else if ( pOwner->m_afButtonPressed & IN_ATTACK2 ) + { + if ( m_flNextSecondaryAttack <= gpGlobals->curtime ) + SecondaryAttack(); + } + else if ( pOwner->m_nButtons & IN_ATTACK ) + { + if ( m_flNextPrimaryAttack <= gpGlobals->curtime ) + 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_iPrimaryAmmoType ) != MAX_PELLETS || m_freezeCount > 0 ) + { + pOwner->SetAmmoCount( MAX_PELLETS, m_iPrimaryAmmoType ); + DeleteActivePellets(); + UnfreezeObjects(); + WeaponSound( RELOAD ); + return true; + } + + return false; +} diff --git a/src/game/server/server_jbmod.vpc b/src/game/server/server_jbmod.vpc index a50b2e8dbae..23d04372b59 100644 --- a/src/game/server/server_jbmod.vpc +++ b/src/game/server/server_jbmod.vpc @@ -467,6 +467,7 @@ $Project "Server (JBMod)" $File "$SRCDIR\game\shared\jbmod\weapon_stunstick.cpp" $File "$SRCDIR\game\shared\jbmod\weapon_scripted.cpp" $File "$SRCDIR\game\shared\jbmod\weapon_scripted.h" + $File "jbmod\weapon_physgun.cpp" } } } diff --git a/src/game/shared/jbmod/jbmod_gamerules.cpp b/src/game/shared/jbmod/jbmod_gamerules.cpp index 4099fb6a2eb..1690465cfa6 100644 --- a/src/game/shared/jbmod/jbmod_gamerules.cpp +++ b/src/game/shared/jbmod/jbmod_gamerules.cpp @@ -1015,6 +1015,7 @@ CAmmoDef *GetAmmoDef() def.AddAmmoType("SMG1_Grenade", DMG_BURN, TRACER_NONE, 0, 0, 3, 0, 0 ); def.AddAmmoType("Grenade", DMG_BURN, TRACER_NONE, 0, 0, 5, 0, 0 ); def.AddAmmoType("slam", DMG_BURN, TRACER_NONE, 0, 0, 5, 0, 0 ); + def.AddAmmoType("Gravity", DMG_CLUB, TRACER_NONE, 0, 0, 8, 0, 0 ); } return &def;