//========= 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( 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( 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( 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; 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( pOther ); CTFMerasmusTrickOrTreatProp *pMerasmusProp = dynamic_cast( pOther ); CTFRobotDestruction_Robot *pRobot = dynamic_cast( 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