You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
523 lines
16 KiB
523 lines
16 KiB
//========= 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
|
|
|