//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: The Escort's Shield weapon // // $Revision: $ // $NoKeywords: $ //=============================================================================// #include "cbase.h" #include "tf_shield.h" #include "tf_shareddefs.h" #include "collisionutils.h" #include #include "sendproxy.h" #include "mathlib/mathlib.h" #define PROBE_EFFECT_TIME 0.15f ConVar shield_explosive_damage( "shield_explosive_damage","10", FCVAR_REPLICATED, "Shield power damage from explosions" ); // Percentage of total health that the shield's allowed to go below 0 #define SHIELD_MIN_HEALTH_FACTOR (-0.5) //----------------------------------------------------------------------------- // Stores a list of all active shields //----------------------------------------------------------------------------- CUtlVector< CShield* > CShield::s_Shields; //----------------------------------------------------------------------------- // Returns true if the entity is a shield //----------------------------------------------------------------------------- bool IsShield( CBaseEntity *pEnt ) { // This is a faster way... return pEnt && (pEnt->GetCollisionGroup() == TFCOLLISION_GROUP_SHIELD); // return pEnt && FClassnameIs( pEnt, "shield" ) } //============================================================================= // Shield effect //============================================================================= EXTERN_SEND_TABLE(DT_BaseEntity) IMPLEMENT_SERVERCLASS_ST(CShield, DT_Shield) SendPropInt(SENDINFO(m_nOwningPlayerIndex), MAX_EDICT_BITS, SPROP_UNSIGNED ), SendPropFloat(SENDINFO(m_flPowerLevel), 9, SPROP_ROUNDUP, SHIELD_MIN_HEALTH_FACTOR, 1.0f ), SendPropInt(SENDINFO(m_bIsEMPed), 1, SPROP_UNSIGNED ), END_SEND_TABLE() //----------------------------------------------------------------------------- // constructor //----------------------------------------------------------------------------- CShield::CShield() { s_Shields.AddToTail(this); AddEFlags( EFL_FORCE_CHECK_TRANSMIT ); SetupRecharge( 0,0,0,0 ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CShield::~CShield() { int i = s_Shields.Find(this); if (i >= 0) s_Shields.FastRemove(i); } //----------------------------------------------------------------------------- // Precache !! //----------------------------------------------------------------------------- void CShield::Precache() { SetClassname( "shield" ); } //----------------------------------------------------------------------------- // Spawn !! //----------------------------------------------------------------------------- void CShield::Spawn( void ) { m_bIsEMPed = 0; SetCollisionGroup( TFCOLLISION_GROUP_SHIELD ); // Make it translucent m_nRenderFX = kRenderTransAlpha; SetRenderColorA( 255 ); m_flNextRechargeTime = gpGlobals->curtime; CollisionProp()->SetSurroundingBoundsType( USE_GAME_CODE ); SetSolid( SOLID_CUSTOM ); // Stuff can't come to a rest on shields! AddSolidFlags( FSOLID_NOT_STANDABLE ); UTIL_SetSize( this, vec3_origin, vec3_origin ); } //----------------------------------------------------------------------------- // Owner //----------------------------------------------------------------------------- void CShield::SetOwnerEntity( CBaseEntity *pOwner ) { BaseClass::SetOwnerEntity( pOwner ); if (pOwner->IsPlayer()) { m_nOwningPlayerIndex = pOwner->entindex(); } else { m_nOwningPlayerIndex = 0; } ChangeTeam( pOwner->GetTeamNumber() ); } //----------------------------------------------------------------------------- // Does this shield protect from a particular damage type? //----------------------------------------------------------------------------- float CShield::ProtectionAmount( int damageType ) const { // As a test, we're trying to make shields impervious to everything return 1.0f; } //----------------------------------------------------------------------------- // Activates/deactivates a shield for collision purposes //----------------------------------------------------------------------------- void CShield::ActivateCollisions( bool activate ) { if ( activate ) { RemoveSolidFlags( FSOLID_NOT_SOLID ); } else { AddSolidFlags( FSOLID_NOT_SOLID ); } } //----------------------------------------------------------------------------- // Activates all shields //----------------------------------------------------------------------------- void CShield::ActivateShields( bool activate, int team ) { for (int i = s_Shields.Count(); --i >= 0; ) { // Activate all shields on the same team if ( (team == -1) || (team == s_Shields[i]->GetTeamNumber()) ) { s_Shields[i]->ActivateCollisions( activate ); } } } //----------------------------------------------------------------------------- // Checks a ray against shields only //----------------------------------------------------------------------------- bool CShield::IsBlockedByShields( const Vector& src, const Vector& end ) { trace_t tr; Ray_t ray; ray.Init( src, end ); for (int i = s_Shields.Count(); --i >= 0; ) { if (!s_Shields[i]->ShouldCollide( TFCOLLISION_GROUP_WEAPON, MASK_ALL )) continue; // Coarse bbox test first Vector vecAbsMins, vecAbsMaxs; s_Shields[i]->CollisionProp()->WorldSpaceAABB( &vecAbsMins, &vecAbsMaxs ); if (!IsBoxIntersectingRay( vecAbsMins, vecAbsMaxs, ray.m_Start, ray.m_Delta )) continue; if (s_Shields[i]->TestCollision( ray, MASK_ALL, tr )) return true; } return false; } //----------------------------------------------------------------------------- // Called when we hit something that we deflect... //----------------------------------------------------------------------------- void CShield::RegisterDeflection(const Vector& vecDir, int bitsDamageType, trace_t *ptr) { // Probes don't hurt shields if (bitsDamageType & DMG_PROBE) return; Vector normalDir; VectorCopy( vecDir, normalDir ); VectorNormalize( normalDir ); // Uncomment this line when the client predicts the shield deflections //filter.UsePredictionRules(); EntityMessageBegin( this ); WRITE_LONG( ptr->hitgroup ); WRITE_VEC3NORMAL( normalDir ); WRITE_BYTE( false ); // This was not a partial block MessageEnd(); // If this is a buckshot round, don't pay the power cost to stop it once we've gone over our max buckshot hits this frame if ( bitsDamageType & DMG_BUCKSHOT ) { m_iBuckshotHitsThisFrame++; if ( m_iBuckshotHitsThisFrame > 4 ) return; } // If this is an explosion, it counts for extra if ( bitsDamageType & DMG_BLAST ) { SetPower( m_flPower - shield_explosive_damage.GetFloat() ); } else { // Reduce our power level by a hit SetPower( m_flPower - 1 ); } // If we've lost power fully, don't recharge for a bit if ( m_flPower <= 0 ) { m_flNextRechargeTime = gpGlobals->curtime + 1.0; } } //----------------------------------------------------------------------------- // Called when we hit something that we let through... //----------------------------------------------------------------------------- void CShield::RegisterPassThru(const Vector& vecDir, int bitsDamageType, trace_t *ptr) { // Probes don't hurt shields if (bitsDamageType & DMG_PROBE) return; Vector normalDir; VectorCopy( vecDir, normalDir ); VectorNormalize( normalDir ); EntityMessageBegin( this ); WRITE_LONG( ptr->hitgroup ); WRITE_VEC3NORMAL( normalDir ); WRITE_BYTE( true ); // This was a partial block MessageEnd(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CShield::SetPower( float flPower ) { m_flPower = MAX( (SHIELD_MIN_HEALTH_FACTOR * m_flMaxPower), flPower ); if ( m_flPower > m_flMaxPower ) { m_flPower = m_flMaxPower; } m_flPowerLevel = ( m_flPower / m_flMaxPower ); } //----------------------------------------------------------------------------- // Purpose: Setup shield recharging parameters //----------------------------------------------------------------------------- void CShield::SetupRecharge( float flPower, float flDelay, float flAmount, float flTickTime ) { m_flMaxPower = flPower; SetPower( flPower ); m_flPowerLevel = 1.0; m_flRechargeDelay = flDelay; m_flRechargeAmount = flAmount; m_flRechargeTime = flTickTime; } //----------------------------------------------------------------------------- // Purpose: Recharge the shield if we haven't taken damage for a while //----------------------------------------------------------------------------- void CShield::Think( void ) { // Clear out buckshot count m_iBuckshotHitsThisFrame = 0; if ( m_flNextRechargeTime < gpGlobals->curtime ) { if ( m_flPower < m_flMaxPower ) { SetPower( m_flPower + m_flRechargeAmount ); } m_flNextRechargeTime = gpGlobals->curtime + m_flRechargeTime; } // Let derived objects think if they want to if ( m_pfnThink ) { (this->*m_pfnThink)(); } } //----------------------------------------------------------------------------- // Indicates the shield has been EMPed (or not) //----------------------------------------------------------------------------- void CShield::SetEMPed( bool isEmped ) { m_bIsEMPed = isEmped; } //----------------------------------------------------------------------------- // Helper method for collision testing //----------------------------------------------------------------------------- #pragma warning ( disable : 4701 ) bool CShield::TestCollision( const Ray_t& ray, unsigned int mask, trace_t& trace ) { // Can't block anything if we're EMPed, or we've got no power left to block if ( IsEMPed() ) return false; if ( m_flPower <= 0 ) return false; // Here, we're gonna test for collision. // If we don't stop this kind of bullet, we'll generate an effect here // but we won't change the trace to indicate a collision. // It's just polygon soup... int hitgroup; bool firstTri; int v1[2], v2[2], v3[2]; float ihit, jhit; float mint = FLT_MAX; float t; int h = Height(); int w = Width(); for (int i = 0; i < h - 1; ++i) { for (int j = 0; j < w - 1; ++j) { // Don't test if this panel ain't active... if (!IsPanelActive( j, i )) continue; // NOTE: Structure order of points so that our barycentric // axes for each triangle are along the (u,v) directions of the mesh // The barycentric coords we'll need below // Two triangles per quad... t = IntersectRayWithTriangle( ray, GetPoint( j, i + 1 ), GetPoint( j + 1, i + 1 ), GetPoint( j, i ), true ); if ((t >= 0.0f) && (t < mint)) { mint = t; v1[0] = j; v1[1] = i + 1; v2[0] = j + 1; v2[1] = i + 1; v3[0] = j; v3[1] = i; ihit = i; jhit = j; firstTri = true; } t = IntersectRayWithTriangle( ray, GetPoint( j + 1, i ), GetPoint( j, i ), GetPoint( j + 1, i + 1 ), true ); if ((t >= 0.0f) && (t < mint)) { mint = t; v1[0] = j + 1; v1[1] = i; v2[0] = j; v2[1] = i; v3[0] = j + 1; v3[1] = i + 1; ihit = i; jhit = j; firstTri = false; } } } if (mint == FLT_MAX) return false; // Stuff the barycentric coordinates of the triangle hit into the hit group // For the first triangle, the first edge goes along u, the second edge goes // along -v. For the second triangle, the first edge goes along -u, // the second edge goes along v. const Vector& v1vec = GetPoint(v1[0], v1[1]); const Vector& v2vec = GetPoint(v2[0], v2[1]); const Vector& v3vec = GetPoint(v3[0], v3[1]); float u, v; bool ok = ComputeIntersectionBarycentricCoordinates( ray, v1vec, v2vec, v3vec, u, v ); Assert( ok ); if ( !ok ) { return false; } if (firstTri) v = 1.0 - v; else u = 1.0 - u; v += ihit; u += jhit; v /= (h - 1); u /= (w - 1); // Compress (u,v) into 1 dot 15, v in top bits hitgroup = (((int)(v * (1 << 15))) << 16) + (int)(u * (1 << 15)); Vector normal; float intercept; ComputeTrianglePlane( v1vec, v2vec, v3vec, normal, intercept ); UTIL_SetTrace( trace, ray, edict(), mint, hitgroup, CONTENTS_SOLID, normal, intercept ); VectorAdd( trace.startpos, ray.m_StartOffset, trace.startpos ); VectorAdd( trace.endpos, ray.m_StartOffset, trace.endpos ); return true; } #pragma warning ( default : 4701 )