summaryrefslogtreecommitdiff
path: root/game/shared/tf2/weapon_drainbeam.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'game/shared/tf2/weapon_drainbeam.cpp')
-rw-r--r--game/shared/tf2/weapon_drainbeam.cpp523
1 files changed, 523 insertions, 0 deletions
diff --git a/game/shared/tf2/weapon_drainbeam.cpp b/game/shared/tf2/weapon_drainbeam.cpp
new file mode 100644
index 0000000..925fa15
--- /dev/null
+++ b/game/shared/tf2/weapon_drainbeam.cpp
@@ -0,0 +1,523 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Sapper's draining beam
+//
+// $NoKeywords: $
+//=============================================================================//
+#include "cbase.h"
+#include "basetfplayer_shared.h"
+#include "in_buttons.h"
+#include "weapon_combatshield.h"
+#include "engine/IEngineSound.h"
+#include "grenade_base_empable.h"
+
+#if defined( CLIENT_DLL )
+
+#include "particles_simple.h"
+
+#else
+
+#include "tf_gamerules.h"
+#include "tf_class_sapper.h"
+
+#endif
+
+#include "weapon_drainbeam.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+
+#define PARTICLE_PATH_VEL 140.0
+#define NUM_PATH_PARTICLES_PER_SEC 600.0f
+#define NUM_DRIBBLE_PARTICLES_PER_SEC 20.0f
+
+#define NUM_DRAINBEAM_PATH_POINTS 8
+
+// Buff ranges
+static ConVar weapon_drainbeam_target_range( "weapon_drainbeam_target_range", "900", FCVAR_REPLICATED, "The farthest away you can be for the drain gun to initially lock onto a target." );
+static ConVar weapon_drainbeam_stick_range( "weapon_drainbeam_stick_range", "612", FCVAR_REPLICATED, "How far away the drain gun can stay locked onto a target." );
+static ConVar weapon_drainbeam_max_rate( "weapon_drainbeam_max_rate", "10", FCVAR_REPLICATED );
+static ConVar weapon_drainbeam_max_rate_time( "weapon_drainbeam_max_rate_time", "5", FCVAR_REPLICATED );
+static ConVar weapon_drainbeam_emp_time_factor( "weapon_drainbeam_emp_time_factor", "0.75", FCVAR_REPLICATED );
+
+
+LINK_ENTITY_TO_CLASS( weapon_drainbeam, CWeaponDrainBeam );
+
+PRECACHE_WEAPON_REGISTER( weapon_drainbeam );
+
+IMPLEMENT_NETWORKCLASS_ALIASED( WeaponDrainBeam, DT_WeaponDrainBeam )
+
+BEGIN_NETWORK_TABLE( CWeaponDrainBeam, DT_WeaponDrainBeam )
+#if !defined( CLIENT_DLL )
+ SendPropInt( SENDINFO( m_bDraining ), 1, SPROP_UNSIGNED ),
+ SendPropInt( SENDINFO( m_bAttacking ), 1, SPROP_UNSIGNED ),
+ SendPropVector( SENDINFO( m_vFireTarget ), 0, SPROP_COORD ),
+ SendPropEHandle( SENDINFO( m_hDrainTarget ) ),
+#else
+ RecvPropInt( RECVINFO( m_bAttacking ) ),
+ RecvPropInt( RECVINFO( m_bDraining ) ),
+ RecvPropVector( RECVINFO(m_vFireTarget) ),
+ RecvPropEHandle( RECVINFO(m_hDrainTarget) ),
+#endif
+END_NETWORK_TABLE()
+
+BEGIN_PREDICTION_DATA( CWeaponDrainBeam )
+
+ DEFINE_PRED_FIELD( m_bDraining, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ),
+ DEFINE_PRED_FIELD( m_bAttacking, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ),
+ DEFINE_PRED_FIELD_TOL( m_vFireTarget, FIELD_VECTOR, FTYPEDESC_INSENDTABLE, 0.125f ),
+
+ DEFINE_PRED_FIELD( m_hDrainTarget, FIELD_EHANDLE, FTYPEDESC_INSENDTABLE ),
+
+// DEFINE_PRED_FIELD( m_pEmitter, FIELD_POINTER ),
+// DEFINE_PRED_FIELD( m_hParticleMaterial, FIELD_???, ),
+// DEFINE_PRED_FIELD( m_PathParticleEvent, FIELD_???, ),
+// DEFINE_PRED_FIELD( m_DribbleParticleEvent, FIELD_??? ),
+// DEFINE_PRED_FIELD( m_bPlayingSound, FIELD_BOOLEAN ),
+
+END_PREDICTION_DATA()
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CWeaponDrainBeam::CWeaponDrainBeam( void )
+{
+ m_bDraining = false;
+ m_bAttacking = false;
+ m_flNextBuzzTime = 0;
+
+ SetPredictionEligible( true );
+#if defined( CLIENT_DLL )
+ m_pEmitter = NULL;
+ m_hParticleMaterial = INVALID_MATERIAL_HANDLE;
+ m_PathParticleEvent.Init( NUM_PATH_PARTICLES_PER_SEC );
+ m_DribbleParticleEvent.Init( NUM_DRIBBLE_PARTICLES_PER_SEC );
+ m_bPlayingSound = false;
+#endif
+}
+
+void CWeaponDrainBeam::Precache()
+{
+ BaseClass::Precache();
+
+ PrecacheScriptSound( "WeaponRepairGun.Healing" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CWeaponDrainBeam::Holster( CBaseCombatWeapon *pSwitchingTo )
+{
+ RemoveDrainTarget();
+
+ return BaseClass::Holster( pSwitchingTo );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns a pointer to a drainable target
+//-----------------------------------------------------------------------------
+CBaseEntity *CWeaponDrainBeam::GetTargetToDrain( CBaseEntity *pCurDrainTarget )
+{
+ CBaseTFPlayer *pOwner = ToBaseTFPlayer( GetOwner() );
+ if ( !pOwner )
+ return NULL;
+
+#if !defined( CLIENT_DLL )
+ Vector vecSrc = pOwner->Weapon_ShootPosition( );
+
+ // ROBIN: Disabled drainbeam locking to target
+/*
+ // If we're already draining something, stick onto it as long as possible.
+ CBaseEntity *pEntity = pCurDrainTarget;
+ if ( pEntity && pEntity->IsAlive() )
+ {
+ // Make sure the target didn't go out of range.
+ Vector vecTargetCenter = pEntity->Center();
+ if ( (vecTargetCenter - vecSrc).Length() < weapon_drainbeam_stick_range.GetFloat() )
+ {
+ // Now make sure there isn't something other than team players in the way.
+ class CDrainFilter : public CTraceFilterSimple
+ {
+ public:
+ CDrainFilter( CBaseEntity *pShooter ) : CTraceFilterSimple( pShooter, TFCOLLISION_GROUP_WEAPON )
+ {
+ m_pShooter = pShooter;
+ }
+
+ virtual bool ShouldHitEntity( IServerEntity *pServerEntity, int contentsMask )
+ {
+ // If it hit an edict the isn't the target and is on our team, then the ray is blocked.
+ CBaseEntity *pEnt = (CBaseEntity *)pServerEntity;
+
+ // Ignore collisions with the shooter
+ if ( pEnt == m_pShooter )
+ return false;
+
+ // Ignore neutral objects
+ if ( pEnt->GetTeamNumber() == 0 )
+ return false;
+
+ // Ignore collisions with teammates
+ if ( pEnt->GetTeam() == m_pShooter->GetTeam() )
+ return false;
+
+ // Ignore players
+ if ( pEnt->IsPlayer() )
+ return false;
+
+ return CTraceFilterSimple::ShouldHitEntity( pServerEntity, contentsMask );
+ }
+
+ CBaseEntity *m_pShooter;
+ };
+
+ trace_t tr;
+ CDrainFilter drainFilter( pOwner );
+ engine->TraceLine( vecSrc, vecTargetCenter, MASK_SHOT, &drainFilter, &tr );
+
+ if (( tr.fraction == 1.0f) || (tr.m_pEnt == pEntity))
+ return pEntity;
+ }
+
+ // Return null so we can't target this player but m_hDrainTarget stays set to them.
+ return NULL;
+ }
+ else
+*/
+ {
+ // Ok, try to find a new target to drain.
+ // Get the target point and location
+ Vector vecAiming;
+ pOwner->EyeVectors( &vecAiming );
+
+ // Find a player in range of this player, and make sure they're drainable.
+ Vector vecEnd = vecSrc + vecAiming * weapon_drainbeam_target_range.GetFloat();
+ trace_t tr;
+
+ // Use WeaponTraceLine so shields are tested...
+ TFGameRules()->WeaponTraceLine( vecSrc, vecEnd, MASK_SHOT & (~CONTENTS_HITBOX), pOwner, DMG_PROBE, &tr );
+
+ if ( tr.fraction != 1.0 )
+ {
+ CBaseEntity *pEntity = tr.m_pEnt;
+ if ( pEntity && (pEntity != pOwner) && pEntity->IsAlive() )
+ {
+ // Target needs to not be a player, take EMP damage, and needs to be an enemy
+ if ( !pEntity->IsPlayer() && pEntity->CanBePoweredUp() && pEntity->GetTeamNumber() != 0 && !pEntity->InSameTeam( pOwner ) )
+ {
+ // If we've just locked onto a new target, restart our drain rate
+ if ( pCurDrainTarget != pEntity )
+ {
+ m_flDrainStartedAt = gpGlobals->curtime;
+ }
+ return pEntity;
+ }
+ }
+ }
+
+ return NULL;
+ }
+#endif
+
+ return NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Overloaded to handle the hold-down draining
+//-----------------------------------------------------------------------------
+void CWeaponDrainBeam::ItemPostFrame( void )
+{
+ CBaseTFPlayer *pOwner = ToBaseTFPlayer( GetOwner() );
+ if ( !pOwner )
+ return;
+
+ // Try to start draining
+ m_bAttacking = false;
+ if ( (pOwner->m_nButtons & IN_ATTACK) && GetShieldState() == SS_DOWN )
+ {
+ PrimaryAttack();
+ m_bAttacking = true;
+ }
+ else if ( GetCurDrainTarget() )
+ {
+ // Detach from the player if they release the attack button.
+ RemoveDrainTarget();
+ }
+
+ // Prevent shield post frame if we're not ready to attack, or we're draining
+ AllowShieldPostFrame( !m_bAttacking );
+
+ WeaponIdle();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CWeaponDrainBeam::RemoveDrainTarget( void )
+{
+ m_hDrainTarget = NULL;
+ m_bDraining = false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Attempt to drain any player within range of the medikit
+//-----------------------------------------------------------------------------
+void CWeaponDrainBeam::PrimaryAttack( void )
+{
+ CBaseTFPlayer *pOwner = ToBaseTFPlayer( GetOwner() );
+ if ( !pOwner )
+ return;
+
+#if !defined( CLIENT_DLL )
+ // Find a target to drain
+ CBaseEntity *pTarget = GetTargetToDrain( GetCurDrainTarget() );
+ if ( pTarget )
+ {
+ // EMP the target
+ if ( pTarget->CanBePoweredUp() )
+ {
+ // Increase the EMP time based upon the time we spent draining
+ float flDrainTime = (gpGlobals->curtime - m_flDrainStartedAt);
+ float flPostDrainTime = weapon_drainbeam_emp_time_factor.GetFloat() * flDrainTime;
+ if ( pTarget->AttemptToPowerup( POWERUP_EMP, flPostDrainTime ) )
+ {
+ // Crank up the drain rate based on the time since we started draining this target
+ flDrainTime = MIN( weapon_drainbeam_max_rate_time.GetFloat(), flDrainTime );
+ float flDrainAmount = weapon_drainbeam_max_rate.GetFloat() * ( flDrainTime / weapon_drainbeam_max_rate_time.GetFloat() );
+
+ // It uses floating point in here so it doesn't lose the fractional part on small frame times.
+ static float flCurDamageAmt = 0.99;
+ float flOldDamageAmt = flCurDamageAmt;
+ flCurDamageAmt += gpGlobals->frametime * flDrainAmount;
+ int iDamage = (int)flCurDamageAmt - (int)flOldDamageAmt;
+
+ // Accumulated enough to do something?
+ if ( iDamage )
+ {
+ // EMPed my target, make it take damage
+ if ( pTarget->GetHealth() )
+ {
+ pTarget->TakeDamage( CTakeDamageInfo( this, pOwner, iDamage, DMG_GENERIC ) );
+ }
+
+ // Increase my health
+ if ( pOwner->GetHealth() < pOwner->GetMaxHealth() )
+ {
+ int maxHealthToAdd = pOwner->GetMaxHealth() - pOwner->GetHealth();
+ int nHealthAdded = MIN( iDamage, maxHealthToAdd );
+ pOwner->TakeHealth( nHealthAdded, DMG_GENERIC );
+ }
+
+ // If I'm a sapper, I store off the energy
+ if ( pOwner->IsClass( TFCLASS_SAPPER ) )
+ {
+ ((CPlayerClassSapper*)pOwner->GetPlayerClass())->AddDrainedEnergy( iDamage );
+ }
+ }
+ }
+ }
+
+ // Tell the client who we're trying to drain.
+ m_bDraining = true;
+ m_vFireTarget = pTarget->WorldSpaceCenter();
+ m_hDrainTarget = pTarget;
+ }
+ else
+ {
+ RemoveDrainTarget();
+ }
+#endif
+
+ // Fire continuously.
+ m_flNextPrimaryAttack = gpGlobals->curtime;
+
+ CheckRemoveDisguise();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CWeaponDrainBeam::PlayAttackAnimation( int activity )
+{
+ SendWeaponAnim( activity );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CWeaponDrainBeam::WeaponIdle( void )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CWeaponDrainBeam::GainedNewTechnology( CBaseTechnology *pTechnology )
+{
+}
+
+#if defined( CLIENT_DLL )
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CWeaponDrainBeam::StopDrainSound( bool bStopHealingSound, bool bStopNoTargetSound )
+{
+ if ( bStopHealingSound )
+ StopSound( entindex(), "WeaponRepairGun.Healing" );
+
+ if ( bStopNoTargetSound )
+ StopSound( entindex(), "WeaponRepairGun.NoTarget" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CWeaponDrainBeam::OnDataChanged( DataUpdateType_t updateType )
+{
+ BaseClass::OnDataChanged( updateType );
+
+ if ( updateType == DATA_UPDATE_CREATED )
+ {
+ m_pEmitter = CSimpleEmitter::Create( "C_WeaponDrainBeam" );
+ m_hParticleMaterial = m_pEmitter->GetPMaterial( "sprites/chargeball" );
+
+ ClientThinkList()->SetNextClientThink( GetClientHandle(), CLIENT_THINK_ALWAYS );
+ }
+
+ // Think?
+ if ( !m_bDraining )
+ {
+ m_bPlayingSound = false;
+ StopDrainSound( true, false );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CWeaponDrainBeam::ClientThink()
+{
+ C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer();
+ if ( !pPlayer )
+ return;
+
+ // Don't show it while the player is dead. Ideally, we'd respond to m_bDraining in OnDataChanged,
+ // but it stops sending the weapon when it's holstered, and it gets holstered when the player dies.
+ C_BasePlayer *pFiringPlayer = dynamic_cast< C_BasePlayer* >( GetOwner() );
+ if ( !pFiringPlayer || pFiringPlayer->IsPlayerDead() )
+ {
+ m_bPlayingSound = false;
+ StopDrainSound();
+ return;
+ }
+
+ // Abort if we're not draining, or the player doesn't have the fire button down
+ if ( !m_bDraining && !m_bAttacking )
+ return;
+
+ // Ready for particle emission, sir
+ m_pEmitter->SetSortOrigin( (m_vFireTarget + pPlayer->GetAbsOrigin()) * 0.5f );
+ float flCur = gpGlobals->frametime;
+ Vector vForward, vOrigin;
+ QAngle vAngles;
+ GetShootPosition( vOrigin, vAngles );
+ AngleVectors( vAngles, &vForward );
+
+ // Are they holding the attack button but not draining anyone? Give feedback.
+ if ( !m_bDraining )
+ {
+ // Add random short-lived particles from the gun tip to the target.
+ while ( m_DribbleParticleEvent.NextEvent( flCur ) )
+ {
+ Vector vPos = vOrigin;
+ vPos += RandomVector( -2, 2 );
+ SimpleParticle *pParticle = m_pEmitter->AddSimpleParticle( m_hParticleMaterial, vPos );
+ if ( pParticle )
+ {
+ // Move the points along the path.
+ pParticle->m_vecVelocity = vForward;
+ pParticle->m_vecVelocity *= 50;
+
+ pParticle->m_flRoll = 0;
+ pParticle->m_flRollDelta = 0;
+ pParticle->m_flDieTime = 0.4f;
+ pParticle->m_flLifetime = 0;
+ pParticle->m_uchColor[0] = 255;
+ pParticle->m_uchColor[1] = 255;
+ pParticle->m_uchColor[2] = 255;
+ pParticle->m_uchStartAlpha = 32;
+ pParticle->m_uchEndAlpha = 0;
+ pParticle->m_uchStartSize = 4;
+ pParticle->m_uchEndSize = 0;
+ pParticle->m_iFlags = 0;
+ }
+ }
+ return;
+ }
+
+ if ( !m_bPlayingSound )
+ {
+ m_bPlayingSound = true;
+ CLocalPlayerFilter filter;
+
+ EmitSound( filter, entindex(), "WeaponRepairGun.Healing" );
+ }
+
+ Vector points[NUM_DRAINBEAM_PATH_POINTS];
+
+ // First generate a sequence of points so we can parameterize the (curvy) path from the
+ // tip of the gun to the target.
+ Vector vDirTo = m_vFireTarget - vOrigin;
+ float flDistanceTo = vDirTo.Length();
+ vDirTo /= flDistanceTo;
+
+ float flBendDist = flDistanceTo * 3;
+ const Vector A = vOrigin - vForward * flBendDist;
+ const Vector &B = vOrigin;
+ const Vector &C = m_vFireTarget;
+ const Vector D = m_vFireTarget - vForward * flBendDist;
+
+ for ( int i=0; i < NUM_DRAINBEAM_PATH_POINTS; i++ )
+ {
+ Catmull_Rom_Spline( A, B, C, D, (float)i / (NUM_DRAINBEAM_PATH_POINTS-1), points[i] );
+ }
+
+ // Add random short-lived particles from the gun tip to the target.
+ while ( m_PathParticleEvent.NextEvent( flCur ) )
+ {
+ float t = RandomFloat( 0, 1 );
+ int iPrev = (int)( t * (NUM_DRAINBEAM_PATH_POINTS - 1.001) );
+ float tPrev = (float)iPrev / (NUM_DRAINBEAM_PATH_POINTS - 1);
+ float tNext = (float)(iPrev+1) / (NUM_DRAINBEAM_PATH_POINTS - 1);
+ Assert( tNext <= NUM_DRAINBEAM_PATH_POINTS-1 );
+
+ Vector vPos;
+ VectorLerp( points[iPrev], points[iPrev+1], (t-tPrev) / (tNext - tPrev), vPos );
+ vPos += RandomVector( -2, 2 );
+
+ SimpleParticle *pParticle = m_pEmitter->AddSimpleParticle( m_hParticleMaterial, vPos );
+ if ( pParticle )
+ {
+ // Move the points along the path.
+ pParticle->m_vecVelocity = points[iPrev+1] - points[iPrev];
+ VectorNormalize( pParticle->m_vecVelocity );
+ pParticle->m_vecVelocity *= -PARTICLE_PATH_VEL;
+
+ pParticle->m_flRoll = 0;
+ pParticle->m_flRollDelta = 0;
+ pParticle->m_flDieTime = 0.2f;
+ pParticle->m_flLifetime = 0;
+ pParticle->m_uchColor[0] = 255;
+ pParticle->m_uchColor[1] = 255;
+ pParticle->m_uchColor[2] = 255;
+ pParticle->m_uchStartAlpha = 32;
+ pParticle->m_uchEndAlpha = 0;
+ pParticle->m_uchStartSize = 4;
+ pParticle->m_uchEndSize = 2;
+ pParticle->m_iFlags = 0;
+ }
+ }
+}
+#endif