mirror of
https://github.com/alliedmodders/hl2sdk.git
synced 2025-12-12 16:48:28 +00:00
939 lines
27 KiB
C++
939 lines
27 KiB
C++
//========= 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"
|
|
#include "tf_gamerules.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_PER_PROJECTILE_SHOCK 5
|
|
|
|
const float tf_mecharm_orb_size = 100.f;
|
|
const float tf_mecharm_orb_speed = 700.f;
|
|
const int tf_mecharm_orb_cost = 65;
|
|
const int tf_mecharm_orb_zap_targets = 2;
|
|
const int tf_mecharm_orb_zap_damage = 15;
|
|
const float tf_mecharm_orb_lifetime = 1.2f;
|
|
|
|
|
|
//=============================================================================
|
|
//
|
|
// 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" );
|
|
PrecacheScriptSound( "Weapon_Upgrade.ExplosiveHeadshot" );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// 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 ) < tf_mecharm_orb_cost )
|
|
return false;
|
|
|
|
#ifdef GAME_DLL
|
|
if ( pOwner->m_Shared.IsStealthed() )
|
|
{
|
|
pOwner->RemoveInvisibility();
|
|
}
|
|
|
|
// Remove the base cost for attempting to fire, regardless of what we hit
|
|
pOwner->RemoveAmmo( tf_mecharm_orb_cost, m_iPrimaryAmmoType );
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
#ifdef GAME_DLL
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
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;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
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
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
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 );
|
|
#ifdef GAME_DLL
|
|
Vector vecForward, vecRight, vecUp;
|
|
AngleVectors( pOwner->EyeAngles(), &vecForward, &vecRight, &vecUp );
|
|
|
|
float fRight = 8.f;
|
|
if ( IsViewModelFlipped() )
|
|
{
|
|
fRight *= -1;
|
|
}
|
|
// Vector vecSrc = pOwner->Weapon_ShootPosition();
|
|
// vecSrc = vecSrc + ( vecUp * -9.0f ) + ( vecRight * 7.0f ) + ( vecForward * 3.0f );
|
|
Vector vecSrc = pOwner->EyePosition()
|
|
+ ( vecForward * 40.f )
|
|
+ ( vecRight * 15.f )
|
|
+ ( vecUp * -10.f );
|
|
|
|
QAngle angForward = pOwner->EyeAngles();
|
|
|
|
trace_t trace;
|
|
Vector vecEye = pOwner->EyePosition();
|
|
CTraceFilterSimple traceFilter( this, COLLISION_GROUP_PROJECTILE );
|
|
UTIL_TraceHull( vecEye, vecSrc, -Vector( 8.f, 8.f, 8.f ), Vector( 8.f, 8.f, 8.f ), MASK_SOLID_BRUSHONLY, &traceFilter, &trace );
|
|
if ( !trace.DidHit() )
|
|
{
|
|
CTFProjectile_MechanicalArmOrb *pOrb = static_cast< CTFProjectile_MechanicalArmOrb* >( CBaseEntity::CreateNoSpawn( "tf_projectile_mechanicalarmorb", vecSrc, angForward, pOwner ) );
|
|
if ( pOrb )
|
|
{
|
|
pOrb->SetOwnerEntity( pOwner );
|
|
pOrb->SetLauncher( this );
|
|
|
|
Vector vForward;
|
|
AngleVectors( angForward, &vForward, NULL, NULL );
|
|
|
|
pOrb->SetAbsVelocity( vForward * tf_mecharm_orb_speed );
|
|
|
|
pOrb->ChangeTeam( pOwner->GetTeamNumber() );
|
|
pOrb->SetCritical( false );
|
|
|
|
DispatchSpawn( pOrb );
|
|
}
|
|
}
|
|
#endif // GAME_DLL
|
|
}
|
|
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();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
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;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
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
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
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() );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
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;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
IMPLEMENT_NETWORKCLASS_ALIASED( TFProjectile_MechanicalArmOrb, DT_TFProjectile_MechanicalArmOrb )
|
|
BEGIN_NETWORK_TABLE( CTFProjectile_MechanicalArmOrb, DT_TFProjectile_MechanicalArmOrb )
|
|
END_NETWORK_TABLE()
|
|
|
|
LINK_ENTITY_TO_CLASS( tf_projectile_mechanicalarmorb, CTFProjectile_MechanicalArmOrb );
|
|
PRECACHE_WEAPON_REGISTER( tf_projectile_mechanicalarmorb );
|
|
|
|
const char *pszParticleRed = "dxhr_lightningball_parent_red";
|
|
const char *pszParticleBlue = "dxhr_lightningball_parent_blue";
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
CTFProjectile_MechanicalArmOrb::CTFProjectile_MechanicalArmOrb()
|
|
{
|
|
#ifdef CLIENT_DLL
|
|
m_pTrailParticle = NULL;
|
|
#endif // CLIENT_DLL
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
CTFProjectile_MechanicalArmOrb::~CTFProjectile_MechanicalArmOrb()
|
|
{
|
|
#ifdef CLIENT_DLL
|
|
if ( m_pTrailParticle )
|
|
{
|
|
ParticleProp()->StopEmissionAndDestroyImmediately( m_pTrailParticle );
|
|
m_pTrailParticle = NULL;
|
|
}
|
|
#endif // CLIENT_DLL
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CTFProjectile_MechanicalArmOrb::Precache()
|
|
{
|
|
BaseClass::Precache();
|
|
|
|
PrecacheModel( "models/weapons/w_models/w_drg_ball.mdl" );
|
|
PrecacheParticleSystem( pszParticleRed );
|
|
PrecacheParticleSystem( pszParticleBlue );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CTFProjectile_MechanicalArmOrb::Spawn()
|
|
{
|
|
BaseClass::Spawn();
|
|
|
|
SetModel( "models/weapons/w_models/w_drg_ball.mdl" );
|
|
|
|
#ifdef GAME_DLL
|
|
SetSolid( SOLID_BBOX );
|
|
SetMoveType( MOVETYPE_FLY, MOVECOLLIDE_FLY_CUSTOM );
|
|
SetSolidFlags( FSOLID_TRIGGER | FSOLID_NOT_SOLID );
|
|
SetRenderMode( kRenderTransAlpha );
|
|
SetRenderColorA( 1 );
|
|
CollisionProp()->SetCollisionBounds( Vector( -1.f, -1.f, -1.f ), Vector( 1.f, 1.f, 1.f ) );
|
|
SetCollisionGroup( TFCOLLISION_GROUP_ROCKET_BUT_NOT_WITH_OTHER_ROCKETS );
|
|
|
|
AddEFlags( EFL_NO_WATER_VELOCITY_CHANGE );
|
|
AddEffects( EF_NOSHADOW );
|
|
SetGravity( 0.f );
|
|
SetTouch( &CTFBaseRocket::RocketTouch );
|
|
AddFlag( FL_GRENADE );
|
|
|
|
SetContextThink( &CTFProjectile_MechanicalArmOrb::OrbThink, gpGlobals->curtime, "OrbThink" );
|
|
SetContextThink( &CTFProjectile_MechanicalArmOrb::ExplodeAndRemove, gpGlobals->curtime + tf_mecharm_orb_lifetime, "ExplodeAndRemoveThink" );
|
|
m_flOrbNextAttackTime = -1.f;
|
|
#endif // GAME_DLL
|
|
}
|
|
|
|
#ifdef GAME_DLL
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
bool CTFProjectile_MechanicalArmOrb::ShouldProjectileIgnore( CBaseEntity *pOther )
|
|
{
|
|
Assert( pOther );
|
|
if ( !pOther )
|
|
return true;
|
|
|
|
if ( pOther->IsWorld() )
|
|
return false;
|
|
|
|
if ( GetOwnerEntity() == pOther )
|
|
return true;
|
|
|
|
if ( !pOther->IsSolid() || pOther->IsSolidFlagSet( FSOLID_VOLUME_CONTENTS ) )
|
|
return true;
|
|
|
|
if ( pOther->GetCollisionGroup() == TFCOLLISION_GROUP_RESPAWNROOMS )
|
|
return true;
|
|
|
|
if ( pOther->IsFuncLOD() )
|
|
return true;
|
|
|
|
const trace_t *pTrace = &CBaseEntity::GetTouchTrace();
|
|
if ( pTrace->surface.flags & CONTENTS_LADDER )
|
|
return true;
|
|
|
|
if ( !ShouldTouchNonWorldSolid( pOther, pTrace ) )
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CTFProjectile_MechanicalArmOrb::RocketTouch( CBaseEntity *pOther )
|
|
{
|
|
if ( pOther->IsPlayer() )
|
|
return;
|
|
|
|
const trace_t *pTrace = &CBaseEntity::GetTouchTrace();
|
|
if ( pTrace->surface.flags & SURF_SKY )
|
|
{
|
|
UTIL_Remove( this );
|
|
return;
|
|
}
|
|
|
|
if ( ShouldProjectileIgnore( pOther ) )
|
|
return;
|
|
|
|
// End if we run into something
|
|
ExplodeAndRemove();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CTFProjectile_MechanicalArmOrb::ExplodeAndRemove( void )
|
|
{
|
|
// Particle
|
|
const char *pszParticleName = ( GetTeamNumber() == TF_TEAM_BLUE ) ? "drg_cow_explosioncore_normal_blue" : "drg_cow_explosioncore_normal";
|
|
|
|
CPVSFilter filter( GetAbsOrigin() );
|
|
TE_TFParticleEffect( filter, 0.f, pszParticleName, GetAbsOrigin(), vec3_angle );
|
|
|
|
EmitSound( filter, entindex(), "Halloween.spell_lightning_impact" );
|
|
|
|
// Go out with a bang
|
|
CheckForPlayers( 16 );
|
|
|
|
#ifdef CLIENT_DLL
|
|
if ( m_pTrailParticle )
|
|
{
|
|
ParticleProp()->StopEmissionAndDestroyImmediately( m_pTrailParticle );
|
|
m_pTrailParticle = NULL;
|
|
}
|
|
#endif // CLIENT_DLL
|
|
|
|
SetContextThink( &CBaseGrenade::SUB_Remove, gpGlobals->curtime, "RemoveThink" );
|
|
return;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CTFProjectile_MechanicalArmOrb::ZapPlayer( const CTakeDamageInfo &info, trace_t *pTrace, CTFPlayer *pTFPlayer )
|
|
{
|
|
if ( !pTrace )
|
|
return;
|
|
|
|
if ( !pTFPlayer )
|
|
return;
|
|
|
|
// Shoot a beam at them
|
|
CPVSFilter filter( pTFPlayer->WorldSpaceCenter() );
|
|
Vector vStart = WorldSpaceCenter();
|
|
Vector vEnd = pTFPlayer->EyePosition();
|
|
const char *pszHitEffect = ( GetTeamNumber() == TF_TEAM_BLUE ) ? "dxhr_lightningball_hit_blue" : "dxhr_lightningball_hit_red";
|
|
te_tf_particle_effects_control_point_t controlPoint = { PATTACH_ABSORIGIN, vEnd };
|
|
TE_TFParticleEffectComplex( filter, 0.0f, pszHitEffect, vStart, QAngle( 0, 0, 0 ), NULL, &controlPoint, pTFPlayer, PATTACH_CUSTOMORIGIN );
|
|
|
|
// Hurt 'em.
|
|
Vector dir;
|
|
AngleVectors( GetAbsAngles(), &dir );
|
|
pTFPlayer->DispatchTraceAttack( info, dir, pTrace );
|
|
ApplyMultiDamage();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CTFProjectile_MechanicalArmOrb::CheckForPlayers( int nNumToZap )
|
|
{
|
|
CTFPlayer *pTFOwner = ToTFPlayer( GetOwnerEntity() );
|
|
if ( !pTFOwner )
|
|
return;
|
|
|
|
CTakeDamageInfo info;
|
|
info.SetAttacker( pTFOwner );
|
|
info.SetInflictor( this );
|
|
info.SetWeapon( GetLauncher() );
|
|
info.SetDamage( tf_mecharm_orb_zap_damage );
|
|
info.SetDamageCustom( TF_DMG_CUSTOM_PLASMA );
|
|
info.SetDamagePosition( GetAbsOrigin() );
|
|
info.SetDamageType( DMG_SHOCK );
|
|
|
|
CBaseEntity *pListOfEntities[5];
|
|
int iEntities = UTIL_EntitiesInSphere( pListOfEntities, 5, GetAbsOrigin(), tf_mecharm_orb_size, FL_CLIENT | FL_FAKECLIENT | FL_NPC );
|
|
|
|
// Shuffle the list
|
|
for ( int i = iEntities - 1; i > 0; --i )
|
|
{
|
|
V_swap( pListOfEntities[i], pListOfEntities[RandomInt( 0, i )] );
|
|
}
|
|
|
|
CTraceFilterIgnoreTeammates tracefilter( this, COLLISION_GROUP_NONE, GetTeamNumber() );
|
|
|
|
// Zap as many targets as we're told to, if we can
|
|
int nHits = 0;
|
|
for ( int i = 0; i < iEntities && nHits < nNumToZap; ++i )
|
|
{
|
|
CBaseEntity* pTarget = pListOfEntities[i];
|
|
if ( !pTarget )
|
|
continue;
|
|
|
|
if ( !pTarget->IsAlive() )
|
|
continue;
|
|
|
|
if ( pTFOwner->InSameTeam( pTarget ) )
|
|
continue;
|
|
|
|
if ( !FVisible( pTarget, MASK_OPAQUE ) )
|
|
continue;
|
|
|
|
CTFPlayer *pTFPlayer = ToTFPlayer( pTarget );
|
|
if ( pTFPlayer )
|
|
{
|
|
if ( pTFPlayer->m_Shared.InCond( TF_COND_PHASE ) || pTFPlayer->m_Shared.InCond( TF_COND_PASSTIME_INTERCEPTION ) )
|
|
continue;
|
|
|
|
if ( pTFPlayer->m_Shared.IsInvulnerable() )
|
|
continue;
|
|
}
|
|
|
|
trace_t trace;
|
|
UTIL_TraceLine( GetAbsOrigin(), pTarget->GetAbsOrigin(), ( MASK_SHOT & ~( CONTENTS_HITBOX ) ), &tracefilter, &trace );
|
|
if ( trace.DidHitWorld() )
|
|
continue;
|
|
|
|
ZapPlayer( info, &trace, pTFPlayer );
|
|
++nHits;
|
|
}
|
|
|
|
// We zapped someone. Play a sound
|
|
if ( nHits > 0 )
|
|
{
|
|
EmitSound( "TFPlayer.MedicChargedDeath" );
|
|
|
|
// If the owner is close, zap them too -- to punish shoot-the-floor patterns
|
|
if ( ( pTFOwner->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr() < Square( 80.f ) )
|
|
{
|
|
trace_t trace;
|
|
UTIL_TraceLine( GetAbsOrigin(), pTFOwner->GetAbsOrigin(), ( MASK_SHOT & ~( CONTENTS_HITBOX ) ), &tracefilter, &trace );
|
|
ZapPlayer( info, &trace, pTFOwner );
|
|
}
|
|
}
|
|
|
|
m_flOrbNextAttackTime = gpGlobals->curtime + 0.15f;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CTFProjectile_MechanicalArmOrb::CheckForProjectiles( void )
|
|
{
|
|
const int nMaxEnts = 16;
|
|
const float flRadius = tf_mecharm_orb_size * 1.5f; // Bloat it a little
|
|
|
|
Vector vecPos = GetAbsOrigin();
|
|
CBaseEntity *pObjects[nMaxEnts];
|
|
int nCount = UTIL_EntitiesInSphere( pObjects, nMaxEnts, vecPos, flRadius, FL_GRENADE );
|
|
|
|
//NDebugOverlay::Sphere( vecPos, flRadius, 0, 255, 0, false, 0.35f );
|
|
|
|
CTFPlayer *pTFOwner = ToTFPlayer( GetOwnerEntity() );
|
|
|
|
bool bAnnihilate = false;
|
|
|
|
// Destroy projectiles along the way
|
|
for ( int i = 0; i < nCount; i++ )
|
|
{
|
|
if ( !pObjects[i] )
|
|
continue;
|
|
|
|
if ( pObjects[i] == this )
|
|
continue;
|
|
|
|
if ( pObjects[i]->InSameTeam( this ) )
|
|
continue;
|
|
|
|
if ( pObjects[i]->GetAbsOrigin().DistToSqr( GetAbsOrigin() ) > Square( tf_mecharm_orb_size ) )
|
|
continue;
|
|
|
|
if ( !FVisible( pObjects[i], MASK_SOLID ) )
|
|
continue;
|
|
|
|
CBaseProjectile *pProjectile = dynamic_cast< CBaseProjectile* >( pObjects[i] );
|
|
if ( pProjectile && pProjectile->IsDestroyable( true ) )
|
|
{
|
|
CPVSFilter filter( WorldSpaceCenter() );
|
|
const char *pszHitEffect = ( GetTeamNumber() == TF_TEAM_BLUE ) ? "dxhr_lightningball_hit_blue" : "dxhr_lightningball_hit_red";
|
|
te_tf_particle_effects_control_point_t controlPoint = { PATTACH_ABSORIGIN, pObjects[i]->GetAbsOrigin() };
|
|
TE_TFParticleEffectComplex( filter, 0.0f, pszHitEffect, WorldSpaceCenter(), QAngle( 0, 0, 0 ), NULL, &controlPoint, pProjectile, PATTACH_CUSTOMORIGIN );
|
|
|
|
EmitSound( "Weapon_Upgrade.ExplosiveHeadshot" );
|
|
|
|
// If we touch another orb, then we need to annihilate. Destroy the other orb, and also destroy ourselves
|
|
CTFProjectile_MechanicalArmOrb* pOtherOrb = dynamic_cast< CTFProjectile_MechanicalArmOrb* >( pProjectile );
|
|
if ( pOtherOrb )
|
|
{
|
|
pOtherOrb->ExplodeAndRemove();
|
|
bAnnihilate = true;
|
|
}
|
|
else
|
|
{
|
|
pProjectile->Destroy( true, false );
|
|
}
|
|
|
|
if ( pTFOwner )
|
|
{
|
|
CTF_GameStats.Event_PlayerAwardBonusPoints( pTFOwner, NULL, 2 );
|
|
}
|
|
}
|
|
}
|
|
|
|
// We hit another orb. Destroy ourselves
|
|
if ( bAnnihilate )
|
|
{
|
|
ExplodeAndRemove();
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CTFProjectile_MechanicalArmOrb::OrbThink( void )
|
|
{
|
|
if ( gpGlobals->curtime >= m_flOrbNextAttackTime )
|
|
{
|
|
CheckForPlayers( tf_mecharm_orb_zap_targets );
|
|
}
|
|
|
|
CheckForProjectiles();
|
|
|
|
SetContextThink( &CTFProjectile_MechanicalArmOrb::OrbThink, gpGlobals->curtime + 0.1f, "OrbThink" );
|
|
}
|
|
#endif
|
|
|
|
#ifdef CLIENT_DLL
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CTFProjectile_MechanicalArmOrb::OnDataChanged( DataUpdateType_t updateType )
|
|
{
|
|
BaseClass::OnDataChanged( updateType );
|
|
|
|
if ( updateType == DATA_UPDATE_CREATED )
|
|
{
|
|
m_iTeamNumPrev = GetTeamNumber();
|
|
CreateTrails();
|
|
}
|
|
else if ( updateType == DATA_UPDATE_DATATABLE_CHANGED )
|
|
{
|
|
if ( m_iTeamNumPrev != GetTeamNumber() )
|
|
{
|
|
m_iTeamNumPrev = GetTeamNumber();
|
|
|
|
if ( m_pTrailParticle )
|
|
{
|
|
ParticleProp()->StopEmissionAndDestroyImmediately( m_pTrailParticle );
|
|
m_pTrailParticle = NULL;
|
|
}
|
|
CreateTrails();
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CTFProjectile_MechanicalArmOrb::CreateTrails( void )
|
|
{
|
|
BaseClass::CreateTrails();
|
|
|
|
if ( !m_pTrailParticle )
|
|
{
|
|
m_pTrailParticle = ParticleProp()->Create( ( GetTeamNumber() == TF_TEAM_BLUE ? pszParticleBlue : pszParticleRed ), PATTACH_ABSORIGIN_FOLLOW );
|
|
}
|
|
}
|
|
#endif // CLIENT_DLL
|