//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: Projectile shot from the MP5 // // $Workfile: $ // $Date: $ // //----------------------------------------------------------------------------- // $Log: $ // // $NoKeywords: $ //=============================================================================// #include "cbase.h" #include "soundent.h" #include "engine/IEngineSound.h" #include "ai_senses.h" #include "hl1_npc_snark.h" #include "SoundEmitterSystem/isoundemittersystembase.h" ConVar sk_snark_health ( "sk_snark_health", "0" ); ConVar sk_snark_dmg_bite ( "sk_snark_dmg_bite", "0" ); ConVar sk_snark_dmg_pop ( "sk_snark_dmg_pop", "0" ); LINK_ENTITY_TO_CLASS( monster_snark, CSnark); //--------------------------------------------------------- // Save/Restore //--------------------------------------------------------- BEGIN_DATADESC( CSnark ) DEFINE_FIELD( m_flDie, FIELD_TIME ), DEFINE_FIELD( m_vecTarget, FIELD_VECTOR ), DEFINE_FIELD( m_flNextHunt, FIELD_TIME ), DEFINE_FIELD( m_flNextHit, FIELD_TIME ), DEFINE_FIELD( m_posPrev, FIELD_POSITION_VECTOR ), DEFINE_FIELD( m_hOwner, FIELD_EHANDLE ), DEFINE_FIELD( m_iMyClass, FIELD_INTEGER ), DEFINE_ENTITYFUNC( SuperBounceTouch ), DEFINE_THINKFUNC( HuntThink ), END_DATADESC() #define SQUEEK_DETONATE_DELAY 15.0 #define SNARK_EXPLOSION_VOLUME 512 enum w_squeak_e { WSQUEAK_IDLE1 = 0, WSQUEAK_FIDGET, WSQUEAK_JUMP, WSQUEAK_RUN, }; float CSnark::m_flNextBounceSoundTime = 0; void CSnark::Precache( void ) { BaseClass::Precache(); PrecacheModel( "models/w_squeak2.mdl" ); PrecacheScriptSound( "Snark.Die" ); PrecacheScriptSound( "Snark.Gibbed" ); PrecacheScriptSound( "Snark.Squeak" ); PrecacheScriptSound( "Snark.Deploy" ); PrecacheScriptSound( "Snark.Bounce" ); } void CSnark::Spawn( void ) { Precache(); SetSolid( SOLID_BBOX ); AddSolidFlags( FSOLID_NOT_STANDABLE ); SetMoveType( MOVETYPE_FLYGRAVITY, MOVECOLLIDE_FLY_CUSTOM ); SetFriction(1.0); SetModel( "models/w_squeak2.mdl" ); UTIL_SetSize( this, Vector( -4, -4, 0 ), Vector( 4, 4, 8 ) ); SetBloodColor( BLOOD_COLOR_YELLOW ); SetTouch( &CSnark::SuperBounceTouch ); SetThink( &CSnark::HuntThink ); SetNextThink( gpGlobals->curtime + 0.1f ); m_flNextHit = gpGlobals->curtime; m_flNextHunt = gpGlobals->curtime + 1E6; m_flNextBounceSoundTime = gpGlobals->curtime; AddFlag( FL_AIMTARGET | FL_NPC ); m_takedamage = DAMAGE_YES; m_iHealth = sk_snark_health.GetFloat(); m_iMaxHealth = m_iHealth; SetGravity( UTIL_ScaleForGravity( 400 ) ); // use a lower gravity for snarks SetFriction( 0.5 ); SetDamage( sk_snark_dmg_pop.GetFloat() ); m_flDie = gpGlobals->curtime + SQUEEK_DETONATE_DELAY; m_flFieldOfView = 0; // 180 degrees if ( GetOwnerEntity() ) m_hOwner = GetOwnerEntity(); m_flNextBounceSoundTime = gpGlobals->curtime;// reset each time a snark is spawned. SetSequence( WSQUEAK_RUN ); ResetSequenceInfo( ); m_iMyClass = CLASS_NONE; m_posPrev = Vector( 0, 0, 0 ); } Class_T CSnark::Classify( void ) { if ( m_iMyClass != CLASS_NONE ) return m_iMyClass; // protect against recursion if ( GetEnemy() != NULL ) { m_iMyClass = CLASS_INSECT; // no one cares about it switch( GetEnemy()->Classify( ) ) { case CLASS_PLAYER: case CLASS_HUMAN_PASSIVE: case CLASS_HUMAN_MILITARY: m_iMyClass = CLASS_NONE; return CLASS_ALIEN_MILITARY; // barney's get mad, grunts get mad at it } m_iMyClass = CLASS_NONE; } return CLASS_ALIEN_BIOWEAPON; } void CSnark::Event_Killed( const CTakeDamageInfo &inputInfo ) { // pev->model = iStringNull;// make invisible SetThink( &CSnark::SUB_Remove ); SetNextThink( gpGlobals->curtime + 0.1f ); SetTouch( NULL ); // since squeak grenades never leave a body behind, clear out their takedamage now. // Squeaks do a bit of radius damage when they pop, and that radius damage will // continue to call this function unless we acknowledge the Squeak's death now. (sjb) m_takedamage = DAMAGE_NO; // play squeek blast CPASAttenuationFilter filter( this, 0.5 ); EmitSound( filter, entindex(), "Snark.Die" ); CSoundEnt::InsertSound( SOUND_COMBAT, GetAbsOrigin(), SNARK_EXPLOSION_VOLUME, 3.0 ); UTIL_BloodDrips( WorldSpaceCenter(), Vector( 0, 0, 0 ), BLOOD_COLOR_YELLOW, 80 ); if ( m_hOwner != NULL ) { RadiusDamage( CTakeDamageInfo( this, m_hOwner, GetDamage(), DMG_BLAST ), GetAbsOrigin(), GetDamage() * 2.5, CLASS_NONE, NULL ); } else { RadiusDamage( CTakeDamageInfo( this, this, GetDamage(), DMG_BLAST ), GetAbsOrigin(), GetDamage() * 2.5, CLASS_NONE, NULL ); } // reset owner so death message happens if ( m_hOwner != NULL ) SetOwnerEntity( m_hOwner ); CTakeDamageInfo info = inputInfo; int iGibDamage = g_pGameRules->Damage_GetShouldGibCorpse(); info.SetDamageType( iGibDamage ); BaseClass::Event_Killed( info ); } bool CSnark::Event_Gibbed( const CTakeDamageInfo &info ) { CPASAttenuationFilter filter( this ); EmitSound( filter, entindex(), "Snark.Gibbed" ); return BaseClass::Event_Gibbed( info ); } void CSnark::HuntThink( void ) { if (!IsInWorld()) { SetTouch( NULL ); UTIL_Remove( this ); return; } StudioFrameAdvance( ); SetNextThink( gpGlobals->curtime + 0.1f ); //FIXME: There's a problem in this movetype that causes it to set a ground entity but never recheck to clear it // For now, we stomp it clear and force it to revalidate -- jdw SetGroundEntity( NULL ); PhysicsStepRecheckGround(); // explode when ready if ( gpGlobals->curtime >= m_flDie ) { g_vecAttackDir = GetAbsVelocity(); VectorNormalize( g_vecAttackDir ); m_iHealth = -1; CTakeDamageInfo info( this, this, 1, DMG_GENERIC ); Event_Killed( info ); return; } // float if ( GetWaterLevel() != 0) { if ( GetMoveType() == MOVETYPE_FLYGRAVITY ) { SetMoveType( MOVETYPE_FLY, MOVECOLLIDE_FLY_CUSTOM ); } Vector vecVel = GetAbsVelocity(); vecVel *= 0.9; vecVel.z += 8.0; SetAbsVelocity( vecVel ); } else if ( GetMoveType() == MOVETYPE_FLY ) { SetMoveType( MOVETYPE_FLYGRAVITY, MOVECOLLIDE_FLY_CUSTOM ); } // return if not time to hunt if ( m_flNextHunt > gpGlobals->curtime ) return; m_flNextHunt = gpGlobals->curtime + 2.0; Vector vecFlat = GetAbsVelocity(); vecFlat.z = 0; VectorNormalize( vecFlat ); if ( GetEnemy() == NULL || !GetEnemy()->IsAlive() ) { // find target, bounce a bit towards it. GetSenses()->Look( 1024 ); SetEnemy( BestEnemy() ); } // squeek if it's about time blow up if ( (m_flDie - gpGlobals->curtime <= 0.5) && (m_flDie - gpGlobals->curtime >= 0.3) ) { CPASAttenuationFilter filter( this ); EmitSound( filter, entindex(), "Snark.Squeak" ); CSoundEnt::InsertSound( SOUND_COMBAT, GetAbsOrigin(), 256, 0.25 ); } // higher pitch as squeeker gets closer to detonation time float flpitch = 155.0 - 60.0 * ( (m_flDie - gpGlobals->curtime) / SQUEEK_DETONATE_DELAY ); if ( flpitch < 80 ) flpitch = 80; if ( GetEnemy() != NULL ) { if ( FVisible( GetEnemy() ) ) { m_vecTarget = GetEnemy()->EyePosition() - GetAbsOrigin(); VectorNormalize( m_vecTarget ); } float flVel = GetAbsVelocity().Length(); float flAdj = 50.0 / ( flVel + 10.0 ); if ( flAdj > 1.2 ) flAdj = 1.2; // ALERT( at_console, "think : enemy\n"); // ALERT( at_console, "%.0f %.2f %.2f %.2f\n", flVel, m_vecTarget.x, m_vecTarget.y, m_vecTarget.z ); SetAbsVelocity( GetAbsVelocity() * flAdj + (m_vecTarget * 300) ); } if ( GetFlags() & FL_ONGROUND ) { SetLocalAngularVelocity( QAngle( 0, 0, 0 ) ); } else { QAngle angVel = GetLocalAngularVelocity(); if ( angVel == QAngle( 0, 0, 0 ) ) { angVel.x = random->RandomFloat( -100, 100 ); angVel.z = random->RandomFloat( -100, 100 ); SetLocalAngularVelocity( angVel ); } } if ( ( GetAbsOrigin() - m_posPrev ).Length() < 1.0 ) { Vector vecVel = GetAbsVelocity(); vecVel.x = random->RandomFloat( -100, 100 ); vecVel.y = random->RandomFloat( -100, 100 ); SetAbsVelocity( vecVel ); } m_posPrev = GetAbsOrigin(); QAngle angles; VectorAngles( GetAbsVelocity(), angles ); angles.z = 0; angles.x = 0; SetAbsAngles( angles ); } unsigned int CSnark::PhysicsSolidMaskForEntity( void ) const { unsigned int iMask = BaseClass::PhysicsSolidMaskForEntity(); iMask &= ~CONTENTS_MONSTERCLIP; return iMask; } // Custom collision that provides a good bounce when we hit walls // and also gives gravity velocity so the snarks fall off of edges. void CSnark::ResolveFlyCollisionCustom( trace_t &trace, Vector &vecVelocity ) { // Get the impact surface's friction. float flSurfaceFriction; physprops->GetPhysicsProperties( trace.surface.surfaceProps, NULL, NULL, &flSurfaceFriction, NULL ); Vector vecAbsVelocity = GetAbsVelocity(); // If we hit a wall if ( trace.plane.normal.z <= 0.7 ) // Floor { Vector vecDir = vecAbsVelocity; float speed = vecDir.Length(); VectorNormalize( vecDir ); float hitDot = DotProduct( trace.plane.normal, -vecDir ); Vector vReflection = 2.0f * trace.plane.normal * hitDot + vecDir; SetAbsVelocity( vReflection * speed * 0.6f ); return; } // Stop if on ground. // Get the total velocity (player + conveyors, etc.) VectorAdd( vecAbsVelocity, GetBaseVelocity(), vecVelocity ); float flSpeedSqr = DotProduct( vecVelocity, vecVelocity ); // Verify that we have an entity. CBaseEntity *pEntity = trace.m_pEnt; Assert( pEntity ); if ( vecVelocity.z < ( 800 * gpGlobals->frametime ) ) { vecAbsVelocity.z = 0.0f; // Recompute speedsqr based on the new absvel VectorAdd( vecAbsVelocity, GetBaseVelocity(), vecVelocity ); flSpeedSqr = DotProduct( vecVelocity, vecVelocity ); } SetAbsVelocity( vecAbsVelocity ); if ( flSpeedSqr < ( 30 * 30 ) ) { if ( pEntity->IsStandable() ) { SetGroundEntity( pEntity ); } // Reset velocities. SetAbsVelocity( vec3_origin ); SetLocalAngularVelocity( vec3_angle ); } else { vecAbsVelocity += GetBaseVelocity(); vecAbsVelocity *= ( 1.0f - trace.fraction ) * gpGlobals->frametime * flSurfaceFriction; PhysicsPushEntity( vecAbsVelocity, &trace ); } } void CSnark::SuperBounceTouch( CBaseEntity *pOther ) { float flpitch; trace_t tr; tr = CBaseEntity::GetTouchTrace( ); // don't hit the guy that launched this grenade if ( GetOwnerEntity() && ( pOther == GetOwnerEntity() ) ) return; // at least until we've bounced once SetOwnerEntity( NULL ); QAngle angles = GetAbsAngles(); angles.x = 0; angles.z = 0; SetAbsAngles( angles ); // avoid bouncing too much if ( m_flNextHit > gpGlobals->curtime) return; // higher pitch as squeeker gets closer to detonation time flpitch = 155.0 - 60.0 * ( ( m_flDie - gpGlobals->curtime ) / SQUEEK_DETONATE_DELAY ); if ( pOther->m_takedamage && m_flNextAttack < gpGlobals->curtime ) { // attack! // make sure it's me who has touched them if ( tr.m_pEnt == pOther ) { // and it's not another squeakgrenade if ( tr.m_pEnt->GetModelIndex() != GetModelIndex() ) { // ALERT( at_console, "hit enemy\n"); ClearMultiDamage(); Vector vecForward; AngleVectors( GetAbsAngles(), &vecForward ); if ( m_hOwner != NULL ) { CTakeDamageInfo info( this, m_hOwner, sk_snark_dmg_bite.GetFloat(), DMG_SLASH ); CalculateMeleeDamageForce( &info, vecForward, tr.endpos ); pOther->DispatchTraceAttack( info, vecForward, &tr ); } else { CTakeDamageInfo info( this, this, sk_snark_dmg_bite.GetFloat(), DMG_SLASH ); CalculateMeleeDamageForce( &info, vecForward, tr.endpos ); pOther->DispatchTraceAttack( info, vecForward, &tr ); } ApplyMultiDamage(); SetDamage( GetDamage() + sk_snark_dmg_pop.GetFloat() ); // add more explosion damage // m_flDie += 2.0; // add more life // make bite sound CPASAttenuationFilter filter( this ); CSoundParameters params; if ( GetParametersForSound( "Snark.Deploy", params, NULL ) ) { EmitSound_t ep( params ); ep.m_nPitch = (int)flpitch; EmitSound( filter, entindex(), ep ); } m_flNextAttack = gpGlobals->curtime + 0.5; } } else { // ALERT( at_console, "been hit\n"); } } m_flNextHit = gpGlobals->curtime + 0.1; m_flNextHunt = gpGlobals->curtime; if ( g_pGameRules->IsMultiplayer() ) { // in multiplayer, we limit how often snarks can make their bounce sounds to prevent overflows. if ( gpGlobals->curtime < m_flNextBounceSoundTime ) { // too soon! return; } } if ( !( GetFlags() & FL_ONGROUND ) ) { // play bounce sound CPASAttenuationFilter filter2( this ); CSoundParameters params; if ( GetParametersForSound( "Snark.Bounce", params, NULL ) ) { EmitSound_t ep( params ); ep.m_nPitch = (int)flpitch; EmitSound( filter2, entindex(), ep ); } CSoundEnt::InsertSound( SOUND_COMBAT, GetAbsOrigin(), 256, 0.25 ); } else { // skittering sound CSoundEnt::InsertSound( SOUND_COMBAT, GetAbsOrigin(), 100, 0.1 ); } m_flNextBounceSoundTime = gpGlobals->curtime + 0.5;// half second. } bool CSnark::IsValidEnemy( CBaseEntity *pEnemy ) { return CHL1BaseNPC::IsValidEnemy( pEnemy ); }