//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// // // Purpose: // //=============================================================================// #include "cbase.h" #include "sensorgrenade_projectile.h" #include "engine/IEngineSound.h" #include "keyvalues.h" #include "weapon_csbase.h" #include "particle_parse.h" #if defined( CLIENT_DLL ) #include "c_cs_player.h" #else #include "sendproxy.h" #include "cs_player.h" #include "bot_manager.h" #include "cs_bot.h" #endif // NOTE: This has to be the last file included! #include "tier0/memdbgon.h" #if defined( CLIENT_DLL ) IMPLEMENT_CLIENTCLASS_DT( C_SensorGrenadeProjectile, DT_SensorGrenadeProjectile, CSensorGrenadeProjectile ) END_RECV_TABLE() //-------------------------------------------------------------------------------------------------------- void C_SensorGrenadeProjectile::OnNewParticleEffect( const char *pszParticleName, CNewParticleEffect *pNewParticleEffect ) { if ( FStrEq( pszParticleName, "weapon_sensorgren_detlight" ) ) { m_sensorgrenadeParticleEffect = pNewParticleEffect; } } //-------------------------------------------------------------------------------------------------------- void C_SensorGrenadeProjectile::OnParticleEffectDeleted( CNewParticleEffect *pParticleEffect ) { if ( m_sensorgrenadeParticleEffect == pParticleEffect ) { m_sensorgrenadeParticleEffect = NULL; } } //-------------------------------------------------------------------------------------------------------- bool C_SensorGrenadeProjectile::Simulate( void ) { // we are still moving if ( GetAbsVelocity().Length() > 0.1f ) { return true; } if ( !m_sensorgrenadeParticleEffect.IsValid() ) { DispatchParticleEffect( "weapon_sensorgren_detlight", PATTACH_POINT_FOLLOW, this, "Wick" ); } else { m_sensorgrenadeParticleEffect->SetSortOrigin( GetAbsOrigin() ); m_sensorgrenadeParticleEffect->SetNeedsBBoxUpdate( true ); } BaseClass::Simulate(); return true; } #else // GAME_DLL #define GRENADE_MODEL "models/Weapons/w_eq_sensorgrenade_thrown.mdl" LINK_ENTITY_TO_CLASS( tagrenade_projectile, CSensorGrenadeProjectile ); PRECACHE_REGISTER( tagrenade_projectile ); IMPLEMENT_SERVERCLASS_ST( CSensorGrenadeProjectile, DT_SensorGrenadeProjectile ) END_SEND_TABLE() BEGIN_DATADESC( CSensorGrenadeProjectile ) DEFINE_THINKFUNC( Think_Arm ), DEFINE_THINKFUNC( Think_Remove ), DEFINE_THINKFUNC( SensorThink ) END_DATADESC() // --------------------------------------------------------------------------------------------------- // // CFlashbangProjectile implementation. // --------------------------------------------------------------------------------------------------- // CSensorGrenadeProjectile* CSensorGrenadeProjectile::Create( const Vector &position, const QAngle &angles, const Vector &velocity, const AngularImpulse &angVelocity, CBaseCombatCharacter *pOwner, const CCSWeaponInfo& weaponInfo ) { CSensorGrenadeProjectile *pGrenade = ( CSensorGrenadeProjectile* )CBaseEntity::Create( "tagrenade_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->SetTimer( 2.0f ); pGrenade->SetAbsVelocity( velocity ); pGrenade->SetupInitialTransmittedGrenadeVelocity( velocity ); pGrenade->SetThrower( pOwner ); pGrenade->m_flDamage = 1.0f; // 25 = 1/4 of HEGrenade Damage pGrenade->m_DmgRadius = pGrenade->m_flDamage * 3.5f; // Changing damage will change the radius pGrenade->ChangeTeam( pOwner->GetTeamNumber() ); pGrenade->SetAbsAngles( pOwner->EyeAngles() + QAngle( -80, 40, 0 ) ); QAngle angRotationVel = QAngle( RandomFloat(100,200), RandomFloat(-100,200), RandomFloat(-100,200) ); pGrenade->SetLocalAngularVelocity( angRotationVel ); pGrenade->SetTouch( &CSensorGrenadeProjectile::BounceTouch ); pGrenade->SetGravity( BaseClass::GetGrenadeGravity() ); pGrenade->SetFriction( BaseClass::GetGrenadeFriction() ); pGrenade->SetElasticity( BaseClass::GetGrenadeElasticity() ); pGrenade->m_pWeaponInfo = &weaponInfo; ASSERT(pOwner != NULL); //pGrenade->SetCollisionGroup( COLLISION_GROUP_PROJECTILE ); return pGrenade; } void CSensorGrenadeProjectile::SetTimer( float timer ) { SetThink( &CSensorGrenadeProjectile::Think_Arm ); SetNextThink( gpGlobals->curtime + timer ); m_fNextDetectPlayerSound = gpGlobals->curtime; TheBots->SetGrenadeRadius( this, 0.0f ); } void CSensorGrenadeProjectile::Think_Arm( void ) { #if 1 if ( GetAbsVelocity().Length() > 0.2f ) { // Still moving. Don't detonate yet. SetNextThink( gpGlobals->curtime + 0.2f ); return; } #endif // 0 m_fExpireTime = gpGlobals->curtime + 2.0f; // TODO: Make this Data Driven SetThink( &CSensorGrenadeProjectile::SensorThink ); //TheBots->SetGrenadeRadius( this, SensorGrenadeGrenadeRadius ); SensorThink(); // This will handling the 'Detonate' } void CSensorGrenadeProjectile::Think_Remove( void ) { UTIL_Remove( this ); } void CSensorGrenadeProjectile::Detonate( void ) { // [mlowrance] The SensorGrenade is handling it's own detonate. Assert(!"SensorGrenade grenade handles its own detonation"); } void CSensorGrenadeProjectile::SensorThink( void ) { // tell the bots about the gunfire CCSPlayer *pThrower = ToCSPlayer( GetThrower() ); if ( !pThrower ) return; if ( gpGlobals->curtime > m_fNextDetectPlayerSound ) { EmitSound( "Sensor.WarmupBeep" ); m_fNextDetectPlayerSound = gpGlobals->curtime + 1.0f; // TODO: Make this Data Driven } if ( gpGlobals->curtime < m_fExpireTime ) { SetNextThink( gpGlobals->curtime + 0.1f ); } else { // [mlowrance] Do the damage on Despawn and post event CCSPlayer *player = ToCSPlayer( GetThrower() ); if ( player ) { IGameEvent * event = gameeventmanager->CreateEvent( "tagrenade_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 ); } } TheBots->RemoveGrenade( this ); DispatchParticleEffect( "weapon_sensorgren_detonate", PATTACH_POINT, this, "Wick" ); EmitSound( "Sensor.Detonate" ); DoDetectWave(); //BaseClass::Detonate(); } } void CSensorGrenadeProjectile::DoDetectWave( void ) { // tell the bots about the gunfire CCSPlayer *pThrower = ToCSPlayer( GetThrower() ); if ( !pThrower ) return; for ( int i = 1; i <= MAX_PLAYERS; i++ ) { CCSPlayer *pPlayer = ToCSPlayer( UTIL_PlayerByIndex( i ) ); if ( !pPlayer || !pPlayer->IsAlive() || !pThrower->IsOtherEnemy( pPlayer ) ) continue; Vector vDelta = pPlayer->EyePosition() - GetAbsOrigin(); float flDistance = vDelta.Length(); float flMaxTraceDist = 1600; if ( flDistance <= flMaxTraceDist ) { trace_t tr; //if ( pCSPlayer->IsAlive() && ( flTargetIDCone > flViewCone ) && !bShowAllNamesForSpec ) { if ( TheCSBots()->IsLineBlockedBySmoke( pPlayer->EyePosition(), GetAbsOrigin(), 1.0f ) ) { // if we are outside half the max dist and don't trace, dont show if ( flDistance > (flMaxTraceDist/2) ) continue; } UTIL_TraceLine( pPlayer->EyePosition(), GetAbsOrigin(), MASK_VISIBLE, pPlayer, COLLISION_GROUP_DEBRIS, &tr ); if ( tr.fraction != 1 ) { trace_t tr2; UTIL_TraceLine( pPlayer->GetAbsOrigin() + Vector( 0, 0, 16 ), GetAbsOrigin(), MASK_VISIBLE, pPlayer, COLLISION_GROUP_DEBRIS, &tr2 ); if ( tr2.fraction != 1 ) { // if we are outside half the max dist and don't trace, dont show if ( flDistance > (flMaxTraceDist/2) ) continue; } } int nThrowerIndex = 0; for ( int i = 1; i <= MAX_PLAYERS; i++ ) { CCSPlayer *pPlayer = ToCSPlayer( UTIL_PlayerByIndex( i ) ); if ( pPlayer == pThrower ) { nThrowerIndex = i; break; } } DebugDrawLine( WorldSpaceCenter(), pPlayer->WorldSpaceCenter(), 90, 0, 0, true, 1.5f ); pPlayer->SetIsSpotted( true ); pPlayer->SetIsSpottedBy( nThrowerIndex ); pPlayer->m_flDetectedByEnemySensorTime = gpGlobals->curtime; pPlayer->Blind( 0.02f, 1.0f, 128 ); pPlayer->EmitSound( "Sensor.WarmupBeep" ); } } } SetNextThink( gpGlobals->curtime + 0.25f ); SetThink( &CSensorGrenadeProjectile::Think_Remove ); } void CSensorGrenadeProjectile::Spawn( void ) { SetModel( GRENADE_MODEL ); BaseClass::Spawn(); SetSolid( SOLID_BBOX ); AddSolidFlags( FSOLID_NOT_STANDABLE ); } void CSensorGrenadeProjectile::Precache( void ) { PrecacheModel( GRENADE_MODEL ); PrecacheScriptSound( "Sensor.Detonate" ); PrecacheScriptSound( "Sensor.WarmupBeep" ); PrecacheScriptSound( "Sensor.Activate" ); PrecacheScriptSound( "Flashbang.Bounce" ); //PrecacheParticleSystem( "weapon_sen_active" ); PrecacheParticleSystem( "weapon_sensorgren_detlight" ); PrecacheParticleSystem( "weapon_sensorgren_detonate" ); BaseClass::Precache(); } void CSensorGrenadeProjectile::BounceTouch( CBaseEntity *other ) { if ( other->IsSolidFlagSet( FSOLID_TRIGGER | FSOLID_VOLUME_CONTENTS ) ) return; // don't hit the guy that launched this grenade if ( other == GetThrower() ) return; if ( FClassnameIs( other, "func_breakable" ) ) { return; } if ( FClassnameIs( other, "func_breakable_surf" ) ) { return; } // don't detonate on ladders if ( FClassnameIs( other, "func_ladder" ) ) { return; } // Deal car alarms direct damage to set them off - flames won't do so if ( FClassnameIs( other, "prop_car_alarm" ) || FClassnameIs( other, "prop_car_glass" ) ) { CTakeDamageInfo info( this, GetThrower(), 10, DMG_GENERIC ); other->OnTakeDamage( info ); } const trace_t &hitTrace = GetTouchTrace(); if ( hitTrace.m_pEnt && hitTrace.m_pEnt->MyCombatCharacterPointer() ) { // don't break if we hit an actor - wait until we hit the environment return; } else { SetAbsVelocity( Vector( 0, 0, 0) ); SetMoveType(MOVETYPE_NONE); SetNextThink( gpGlobals->curtime + 1.0f ); SetThink( &CSensorGrenadeProjectile::Think_Arm ); EmitSound( "Sensor.Activate" ); m_fExpireTime = gpGlobals->curtime + 15.0f; // TODO: Make this Data Driven // stick the grenade onto the target surface using the closest rotational alignment to match the in-flight orientation, // ( like breach charges ) Vector vecSurfNormal = hitTrace.plane.normal.Normalized(); Vector vecProjectileZ = EntityToWorldTransform().GetColumn(Z_AXIS); // sensor grenades can stick on either of two sides, unlike the breach charges. So they don't need to flip when they land on their 'backs'. if ( DotProduct( vecSurfNormal, vecProjectileZ ) < 0 ) vecSurfNormal = -vecSurfNormal; QAngle angSurface; MatrixAngles( ConcatTransforms( QuaternionMatrix( RotateBetween( vecProjectileZ, vecSurfNormal ) ), EntityToWorldTransform() ), angSurface ); SetAbsAngles( angSurface ); //if ( fabs(hitTrace.plane.normal.Dot(Vector(0,0,1))) > 0.65f ) { //get the player forward vector // Vector vecFlatForward; // VectorCopy( pPlayer->Forward(), vecFlatForward ); // vecFlatForward.z = 0; // // //derive c4 forward and right // Vector vecC4Right = CrossProduct( vecFlatForward.Normalized(), hitTrace.plane.normal ); // Vector vecC4Forward = CrossProduct( vecC4Right, trPlant.plane.normal ); //QAngle angle; //VectorAngles( hitTrace.plane.normal, angle ); //SetAbsAngles( angle ); //m_hDisplayGrenade = CreatePhysicsProp( GRENADE_MODEL, GetAbsOrigin(), GetAbsOrigin(), NULL, false, "prop_physics_multiplayer" ); /* m_hDisplayGrenade = ( CBreakableProp * )CBaseEntity::CreateNoSpawn( "prop_physics", GetAbsOrigin(), angle ); CBreakableProp *pDisplay = dynamic_cast< CBreakableProp* >( m_hDisplayGrenade.Get() ); if ( pDisplay ) { pDisplay->KeyValue( "fademindist", "-1" ); pDisplay->KeyValue( "fademaxdist", "0" ); pDisplay->KeyValue( "fadescale", "1" ); pDisplay->KeyValue( "inertiaScale", "1.0" ); pDisplay->KeyValue( "physdamagescale", "0.1" ); //pDisplay->SetPhysicsMode( PHYSICS_MULTIPLAYER_SOLID ); pDisplay->SetModel( GRENADE_MODEL ); pDisplay->SetSolid( SOLID_BBOX ); pDisplay->AddSolidFlags( FSOLID_NOT_STANDABLE ); pDisplay->AddSpawnFlags( SF_PHYSPROP_MOTIONDISABLED ); pDisplay->Precache(); DispatchSpawn( pDisplay ); pDisplay->Activate(); // disable the parent model SetModelName( NULL_STRING );//invisible SetSolid( SOLID_NONE ); } */ } /* // only detonate on surfaces less steep than this const float kMinCos = cosf( DEG2RAD( weapon_molotov_maxdetonateslope.GetFloat() ) ); if ( hitTrace.plane.normal.z >= kMinCos ) { //Stick(); } */ } } //TODO: Let physics handle the sound! void CSensorGrenadeProjectile::BounceSound( void ) { EmitSound( "Flashbang.Bounce" ); } #endif // GAME_DLL