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.
455 lines
13 KiB
455 lines
13 KiB
//========= 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 |
|
if( !m_pMainGlow ) |
|
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 |
|
if( !m_pGlowTrail ) |
|
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; |
|
}
|
|
|