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.

1284 lines
32 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. //=============================================================================//
  6. #include "cbase.h"
  7. #include "soundenvelope.h"
  8. #include "Sprite.h"
  9. #include "entitylist.h"
  10. #include "ai_basenpc.h"
  11. #include "soundent.h"
  12. #include "explode.h"
  13. #include "physics.h"
  14. #include "physics_saverestore.h"
  15. #include "combine_mine.h"
  16. #include "movevars_shared.h"
  17. #include "vphysics/constraints.h"
  18. #include "ai_hint.h"
  19. enum
  20. {
  21. MINE_STATE_DORMANT = 0,
  22. MINE_STATE_DEPLOY, // Try to lock down and arm
  23. MINE_STATE_CAPTIVE, // Held in the physgun
  24. MINE_STATE_ARMED, // Locked down and looking for targets
  25. MINE_STATE_TRIGGERED, // No turning back. I'm going to explode when I touch something.
  26. MINE_STATE_LAUNCHED, // Similar. Thrown from physgun.
  27. };
  28. // for the Modification keyfield
  29. enum
  30. {
  31. MINE_MODIFICATION_NORMAL = 0,
  32. MINE_MODIFICATION_CAVERN,
  33. };
  34. // the citizen modified skins for the mine (inclusive):
  35. #define MINE_CITIZEN_SKIN_MIN 1
  36. #define MINE_CITIZEN_SKIN_MAX 2
  37. char *pszMineStateNames[] =
  38. {
  39. "Dormant",
  40. "Deploy",
  41. "Captive",
  42. "Armed",
  43. "Triggered",
  44. "Launched",
  45. };
  46. // memdbgon must be the last include file in a .cpp file!!!
  47. #include "tier0/memdbgon.h"
  48. // After this many flips, seriously cut the frequency with which you try.
  49. #define BOUNCEBOMB_MAX_FLIPS 5
  50. // Approximate radius of the bomb's model
  51. #define BOUNCEBOMB_RADIUS 24
  52. BEGIN_DATADESC( CBounceBomb )
  53. DEFINE_THINKFUNC( ExplodeThink ),
  54. DEFINE_ENTITYFUNC( ExplodeTouch ),
  55. DEFINE_THINKFUNC( SearchThink ),
  56. DEFINE_THINKFUNC( BounceThink ),
  57. DEFINE_THINKFUNC( SettleThink ),
  58. DEFINE_THINKFUNC( CaptiveThink ),
  59. DEFINE_THINKFUNC( CavernBounceThink ),
  60. DEFINE_SOUNDPATCH( m_pWarnSound ),
  61. DEFINE_KEYFIELD( m_flExplosionDelay, FIELD_FLOAT, "ExplosionDelay" ),
  62. DEFINE_KEYFIELD( m_bBounce, FIELD_BOOLEAN, "Bounce" ),
  63. DEFINE_FIELD( m_bAwake, FIELD_BOOLEAN ),
  64. DEFINE_FIELD( m_hNearestNPC, FIELD_EHANDLE ),
  65. DEFINE_FIELD( m_hSprite, FIELD_EHANDLE ),
  66. DEFINE_FIELD( m_LastSpriteColor, FIELD_COLOR32 ),
  67. DEFINE_FIELD( m_flHookPositions, FIELD_FLOAT ),
  68. DEFINE_FIELD( m_iHookN, FIELD_INTEGER ),
  69. DEFINE_FIELD( m_iHookE, FIELD_INTEGER ),
  70. DEFINE_FIELD( m_iHookS, FIELD_INTEGER ),
  71. DEFINE_FIELD( m_iAllHooks, FIELD_INTEGER ),
  72. DEFINE_KEYFIELD( m_bLockSilently, FIELD_BOOLEAN, "LockSilently" ),
  73. DEFINE_FIELD( m_bFoeNearest, FIELD_BOOLEAN ),
  74. DEFINE_FIELD( m_flIgnoreWorldTime, FIELD_TIME ),
  75. DEFINE_KEYFIELD( m_bDisarmed, FIELD_BOOLEAN, "StartDisarmed" ),
  76. DEFINE_KEYFIELD( m_iModification, FIELD_INTEGER, "Modification" ),
  77. DEFINE_FIELD( m_bPlacedByPlayer, FIELD_BOOLEAN ),
  78. DEFINE_FIELD( m_bHeldByPhysgun, FIELD_BOOLEAN ),
  79. DEFINE_FIELD( m_iFlipAttempts, FIELD_INTEGER ),
  80. DEFINE_FIELD( m_flTimeGrabbed, FIELD_TIME ),
  81. DEFINE_FIELD( m_iMineState, FIELD_INTEGER ),
  82. // Physics Influence
  83. DEFINE_FIELD( m_hPhysicsAttacker, FIELD_EHANDLE ),
  84. DEFINE_FIELD( m_flLastPhysicsInfluenceTime, FIELD_TIME ),
  85. DEFINE_PHYSPTR( m_pConstraint ),
  86. DEFINE_OUTPUT( m_OnPulledUp, "OnPulledUp" ),
  87. DEFINE_INPUTFUNC( FIELD_VOID, "Disarm", InputDisarm ),
  88. END_DATADESC()
  89. string_t CBounceBomb::gm_iszFloorTurretClassname;
  90. string_t CBounceBomb::gm_iszGroundTurretClassname;
  91. //---------------------------------------------------------
  92. //---------------------------------------------------------
  93. void CBounceBomb::Precache()
  94. {
  95. PrecacheModel("models/props_combine/combine_mine01.mdl");
  96. PrecacheScriptSound( "NPC_CombineMine.Hop" );
  97. PrecacheScriptSound( "NPC_CombineMine.FlipOver" );
  98. PrecacheScriptSound( "NPC_CombineMine.TurnOn" );
  99. PrecacheScriptSound( "NPC_CombineMine.TurnOff" );
  100. PrecacheScriptSound( "NPC_CombineMine.OpenHooks" );
  101. PrecacheScriptSound( "NPC_CombineMine.CloseHooks" );
  102. PrecacheScriptSound( "NPC_CombineMine.ActiveLoop" );
  103. PrecacheModel( "sprites/glow01.vmt" );
  104. gm_iszFloorTurretClassname = AllocPooledString( "npc_turret_floor" );
  105. gm_iszGroundTurretClassname = AllocPooledString( "npc_turret_ground" );
  106. }
  107. //---------------------------------------------------------
  108. //---------------------------------------------------------
  109. void CBounceBomb::Spawn()
  110. {
  111. Precache();
  112. Wake( false );
  113. SetModel("models/props_combine/combine_mine01.mdl");
  114. SetSolid( SOLID_VPHYSICS );
  115. m_hSprite.Set( NULL );
  116. m_takedamage = DAMAGE_EVENTS_ONLY;
  117. // Find my feet!
  118. m_iHookN = LookupPoseParameter( "blendnorth" );
  119. m_iHookE = LookupPoseParameter( "blendeast" );
  120. m_iHookS = LookupPoseParameter( "blendsouth" );
  121. m_iAllHooks = LookupPoseParameter( "blendstates" );
  122. m_flHookPositions = 0;
  123. SetHealth( 100 );
  124. m_bBounce = true;
  125. SetSequence( SelectWeightedSequence( ACT_IDLE ) );
  126. OpenHooks( true );
  127. m_bHeldByPhysgun = false;
  128. m_iFlipAttempts = 0;
  129. if( !GetParent() )
  130. {
  131. // Create vphysics now if I'm not being carried.
  132. CreateVPhysics();
  133. }
  134. m_flTimeGrabbed = FLT_MAX;
  135. if( m_bDisarmed )
  136. {
  137. SetMineState( MINE_STATE_DORMANT );
  138. }
  139. else
  140. {
  141. SetMineState( MINE_STATE_DEPLOY );
  142. }
  143. // default to a different skin for cavern turrets (unless explicitly overridden)
  144. if ( m_iModification == MINE_MODIFICATION_CAVERN )
  145. {
  146. // look for this value in the first datamap
  147. // loop through the data description list, restoring each data desc block
  148. datamap_t *dmap = GetDataDescMap();
  149. bool bFoundSkin = false;
  150. // search through all the readable fields in the data description, looking for a match
  151. for ( int i = 0; i < dmap->dataNumFields; ++i )
  152. {
  153. if ( dmap->dataDesc[i].flags & (FTYPEDESC_OUTPUT | FTYPEDESC_KEY) )
  154. {
  155. if ( !Q_stricmp(dmap->dataDesc[i].externalName, "Skin") )
  156. {
  157. bFoundSkin = true;
  158. break;
  159. }
  160. }
  161. }
  162. if (!bFoundSkin)
  163. {
  164. // select a random skin for the mine. Actually, we'll cycle through the available skins
  165. // using a static variable to provide better distribution. The static isn't saved but
  166. // really it's only cosmetic.
  167. static unsigned int nextSkin = MINE_CITIZEN_SKIN_MIN;
  168. m_nSkin = nextSkin;
  169. // increment the skin for next time
  170. nextSkin = (nextSkin >= MINE_CITIZEN_SKIN_MAX) ? MINE_CITIZEN_SKIN_MIN : nextSkin + 1;
  171. }
  172. // pretend like the player set me down.
  173. m_bPlacedByPlayer = true;
  174. }
  175. }
  176. //---------------------------------------------------------
  177. //---------------------------------------------------------
  178. void CBounceBomb::OnRestore()
  179. {
  180. BaseClass::OnRestore();
  181. if ( gpGlobals->eLoadType == MapLoad_Transition && !m_hSprite && m_LastSpriteColor.GetRawColor() != 0 )
  182. {
  183. UpdateLight( true, m_LastSpriteColor.r(), m_LastSpriteColor.g(), m_LastSpriteColor.b(), m_LastSpriteColor.a() );
  184. }
  185. if( VPhysicsGetObject() )
  186. {
  187. VPhysicsGetObject()->Wake();
  188. }
  189. }
  190. //---------------------------------------------------------
  191. //---------------------------------------------------------
  192. int CBounceBomb::DrawDebugTextOverlays(void)
  193. {
  194. int text_offset = BaseClass::DrawDebugTextOverlays();
  195. if (m_debugOverlays & OVERLAY_TEXT_BIT)
  196. {
  197. char tempstr[512];
  198. Q_snprintf(tempstr,sizeof(tempstr), "%s", pszMineStateNames[m_iMineState] );
  199. EntityText(text_offset,tempstr,0);
  200. text_offset++;
  201. }
  202. return text_offset;
  203. }
  204. //---------------------------------------------------------
  205. //---------------------------------------------------------
  206. void CBounceBomb::SetMineState( int iState )
  207. {
  208. m_iMineState = iState;
  209. switch( iState )
  210. {
  211. case MINE_STATE_DORMANT:
  212. {
  213. CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
  214. controller.SoundChangeVolume( m_pWarnSound, 0.0, 0.1 );
  215. UpdateLight( false, 0, 0, 0, 0 );
  216. SetThink( NULL );
  217. }
  218. break;
  219. case MINE_STATE_CAPTIVE:
  220. {
  221. CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
  222. controller.SoundChangeVolume( m_pWarnSound, 0.0, 0.2 );
  223. // Unhook
  224. unsigned int flags = VPhysicsGetObject()->GetCallbackFlags();
  225. VPhysicsGetObject()->SetCallbackFlags( flags | CALLBACK_GLOBAL_TOUCH_STATIC );
  226. OpenHooks();
  227. physenv->DestroyConstraint( m_pConstraint );
  228. m_pConstraint = NULL;
  229. UpdateLight( true, 0, 0, 255, 190 );
  230. SetThink( &CBounceBomb::CaptiveThink );
  231. SetNextThink( gpGlobals->curtime + 0.1f );
  232. SetTouch( NULL );
  233. }
  234. break;
  235. case MINE_STATE_DEPLOY:
  236. OpenHooks( true );
  237. UpdateLight( true, 0, 0, 255, 190 );
  238. SetThink( &CBounceBomb::SettleThink );
  239. SetTouch( NULL );
  240. SetNextThink( gpGlobals->curtime + 0.1f );
  241. break;
  242. case MINE_STATE_ARMED:
  243. UpdateLight( false, 0, 0, 0, 0 );
  244. SetThink( &CBounceBomb::SearchThink );
  245. SetNextThink( gpGlobals->curtime + 0.1f );
  246. break;
  247. case MINE_STATE_TRIGGERED:
  248. {
  249. OpenHooks();
  250. if( m_pConstraint )
  251. {
  252. physenv->DestroyConstraint( m_pConstraint );
  253. m_pConstraint = NULL;
  254. }
  255. // Scare NPC's
  256. CSoundEnt::InsertSound( SOUND_DANGER, GetAbsOrigin(), 300, 1.0f, this );
  257. CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
  258. controller.SoundChangeVolume( m_pWarnSound, 0.0, 0.2 );
  259. SetTouch( &CBounceBomb::ExplodeTouch );
  260. unsigned int flags = VPhysicsGetObject()->GetCallbackFlags();
  261. VPhysicsGetObject()->SetCallbackFlags( flags | CALLBACK_GLOBAL_TOUCH_STATIC );
  262. Vector vecNudge;
  263. vecNudge.x = random->RandomFloat( -1, 1 );
  264. vecNudge.y = random->RandomFloat( -1, 1 );
  265. vecNudge.z = 1.5;
  266. vecNudge *= 350;
  267. VPhysicsGetObject()->Wake();
  268. VPhysicsGetObject()->ApplyForceCenter( vecNudge );
  269. float x, y;
  270. x = 10 + random->RandomFloat( 0, 20 );
  271. y = 10 + random->RandomFloat( 0, 20 );
  272. VPhysicsGetObject()->ApplyTorqueCenter( AngularImpulse( x, y, 0 ) );
  273. // Since we just nudged the mine, ignore collisions with the world until
  274. // the mine is in the air. We only want to explode if the player tries to
  275. // run over the mine before it jumps up.
  276. m_flIgnoreWorldTime = gpGlobals->curtime + 1.0;
  277. UpdateLight( true, 255, 0, 0, 190 );
  278. // use the correct bounce behavior
  279. if (m_iModification == MINE_MODIFICATION_CAVERN)
  280. {
  281. SetThink ( &CBounceBomb::CavernBounceThink );
  282. SetNextThink( gpGlobals->curtime + 0.15 );
  283. }
  284. else
  285. {
  286. SetThink( &CBounceBomb::BounceThink );
  287. SetNextThink( gpGlobals->curtime + 0.5 );
  288. }
  289. }
  290. break;
  291. case MINE_STATE_LAUNCHED:
  292. {
  293. UpdateLight( true, 255, 0, 0, 190 );
  294. SetThink( NULL );
  295. SetNextThink( gpGlobals->curtime + 0.5 );
  296. SetTouch( &CBounceBomb::ExplodeTouch );
  297. unsigned int flags = VPhysicsGetObject()->GetCallbackFlags();
  298. VPhysicsGetObject()->SetCallbackFlags( flags | CALLBACK_GLOBAL_TOUCH_STATIC );
  299. }
  300. break;
  301. default:
  302. DevMsg("**Unknown Mine State: %d\n", iState );
  303. break;
  304. }
  305. }
  306. //---------------------------------------------------------
  307. // Bouncbomb flips to try to right itself, try to get off
  308. // of and object that it's not allowed to clamp to, or
  309. // to get away from a hint node that inhibits placement
  310. // of mines.
  311. //---------------------------------------------------------
  312. void CBounceBomb::Flip( const Vector &vecForce, const AngularImpulse &torque )
  313. {
  314. if( m_iFlipAttempts > BOUNCEBOMB_MAX_FLIPS )
  315. {
  316. // Not allowed to try anymore.
  317. SetThink(NULL);
  318. return;
  319. }
  320. EmitSound( "NPC_CombineMine.FlipOver" );
  321. VPhysicsGetObject()->ApplyForceCenter( vecForce );
  322. VPhysicsGetObject()->ApplyTorqueCenter( torque );
  323. m_iFlipAttempts++;
  324. }
  325. //---------------------------------------------------------
  326. //---------------------------------------------------------
  327. #define MINE_MIN_PROXIMITY_SQR 676 // 27 inches
  328. bool CBounceBomb::IsValidLocation()
  329. {
  330. CBaseEntity *pAvoidObject = NULL;
  331. float flAvoidForce = 0.0f;
  332. CAI_Hint *pHint;
  333. CHintCriteria criteria;
  334. criteria.SetHintType( HINT_WORLD_INHIBIT_COMBINE_MINES );
  335. criteria.SetFlag( bits_HINT_NODE_NEAREST );
  336. criteria.AddIncludePosition( GetAbsOrigin(), 12.0f * 15.0f );
  337. pHint = CAI_HintManager::FindHint( GetAbsOrigin(), criteria );
  338. if( pHint )
  339. {
  340. pAvoidObject = pHint;
  341. flAvoidForce = 120.0f;
  342. }
  343. else
  344. {
  345. // Look for other mines that are too close to me.
  346. CBaseEntity *pEntity = gEntList.FirstEnt();
  347. Vector vecMyPosition = GetAbsOrigin();
  348. while( pEntity )
  349. {
  350. if( pEntity->m_iClassname == m_iClassname && pEntity != this )
  351. {
  352. // Don't lock down if I'm near a mine that's already locked down.
  353. if( vecMyPosition.DistToSqr(pEntity->GetAbsOrigin()) < MINE_MIN_PROXIMITY_SQR )
  354. {
  355. pAvoidObject = pEntity;
  356. flAvoidForce = 60.0f;
  357. break;
  358. }
  359. }
  360. pEntity = gEntList.NextEnt( pEntity );
  361. }
  362. }
  363. if( pAvoidObject )
  364. {
  365. // Build a force vector to push us away from the inhibitor.
  366. // Start by pushing upwards.
  367. Vector vecForce = Vector( 0, 0, VPhysicsGetObject()->GetMass() * 200.0f );
  368. // Now add some force in the direction that takes us away from the inhibitor.
  369. Vector vecDir = GetAbsOrigin() - pAvoidObject->GetAbsOrigin();
  370. vecDir.z = 0.0f;
  371. VectorNormalize( vecDir );
  372. vecForce += vecDir * VPhysicsGetObject()->GetMass() * flAvoidForce;
  373. Flip( vecForce, AngularImpulse( 100, 0, 0 ) );
  374. // Tell the code that asked that this position isn't valid.
  375. return false;
  376. }
  377. return true;
  378. }
  379. //---------------------------------------------------------
  380. // Release the spikes
  381. //---------------------------------------------------------
  382. void CBounceBomb::BounceThink()
  383. {
  384. SetNextThink( gpGlobals->curtime + 0.1 );
  385. StudioFrameAdvance();
  386. IPhysicsObject *pPhysicsObject = VPhysicsGetObject();
  387. if ( pPhysicsObject != NULL )
  388. {
  389. const float MINE_MAX_JUMP_HEIGHT = 200;
  390. // Figure out how much headroom the mine has, and hop to within a few inches of that.
  391. trace_t tr;
  392. UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + Vector( 0, 0, MINE_MAX_JUMP_HEIGHT ), MASK_SHOT, this, COLLISION_GROUP_INTERACTIVE, &tr );
  393. float height;
  394. if( tr.m_pEnt && tr.m_pEnt->VPhysicsGetObject() )
  395. {
  396. // Physics object resting on me. Jump as hard as allowed to try to knock it away.
  397. height = MINE_MAX_JUMP_HEIGHT;
  398. }
  399. else
  400. {
  401. height = tr.endpos.z - GetAbsOrigin().z;
  402. height -= BOUNCEBOMB_RADIUS;
  403. if ( height < 0.1 )
  404. height = 0.1;
  405. }
  406. float time = sqrt( height / (0.5 * GetCurrentGravity()) );
  407. float velocity = GetCurrentGravity() * time;
  408. // or you can just AddVelocity to the object instead of ApplyForce
  409. float force = velocity * pPhysicsObject->GetMass();
  410. Vector up;
  411. GetVectors( NULL, NULL, &up );
  412. pPhysicsObject->Wake();
  413. pPhysicsObject->ApplyForceCenter( up * force );
  414. pPhysicsObject->ApplyTorqueCenter( AngularImpulse( random->RandomFloat( 5, 25 ), random->RandomFloat( 5, 25 ), 0 ) );
  415. if( m_hNearestNPC )
  416. {
  417. Vector vecPredict = m_hNearestNPC->GetSmoothedVelocity();
  418. pPhysicsObject->ApplyForceCenter( vecPredict * 10 );
  419. }
  420. EmitSound( "NPC_CombineMine.Hop" );
  421. SetThink( NULL );
  422. }
  423. }
  424. //---------------------------------------------------------
  425. // A different bounce behavior for the citizen-modified mine. Detonates at the top of its apex,
  426. // and does not attempt to track enemies.
  427. //---------------------------------------------------------
  428. void CBounceBomb::CavernBounceThink()
  429. {
  430. SetNextThink( gpGlobals->curtime + 0.1 );
  431. StudioFrameAdvance();
  432. IPhysicsObject *pPhysicsObject = VPhysicsGetObject();
  433. if ( pPhysicsObject != NULL )
  434. {
  435. const float MINE_MAX_JUMP_HEIGHT = 78;
  436. // Figure out how much headroom the mine has, and hop to within a few inches of that.
  437. trace_t tr;
  438. UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + Vector( 0, 0, MINE_MAX_JUMP_HEIGHT ), MASK_SHOT, this, COLLISION_GROUP_INTERACTIVE, &tr );
  439. float height;
  440. if( tr.m_pEnt && tr.m_pEnt->VPhysicsGetObject() )
  441. {
  442. // Physics object resting on me. Jump as hard as allowed to try to knock it away.
  443. height = MINE_MAX_JUMP_HEIGHT;
  444. }
  445. else
  446. {
  447. height = tr.endpos.z - GetAbsOrigin().z;
  448. height -= BOUNCEBOMB_RADIUS;
  449. if ( height < 0.1 )
  450. height = 0.1;
  451. }
  452. float time = sqrt( height / (0.5 * GetCurrentGravity()) );
  453. float velocity = GetCurrentGravity() * time;
  454. // or you can just AddVelocity to the object instead of ApplyForce
  455. float force = velocity * pPhysicsObject->GetMass();
  456. Vector up;
  457. GetVectors( NULL, NULL, &up );
  458. pPhysicsObject->Wake();
  459. pPhysicsObject->ApplyForceCenter( up * force );
  460. if( m_hNearestNPC )
  461. {
  462. Vector vecPredict = m_hNearestNPC->GetSmoothedVelocity();
  463. pPhysicsObject->ApplyForceCenter( vecPredict * (pPhysicsObject->GetMass() * 0.65f) );
  464. }
  465. pPhysicsObject->ApplyTorqueCenter( AngularImpulse( random->RandomFloat( 15, 40 ), random->RandomFloat( 15, 40 ), random->RandomFloat( 30, 60 ) ) );
  466. EmitSound( "NPC_CombineMine.Hop" );
  467. SetThink( &CBounceBomb::ExplodeThink );
  468. SetNextThink( gpGlobals->curtime + 0.33f );
  469. }
  470. }
  471. //---------------------------------------------------------
  472. //---------------------------------------------------------
  473. void CBounceBomb::CaptiveThink()
  474. {
  475. SetNextThink( gpGlobals->curtime + 0.05 );
  476. StudioFrameAdvance();
  477. float phase = fabs( sin( gpGlobals->curtime * 4.0f ) );
  478. phase *= BOUNCEBOMB_HOOK_RANGE;
  479. SetPoseParameter( m_iAllHooks, phase );
  480. return;
  481. }
  482. //---------------------------------------------------------
  483. //---------------------------------------------------------
  484. void CBounceBomb::SettleThink()
  485. {
  486. SetNextThink( gpGlobals->curtime + 0.05 );
  487. StudioFrameAdvance();
  488. if( GetParent() )
  489. {
  490. // A scanner or something is carrying me. Just keep checking back.
  491. return;
  492. }
  493. // Not being carried.
  494. if( !VPhysicsGetObject() )
  495. {
  496. // Probably was just dropped. Get physics going.
  497. CreateVPhysics();
  498. if( !VPhysicsGetObject() )
  499. {
  500. Msg("**** Can't create vphysics for combine_mine!\n" );
  501. UTIL_Remove( this );
  502. return;
  503. }
  504. VPhysicsGetObject()->Wake();
  505. return;
  506. }
  507. if( !m_bDisarmed )
  508. {
  509. if( VPhysicsGetObject()->IsAsleep() && !(VPhysicsGetObject()->GetGameFlags() & FVPHYSICS_PLAYER_HELD) )
  510. {
  511. // If i'm not resting on the world, jump randomly.
  512. trace_t tr;
  513. UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() - Vector( 0, 0, 1024 ), MASK_SHOT|CONTENTS_GRATE, this, COLLISION_GROUP_NONE, &tr );
  514. bool bHop = false;
  515. if( tr.m_pEnt )
  516. {
  517. IPhysicsObject *pPhysics = tr.m_pEnt->VPhysicsGetObject();
  518. if( pPhysics && pPhysics->GetMass() <= 1000 )
  519. {
  520. // Light physics objects can be moved out from under the mine.
  521. bHop = true;
  522. }
  523. else if( tr.m_pEnt->m_takedamage != DAMAGE_NO )
  524. {
  525. // Things that can be harmed can likely be broken.
  526. bHop = true;
  527. }
  528. if( bHop )
  529. {
  530. Vector vecForce;
  531. vecForce.x = random->RandomFloat( -1000, 1000 );
  532. vecForce.y = random->RandomFloat( -1000, 1000 );
  533. vecForce.z = 2500;
  534. AngularImpulse torque( 160, 0, 160 );
  535. Flip( vecForce, torque );
  536. return;
  537. }
  538. // Check for upside-down
  539. Vector vecUp;
  540. GetVectors( NULL, NULL, &vecUp );
  541. if( vecUp.z <= 0.8 )
  542. {
  543. // Landed upside down. Right self
  544. Vector vecForce( 0, 0, 2500 );
  545. Flip( vecForce, AngularImpulse( 60, 0, 0 ) );
  546. return;
  547. }
  548. }
  549. // Check to make sure I'm not in a forbidden location
  550. if( !IsValidLocation() )
  551. {
  552. return;
  553. }
  554. // Lock to what I'm resting on
  555. constraint_ballsocketparams_t ballsocket;
  556. ballsocket.Defaults();
  557. ballsocket.constraint.Defaults();
  558. ballsocket.constraint.forceLimit = lbs2kg(1000);
  559. ballsocket.constraint.torqueLimit = lbs2kg(1000);
  560. ballsocket.InitWithCurrentObjectState( g_PhysWorldObject, VPhysicsGetObject(), GetAbsOrigin() );
  561. m_pConstraint = physenv->CreateBallsocketConstraint( g_PhysWorldObject, VPhysicsGetObject(), NULL, ballsocket );
  562. CloseHooks();
  563. SetMineState( MINE_STATE_ARMED );
  564. }
  565. }
  566. }
  567. //---------------------------------------------------------
  568. //---------------------------------------------------------
  569. int CBounceBomb::OnTakeDamage( const CTakeDamageInfo &info )
  570. {
  571. if( m_pConstraint || !VPhysicsGetObject())
  572. {
  573. return false;
  574. }
  575. VPhysicsTakeDamage( info );
  576. return true;
  577. }
  578. //---------------------------------------------------------
  579. //---------------------------------------------------------
  580. void CBounceBomb::UpdateLight( bool bTurnOn, unsigned int r, unsigned int g, unsigned int b, unsigned int a )
  581. {
  582. if( bTurnOn )
  583. {
  584. Assert( a > 0 );
  585. // Throw the old sprite away
  586. if( m_hSprite )
  587. {
  588. UTIL_Remove( m_hSprite );
  589. m_hSprite.Set( NULL );
  590. }
  591. if( !m_hSprite.Get() )
  592. {
  593. Vector up;
  594. GetVectors( NULL, NULL, &up );
  595. // Light isn't on.
  596. m_hSprite = CSprite::SpriteCreate( "sprites/glow01.vmt", GetAbsOrigin() + up * 10.0f, false );
  597. CSprite *pSprite = (CSprite *)m_hSprite.Get();
  598. if( m_hSprite )
  599. {
  600. pSprite->SetParent( this );
  601. pSprite->SetTransparency( kRenderTransAdd, r, g, b, a, kRenderFxNone );
  602. pSprite->SetScale( 0.35, 0.0 );
  603. }
  604. }
  605. else
  606. {
  607. // Update color
  608. CSprite *pSprite = (CSprite *)m_hSprite.Get();
  609. pSprite->SetTransparency( kRenderTransAdd, r, g, b, a, kRenderFxNone );
  610. }
  611. }
  612. if( !bTurnOn )
  613. {
  614. if( m_hSprite )
  615. {
  616. UTIL_Remove( m_hSprite );
  617. m_hSprite.Set( NULL );
  618. }
  619. }
  620. if ( !m_hSprite )
  621. {
  622. m_LastSpriteColor.SetRawColor( 0 );
  623. }
  624. else
  625. {
  626. m_LastSpriteColor.SetColor( r, g, b, a );
  627. }
  628. }
  629. //---------------------------------------------------------
  630. //---------------------------------------------------------
  631. void CBounceBomb::Wake( bool bAwake )
  632. {
  633. CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
  634. CReliableBroadcastRecipientFilter filter;
  635. if( !m_pWarnSound )
  636. {
  637. m_pWarnSound = controller.SoundCreate( filter, entindex(), "NPC_CombineMine.ActiveLoop" );
  638. controller.Play( m_pWarnSound, 1.0, PITCH_NORM );
  639. }
  640. if( bAwake )
  641. {
  642. // Turning on
  643. if( m_bFoeNearest )
  644. {
  645. EmitSound( "NPC_CombineMine.TurnOn" );
  646. controller.SoundChangeVolume( m_pWarnSound, 1.0, 0.1 );
  647. }
  648. unsigned char r, g, b;
  649. r = g = b = 0;
  650. if( m_bFoeNearest )
  651. {
  652. r = 255;
  653. }
  654. else
  655. {
  656. g = 255;
  657. }
  658. UpdateLight( true, r, g, b, 190 );
  659. }
  660. else
  661. {
  662. // Turning off
  663. if( m_bFoeNearest )
  664. {
  665. EmitSound( "NPC_CombineMine.TurnOff" );
  666. }
  667. SetNearestNPC( NULL );
  668. controller.SoundChangeVolume( m_pWarnSound, 0.0, 0.1 );
  669. UpdateLight( false, 0, 0, 0, 0 );
  670. }
  671. m_bAwake = bAwake;
  672. }
  673. //---------------------------------------------------------
  674. // Returns distance to the nearest BaseCombatCharacter.
  675. //---------------------------------------------------------
  676. float CBounceBomb::FindNearestNPC()
  677. {
  678. float flNearest = (BOUNCEBOMB_WARN_RADIUS * BOUNCEBOMB_WARN_RADIUS) + 1.0;
  679. // Assume this search won't find anyone.
  680. SetNearestNPC( NULL );
  681. CAI_BaseNPC **ppAIs = g_AI_Manager.AccessAIs();
  682. int nAIs = g_AI_Manager.NumAIs();
  683. for ( int i = 0; i < nAIs; i++ )
  684. {
  685. CAI_BaseNPC *pNPC = ppAIs[ i ];
  686. if( pNPC->IsAlive() )
  687. {
  688. // ignore hidden objects
  689. if ( pNPC->IsEffectActive( EF_NODRAW ) )
  690. continue;
  691. // Don't bother with NPC's that are below me.
  692. if( pNPC->EyePosition().z < GetAbsOrigin().z )
  693. continue;
  694. // Disregard things that want to be disregarded
  695. if( pNPC->Classify() == CLASS_NONE )
  696. continue;
  697. // Disregard bullseyes
  698. if( pNPC->Classify() == CLASS_BULLSEYE )
  699. continue;
  700. // Disregard turrets
  701. if( pNPC->m_iClassname == gm_iszFloorTurretClassname || pNPC->m_iClassname == gm_iszGroundTurretClassname )
  702. continue;
  703. float flDist = (GetAbsOrigin() - pNPC->GetAbsOrigin()).LengthSqr();
  704. if( flDist < flNearest )
  705. {
  706. // Now do a visibility test.
  707. if( FVisible( pNPC, MASK_SOLID_BRUSHONLY ) )
  708. {
  709. flNearest = flDist;
  710. SetNearestNPC( pNPC );
  711. }
  712. }
  713. }
  714. }
  715. // finally, check the player.
  716. CBasePlayer *pPlayer = UTIL_GetLocalPlayer();
  717. if( pPlayer && !(pPlayer->GetFlags() & FL_NOTARGET) )
  718. {
  719. float flDist = (pPlayer->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr();
  720. if( flDist < flNearest && FVisible( pPlayer, MASK_SOLID_BRUSHONLY ) )
  721. {
  722. flNearest = flDist;
  723. SetNearestNPC( pPlayer );
  724. }
  725. }
  726. if( m_hNearestNPC.Get() )
  727. {
  728. // If sprite is active, update its color to reflect who is nearest.
  729. if( IsFriend( m_hNearestNPC ) )
  730. {
  731. if( m_bFoeNearest )
  732. {
  733. // Changing state to where a friend is nearest.
  734. if( IsFriend( m_hNearestNPC ) )
  735. {
  736. // Friend
  737. UpdateLight( true, 0, 255, 0, 190 );
  738. m_bFoeNearest = false;
  739. }
  740. }
  741. }
  742. else // it's a foe
  743. {
  744. if( !m_bFoeNearest )
  745. {
  746. // Changing state to where a foe is nearest.
  747. UpdateLight( true, 255, 0, 0, 190 );
  748. m_bFoeNearest = true;
  749. }
  750. }
  751. }
  752. return sqrt( flNearest );
  753. }
  754. //---------------------------------------------------------
  755. //---------------------------------------------------------
  756. bool CBounceBomb::IsFriend( CBaseEntity *pEntity )
  757. {
  758. int classify = pEntity->Classify();
  759. bool bIsCombine = false;
  760. // Unconditional enemies to combine and Player.
  761. if( classify == CLASS_ZOMBIE || classify == CLASS_HEADCRAB || classify == CLASS_ANTLION )
  762. {
  763. return false;
  764. }
  765. if( classify == CLASS_METROPOLICE ||
  766. classify == CLASS_COMBINE ||
  767. classify == CLASS_MILITARY ||
  768. classify == CLASS_COMBINE_HUNTER ||
  769. classify == CLASS_SCANNER )
  770. {
  771. bIsCombine = true;
  772. }
  773. if( m_bPlacedByPlayer )
  774. {
  775. return !bIsCombine;
  776. }
  777. else
  778. {
  779. return bIsCombine;
  780. }
  781. }
  782. //---------------------------------------------------------
  783. //---------------------------------------------------------
  784. void CBounceBomb::SearchThink()
  785. {
  786. if( !UTIL_FindClientInPVS(edict()) )
  787. {
  788. // Sleep!
  789. SetNextThink( gpGlobals->curtime + 0.5 );
  790. return;
  791. }
  792. if( (CAI_BaseNPC::m_nDebugBits & bits_debugDisableAI) )
  793. {
  794. if( IsAwake() )
  795. {
  796. Wake(false);
  797. }
  798. SetNextThink( gpGlobals->curtime + 0.5 );
  799. return;
  800. }
  801. SetNextThink( gpGlobals->curtime + 0.1 );
  802. StudioFrameAdvance();
  803. if( m_pConstraint && gpGlobals->curtime - m_flTimeGrabbed >= 1.0f )
  804. {
  805. m_OnPulledUp.FireOutput( this, this );
  806. SetMineState( MINE_STATE_CAPTIVE );
  807. return;
  808. }
  809. float flNearestNPCDist = FindNearestNPC();
  810. if( flNearestNPCDist <= BOUNCEBOMB_WARN_RADIUS )
  811. {
  812. if( !IsAwake() )
  813. {
  814. Wake( true );
  815. }
  816. }
  817. else
  818. {
  819. if( IsAwake() )
  820. {
  821. Wake( false );
  822. }
  823. return;
  824. }
  825. if( flNearestNPCDist <= BOUNCEBOMB_DETONATE_RADIUS && !IsFriend( m_hNearestNPC ) )
  826. {
  827. if( m_bBounce )
  828. {
  829. SetMineState( MINE_STATE_TRIGGERED );
  830. }
  831. else
  832. {
  833. // Don't pop up in the air, just explode if the NPC gets closer than explode radius.
  834. SetThink( &CBounceBomb::ExplodeThink );
  835. SetNextThink( gpGlobals->curtime + m_flExplosionDelay );
  836. }
  837. }
  838. }
  839. //---------------------------------------------------------
  840. //---------------------------------------------------------
  841. void CBounceBomb::ExplodeTouch( CBaseEntity *pOther )
  842. {
  843. // Don't touch anything if held by physgun.
  844. if( m_bHeldByPhysgun )
  845. return;
  846. // Don't touch triggers.
  847. if( pOther->IsSolidFlagSet(FSOLID_TRIGGER) )
  848. return;
  849. // Don't touch gibs and other debris
  850. if( pOther->GetCollisionGroup() == COLLISION_GROUP_DEBRIS )
  851. {
  852. if( hl2_episodic.GetBool() )
  853. {
  854. Vector vecVelocity;
  855. VPhysicsGetObject()->GetVelocity( &vecVelocity, NULL );
  856. if( vecVelocity == vec3_origin )
  857. {
  858. ExplodeThink();
  859. }
  860. }
  861. return;
  862. }
  863. // Don't detonate against the world if not allowed. Actually, don't
  864. // detonate against anything that's probably not an NPC (such as physics props)
  865. if( m_flIgnoreWorldTime > gpGlobals->curtime && !pOther->MyCombatCharacterPointer() )
  866. {
  867. return;
  868. }
  869. ExplodeThink();
  870. }
  871. //---------------------------------------------------------
  872. //---------------------------------------------------------
  873. void CBounceBomb::ExplodeThink()
  874. {
  875. SetSolid( SOLID_NONE );
  876. // Don't catch self in own explosion!
  877. m_takedamage = DAMAGE_NO;
  878. if( m_hSprite )
  879. {
  880. UpdateLight( false, 0, 0, 0, 0 );
  881. }
  882. if( m_pWarnSound )
  883. {
  884. CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
  885. controller.SoundDestroy( m_pWarnSound );
  886. }
  887. CBaseEntity *pThrower = HasPhysicsAttacker( 0.5 );
  888. if (m_iModification == MINE_MODIFICATION_CAVERN)
  889. {
  890. ExplosionCreate( GetAbsOrigin(), GetAbsAngles(), (pThrower) ? pThrower : this, BOUNCEBOMB_EXPLODE_DAMAGE, BOUNCEBOMB_EXPLODE_RADIUS, true,
  891. NULL, CLASS_PLAYER_ALLY );
  892. }
  893. else
  894. {
  895. ExplosionCreate( GetAbsOrigin(), GetAbsAngles(), (pThrower) ? pThrower : this, BOUNCEBOMB_EXPLODE_DAMAGE, BOUNCEBOMB_EXPLODE_RADIUS, true);
  896. }
  897. UTIL_Remove( this );
  898. }
  899. //---------------------------------------------------------
  900. //---------------------------------------------------------
  901. void CBounceBomb::OpenHooks( bool bSilent )
  902. {
  903. if( !bSilent )
  904. {
  905. EmitSound( "NPC_CombineMine.OpenHooks" );
  906. }
  907. if( VPhysicsGetObject() )
  908. {
  909. // It's possible to not have a valid physics object here, since this function doubles as an initialization function.
  910. PhysClearGameFlags( VPhysicsGetObject(), FVPHYSICS_CONSTRAINT_STATIC );
  911. VPhysicsGetObject()->EnableMotion( true );
  912. }
  913. SetPoseParameter( m_iAllHooks, BOUNCEBOMB_HOOK_RANGE );
  914. #ifdef _XBOX
  915. RemoveEffects( EF_NOSHADOW );
  916. #endif
  917. }
  918. //---------------------------------------------------------
  919. //---------------------------------------------------------
  920. void CBounceBomb::CloseHooks()
  921. {
  922. if( !m_bLockSilently )
  923. {
  924. EmitSound( "NPC_CombineMine.CloseHooks" );
  925. }
  926. if( VPhysicsGetObject() )
  927. {
  928. // It's possible to not have a valid physics object here, since this function doubles as an initialization function.
  929. PhysSetGameFlags( VPhysicsGetObject(), FVPHYSICS_CONSTRAINT_STATIC );
  930. }
  931. // Only lock silently the first time we call this.
  932. m_bLockSilently = false;
  933. SetPoseParameter( m_iAllHooks, 0 );
  934. VPhysicsGetObject()->EnableMotion( false );
  935. // Once I lock down, forget how many tries it took.
  936. m_iFlipAttempts = 0;
  937. #ifdef _XBOX
  938. AddEffects( EF_NOSHADOW );
  939. #endif
  940. }
  941. //---------------------------------------------------------
  942. //---------------------------------------------------------
  943. void CBounceBomb::InputDisarm( inputdata_t &inputdata )
  944. {
  945. // Only affect a mine that's armed and not placed by player.
  946. if( !m_bPlacedByPlayer && m_iMineState == MINE_STATE_ARMED )
  947. {
  948. if( m_pConstraint )
  949. {
  950. physenv->DestroyConstraint( m_pConstraint );
  951. m_pConstraint = NULL;
  952. }
  953. m_bDisarmed = true;
  954. OpenHooks(false);
  955. SetMineState(MINE_STATE_DORMANT);
  956. }
  957. }
  958. //---------------------------------------------------------
  959. //---------------------------------------------------------
  960. void CBounceBomb::OnPhysGunDrop( CBasePlayer *pPhysGunUser, PhysGunDrop_t Reason )
  961. {
  962. m_hPhysicsAttacker = pPhysGunUser;
  963. m_flLastPhysicsInfluenceTime = gpGlobals->curtime;
  964. m_flTimeGrabbed = FLT_MAX;
  965. m_bHeldByPhysgun = false;
  966. if( m_iMineState == MINE_STATE_ARMED )
  967. {
  968. // Put the mine back to searching.
  969. Wake( false );
  970. return;
  971. }
  972. if( Reason == DROPPED_BY_CANNON )
  973. {
  974. // Set to lock down to ground again.
  975. m_bPlacedByPlayer = true;
  976. OpenHooks( true );
  977. SetMineState( MINE_STATE_DEPLOY );
  978. }
  979. else if ( Reason == LAUNCHED_BY_CANNON )
  980. {
  981. SetMineState( MINE_STATE_LAUNCHED );
  982. }
  983. }
  984. //---------------------------------------------------------
  985. //---------------------------------------------------------
  986. CBasePlayer *CBounceBomb::HasPhysicsAttacker( float dt )
  987. {
  988. if (gpGlobals->curtime - dt <= m_flLastPhysicsInfluenceTime)
  989. {
  990. return m_hPhysicsAttacker;
  991. }
  992. return NULL;
  993. }
  994. //---------------------------------------------------------
  995. //---------------------------------------------------------
  996. void CBounceBomb::OnPhysGunPickup( CBasePlayer *pPhysGunUser, PhysGunPickup_t reason )
  997. {
  998. m_hPhysicsAttacker = pPhysGunUser;
  999. m_flLastPhysicsInfluenceTime = gpGlobals->curtime;
  1000. m_iFlipAttempts = 0;
  1001. if( reason != PUNTED_BY_CANNON )
  1002. {
  1003. if( m_iMineState == MINE_STATE_ARMED )
  1004. {
  1005. // Yanking on a mine that is locked down, trying to rip it loose.
  1006. UpdateLight( true, 255, 255, 0, 190 );
  1007. m_flTimeGrabbed = gpGlobals->curtime;
  1008. m_bHeldByPhysgun = true;
  1009. VPhysicsGetObject()->EnableMotion( true );
  1010. // Try to scatter NPCs without panicking them. Make a move away sound up around their
  1011. // ear level.
  1012. CSoundEnt::InsertSound( SOUND_MOVE_AWAY, GetAbsOrigin() + Vector( 0, 0, 60), 32.0f, 0.2f );
  1013. return;
  1014. }
  1015. else
  1016. {
  1017. // Picked up a mine that was not locked down.
  1018. m_bHeldByPhysgun = true;
  1019. if( m_iMineState == MINE_STATE_TRIGGERED )
  1020. {
  1021. // This mine's already set to blow. Player can't place it.
  1022. return;
  1023. }
  1024. else
  1025. {
  1026. m_bDisarmed = false;
  1027. SetMineState( MINE_STATE_DEPLOY );
  1028. }
  1029. }
  1030. }
  1031. else
  1032. {
  1033. m_bHeldByPhysgun = false;
  1034. }
  1035. if( reason == PUNTED_BY_CANNON )
  1036. {
  1037. if( m_iMineState == MINE_STATE_TRIGGERED || m_iMineState == MINE_STATE_ARMED )
  1038. {
  1039. // Already set to blow
  1040. return;
  1041. }
  1042. m_bDisarmed = false;
  1043. m_bPlacedByPlayer = true;
  1044. SetTouch( NULL );
  1045. SetThink( &CBounceBomb::SettleThink );
  1046. SetNextThink( gpGlobals->curtime + 0.1);
  1047. // Since being punted causes the mine to flip, sometimes it 'catches an edge'
  1048. // and ends up touching the ground from whence it came, exploding instantly.
  1049. // This little stunt prevents that by ignoring world collisions for a very short time.
  1050. m_flIgnoreWorldTime = gpGlobals->curtime + 0.1;
  1051. }
  1052. }
  1053. LINK_ENTITY_TO_CLASS( bounce_bomb, CBounceBomb );
  1054. LINK_ENTITY_TO_CLASS( combine_bouncemine, CBounceBomb );
  1055. LINK_ENTITY_TO_CLASS( combine_mine, CBounceBomb );
  1056. /*
  1057. */