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.
509 lines
16 KiB
509 lines
16 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// TF Energy Ring |
|
// |
|
//============================================================================= |
|
#include "cbase.h" |
|
#include "tf_projectile_energy_ring.h" |
|
#include "tf_weapon_raygun.h" |
|
|
|
#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" |
|
#include "cliententitylist.h" |
|
#endif |
|
|
|
#ifdef GAME_DLL |
|
#include "tf_player.h" |
|
#include "tf_player_shared.h" |
|
#include "particle_parse.h" |
|
#include "tf_pumpkin_bomb.h" |
|
#include "halloween/merasmus/merasmus_trick_or_treat_prop.h" |
|
#include "tf_robot_destruction_robot.h" |
|
#endif |
|
|
|
#define ENERGY_RING_DISPATCH_EFFECT "ClientProjectile_EnergyRing" |
|
#define ENERGY_RING_DISPATCH_EFFECT_POMSON "ClientProjectile_EnergyRingPomson" |
|
|
|
const char* g_pszEnergyRingModel ( "models/weapons/w_models/w_drg_ball.mdl" ); |
|
|
|
const char* g_pszPomsonImpactFleshSound ( "Weapon_Pomson.ProjectileImpactWorld" ); |
|
const char* g_pszPomsonImpactWorldSound ( "Weapon_Pomson.ProjectileImpactFlesh" ); |
|
const char* g_pszPomsonTrailParticle ( "drg_pomson_projectile" ); |
|
const char* g_pszPomsonTrailParticleCrit ( "drg_pomson_projectile_crit" ); |
|
|
|
const char* g_pszBisonImpactFleshSound ( "Weapon_Bison.ProjectileImpactWorld" ); |
|
const char* g_pszBisonImpactWorldSound ( "Weapon_Bison.ProjectileImpactFlesh" ); |
|
const char* g_pszBisonTrailParticle ( "drg_bison_projectile" ); |
|
const char* g_pszBisonTrailParticleCrit ( "drg_bison_projectile_crit" ); |
|
|
|
const char* g_pszEnergyProjectileImpactParticle ( "drg_pomson_impact" ); |
|
//============================================================================= |
|
// |
|
// TF Energy Ring Projectile functions |
|
// |
|
|
|
IMPLEMENT_NETWORKCLASS_ALIASED( TFProjectile_EnergyRing, DT_TFProjectile_EnergyRing ) |
|
|
|
BEGIN_NETWORK_TABLE( CTFProjectile_EnergyRing, DT_TFProjectile_EnergyRing ) |
|
END_NETWORK_TABLE() |
|
|
|
//----------------------------------------------------------------------------- |
|
LINK_ENTITY_TO_CLASS( tf_projectile_energy_ring, CTFProjectile_EnergyRing ); |
|
PRECACHE_WEAPON_REGISTER( tf_projectile_energy_ring ); |
|
|
|
short g_sModelIndexRing; |
|
void PrecacheRing(void *pUser) |
|
{ |
|
g_sModelIndexRing = modelinfo->GetModelIndex( g_pszEnergyRingModel ); |
|
} |
|
PRECACHE_REGISTER_FN(PrecacheRing); |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
CTFProjectile_EnergyRing::CTFProjectile_EnergyRing() |
|
{ |
|
m_vecPrevPos = vec3_origin; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
const char *CTFProjectile_EnergyRing::GetProjectileModelName( void ) |
|
{ |
|
return g_pszEnergyRingModel; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
float CTFProjectile_EnergyRing::GetGravity( void ) |
|
{ |
|
return 0.f; |
|
} |
|
|
|
float CTFProjectile_EnergyRing::GetInitialVelocity( void ) |
|
{ |
|
return ShouldPenetrate() ? 840.f : 1200.f; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
CTFProjectile_EnergyRing *CTFProjectile_EnergyRing::Create( CTFWeaponBaseGun *pLauncher, const Vector &vecOrigin, const QAngle& vecAngles, float fSpeed, float fGravity, |
|
CBaseEntity *pOwner, CBaseEntity *pScorer, Vector vColor1, Vector vColor2, bool bCritical ) |
|
{ |
|
CTFProjectile_EnergyRing *pRing = NULL; |
|
|
|
#ifdef GAME_DLL |
|
Vector vecForward, vecRight, vecUp; |
|
AngleVectors( vecAngles, &vecForward, &vecRight, &vecUp ); |
|
|
|
pRing = static_cast<CTFProjectile_EnergyRing*>( CBaseEntity::Create( "tf_projectile_energy_ring", vecOrigin, vecAngles, pOwner ) ); |
|
if ( !pRing ) |
|
return NULL; |
|
|
|
// Initialize the owner. |
|
pRing->SetOwnerEntity( pOwner ); |
|
pRing->SetLauncher( pLauncher ); |
|
|
|
pRing->SetScorer( pScorer ); |
|
|
|
// Spawn. |
|
pRing->Spawn(); |
|
|
|
Vector vecVelocity = vecForward * pRing->GetInitialVelocity(); |
|
pRing->SetAbsVelocity( vecVelocity ); |
|
|
|
// Setup the initial angles. |
|
QAngle angles; |
|
VectorAngles( vecVelocity, angles ); |
|
pRing->SetAbsAngles( angles ); |
|
|
|
// Set team. |
|
pRing->ChangeTeam( pOwner->GetTeamNumber() ); |
|
|
|
if ( pScorer ) |
|
{ |
|
pRing->SetTruceValidForEnt( pScorer->IsTruceValidForEnt() ); |
|
} |
|
#endif |
|
|
|
#ifdef CLIENT_DLL |
|
// This is silly code to support demos when the client created its own effects |
|
// for the Pomson and Righteous Bison |
|
CTFRaygun* pRaygun = assert_cast< CTFRaygun* >( pLauncher ); |
|
|
|
if ( pRaygun && !pRaygun->UseNewProjectileCode() ) |
|
{ |
|
if ( pRaygun->GetWeaponID() == TF_WEAPON_DRG_POMSON ) |
|
{ |
|
pRing = static_cast<CTFProjectile_EnergyRing*>( CTFBaseProjectile::Create( "tf_projectile_energy_ring", vecOrigin, vecAngles, pOwner, |
|
1200.f, g_sModelIndexRing, |
|
ENERGY_RING_DISPATCH_EFFECT_POMSON, pScorer, bCritical, vColor1, vColor2 ) ); |
|
} |
|
else |
|
{ |
|
pRing = static_cast<CTFProjectile_EnergyRing*>( CTFBaseProjectile::Create( "tf_projectile_energy_ring", vecOrigin, vecAngles, pOwner, |
|
1200.f, g_sModelIndexRing, |
|
ENERGY_RING_DISPATCH_EFFECT, pScorer, bCritical, vColor1, vColor2 ) ); |
|
} |
|
|
|
if ( pRing ) |
|
{ |
|
pRing->SetRenderMode( kRenderNone ); |
|
pRing->SetSolidFlags( FSOLID_TRIGGER | FSOLID_NOT_SOLID ); |
|
pRing->SetCollisionGroup( TFCOLLISION_GROUP_ROCKETS ); |
|
} |
|
} |
|
#endif |
|
|
|
return pRing; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CTFProjectile_EnergyRing::Spawn() |
|
{ |
|
BaseClass::Spawn(); |
|
|
|
SetSolid( SOLID_BBOX ); |
|
SetMoveType( MOVETYPE_FLY, MOVECOLLIDE_FLY_CUSTOM ); |
|
SetRenderMode( kRenderNone ); |
|
SetSolidFlags( FSOLID_TRIGGER | FSOLID_NOT_SOLID ); |
|
SetCollisionGroup( TFCOLLISION_GROUP_ROCKETS ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CTFProjectile_EnergyRing::Precache() |
|
{ |
|
PrecacheParticleSystem( g_pszEnergyProjectileImpactParticle ); |
|
|
|
PrecacheParticleSystem( g_pszBisonTrailParticle ); |
|
PrecacheParticleSystem( g_pszBisonTrailParticleCrit ); |
|
PrecacheScriptSound( g_pszBisonImpactWorldSound ); |
|
PrecacheScriptSound( g_pszBisonImpactFleshSound ); |
|
|
|
PrecacheParticleSystem( g_pszPomsonTrailParticle ); |
|
PrecacheParticleSystem( g_pszPomsonTrailParticleCrit ); |
|
PrecacheScriptSound( g_pszPomsonImpactWorldSound ); |
|
PrecacheScriptSound( g_pszPomsonImpactFleshSound ); |
|
|
|
BaseClass::Precache(); |
|
} |
|
|
|
#ifdef GAME_DLL |
|
|
|
struct collidelist_t |
|
{ |
|
const CPhysCollide *pCollide; |
|
Vector origin; |
|
QAngle angles; |
|
}; |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CTFProjectile_EnergyRing::ProjectileTouch( CBaseEntity *pOther ) |
|
{ |
|
// Verify a correct "other." |
|
Assert( pOther ); |
|
if ( !pOther->IsSolid() || pOther->IsSolidFlagSet( FSOLID_VOLUME_CONTENTS ) || pOther->IsSolidFlagSet( FSOLID_NOT_SOLID ) |
|
|| pOther->GetCollisionGroup() == TFCOLLISION_GROUP_RESPAWNROOMS ) |
|
return; |
|
|
|
CBaseEntity* pOwner = GetOwnerEntity(); |
|
// Don't shoot ourselves |
|
if ( pOwner == pOther ) |
|
return; |
|
|
|
// Handle hitting skybox (disappear). |
|
const trace_t *pTrace = &CBaseEntity::GetTouchTrace(); |
|
if( pTrace->surface.flags & SURF_SKY ) |
|
{ |
|
UTIL_Remove( this ); |
|
return; |
|
} |
|
|
|
// pass through ladders |
|
if( pTrace->surface.flags & CONTENTS_LADDER ) |
|
return; |
|
|
|
// Used when checking against things like FUNC_BRUSHES |
|
if ( !pOther->IsWorld() && pOther->GetSolid() == SOLID_VPHYSICS ) |
|
{ |
|
CPhysCollide *pTriggerCollide = modelinfo->GetVCollide( GetModelIndex() )->solids[0]; |
|
Assert( pTriggerCollide ); |
|
|
|
CUtlVector<collidelist_t> collideList; |
|
IPhysicsObject *pList[VPHYSICS_MAX_OBJECT_LIST_COUNT]; |
|
int physicsCount = pOther->VPhysicsGetObjectList( pList, ARRAYSIZE(pList) ); |
|
vcollide_t *pVCollide = modelinfo->GetVCollide( pOther->GetModelIndex() ); |
|
|
|
if ( physicsCount ) |
|
{ |
|
for ( int i = 0; i < physicsCount; i++ ) |
|
{ |
|
const CPhysCollide *pCollide = pList[i]->GetCollide(); |
|
if ( pCollide ) |
|
{ |
|
collidelist_t element; |
|
element.pCollide = pCollide; |
|
pList[i]->GetPosition( &element.origin, &element.angles ); |
|
collideList.AddToTail( element ); |
|
} |
|
} |
|
} |
|
else if ( pVCollide && pVCollide->solidCount ) |
|
{ |
|
collidelist_t element; |
|
element.pCollide = pVCollide->solids[0]; |
|
element.origin = pOther->GetAbsOrigin(); |
|
element.angles = pOther->GetAbsAngles(); |
|
collideList.AddToTail( element ); |
|
} |
|
else |
|
{ |
|
return; |
|
} |
|
|
|
for ( int i = collideList.Count()-1; i >= 0; --i ) |
|
{ |
|
const collidelist_t &element = collideList[i]; |
|
trace_t tr; |
|
physcollision->TraceCollide( pTrace->startpos, element.origin, element.pCollide, element.angles, pTriggerCollide, GetAbsOrigin(), GetAbsAngles(), &tr ); |
|
if ( !tr.DidHit() ) |
|
return; |
|
} |
|
} |
|
|
|
// The stuff we collide with |
|
bool bCombatEntity = pOther->IsPlayer() || |
|
pOther->IsBaseObject() || |
|
pOther->IsCombatCharacter() || |
|
pOther->IsCombatItem(); |
|
|
|
if ( !bCombatEntity ) |
|
{ |
|
// Couple more things that we collide with |
|
// HACK: these are the same checks we do in CTFProjectile_Arrow::ArrowTouch()...need to figure out a better way to do this when we have time |
|
CTFPumpkinBomb *pPumpkinBomb = dynamic_cast<CTFPumpkinBomb*>( pOther ); |
|
CTFMerasmusTrickOrTreatProp *pMerasmusProp = dynamic_cast<CTFMerasmusTrickOrTreatProp*>( pOther ); |
|
CTFRobotDestruction_Robot *pRobot = dynamic_cast<CTFRobotDestruction_Robot*>( pOther ); |
|
if ( pPumpkinBomb || pMerasmusProp || pRobot ) |
|
{ |
|
bCombatEntity = true; |
|
} |
|
} |
|
|
|
if ( bCombatEntity && ( pOther != pOwner ) ) |
|
{ |
|
// Bison projectiles shouldn't collide with friendly things |
|
if ( pOther->GetTeamNumber() == GetTeamNumber() && ShouldPenetrate() ) |
|
{ |
|
return; |
|
} |
|
|
|
FOR_EACH_VEC( m_vecHitEnemies, i ) |
|
{ |
|
// Check if we've already damaged this entity. If so, don't do it again |
|
if ( m_vecHitEnemies[i] == pOther ) |
|
return; |
|
} |
|
|
|
const int nMaxPenetrates = 5; |
|
const int nDamage = GetDamage() * pow( 0.75f, m_vecHitEnemies.Count() ); |
|
|
|
CTakeDamageInfo info( this, pOwner, GetLauncher(), nDamage, GetDamageType(), TF_DMG_CUSTOM_PLASMA ); |
|
info.SetReportedPosition( pOwner->GetAbsOrigin() ); |
|
info.SetDamagePosition( pTrace->endpos ); |
|
|
|
if ( info.GetDamageType() & DMG_CRITICAL ) |
|
{ |
|
info.SetCritType( CTakeDamageInfo::CRIT_FULL ); |
|
} |
|
|
|
trace_t traceAttack; |
|
UTIL_TraceLine( WorldSpaceCenter(), pOther->WorldSpaceCenter(), MASK_SOLID|CONTENTS_HITBOX, this, COLLISION_GROUP_NONE, &traceAttack ); |
|
|
|
pOther->DispatchTraceAttack( info, GetAbsVelocity(), &traceAttack ); |
|
|
|
ApplyMultiDamage(); |
|
|
|
m_vecHitEnemies.AddToTail( pOther ); |
|
|
|
// Get a position on whatever we hit |
|
Vector vecDelta = pOther->GetAbsOrigin() - GetAbsOrigin(); |
|
Vector vecNormalVel = GetAbsVelocity().Normalized(); |
|
Vector vecNewPos = ( DotProduct( vecDelta, vecNormalVel ) * vecNormalVel ) + GetAbsOrigin(); |
|
|
|
PlayImpactEffects( vecNewPos, pOther->IsPlayer() ); |
|
|
|
int iPenetrate = 0; |
|
CALL_ATTRIB_HOOK_INT_ON_OTHER( GetOwnerEntity(), iPenetrate, energy_weapon_penetration ); |
|
if ( iPenetrate && m_vecHitEnemies.Count() < nMaxPenetrates ) |
|
{ |
|
return; |
|
} |
|
|
|
|
|
UTIL_Remove( this ); |
|
return; |
|
} |
|
|
|
if ( pOther->IsWorld() ) |
|
{ |
|
SetAbsVelocity( vec3_origin ); |
|
AddSolidFlags( FSOLID_NOT_SOLID ); |
|
} |
|
|
|
PlayImpactEffects( pTrace->endpos, false ); |
|
|
|
// Remove by default. Fixes this entity living forever on things like doors. |
|
UTIL_Remove( this ); |
|
} |
|
|
|
void CTFProjectile_EnergyRing::ResolveFlyCollisionCustom( trace_t &trace, Vector &vecVelocity ) |
|
{ |
|
PlayImpactEffects( trace.endpos, false ); |
|
|
|
// Remove by default. Fixes this entity living forever on things like doors. |
|
UTIL_Remove( this ); |
|
} |
|
|
|
void CTFProjectile_EnergyRing::PlayImpactEffects( const Vector& vecPos, bool bHitFlesh ) |
|
{ |
|
CTFWeaponBaseGun* pTFGun = dynamic_cast< CTFWeaponBaseGun* >( GetLauncher() ); |
|
if ( pTFGun ) |
|
{ |
|
DispatchParticleEffect( g_pszEnergyProjectileImpactParticle, vecPos, GetAbsAngles(), pTFGun->GetParticleColor( 1 ), pTFGun->GetParticleColor( 2 ), true, NULL, 0 ); |
|
const char* pszSoundString = NULL; |
|
if ( ShouldPenetrate() ) |
|
{ |
|
pszSoundString = bHitFlesh ? g_pszBisonImpactFleshSound : g_pszBisonImpactWorldSound; |
|
} |
|
else |
|
{ |
|
pszSoundString = bHitFlesh ? g_pszPomsonImpactFleshSound : g_pszPomsonImpactWorldSound; |
|
} |
|
EmitSound( pszSoundString ); |
|
} |
|
} |
|
|
|
#else |
|
|
|
void CTFProjectile_EnergyRing::OnDataChanged( DataUpdateType_t updateType ) |
|
{ |
|
BaseClass::OnDataChanged( updateType ); |
|
|
|
if ( updateType == DATA_UPDATE_CREATED ) |
|
{ |
|
CNewParticleEffect* pEffect = ParticleProp()->Create( GetTrailParticleName(), PATTACH_ABSORIGIN_FOLLOW ); |
|
CTFWeaponBaseGun* pTFGun = dynamic_cast< CTFWeaponBaseGun* >( GetLauncher() ); |
|
if ( pEffect && pTFGun ) |
|
{ |
|
pEffect->SetControlPoint( CUSTOM_COLOR_CP1, pTFGun->GetParticleColor( 0 ) ); |
|
pEffect->SetControlPoint( CUSTOM_COLOR_CP2, pTFGun->GetParticleColor( 1 ) ); |
|
} |
|
} |
|
} |
|
|
|
#endif |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
float CTFProjectile_EnergyRing::GetDamage() |
|
{ |
|
return ShouldPenetrate() ? 45.f : 60.f; |
|
} |
|
|
|
bool CTFProjectile_EnergyRing::ShouldPenetrate() const |
|
{ |
|
int iPenetrate = 0; |
|
CALL_ATTRIB_HOOK_INT_ON_OTHER( GetOwnerEntity(), iPenetrate, energy_weapon_penetration ); |
|
|
|
return iPenetrate != 0; |
|
} |
|
|
|
const char* CTFProjectile_EnergyRing::GetTrailParticleName() const |
|
{ |
|
if ( ShouldPenetrate() ) // Righteous Bison |
|
{ |
|
return IsCritical() ? g_pszBisonTrailParticleCrit : g_pszBisonTrailParticle; |
|
} |
|
else // Pomson |
|
{ |
|
return IsCritical() ? g_pszPomsonTrailParticleCrit : g_pszPomsonTrailParticle; |
|
} |
|
} |
|
|
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// The following is legacy code to support old demos |
|
//----------------------------------------------------------------------------- |
|
|
|
#ifdef CLIENT_DLL |
|
void CreateClientSideEnergyRing( const char* pszStandardParticle, const char* pszCritParticle, const CEffectData &data, int nFlags ) |
|
{ |
|
C_BaseEntity *entity = ClientEntityList().GetBaseEntityFromHandle( data.m_hEntity ); |
|
if ( !entity ) |
|
{ |
|
return; |
|
} |
|
|
|
C_TFPlayer *pPlayer = dynamic_cast< C_TFPlayer * >( entity->GetOwnerEntity() ); |
|
if ( pPlayer ) |
|
{ |
|
C_LocalTempEntity *pRing = ClientsideProjectileCallback( data, 0.f ); |
|
if ( pRing ) |
|
{ |
|
bool bCritical = ( ( data.m_nDamageType & DMG_CRITICAL ) != 0 ); |
|
CNewParticleEffect* pEffect = pRing->AddParticleEffect( bCritical ? pszCritParticle : pszStandardParticle ); |
|
if ( pEffect ) |
|
{ |
|
pEffect->SetControlPoint( CUSTOM_COLOR_CP1, data.m_CustomColors.m_vecColor1 ); |
|
pEffect->SetControlPoint( CUSTOM_COLOR_CP2, data.m_CustomColors.m_vecColor2 ); |
|
} |
|
|
|
pRing->AddEffects( EF_NOSHADOW ); |
|
pRing->flags = nFlags; |
|
pRing->SetRenderMode( kRenderNone ); |
|
pRing->SetSolidFlags( FSOLID_TRIGGER | FSOLID_NOT_SOLID ); |
|
pRing->SetCollisionGroup( TFCOLLISION_GROUP_ROCKETS ); |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Bison effect callback |
|
//----------------------------------------------------------------------------- |
|
void ClientsideProjectileRingCallback( const CEffectData &data ) |
|
{ |
|
CreateClientSideEnergyRing( g_pszBisonTrailParticle, g_pszBisonTrailParticleCrit, data, FTENT_COLLIDEKILL | FTENT_COLLIDEPROPS | FTENT_ATTACHTOTARGET | FTENT_ALIGNTOMOTION | FTENT_CLIENTSIDEPARTICLES ); |
|
} |
|
|
|
DECLARE_CLIENT_EFFECT( ENERGY_RING_DISPATCH_EFFECT, ClientsideProjectileRingCallback ); |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Pomson effect callback |
|
//----------------------------------------------------------------------------- |
|
void ClientsideProjectileRingPomsonCallback( const CEffectData &data ) |
|
{ |
|
CreateClientSideEnergyRing( g_pszPomsonTrailParticle, g_pszPomsonTrailParticleCrit, data, FTENT_COLLIDEALL | FTENT_USEFASTCOLLISIONS | FTENT_ATTACHTOTARGET | FTENT_ALIGNTOMOTION | FTENT_CLIENTSIDEPARTICLES ); |
|
} |
|
|
|
DECLARE_CLIENT_EFFECT( ENERGY_RING_DISPATCH_EFFECT_POMSON, ClientsideProjectileRingPomsonCallback ); |
|
|
|
#endif |