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.
537 lines
15 KiB
537 lines
15 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
//============================================================================= |
|
|
|
#include "cbase.h" |
|
#include "tf_weapon_mechanical_arm.h" |
|
#include "in_buttons.h" |
|
|
|
#if !defined( CLIENT_DLL ) |
|
#include "tf_player.h" |
|
#include "tf_gamestats.h" |
|
#include "ilagcompensationmanager.h" |
|
#include "particle_parse.h" |
|
#include "tf_fx.h" |
|
#include "tf_weapon_grenade_pipebomb.h" |
|
#include "tf_team.h" |
|
#include "tf_passtime_logic.h" |
|
#else |
|
#include "c_tf_player.h" |
|
#endif |
|
|
|
|
|
//============================================================================= |
|
// |
|
// tables. |
|
// |
|
|
|
IMPLEMENT_NETWORKCLASS_ALIASED( TFMechanicalArm, DT_TFMechanicalArm ) |
|
|
|
BEGIN_NETWORK_TABLE( CTFMechanicalArm, DT_TFMechanicalArm ) |
|
END_NETWORK_TABLE() |
|
|
|
BEGIN_PREDICTION_DATA( CTFMechanicalArm ) |
|
END_PREDICTION_DATA() |
|
|
|
LINK_ENTITY_TO_CLASS( tf_weapon_mechanical_arm, CTFMechanicalArm ); |
|
PRECACHE_WEAPON_REGISTER( tf_weapon_mechanical_arm ); |
|
|
|
#define AMMO_BASE_PROJECTILE_SHOCK 10 |
|
#define AMMO_PER_PROJECTILE_SHOCK 5 |
|
|
|
//============================================================================= |
|
// |
|
// CTFMechanicalArm |
|
// |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
CTFMechanicalArm::CTFMechanicalArm() |
|
{ |
|
#ifdef CLIENT_DLL |
|
m_pParticleBeamEffect = NULL; |
|
m_pParticleBeamSpark = NULL; |
|
m_pEffectOwner = NULL; |
|
#endif // CLIENT_DLL |
|
} |
|
|
|
CTFMechanicalArm::~CTFMechanicalArm() |
|
{ |
|
#ifdef CLIENT_DLL |
|
if ( m_pEffectOwner ) |
|
{ |
|
if ( m_pParticleBeamEffect ) |
|
{ |
|
m_pEffectOwner->ParticleProp()->StopEmissionAndDestroyImmediately( m_pParticleBeamEffect ); |
|
m_pParticleBeamEffect = NULL; |
|
} |
|
|
|
if ( m_pParticleBeamSpark ) |
|
{ |
|
m_pEffectOwner->ParticleProp()->StopEmissionAndDestroyImmediately( m_pParticleBeamSpark ); |
|
m_pParticleBeamSpark = NULL; |
|
} |
|
|
|
m_pEffectOwner = NULL; |
|
} |
|
#endif // CLIENT_DLL |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CTFMechanicalArm::Precache() |
|
{ |
|
BaseClass::Precache(); |
|
|
|
PrecacheParticleSystem( "dxhr_arm_muzzleflash" ); |
|
PrecacheParticleSystem( "dxhr_arm_muzzleflash2" ); |
|
PrecacheParticleSystem( "dxhr_arm_impact" ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool CTFMechanicalArm::ShockAttack( void ) |
|
{ |
|
CTFPlayer *pOwner = ToTFPlayer( GetPlayerOwner() ); |
|
if ( !pOwner ) |
|
return false; |
|
|
|
if ( pOwner->GetWaterLevel() == WL_Eyes ) |
|
return false; |
|
|
|
// Enough ammo to shock at least one target? |
|
if ( pOwner->GetAmmoCount( m_iPrimaryAmmoType ) < ( AMMO_BASE_PROJECTILE_SHOCK + AMMO_PER_PROJECTILE_SHOCK ) ) |
|
return false; |
|
|
|
#ifdef GAME_DLL |
|
if ( pOwner->m_Shared.IsStealthed() ) |
|
{ |
|
pOwner->RemoveInvisibility(); |
|
} |
|
|
|
lagcompensation->StartLagCompensation( pOwner, pOwner->GetCurrentCommand() ); |
|
|
|
Vector vecEye = pOwner->EyePosition(); |
|
Vector vecForward, vecRight, vecUp; |
|
AngleVectors( pOwner->EyeAngles(), &vecForward, &vecRight, &vecUp ); |
|
Vector vecSize = Vector( 128, 128, 64 ); |
|
float flMaxElement = 0.0f; |
|
for ( int i = 0; i < 3; ++i ) |
|
{ |
|
flMaxElement = MAX( flMaxElement, vecSize[i] ); |
|
} |
|
Vector vecCenter = vecEye + vecForward * flMaxElement; |
|
|
|
CUtlVector< CBaseEntity* > vecShockTargets; |
|
|
|
// Get a list of entities in the box defined by vecSize at VecCenter. |
|
// We will then try to deflect everything in the box. |
|
const int maxCollectedEntities = 64; |
|
CBaseEntity *pObjects[ maxCollectedEntities ]; |
|
int count = UTIL_EntitiesInBox( pObjects, maxCollectedEntities, vecCenter - vecSize, vecCenter + vecSize, FL_GRENADE | FL_CLIENT | FL_FAKECLIENT ); |
|
for ( int i = 0; i < count; i++ ) |
|
{ |
|
if ( IsValidVictim( pOwner, pObjects[i] ) ) |
|
{ |
|
vecShockTargets.AddToTail( pObjects[i] ); |
|
} |
|
} |
|
|
|
// Remove the base cost for attempting to fire, regardless of what we hit |
|
pOwner->RemoveAmmo( AMMO_BASE_PROJECTILE_SHOCK, m_iPrimaryAmmoType ); |
|
|
|
// We attacked by didn't hit anything |
|
if ( vecShockTargets.Count() == 0 ) |
|
{ |
|
lagcompensation->FinishLagCompensation( pOwner ); |
|
return true; |
|
} |
|
|
|
// Horribly hacked muzzle position |
|
Vector vForward, vRight, vUp; |
|
AngleVectors( pOwner->EyeAngles(), &vForward, &vRight, &vUp ); |
|
|
|
Vector vStart = pOwner->EyePosition() |
|
+ (vForward * 40.0f) |
|
+ (vRight * 15.0f) |
|
+ (vUp * -10.0f); |
|
|
|
FOR_EACH_VEC( vecShockTargets, i ) |
|
{ |
|
if ( pOwner->GetAmmoCount( m_iPrimaryAmmoType ) < AMMO_PER_PROJECTILE_SHOCK ) |
|
break; |
|
|
|
CBaseEntity* pTarget = vecShockTargets[i]; |
|
Vector vecTarget = pTarget->WorldSpaceCenter(); |
|
|
|
ShockVictim( pOwner, pTarget ); |
|
pOwner->RemoveAmmo( AMMO_PER_PROJECTILE_SHOCK, m_iPrimaryAmmoType ); |
|
|
|
// Play an effect where the target is |
|
CPVSFilter filter( vecTarget ); |
|
const char *shootsound = GetShootSound( SPECIAL3 ); |
|
if ( shootsound && *shootsound ) |
|
{ |
|
EmitSound( shootsound ); |
|
} |
|
|
|
// play the electrical effect from the gun to the pipes |
|
te_tf_particle_effects_control_point_t controlPoint = { PATTACH_WORLDORIGIN, vecTarget }; |
|
TE_TFParticleEffectComplex( filter, 0.0f, "dxhr_arm_muzzleflash", vStart, QAngle( 0, 0, 0 ), NULL, &controlPoint, pTarget, PATTACH_CUSTOMORIGIN ); |
|
} |
|
|
|
lagcompensation->FinishLagCompensation( pOwner ); |
|
#endif |
|
|
|
return true; |
|
} |
|
|
|
#ifdef GAME_DLL |
|
//----------------------------------------------------------------------------- |
|
bool CTFMechanicalArm::IsValidVictim( CTFPlayer *pOwner, CBaseEntity *pTarget ) |
|
{ |
|
if ( pTarget == NULL ) |
|
return false; |
|
|
|
if ( pTarget == pOwner ) |
|
return false; |
|
|
|
if ( pTarget->IsPlayer() && pTarget->GetTeamNumber() == TEAM_SPECTATOR ) |
|
return false; |
|
|
|
if ( pTarget->GetTeamNumber() == pOwner->GetTeamNumber() ) |
|
return false; |
|
|
|
if ( pTarget->IsPlayer() && !pTarget->IsAlive() ) |
|
return false; |
|
|
|
if ( !pTarget->IsDeflectable() && !FClassnameIs( pTarget, "prop_physics" ) ) |
|
return false; |
|
|
|
if ( pOwner->FVisible( pTarget, MASK_SOLID_BRUSHONLY ) == false ) |
|
return false; |
|
|
|
if ( g_pPasstimeLogic && ( g_pPasstimeLogic->GetBall() == pTarget ) ) |
|
return false; |
|
|
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void CTFMechanicalArm::ShockVictim( CTFPlayer *pOwner, CBaseEntity *pTarget ) |
|
{ |
|
// Projectile |
|
if ( !pTarget->IsPlayer() ) |
|
{ |
|
pTarget->SetThink( &BaseClass::SUB_Remove ); |
|
pTarget->SetNextThink( gpGlobals->curtime ); |
|
pTarget->SetTouch( NULL ); |
|
pTarget->AddEffects( EF_NODRAW ); |
|
pTarget->RemoveFlag( FL_GRENADE ); |
|
} |
|
|
|
// deal damage |
|
CTakeDamageInfo info; |
|
info.SetDamageType( DMG_SHOCK ); |
|
info.SetAttacker( pOwner ); |
|
info.SetInflictor( this ); |
|
info.SetWeapon( this ); |
|
info.SetDamage( 20 ); |
|
info.SetDamagePosition( pTarget->WorldSpaceCenter() ); |
|
pTarget->TakeDamage( info ); |
|
|
|
// Achievement |
|
CTFGrenadePipebombProjectile *pPipebomb = dynamic_cast<CTFGrenadePipebombProjectile*>( pTarget ); |
|
if ( pPipebomb && pPipebomb->HasStickyEffects() ) |
|
{ |
|
// If we are near a building, award achievement progress. |
|
CTFTeam *pTeam = pOwner->GetTFTeam(); |
|
if ( pTeam ) |
|
{ |
|
for ( int j = 0; j < pTeam->GetNumObjects(); j++ ) |
|
{ |
|
CBaseObject *pTemp = pTeam->GetObject( j ); |
|
if ( pTemp && ( pTemp->ObjectType() != OBJ_ATTACHMENT_SAPPER ) ) |
|
{ |
|
if ( ( pTemp->GetAbsOrigin().DistTo( pPipebomb->GetAbsOrigin() ) < 100 ) && |
|
( pTemp->FVisible( pPipebomb, MASK_SOLID_BRUSHONLY ) ) ) |
|
{ |
|
pOwner->AwardAchievement( ACHIEVEMENT_TF_ENGINEER_DESTROY_STICKIES, 1 ); |
|
break; // Only one award per sticky. |
|
} |
|
} |
|
} |
|
} |
|
} |
|
} |
|
#endif |
|
|
|
//----------------------------------------------------------------------------- |
|
void CTFMechanicalArm::SecondaryAttack( void ) |
|
{ |
|
CTFPlayer *pOwner = ToTFPlayer( GetPlayerOwner() ); |
|
if ( !pOwner ) |
|
return; |
|
|
|
// Are we capable of firing again? |
|
if ( m_flNextSecondaryAttack > gpGlobals->curtime ) |
|
return; |
|
|
|
if ( m_iPrimaryAmmoType == TF_AMMO_METAL ) |
|
{ |
|
if ( ( GetAmmoPerShot() > GetOwner()->GetAmmoCount( m_iPrimaryAmmoType ) ) || |
|
( pOwner->GetWaterLevel() == WL_Eyes ) ) |
|
{ |
|
WeaponSound( EMPTY ); |
|
m_flNextSecondaryAttack = gpGlobals->curtime + 0.67f; |
|
return; |
|
} |
|
} |
|
|
|
if ( !CanAttack() ) |
|
return; |
|
|
|
if ( ShockAttack() ) |
|
{ |
|
WeaponSound( SPECIAL3 ); |
|
} |
|
else |
|
{ |
|
WeaponSound( EMPTY ); |
|
} |
|
|
|
#ifdef CLIENT_DLL |
|
// Play an effect on the client so they have some kind of visual feedback that something happened |
|
int iParticleAttachment = LookupAttachment( "muzzle" ); |
|
CNewParticleEffect* pEffect = ParticleProp()->Create( "dxhr_sniper_fizzle", PATTACH_POINT_FOLLOW, iParticleAttachment ); |
|
ParticleProp()->AddControlPoint( pEffect, 1, this, PATTACH_POINT_FOLLOW, "muzzle" ); |
|
#endif |
|
|
|
SendWeaponAnim( ACT_VM_PRIMARYATTACK ); |
|
|
|
pOwner->SetAnimation( PLAYER_ATTACK1 ); |
|
|
|
m_flNextPrimaryAttack = gpGlobals->curtime + 0.67f; |
|
m_flNextSecondaryAttack = gpGlobals->curtime + 0.67f; |
|
} |
|
|
|
#ifdef CLIENT_DLL |
|
void CTFMechanicalArm::OnDataChanged( DataUpdateType_t updateType ) |
|
{ |
|
BaseClass::OnDataChanged( updateType ); |
|
|
|
UpdateParticleBeam(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void CTFMechanicalArm::StopParticleBeam( void ) |
|
{ |
|
if ( !m_pEffectOwner ) |
|
return; |
|
|
|
// Different owners, kill the old effect |
|
if ( m_pParticleBeamEffect ) |
|
{ |
|
m_pEffectOwner->ParticleProp()->StopEmissionAndDestroyImmediately( m_pParticleBeamEffect ); |
|
m_pParticleBeamEffect = NULL; |
|
} |
|
|
|
if ( m_pParticleBeamSpark ) |
|
{ |
|
m_pEffectOwner->ParticleProp()->StopEmissionAndDestroyImmediately( m_pParticleBeamSpark ); |
|
m_pParticleBeamSpark = NULL; |
|
} |
|
|
|
m_pEffectOwner = NULL; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void CTFMechanicalArm::UpdateParticleBeam() |
|
{ |
|
// Update Particle |
|
// If we are attacking, update the particle (make it render) |
|
CTFPlayer *pFiringPlayer = ToTFPlayer( GetOwnerEntity() ); |
|
if ( !pFiringPlayer ) |
|
{ |
|
StopParticleBeam(); |
|
return; |
|
} |
|
|
|
C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer(); |
|
C_BaseEntity *pEffectOwner = this; |
|
if ( pLocalPlayer == pFiringPlayer ) |
|
{ |
|
pEffectOwner = pLocalPlayer->GetRenderedWeaponModel(); |
|
if ( !pEffectOwner ) |
|
{ |
|
StopParticleBeam(); |
|
return; |
|
} |
|
} |
|
|
|
if ( m_pEffectOwner && m_pEffectOwner != pEffectOwner ) |
|
{ |
|
StopParticleBeam(); |
|
return; |
|
} |
|
|
|
if ( m_flNextSecondaryAttack > gpGlobals->curtime ) |
|
{ |
|
StopParticleBeam(); |
|
return; |
|
} |
|
|
|
m_pEffectOwner = pEffectOwner; |
|
|
|
// Constantly perform the shock attack and update control points if attack is down and we've already fired |
|
if ( pFiringPlayer |
|
&& pFiringPlayer->m_nButtons & IN_ATTACK |
|
&& pFiringPlayer->GetActiveWeapon() == this |
|
&& pFiringPlayer->GetWaterLevel() != WL_Eyes |
|
&& pFiringPlayer->m_flNextAttack < gpGlobals->curtime ) |
|
{ |
|
trace_t tr; |
|
Vector vecAiming; |
|
pFiringPlayer->EyeVectors( &vecAiming ); |
|
Vector vecEnd = pFiringPlayer->EyePosition() + vecAiming * 256.0f; |
|
UTIL_TraceLine( pFiringPlayer->EyePosition(), vecEnd, ( MASK_SHOT & ~CONTENTS_HITBOX ), pFiringPlayer, DMG_GENERIC, &tr ); |
|
|
|
// Line laser |
|
if ( !m_pParticleBeamEffect ) |
|
{ |
|
const char *pszEffectName = "dxhr_arm_muzzleflash2"; |
|
m_pParticleBeamEffect = pEffectOwner->ParticleProp()->Create( pszEffectName, PATTACH_POINT_FOLLOW, "muzzle" ); |
|
} |
|
if ( m_pParticleBeamEffect ) |
|
{ |
|
Vector vEndPos = tr.endpos - pFiringPlayer->GetAbsOrigin(); |
|
pEffectOwner->ParticleProp()->AddControlPoint( m_pParticleBeamEffect, 1, pFiringPlayer, PATTACH_ABSORIGIN_FOLLOW, NULL, vEndPos ); |
|
} |
|
|
|
// Spark |
|
if ( !m_pParticleBeamSpark && tr.m_pEnt && tr.m_pEnt->IsPlayer( ) ) |
|
{ |
|
m_pParticleBeamSpark = pEffectOwner->ParticleProp( )->Create( "dxhr_arm_impact", PATTACH_ABSORIGIN_FOLLOW ); |
|
} |
|
else if ( m_pParticleBeamSpark && (!tr.m_pEnt || !tr.m_pEnt->IsPlayer() ) ) |
|
{ |
|
m_pEffectOwner->ParticleProp()->StopEmissionAndDestroyImmediately( m_pParticleBeamSpark ); |
|
m_pParticleBeamSpark = NULL; |
|
} |
|
|
|
if ( m_pParticleBeamSpark ) |
|
{ |
|
Vector vEndPos = tr.endpos - pFiringPlayer->GetAbsOrigin(); |
|
pEffectOwner->ParticleProp( )->AddControlPoint( m_pParticleBeamSpark, 1, pFiringPlayer, PATTACH_ABSORIGIN_FOLLOW, NULL, vEndPos ); |
|
} |
|
} |
|
else if ( m_pEffectOwner ) |
|
{ |
|
StopParticleBeam( ); |
|
} |
|
} |
|
#endif // CLIENT_DLL |
|
|
|
//----------------------------------------------------------------------------- |
|
void CTFMechanicalArm::PrimaryAttack() |
|
{ |
|
CTFPlayer *pOwner = ToTFPlayer( GetPlayerOwner() ); |
|
if ( !pOwner ) |
|
return; |
|
|
|
float flFireDelay = ApplyFireDelay( m_pWeaponInfo->GetWeaponData( m_iWeaponMode ).m_flTimeFireDelay ); |
|
|
|
if ( m_iPrimaryAmmoType == TF_AMMO_METAL ) |
|
{ |
|
if ( ( GetAmmoPerShot() > GetOwner()->GetAmmoCount( m_iPrimaryAmmoType ) ) || |
|
( pOwner->GetWaterLevel( ) == WL_Eyes ) ) |
|
{ |
|
WeaponSound( EMPTY ); |
|
m_flNextPrimaryAttack = gpGlobals->curtime + flFireDelay; |
|
return; |
|
} |
|
} |
|
|
|
// Are we capable of firing again? |
|
if ( m_flNextPrimaryAttack > gpGlobals->curtime ) |
|
return; |
|
|
|
// Get the player owning the weapon. |
|
CTFPlayer *pPlayer = ToTFPlayer( GetPlayerOwner() ); |
|
if ( !pPlayer ) |
|
return; |
|
|
|
if ( !CanAttack() ) |
|
return; |
|
|
|
#ifdef GAME_DLL |
|
CTF_GameStats.Event_PlayerFiredWeapon( pPlayer, false ); |
|
|
|
int iAmmoPerShot = 0; |
|
CALL_ATTRIB_HOOK_INT( iAmmoPerShot, mod_ammo_per_shot ); |
|
pOwner->RemoveAmmo( iAmmoPerShot, m_iPrimaryAmmoType ); |
|
|
|
//int nAmmoToTake = bShocked ? 0 : GetAmmoPerShot(); |
|
//pOwner->RemoveAmmo( nAmmoToTake, m_iPrimaryAmmoType ); |
|
|
|
FireProjectile( pPlayer ); |
|
#endif |
|
|
|
#ifdef CLIENT_DLL |
|
// Play an effect on the client so they have some kind of visual feedback that something happened |
|
int iParticleAttachment = LookupAttachment( "muzzle" ); |
|
CNewParticleEffect* pEffect = ParticleProp()->Create( "dxhr_sniper_fizzle", PATTACH_POINT_FOLLOW, iParticleAttachment ); |
|
ParticleProp()->AddControlPoint( pEffect, 1, this, PATTACH_POINT_FOLLOW, "muzzle" ); |
|
#endif |
|
|
|
WeaponSound( SINGLE ); |
|
|
|
// Set the weapon mode. |
|
m_iWeaponMode = TF_WEAPON_PRIMARY_MODE; |
|
|
|
SendWeaponAnim( ACT_VM_PRIMARYATTACK ); |
|
|
|
pPlayer->SetAnimation( PLAYER_ATTACK1 ); |
|
|
|
// Set next attack times. |
|
m_flNextPrimaryAttack = gpGlobals->curtime + flFireDelay; |
|
|
|
SetWeaponIdleTime( gpGlobals->curtime + SequenceDuration() ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
int CTFMechanicalArm::GetAmmoPerShot( void ) |
|
{ |
|
// Used by normal fire code, we only decrement ammo on ticks which uses an Attr |
|
return 0; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool CTFMechanicalArm::UpdateBodygroups( CBaseCombatCharacter* pOwner, int iState ) |
|
{ |
|
if ( !pOwner ) |
|
return false; |
|
|
|
iState = pOwner->GetActiveWeapon() == this; |
|
|
|
bool res = BaseClass::UpdateBodygroups( pOwner, iState ); |
|
|
|
CTFPlayer *pTFOwner = ToTFPlayer( pOwner ); |
|
if ( pTFOwner ) |
|
{ |
|
CBaseViewModel *pVM = pTFOwner->GetViewModel(); |
|
if ( pVM ) |
|
{ |
|
pVM->SetBodygroup( 1, iState ); |
|
} |
|
} |
|
|
|
return res; |
|
} |