diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /game/shared/tf2/weapon_drainbeam.cpp | |
| download | archived-source-engine-2018-hl2-src-master.tar.xz archived-source-engine-2018-hl2-src-master.zip | |
Diffstat (limited to 'game/shared/tf2/weapon_drainbeam.cpp')
| -rw-r--r-- | game/shared/tf2/weapon_drainbeam.cpp | 523 |
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 |