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.
451 lines
13 KiB
451 lines
13 KiB
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
|
|
//
|
|
// Purpose:
|
|
//
|
|
//=============================================================================//
|
|
|
|
#include "cbase.h"
|
|
#include "flashbang_projectile.h"
|
|
#include "shake.h"
|
|
#include "engine/IEngineSound.h"
|
|
#include "cs_player.h"
|
|
#include "dlight.h"
|
|
#include "keyvalues.h"
|
|
#include "weapon_csbase.h"
|
|
#include "cs_gamerules.h"
|
|
#include "animation.h"
|
|
|
|
#define GRENADE_MODEL "models/Weapons/w_eq_flashbang_dropped.mdl"
|
|
|
|
|
|
LINK_ENTITY_TO_CLASS( flashbang_projectile, CFlashbangProjectile );
|
|
PRECACHE_REGISTER( flashbang_projectile );
|
|
|
|
#if !defined( CLIENT_DLL )
|
|
BEGIN_DATADESC( CFlashbangProjectile )
|
|
|
|
// Fields
|
|
//DEFINE_KEYFIELD( m_flTimeToDetonate, FIELD_FLOAT, "TimeToDetonate" ),
|
|
|
|
// Inputs
|
|
DEFINE_INPUTFUNC( FIELD_FLOAT, "SetTimer", InputSetTimer ),
|
|
|
|
END_DATADESC()
|
|
#endif
|
|
|
|
// hack to allow de_nuke vents to occlude flashbangs when closed
|
|
class CTraceFilterNoPlayersAndFlashbangPassableAnims : public CTraceFilterNoPlayers
|
|
{
|
|
public:
|
|
CTraceFilterNoPlayersAndFlashbangPassableAnims( const IHandleEntity *passentity = NULL, int collisionGroup = COLLISION_GROUP_NONE )
|
|
: CTraceFilterNoPlayers( passentity, collisionGroup )
|
|
{
|
|
}
|
|
|
|
virtual bool ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask )
|
|
{
|
|
|
|
CBaseEntity *pEnt = EntityFromEntityHandle(pHandleEntity);
|
|
if ( pEnt )
|
|
{
|
|
CBaseAnimating* pAnimating = dynamic_cast< CBaseAnimating* >( pEnt );
|
|
if ( pAnimating )
|
|
{
|
|
// look for the flashbang passable animtag
|
|
float flFlashbangPassable = pAnimating->GetAnySequenceAnimTag( pAnimating->GetSequence(), ANIMTAG_FLASHBANG_PASSABLE, -1 );
|
|
|
|
if ( flFlashbangPassable != -1 )
|
|
return false; // model animation is tagged to allow flashbangs through
|
|
}
|
|
|
|
// Weapons don't block flashbangs
|
|
CWeaponCSBase* pWeapon = dynamic_cast< CWeaponCSBase* >( pEnt );
|
|
CBaseGrenade* pGrenade = dynamic_cast< CBaseGrenade* > ( pEnt );
|
|
if ( pWeapon || pGrenade )
|
|
return false;
|
|
}
|
|
|
|
return CTraceFilterNoPlayers::ShouldHitEntity( pHandleEntity, contentsMask );
|
|
}
|
|
};
|
|
|
|
float PercentageOfFlashForPlayer(CBaseEntity *player, Vector flashPos, CBaseEntity *pevInflictor)
|
|
{
|
|
if (!(player->IsPlayer()))
|
|
{
|
|
// if this entity isn't a player, it's a hostage or some other entity, then don't bother with the expensive checks
|
|
// that come below.
|
|
return 0.0f;
|
|
}
|
|
|
|
const float FLASH_FRACTION = 0.167f;
|
|
const float SIDE_OFFSET = 75.0f;
|
|
|
|
Vector pos = player->EyePosition();
|
|
Vector vecRight, vecUp;
|
|
|
|
QAngle tempAngle;
|
|
VectorAngles(player->EyePosition() - flashPos, tempAngle);
|
|
AngleVectors(tempAngle, NULL, &vecRight, &vecUp);
|
|
|
|
vecRight.NormalizeInPlace();
|
|
vecUp.NormalizeInPlace();
|
|
|
|
// Set up all the ray stuff.
|
|
// We don't want to let other players block the flash bang so we use this custom filter.
|
|
Ray_t ray;
|
|
trace_t tr;
|
|
CTraceFilterNoPlayersAndFlashbangPassableAnims traceFilter( pevInflictor, COLLISION_GROUP_NONE );
|
|
unsigned int FLASH_MASK = MASK_OPAQUE_AND_NPCS | CONTENTS_DEBRIS;
|
|
|
|
// According to comment in IsNoDrawBrush in cmodel.cpp, CONTENTS_OPAQUE is ONLY used for block light surfaces,
|
|
// and we want flashbang traces to pass through those, since the block light surface is only used for blocking
|
|
// lightmap light rays during map compilation.
|
|
FLASH_MASK &= ~CONTENTS_OPAQUE;
|
|
|
|
ray.Init( flashPos, pos );
|
|
enginetrace->TraceRay( ray, FLASH_MASK, &traceFilter, &tr );
|
|
|
|
if ((tr.fraction == 1.0f) || (tr.m_pEnt == player))
|
|
{
|
|
return 1.0f;
|
|
}
|
|
|
|
float retval = 0.0f;
|
|
|
|
// check the point straight up.
|
|
pos = flashPos + vecUp*50.0f;
|
|
ray.Init( flashPos, pos );
|
|
enginetrace->TraceRay( ray, FLASH_MASK, &traceFilter, &tr );
|
|
// Now shoot it to the player's eye.
|
|
pos = player->EyePosition();
|
|
ray.Init( tr.endpos, pos );
|
|
enginetrace->TraceRay( ray, FLASH_MASK, &traceFilter, &tr );
|
|
|
|
if ((tr.fraction == 1.0f) || (tr.m_pEnt == player))
|
|
{
|
|
retval += FLASH_FRACTION;
|
|
}
|
|
|
|
// check the point up and right.
|
|
pos = flashPos + vecRight*SIDE_OFFSET + vecUp*10.0f;
|
|
ray.Init( flashPos, pos );
|
|
enginetrace->TraceRay( ray, FLASH_MASK, &traceFilter, &tr );
|
|
// Now shoot it to the player's eye.
|
|
pos = player->EyePosition();
|
|
ray.Init( tr.endpos, pos );
|
|
enginetrace->TraceRay( ray, FLASH_MASK, &traceFilter, &tr );
|
|
|
|
if ((tr.fraction == 1.0f) || (tr.m_pEnt == player))
|
|
{
|
|
retval += FLASH_FRACTION;
|
|
}
|
|
|
|
// Check the point up and left.
|
|
pos = flashPos - vecRight*SIDE_OFFSET + vecUp*10.0f;
|
|
ray.Init( flashPos, pos );
|
|
enginetrace->TraceRay( ray, FLASH_MASK, &traceFilter, &tr );
|
|
// Now shoot it to the player's eye.
|
|
pos = player->EyePosition();
|
|
ray.Init( tr.endpos, pos );
|
|
enginetrace->TraceRay( ray, FLASH_MASK, &traceFilter, &tr );
|
|
|
|
if ((tr.fraction == 1.0f) || (tr.m_pEnt == player))
|
|
{
|
|
retval += FLASH_FRACTION;
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------------------------- //
|
|
//
|
|
// RadiusDamage - this entity is exploding, or otherwise needs to inflict damage upon entities within a certain range.
|
|
//
|
|
// only damage ents that can clearly be seen by the explosion!
|
|
// --------------------------------------------------------------------------------------------------- //
|
|
|
|
void RadiusFlash(
|
|
Vector vecSrc,
|
|
CBaseEntity *pevInflictor,
|
|
CBaseEntity *pevAttacker,
|
|
float flDamage,
|
|
int iClassIgnore,
|
|
int bitsDamageType,
|
|
uint8 *pOutNumOpponentsEffected = NULL,
|
|
uint8 *pOutNumTeammatesEffected = NULL )
|
|
{
|
|
vecSrc.z += 1;// in case grenade is lying on the ground
|
|
|
|
if ( !pevAttacker )
|
|
pevAttacker = pevInflictor;
|
|
|
|
if ( pOutNumOpponentsEffected )
|
|
*pOutNumOpponentsEffected = 0;
|
|
|
|
if ( pOutNumTeammatesEffected )
|
|
*pOutNumTeammatesEffected = 0;
|
|
|
|
trace_t tr;
|
|
float flAdjustedDamage;
|
|
variant_t var;
|
|
Vector vecEyePos;
|
|
float fadeTime, fadeHold;
|
|
Vector vForward;
|
|
Vector vecLOS;
|
|
float flDot;
|
|
|
|
CBaseEntity *pEntity = NULL;
|
|
static float flRadius = 3000;
|
|
float falloff = flDamage / flRadius;
|
|
|
|
//bool bInWater = (UTIL_PointContents( vecSrc, MASK_WATER ) == CONTENTS_WATER);
|
|
|
|
// iterate on all entities in the vicinity.
|
|
while ((pEntity = gEntList.FindEntityInSphere( pEntity, vecSrc, flRadius )) != NULL)
|
|
{
|
|
bool bPlayer = pEntity->IsPlayer();
|
|
|
|
if( !bPlayer )
|
|
continue;
|
|
|
|
vecEyePos = pEntity->EyePosition();
|
|
|
|
//// blasts used to not travel into or out of water, users assumed it was a bug. Fix is not to run this check -wills
|
|
//if ( bInWater && pEntity->GetWaterLevel() == WL_NotInWater)
|
|
// continue;
|
|
//if (!bInWater && pEntity->GetWaterLevel() == WL_Eyes)
|
|
// continue;
|
|
|
|
float percentageOfFlash = PercentageOfFlashForPlayer(pEntity, vecSrc, pevInflictor);
|
|
|
|
if ( percentageOfFlash > 0.0 )
|
|
{
|
|
if ( pOutNumOpponentsEffected && pEntity->GetTeamNumber() != pevAttacker->GetTeamNumber() )
|
|
(*pOutNumOpponentsEffected)++;
|
|
if ( pOutNumTeammatesEffected && pEntity->GetTeamNumber() == pevAttacker->GetTeamNumber() )
|
|
(*pOutNumTeammatesEffected)++;
|
|
|
|
// decrease damage for an ent that's farther from the grenade
|
|
flAdjustedDamage = flDamage - ( vecSrc - pEntity->EyePosition() ).Length() * falloff;
|
|
|
|
if ( flAdjustedDamage > 0 )
|
|
{
|
|
// See if we were facing the flash
|
|
AngleVectors( pEntity->EyeAngles(), &vForward );
|
|
|
|
vecLOS = ( vecSrc - vecEyePos );
|
|
|
|
float flDistance = vecLOS.Length();
|
|
|
|
//DebugDrawLine( vecEyePos, vecEyePos + (100.0 * vecLOS), 0, 255, 0, true, 10.0 );
|
|
//DebugDrawLine( vecEyePos, vecEyePos + (100.0 * vForward), 0, 0, 255, true, 10.0 );
|
|
|
|
// Normalize both vectors so the dotproduct is in the range -1.0 <= x <= 1.0
|
|
vecLOS.NormalizeInPlace();
|
|
|
|
|
|
flDot = DotProduct (vecLOS, vForward);
|
|
|
|
float startingAlpha = 255;
|
|
|
|
// if target is facing the bomb, the effect lasts longer
|
|
if( flDot >= 0.6 )
|
|
{
|
|
// looking at the flashbang
|
|
fadeTime = flAdjustedDamage * 2.5f;
|
|
fadeHold = flAdjustedDamage * 1.25f;
|
|
}
|
|
else if( flDot >= 0.3 )
|
|
{
|
|
// looking to the side
|
|
fadeTime = flAdjustedDamage * 1.75f;
|
|
fadeHold = flAdjustedDamage * 0.8f;
|
|
}
|
|
else if( flDot >= -0.2 )
|
|
{
|
|
// looking to the side
|
|
fadeTime = flAdjustedDamage * 1.00f;
|
|
fadeHold = flAdjustedDamage * 0.5f;
|
|
}
|
|
else
|
|
{
|
|
// facing away
|
|
fadeTime = flAdjustedDamage * 0.5f;
|
|
fadeHold = flAdjustedDamage * 0.25f;
|
|
// startingAlpha = 200;
|
|
}
|
|
|
|
fadeTime *= percentageOfFlash;
|
|
fadeHold *= percentageOfFlash;
|
|
|
|
if ( bPlayer )
|
|
{
|
|
// blind players and bots
|
|
CCSPlayer *player = static_cast< CCSPlayer * >( pEntity );
|
|
|
|
// [tj] Store who was responsible for the most recent flashbang blinding.
|
|
CCSPlayer *attacker = ToCSPlayer (pevAttacker);
|
|
if ( attacker && player && player->IsAlive() )
|
|
{
|
|
player->SetLastFlashbangAttacker(attacker);
|
|
|
|
// score points/penalties for blinding players
|
|
if ( flDot >= 0.0f )
|
|
{
|
|
if ( attacker->GetTeamNumber() == player->GetTeamNumber() )
|
|
CSGameRules()->ScoreBlindFriendly( attacker );
|
|
else
|
|
CSGameRules()->ScoreBlindEnemy( attacker );
|
|
}
|
|
}
|
|
|
|
player->Blind( fadeHold, fadeTime, startingAlpha );
|
|
|
|
// fire an event when a player has been sufficiently blinded as to not
|
|
// be able to perform the training map flashbang range test
|
|
if ( CSGameRules()->IsPlayingTraining() && fadeHold > 1.9f )
|
|
{
|
|
IGameEvent * event = gameeventmanager->CreateEvent( "tr_player_flashbanged" );
|
|
if ( event )
|
|
{
|
|
event->SetInt( "userid", player->GetUserID() );
|
|
gameeventmanager->FireEvent( event );
|
|
}
|
|
}
|
|
|
|
IGameEvent * event = gameeventmanager->CreateEvent( "player_blind" );
|
|
if ( event )
|
|
{
|
|
event->SetInt( "userid", player->GetUserID() );
|
|
event->SetInt( "attacker", attacker ? attacker->GetUserID() : 0 );
|
|
event->SetInt( "entityid", pevInflictor ? pevInflictor->entindex() : 0 );
|
|
event->SetFloat( "blind_duration", player->m_flFlashDuration );
|
|
gameeventmanager->FireEvent( event );
|
|
}
|
|
|
|
// deafen players and bots
|
|
player->Deafen( flDistance );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
CPVSFilter filter(vecSrc);
|
|
te->DynamicLight( filter, 0.0, &vecSrc, 255, 255, 255, 2, 400, 0.1, 768 );
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------------------------- //
|
|
// CFlashbangProjectile implementation.
|
|
// --------------------------------------------------------------------------------------------------- //
|
|
|
|
CFlashbangProjectile* CFlashbangProjectile::Create(
|
|
const Vector &position,
|
|
const QAngle &angles,
|
|
const Vector &velocity,
|
|
const AngularImpulse &angVelocity,
|
|
CBaseCombatCharacter *pOwner,
|
|
const CCSWeaponInfo& weaponInfo )
|
|
{
|
|
CFlashbangProjectile *pGrenade = (CFlashbangProjectile*)CBaseEntity::Create( "flashbang_projectile", position, angles, pOwner );
|
|
|
|
// Set the timer for 1 second less than requested. We're going to issue a SOUND_DANGER
|
|
// one second before detonation.
|
|
pGrenade->SetAbsVelocity( velocity );
|
|
pGrenade->SetupInitialTransmittedGrenadeVelocity( velocity );
|
|
pGrenade->SetThrower( pOwner );
|
|
pGrenade->m_pWeaponInfo = &weaponInfo;
|
|
|
|
pGrenade->ChangeTeam( pOwner->GetTeamNumber() );
|
|
|
|
pGrenade->ApplyLocalAngularVelocityImpulse( angVelocity );
|
|
pGrenade->SetCollisionGroup( COLLISION_GROUP_PROJECTILE );
|
|
return pGrenade;
|
|
}
|
|
|
|
CFlashbangProjectile::CFlashbangProjectile()
|
|
{
|
|
m_flDamage = 100;
|
|
// default timer value for when a player throws it
|
|
// can be overridden when spawned from an entity maker
|
|
m_flTimeToDetonate = 1.5;
|
|
m_numOpponentsHit = m_numTeammatesHit = 0;
|
|
}
|
|
|
|
void CFlashbangProjectile::Spawn()
|
|
{
|
|
SetModel( GRENADE_MODEL );
|
|
|
|
SetDetonateTimerLength( m_flTimeToDetonate );
|
|
|
|
SetTouch( &CBaseGrenade::BounceTouch );
|
|
|
|
SetThink( &CBaseCSGrenadeProjectile::DangerSoundThink );
|
|
SetNextThink( gpGlobals->curtime );
|
|
|
|
SetGravity( BaseClass::GetGrenadeGravity() );
|
|
SetFriction( BaseClass::GetGrenadeFriction() );
|
|
SetElasticity( BaseClass::GetGrenadeElasticity() );
|
|
|
|
m_pWeaponInfo = GetWeaponInfo( WEAPON_FLASHBANG );
|
|
|
|
BaseClass::Spawn();
|
|
|
|
SetBodygroupPreset( "thrown" );
|
|
}
|
|
|
|
void CFlashbangProjectile::Precache()
|
|
{
|
|
PrecacheModel( GRENADE_MODEL );
|
|
|
|
PrecacheScriptSound( "Flashbang.Explode" );
|
|
PrecacheScriptSound( "Flashbang.Bounce" );
|
|
|
|
BaseClass::Precache();
|
|
}
|
|
|
|
ConVar sv_flashbang_strength( "sv_flashbang_strength", "3.55", FCVAR_REPLICATED, "Flashbang strength", true, 2.0, true, 8.0 );
|
|
|
|
void CFlashbangProjectile::Detonate()
|
|
{
|
|
// tell the bots a flashbang grenade has exploded (and record log events)
|
|
CCSPlayer *player = ToCSPlayer( GetThrower() );
|
|
if ( player )
|
|
{
|
|
IGameEvent * event = gameeventmanager->CreateEvent( "flashbang_detonate" );
|
|
if ( event )
|
|
{
|
|
event->SetInt( "userid", player->GetUserID() );
|
|
event->SetInt( "entityid", this->entindex() );
|
|
event->SetFloat( "x", GetAbsOrigin().x );
|
|
event->SetFloat( "y", GetAbsOrigin().y );
|
|
event->SetFloat( "z", GetAbsOrigin().z );
|
|
gameeventmanager->FireEvent( event );
|
|
}
|
|
}
|
|
|
|
RadiusFlash ( GetAbsOrigin(), this, GetThrower(), sv_flashbang_strength.GetInt(), CLASS_NONE, DMG_BLAST, &m_numOpponentsHit, &m_numTeammatesHit );
|
|
EmitSound( "Flashbang.Explode" );
|
|
|
|
trace_t tr;
|
|
Vector vecSpot = GetAbsOrigin() + Vector ( 0 , 0 , 2 );
|
|
UTIL_TraceLine ( vecSpot, vecSpot + Vector ( 0, 0, -64 ), MASK_SHOT_HULL, this, COLLISION_GROUP_NONE, & tr);
|
|
UTIL_DecalTrace( &tr, "Scorch" );
|
|
|
|
// Because we don't chain to base, tell ogs to record this detonation here
|
|
RecordDetonation();
|
|
|
|
UTIL_Remove( this );
|
|
}
|
|
|
|
//TODO: Let physics handle the sound!
|
|
void CFlashbangProjectile::BounceSound( void )
|
|
{
|
|
EmitSound( "Flashbang.Bounce" );
|
|
}
|
|
|
|
void CFlashbangProjectile::InputSetTimer( inputdata_t &inputdata )
|
|
{
|
|
m_flTimeToDetonate = inputdata.value.Float();
|
|
SetDetonateTimerLength( m_flTimeToDetonate );
|
|
}
|