Team Fortress 2 Source Code as on 22/4/2020
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.
 
 
 
 
 
 

495 lines
14 KiB

//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: TF Base Rockets.
//
//=============================================================================//
#include "cbase.h"
#include "tf_projectile_base.h"
#include "effect_dispatch_data.h"
#include "tf_shareddefs.h"
#include "tf_gamerules.h"
#ifdef GAME_DLL
#include "te_effect_dispatch.h"
#else
#include "c_te_effect_dispatch.h"
#endif
#ifdef CLIENT_DLL
#include "c_basetempentity.h"
#include "c_te_legacytempents.h"
#include "c_te_effect_dispatch.h"
#include "input.h"
#include "c_tf_player.h"
#else
#include "tf_player.h"
#endif
#ifdef _DEBUG
ConVar tf_debug_projectile( "tf_debug_projectile", "0", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY | FCVAR_CHEAT );
#endif // _DEBUG
IMPLEMENT_NETWORKCLASS_ALIASED( TFBaseProjectile, DT_TFBaseProjectile )
BEGIN_NETWORK_TABLE( CTFBaseProjectile, DT_TFBaseProjectile )
#ifdef CLIENT_DLL
RecvPropVector( RECVINFO( m_vInitialVelocity ) ),
RecvPropEHandle( RECVINFO( m_hLauncher ) )
#else
SendPropVector( SENDINFO( m_vInitialVelocity ), 20 /*nbits*/, 0 /*flags*/, -3000 /*low value*/, 3000 /*high value*/ ),
SendPropEHandle( SENDINFO( m_hLauncher ) )
#endif
END_NETWORK_TABLE()
// Server specific.
#ifdef GAME_DLL
BEGIN_DATADESC( CTFBaseProjectile )
//DEFINE_FUNCTION( ProjectileTouch ),
DEFINE_THINKFUNC( FlyThink ),
END_DATADESC()
#endif
//-----------------------------------------------------------------------------
// Purpose: Constructor.
//-----------------------------------------------------------------------------
CTFBaseProjectile::CTFBaseProjectile()
{
m_vInitialVelocity.Init();
SetWeaponID( TF_WEAPON_NONE );
// Client specific.
#ifdef CLIENT_DLL
m_flSpawnTime = 0.0f;
// Server specific.
#else
m_flDamage = 0.0f;
#endif
}
//-----------------------------------------------------------------------------
// Purpose: Destructor.
//-----------------------------------------------------------------------------
CTFBaseProjectile::~CTFBaseProjectile()
{
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFBaseProjectile::Precache( void )
{
#ifdef GAME_DLL
PrecacheModel( GetProjectileModelName() );
#endif
BaseClass::Precache();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFBaseProjectile::Spawn( void )
{
// Client specific.
#ifdef CLIENT_DLL
m_flSpawnTime = gpGlobals->curtime;
BaseClass::Spawn();
// Server specific.
#else
// Precache.
Precache();
SetModel( GetProjectileModelName() );
SetSolid( SOLID_BBOX );
SetMoveType( MOVETYPE_FLYGRAVITY, MOVECOLLIDE_FLY_CUSTOM );
AddEFlags( EFL_NO_WATER_VELOCITY_CHANGE );
UTIL_SetSize( this, -Vector( 1.0f, 1.0f, 1.0f ), Vector( 1.0f, 1.0f, 1.0f ) );
// Setup attributes.
SetGravity( GetGravity() );
m_takedamage = DAMAGE_NO;
SetDamage( 25.0f );
SetCollisionGroup( COLLISION_GROUP_PROJECTILE );
// Setup the touch and think functions.
SetTouch( &CTFBaseProjectile::ProjectileTouch );
SetThink( &CTFBaseProjectile::FlyThink );
SetNextThink( gpGlobals->curtime );
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CTFBaseProjectile *CTFBaseProjectile::Create( const char *pszClassname, const Vector &vecOrigin,
const QAngle &vecAngles, CBaseEntity *pOwner, float flVelocity, short iProjModelIndex, const char *pszDispatchEffect,
CBaseEntity *pScorer, bool bCritical, Vector vColor1, Vector vColor2 )
{
CTFBaseProjectile *pProjectile = NULL;
Vector vecForward, vecRight, vecUp;
AngleVectors( vecAngles, &vecForward, &vecRight, &vecUp );
Vector vecVelocity = vecForward * flVelocity;
#ifdef GAME_DLL
pProjectile = static_cast<CTFBaseProjectile*>( CBaseEntity::Create( pszClassname, vecOrigin, vecAngles, pOwner ) );
if ( !pProjectile )
return NULL;
// Initialize the owner.
pProjectile->SetOwnerEntity( pOwner );
pProjectile->SetScorer( pScorer );
// Spawn.
pProjectile->Spawn();
pProjectile->SetAbsVelocity( vecVelocity );
//pProjectile->SetupInitialTransmittedGrenadeVelocity( vecVelocity );
// Setup the initial angles.
QAngle angles;
VectorAngles( vecVelocity, angles );
pProjectile->SetAbsAngles( angles );
// Set team.
pProjectile->ChangeTeam( pOwner->GetTeamNumber() );
// Hide the projectile and create a fake one on the client
pProjectile->AddEffects( EF_NODRAW );
#endif
if ( pszDispatchEffect )
{
// we'd like to just send this projectile to a person in the shooter's PAS. However
// the projectile won't be sent to a player outside of water if shot from inside water
// and vice-versa, so we do a trace here to figure out if the trace starts or stops in water.
// if it crosses contents, we'll just broadcast the projectile. Otherwise, just send to PVS
// of the trace's endpoint.
trace_t tr;
CTraceFilterSimple traceFilter( pOwner, COLLISION_GROUP_NONE );
ITraceFilter *pFilterChain = NULL;
CTraceFilterIgnoreFriendlyCombatItems traceFilterCombatItem( pOwner, COLLISION_GROUP_NONE, pOwner->GetTeamNumber() );
if ( TFGameRules() && TFGameRules()->GameModeUsesUpgrades() )
{
// Ignore teammates and their (physical) upgrade items in MvM
pFilterChain = &traceFilterCombatItem;
}
CTraceFilterChain traceFilterChain( &traceFilter, pFilterChain );
UTIL_TraceLine( vecOrigin, vecOrigin + vecForward * MAX_COORD_RANGE, (CONTENTS_SOLID|CONTENTS_MOVEABLE|CONTENTS_WINDOW|CONTENTS_GRATE), &traceFilterChain, &tr );
bool bBroadcast = ( UTIL_PointContents( vecOrigin ) != UTIL_PointContents( tr.endpos ) );
IRecipientFilter *pFilter;
if ( bBroadcast )
{
// The projectile is going to cross content types
// (which will block PVS/PAS). Send to every client
pFilter = new CReliableBroadcastRecipientFilter();
}
else
{
// just the PVS of where the projectile will hit.
pFilter = new CPASFilter( tr.endpos );
}
CEffectData data;
data.m_vOrigin = vecOrigin;
data.m_vStart = vecVelocity;
data.m_fFlags = 6; // Lifetime
data.m_nDamageType = 0;
if ( bCritical )
{
data.m_nDamageType |= DMG_CRITICAL;
}
data.m_CustomColors.m_vecColor1 = vColor1;
data.m_CustomColors.m_vecColor2 = vColor2;
#ifdef GAME_DLL
data.m_nMaterial = pProjectile->GetModelIndex();
data.m_nEntIndex = pOwner->entindex();
#else
data.m_nMaterial = iProjModelIndex;
data.m_hEntity = ClientEntityList().EntIndexToHandle( pOwner->entindex() );
#endif
DispatchEffect( pszDispatchEffect, data );
}
return pProjectile;
}
const char *CTFBaseProjectile::GetProjectileModelName( void )
{
// should not try to init a base projectile
Assert( 0 );
return "";
}
//=============================================================================
//
// Client specific functions.
//
#ifdef CLIENT_DLL
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFBaseProjectile::PostDataUpdate( DataUpdateType_t type )
{
// Pass through to the base class.
BaseClass::PostDataUpdate( type );
if ( type == DATA_UPDATE_CREATED )
{
// Now stick our initial velocity and angles into the interpolation history.
CInterpolatedVar<Vector> &interpolator = GetOriginInterpolator();
interpolator.ClearHistory();
CInterpolatedVar<QAngle> &rotInterpolator = GetRotationInterpolator();
rotInterpolator.ClearHistory();
float flChangeTime = GetLastChangeTime( LATCH_SIMULATION_VAR );
// Add a sample 1 second back.
Vector vCurOrigin = GetLocalOrigin() - m_vInitialVelocity;
interpolator.AddToHead( flChangeTime - 1.0f, &vCurOrigin, false );
QAngle vCurAngles = GetLocalAngles();
rotInterpolator.AddToHead( flChangeTime - 1.0f, &vCurAngles, false );
// Add the current sample.
vCurOrigin = GetLocalOrigin();
interpolator.AddToHead( flChangeTime, &vCurOrigin, false );
rotInterpolator.AddToHead( flChangeTime - 1.0, &vCurAngles, false );
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int CTFBaseProjectile::DrawModel( int flags )
{
// During the first 0.2 seconds of our life, don't draw ourselves.
if ( gpGlobals->curtime - m_flSpawnTime < 0.1f )
return 0;
return BaseClass::DrawModel( flags );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
C_LocalTempEntity *ClientsideProjectileCallback( const CEffectData &data, float flGravityBase, const char *pszParticleName )
{
// Create a nail temp ent, and give it an impact callback to use
C_BaseEntity *pEnt = C_BaseEntity::Instance( data.m_hEntity );
if ( !pEnt || pEnt->IsDormant() )
{
//Assert( 0 );
return NULL;
}
Vector vecSrc = data.m_vOrigin;
// If we're seeing another player shooting the nails, move their start point to the weapon origin
if ( pEnt && pEnt->IsPlayer() )
{
C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer();
if ( pLocalPlayer != pEnt || C_BasePlayer::ShouldDrawLocalPlayer() )
{
CTFPlayer *pTFPlayer = ToTFPlayer( pEnt );
if ( pTFPlayer->GetActiveWeapon() )
{
pTFPlayer->GetActiveWeapon()->GetAttachment( "muzzle", vecSrc );
}
}
else
{
C_BaseEntity *pViewModel = pLocalPlayer->GetViewModel();
if ( pViewModel )
{
QAngle vecAngles;
Vector vecMuzzleOrigin;
int iMuzzleFlashAttachment = pViewModel->LookupAttachment( "muzzle" );
pViewModel->GetAttachment( iMuzzleFlashAttachment, vecMuzzleOrigin, vecAngles );
Vector vForward;
AngleVectors( vecAngles, &vForward );
trace_t trace;
UTIL_TraceLine( vecMuzzleOrigin + vForward * -50, vecMuzzleOrigin, MASK_SOLID, pEnt, COLLISION_GROUP_NONE, &trace );
if ( trace.fraction != 1.0 )
{
vecSrc = trace.endpos;
}
}
}
}
float flGravity = ( flGravityBase * 800 );
Vector vecGravity(0,0,-flGravity);
return tempents->ClientProjectile( vecSrc, data.m_vStart, vecGravity, data.m_nMaterial, data.m_fFlags, pEnt, "Impact", pszParticleName );
}
//=============================================================================
//
// Server specific functions.
//
#else
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
unsigned int CTFBaseProjectile::PhysicsSolidMaskForEntity( void ) const
{
return BaseClass::PhysicsSolidMaskForEntity() | CONTENTS_HITBOX;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFBaseProjectile::ProjectileTouch( CBaseEntity *pOther )
{
// Verify a correct "other."
Assert( pOther );
if ( !pOther->IsSolid() || pOther->IsSolidFlagSet( FSOLID_VOLUME_CONTENTS ) )
return;
// Handle hitting skybox (disappear).
const trace_t *pTrace = &CBaseEntity::GetTouchTrace();
trace_t *pNewTrace = const_cast<trace_t*>( pTrace );
if( pTrace->surface.flags & SURF_SKY )
{
UTIL_Remove( this );
return;
}
// pass through ladders
if( pTrace->surface.flags & CONTENTS_LADDER )
return;
if ( TFGameRules() && TFGameRules()->GameModeUsesUpgrades() )
{
// Projectile shields
if ( InSameTeam( pOther ) && pOther->IsCombatItem() )
return;
}
if ( pOther->IsWorld() )
{
SetAbsVelocity( vec3_origin );
AddSolidFlags( FSOLID_NOT_SOLID );
// Remove immediately. Clientside projectiles will stick in the wall for a bit.
UTIL_Remove( this );
return;
}
// determine the inflictor, which is the weapon which fired this projectile
CBaseEntity *pInflictor = GetLauncher();
CTakeDamageInfo info;
info.SetAttacker( GetOwnerEntity() ); // the player who operated the thing that emitted nails
info.SetInflictor( pInflictor ); // the weapon that emitted this projectile
info.SetWeapon( pInflictor );
info.SetDamage( GetDamage() );
info.SetDamageForce( GetDamageForce() );
info.SetDamagePosition( GetAbsOrigin() );
info.SetDamageType( GetDamageType() );
Vector dir;
AngleVectors( GetAbsAngles(), &dir );
pOther->DispatchTraceAttack( info, dir, pNewTrace );
ApplyMultiDamage();
if ( pOther && pOther->IsPlayer() )
{
int iMadMilkSyringes = 0;
CALL_ATTRIB_HOOK_INT_ON_OTHER( GetOwnerEntity(), iMadMilkSyringes, mad_milk_syringes );
if ( iMadMilkSyringes )
{
CTFPlayer *pTFVictim = ToTFPlayer( pOther );
CTFPlayer *pTFOwner = ToTFPlayer( GetOwnerEntity() );
if ( pTFVictim && pTFOwner && pTFVictim->GetTeamNumber() != pTFOwner->GetTeamNumber() )
{
pTFVictim->m_Shared.AddCond( TF_COND_MAD_MILK, 1.f, pTFOwner );
}
}
}
UTIL_Remove( this );
}
Vector CTFBaseProjectile::GetDamageForce( void )
{
Vector vecVelocity = GetAbsVelocity();
VectorNormalize( vecVelocity );
return (vecVelocity * GetDamage());
}
void CTFBaseProjectile::FlyThink( void )
{
QAngle angles;
VectorAngles( GetAbsVelocity(), angles );
SetAbsAngles( angles );
SetNextThink( gpGlobals->curtime + 0.1f );
#ifdef _DEBUG
if ( tf_debug_projectile.GetBool() )
{
NDebugOverlay::Box( GetAbsOrigin(), Vector( 0.5, 0.5, 0.5 ), -Vector( 0.5, 0.5, 0.5 ), 0, 255, 0, 100, 0.1 );
}
#endif // _DEBUG
}
void CTFBaseProjectile::SetScorer( CBaseEntity *pScorer )
{
m_Scorer = pScorer;
}
CBasePlayer *CTFBaseProjectile::GetScorer( void )
{
return dynamic_cast<CBasePlayer *>( m_Scorer.Get() );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int CTFBaseProjectile::GetDamageType( void )
{
Assert( GetWeaponID() != TF_WEAPON_NONE );
int iDmgType = g_aWeaponDamageTypes[ GetWeaponID() ];
if ( m_bCritical )
{
iDmgType |= DMG_CRITICAL;
}
return iDmgType;
}
#endif