Team Fortress 2 Source Code as on 22/4/2020
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.

527 lines
13 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: Projectile shot from the MP5
  4. //
  5. // $Workfile: $
  6. // $Date: $
  7. //
  8. //-----------------------------------------------------------------------------
  9. // $Log: $
  10. //
  11. // $NoKeywords: $
  12. //=============================================================================//
  13. #include "cbase.h"
  14. #include "soundent.h"
  15. #include "engine/IEngineSound.h"
  16. #include "ai_senses.h"
  17. #include "hl1_npc_snark.h"
  18. #include "SoundEmitterSystem/isoundemittersystembase.h"
  19. ConVar sk_snark_health ( "sk_snark_health", "0" );
  20. ConVar sk_snark_dmg_bite ( "sk_snark_dmg_bite", "0" );
  21. ConVar sk_snark_dmg_pop ( "sk_snark_dmg_pop", "0" );
  22. LINK_ENTITY_TO_CLASS( monster_snark, CSnark);
  23. //---------------------------------------------------------
  24. // Save/Restore
  25. //---------------------------------------------------------
  26. BEGIN_DATADESC( CSnark )
  27. DEFINE_FIELD( m_flDie, FIELD_TIME ),
  28. DEFINE_FIELD( m_vecTarget, FIELD_VECTOR ),
  29. DEFINE_FIELD( m_flNextHunt, FIELD_TIME ),
  30. DEFINE_FIELD( m_flNextHit, FIELD_TIME ),
  31. DEFINE_FIELD( m_posPrev, FIELD_POSITION_VECTOR ),
  32. DEFINE_FIELD( m_hOwner, FIELD_EHANDLE ),
  33. DEFINE_FIELD( m_iMyClass, FIELD_INTEGER ),
  34. DEFINE_ENTITYFUNC( SuperBounceTouch ),
  35. DEFINE_THINKFUNC( HuntThink ),
  36. END_DATADESC()
  37. #define SQUEEK_DETONATE_DELAY 15.0
  38. #define SNARK_EXPLOSION_VOLUME 512
  39. enum w_squeak_e {
  40. WSQUEAK_IDLE1 = 0,
  41. WSQUEAK_FIDGET,
  42. WSQUEAK_JUMP,
  43. WSQUEAK_RUN,
  44. };
  45. float CSnark::m_flNextBounceSoundTime = 0;
  46. void CSnark::Precache( void )
  47. {
  48. BaseClass::Precache();
  49. PrecacheModel( "models/w_squeak2.mdl" );
  50. PrecacheScriptSound( "Snark.Die" );
  51. PrecacheScriptSound( "Snark.Gibbed" );
  52. PrecacheScriptSound( "Snark.Squeak" );
  53. PrecacheScriptSound( "Snark.Deploy" );
  54. PrecacheScriptSound( "Snark.Bounce" );
  55. }
  56. void CSnark::Spawn( void )
  57. {
  58. Precache();
  59. SetSolid( SOLID_BBOX );
  60. AddSolidFlags( FSOLID_NOT_STANDABLE );
  61. SetMoveType( MOVETYPE_FLYGRAVITY, MOVECOLLIDE_FLY_CUSTOM );
  62. SetFriction(1.0);
  63. SetModel( "models/w_squeak2.mdl" );
  64. UTIL_SetSize( this, Vector( -4, -4, 0 ), Vector( 4, 4, 8 ) );
  65. SetBloodColor( BLOOD_COLOR_YELLOW );
  66. SetTouch( &CSnark::SuperBounceTouch );
  67. SetThink( &CSnark::HuntThink );
  68. SetNextThink( gpGlobals->curtime + 0.1f );
  69. m_flNextHit = gpGlobals->curtime;
  70. m_flNextHunt = gpGlobals->curtime + 1E6;
  71. m_flNextBounceSoundTime = gpGlobals->curtime;
  72. AddFlag( FL_AIMTARGET | FL_NPC );
  73. m_takedamage = DAMAGE_YES;
  74. m_iHealth = sk_snark_health.GetFloat();
  75. m_iMaxHealth = m_iHealth;
  76. SetGravity( UTIL_ScaleForGravity( 400 ) ); // use a lower gravity for snarks
  77. SetFriction( 0.5 );
  78. SetDamage( sk_snark_dmg_pop.GetFloat() );
  79. m_flDie = gpGlobals->curtime + SQUEEK_DETONATE_DELAY;
  80. m_flFieldOfView = 0; // 180 degrees
  81. if ( GetOwnerEntity() )
  82. m_hOwner = GetOwnerEntity();
  83. m_flNextBounceSoundTime = gpGlobals->curtime;// reset each time a snark is spawned.
  84. SetSequence( WSQUEAK_RUN );
  85. ResetSequenceInfo( );
  86. m_iMyClass = CLASS_NONE;
  87. m_posPrev = Vector( 0, 0, 0 );
  88. }
  89. Class_T CSnark::Classify( void )
  90. {
  91. if ( m_iMyClass != CLASS_NONE )
  92. return m_iMyClass; // protect against recursion
  93. if ( GetEnemy() != NULL )
  94. {
  95. m_iMyClass = CLASS_INSECT; // no one cares about it
  96. switch( GetEnemy()->Classify( ) )
  97. {
  98. case CLASS_PLAYER:
  99. case CLASS_HUMAN_PASSIVE:
  100. case CLASS_HUMAN_MILITARY:
  101. m_iMyClass = CLASS_NONE;
  102. return CLASS_ALIEN_MILITARY; // barney's get mad, grunts get mad at it
  103. }
  104. m_iMyClass = CLASS_NONE;
  105. }
  106. return CLASS_ALIEN_BIOWEAPON;
  107. }
  108. void CSnark::Event_Killed( const CTakeDamageInfo &inputInfo )
  109. {
  110. // pev->model = iStringNull;// make invisible
  111. SetThink( &CSnark::SUB_Remove );
  112. SetNextThink( gpGlobals->curtime + 0.1f );
  113. SetTouch( NULL );
  114. // since squeak grenades never leave a body behind, clear out their takedamage now.
  115. // Squeaks do a bit of radius damage when they pop, and that radius damage will
  116. // continue to call this function unless we acknowledge the Squeak's death now. (sjb)
  117. m_takedamage = DAMAGE_NO;
  118. // play squeek blast
  119. CPASAttenuationFilter filter( this, 0.5 );
  120. EmitSound( filter, entindex(), "Snark.Die" );
  121. CSoundEnt::InsertSound( SOUND_COMBAT, GetAbsOrigin(), SNARK_EXPLOSION_VOLUME, 3.0 );
  122. UTIL_BloodDrips( WorldSpaceCenter(), Vector( 0, 0, 0 ), BLOOD_COLOR_YELLOW, 80 );
  123. if ( m_hOwner != NULL )
  124. {
  125. RadiusDamage( CTakeDamageInfo( this, m_hOwner, GetDamage(), DMG_BLAST ), GetAbsOrigin(), GetDamage() * 2.5, CLASS_NONE, NULL );
  126. }
  127. else
  128. {
  129. RadiusDamage( CTakeDamageInfo( this, this, GetDamage(), DMG_BLAST ), GetAbsOrigin(), GetDamage() * 2.5, CLASS_NONE, NULL );
  130. }
  131. // reset owner so death message happens
  132. if ( m_hOwner != NULL )
  133. SetOwnerEntity( m_hOwner );
  134. CTakeDamageInfo info = inputInfo;
  135. int iGibDamage = g_pGameRules->Damage_GetShouldGibCorpse();
  136. info.SetDamageType( iGibDamage );
  137. BaseClass::Event_Killed( info );
  138. }
  139. bool CSnark::Event_Gibbed( const CTakeDamageInfo &info )
  140. {
  141. CPASAttenuationFilter filter( this );
  142. EmitSound( filter, entindex(), "Snark.Gibbed" );
  143. return BaseClass::Event_Gibbed( info );
  144. }
  145. void CSnark::HuntThink( void )
  146. {
  147. if (!IsInWorld())
  148. {
  149. SetTouch( NULL );
  150. UTIL_Remove( this );
  151. return;
  152. }
  153. StudioFrameAdvance( );
  154. SetNextThink( gpGlobals->curtime + 0.1f );
  155. //FIXME: There's a problem in this movetype that causes it to set a ground entity but never recheck to clear it
  156. // For now, we stomp it clear and force it to revalidate -- jdw
  157. SetGroundEntity( NULL );
  158. PhysicsStepRecheckGround();
  159. // explode when ready
  160. if ( gpGlobals->curtime >= m_flDie )
  161. {
  162. g_vecAttackDir = GetAbsVelocity();
  163. VectorNormalize( g_vecAttackDir );
  164. m_iHealth = -1;
  165. CTakeDamageInfo info( this, this, 1, DMG_GENERIC );
  166. Event_Killed( info );
  167. return;
  168. }
  169. // float
  170. if ( GetWaterLevel() != 0)
  171. {
  172. if ( GetMoveType() == MOVETYPE_FLYGRAVITY )
  173. {
  174. SetMoveType( MOVETYPE_FLY, MOVECOLLIDE_FLY_CUSTOM );
  175. }
  176. Vector vecVel = GetAbsVelocity();
  177. vecVel *= 0.9;
  178. vecVel.z += 8.0;
  179. SetAbsVelocity( vecVel );
  180. }
  181. else if ( GetMoveType() == MOVETYPE_FLY )
  182. {
  183. SetMoveType( MOVETYPE_FLYGRAVITY, MOVECOLLIDE_FLY_CUSTOM );
  184. }
  185. // return if not time to hunt
  186. if ( m_flNextHunt > gpGlobals->curtime )
  187. return;
  188. m_flNextHunt = gpGlobals->curtime + 2.0;
  189. Vector vecFlat = GetAbsVelocity();
  190. vecFlat.z = 0;
  191. VectorNormalize( vecFlat );
  192. if ( GetEnemy() == NULL || !GetEnemy()->IsAlive() )
  193. {
  194. // find target, bounce a bit towards it.
  195. GetSenses()->Look( 1024 );
  196. SetEnemy( BestEnemy() );
  197. }
  198. // squeek if it's about time blow up
  199. if ( (m_flDie - gpGlobals->curtime <= 0.5) && (m_flDie - gpGlobals->curtime >= 0.3) )
  200. {
  201. CPASAttenuationFilter filter( this );
  202. EmitSound( filter, entindex(), "Snark.Squeak" );
  203. CSoundEnt::InsertSound( SOUND_COMBAT, GetAbsOrigin(), 256, 0.25 );
  204. }
  205. // higher pitch as squeeker gets closer to detonation time
  206. float flpitch = 155.0 - 60.0 * ( (m_flDie - gpGlobals->curtime) / SQUEEK_DETONATE_DELAY );
  207. if ( flpitch < 80 )
  208. flpitch = 80;
  209. if ( GetEnemy() != NULL )
  210. {
  211. if ( FVisible( GetEnemy() ) )
  212. {
  213. m_vecTarget = GetEnemy()->EyePosition() - GetAbsOrigin();
  214. VectorNormalize( m_vecTarget );
  215. }
  216. float flVel = GetAbsVelocity().Length();
  217. float flAdj = 50.0 / ( flVel + 10.0 );
  218. if ( flAdj > 1.2 )
  219. flAdj = 1.2;
  220. // ALERT( at_console, "think : enemy\n");
  221. // ALERT( at_console, "%.0f %.2f %.2f %.2f\n", flVel, m_vecTarget.x, m_vecTarget.y, m_vecTarget.z );
  222. SetAbsVelocity( GetAbsVelocity() * flAdj + (m_vecTarget * 300) );
  223. }
  224. if ( GetFlags() & FL_ONGROUND )
  225. {
  226. SetLocalAngularVelocity( QAngle( 0, 0, 0 ) );
  227. }
  228. else
  229. {
  230. QAngle angVel = GetLocalAngularVelocity();
  231. if ( angVel == QAngle( 0, 0, 0 ) )
  232. {
  233. angVel.x = random->RandomFloat( -100, 100 );
  234. angVel.z = random->RandomFloat( -100, 100 );
  235. SetLocalAngularVelocity( angVel );
  236. }
  237. }
  238. if ( ( GetAbsOrigin() - m_posPrev ).Length() < 1.0 )
  239. {
  240. Vector vecVel = GetAbsVelocity();
  241. vecVel.x = random->RandomFloat( -100, 100 );
  242. vecVel.y = random->RandomFloat( -100, 100 );
  243. SetAbsVelocity( vecVel );
  244. }
  245. m_posPrev = GetAbsOrigin();
  246. QAngle angles;
  247. VectorAngles( GetAbsVelocity(), angles );
  248. angles.z = 0;
  249. angles.x = 0;
  250. SetAbsAngles( angles );
  251. }
  252. unsigned int CSnark::PhysicsSolidMaskForEntity( void ) const
  253. {
  254. unsigned int iMask = BaseClass::PhysicsSolidMaskForEntity();
  255. iMask &= ~CONTENTS_MONSTERCLIP;
  256. return iMask;
  257. }
  258. // Custom collision that provides a good bounce when we hit walls
  259. // and also gives gravity velocity so the snarks fall off of edges.
  260. void CSnark::ResolveFlyCollisionCustom( trace_t &trace, Vector &vecVelocity )
  261. {
  262. // Get the impact surface's friction.
  263. float flSurfaceFriction;
  264. physprops->GetPhysicsProperties( trace.surface.surfaceProps, NULL, NULL, &flSurfaceFriction, NULL );
  265. Vector vecAbsVelocity = GetAbsVelocity();
  266. // If we hit a wall
  267. if ( trace.plane.normal.z <= 0.7 ) // Floor
  268. {
  269. Vector vecDir = vecAbsVelocity;
  270. float speed = vecDir.Length();
  271. VectorNormalize( vecDir );
  272. float hitDot = DotProduct( trace.plane.normal, -vecDir );
  273. Vector vReflection = 2.0f * trace.plane.normal * hitDot + vecDir;
  274. SetAbsVelocity( vReflection * speed * 0.6f );
  275. return;
  276. }
  277. // Stop if on ground.
  278. // Get the total velocity (player + conveyors, etc.)
  279. VectorAdd( vecAbsVelocity, GetBaseVelocity(), vecVelocity );
  280. float flSpeedSqr = DotProduct( vecVelocity, vecVelocity );
  281. // Verify that we have an entity.
  282. CBaseEntity *pEntity = trace.m_pEnt;
  283. Assert( pEntity );
  284. if ( vecVelocity.z < ( 800 * gpGlobals->frametime ) )
  285. {
  286. vecAbsVelocity.z = 0.0f;
  287. // Recompute speedsqr based on the new absvel
  288. VectorAdd( vecAbsVelocity, GetBaseVelocity(), vecVelocity );
  289. flSpeedSqr = DotProduct( vecVelocity, vecVelocity );
  290. }
  291. SetAbsVelocity( vecAbsVelocity );
  292. if ( flSpeedSqr < ( 30 * 30 ) )
  293. {
  294. if ( pEntity->IsStandable() )
  295. {
  296. SetGroundEntity( pEntity );
  297. }
  298. // Reset velocities.
  299. SetAbsVelocity( vec3_origin );
  300. SetLocalAngularVelocity( vec3_angle );
  301. }
  302. else
  303. {
  304. vecAbsVelocity += GetBaseVelocity();
  305. vecAbsVelocity *= ( 1.0f - trace.fraction ) * gpGlobals->frametime * flSurfaceFriction;
  306. PhysicsPushEntity( vecAbsVelocity, &trace );
  307. }
  308. }
  309. void CSnark::SuperBounceTouch( CBaseEntity *pOther )
  310. {
  311. float flpitch;
  312. trace_t tr;
  313. tr = CBaseEntity::GetTouchTrace( );
  314. // don't hit the guy that launched this grenade
  315. if ( GetOwnerEntity() && ( pOther == GetOwnerEntity() ) )
  316. return;
  317. // at least until we've bounced once
  318. SetOwnerEntity( NULL );
  319. QAngle angles = GetAbsAngles();
  320. angles.x = 0;
  321. angles.z = 0;
  322. SetAbsAngles( angles );
  323. // avoid bouncing too much
  324. if ( m_flNextHit > gpGlobals->curtime)
  325. return;
  326. // higher pitch as squeeker gets closer to detonation time
  327. flpitch = 155.0 - 60.0 * ( ( m_flDie - gpGlobals->curtime ) / SQUEEK_DETONATE_DELAY );
  328. if ( pOther->m_takedamage && m_flNextAttack < gpGlobals->curtime )
  329. {
  330. // attack!
  331. // make sure it's me who has touched them
  332. if ( tr.m_pEnt == pOther )
  333. {
  334. // and it's not another squeakgrenade
  335. if ( tr.m_pEnt->GetModelIndex() != GetModelIndex() )
  336. {
  337. // ALERT( at_console, "hit enemy\n");
  338. ClearMultiDamage();
  339. Vector vecForward;
  340. AngleVectors( GetAbsAngles(), &vecForward );
  341. if ( m_hOwner != NULL )
  342. {
  343. CTakeDamageInfo info( this, m_hOwner, sk_snark_dmg_bite.GetFloat(), DMG_SLASH );
  344. CalculateMeleeDamageForce( &info, vecForward, tr.endpos );
  345. pOther->DispatchTraceAttack( info, vecForward, &tr );
  346. }
  347. else
  348. {
  349. CTakeDamageInfo info( this, this, sk_snark_dmg_bite.GetFloat(), DMG_SLASH );
  350. CalculateMeleeDamageForce( &info, vecForward, tr.endpos );
  351. pOther->DispatchTraceAttack( info, vecForward, &tr );
  352. }
  353. ApplyMultiDamage();
  354. SetDamage( GetDamage() + sk_snark_dmg_pop.GetFloat() ); // add more explosion damage
  355. // m_flDie += 2.0; // add more life
  356. // make bite sound
  357. CPASAttenuationFilter filter( this );
  358. CSoundParameters params;
  359. if ( GetParametersForSound( "Snark.Deploy", params, NULL ) )
  360. {
  361. EmitSound_t ep( params );
  362. ep.m_nPitch = (int)flpitch;
  363. EmitSound( filter, entindex(), ep );
  364. }
  365. m_flNextAttack = gpGlobals->curtime + 0.5;
  366. }
  367. }
  368. else
  369. {
  370. // ALERT( at_console, "been hit\n");
  371. }
  372. }
  373. m_flNextHit = gpGlobals->curtime + 0.1;
  374. m_flNextHunt = gpGlobals->curtime;
  375. if ( g_pGameRules->IsMultiplayer() )
  376. {
  377. // in multiplayer, we limit how often snarks can make their bounce sounds to prevent overflows.
  378. if ( gpGlobals->curtime < m_flNextBounceSoundTime )
  379. {
  380. // too soon!
  381. return;
  382. }
  383. }
  384. if ( !( GetFlags() & FL_ONGROUND ) )
  385. {
  386. // play bounce sound
  387. CPASAttenuationFilter filter2( this );
  388. CSoundParameters params;
  389. if ( GetParametersForSound( "Snark.Bounce", params, NULL ) )
  390. {
  391. EmitSound_t ep( params );
  392. ep.m_nPitch = (int)flpitch;
  393. EmitSound( filter2, entindex(), ep );
  394. }
  395. CSoundEnt::InsertSound( SOUND_COMBAT, GetAbsOrigin(), 256, 0.25 );
  396. }
  397. else
  398. {
  399. // skittering sound
  400. CSoundEnt::InsertSound( SOUND_COMBAT, GetAbsOrigin(), 100, 0.1 );
  401. }
  402. m_flNextBounceSoundTime = gpGlobals->curtime + 0.5;// half second.
  403. }
  404. bool CSnark::IsValidEnemy( CBaseEntity *pEnemy )
  405. {
  406. return CHL1BaseNPC::IsValidEnemy( pEnemy );
  407. }