diff options
Diffstat (limited to 'game/server/hl2/grenade_frag.cpp')
| -rw-r--r-- | game/server/hl2/grenade_frag.cpp | 453 |
1 files changed, 453 insertions, 0 deletions
diff --git a/game/server/hl2/grenade_frag.cpp b/game/server/hl2/grenade_frag.cpp new file mode 100644 index 0000000..1788d19 --- /dev/null +++ b/game/server/hl2/grenade_frag.cpp @@ -0,0 +1,453 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "basegrenade_shared.h" +#include "grenade_frag.h" +#include "Sprite.h" +#include "SpriteTrail.h" +#include "soundent.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#define FRAG_GRENADE_BLIP_FREQUENCY 1.0f +#define FRAG_GRENADE_BLIP_FAST_FREQUENCY 0.3f + +#define FRAG_GRENADE_GRACE_TIME_AFTER_PICKUP 1.5f +#define FRAG_GRENADE_WARN_TIME 1.5f + +const float GRENADE_COEFFICIENT_OF_RESTITUTION = 0.2f; + +ConVar sk_plr_dmg_fraggrenade ( "sk_plr_dmg_fraggrenade","0"); +ConVar sk_npc_dmg_fraggrenade ( "sk_npc_dmg_fraggrenade","0"); +ConVar sk_fraggrenade_radius ( "sk_fraggrenade_radius", "0"); + +#define GRENADE_MODEL "models/Weapons/w_grenade.mdl" + +class CGrenadeFrag : public CBaseGrenade +{ + DECLARE_CLASS( CGrenadeFrag, CBaseGrenade ); + +#if !defined( CLIENT_DLL ) + DECLARE_DATADESC(); +#endif + + ~CGrenadeFrag( void ); + +public: + void Spawn( void ); + void OnRestore( void ); + void Precache( void ); + bool CreateVPhysics( void ); + void CreateEffects( void ); + void SetTimer( float detonateDelay, float warnDelay ); + void SetVelocity( const Vector &velocity, const AngularImpulse &angVelocity ); + int OnTakeDamage( const CTakeDamageInfo &inputInfo ); + void BlipSound() { EmitSound( "Grenade.Blip" ); } + void DelayThink(); + void VPhysicsUpdate( IPhysicsObject *pPhysics ); + void OnPhysGunPickup( CBasePlayer *pPhysGunUser, PhysGunPickup_t reason ); + void SetCombineSpawned( bool combineSpawned ) { m_combineSpawned = combineSpawned; } + bool IsCombineSpawned( void ) const { return m_combineSpawned; } + void SetPunted( bool punt ) { m_punted = punt; } + bool WasPunted( void ) const { return m_punted; } + + // this function only used in episodic. +#if defined(HL2_EPISODIC) && 0 // FIXME: HandleInteraction() is no longer called now that base grenade derives from CBaseAnimating + bool HandleInteraction(int interactionType, void *data, CBaseCombatCharacter* sourceEnt); +#endif + + void InputSetTimer( inputdata_t &inputdata ); + +protected: + CHandle<CSprite> m_pMainGlow; + CHandle<CSpriteTrail> m_pGlowTrail; + + float m_flNextBlipTime; + bool m_inSolid; + bool m_combineSpawned; + bool m_punted; +}; + +LINK_ENTITY_TO_CLASS( npc_grenade_frag, CGrenadeFrag ); + +BEGIN_DATADESC( CGrenadeFrag ) + + // Fields + DEFINE_FIELD( m_pMainGlow, FIELD_EHANDLE ), + DEFINE_FIELD( m_pGlowTrail, FIELD_EHANDLE ), + DEFINE_FIELD( m_flNextBlipTime, FIELD_TIME ), + DEFINE_FIELD( m_inSolid, FIELD_BOOLEAN ), + DEFINE_FIELD( m_combineSpawned, FIELD_BOOLEAN ), + DEFINE_FIELD( m_punted, FIELD_BOOLEAN ), + + // Function Pointers + DEFINE_THINKFUNC( DelayThink ), + + // Inputs + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetTimer", InputSetTimer ), + +END_DATADESC() + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CGrenadeFrag::~CGrenadeFrag( void ) +{ +} + +void CGrenadeFrag::Spawn( void ) +{ + Precache( ); + + SetModel( GRENADE_MODEL ); + + if( GetOwnerEntity() && GetOwnerEntity()->IsPlayer() ) + { + m_flDamage = sk_plr_dmg_fraggrenade.GetFloat(); + m_DmgRadius = sk_fraggrenade_radius.GetFloat(); + } + else + { + m_flDamage = sk_npc_dmg_fraggrenade.GetFloat(); + m_DmgRadius = sk_fraggrenade_radius.GetFloat(); + } + + m_takedamage = DAMAGE_YES; + m_iHealth = 1; + + SetSize( -Vector(4,4,4), Vector(4,4,4) ); + SetCollisionGroup( COLLISION_GROUP_WEAPON ); + CreateVPhysics(); + + BlipSound(); + m_flNextBlipTime = gpGlobals->curtime + FRAG_GRENADE_BLIP_FREQUENCY; + + AddSolidFlags( FSOLID_NOT_STANDABLE ); + + m_combineSpawned = false; + m_punted = false; + + BaseClass::Spawn(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CGrenadeFrag::OnRestore( void ) +{ + // If we were primed and ready to detonate, put FX on us. + if (m_flDetonateTime > 0) + CreateEffects(); + + BaseClass::OnRestore(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CGrenadeFrag::CreateEffects( void ) +{ + // Start up the eye glow + m_pMainGlow = CSprite::SpriteCreate( "sprites/redglow1.vmt", GetLocalOrigin(), false ); + + int nAttachment = LookupAttachment( "fuse" ); + + if ( m_pMainGlow != NULL ) + { + m_pMainGlow->FollowEntity( this ); + m_pMainGlow->SetAttachment( this, nAttachment ); + m_pMainGlow->SetTransparency( kRenderGlow, 255, 255, 255, 200, kRenderFxNoDissipation ); + m_pMainGlow->SetScale( 0.2f ); + m_pMainGlow->SetGlowProxySize( 4.0f ); + } + + // Start up the eye trail + m_pGlowTrail = CSpriteTrail::SpriteTrailCreate( "sprites/bluelaser1.vmt", GetLocalOrigin(), false ); + + if ( m_pGlowTrail != NULL ) + { + m_pGlowTrail->FollowEntity( this ); + m_pGlowTrail->SetAttachment( this, nAttachment ); + m_pGlowTrail->SetTransparency( kRenderTransAdd, 255, 0, 0, 255, kRenderFxNone ); + m_pGlowTrail->SetStartWidth( 8.0f ); + m_pGlowTrail->SetEndWidth( 1.0f ); + m_pGlowTrail->SetLifeTime( 0.5f ); + } +} + +bool CGrenadeFrag::CreateVPhysics() +{ + // Create the object in the physics system + VPhysicsInitNormal( SOLID_BBOX, 0, false ); + return true; +} + +// this will hit only things that are in newCollisionGroup, but NOT in collisionGroupAlreadyChecked +class CTraceFilterCollisionGroupDelta : public CTraceFilterEntitiesOnly +{ +public: + // It does have a base, but we'll never network anything below here.. + DECLARE_CLASS_NOBASE( CTraceFilterCollisionGroupDelta ); + + CTraceFilterCollisionGroupDelta( const IHandleEntity *passentity, int collisionGroupAlreadyChecked, int newCollisionGroup ) + : m_pPassEnt(passentity), m_collisionGroupAlreadyChecked( collisionGroupAlreadyChecked ), m_newCollisionGroup( newCollisionGroup ) + { + } + + virtual bool ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask ) + { + if ( !PassServerEntityFilter( pHandleEntity, m_pPassEnt ) ) + return false; + CBaseEntity *pEntity = EntityFromEntityHandle( pHandleEntity ); + + if ( pEntity ) + { + if ( g_pGameRules->ShouldCollide( m_collisionGroupAlreadyChecked, pEntity->GetCollisionGroup() ) ) + return false; + if ( g_pGameRules->ShouldCollide( m_newCollisionGroup, pEntity->GetCollisionGroup() ) ) + return true; + } + + return false; + } + +protected: + const IHandleEntity *m_pPassEnt; + int m_collisionGroupAlreadyChecked; + int m_newCollisionGroup; +}; + +void CGrenadeFrag::VPhysicsUpdate( IPhysicsObject *pPhysics ) +{ + BaseClass::VPhysicsUpdate( pPhysics ); + Vector vel; + AngularImpulse angVel; + pPhysics->GetVelocity( &vel, &angVel ); + + Vector start = GetAbsOrigin(); + // find all entities that my collision group wouldn't hit, but COLLISION_GROUP_NONE would and bounce off of them as a ray cast + CTraceFilterCollisionGroupDelta filter( this, GetCollisionGroup(), COLLISION_GROUP_NONE ); + trace_t tr; + + // UNDONE: Hull won't work with hitboxes - hits outer hull. But the whole point of this test is to hit hitboxes. +#if 0 + UTIL_TraceHull( start, start + vel * gpGlobals->frametime, CollisionProp()->OBBMins(), CollisionProp()->OBBMaxs(), CONTENTS_HITBOX|CONTENTS_MONSTER|CONTENTS_SOLID, &filter, &tr ); +#else + UTIL_TraceLine( start, start + vel * gpGlobals->frametime, CONTENTS_HITBOX|CONTENTS_MONSTER|CONTENTS_SOLID, &filter, &tr ); +#endif + if ( tr.startsolid ) + { + if ( !m_inSolid ) + { + // UNDONE: Do a better contact solution that uses relative velocity? + vel *= -GRENADE_COEFFICIENT_OF_RESTITUTION; // bounce backwards + pPhysics->SetVelocity( &vel, NULL ); + } + m_inSolid = true; + return; + } + m_inSolid = false; + if ( tr.DidHit() ) + { + Vector dir = vel; + VectorNormalize(dir); + // send a tiny amount of damage so the character will react to getting bonked + CTakeDamageInfo info( this, GetThrower(), pPhysics->GetMass() * vel, GetAbsOrigin(), 0.1f, DMG_CRUSH ); + tr.m_pEnt->TakeDamage( info ); + + // reflect velocity around normal + vel = -2.0f * tr.plane.normal * DotProduct(vel,tr.plane.normal) + vel; + + // absorb 80% in impact + vel *= GRENADE_COEFFICIENT_OF_RESTITUTION; + angVel *= -0.5f; + pPhysics->SetVelocity( &vel, &angVel ); + } +} + + +void CGrenadeFrag::Precache( void ) +{ + PrecacheModel( GRENADE_MODEL ); + + PrecacheScriptSound( "Grenade.Blip" ); + + PrecacheModel( "sprites/redglow1.vmt" ); + PrecacheModel( "sprites/bluelaser1.vmt" ); + + BaseClass::Precache(); +} + +void CGrenadeFrag::SetTimer( float detonateDelay, float warnDelay ) +{ + m_flDetonateTime = gpGlobals->curtime + detonateDelay; + m_flWarnAITime = gpGlobals->curtime + warnDelay; + SetThink( &CGrenadeFrag::DelayThink ); + SetNextThink( gpGlobals->curtime ); + + CreateEffects(); +} + +void CGrenadeFrag::OnPhysGunPickup( CBasePlayer *pPhysGunUser, PhysGunPickup_t reason ) +{ + SetThrower( pPhysGunUser ); + +#ifdef HL2MP + SetTimer( FRAG_GRENADE_GRACE_TIME_AFTER_PICKUP, FRAG_GRENADE_GRACE_TIME_AFTER_PICKUP / 2); + + BlipSound(); + m_flNextBlipTime = gpGlobals->curtime + FRAG_GRENADE_BLIP_FAST_FREQUENCY; + m_bHasWarnedAI = true; +#else + if( IsX360() ) + { + // Give 'em a couple of seconds to aim and throw. + SetTimer( 2.0f, 1.0f); + BlipSound(); + m_flNextBlipTime = gpGlobals->curtime + FRAG_GRENADE_BLIP_FAST_FREQUENCY; + } +#endif + +#ifdef HL2_EPISODIC + SetPunted( true ); +#endif + + BaseClass::OnPhysGunPickup( pPhysGunUser, reason ); +} + +void CGrenadeFrag::DelayThink() +{ + if( gpGlobals->curtime > m_flDetonateTime ) + { + Detonate(); + return; + } + + if( !m_bHasWarnedAI && gpGlobals->curtime >= m_flWarnAITime ) + { +#if !defined( CLIENT_DLL ) + CSoundEnt::InsertSound ( SOUND_DANGER, GetAbsOrigin(), 400, 1.5, this ); +#endif + m_bHasWarnedAI = true; + } + + if( gpGlobals->curtime > m_flNextBlipTime ) + { + BlipSound(); + + if( m_bHasWarnedAI ) + { + m_flNextBlipTime = gpGlobals->curtime + FRAG_GRENADE_BLIP_FAST_FREQUENCY; + } + else + { + m_flNextBlipTime = gpGlobals->curtime + FRAG_GRENADE_BLIP_FREQUENCY; + } + } + + SetNextThink( gpGlobals->curtime + 0.1 ); +} + +void CGrenadeFrag::SetVelocity( const Vector &velocity, const AngularImpulse &angVelocity ) +{ + IPhysicsObject *pPhysicsObject = VPhysicsGetObject(); + if ( pPhysicsObject ) + { + pPhysicsObject->AddVelocity( &velocity, &angVelocity ); + } +} + +int CGrenadeFrag::OnTakeDamage( const CTakeDamageInfo &inputInfo ) +{ + // Manually apply vphysics because BaseCombatCharacter takedamage doesn't call back to CBaseEntity OnTakeDamage + VPhysicsTakeDamage( inputInfo ); + + // Grenades only suffer blast damage and burn damage. + if( !(inputInfo.GetDamageType() & (DMG_BLAST|DMG_BURN) ) ) + return 0; + + return BaseClass::OnTakeDamage( inputInfo ); +} + +#if defined(HL2_EPISODIC) && 0 // FIXME: HandleInteraction() is no longer called now that base grenade derives from CBaseAnimating +extern int g_interactionBarnacleVictimGrab; ///< usually declared in ai_interactions.h but no reason to haul all of that in here. +extern int g_interactionBarnacleVictimBite; +extern int g_interactionBarnacleVictimReleased; +bool CGrenadeFrag::HandleInteraction(int interactionType, void *data, CBaseCombatCharacter* sourceEnt) +{ + // allow fragnades to be grabbed by barnacles. + if ( interactionType == g_interactionBarnacleVictimGrab ) + { + // give the grenade another five seconds seconds so the player can have the satisfaction of blowing up the barnacle with it + float timer = m_flDetonateTime - gpGlobals->curtime + 5.0f; + SetTimer( timer, timer - FRAG_GRENADE_WARN_TIME ); + + return true; + } + else if ( interactionType == g_interactionBarnacleVictimBite ) + { + // detonate the grenade immediately + SetTimer( 0, 0 ); + return true; + } + else if ( interactionType == g_interactionBarnacleVictimReleased ) + { + // take the five seconds back off the timer. + float timer = MAX(m_flDetonateTime - gpGlobals->curtime - 5.0f,0.0f); + SetTimer( timer, timer - FRAG_GRENADE_WARN_TIME ); + return true; + } + else + { + return BaseClass::HandleInteraction( interactionType, data, sourceEnt ); + } +} +#endif + +void CGrenadeFrag::InputSetTimer( inputdata_t &inputdata ) +{ + SetTimer( inputdata.value.Float(), inputdata.value.Float() - FRAG_GRENADE_WARN_TIME ); +} + +CBaseGrenade *Fraggrenade_Create( const Vector &position, const QAngle &angles, const Vector &velocity, const AngularImpulse &angVelocity, CBaseEntity *pOwner, float timer, bool combineSpawned ) +{ + // Don't set the owner here, or the player can't interact with grenades he's thrown + CGrenadeFrag *pGrenade = (CGrenadeFrag *)CBaseEntity::Create( "npc_grenade_frag", position, angles, pOwner ); + + pGrenade->SetTimer( timer, timer - FRAG_GRENADE_WARN_TIME ); + pGrenade->SetVelocity( velocity, angVelocity ); + pGrenade->SetThrower( ToBaseCombatCharacter( pOwner ) ); + pGrenade->m_takedamage = DAMAGE_EVENTS_ONLY; + pGrenade->SetCombineSpawned( combineSpawned ); + + return pGrenade; +} + +bool Fraggrenade_WasPunted( const CBaseEntity *pEntity ) +{ + const CGrenadeFrag *pFrag = dynamic_cast<const CGrenadeFrag *>( pEntity ); + if ( pFrag ) + { + return pFrag->WasPunted(); + } + + return false; +} + +bool Fraggrenade_WasCreatedByCombine( const CBaseEntity *pEntity ) +{ + const CGrenadeFrag *pFrag = dynamic_cast<const CGrenadeFrag *>( pEntity ); + if ( pFrag ) + { + return pFrag->IsCombineSpawned(); + } + + return false; +}
\ No newline at end of file |