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.

4431 lines
126 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. //=============================================================================
  6. #include "cbase.h"
  7. #include "func_tank.h"
  8. #include "Sprite.h"
  9. #include "EnvLaser.h"
  10. #include "basecombatweapon.h"
  11. #include "explode.h"
  12. #include "eventqueue.h"
  13. #include "gamerules.h"
  14. #include "ammodef.h"
  15. #include "in_buttons.h"
  16. #include "soundent.h"
  17. #include "ndebugoverlay.h"
  18. #include "grenade_beam.h"
  19. #include "vstdlib/random.h"
  20. #include "engine/IEngineSound.h"
  21. #include "physics_cannister.h"
  22. #include "decals.h"
  23. #include "shake.h"
  24. #include "particle_smokegrenade.h"
  25. #include "player.h"
  26. #include "entitylist.h"
  27. #include "IEffects.h"
  28. #include "ai_basenpc.h"
  29. #include "ai_behavior_functank.h"
  30. #include "weapon_rpg.h"
  31. #include "effects.h"
  32. #include "iservervehicle.h"
  33. #include "soundenvelope.h"
  34. #include "effect_dispatch_data.h"
  35. #include "te_effect_dispatch.h"
  36. #include "props.h"
  37. #include "rumble_shared.h"
  38. #include "particle_parse.h"
  39. // NVNT turret recoil
  40. #include "haptics/haptic_utils.h"
  41. #ifdef HL2_DLL
  42. #include "hl2_player.h"
  43. #endif //HL2_DLL
  44. // memdbgon must be the last include file in a .cpp file!!!
  45. #include "tier0/memdbgon.h"
  46. extern Vector PointOnLineNearestPoint(const Vector& vStartPos, const Vector& vEndPos, const Vector& vPoint);
  47. ConVar mortar_visualize("mortar_visualize", "0" );
  48. BEGIN_DATADESC( CFuncTank )
  49. DEFINE_KEYFIELD( m_yawRate, FIELD_FLOAT, "yawrate" ),
  50. DEFINE_KEYFIELD( m_yawRange, FIELD_FLOAT, "yawrange" ),
  51. DEFINE_KEYFIELD( m_yawTolerance, FIELD_FLOAT, "yawtolerance" ),
  52. DEFINE_KEYFIELD( m_pitchRate, FIELD_FLOAT, "pitchrate" ),
  53. DEFINE_KEYFIELD( m_pitchRange, FIELD_FLOAT, "pitchrange" ),
  54. DEFINE_KEYFIELD( m_pitchTolerance, FIELD_FLOAT, "pitchtolerance" ),
  55. DEFINE_KEYFIELD( m_fireRate, FIELD_FLOAT, "firerate" ),
  56. DEFINE_FIELD( m_fireTime, FIELD_TIME ),
  57. DEFINE_KEYFIELD( m_persist, FIELD_FLOAT, "persistence" ),
  58. DEFINE_KEYFIELD( m_persist2, FIELD_FLOAT, "persistence2" ),
  59. DEFINE_KEYFIELD( m_minRange, FIELD_FLOAT, "minRange" ),
  60. DEFINE_KEYFIELD( m_maxRange, FIELD_FLOAT, "maxRange" ),
  61. DEFINE_FIELD( m_flMinRange2, FIELD_FLOAT ),
  62. DEFINE_FIELD( m_flMaxRange2, FIELD_FLOAT ),
  63. DEFINE_KEYFIELD( m_iAmmoCount, FIELD_INTEGER, "ammo_count" ),
  64. DEFINE_KEYFIELD( m_spriteScale, FIELD_FLOAT, "spritescale" ),
  65. DEFINE_KEYFIELD( m_iszSpriteSmoke, FIELD_STRING, "spritesmoke" ),
  66. DEFINE_KEYFIELD( m_iszSpriteFlash, FIELD_STRING, "spriteflash" ),
  67. DEFINE_KEYFIELD( m_bulletType, FIELD_INTEGER, "bullet" ),
  68. DEFINE_FIELD( m_nBulletCount, FIELD_INTEGER ),
  69. DEFINE_KEYFIELD( m_spread, FIELD_INTEGER, "firespread" ),
  70. DEFINE_KEYFIELD( m_iBulletDamage, FIELD_INTEGER, "bullet_damage" ),
  71. DEFINE_KEYFIELD( m_iBulletDamageVsPlayer, FIELD_INTEGER, "bullet_damage_vs_player" ),
  72. DEFINE_KEYFIELD( m_iszMaster, FIELD_STRING, "master" ),
  73. #ifdef HL2_EPISODIC
  74. DEFINE_KEYFIELD( m_iszAmmoType, FIELD_STRING, "ammotype" ),
  75. DEFINE_FIELD( m_iAmmoType, FIELD_INTEGER ),
  76. #else
  77. DEFINE_FIELD( m_iSmallAmmoType, FIELD_INTEGER ),
  78. DEFINE_FIELD( m_iMediumAmmoType, FIELD_INTEGER ),
  79. DEFINE_FIELD( m_iLargeAmmoType, FIELD_INTEGER ),
  80. #endif // HL2_EPISODIC
  81. DEFINE_KEYFIELD( m_soundStartRotate, FIELD_SOUNDNAME, "rotatestartsound" ),
  82. DEFINE_KEYFIELD( m_soundStopRotate, FIELD_SOUNDNAME, "rotatestopsound" ),
  83. DEFINE_KEYFIELD( m_soundLoopRotate, FIELD_SOUNDNAME, "rotatesound" ),
  84. DEFINE_KEYFIELD( m_flPlayerGracePeriod, FIELD_FLOAT, "playergraceperiod" ),
  85. DEFINE_KEYFIELD( m_flIgnoreGraceUpto, FIELD_FLOAT, "ignoregraceupto" ),
  86. DEFINE_KEYFIELD( m_flPlayerLockTimeBeforeFire, FIELD_FLOAT, "playerlocktimebeforefire" ),
  87. DEFINE_FIELD( m_flLastSawNonPlayer, FIELD_TIME ),
  88. DEFINE_FIELD( m_yawCenter, FIELD_FLOAT ),
  89. DEFINE_FIELD( m_yawCenterWorld, FIELD_FLOAT ),
  90. DEFINE_FIELD( m_pitchCenter, FIELD_FLOAT ),
  91. DEFINE_FIELD( m_pitchCenterWorld, FIELD_FLOAT ),
  92. DEFINE_FIELD( m_fireLast, FIELD_TIME ),
  93. DEFINE_FIELD( m_lastSightTime, FIELD_TIME ),
  94. DEFINE_FIELD( m_barrelPos, FIELD_VECTOR ),
  95. DEFINE_FIELD( m_sightOrigin, FIELD_POSITION_VECTOR ),
  96. DEFINE_FIELD( m_hFuncTankTarget, FIELD_EHANDLE ),
  97. DEFINE_FIELD( m_hController, FIELD_EHANDLE ),
  98. DEFINE_FIELD( m_vecControllerUsePos, FIELD_VECTOR ),
  99. DEFINE_FIELD( m_flNextAttack, FIELD_TIME ),
  100. DEFINE_FIELD( m_targetEntityName, FIELD_STRING ),
  101. DEFINE_FIELD( m_hTarget, FIELD_EHANDLE ),
  102. DEFINE_FIELD( m_vTargetPosition, FIELD_POSITION_VECTOR ),
  103. DEFINE_FIELD( m_vecNPCIdleTarget, FIELD_POSITION_VECTOR ),
  104. DEFINE_FIELD( m_persist2burst, FIELD_FLOAT),
  105. //DEFINE_FIELD( m_parentMatrix, FIELD_MATRIX ), // DON'T SAVE
  106. DEFINE_FIELD( m_hControlVolume, FIELD_EHANDLE ),
  107. DEFINE_KEYFIELD( m_iszControlVolume, FIELD_STRING, "control_volume" ),
  108. DEFINE_FIELD( m_flNextControllerSearch, FIELD_TIME ),
  109. DEFINE_FIELD( m_bShouldFindNPCs, FIELD_BOOLEAN ),
  110. DEFINE_FIELD( m_bNPCInRoute, FIELD_BOOLEAN ),
  111. DEFINE_KEYFIELD( m_iszNPCManPoint, FIELD_STRING, "npc_man_point" ),
  112. DEFINE_FIELD( m_bReadyToFire, FIELD_BOOLEAN ),
  113. DEFINE_KEYFIELD( m_bPerformLeading, FIELD_BOOLEAN, "LeadTarget" ),
  114. DEFINE_FIELD( m_flStartLeadFactor, FIELD_FLOAT ),
  115. DEFINE_FIELD( m_flStartLeadFactorTime, FIELD_TIME ),
  116. DEFINE_FIELD( m_flNextLeadFactor, FIELD_FLOAT ),
  117. DEFINE_FIELD( m_flNextLeadFactorTime, FIELD_TIME ),
  118. // Used for when the gun is attached to another entity
  119. DEFINE_KEYFIELD( m_iszBaseAttachment, FIELD_STRING, "gun_base_attach" ),
  120. DEFINE_KEYFIELD( m_iszBarrelAttachment, FIELD_STRING, "gun_barrel_attach" ),
  121. // DEFINE_FIELD( m_nBarrelAttachment, FIELD_INTEGER ),
  122. // Used when the gun is actually a part of the parent entity, and pose params aim it
  123. DEFINE_KEYFIELD( m_iszYawPoseParam, FIELD_STRING, "gun_yaw_pose_param" ),
  124. DEFINE_KEYFIELD( m_iszPitchPoseParam, FIELD_STRING, "gun_pitch_pose_param" ),
  125. DEFINE_KEYFIELD( m_flYawPoseCenter, FIELD_FLOAT, "gun_yaw_pose_center" ),
  126. DEFINE_KEYFIELD( m_flPitchPoseCenter, FIELD_FLOAT, "gun_pitch_pose_center" ),
  127. DEFINE_FIELD( m_bUsePoseParameters, FIELD_BOOLEAN ),
  128. DEFINE_KEYFIELD( m_iEffectHandling, FIELD_INTEGER, "effecthandling" ),
  129. // Inputs
  130. DEFINE_INPUTFUNC( FIELD_VOID, "Activate", InputActivate ),
  131. DEFINE_INPUTFUNC( FIELD_VOID, "Deactivate", InputDeactivate ),
  132. DEFINE_INPUTFUNC( FIELD_FLOAT, "SetFireRate", InputSetFireRate ),
  133. DEFINE_INPUTFUNC( FIELD_INTEGER, "SetDamage", InputSetDamage ),
  134. DEFINE_INPUTFUNC( FIELD_VECTOR, "SetTargetPosition", InputSetTargetPosition ),
  135. DEFINE_INPUTFUNC( FIELD_VECTOR, "SetTargetDir", InputSetTargetDir ),
  136. DEFINE_INPUTFUNC( FIELD_STRING, "SetTargetEntityName", InputSetTargetEntityName ),
  137. DEFINE_INPUTFUNC( FIELD_EHANDLE, "SetTargetEntity", InputSetTargetEntity ),
  138. DEFINE_INPUTFUNC( FIELD_VOID, "ClearTargetEntity", InputClearTargetEntity ),
  139. DEFINE_INPUTFUNC( FIELD_STRING, "FindNPCToManTank", InputFindNPCToManTank ),
  140. DEFINE_INPUTFUNC( FIELD_VOID, "StopFindingNPCs", InputStopFindingNPCs ),
  141. DEFINE_INPUTFUNC( FIELD_VOID, "StartFindingNPCs", InputStartFindingNPCs ),
  142. DEFINE_INPUTFUNC( FIELD_VOID, "ForceNPCOff", InputForceNPCOff ),
  143. DEFINE_INPUTFUNC( FIELD_FLOAT, "SetMaxRange", InputSetMaxRange ),
  144. // Outputs
  145. DEFINE_OUTPUT(m_OnFire, "OnFire"),
  146. DEFINE_OUTPUT(m_OnLoseTarget, "OnLoseTarget"),
  147. DEFINE_OUTPUT(m_OnAquireTarget, "OnAquireTarget"),
  148. DEFINE_OUTPUT(m_OnAmmoDepleted, "OnAmmoDepleted"),
  149. DEFINE_OUTPUT(m_OnGotController, "OnGotController"),
  150. DEFINE_OUTPUT(m_OnLostController, "OnLostController"),
  151. DEFINE_OUTPUT(m_OnGotPlayerController, "OnGotPlayerController"),
  152. DEFINE_OUTPUT(m_OnLostPlayerController, "OnLostPlayerController"),
  153. DEFINE_OUTPUT(m_OnReadyToFire, "OnReadyToFire"),
  154. END_DATADESC()
  155. //-----------------------------------------------------------------------------
  156. // Purpose:
  157. //-----------------------------------------------------------------------------
  158. CFuncTank::CFuncTank()
  159. {
  160. m_nBulletCount = 0;
  161. m_bNPCInRoute = false;
  162. m_flNextControllerSearch = 0;
  163. m_bShouldFindNPCs = true;
  164. }
  165. //-----------------------------------------------------------------------------
  166. // Purpose:
  167. //-----------------------------------------------------------------------------
  168. CFuncTank::~CFuncTank( void )
  169. {
  170. if ( m_soundLoopRotate != NULL_STRING && ( m_spawnflags & SF_TANK_SOUNDON ) )
  171. {
  172. StopSound( entindex(), CHAN_STATIC, STRING(m_soundLoopRotate) );
  173. }
  174. }
  175. //------------------------------------------------------------------------------
  176. // Purpose:
  177. //------------------------------------------------------------------------------
  178. inline bool CFuncTank::CanFire( void )
  179. {
  180. float flTimeDelay = gpGlobals->curtime - m_lastSightTime;
  181. // Fire when can't see enemy if time is less that persistence time
  182. if ( flTimeDelay <= m_persist )
  183. return true;
  184. // Fire when I'm in a persistence2 burst
  185. if ( flTimeDelay <= m_persist2burst )
  186. return true;
  187. // If less than persistence2, occasionally do another burst
  188. if ( flTimeDelay <= m_persist2 )
  189. {
  190. if ( random->RandomInt( 0, 30 ) == 0 )
  191. {
  192. m_persist2burst = flTimeDelay + 0.5f;
  193. return true;
  194. }
  195. }
  196. return false;
  197. }
  198. //------------------------------------------------------------------------------
  199. // Purpose: Input handler for activating the tank.
  200. //------------------------------------------------------------------------------
  201. void CFuncTank::InputActivate( inputdata_t &inputdata )
  202. {
  203. TankActivate();
  204. }
  205. //-----------------------------------------------------------------------------
  206. // Purpose:
  207. //-----------------------------------------------------------------------------
  208. void CFuncTank::TankActivate( void )
  209. {
  210. m_spawnflags |= SF_TANK_ACTIVE;
  211. SetNextThink( gpGlobals->curtime + 0.1f );
  212. m_fireLast = gpGlobals->curtime;
  213. }
  214. //-----------------------------------------------------------------------------
  215. // Purpose: Input handler for deactivating the tank.
  216. //-----------------------------------------------------------------------------
  217. void CFuncTank::InputDeactivate( inputdata_t &inputdata )
  218. {
  219. TankDeactivate();
  220. }
  221. //-----------------------------------------------------------------------------
  222. // Purpose:
  223. //-----------------------------------------------------------------------------
  224. void CFuncTank::TankDeactivate( void )
  225. {
  226. m_spawnflags &= ~SF_TANK_ACTIVE;
  227. m_fireLast = 0;
  228. StopRotSound();
  229. }
  230. //-----------------------------------------------------------------------------
  231. // Purpose: Input handler for changing the name of the tank's target entity.
  232. //-----------------------------------------------------------------------------
  233. void CFuncTank::InputSetTargetEntityName( inputdata_t &inputdata )
  234. {
  235. m_targetEntityName = inputdata.value.StringID();
  236. m_hTarget = FindTarget( m_targetEntityName, inputdata.pActivator );
  237. // No longer aim at target position if have one
  238. m_spawnflags &= ~SF_TANK_AIM_AT_POS;
  239. }
  240. //-----------------------------------------------------------------------------
  241. // Purpose: Input handler for setting a new target entity by ehandle.
  242. //-----------------------------------------------------------------------------
  243. void CFuncTank::InputSetTargetEntity( inputdata_t &inputdata )
  244. {
  245. if ( inputdata.value.Entity() != NULL )
  246. {
  247. m_targetEntityName = inputdata.value.Entity()->GetEntityName();
  248. }
  249. else
  250. {
  251. m_targetEntityName = NULL_STRING;
  252. }
  253. m_hTarget = inputdata.value.Entity();
  254. // No longer aim at target position if have one
  255. m_spawnflags &= ~SF_TANK_AIM_AT_POS;
  256. }
  257. //-----------------------------------------------------------------------------
  258. // Purpose: Input handler for clearing the tank's target entity
  259. //-----------------------------------------------------------------------------
  260. void CFuncTank::InputClearTargetEntity( inputdata_t &inputdata )
  261. {
  262. m_targetEntityName = NULL_STRING;
  263. m_hTarget = NULL;
  264. // No longer aim at target position if have one
  265. m_spawnflags &= ~SF_TANK_AIM_AT_POS;
  266. }
  267. //-----------------------------------------------------------------------------
  268. // Purpose: Input handler for setting the rate of fire in shots per second.
  269. //-----------------------------------------------------------------------------
  270. void CFuncTank::InputSetFireRate( inputdata_t &inputdata )
  271. {
  272. m_fireRate = inputdata.value.Float();
  273. }
  274. //-----------------------------------------------------------------------------
  275. // Purpose: Input handler for setting the damage
  276. //-----------------------------------------------------------------------------
  277. void CFuncTank::InputSetDamage( inputdata_t &inputdata )
  278. {
  279. m_iBulletDamage = inputdata.value.Int();
  280. }
  281. //-----------------------------------------------------------------------------
  282. // Purpose: Input handler for setting the target as a position.
  283. //-----------------------------------------------------------------------------
  284. void CFuncTank::InputSetTargetPosition( inputdata_t &inputdata )
  285. {
  286. m_spawnflags |= SF_TANK_AIM_AT_POS;
  287. m_hTarget = NULL;
  288. inputdata.value.Vector3D( m_vTargetPosition );
  289. }
  290. //-----------------------------------------------------------------------------
  291. // Purpose: Input handler for setting the target as a position.
  292. //-----------------------------------------------------------------------------
  293. void CFuncTank::InputSetTargetDir( inputdata_t &inputdata )
  294. {
  295. m_spawnflags |= SF_TANK_AIM_AT_POS;
  296. m_hTarget = NULL;
  297. Vector vecTargetDir;
  298. inputdata.value.Vector3D( vecTargetDir );
  299. m_vTargetPosition = GetAbsOrigin() + m_barrelPos.LengthSqr() * vecTargetDir;
  300. }
  301. //-----------------------------------------------------------------------------
  302. // Purpose: Input handler for telling the func_tank to find an NPC to man it.
  303. //-----------------------------------------------------------------------------
  304. void CFuncTank::InputFindNPCToManTank( inputdata_t &inputdata )
  305. {
  306. // Verify the func_tank is controllable and available.
  307. if ( !IsNPCControllable() && !IsNPCSetController() )
  308. return;
  309. // If we have a controller already - don't look for one.
  310. if ( HasController() )
  311. return;
  312. // NPC assigned to man the func_tank?
  313. CBaseEntity *pEntity = gEntList.FindEntityByName( NULL, inputdata.value.StringID() );
  314. if ( pEntity )
  315. {
  316. CAI_BaseNPC *pNPC = pEntity->MyNPCPointer();
  317. if ( pNPC )
  318. {
  319. // Verify the npc has the func_tank controller behavior.
  320. CAI_FuncTankBehavior *pBehavior;
  321. if ( pNPC->GetBehavior( &pBehavior ) )
  322. {
  323. m_hController = pNPC;
  324. pBehavior->SetFuncTank( this );
  325. NPC_SetInRoute( true );
  326. return;
  327. }
  328. }
  329. }
  330. // No controller? Find a nearby NPC who can man this func_tank.
  331. NPC_FindController();
  332. }
  333. //-----------------------------------------------------------------------------
  334. // Purpose:
  335. // Input : &inputdata -
  336. //-----------------------------------------------------------------------------
  337. void CFuncTank::InputStopFindingNPCs( inputdata_t &inputdata )
  338. {
  339. m_bShouldFindNPCs = false;
  340. }
  341. //-----------------------------------------------------------------------------
  342. // Purpose:
  343. // Input : &inputdata -
  344. //-----------------------------------------------------------------------------
  345. void CFuncTank::InputStartFindingNPCs( inputdata_t &inputdata )
  346. {
  347. m_bShouldFindNPCs = true;
  348. }
  349. //-----------------------------------------------------------------------------
  350. // Purpose:
  351. // Input : &inputdata -
  352. //-----------------------------------------------------------------------------
  353. void CFuncTank::InputForceNPCOff( inputdata_t &inputdata )
  354. {
  355. // Interrupt any npc in route (ally or not).
  356. if ( NPC_InRoute() )
  357. {
  358. // Interrupt the npc's route.
  359. NPC_InterruptRoute();
  360. }
  361. // If we don't have a controller - then the gun should be free.
  362. if ( !m_hController )
  363. return;
  364. CAI_BaseNPC *pNPC = m_hController->MyNPCPointer();
  365. if ( !pNPC )
  366. return;
  367. CAI_FuncTankBehavior *pBehavior;
  368. if ( pNPC->GetBehavior( &pBehavior ) )
  369. {
  370. pBehavior->Dismount();
  371. }
  372. m_hController = NULL;
  373. }
  374. //-----------------------------------------------------------------------------
  375. // Purpose:
  376. // Input : &inputdata -
  377. //-----------------------------------------------------------------------------
  378. void CFuncTank::InputSetMaxRange( inputdata_t &inputdata )
  379. {
  380. m_maxRange = inputdata.value.Float();
  381. m_flMaxRange2 = m_maxRange * m_maxRange;
  382. }
  383. //-----------------------------------------------------------------------------
  384. // Purpose: Find the closest NPC with the func_tank behavior.
  385. //-----------------------------------------------------------------------------
  386. void CFuncTank::NPC_FindController( void )
  387. {
  388. // Not NPC controllable or controllable on by specified npc's return.
  389. if ( !IsNPCControllable() || IsNPCSetController() )
  390. return;
  391. // Initialize for finding closest NPC.
  392. CAI_BaseNPC *pClosestNPC = NULL;
  393. float flClosestDist2 = ( FUNCTANK_DISTANCE_MAX * FUNCTANK_DISTANCE_MAX );
  394. float flMinDistToEnemy2 = ( FUNCTANK_DISTANCE_MIN_TO_ENEMY * FUNCTANK_DISTANCE_MIN_TO_ENEMY );
  395. CAI_FuncTankBehavior *pClosestBehavior = NULL;
  396. // Get the mount position.
  397. Vector vecMountPos;
  398. NPC_FindManPoint( vecMountPos );
  399. // Search through the AI list for the closest NPC with the func_tank behavior.
  400. CAI_BaseNPC **ppAIs = g_AI_Manager.AccessAIs();
  401. int nAICount = g_AI_Manager.NumAIs();
  402. for ( int iAI = 0; iAI < nAICount; ++iAI )
  403. {
  404. CAI_BaseNPC *pNPC = ppAIs[iAI];
  405. if ( !pNPC )
  406. continue;
  407. if ( !pNPC->IsAlive() )
  408. continue;
  409. if ( pNPC->IsInAScript() )
  410. continue;
  411. CAI_FuncTankBehavior *pBehavior;
  412. if ( pNPC->GetBehavior( &pBehavior ) )
  413. {
  414. // Don't mount the func_tank if your "enemy" is within X feet or it or the npc.
  415. CBaseEntity *pEnemy = pNPC->GetEnemy();
  416. if ( pEnemy )
  417. {
  418. if ( !IsEntityInViewCone(pEnemy) )
  419. {
  420. // Don't mount the tank if the tank can't be aimed at the enemy.
  421. continue;
  422. }
  423. float flDist2 = ( pEnemy->GetAbsOrigin() - pNPC->GetAbsOrigin() ).LengthSqr();
  424. if ( flDist2 < flMinDistToEnemy2 )
  425. continue;
  426. flDist2 = ( vecMountPos - pEnemy->GetAbsOrigin() ).LengthSqr();
  427. if ( flDist2 < flMinDistToEnemy2 )
  428. continue;
  429. if ( !pNPC->FVisible( vecMountPos + pNPC->GetViewOffset() ) )
  430. continue;
  431. }
  432. trace_t tr;
  433. UTIL_TraceEntity( pNPC, vecMountPos, vecMountPos, MASK_NPCSOLID, this, pNPC->GetCollisionGroup(), &tr );
  434. if( tr.startsolid || tr.fraction < 1.0 )
  435. {
  436. // Don't mount the tank if someone/something is located on the control point.
  437. continue;
  438. }
  439. if ( !pBehavior->HasFuncTank() && !pBehavior->IsBusy() )
  440. {
  441. float flDist2 = ( vecMountPos - pNPC->GetAbsOrigin() ).LengthSqr();
  442. if ( flDist2 < flClosestDist2 )
  443. {
  444. pClosestNPC = pNPC;
  445. pClosestBehavior = pBehavior;
  446. flClosestDist2 = flDist2;
  447. }
  448. }
  449. }
  450. }
  451. // Set the closest NPC as controller.
  452. if ( pClosestNPC )
  453. {
  454. m_hController = pClosestNPC;
  455. pClosestBehavior->SetFuncTank( this );
  456. NPC_SetInRoute( true );
  457. }
  458. }
  459. //-----------------------------------------------------------------------------
  460. // Purpose: Draw any debug text overlays
  461. // Output : Current text offset from the top
  462. //-----------------------------------------------------------------------------
  463. int CFuncTank::DrawDebugTextOverlays(void)
  464. {
  465. int text_offset = BaseClass::DrawDebugTextOverlays();
  466. if (m_debugOverlays & OVERLAY_TEXT_BIT)
  467. {
  468. // --------------
  469. // State
  470. // --------------
  471. char tempstr[255];
  472. if (IsActive())
  473. {
  474. Q_strncpy(tempstr,"State: Active",sizeof(tempstr));
  475. }
  476. else
  477. {
  478. Q_strncpy(tempstr,"State: Inactive",sizeof(tempstr));
  479. }
  480. EntityText(text_offset,tempstr,0);
  481. text_offset++;
  482. // -------------------
  483. // Print Firing Speed
  484. // --------------------
  485. Q_snprintf(tempstr,sizeof(tempstr),"Fire Rate: %f",m_fireRate);
  486. EntityText(text_offset,tempstr,0);
  487. text_offset++;
  488. // --------------
  489. // Print Target
  490. // --------------
  491. if (m_hTarget!=NULL)
  492. {
  493. Q_snprintf(tempstr,sizeof(tempstr),"Target: %s",m_hTarget->GetDebugName());
  494. }
  495. else
  496. {
  497. Q_snprintf(tempstr,sizeof(tempstr),"Target: - ");
  498. }
  499. EntityText(text_offset,tempstr,0);
  500. text_offset++;
  501. // --------------
  502. // Target Pos
  503. // --------------
  504. if (m_spawnflags & SF_TANK_AIM_AT_POS)
  505. {
  506. Q_snprintf(tempstr,sizeof(tempstr),"Aim Pos: %3.0f %3.0f %3.0f",m_vTargetPosition.x,m_vTargetPosition.y,m_vTargetPosition.z);
  507. }
  508. else
  509. {
  510. Q_snprintf(tempstr,sizeof(tempstr),"Aim Pos: - ");
  511. }
  512. EntityText(text_offset,tempstr,0);
  513. text_offset++;
  514. }
  515. return text_offset;
  516. }
  517. //-----------------------------------------------------------------------------
  518. // Purpose: Override base class to add display of fly direction
  519. // Input :
  520. // Output :
  521. //-----------------------------------------------------------------------------
  522. void CFuncTank::DrawDebugGeometryOverlays(void)
  523. {
  524. // Center
  525. QAngle angCenter;
  526. Vector vecForward;
  527. angCenter = QAngle( 0, YawCenterWorld(), 0 );
  528. AngleVectors( angCenter, &vecForward );
  529. NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + (vecForward * 64), 255,255,255, true, 0.1);
  530. // Draw the yaw ranges
  531. angCenter = QAngle( 0, YawCenterWorld() + m_yawRange, 0 );
  532. AngleVectors( angCenter, &vecForward );
  533. NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + (vecForward * 128), 0,255,0, true, 0.1);
  534. angCenter = QAngle( 0, YawCenterWorld() - m_yawRange, 0 );
  535. AngleVectors( angCenter, &vecForward );
  536. NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + (vecForward * 128), 0,255,0, true, 0.1);
  537. // Draw the pitch ranges
  538. angCenter = QAngle( PitchCenterWorld() + m_pitchRange, 0, 0 );
  539. AngleVectors( angCenter, &vecForward );
  540. NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + (vecForward * 128), 255,0,0, true, 0.1);
  541. angCenter = QAngle( PitchCenterWorld() - m_pitchRange, 0, 0 );
  542. AngleVectors( angCenter, &vecForward );
  543. NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + (vecForward * 128), 255,0,0, true, 0.1);
  544. BaseClass::DrawDebugGeometryOverlays();
  545. }
  546. //-----------------------------------------------------------------------------
  547. // Purpose:
  548. // Input : pAttacker -
  549. // flDamage -
  550. // vecDir -
  551. // ptr -
  552. // bitsDamageType -
  553. //-----------------------------------------------------------------------------
  554. void CFuncTank::TraceAttack( CBaseEntity *pAttacker, float flDamage, const Vector &vecDir, trace_t *ptr, int bitsDamageType)
  555. {
  556. if (m_spawnflags & SF_TANK_DAMAGE_KICK)
  557. {
  558. // Deflect the func_tank
  559. // Only adjust yaw for now
  560. if (pAttacker)
  561. {
  562. Vector vFromAttacker = (pAttacker->EyePosition()-GetAbsOrigin());
  563. vFromAttacker.z = 0;
  564. VectorNormalize(vFromAttacker);
  565. Vector vFromAttacker2 = (ptr->endpos-GetAbsOrigin());
  566. vFromAttacker2.z = 0;
  567. VectorNormalize(vFromAttacker2);
  568. Vector vCrossProduct;
  569. CrossProduct(vFromAttacker,vFromAttacker2, vCrossProduct);
  570. QAngle angles;
  571. angles = GetLocalAngles();
  572. if (vCrossProduct.z > 0)
  573. {
  574. angles.y += 10;
  575. }
  576. else
  577. {
  578. angles.y -= 10;
  579. }
  580. // Limit against range in y
  581. if ( angles.y > m_yawCenter + m_yawRange )
  582. {
  583. angles.y = m_yawCenter + m_yawRange;
  584. }
  585. else if ( angles.y < (m_yawCenter - m_yawRange) )
  586. {
  587. angles.y = (m_yawCenter - m_yawRange);
  588. }
  589. SetLocalAngles( angles );
  590. }
  591. }
  592. }
  593. //-----------------------------------------------------------------------------
  594. // Purpose:
  595. // Input : targetName -
  596. // pActivator -
  597. //-----------------------------------------------------------------------------
  598. CBaseEntity *CFuncTank::FindTarget( string_t targetName, CBaseEntity *pActivator )
  599. {
  600. return gEntList.FindEntityGenericNearest( STRING( targetName ), GetAbsOrigin(), 0, this, pActivator );
  601. }
  602. //-----------------------------------------------------------------------------
  603. // Purpose: Caches entity key values until spawn is called.
  604. // Input : szKeyName -
  605. // szValue -
  606. // Output :
  607. //-----------------------------------------------------------------------------
  608. bool CFuncTank::KeyValue( const char *szKeyName, const char *szValue )
  609. {
  610. if (FStrEq(szKeyName, "barrel"))
  611. {
  612. m_barrelPos.x = atof(szValue);
  613. return true;
  614. }
  615. if (FStrEq(szKeyName, "barrely"))
  616. {
  617. m_barrelPos.y = atof(szValue);
  618. return true;
  619. }
  620. if (FStrEq(szKeyName, "barrelz"))
  621. {
  622. m_barrelPos.z = atof(szValue);
  623. return true;
  624. }
  625. return BaseClass::KeyValue( szKeyName, szValue );
  626. }
  627. static Vector gTankSpread[] =
  628. {
  629. Vector( 0, 0, 0 ), // perfect
  630. Vector( 0.025, 0.025, 0.025 ), // small cone
  631. Vector( 0.05, 0.05, 0.05 ), // medium cone
  632. Vector( 0.1, 0.1, 0.1 ), // large cone
  633. Vector( 0.25, 0.25, 0.25 ), // extra-large cone
  634. };
  635. #define MAX_FIRING_SPREADS ARRAYSIZE(gTankSpread)
  636. //-----------------------------------------------------------------------------
  637. // Purpose:
  638. //-----------------------------------------------------------------------------
  639. void CFuncTank::Spawn( void )
  640. {
  641. Precache();
  642. #ifdef HL2_EPISODIC
  643. m_iAmmoType = GetAmmoDef()->Index( STRING( m_iszAmmoType ) );
  644. #else
  645. m_iSmallAmmoType = GetAmmoDef()->Index("Pistol");
  646. m_iMediumAmmoType = GetAmmoDef()->Index("SMG1");
  647. m_iLargeAmmoType = GetAmmoDef()->Index("AR2");
  648. #endif // HL2_EPISODIC
  649. SetMoveType( MOVETYPE_PUSH ); // so it doesn't get pushed by anything
  650. SetSolid( SOLID_VPHYSICS );
  651. SetModel( STRING( GetModelName() ) );
  652. AddEFlags( EFL_USE_PARTITION_WHEN_NOT_SOLID );
  653. if ( HasSpawnFlags(SF_TANK_NOTSOLID) )
  654. {
  655. AddSolidFlags( FSOLID_NOT_SOLID );
  656. }
  657. m_hControlVolume = NULL;
  658. if ( GetParent() && GetParent()->GetBaseAnimating() )
  659. {
  660. CBaseAnimating *pAnim = GetParent()->GetBaseAnimating();
  661. if ( m_iszBaseAttachment != NULL_STRING )
  662. {
  663. int nAttachment = pAnim->LookupAttachment( STRING( m_iszBaseAttachment ) );
  664. if ( nAttachment != 0 )
  665. {
  666. SetParent( pAnim, nAttachment );
  667. SetLocalOrigin( vec3_origin );
  668. SetLocalAngles( vec3_angle );
  669. }
  670. }
  671. m_bUsePoseParameters = (m_iszYawPoseParam != NULL_STRING) && (m_iszPitchPoseParam != NULL_STRING);
  672. if ( m_iszBarrelAttachment != NULL_STRING )
  673. {
  674. if ( m_bUsePoseParameters )
  675. {
  676. pAnim->SetPoseParameter( STRING( m_iszYawPoseParam ), 0 );
  677. pAnim->SetPoseParameter( STRING( m_iszPitchPoseParam ), 0 );
  678. pAnim->InvalidateBoneCache();
  679. }
  680. m_nBarrelAttachment = pAnim->LookupAttachment( STRING(m_iszBarrelAttachment) );
  681. Vector vecWorldBarrelPos;
  682. QAngle worldBarrelAngle;
  683. pAnim->GetAttachment( m_nBarrelAttachment, vecWorldBarrelPos, worldBarrelAngle );
  684. VectorITransform( vecWorldBarrelPos, EntityToWorldTransform( ), m_barrelPos );
  685. }
  686. if ( m_bUsePoseParameters )
  687. {
  688. // In this case, we're relying on the parent to have the gun model
  689. AddEffects( EF_NODRAW );
  690. QAngle localAngles( m_flPitchPoseCenter, m_flYawPoseCenter, 0 );
  691. SetLocalAngles( localAngles );
  692. SetSolid( SOLID_NONE );
  693. SetMoveType( MOVETYPE_NOCLIP );
  694. // If our parent is a prop_dynamic, make it use hitboxes for renderbox
  695. CDynamicProp *pProp = dynamic_cast<CDynamicProp*>(GetParent());
  696. if ( pProp )
  697. {
  698. pProp->m_bUseHitboxesForRenderBox = true;
  699. }
  700. }
  701. }
  702. // For smoothing out leading
  703. m_flStartLeadFactor = 1.0f;
  704. m_flNextLeadFactor = 1.0f;
  705. m_flStartLeadFactorTime = gpGlobals->curtime;
  706. m_flNextLeadFactorTime = gpGlobals->curtime + 1.0f;
  707. m_yawCenter = GetLocalAngles().y;
  708. m_yawCenterWorld = GetAbsAngles().y;
  709. m_pitchCenter = GetLocalAngles().x;
  710. m_pitchCenterWorld = GetAbsAngles().y;
  711. m_vTargetPosition = vec3_origin;
  712. if ( IsActive() || (IsControllable() && !HasController()) )
  713. {
  714. // Think to find controllers.
  715. SetNextThink( gpGlobals->curtime + 1.0f );
  716. m_flNextControllerSearch = gpGlobals->curtime + 1.0f;
  717. }
  718. UpdateMatrix();
  719. m_sightOrigin = WorldBarrelPosition(); // Point at the end of the barrel
  720. if ( m_spread > MAX_FIRING_SPREADS )
  721. {
  722. m_spread = 0;
  723. }
  724. // No longer aim at target position if have one
  725. m_spawnflags &= ~SF_TANK_AIM_AT_POS;
  726. if (m_spawnflags & SF_TANK_DAMAGE_KICK)
  727. {
  728. m_takedamage = DAMAGE_YES;
  729. }
  730. // UNDONE: Do this?
  731. //m_targetEntityName = m_target;
  732. if ( GetSolid() != SOLID_NONE )
  733. {
  734. CreateVPhysics();
  735. }
  736. // Setup squared min/max range.
  737. m_flMinRange2 = m_minRange * m_minRange;
  738. m_flMaxRange2 = m_maxRange * m_maxRange;
  739. m_flIgnoreGraceUpto *= m_flIgnoreGraceUpto;
  740. m_flLastSawNonPlayer = 0;
  741. if( IsActive() )
  742. {
  743. m_OnReadyToFire.FireOutput( this, this );
  744. }
  745. }
  746. //-----------------------------------------------------------------------------
  747. // Purpose:
  748. //-----------------------------------------------------------------------------
  749. void CFuncTank::Activate( void )
  750. {
  751. BaseClass::Activate();
  752. // Necessary for save/load
  753. if ( (m_iszBarrelAttachment != NULL_STRING) && (m_nBarrelAttachment == 0) )
  754. {
  755. if ( GetParent() && GetParent()->GetBaseAnimating() )
  756. {
  757. CBaseAnimating *pAnim = GetParent()->GetBaseAnimating();
  758. m_nBarrelAttachment = pAnim->LookupAttachment( STRING(m_iszBarrelAttachment) );
  759. }
  760. }
  761. }
  762. bool CFuncTank::CreateVPhysics()
  763. {
  764. VPhysicsInitShadow( false, false );
  765. return true;
  766. }
  767. void CFuncTank::Precache( void )
  768. {
  769. if ( m_iszSpriteSmoke != NULL_STRING )
  770. PrecacheModel( STRING(m_iszSpriteSmoke) );
  771. if ( m_iszSpriteFlash != NULL_STRING )
  772. PrecacheModel( STRING(m_iszSpriteFlash) );
  773. if ( m_soundStartRotate != NULL_STRING )
  774. PrecacheScriptSound( STRING(m_soundStartRotate) );
  775. if ( m_soundStopRotate != NULL_STRING )
  776. PrecacheScriptSound( STRING(m_soundStopRotate) );
  777. if ( m_soundLoopRotate != NULL_STRING )
  778. PrecacheScriptSound( STRING(m_soundLoopRotate) );
  779. PrecacheScriptSound( "Func_Tank.BeginUse" );
  780. // Precache the combine cannon
  781. if ( m_iEffectHandling == EH_COMBINE_CANNON )
  782. {
  783. PrecacheScriptSound( "NPC_Combine_Cannon.FireBullet" );
  784. }
  785. }
  786. void CFuncTank::UpdateOnRemove( void )
  787. {
  788. if ( HasController() )
  789. {
  790. StopControl();
  791. }
  792. BaseClass::UpdateOnRemove();
  793. }
  794. //-----------------------------------------------------------------------------
  795. // Barrel position
  796. //-----------------------------------------------------------------------------
  797. void CFuncTank::UpdateMatrix( void )
  798. {
  799. m_parentMatrix.InitFromEntity( GetParent(), GetParentAttachment() );
  800. }
  801. //-----------------------------------------------------------------------------
  802. // Barrel position
  803. //-----------------------------------------------------------------------------
  804. Vector CFuncTank::WorldBarrelPosition( void )
  805. {
  806. if ( (m_nBarrelAttachment == 0) || !GetParent() )
  807. {
  808. EntityMatrix tmp;
  809. tmp.InitFromEntity( this );
  810. return tmp.LocalToWorld( m_barrelPos );
  811. }
  812. Vector vecOrigin;
  813. QAngle vecAngles;
  814. CBaseAnimating *pAnim = GetParent()->GetBaseAnimating();
  815. pAnim->GetAttachment( m_nBarrelAttachment, vecOrigin, vecAngles );
  816. return vecOrigin;
  817. }
  818. //-----------------------------------------------------------------------------
  819. // Make the parent's pose parameters match the func_tank
  820. //-----------------------------------------------------------------------------
  821. void CFuncTank::PhysicsSimulate( void )
  822. {
  823. BaseClass::PhysicsSimulate();
  824. if ( m_bUsePoseParameters && GetParent() )
  825. {
  826. const QAngle &angles = GetLocalAngles();
  827. CBaseAnimating *pAnim = GetParent()->GetBaseAnimating();
  828. pAnim->SetPoseParameter( STRING( m_iszYawPoseParam ), angles.y );
  829. pAnim->SetPoseParameter( STRING( m_iszPitchPoseParam ), angles.x );
  830. pAnim->StudioFrameAdvance();
  831. }
  832. }
  833. //=============================================================================
  834. //
  835. // TANK CONTROLLING
  836. //
  837. //-----------------------------------------------------------------------------
  838. // Purpose:
  839. //-----------------------------------------------------------------------------
  840. bool CFuncTank::OnControls( CBaseEntity *pTest )
  841. {
  842. // Is the tank controllable.
  843. if ( !IsControllable() )
  844. return false;
  845. if ( !m_hControlVolume )
  846. {
  847. // Find our control volume
  848. if ( m_iszControlVolume != NULL_STRING )
  849. {
  850. m_hControlVolume = dynamic_cast<CBaseTrigger*>( gEntList.FindEntityByName( NULL, m_iszControlVolume ) );
  851. }
  852. if (( !m_hControlVolume ) && IsControllable() )
  853. {
  854. Msg( "ERROR: Couldn't find control volume for player-controllable func_tank %s.\n", STRING(GetEntityName()) );
  855. return false;
  856. }
  857. }
  858. if ( m_hControlVolume->IsTouching( pTest ) )
  859. return true;
  860. return false;
  861. }
  862. //-----------------------------------------------------------------------------
  863. // Purpose:
  864. //-----------------------------------------------------------------------------
  865. bool CFuncTank::StartControl( CBaseCombatCharacter *pController )
  866. {
  867. // Check to see if we have a controller.
  868. if ( HasController() && GetController() != pController )
  869. return false;
  870. // Team only or disabled?
  871. if ( m_iszMaster != NULL_STRING )
  872. {
  873. if ( !UTIL_IsMasterTriggered( m_iszMaster, pController ) )
  874. return false;
  875. }
  876. // Set func_tank as manned by player/npc.
  877. m_hController = pController;
  878. if ( pController->IsPlayer() )
  879. {
  880. m_spawnflags |= SF_TANK_PLAYER;
  881. CBasePlayer *pPlayer = static_cast<CBasePlayer*>( m_hController.Get() );
  882. pPlayer->m_Local.m_iHideHUD |= HIDEHUD_WEAPONSELECTION;
  883. }
  884. else
  885. {
  886. m_spawnflags |= SF_TANK_NPC;
  887. NPC_SetInRoute( false );
  888. }
  889. // Holster player/npc weapon
  890. if ( m_hController->GetActiveWeapon() )
  891. {
  892. m_hController->GetActiveWeapon()->Holster();
  893. }
  894. // Set the controller's position to be the use position.
  895. m_vecControllerUsePos = m_hController->GetLocalOrigin();
  896. EmitSound( "Func_Tank.BeginUse" );
  897. SetNextThink( gpGlobals->curtime + 0.1f );
  898. // Let the map maker know a controller has been found
  899. if ( m_hController->IsPlayer() )
  900. {
  901. m_OnGotPlayerController.FireOutput( this, this );
  902. }
  903. else
  904. {
  905. m_OnGotController.FireOutput( this, this );
  906. }
  907. OnStartControlled();
  908. return true;
  909. }
  910. //-----------------------------------------------------------------------------
  911. // Purpose:
  912. // TODO: bring back the controllers current weapon
  913. //-----------------------------------------------------------------------------
  914. void CFuncTank::StopControl()
  915. {
  916. // Do we have a controller?
  917. if ( !m_hController )
  918. return;
  919. OnStopControlled();
  920. // Arm player/npc weapon.
  921. if ( m_hController->GetActiveWeapon() )
  922. {
  923. m_hController->GetActiveWeapon()->Deploy();
  924. }
  925. if ( m_hController->IsPlayer() )
  926. {
  927. CBasePlayer *pPlayer = static_cast<CBasePlayer*>( m_hController.Get() );
  928. pPlayer->m_Local.m_iHideHUD &= ~HIDEHUD_WEAPONSELECTION;
  929. }
  930. // Stop thinking.
  931. SetNextThink( TICK_NEVER_THINK );
  932. // Let the map maker know a controller has been lost.
  933. if ( m_hController->IsPlayer() )
  934. {
  935. m_OnLostPlayerController.FireOutput( this, this );
  936. }
  937. else
  938. {
  939. m_OnLostController.FireOutput( this, this );
  940. }
  941. // Reset the func_tank as unmanned (player/npc).
  942. if ( m_hController->IsPlayer() )
  943. {
  944. m_spawnflags &= ~SF_TANK_PLAYER;
  945. }
  946. else
  947. {
  948. m_spawnflags &= ~SF_TANK_NPC;
  949. }
  950. m_hController = NULL;
  951. // Set think, if the func_tank can think on its own.
  952. if ( IsActive() || (IsControllable() && !HasController()) )
  953. {
  954. // Delay the think to find controllers a bit
  955. #ifdef FUNCTANK_AUTOUSE
  956. m_flNextControllerSearch = gpGlobals->curtime + 1.0f;
  957. #else
  958. m_flNextControllerSearch = gpGlobals->curtime + 5.0f;
  959. #endif//FUNCTANK_AUTOUSE
  960. SetNextThink( m_flNextControllerSearch );
  961. }
  962. SetLocalAngularVelocity( vec3_angle );
  963. }
  964. //-----------------------------------------------------------------------------
  965. // Purpose:
  966. // Called each frame by the player's ItemPostFrame
  967. //-----------------------------------------------------------------------------
  968. // NVNT turret recoil
  969. ConVar hap_turret_mag("hap_turret_mag", "5", 0);
  970. void CFuncTank::ControllerPostFrame( void )
  971. {
  972. // Make sure we have a contoller.
  973. Assert( m_hController != NULL );
  974. // Control the firing rate.
  975. if ( gpGlobals->curtime < m_flNextAttack )
  976. return;
  977. if ( !IsPlayerManned() )
  978. return;
  979. CBasePlayer *pPlayer = static_cast<CBasePlayer*>( m_hController.Get() );
  980. if ( ( pPlayer->m_nButtons & IN_ATTACK ) == 0 )
  981. return;
  982. Vector forward;
  983. AngleVectors( GetAbsAngles(), &forward );
  984. m_fireLast = gpGlobals->curtime - (1/m_fireRate) - 0.01; // to make sure the gun doesn't fire too many bullets
  985. int bulletCount = (gpGlobals->curtime - m_fireLast) * m_fireRate;
  986. if( HasSpawnFlags( SF_TANK_AIM_ASSISTANCE ) )
  987. {
  988. // Trace out a hull and if it hits something, adjust the shot to hit that thing.
  989. trace_t tr;
  990. Vector start = WorldBarrelPosition();
  991. Vector dir = forward;
  992. UTIL_TraceHull( start, start + forward * 8192, -Vector(8,8,8), Vector(8,8,8), MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
  993. if( tr.m_pEnt && tr.m_pEnt->m_takedamage != DAMAGE_NO && (tr.m_pEnt->GetFlags() & FL_AIMTARGET) )
  994. {
  995. forward = tr.m_pEnt->WorldSpaceCenter() - start;
  996. VectorNormalize( forward );
  997. }
  998. }
  999. Fire( bulletCount, WorldBarrelPosition(), forward, pPlayer, false );
  1000. #if defined( WIN32 ) && !defined( _X360 )
  1001. // NVNT apply a punch on the player each time fired
  1002. HapticPunch(pPlayer,0,0,hap_turret_mag.GetFloat());
  1003. #endif
  1004. // HACKHACK -- make some noise (that the AI can hear)
  1005. CSoundEnt::InsertSound( SOUND_COMBAT, WorldSpaceCenter(), FUNCTANK_FIREVOLUME, 0.2 );
  1006. if( m_iAmmoCount > -1 )
  1007. {
  1008. if( !(m_iAmmoCount % 10) )
  1009. {
  1010. Msg("Ammo Remaining: %d\n", m_iAmmoCount );
  1011. }
  1012. if( --m_iAmmoCount == 0 )
  1013. {
  1014. // Kick the player off the gun, and make myself not usable.
  1015. m_spawnflags &= ~SF_TANK_CANCONTROL;
  1016. StopControl();
  1017. return;
  1018. }
  1019. }
  1020. SetNextAttack( gpGlobals->curtime + (1/m_fireRate) );
  1021. }
  1022. //-----------------------------------------------------------------------------
  1023. // Purpose:
  1024. // Output : Returns true on success, false on failure.
  1025. //-----------------------------------------------------------------------------
  1026. bool CFuncTank::HasController( void )
  1027. {
  1028. return (m_hController != NULL);
  1029. }
  1030. //-----------------------------------------------------------------------------
  1031. // Purpose:
  1032. // Output : CBaseCombatCharacter
  1033. //-----------------------------------------------------------------------------
  1034. CBaseCombatCharacter *CFuncTank::GetController( void )
  1035. {
  1036. return m_hController;
  1037. }
  1038. //-----------------------------------------------------------------------------
  1039. // Purpose:
  1040. //-----------------------------------------------------------------------------
  1041. bool CFuncTank::NPC_FindManPoint( Vector &vecPos )
  1042. {
  1043. if ( m_iszNPCManPoint != NULL_STRING )
  1044. {
  1045. CBaseEntity *pEntity = gEntList.FindEntityByName( NULL, m_iszNPCManPoint );
  1046. if ( pEntity )
  1047. {
  1048. vecPos = pEntity->GetAbsOrigin();
  1049. return true;
  1050. }
  1051. }
  1052. return false;
  1053. }
  1054. //-----------------------------------------------------------------------------
  1055. // Purpose: The NPC manning this gun just saw a player for the first time since he left cover
  1056. //-----------------------------------------------------------------------------
  1057. void CFuncTank::NPC_JustSawPlayer( CBaseEntity *pTarget )
  1058. {
  1059. SetNextAttack( gpGlobals->curtime + m_flPlayerLockTimeBeforeFire );
  1060. }
  1061. //-----------------------------------------------------------------------------
  1062. // Purpose:
  1063. //-----------------------------------------------------------------------------
  1064. void CFuncTank::NPC_Fire( void )
  1065. {
  1066. // Control the firing rate.
  1067. if ( gpGlobals->curtime < m_flNextAttack )
  1068. return;
  1069. // Check for a valid npc controller.
  1070. if ( !m_hController )
  1071. return;
  1072. CAI_BaseNPC *pNPC = m_hController->MyNPCPointer();
  1073. if ( !pNPC )
  1074. return;
  1075. // Setup for next round of firing.
  1076. if ( m_nBulletCount == 0 )
  1077. {
  1078. m_nBulletCount = GetRandomBurst();
  1079. m_fireTime = 1.0f;
  1080. }
  1081. // m_fireLast looks like it is only needed for Active non-controlled func_tank.
  1082. // m_fireLast = gpGlobals->curtime - (1/m_fireRate) - 0.01; // to make sure the gun doesn't fire too many bullets
  1083. Vector vecBarrelEnd = WorldBarrelPosition();
  1084. Vector vecForward;
  1085. AngleVectors( GetAbsAngles(), &vecForward );
  1086. if ( (pNPC->CapabilitiesGet() & bits_CAP_NO_HIT_SQUADMATES) && pNPC->IsInSquad() )
  1087. {
  1088. // Avoid shooting squadmates.
  1089. if ( pNPC->IsSquadmateInSpread( vecBarrelEnd, vecBarrelEnd + vecForward * 2048, gTankSpread[m_spread].x, 8*12 ) )
  1090. {
  1091. return;
  1092. }
  1093. }
  1094. if ( !HasSpawnFlags( SF_TANK_ALLOW_PLAYER_HITS ) && (pNPC->CapabilitiesGet() & bits_CAP_NO_HIT_PLAYER) )
  1095. {
  1096. // Avoid shooting player.
  1097. if ( pNPC->PlayerInSpread( vecBarrelEnd, vecBarrelEnd + vecForward * 2048, gTankSpread[m_spread].x, 8*12 ) )
  1098. {
  1099. return;
  1100. }
  1101. }
  1102. bool bIgnoreSpread = false;
  1103. CBaseEntity *pEnemy = pNPC->GetEnemy();
  1104. if ( HasSpawnFlags( SF_TANK_HACKPLAYERHIT ) && pEnemy && pEnemy->IsPlayer() )
  1105. {
  1106. // Every third shot should be fired directly at the player
  1107. if ( m_nBulletCount%2 == 0 )
  1108. {
  1109. Vector vecBodyTarget = pEnemy->BodyTarget( vecBarrelEnd, false );
  1110. vecForward = (vecBodyTarget - vecBarrelEnd);
  1111. VectorNormalize( vecForward );
  1112. bIgnoreSpread = true;
  1113. }
  1114. }
  1115. // Fire the bullet(s).
  1116. Fire( 1, vecBarrelEnd, vecForward, m_hController, bIgnoreSpread );
  1117. --m_nBulletCount;
  1118. // Check ammo counts and dismount when empty.
  1119. if( m_iAmmoCount > -1 )
  1120. {
  1121. if( --m_iAmmoCount == 0 )
  1122. {
  1123. // Disable the func_tank.
  1124. m_spawnflags &= ~SF_TANK_CANCONTROL;
  1125. // Remove the npc.
  1126. StopControl();
  1127. return;
  1128. }
  1129. }
  1130. float flFireTime = GetRandomFireTime();
  1131. if ( m_nBulletCount != 0 )
  1132. {
  1133. m_fireTime -= flFireTime;
  1134. SetNextAttack( gpGlobals->curtime + flFireTime );
  1135. }
  1136. else
  1137. {
  1138. SetNextAttack( gpGlobals->curtime + m_fireTime );
  1139. }
  1140. }
  1141. //-----------------------------------------------------------------------------
  1142. // Purpose:
  1143. //-----------------------------------------------------------------------------
  1144. bool CFuncTank::NPC_HasEnemy( void )
  1145. {
  1146. if ( !IsNPCManned() )
  1147. return false;
  1148. CAI_BaseNPC *pNPC = m_hController->MyNPCPointer();
  1149. Assert( pNPC );
  1150. return ( pNPC->GetEnemy() != NULL );
  1151. }
  1152. //-----------------------------------------------------------------------------
  1153. // Purpose:
  1154. //-----------------------------------------------------------------------------
  1155. void CFuncTank::NPC_InterruptRoute( void )
  1156. {
  1157. if ( !m_hController )
  1158. return;
  1159. CAI_BaseNPC *pNPC = m_hController->MyNPCPointer();
  1160. if ( !pNPC )
  1161. return;
  1162. CAI_FuncTankBehavior *pBehavior;
  1163. if ( pNPC->GetBehavior( &pBehavior ) )
  1164. {
  1165. pBehavior->SetFuncTank( NULL );
  1166. }
  1167. // Reset the npc controller.
  1168. m_hController = NULL;
  1169. // No NPC's in route.
  1170. NPC_SetInRoute( false );
  1171. // Delay the think to find controllers a bit
  1172. m_flNextControllerSearch = gpGlobals->curtime + 5.0f;
  1173. if ( !HasController() )
  1174. {
  1175. // Start thinking to find controllers again
  1176. SetNextThink( m_flNextControllerSearch );
  1177. }
  1178. }
  1179. //-----------------------------------------------------------------------------
  1180. // Purpose:
  1181. //-----------------------------------------------------------------------------
  1182. bool CFuncTank::NPC_InterruptController( void )
  1183. {
  1184. // If we don't have a controller - then the gun should be free.
  1185. if ( !m_hController )
  1186. return true;
  1187. CAI_BaseNPC *pNPC = m_hController->MyNPCPointer();
  1188. if ( !pNPC || !pNPC->IsPlayerAlly() )
  1189. return false;
  1190. CAI_FuncTankBehavior *pBehavior;
  1191. if ( pNPC->GetBehavior( &pBehavior ) )
  1192. {
  1193. pBehavior->Dismount();
  1194. }
  1195. m_hController = NULL;
  1196. return true;
  1197. }
  1198. //-----------------------------------------------------------------------------
  1199. // Purpose:
  1200. // Output : float
  1201. //-----------------------------------------------------------------------------
  1202. float CFuncTank::GetRandomFireTime( void )
  1203. {
  1204. Assert( m_fireRate != 0 );
  1205. float flOOFireRate = 1.0f / m_fireRate;
  1206. float flOOFireRateBy2 = flOOFireRate * 0.5f;
  1207. float flOOFireRateBy4 = flOOFireRate * 0.25f;
  1208. return random->RandomFloat( flOOFireRateBy4, flOOFireRateBy2 );
  1209. }
  1210. //-----------------------------------------------------------------------------
  1211. // Purpose:
  1212. // Output : int
  1213. //-----------------------------------------------------------------------------
  1214. int CFuncTank::GetRandomBurst( void )
  1215. {
  1216. return random->RandomInt( m_fireRate-2, m_fireRate+2 );
  1217. }
  1218. //-----------------------------------------------------------------------------
  1219. // Purpose:
  1220. // Input : *pActivator -
  1221. // *pCaller -
  1222. // useType -
  1223. // value -
  1224. //-----------------------------------------------------------------------------
  1225. void CFuncTank::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
  1226. {
  1227. if ( !IsControllable() )
  1228. return;
  1229. // player controlled turret
  1230. CBasePlayer *pPlayer = ToBasePlayer( pActivator );
  1231. if ( !pPlayer )
  1232. return;
  1233. if ( value == 2 && useType == USE_SET )
  1234. {
  1235. ControllerPostFrame();
  1236. }
  1237. else if ( m_hController != pPlayer && useType != USE_OFF )
  1238. {
  1239. // The player must be within the func_tank controls
  1240. if ( !m_hControlVolume )
  1241. {
  1242. // Find our control volume
  1243. if ( m_iszControlVolume != NULL_STRING )
  1244. {
  1245. m_hControlVolume = dynamic_cast<CBaseTrigger*>( gEntList.FindEntityByName( NULL, m_iszControlVolume ) );
  1246. }
  1247. if (( !m_hControlVolume ) && IsControllable() )
  1248. {
  1249. Msg( "ERROR: Couldn't find control volume for player-controllable func_tank %s.\n", STRING(GetEntityName()) );
  1250. return;
  1251. }
  1252. }
  1253. if ( !m_hControlVolume->IsTouching( pPlayer ) )
  1254. return;
  1255. // Interrupt any npc in route (ally or not).
  1256. if ( NPC_InRoute() )
  1257. {
  1258. // Interrupt the npc's route.
  1259. NPC_InterruptRoute();
  1260. }
  1261. // Interrupt NPC - if possible (they must be allies).
  1262. if ( IsNPCControllable() && HasController() )
  1263. {
  1264. if ( !NPC_InterruptController() )
  1265. return;
  1266. }
  1267. pPlayer->SetUseEntity( this );
  1268. StartControl( pPlayer );
  1269. }
  1270. else
  1271. {
  1272. StopControl();
  1273. }
  1274. }
  1275. //-----------------------------------------------------------------------------
  1276. // Purpose:
  1277. // Input : range -
  1278. // Output : Returns true on success, false on failure.
  1279. //-----------------------------------------------------------------------------
  1280. bool CFuncTank::InRange( float range )
  1281. {
  1282. if ( range < m_minRange )
  1283. return FALSE;
  1284. if ( (m_maxRange > 0) && (range > m_maxRange) )
  1285. return FALSE;
  1286. return TRUE;
  1287. }
  1288. //-----------------------------------------------------------------------------
  1289. // Purpose:
  1290. //-----------------------------------------------------------------------------
  1291. bool CFuncTank::InRange2( float flRange2 )
  1292. {
  1293. if ( flRange2 < m_flMinRange2 )
  1294. return false;
  1295. if ( ( m_flMaxRange2 > 0.0f ) && ( flRange2 > m_flMaxRange2 ) )
  1296. return false;
  1297. return true;
  1298. }
  1299. //-----------------------------------------------------------------------------
  1300. // Purpose:
  1301. //-----------------------------------------------------------------------------
  1302. void CFuncTank::Think( void )
  1303. {
  1304. FuncTankPreThink();
  1305. m_hFuncTankTarget = NULL;
  1306. // Look for a new controller?
  1307. if ( IsControllable() && !HasController() && (m_flNextControllerSearch <= gpGlobals->curtime) )
  1308. {
  1309. if ( m_bShouldFindNPCs && gpGlobals->curtime > 5.0f )
  1310. {
  1311. // Check for in route and timer.
  1312. if ( !NPC_InRoute() )
  1313. {
  1314. NPC_FindController();
  1315. }
  1316. }
  1317. #ifdef FUNCTANK_AUTOUSE
  1318. CBasePlayer *pPlayer = UTIL_PlayerByIndex(1);
  1319. bool bThinkFast = false;
  1320. if( pPlayer )
  1321. {
  1322. if ( !m_hControlVolume )
  1323. {
  1324. // Find our control volume
  1325. if ( m_iszControlVolume != NULL_STRING )
  1326. {
  1327. m_hControlVolume = dynamic_cast<CBaseTrigger*>( gEntList.FindEntityByName( NULL, m_iszControlVolume ) );
  1328. }
  1329. if (( !m_hControlVolume ) && IsControllable() )
  1330. {
  1331. Msg( "ERROR: Couldn't find control volume for player-controllable func_tank %s.\n", STRING(GetEntityName()) );
  1332. return;
  1333. }
  1334. }
  1335. if ( m_hControlVolume )
  1336. {
  1337. if( m_hControlVolume->IsTouching( pPlayer ) && pPlayer->FInViewCone(WorldSpaceCenter()) )
  1338. {
  1339. // If my control volume is touching a player that's facing the mounted gun, automatically use the gun.
  1340. // !!!BUGBUG - this only works in cases where the player can see the gun whilst standing in the control
  1341. // volume. (This works just fine for all func_tanks mounted on combine walls and small barriers)
  1342. variant_t emptyVariant;
  1343. AcceptInput( "Use", pPlayer, pPlayer, emptyVariant, USE_TOGGLE );
  1344. }
  1345. else
  1346. {
  1347. // If the player is nearby, think faster for snappier response to XBox auto mounting
  1348. float flDistSqr = GetAbsOrigin().DistToSqr( pPlayer->GetAbsOrigin() );
  1349. if( flDistSqr <= Square(360) )
  1350. {
  1351. bThinkFast = true;
  1352. }
  1353. }
  1354. }
  1355. }
  1356. // Keep thinking, in case they turn NPC finding back on
  1357. if ( !HasController() )
  1358. {
  1359. if( bThinkFast )
  1360. {
  1361. SetNextThink( gpGlobals->curtime + 0.1f );
  1362. }
  1363. else
  1364. {
  1365. SetNextThink( gpGlobals->curtime + 2.0f );
  1366. }
  1367. }
  1368. if( bThinkFast )
  1369. {
  1370. m_flNextControllerSearch = gpGlobals->curtime + 0.1f;
  1371. }
  1372. else
  1373. {
  1374. m_flNextControllerSearch = gpGlobals->curtime + 2.0f;
  1375. }
  1376. #else
  1377. // Keep thinking, in case they turn NPC finding back on
  1378. if ( !HasController() )
  1379. {
  1380. SetNextThink( gpGlobals->curtime + 2.0f );
  1381. }
  1382. m_flNextControllerSearch = gpGlobals->curtime + 2.0f;
  1383. #endif//FUNCTANK_AUTOUSE
  1384. }
  1385. // refresh the matrix
  1386. UpdateMatrix();
  1387. SetLocalAngularVelocity( vec3_angle );
  1388. TrackTarget();
  1389. if ( fabs(GetLocalAngularVelocity().x) > 1 || fabs(GetLocalAngularVelocity().y) > 1 )
  1390. {
  1391. StartRotSound();
  1392. }
  1393. else
  1394. {
  1395. StopRotSound();
  1396. }
  1397. FuncTankPostThink();
  1398. }
  1399. //-----------------------------------------------------------------------------
  1400. // Purpose: Aim the offset barrel at a position in parent space
  1401. // Input : parentTarget - the position of the target in parent space
  1402. // Output : Vector - angles in local space
  1403. //-----------------------------------------------------------------------------
  1404. QAngle CFuncTank::AimBarrelAt( const Vector &parentTarget )
  1405. {
  1406. Vector target = parentTarget - GetLocalOrigin();
  1407. float quadTarget = target.LengthSqr();
  1408. float quadTargetXY = target.x*target.x + target.y*target.y;
  1409. // Target is too close! Can't aim at it
  1410. if ( quadTarget <= m_barrelPos.LengthSqr() )
  1411. {
  1412. return GetLocalAngles();
  1413. }
  1414. else
  1415. {
  1416. // We're trying to aim the offset barrel at an arbitrary point.
  1417. // To calculate this, I think of the target as being on a sphere with
  1418. // it's center at the origin of the gun.
  1419. // The rotation we need is the opposite of the rotation that moves the target
  1420. // along the surface of that sphere to intersect with the gun's shooting direction
  1421. // To calculate that rotation, we simply calculate the intersection of the ray
  1422. // coming out of the barrel with the target sphere (that's the new target position)
  1423. // and use atan2() to get angles
  1424. // angles from target pos to center
  1425. float targetToCenterYaw = atan2( target.y, target.x );
  1426. float centerToGunYaw = atan2( m_barrelPos.y, sqrt( quadTarget - (m_barrelPos.y*m_barrelPos.y) ) );
  1427. float targetToCenterPitch = atan2( target.z, sqrt( quadTargetXY ) );
  1428. float centerToGunPitch = atan2( -m_barrelPos.z, sqrt( quadTarget - (m_barrelPos.z*m_barrelPos.z) ) );
  1429. return QAngle( -RAD2DEG(targetToCenterPitch+centerToGunPitch), RAD2DEG( targetToCenterYaw + centerToGunYaw ), 0 );
  1430. }
  1431. }
  1432. //-----------------------------------------------------------------------------
  1433. // Aim the tank at the player crosshair
  1434. //-----------------------------------------------------------------------------
  1435. void CFuncTank::CalcPlayerCrosshairTarget( Vector *pVecTarget )
  1436. {
  1437. // Get the player.
  1438. CBasePlayer *pPlayer = static_cast<CBasePlayer*>( m_hController.Get() );
  1439. // Tank aims at player's crosshair.
  1440. Vector vecStart, vecDir;
  1441. trace_t tr;
  1442. vecStart = pPlayer->EyePosition();
  1443. if ( !IsX360() )
  1444. {
  1445. vecDir = pPlayer->EyeDirection3D();
  1446. }
  1447. else
  1448. {
  1449. // Use autoaim as the eye dir.
  1450. vecDir = pPlayer->GetAutoaimVector( AUTOAIM_SCALE_DEFAULT );
  1451. }
  1452. // Make sure to start the trace outside of the player's bbox!
  1453. UTIL_TraceLine( vecStart + vecDir * 24, vecStart + vecDir * 8192, MASK_BLOCKLOS_AND_NPCS, this, COLLISION_GROUP_NONE, &tr );
  1454. *pVecTarget = tr.endpos;
  1455. }
  1456. //-----------------------------------------------------------------------------
  1457. // Aim the tank at the player crosshair
  1458. //-----------------------------------------------------------------------------
  1459. void CFuncTank::AimBarrelAtPlayerCrosshair( QAngle *pAngles )
  1460. {
  1461. Vector vecTarget;
  1462. CalcPlayerCrosshairTarget( &vecTarget );
  1463. *pAngles = AimBarrelAt( m_parentMatrix.WorldToLocal( vecTarget ) );
  1464. }
  1465. //-----------------------------------------------------------------------------
  1466. // Aim the tank at the NPC's enemy
  1467. //-----------------------------------------------------------------------------
  1468. void CFuncTank::CalcNPCEnemyTarget( Vector *pVecTarget )
  1469. {
  1470. Vector vecTarget;
  1471. CAI_BaseNPC *pNPC = m_hController->MyNPCPointer();
  1472. // Aim the barrel at the npc's enemy, or where the npc is looking.
  1473. CBaseEntity *pEnemy = pNPC->GetEnemy();
  1474. if ( pEnemy )
  1475. {
  1476. // Clear the idle target
  1477. *pVecTarget = pEnemy->BodyTarget( GetAbsOrigin(), false );
  1478. m_vecNPCIdleTarget = *pVecTarget;
  1479. }
  1480. else
  1481. {
  1482. if ( m_vecNPCIdleTarget != vec3_origin )
  1483. {
  1484. *pVecTarget = m_vecNPCIdleTarget;
  1485. }
  1486. else
  1487. {
  1488. Vector vecForward;
  1489. QAngle angCenter( 0, m_yawCenterWorld, 0 );
  1490. AngleVectors( angCenter, &vecForward );
  1491. trace_t tr;
  1492. Vector vecBarrel = GetAbsOrigin() + m_barrelPos;
  1493. UTIL_TraceLine( vecBarrel, vecBarrel + vecForward * 8192, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
  1494. *pVecTarget = tr.endpos;
  1495. }
  1496. }
  1497. }
  1498. //-----------------------------------------------------------------------------
  1499. // Aim the tank at the NPC's enemy
  1500. //-----------------------------------------------------------------------------
  1501. void CFuncTank::AimBarrelAtNPCEnemy( QAngle *pAngles )
  1502. {
  1503. Vector vecTarget;
  1504. CalcNPCEnemyTarget( &vecTarget );
  1505. *pAngles = AimBarrelAt( m_parentMatrix.WorldToLocal( vecTarget ) );
  1506. }
  1507. //-----------------------------------------------------------------------------
  1508. // Returns true if the desired angles are out of range
  1509. //-----------------------------------------------------------------------------
  1510. bool CFuncTank::RotateTankToAngles( const QAngle &angles, float *pDistX, float *pDistY )
  1511. {
  1512. bool bClamped = false;
  1513. // Force the angles to be relative to the center position
  1514. float offsetY = UTIL_AngleDistance( angles.y, m_yawCenter );
  1515. float offsetX = UTIL_AngleDistance( angles.x, m_pitchCenter );
  1516. float flActualYaw = m_yawCenter + offsetY;
  1517. float flActualPitch = m_pitchCenter + offsetX;
  1518. if ( ( fabs( offsetY ) > m_yawRange + m_yawTolerance ) ||
  1519. ( fabs( offsetX ) > m_pitchRange + m_pitchTolerance ) )
  1520. {
  1521. // Limit against range in x
  1522. flActualYaw = clamp( flActualYaw, m_yawCenter - m_yawRange, m_yawCenter + m_yawRange );
  1523. flActualPitch = clamp( flActualPitch, m_pitchCenter - m_pitchRange, m_pitchCenter + m_pitchRange );
  1524. bClamped = true;
  1525. }
  1526. // Get at the angular vel
  1527. QAngle vecAngVel = GetLocalAngularVelocity();
  1528. // Move toward target at rate or less
  1529. float distY = UTIL_AngleDistance( flActualYaw, GetLocalAngles().y );
  1530. vecAngVel.y = distY * 10;
  1531. vecAngVel.y = clamp( vecAngVel.y, -m_yawRate, m_yawRate );
  1532. // Move toward target at rate or less
  1533. float distX = UTIL_AngleDistance( flActualPitch, GetLocalAngles().x );
  1534. vecAngVel.x = distX * 10;
  1535. vecAngVel.x = clamp( vecAngVel.x, -m_pitchRate, m_pitchRate );
  1536. // How exciting! We're done
  1537. SetLocalAngularVelocity( vecAngVel );
  1538. if ( pDistX && pDistY )
  1539. {
  1540. *pDistX = distX;
  1541. *pDistY = distY;
  1542. }
  1543. return bClamped;
  1544. }
  1545. //-----------------------------------------------------------------------------
  1546. // We lost our target!
  1547. //-----------------------------------------------------------------------------
  1548. void CFuncTank::LostTarget( void )
  1549. {
  1550. if (m_fireLast != 0)
  1551. {
  1552. m_OnLoseTarget.FireOutput(this, this);
  1553. m_fireLast = 0;
  1554. }
  1555. }
  1556. //-----------------------------------------------------------------------------
  1557. // Purpose:
  1558. //-----------------------------------------------------------------------------
  1559. void CFuncTank::ComputeLeadingPosition( const Vector &vecShootPosition, CBaseEntity *pTarget, Vector *pLeadPosition )
  1560. {
  1561. Vector vecTarget = pTarget->BodyTarget( vecShootPosition, false );
  1562. float flShotSpeed = GetShotSpeed();
  1563. if ( flShotSpeed == 0 )
  1564. {
  1565. *pLeadPosition = vecTarget;
  1566. return;
  1567. }
  1568. Vector vecVelocity = pTarget->GetSmoothedVelocity();
  1569. vecVelocity.z = 0.0f;
  1570. float flTargetSpeed = VectorNormalize( vecVelocity );
  1571. // Guesstimate...
  1572. if ( m_flNextLeadFactorTime < gpGlobals->curtime )
  1573. {
  1574. m_flStartLeadFactor = m_flNextLeadFactor;
  1575. m_flStartLeadFactorTime = gpGlobals->curtime;
  1576. m_flNextLeadFactor = random->RandomFloat( 0.8f, 1.3f );
  1577. m_flNextLeadFactorTime = gpGlobals->curtime + random->RandomFloat( 2.0f, 4.0f );
  1578. }
  1579. float flFactor = (gpGlobals->curtime - m_flStartLeadFactorTime) / (m_flNextLeadFactorTime - m_flStartLeadFactorTime);
  1580. float flLeadFactor = SimpleSplineRemapVal( flFactor, 0.0f, 1.0f, m_flStartLeadFactor, m_flNextLeadFactor );
  1581. flTargetSpeed *= flLeadFactor;
  1582. Vector vecDelta;
  1583. VectorSubtract( vecShootPosition, vecTarget, vecDelta );
  1584. float flTargetToShooter = VectorNormalize( vecDelta );
  1585. float flCosTheta = DotProduct( vecDelta, vecVelocity );
  1586. // Law of cosines... z^2 = x^2 + y^2 - 2xy cos Theta
  1587. // where z = flShooterToPredictedTargetPosition = flShotSpeed * predicted time
  1588. // x = flTargetSpeed * predicted time
  1589. // y = flTargetToShooter
  1590. // solve for predicted time using at^2 + bt + c = 0, t = (-b +/- sqrt( b^2 - 4ac )) / 2a
  1591. float a = flTargetSpeed * flTargetSpeed - flShotSpeed * flShotSpeed;
  1592. float b = -2.0f * flTargetToShooter * flCosTheta * flTargetSpeed;
  1593. float c = flTargetToShooter * flTargetToShooter;
  1594. float flDiscrim = b*b - 4*a*c;
  1595. if (flDiscrim < 0)
  1596. {
  1597. *pLeadPosition = vecTarget;
  1598. return;
  1599. }
  1600. flDiscrim = sqrt(flDiscrim);
  1601. float t = (-b + flDiscrim) / (2.0f * a);
  1602. float t2 = (-b - flDiscrim) / (2.0f * a);
  1603. if ( t < t2 )
  1604. {
  1605. t = t2;
  1606. }
  1607. if ( t <= 0.0f )
  1608. {
  1609. *pLeadPosition = vecTarget;
  1610. return;
  1611. }
  1612. VectorMA( vecTarget, flTargetSpeed * t, vecVelocity, *pLeadPosition );
  1613. }
  1614. //-----------------------------------------------------------------------------
  1615. // Purpose:
  1616. //-----------------------------------------------------------------------------
  1617. void CFuncTank::AimFuncTankAtTarget( void )
  1618. {
  1619. // Get world target position
  1620. CBaseEntity *pTarget = NULL;
  1621. trace_t tr;
  1622. QAngle angles;
  1623. bool bUpdateTime = false;
  1624. CBaseEntity *pTargetVehicle = NULL;
  1625. Vector barrelEnd = WorldBarrelPosition();
  1626. Vector worldTargetPosition;
  1627. if (m_spawnflags & SF_TANK_AIM_AT_POS)
  1628. {
  1629. worldTargetPosition = m_vTargetPosition;
  1630. }
  1631. else
  1632. {
  1633. CBaseEntity *pEntity = (CBaseEntity *)m_hTarget;
  1634. if ( !pEntity || ( pEntity->GetFlags() & FL_NOTARGET ) )
  1635. {
  1636. if( m_targetEntityName != NULL_STRING )
  1637. {
  1638. m_hTarget = FindTarget( m_targetEntityName, NULL );
  1639. }
  1640. LostTarget();
  1641. return;
  1642. }
  1643. pTarget = pEntity;
  1644. // Calculate angle needed to aim at target
  1645. worldTargetPosition = pEntity->EyePosition();
  1646. if ( pEntity->IsPlayer() )
  1647. {
  1648. CBasePlayer *pPlayer = assert_cast<CBasePlayer*>(pEntity);
  1649. pTargetVehicle = pPlayer->GetVehicleEntity();
  1650. if ( pTargetVehicle )
  1651. {
  1652. worldTargetPosition = pTargetVehicle->BodyTarget( GetAbsOrigin(), false );
  1653. }
  1654. }
  1655. }
  1656. float range2 = worldTargetPosition.DistToSqr( barrelEnd );
  1657. if ( !InRange2( range2 ) )
  1658. {
  1659. if ( m_hTarget )
  1660. {
  1661. m_hTarget = NULL;
  1662. LostTarget();
  1663. }
  1664. return;
  1665. }
  1666. Vector vecAimOrigin = m_sightOrigin;
  1667. if (m_spawnflags & SF_TANK_AIM_AT_POS)
  1668. {
  1669. bUpdateTime = true;
  1670. m_sightOrigin = m_vTargetPosition;
  1671. vecAimOrigin = m_sightOrigin;
  1672. }
  1673. else
  1674. {
  1675. if ( m_spawnflags & SF_TANK_LINEOFSIGHT )
  1676. {
  1677. AI_TraceLOS( barrelEnd, worldTargetPosition, this, &tr );
  1678. }
  1679. else
  1680. {
  1681. tr.fraction = 1.0f;
  1682. tr.m_pEnt = pTarget;
  1683. }
  1684. // No line of sight, don't track
  1685. if ( tr.fraction == 1.0 || tr.m_pEnt == pTarget || (pTargetVehicle && (tr.m_pEnt == pTargetVehicle)) )
  1686. {
  1687. if ( InRange2( range2 ) && pTarget && pTarget->IsAlive() )
  1688. {
  1689. bUpdateTime = true;
  1690. // Sight position is BodyTarget with no noise (so gun doesn't bob up and down)
  1691. CBaseEntity *pInstance = pTargetVehicle ? pTargetVehicle : pTarget;
  1692. m_hFuncTankTarget = pInstance;
  1693. m_sightOrigin = pInstance->BodyTarget( GetAbsOrigin(), false );
  1694. if ( m_bPerformLeading )
  1695. {
  1696. ComputeLeadingPosition( barrelEnd, pInstance, &vecAimOrigin );
  1697. }
  1698. else
  1699. {
  1700. vecAimOrigin = m_sightOrigin;
  1701. }
  1702. }
  1703. }
  1704. }
  1705. // Convert targetPosition to parent
  1706. Vector vecLocalOrigin = m_parentMatrix.WorldToLocal( vecAimOrigin );
  1707. angles = AimBarrelAt( vecLocalOrigin );
  1708. // FIXME: These need to be the clamped angles
  1709. float distX, distY;
  1710. bool bClamped = RotateTankToAngles( angles, &distX, &distY );
  1711. if ( bClamped )
  1712. {
  1713. bUpdateTime = false;
  1714. }
  1715. if ( bUpdateTime )
  1716. {
  1717. if( (gpGlobals->curtime - m_lastSightTime >= 1.0) && (gpGlobals->curtime > m_flNextAttack) )
  1718. {
  1719. // Enemy was hidden for a while, and I COULD fire right now. Instead, tack a delay on.
  1720. m_flNextAttack = gpGlobals->curtime + 0.5;
  1721. }
  1722. m_lastSightTime = gpGlobals->curtime;
  1723. m_persist2burst = 0;
  1724. }
  1725. SetMoveDoneTime( 0.1 );
  1726. if ( CanFire() && ( ( (fabs(distX) <= m_pitchTolerance) && (fabs(distY) <= m_yawTolerance) ) || (m_spawnflags & SF_TANK_LINEOFSIGHT) ) )
  1727. {
  1728. bool fire = false;
  1729. Vector forward;
  1730. AngleVectors( GetLocalAngles(), &forward );
  1731. forward = m_parentMatrix.ApplyRotation( forward );
  1732. if ( m_spawnflags & SF_TANK_LINEOFSIGHT )
  1733. {
  1734. AI_TraceLine( barrelEnd, pTarget->WorldSpaceCenter(), MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
  1735. if ( tr.fraction == 1.0f || (tr.m_pEnt && tr.m_pEnt == pTarget) )
  1736. {
  1737. fire = true;
  1738. }
  1739. }
  1740. else
  1741. {
  1742. fire = true;
  1743. }
  1744. if ( fire )
  1745. {
  1746. if (m_fireLast == 0)
  1747. {
  1748. m_OnAquireTarget.FireOutput(this, this);
  1749. }
  1750. FiringSequence( barrelEnd, forward, this );
  1751. }
  1752. else
  1753. {
  1754. LostTarget();
  1755. }
  1756. }
  1757. else
  1758. {
  1759. LostTarget();
  1760. }
  1761. }
  1762. //-----------------------------------------------------------------------------
  1763. // Purpose:
  1764. //-----------------------------------------------------------------------------
  1765. void CFuncTank::TrackTarget( void )
  1766. {
  1767. QAngle angles;
  1768. if( !m_bReadyToFire && m_flNextAttack <= gpGlobals->curtime )
  1769. {
  1770. m_OnReadyToFire.FireOutput( this, this );
  1771. m_bReadyToFire = true;
  1772. }
  1773. if ( IsPlayerManned() )
  1774. {
  1775. AimBarrelAtPlayerCrosshair( &angles );
  1776. RotateTankToAngles( angles );
  1777. SetNextThink( gpGlobals->curtime + 0.05f );
  1778. SetMoveDoneTime( 0.1 );
  1779. return;
  1780. }
  1781. if ( IsNPCManned() )
  1782. {
  1783. AimBarrelAtNPCEnemy( &angles );
  1784. RotateTankToAngles( angles );
  1785. SetNextThink( gpGlobals->curtime + 0.05f );
  1786. SetMoveDoneTime( 0.1 );
  1787. return;
  1788. }
  1789. if ( !IsActive() )
  1790. {
  1791. // If we're not active, but we're controllable, we need to keep thinking
  1792. if ( IsControllable() && !HasController() )
  1793. {
  1794. // Think to find controllers.
  1795. SetNextThink( m_flNextControllerSearch );
  1796. }
  1797. return;
  1798. }
  1799. // Clean room for unnecessarily complicated old code
  1800. SetNextThink( gpGlobals->curtime + 0.1f );
  1801. AimFuncTankAtTarget();
  1802. }
  1803. //-----------------------------------------------------------------------------
  1804. // Purpose: Start of firing sequence. By default, just fire now.
  1805. // Input : &barrelEnd -
  1806. // &forward -
  1807. // *pAttacker -
  1808. //-----------------------------------------------------------------------------
  1809. void CFuncTank::FiringSequence( const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker )
  1810. {
  1811. if ( m_fireLast != 0 )
  1812. {
  1813. int bulletCount = (gpGlobals->curtime - m_fireLast) * m_fireRate;
  1814. if ( bulletCount > 0 )
  1815. {
  1816. // NOTE: Set m_fireLast first so that Fire can adjust it
  1817. m_fireLast = gpGlobals->curtime;
  1818. Fire( bulletCount, barrelEnd, forward, pAttacker, false );
  1819. }
  1820. }
  1821. else
  1822. {
  1823. m_fireLast = gpGlobals->curtime;
  1824. }
  1825. }
  1826. //-----------------------------------------------------------------------------
  1827. // Purpose:
  1828. //-----------------------------------------------------------------------------
  1829. void CFuncTank::DoMuzzleFlash( void )
  1830. {
  1831. // If we're parented to something, make it play the muzzleflash
  1832. if ( m_bUsePoseParameters && GetParent() )
  1833. {
  1834. CBaseAnimating *pAnim = GetParent()->GetBaseAnimating();
  1835. pAnim->DoMuzzleFlash();
  1836. // Do the AR2 muzzle flash
  1837. if ( m_iEffectHandling == EH_COMBINE_CANNON )
  1838. {
  1839. CEffectData data;
  1840. data.m_nAttachmentIndex = m_nBarrelAttachment;
  1841. data.m_nEntIndex = pAnim->entindex();
  1842. // FIXME: Create a custom entry here!
  1843. DispatchEffect( "ChopperMuzzleFlash", data );
  1844. }
  1845. else
  1846. {
  1847. CEffectData data;
  1848. data.m_nEntIndex = pAnim->entindex();
  1849. data.m_nAttachmentIndex = m_nBarrelAttachment;
  1850. data.m_flScale = 1.0f;
  1851. data.m_fFlags = MUZZLEFLASH_COMBINE;
  1852. DispatchEffect( "MuzzleFlash", data );
  1853. }
  1854. }
  1855. }
  1856. //-----------------------------------------------------------------------------
  1857. // Purpose:
  1858. // Output : const char
  1859. //-----------------------------------------------------------------------------
  1860. const char *CFuncTank::GetTracerType( void )
  1861. {
  1862. switch( m_iEffectHandling )
  1863. {
  1864. case EH_AR2:
  1865. return "AR2Tracer";
  1866. case EH_COMBINE_CANNON:
  1867. return "HelicopterTracer";
  1868. }
  1869. return NULL;
  1870. }
  1871. //-----------------------------------------------------------------------------
  1872. // Purpose: Fire targets and spawn sprites.
  1873. // Input : bulletCount -
  1874. // barrelEnd -
  1875. // forward -
  1876. // pAttacker -
  1877. //-----------------------------------------------------------------------------
  1878. void CFuncTank::Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker, bool bIgnoreSpread )
  1879. {
  1880. // If we have a specific effect handler, apply it's effects
  1881. if ( m_iEffectHandling == EH_AR2 )
  1882. {
  1883. DoMuzzleFlash();
  1884. // Play the AR2 sound
  1885. EmitSound( "Weapon_functank.Single" );
  1886. }
  1887. else if ( m_iEffectHandling == EH_COMBINE_CANNON )
  1888. {
  1889. DoMuzzleFlash();
  1890. // Play the cannon sound
  1891. EmitSound( "NPC_Combine_Cannon.FireBullet" );
  1892. }
  1893. else
  1894. {
  1895. if ( m_iszSpriteSmoke != NULL_STRING )
  1896. {
  1897. CSprite *pSprite = CSprite::SpriteCreate( STRING(m_iszSpriteSmoke), barrelEnd, TRUE );
  1898. pSprite->AnimateAndDie( random->RandomFloat( 15.0, 20.0 ) );
  1899. pSprite->SetTransparency( kRenderTransAlpha, m_clrRender->r, m_clrRender->g, m_clrRender->b, 255, kRenderFxNone );
  1900. Vector vecVelocity( 0, 0, random->RandomFloat(40, 80) );
  1901. pSprite->SetAbsVelocity( vecVelocity );
  1902. pSprite->SetScale( m_spriteScale );
  1903. }
  1904. if ( m_iszSpriteFlash != NULL_STRING )
  1905. {
  1906. CSprite *pSprite = CSprite::SpriteCreate( STRING(m_iszSpriteFlash), barrelEnd, TRUE );
  1907. pSprite->AnimateAndDie( 5 );
  1908. pSprite->SetTransparency( kRenderTransAdd, 255, 255, 255, 255, kRenderFxNoDissipation );
  1909. pSprite->SetScale( m_spriteScale );
  1910. }
  1911. }
  1912. if( pAttacker && pAttacker->IsPlayer() )
  1913. {
  1914. if ( IsX360() )
  1915. {
  1916. UTIL_PlayerByIndex(1)->RumbleEffect( RUMBLE_AR2, 0, RUMBLE_FLAG_RESTART | RUMBLE_FLAG_RANDOM_AMPLITUDE );
  1917. }
  1918. else
  1919. {
  1920. CSoundEnt::InsertSound( SOUND_MOVE_AWAY, barrelEnd + forward * 32.0f, 32.0f, 0.2f, pAttacker, SOUNDENT_CHANNEL_WEAPON );
  1921. }
  1922. }
  1923. m_OnFire.FireOutput(this, this);
  1924. m_bReadyToFire = false;
  1925. }
  1926. void CFuncTank::TankTrace( const Vector &vecStart, const Vector &vecForward, const Vector &vecSpread, trace_t &tr )
  1927. {
  1928. Vector forward, right, up;
  1929. AngleVectors( GetAbsAngles(), &forward, &right, &up );
  1930. // get circular gaussian spread
  1931. float x, y, z;
  1932. do {
  1933. x = random->RandomFloat(-0.5,0.5) + random->RandomFloat(-0.5,0.5);
  1934. y = random->RandomFloat(-0.5,0.5) + random->RandomFloat(-0.5,0.5);
  1935. z = x*x+y*y;
  1936. } while (z > 1);
  1937. Vector vecDir = vecForward +
  1938. x * vecSpread.x * right +
  1939. y * vecSpread.y * up;
  1940. Vector vecEnd;
  1941. vecEnd = vecStart + vecDir * MAX_TRACE_LENGTH;
  1942. UTIL_TraceLine( vecStart, vecEnd, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr );
  1943. }
  1944. void CFuncTank::StartRotSound( void )
  1945. {
  1946. if ( m_spawnflags & SF_TANK_SOUNDON )
  1947. return;
  1948. m_spawnflags |= SF_TANK_SOUNDON;
  1949. if ( m_soundLoopRotate != NULL_STRING )
  1950. {
  1951. CPASAttenuationFilter filter( this );
  1952. filter.MakeReliable();
  1953. EmitSound_t ep;
  1954. ep.m_nChannel = CHAN_STATIC;
  1955. ep.m_pSoundName = (char*)STRING(m_soundLoopRotate);
  1956. ep.m_flVolume = 0.85;
  1957. ep.m_SoundLevel = SNDLVL_NORM;
  1958. EmitSound( filter, entindex(), ep );
  1959. }
  1960. if ( m_soundStartRotate != NULL_STRING )
  1961. {
  1962. CPASAttenuationFilter filter( this );
  1963. EmitSound_t ep;
  1964. ep.m_nChannel = CHAN_BODY;
  1965. ep.m_pSoundName = (char*)STRING(m_soundStartRotate);
  1966. ep.m_flVolume = 1.0f;
  1967. ep.m_SoundLevel = SNDLVL_NORM;
  1968. EmitSound( filter, entindex(), ep );
  1969. }
  1970. }
  1971. void CFuncTank::StopRotSound( void )
  1972. {
  1973. if ( m_spawnflags & SF_TANK_SOUNDON )
  1974. {
  1975. if ( m_soundLoopRotate != NULL_STRING )
  1976. {
  1977. StopSound( entindex(), CHAN_STATIC, (char*)STRING(m_soundLoopRotate) );
  1978. }
  1979. if ( m_soundStopRotate != NULL_STRING )
  1980. {
  1981. CPASAttenuationFilter filter( this );
  1982. EmitSound_t ep;
  1983. ep.m_nChannel = CHAN_BODY;
  1984. ep.m_pSoundName = (char*)STRING(m_soundStopRotate);
  1985. ep.m_flVolume = 1.0f;
  1986. ep.m_SoundLevel = SNDLVL_NORM;
  1987. EmitSound( filter, entindex(), ep );
  1988. }
  1989. }
  1990. m_spawnflags &= ~SF_TANK_SOUNDON;
  1991. }
  1992. //-----------------------------------------------------------------------------
  1993. // Purpose:
  1994. //-----------------------------------------------------------------------------
  1995. bool CFuncTank::IsEntityInViewCone( CBaseEntity *pEntity )
  1996. {
  1997. // First check to see if the enemy is in range.
  1998. Vector vecBarrelEnd = WorldBarrelPosition();
  1999. float flRange2 = ( pEntity->GetAbsOrigin() - vecBarrelEnd ).LengthSqr();
  2000. if( !(GetSpawnFlags() & SF_TANK_IGNORE_RANGE_IN_VIEWCONE) )
  2001. {
  2002. if ( !InRange2( flRange2 ) )
  2003. return false;
  2004. }
  2005. // If we're trying to shoot at a player, and we've seen a non-player recently, check the grace period
  2006. if ( m_flPlayerGracePeriod && pEntity->IsPlayer() && (gpGlobals->curtime - m_flLastSawNonPlayer) < m_flPlayerGracePeriod )
  2007. {
  2008. // Grace period is ignored under a certain distance
  2009. if ( flRange2 > m_flIgnoreGraceUpto )
  2010. return false;
  2011. }
  2012. // Check to see if the entity center lies within the yaw and pitch constraints.
  2013. // This isn't horribly accurate, but should do for now.
  2014. QAngle angGun;
  2015. angGun = AimBarrelAt( m_parentMatrix.WorldToLocal( pEntity->GetAbsOrigin() ) );
  2016. // Force the angles to be relative to the center position
  2017. float flOffsetY = UTIL_AngleDistance( angGun.y, m_yawCenter );
  2018. float flOffsetX = UTIL_AngleDistance( angGun.x, m_pitchCenter );
  2019. angGun.y = m_yawCenter + flOffsetY;
  2020. angGun.x = m_pitchCenter + flOffsetX;
  2021. if ( ( fabs( flOffsetY ) > m_yawRange + m_yawTolerance ) || ( fabs( flOffsetX ) > m_pitchRange + m_pitchTolerance ) )
  2022. return false;
  2023. // Remember the last time we saw a non-player
  2024. if ( !pEntity->IsPlayer() )
  2025. {
  2026. m_flLastSawNonPlayer = gpGlobals->curtime;
  2027. }
  2028. return true;
  2029. }
  2030. //-----------------------------------------------------------------------------
  2031. // Purpose: Return true if this func tank can see the enemy
  2032. //-----------------------------------------------------------------------------
  2033. bool CFuncTank::HasLOSTo( CBaseEntity *pEntity )
  2034. {
  2035. if ( !pEntity )
  2036. return false;
  2037. // Get the barrel position
  2038. Vector vecBarrelEnd = WorldBarrelPosition();
  2039. Vector vecTarget = pEntity->BodyTarget( GetAbsOrigin(), false );
  2040. trace_t tr;
  2041. // Ignore the func_tank and any prop it's parented to
  2042. CTraceFilterSkipTwoEntities traceFilter( this, GetParent(), COLLISION_GROUP_NONE );
  2043. // UNDONE: Should this hit BLOCKLOS brushes?
  2044. AI_TraceLine( vecBarrelEnd, vecTarget, MASK_BLOCKLOS_AND_NPCS, &traceFilter, &tr );
  2045. CBaseEntity *pHitEntity = tr.m_pEnt;
  2046. // Is entity in a vehicle? if so, verify vehicle is target and return if so (so npc shoots at vehicle)
  2047. CBaseCombatCharacter *pCCEntity = pEntity->MyCombatCharacterPointer();
  2048. if ( pCCEntity != NULL && pCCEntity->IsInAVehicle() )
  2049. {
  2050. // Ok, player in vehicle, check if vehicle is target we're looking at, fire if it is
  2051. // Also, check to see if the owner of the entity is the vehicle, in which case it's valid too.
  2052. // This catches vehicles that use bone followers.
  2053. CBaseEntity *pVehicle = pCCEntity->GetVehicle()->GetVehicleEnt();
  2054. if ( pHitEntity == pVehicle || ( pHitEntity != NULL && pHitEntity->GetOwnerEntity() == pVehicle ) )
  2055. return true;
  2056. }
  2057. return ( tr.fraction == 1.0 || tr.m_pEnt == pEntity );
  2058. }
  2059. // #############################################################################
  2060. // CFuncTankGun
  2061. // #############################################################################
  2062. class CFuncTankGun : public CFuncTank
  2063. {
  2064. public:
  2065. DECLARE_CLASS( CFuncTankGun, CFuncTank );
  2066. void Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker, bool bIgnoreSpread );
  2067. };
  2068. LINK_ENTITY_TO_CLASS( func_tank, CFuncTankGun );
  2069. //-----------------------------------------------------------------------------
  2070. // Purpose:
  2071. //-----------------------------------------------------------------------------
  2072. void CFuncTankGun::Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker, bool bIgnoreSpread )
  2073. {
  2074. int i;
  2075. FireBulletsInfo_t info;
  2076. info.m_iShots = 1;
  2077. info.m_vecSrc = barrelEnd;
  2078. info.m_vecDirShooting = forward;
  2079. if ( bIgnoreSpread )
  2080. {
  2081. info.m_vecSpread = gTankSpread[0];
  2082. }
  2083. else
  2084. {
  2085. info.m_vecSpread = gTankSpread[m_spread];
  2086. }
  2087. info.m_flDistance = MAX_TRACE_LENGTH;
  2088. info.m_iTracerFreq = 1;
  2089. info.m_flDamage = m_iBulletDamage;
  2090. info.m_iPlayerDamage = m_iBulletDamageVsPlayer;
  2091. info.m_pAttacker = pAttacker;
  2092. info.m_pAdditionalIgnoreEnt = GetParent();
  2093. #ifdef HL2_EPISODIC
  2094. if ( m_iAmmoType != -1 )
  2095. {
  2096. for ( i = 0; i < bulletCount; i++ )
  2097. {
  2098. info.m_iAmmoType = m_iAmmoType;
  2099. FireBullets( info );
  2100. }
  2101. }
  2102. #else
  2103. for ( i = 0; i < bulletCount; i++ )
  2104. {
  2105. switch( m_bulletType )
  2106. {
  2107. case TANK_BULLET_SMALL:
  2108. info.m_iAmmoType = m_iSmallAmmoType;
  2109. FireBullets( info );
  2110. break;
  2111. case TANK_BULLET_MEDIUM:
  2112. info.m_iAmmoType = m_iMediumAmmoType;
  2113. FireBullets( info );
  2114. break;
  2115. case TANK_BULLET_LARGE:
  2116. info.m_iAmmoType = m_iLargeAmmoType;
  2117. FireBullets( info );
  2118. break;
  2119. default:
  2120. case TANK_BULLET_NONE:
  2121. break;
  2122. }
  2123. }
  2124. #endif // HL2_EPISODIC
  2125. CFuncTank::Fire( bulletCount, barrelEnd, forward, pAttacker, bIgnoreSpread );
  2126. }
  2127. // #############################################################################
  2128. // CFuncTankPulseLaser
  2129. // #############################################################################
  2130. class CFuncTankPulseLaser : public CFuncTankGun
  2131. {
  2132. public:
  2133. DECLARE_CLASS( CFuncTankPulseLaser, CFuncTankGun );
  2134. DECLARE_DATADESC();
  2135. void Precache();
  2136. void Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker, bool bIgnoreSpread );
  2137. float m_flPulseSpeed;
  2138. float m_flPulseWidth;
  2139. color32 m_flPulseColor;
  2140. float m_flPulseLife;
  2141. float m_flPulseLag;
  2142. string_t m_sPulseFireSound;
  2143. };
  2144. LINK_ENTITY_TO_CLASS( func_tankpulselaser, CFuncTankPulseLaser );
  2145. BEGIN_DATADESC( CFuncTankPulseLaser )
  2146. DEFINE_KEYFIELD( m_flPulseSpeed, FIELD_FLOAT, "PulseSpeed" ),
  2147. DEFINE_KEYFIELD( m_flPulseWidth, FIELD_FLOAT, "PulseWidth" ),
  2148. DEFINE_KEYFIELD( m_flPulseColor, FIELD_COLOR32, "PulseColor" ),
  2149. DEFINE_KEYFIELD( m_flPulseLife, FIELD_FLOAT, "PulseLife" ),
  2150. DEFINE_KEYFIELD( m_flPulseLag, FIELD_FLOAT, "PulseLag" ),
  2151. DEFINE_KEYFIELD( m_sPulseFireSound, FIELD_SOUNDNAME, "PulseFireSound" ),
  2152. END_DATADESC()
  2153. //------------------------------------------------------------------------------
  2154. // Purpose :
  2155. // Input :
  2156. // Output :
  2157. //------------------------------------------------------------------------------
  2158. void CFuncTankPulseLaser::Precache(void)
  2159. {
  2160. UTIL_PrecacheOther( "grenade_beam" );
  2161. if ( m_sPulseFireSound != NULL_STRING )
  2162. {
  2163. PrecacheScriptSound( STRING(m_sPulseFireSound) );
  2164. }
  2165. BaseClass::Precache();
  2166. }
  2167. //------------------------------------------------------------------------------
  2168. // Purpose :
  2169. // Input :
  2170. // Output :
  2171. //------------------------------------------------------------------------------
  2172. void CFuncTankPulseLaser::Fire( int bulletCount, const Vector &barrelEnd, const Vector &vecForward, CBaseEntity *pAttacker, bool bIgnoreSpread )
  2173. {
  2174. // --------------------------------------------------
  2175. // Get direction vectors for spread
  2176. // --------------------------------------------------
  2177. Vector vecUp = Vector(0,0,1);
  2178. Vector vecRight;
  2179. CrossProduct ( vecForward, vecUp, vecRight );
  2180. CrossProduct ( vecForward, -vecRight, vecUp );
  2181. for ( int i = 0; i < bulletCount; i++ )
  2182. {
  2183. // get circular gaussian spread
  2184. float x, y, z;
  2185. do {
  2186. x = random->RandomFloat(-0.5,0.5) + random->RandomFloat(-0.5,0.5);
  2187. y = random->RandomFloat(-0.5,0.5) + random->RandomFloat(-0.5,0.5);
  2188. z = x*x+y*y;
  2189. } while (z > 1);
  2190. Vector vecDir = vecForward + x * gTankSpread[m_spread].x * vecRight + y * gTankSpread[m_spread].y * vecUp;
  2191. CGrenadeBeam *pPulse = CGrenadeBeam::Create( pAttacker, barrelEnd);
  2192. pPulse->Format(m_flPulseColor, m_flPulseWidth);
  2193. pPulse->Shoot(vecDir,m_flPulseSpeed,m_flPulseLife,m_flPulseLag,m_iBulletDamage);
  2194. if ( m_sPulseFireSound != NULL_STRING )
  2195. {
  2196. CPASAttenuationFilter filter( this, 0.6f );
  2197. EmitSound_t ep;
  2198. ep.m_nChannel = CHAN_WEAPON;
  2199. ep.m_pSoundName = (char*)STRING(m_sPulseFireSound);
  2200. ep.m_flVolume = 1.0f;
  2201. ep.m_SoundLevel = SNDLVL_85dB;
  2202. EmitSound( filter, entindex(), ep );
  2203. }
  2204. }
  2205. CFuncTank::Fire( bulletCount, barrelEnd, vecForward, pAttacker, bIgnoreSpread );
  2206. }
  2207. // #############################################################################
  2208. // CFuncTankLaser
  2209. // #############################################################################
  2210. class CFuncTankLaser : public CFuncTank
  2211. {
  2212. DECLARE_CLASS( CFuncTankLaser, CFuncTank );
  2213. public:
  2214. void Activate( void );
  2215. void Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker, bool bIgnoreSpread );
  2216. void Think( void );
  2217. CEnvLaser *GetLaser( void );
  2218. DECLARE_DATADESC();
  2219. private:
  2220. CEnvLaser *m_pLaser;
  2221. float m_laserTime;
  2222. string_t m_iszLaserName;
  2223. };
  2224. LINK_ENTITY_TO_CLASS( func_tanklaser, CFuncTankLaser );
  2225. BEGIN_DATADESC( CFuncTankLaser )
  2226. DEFINE_KEYFIELD( m_iszLaserName, FIELD_STRING, "laserentity" ),
  2227. DEFINE_FIELD( m_pLaser, FIELD_CLASSPTR ),
  2228. DEFINE_FIELD( m_laserTime, FIELD_TIME ),
  2229. END_DATADESC()
  2230. void CFuncTankLaser::Activate( void )
  2231. {
  2232. BaseClass::Activate();
  2233. if ( !GetLaser() )
  2234. {
  2235. UTIL_Remove(this);
  2236. Warning( "Laser tank with no env_laser!\n" );
  2237. }
  2238. else
  2239. {
  2240. m_pLaser->TurnOff();
  2241. }
  2242. }
  2243. CEnvLaser *CFuncTankLaser::GetLaser( void )
  2244. {
  2245. if ( m_pLaser )
  2246. return m_pLaser;
  2247. CBaseEntity *pLaser = gEntList.FindEntityByName( NULL, m_iszLaserName );
  2248. while ( pLaser )
  2249. {
  2250. // Found the landmark
  2251. if ( FClassnameIs( pLaser, "env_laser" ) )
  2252. {
  2253. m_pLaser = (CEnvLaser *)pLaser;
  2254. break;
  2255. }
  2256. else
  2257. {
  2258. pLaser = gEntList.FindEntityByName( pLaser, m_iszLaserName );
  2259. }
  2260. }
  2261. return m_pLaser;
  2262. }
  2263. void CFuncTankLaser::Think( void )
  2264. {
  2265. if ( m_pLaser && (gpGlobals->curtime > m_laserTime) )
  2266. m_pLaser->TurnOff();
  2267. CFuncTank::Think();
  2268. }
  2269. void CFuncTankLaser::Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker, bool bIgnoreSpread )
  2270. {
  2271. int i;
  2272. trace_t tr;
  2273. if ( GetLaser() )
  2274. {
  2275. for ( i = 0; i < bulletCount; i++ )
  2276. {
  2277. m_pLaser->SetLocalOrigin( barrelEnd );
  2278. TankTrace( barrelEnd, forward, gTankSpread[m_spread], tr );
  2279. m_laserTime = gpGlobals->curtime;
  2280. m_pLaser->TurnOn();
  2281. m_pLaser->SetFireTime( gpGlobals->curtime - 1.0 );
  2282. m_pLaser->FireAtPoint( tr );
  2283. m_pLaser->SetNextThink( TICK_NEVER_THINK );
  2284. }
  2285. CFuncTank::Fire( bulletCount, barrelEnd, forward, this, bIgnoreSpread );
  2286. }
  2287. }
  2288. class CFuncTankRocket : public CFuncTank
  2289. {
  2290. public:
  2291. DECLARE_CLASS( CFuncTankRocket, CFuncTank );
  2292. void Precache( void );
  2293. void Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker, bool bIgnoreSpread );
  2294. virtual float GetShotSpeed() { return m_flRocketSpeed; }
  2295. protected:
  2296. float m_flRocketSpeed;
  2297. DECLARE_DATADESC();
  2298. };
  2299. BEGIN_DATADESC( CFuncTankRocket )
  2300. DEFINE_KEYFIELD( m_flRocketSpeed, FIELD_FLOAT, "rocketspeed" ),
  2301. END_DATADESC()
  2302. LINK_ENTITY_TO_CLASS( func_tankrocket, CFuncTankRocket );
  2303. void CFuncTankRocket::Precache( void )
  2304. {
  2305. UTIL_PrecacheOther( "rpg_missile" );
  2306. CFuncTank::Precache();
  2307. }
  2308. void CFuncTankRocket::Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker, bool bIgnoreSpread )
  2309. {
  2310. CMissile *pRocket = (CMissile *) CBaseEntity::Create( "rpg_missile", barrelEnd, GetAbsAngles(), this );
  2311. pRocket->DumbFire();
  2312. pRocket->SetNextThink( gpGlobals->curtime + 0.1f );
  2313. pRocket->SetAbsVelocity( forward * m_flRocketSpeed );
  2314. if ( GetController() && GetController()->IsPlayer() )
  2315. {
  2316. pRocket->SetDamage( m_iBulletDamage );
  2317. }
  2318. else
  2319. {
  2320. pRocket->SetDamage( m_iBulletDamageVsPlayer );
  2321. }
  2322. CFuncTank::Fire( bulletCount, barrelEnd, forward, this, bIgnoreSpread );
  2323. }
  2324. //-----------------------------------------------------------------------------
  2325. // Airboat gun
  2326. //-----------------------------------------------------------------------------
  2327. class CFuncTankAirboatGun : public CFuncTank
  2328. {
  2329. public:
  2330. DECLARE_CLASS( CFuncTankAirboatGun, CFuncTank );
  2331. DECLARE_DATADESC();
  2332. void Precache( void );
  2333. virtual void Spawn();
  2334. virtual void Activate();
  2335. virtual void Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker, bool bIgnoreSpread );
  2336. virtual void ControllerPostFrame();
  2337. virtual void OnStopControlled();
  2338. virtual const char *GetTracerType( void );
  2339. virtual Vector WorldBarrelPosition( void );
  2340. virtual void DoImpactEffect( trace_t &tr, int nDamageType );
  2341. private:
  2342. void CreateSounds();
  2343. void DestroySounds();
  2344. void DoMuzzleFlash( );
  2345. void StartFiring();
  2346. void StopFiring();
  2347. CSoundPatch *m_pGunFiringSound;
  2348. float m_flNextHeavyShotTime;
  2349. bool m_bIsFiring;
  2350. string_t m_iszAirboatGunModel;
  2351. CHandle<CBaseAnimating> m_hAirboatGunModel;
  2352. int m_nGunBarrelAttachment;
  2353. float m_flLastImpactEffectTime;
  2354. };
  2355. //-----------------------------------------------------------------------------
  2356. // Save/load:
  2357. //-----------------------------------------------------------------------------
  2358. BEGIN_DATADESC( CFuncTankAirboatGun )
  2359. DEFINE_SOUNDPATCH( m_pGunFiringSound ),
  2360. DEFINE_FIELD( m_flNextHeavyShotTime, FIELD_TIME ),
  2361. DEFINE_FIELD( m_bIsFiring, FIELD_BOOLEAN ),
  2362. DEFINE_KEYFIELD( m_iszAirboatGunModel, FIELD_STRING, "airboat_gun_model" ),
  2363. // DEFINE_FIELD( m_hAirboatGunModel, FIELD_EHANDLE ),
  2364. // DEFINE_FIELD( m_nGunBarrelAttachment, FIELD_INTEGER ),
  2365. DEFINE_FIELD( m_flLastImpactEffectTime, FIELD_TIME ),
  2366. END_DATADESC()
  2367. LINK_ENTITY_TO_CLASS( func_tankairboatgun, CFuncTankAirboatGun );
  2368. //-----------------------------------------------------------------------------
  2369. // Precache:
  2370. //-----------------------------------------------------------------------------
  2371. void CFuncTankAirboatGun::Precache( void )
  2372. {
  2373. BaseClass::Precache();
  2374. PrecacheScriptSound( "Airboat.FireGunLoop" );
  2375. PrecacheScriptSound( "Airboat.FireGunRevDown");
  2376. CreateSounds();
  2377. }
  2378. //-----------------------------------------------------------------------------
  2379. // Precache:
  2380. //-----------------------------------------------------------------------------
  2381. void CFuncTankAirboatGun::Spawn( void )
  2382. {
  2383. BaseClass::Spawn();
  2384. m_flNextHeavyShotTime = 0.0f;
  2385. m_bIsFiring = false;
  2386. m_flLastImpactEffectTime = -1;
  2387. }
  2388. //-----------------------------------------------------------------------------
  2389. // Attachment indices
  2390. //-----------------------------------------------------------------------------
  2391. void CFuncTankAirboatGun::Activate()
  2392. {
  2393. BaseClass::Activate();
  2394. if ( m_iszAirboatGunModel != NULL_STRING )
  2395. {
  2396. m_hAirboatGunModel = dynamic_cast<CBaseAnimating*>( gEntList.FindEntityByName( NULL, m_iszAirboatGunModel ) );
  2397. if ( m_hAirboatGunModel )
  2398. {
  2399. m_nGunBarrelAttachment = m_hAirboatGunModel->LookupAttachment( "muzzle" );
  2400. }
  2401. }
  2402. }
  2403. //-----------------------------------------------------------------------------
  2404. // Create/destroy looping sounds
  2405. //-----------------------------------------------------------------------------
  2406. void CFuncTankAirboatGun::CreateSounds()
  2407. {
  2408. CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
  2409. CPASAttenuationFilter filter( this );
  2410. if (!m_pGunFiringSound)
  2411. {
  2412. m_pGunFiringSound = controller.SoundCreate( filter, entindex(), "Airboat.FireGunLoop" );
  2413. controller.Play( m_pGunFiringSound, 0, 100 );
  2414. }
  2415. }
  2416. void CFuncTankAirboatGun::DestroySounds()
  2417. {
  2418. CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
  2419. controller.SoundDestroy( m_pGunFiringSound );
  2420. m_pGunFiringSound = NULL;
  2421. }
  2422. //-----------------------------------------------------------------------------
  2423. // Stop Firing
  2424. //-----------------------------------------------------------------------------
  2425. void CFuncTankAirboatGun::StartFiring()
  2426. {
  2427. if ( !m_bIsFiring )
  2428. {
  2429. CSoundEnvelopeController *pController = &CSoundEnvelopeController::GetController();
  2430. float flVolume = pController->SoundGetVolume( m_pGunFiringSound );
  2431. pController->SoundChangeVolume( m_pGunFiringSound, 1.0f, 0.1f * (1.0f - flVolume) );
  2432. m_bIsFiring = true;
  2433. }
  2434. }
  2435. void CFuncTankAirboatGun::StopFiring()
  2436. {
  2437. if ( m_bIsFiring )
  2438. {
  2439. CSoundEnvelopeController *pController = &CSoundEnvelopeController::GetController();
  2440. float flVolume = pController->SoundGetVolume( m_pGunFiringSound );
  2441. pController->SoundChangeVolume( m_pGunFiringSound, 0.0f, 0.1f * flVolume );
  2442. EmitSound( "Airboat.FireGunRevDown" );
  2443. m_bIsFiring = false;
  2444. }
  2445. }
  2446. //-----------------------------------------------------------------------------
  2447. // Maintains airboat gun sounds
  2448. //-----------------------------------------------------------------------------
  2449. void CFuncTankAirboatGun::ControllerPostFrame( void )
  2450. {
  2451. if ( IsPlayerManned() )
  2452. {
  2453. CBasePlayer *pPlayer = static_cast<CBasePlayer*>( GetController() );
  2454. if ( pPlayer->m_nButtons & IN_ATTACK )
  2455. {
  2456. StartFiring();
  2457. }
  2458. else
  2459. {
  2460. StopFiring();
  2461. }
  2462. }
  2463. BaseClass::ControllerPostFrame();
  2464. }
  2465. //-----------------------------------------------------------------------------
  2466. // Stop controlled
  2467. //-----------------------------------------------------------------------------
  2468. void CFuncTankAirboatGun::OnStopControlled()
  2469. {
  2470. StopFiring();
  2471. BaseClass::OnStopControlled();
  2472. }
  2473. //-----------------------------------------------------------------------------
  2474. // Barrel position
  2475. //-----------------------------------------------------------------------------
  2476. Vector CFuncTankAirboatGun::WorldBarrelPosition( void )
  2477. {
  2478. if ( !m_hAirboatGunModel || (m_nGunBarrelAttachment == 0) )
  2479. {
  2480. return BaseClass::WorldBarrelPosition();
  2481. }
  2482. Vector vecOrigin;
  2483. m_hAirboatGunModel->GetAttachment( m_nGunBarrelAttachment, vecOrigin );
  2484. return vecOrigin;
  2485. }
  2486. //-----------------------------------------------------------------------------
  2487. // Purpose:
  2488. //-----------------------------------------------------------------------------
  2489. const char *CFuncTankAirboatGun::GetTracerType( void )
  2490. {
  2491. if ( gpGlobals->curtime >= m_flNextHeavyShotTime )
  2492. return "AirboatGunHeavyTracer";
  2493. return "AirboatGunTracer";
  2494. }
  2495. //-----------------------------------------------------------------------------
  2496. // Purpose:
  2497. //-----------------------------------------------------------------------------
  2498. void CFuncTankAirboatGun::DoMuzzleFlash( void )
  2499. {
  2500. if ( m_hAirboatGunModel && (m_nGunBarrelAttachment != 0) )
  2501. {
  2502. CEffectData data;
  2503. data.m_nEntIndex = m_hAirboatGunModel->entindex();
  2504. data.m_nAttachmentIndex = m_nGunBarrelAttachment;
  2505. data.m_flScale = 1.0f;
  2506. DispatchEffect( "AirboatMuzzleFlash", data );
  2507. }
  2508. }
  2509. //-----------------------------------------------------------------------------
  2510. // Allows the shooter to change the impact effect of his bullets
  2511. //-----------------------------------------------------------------------------
  2512. void CFuncTankAirboatGun::DoImpactEffect( trace_t &tr, int nDamageType )
  2513. {
  2514. // The airboat spits out so much crap that we need to do cheaper versions
  2515. // of the impact effects. Also, we need to do less of them.
  2516. if ( m_flLastImpactEffectTime == gpGlobals->curtime )
  2517. return;
  2518. m_flLastImpactEffectTime = gpGlobals->curtime;
  2519. UTIL_ImpactTrace( &tr, nDamageType, "AirboatGunImpact" );
  2520. }
  2521. //-----------------------------------------------------------------------------
  2522. // Fires bullets
  2523. //-----------------------------------------------------------------------------
  2524. #define AIRBOAT_GUN_HEAVY_SHOT_INTERVAL 0.2f
  2525. void CFuncTankAirboatGun::Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker, bool bIgnoreSpread )
  2526. {
  2527. CAmmoDef *pAmmoDef = GetAmmoDef();
  2528. int ammoType = pAmmoDef->Index( "AirboatGun" );
  2529. FireBulletsInfo_t info;
  2530. info.m_vecSrc = barrelEnd;
  2531. info.m_vecDirShooting = forward;
  2532. info.m_flDistance = 4096;
  2533. info.m_iAmmoType = ammoType;
  2534. if ( gpGlobals->curtime >= m_flNextHeavyShotTime )
  2535. {
  2536. info.m_iShots = 1;
  2537. info.m_vecSpread = VECTOR_CONE_PRECALCULATED;
  2538. info.m_flDamageForceScale = 1000.0f;
  2539. }
  2540. else
  2541. {
  2542. info.m_iShots = 2;
  2543. info.m_vecSpread = VECTOR_CONE_5DEGREES;
  2544. }
  2545. FireBullets( info );
  2546. DoMuzzleFlash();
  2547. // NOTE: This must occur after FireBullets
  2548. if ( gpGlobals->curtime >= m_flNextHeavyShotTime )
  2549. {
  2550. m_flNextHeavyShotTime = gpGlobals->curtime + AIRBOAT_GUN_HEAVY_SHOT_INTERVAL;
  2551. }
  2552. }
  2553. //-----------------------------------------------------------------------------
  2554. // APC Rocket
  2555. //-----------------------------------------------------------------------------
  2556. #define DEATH_VOLLEY_MISSILE_COUNT 10
  2557. #define DEATH_VOLLEY_MIN_FIRE_RATE 3
  2558. #define DEATH_VOLLEY_MAX_FIRE_RATE 6
  2559. class CFuncTankAPCRocket : public CFuncTank
  2560. {
  2561. public:
  2562. DECLARE_CLASS( CFuncTankAPCRocket, CFuncTank );
  2563. void Precache( void );
  2564. virtual void Spawn();
  2565. virtual void UpdateOnRemove();
  2566. void Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker, bool bIgnoreSpread );
  2567. virtual void Think();
  2568. virtual float GetShotSpeed() { return m_flRocketSpeed; }
  2569. protected:
  2570. void InputDeathVolley( inputdata_t &inputdata );
  2571. void FireDying( const Vector &barrelEnd );
  2572. EHANDLE m_hLaserDot;
  2573. float m_flRocketSpeed;
  2574. int m_nSide;
  2575. int m_nBurstCount;
  2576. bool m_bDying;
  2577. DECLARE_DATADESC();
  2578. };
  2579. BEGIN_DATADESC( CFuncTankAPCRocket )
  2580. DEFINE_KEYFIELD( m_flRocketSpeed, FIELD_FLOAT, "rocketspeed" ),
  2581. DEFINE_FIELD( m_hLaserDot, FIELD_EHANDLE ),
  2582. DEFINE_FIELD( m_nSide, FIELD_INTEGER ),
  2583. DEFINE_KEYFIELD( m_nBurstCount, FIELD_INTEGER, "burstcount" ),
  2584. DEFINE_FIELD( m_bDying, FIELD_BOOLEAN ),
  2585. DEFINE_INPUTFUNC( FIELD_VOID, "DeathVolley", InputDeathVolley ),
  2586. END_DATADESC()
  2587. LINK_ENTITY_TO_CLASS( func_tankapcrocket, CFuncTankAPCRocket );
  2588. void CFuncTankAPCRocket::Precache( void )
  2589. {
  2590. UTIL_PrecacheOther( "apc_missile" );
  2591. PrecacheScriptSound( "PropAPC.FireCannon" );
  2592. CFuncTank::Precache();
  2593. }
  2594. void CFuncTankAPCRocket::Spawn( void )
  2595. {
  2596. BaseClass::Spawn();
  2597. AddEffects( EF_NODRAW );
  2598. m_nSide = 0;
  2599. m_bDying = false;
  2600. m_hLaserDot = CreateLaserDot( GetAbsOrigin(), this, false );
  2601. m_nBulletCount = m_nBurstCount;
  2602. SetSolid( SOLID_NONE );
  2603. SetLocalVelocity( vec3_origin );
  2604. }
  2605. void CFuncTankAPCRocket::UpdateOnRemove( void )
  2606. {
  2607. if ( m_hLaserDot )
  2608. {
  2609. UTIL_Remove( m_hLaserDot );
  2610. m_hLaserDot = NULL;
  2611. }
  2612. BaseClass::UpdateOnRemove();
  2613. }
  2614. void CFuncTankAPCRocket::FireDying( const Vector &barrelEnd )
  2615. {
  2616. Vector vecDir;
  2617. vecDir.Random( -1.0f, 1.0f );
  2618. if ( vecDir.z < 0.0f )
  2619. {
  2620. vecDir.z *= -1.0f;
  2621. }
  2622. VectorNormalize( vecDir );
  2623. Vector vecVelocity;
  2624. VectorMultiply( vecDir, m_flRocketSpeed * random->RandomFloat( 0.75f, 1.25f ), vecVelocity );
  2625. QAngle angles;
  2626. VectorAngles( vecDir, angles );
  2627. CAPCMissile *pRocket = (CAPCMissile *) CAPCMissile::Create( barrelEnd, angles, vecVelocity, this );
  2628. float flDeathTime = random->RandomFloat( 0.3f, 0.5f );
  2629. if ( random->RandomFloat( 0.0f, 1.0f ) < 0.3f )
  2630. {
  2631. pRocket->ExplodeDelay( flDeathTime );
  2632. }
  2633. else
  2634. {
  2635. pRocket->AugerDelay( flDeathTime );
  2636. }
  2637. // Make erratic firing
  2638. m_fireRate = random->RandomFloat( DEATH_VOLLEY_MIN_FIRE_RATE, DEATH_VOLLEY_MAX_FIRE_RATE );
  2639. if ( --m_nBulletCount <= 0 )
  2640. {
  2641. UTIL_Remove( this );
  2642. }
  2643. }
  2644. void CFuncTankAPCRocket::Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker, bool bIgnoreSpread )
  2645. {
  2646. static float s_pSide[] = { 0.966, 0.866, 0.5, -0.5, -0.866, -0.966 };
  2647. Vector vecDir;
  2648. CrossProduct( Vector( 0, 0, 1 ), forward, vecDir );
  2649. vecDir.z = 1.0f;
  2650. vecDir.x *= s_pSide[m_nSide];
  2651. vecDir.y *= s_pSide[m_nSide];
  2652. if ( ++m_nSide >= 6 )
  2653. {
  2654. m_nSide = 0;
  2655. }
  2656. VectorNormalize( vecDir );
  2657. Vector vecVelocity;
  2658. VectorMultiply( vecDir, m_flRocketSpeed, vecVelocity );
  2659. QAngle angles;
  2660. VectorAngles( vecDir, angles );
  2661. CAPCMissile *pRocket = (CAPCMissile *) CAPCMissile::Create( barrelEnd, angles, vecVelocity, this );
  2662. pRocket->IgniteDelay();
  2663. CFuncTank::Fire( bulletCount, barrelEnd, forward, this, bIgnoreSpread );
  2664. if ( --m_nBulletCount <= 0 )
  2665. {
  2666. m_nBulletCount = m_nBurstCount;
  2667. // This will cause it to wait for a little while before shooting
  2668. m_fireLast += random->RandomFloat( 2.0f, 3.0f );
  2669. }
  2670. EmitSound( "PropAPC.FireCannon" );
  2671. }
  2672. void CFuncTankAPCRocket::Think()
  2673. {
  2674. // Inert if we're carried...
  2675. if ( GetMoveParent() && GetMoveParent()->GetMoveParent() )
  2676. {
  2677. SetNextThink( gpGlobals->curtime + 0.5f );
  2678. return;
  2679. }
  2680. BaseClass::Think();
  2681. m_hLaserDot->SetAbsOrigin( m_sightOrigin );
  2682. SetLaserDotTarget( m_hLaserDot, m_hFuncTankTarget );
  2683. EnableLaserDot( m_hLaserDot, m_hFuncTankTarget != NULL );
  2684. if ( m_bDying )
  2685. {
  2686. FireDying( WorldBarrelPosition() );
  2687. return;
  2688. }
  2689. }
  2690. void CFuncTankAPCRocket::InputDeathVolley( inputdata_t &inputdata )
  2691. {
  2692. if ( !m_bDying )
  2693. {
  2694. m_fireRate = random->RandomFloat( DEATH_VOLLEY_MIN_FIRE_RATE, DEATH_VOLLEY_MAX_FIRE_RATE );
  2695. SetNextAttack( gpGlobals->curtime + (1.0f / m_fireRate ) );
  2696. m_nBulletCount = DEATH_VOLLEY_MISSILE_COUNT;
  2697. m_bDying = true;
  2698. }
  2699. }
  2700. //-----------------------------------------------------------------------------
  2701. // Mortar shell
  2702. //-----------------------------------------------------------------------------
  2703. class CMortarShell : public CBaseEntity
  2704. {
  2705. public:
  2706. DECLARE_CLASS( CMortarShell, CBaseEntity );
  2707. static CMortarShell *Create( const Vector &vecStart, const Vector &vecTarget, const Vector &vecShotDir, float flImpactDelay, float flWarnDelay, string_t warnSound );
  2708. void Spawn( void );
  2709. void Precache( void );
  2710. void Impact( void );
  2711. void Warn( void );
  2712. void FlyThink( void );
  2713. void FadeThink( void );
  2714. int UpdateTransmitState( void );
  2715. private:
  2716. void FixUpImpactPoint( const Vector &initialPos, const Vector &initialNormal, Vector *endPos, Vector *endNormal );
  2717. float m_flFadeTime;
  2718. float m_flImpactTime;
  2719. float m_flWarnTime;
  2720. float m_flNPCWarnTime;
  2721. string_t m_warnSound;
  2722. int m_iSpriteTexture;
  2723. bool m_bHasWarned;
  2724. Vector m_vecFiredFrom;
  2725. Vector m_vecFlyDir;
  2726. float m_flSpawnedTime;
  2727. CHandle<CBeam> m_pBeamEffect[4];
  2728. CNetworkVar( float, m_flLifespan );
  2729. CNetworkVar( float, m_flRadius );
  2730. CNetworkVar( Vector, m_vecSurfaceNormal );
  2731. DECLARE_DATADESC();
  2732. DECLARE_SERVERCLASS();
  2733. };
  2734. LINK_ENTITY_TO_CLASS( mortarshell, CMortarShell );
  2735. BEGIN_DATADESC( CMortarShell )
  2736. DEFINE_FIELD( m_flImpactTime, FIELD_TIME ),
  2737. DEFINE_FIELD( m_flFadeTime, FIELD_TIME ),
  2738. DEFINE_FIELD( m_flWarnTime, FIELD_TIME ),
  2739. DEFINE_FIELD( m_flNPCWarnTime, FIELD_TIME ),
  2740. DEFINE_FIELD( m_warnSound, FIELD_STRING ),
  2741. DEFINE_FIELD( m_iSpriteTexture, FIELD_INTEGER ),
  2742. DEFINE_FIELD( m_bHasWarned, FIELD_BOOLEAN ),
  2743. DEFINE_FIELD( m_flLifespan, FIELD_FLOAT ),
  2744. DEFINE_FIELD( m_vecFiredFrom, FIELD_POSITION_VECTOR ),
  2745. DEFINE_FIELD( m_vecFlyDir, FIELD_VECTOR ),
  2746. DEFINE_FIELD( m_flSpawnedTime, FIELD_TIME ),
  2747. DEFINE_AUTO_ARRAY( m_pBeamEffect, FIELD_EHANDLE),
  2748. DEFINE_FIELD( m_flRadius, FIELD_FLOAT ),
  2749. DEFINE_FIELD( m_vecSurfaceNormal, FIELD_VECTOR ),
  2750. DEFINE_FUNCTION( FlyThink ),
  2751. DEFINE_FUNCTION( FadeThink ),
  2752. END_DATADESC()
  2753. IMPLEMENT_SERVERCLASS_ST( CMortarShell, DT_MortarShell )
  2754. SendPropFloat( SENDINFO( m_flLifespan ), -1, SPROP_NOSCALE ),
  2755. SendPropFloat( SENDINFO( m_flRadius ), -1, SPROP_NOSCALE ),
  2756. SendPropVector( SENDINFO( m_vecSurfaceNormal ), 0, SPROP_NORMAL ),
  2757. END_SEND_TABLE()
  2758. #define MORTAR_TEST_RADIUS 16.0f
  2759. //-----------------------------------------------------------------------------
  2760. // Purpose:
  2761. // Input : &initialPos -
  2762. // *endPos -
  2763. // *endNormal -
  2764. //-----------------------------------------------------------------------------
  2765. void CMortarShell::FixUpImpactPoint( const Vector &initialPos, const Vector &initialNormal, Vector *endPos, Vector *endNormal )
  2766. {
  2767. Vector vecStartOffset;
  2768. vecStartOffset = initialPos + ( initialNormal * 1.0f );
  2769. trace_t tr;
  2770. UTIL_TraceLine( vecStartOffset, vecStartOffset - Vector( 0, 0, 256 ), MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
  2771. if ( tr.fraction < 1.0f )
  2772. {
  2773. if ( endPos )
  2774. {
  2775. *endPos = tr.endpos + ( initialNormal * 16.0f );
  2776. }
  2777. if ( endNormal )
  2778. {
  2779. *endNormal = tr.plane.normal;
  2780. }
  2781. }
  2782. else
  2783. {
  2784. if ( endPos )
  2785. {
  2786. *endPos = initialPos;
  2787. }
  2788. if ( endNormal )
  2789. {
  2790. *endNormal = initialNormal;
  2791. }
  2792. }
  2793. }
  2794. //---------------------------------------------------------
  2795. //---------------------------------------------------------
  2796. #define MORTAR_BLAST_DAMAGE 50
  2797. #define MORTAR_BLAST_HEIGHT 7500
  2798. CMortarShell *CMortarShell::Create( const Vector &vecStart, const Vector &vecTarget, const Vector &vecShotDir, float flImpactDelay, float flWarnDelay, string_t warnSound )
  2799. {
  2800. CMortarShell *pShell = (CMortarShell *)CreateEntityByName("mortarshell" );
  2801. // Place the mortar shell at the target location so that it can make the sound and explode.
  2802. trace_t tr;
  2803. UTIL_TraceLine( vecTarget, vecTarget + ( vecShotDir * 128.0f ), MASK_SOLID_BRUSHONLY, pShell, COLLISION_GROUP_NONE, &tr );
  2804. Vector targetPos, targetNormal;
  2805. pShell->FixUpImpactPoint( tr.endpos, tr.plane.normal, &targetPos, &targetNormal );
  2806. UTIL_SetOrigin( pShell, targetPos );
  2807. Vector vecStartSkew, vecEndSkew;
  2808. vecStartSkew = targetPos - vecStart;
  2809. vecStartSkew[2] = 0.0f;
  2810. float skewLength = VectorNormalize( vecStartSkew );
  2811. vecEndSkew = -vecStartSkew * ( skewLength * 0.25f );
  2812. vecStartSkew *= skewLength * 0.1f;
  2813. // Muzzleflash beam
  2814. pShell->m_pBeamEffect[0] = CBeam::BeamCreate( "sprites/laserbeam.vmt", 1 );
  2815. pShell->m_pBeamEffect[0]->PointsInit( vecStart, vecStart + Vector( vecStartSkew[0], vecStartSkew[1], MORTAR_BLAST_HEIGHT ) );
  2816. pShell->m_pBeamEffect[0]->SetColor( 16, 16, 8 );
  2817. pShell->m_pBeamEffect[0]->SetBrightness( 0 );
  2818. pShell->m_pBeamEffect[0]->SetNoise( 0 );
  2819. pShell->m_pBeamEffect[0]->SetBeamFlag( FBEAM_SHADEOUT );
  2820. pShell->m_pBeamEffect[0]->SetWidth( 64.0f );
  2821. pShell->m_pBeamEffect[0]->SetEndWidth( 64.0f );
  2822. pShell->m_pBeamEffect[1] = CBeam::BeamCreate( "sprites/laserbeam.vmt", 1 );
  2823. pShell->m_pBeamEffect[1]->PointsInit( vecStart, vecStart + Vector( vecStartSkew[0], vecStartSkew[1], MORTAR_BLAST_HEIGHT ) );
  2824. pShell->m_pBeamEffect[1]->SetColor( 255, 255, 255 );
  2825. pShell->m_pBeamEffect[1]->SetBrightness( 0 );
  2826. pShell->m_pBeamEffect[1]->SetNoise( 0 );
  2827. pShell->m_pBeamEffect[1]->SetBeamFlag( FBEAM_SHADEOUT );
  2828. pShell->m_pBeamEffect[1]->SetWidth( 8.0f );
  2829. pShell->m_pBeamEffect[1]->SetEndWidth( 8.0f );
  2830. trace_t skyTrace;
  2831. UTIL_TraceLine( targetPos, targetPos + Vector( vecEndSkew[0], vecEndSkew[1], MORTAR_BLAST_HEIGHT ), MASK_SOLID_BRUSHONLY, pShell, COLLISION_GROUP_NONE, &skyTrace );
  2832. // We must touch the sky to make this beam
  2833. if ( skyTrace.fraction <= 1.0f && skyTrace.surface.flags & SURF_SKY )
  2834. {
  2835. // Impact point beam
  2836. pShell->m_pBeamEffect[2] = CBeam::BeamCreate( "sprites/laserbeam.vmt", 1 );
  2837. pShell->m_pBeamEffect[2]->PointsInit( targetPos, targetPos + Vector( vecEndSkew[0], vecEndSkew[1], MORTAR_BLAST_HEIGHT ) );
  2838. pShell->m_pBeamEffect[2]->SetColor( 16, 16, 8 );
  2839. pShell->m_pBeamEffect[2]->SetBrightness( 0 );
  2840. pShell->m_pBeamEffect[2]->SetNoise( 0 );
  2841. pShell->m_pBeamEffect[2]->SetBeamFlag( FBEAM_SHADEOUT );
  2842. pShell->m_pBeamEffect[2]->SetWidth( 32.0f );
  2843. pShell->m_pBeamEffect[2]->SetEndWidth( 32.0f );
  2844. pShell->m_pBeamEffect[3] = CBeam::BeamCreate( "sprites/laserbeam.vmt", 1 );
  2845. pShell->m_pBeamEffect[3]->PointsInit( targetPos, targetPos + Vector( vecEndSkew[0], vecEndSkew[1], MORTAR_BLAST_HEIGHT ) );
  2846. pShell->m_pBeamEffect[3]->SetColor( 255, 255, 255 );
  2847. pShell->m_pBeamEffect[3]->SetBrightness( 0 );
  2848. pShell->m_pBeamEffect[3]->SetNoise( 0 );
  2849. pShell->m_pBeamEffect[3]->SetBeamFlag( FBEAM_SHADEOUT );
  2850. pShell->m_pBeamEffect[3]->SetWidth( 4.0f );
  2851. pShell->m_pBeamEffect[3]->SetEndWidth( 4.0f );
  2852. }
  2853. else
  2854. {
  2855. // Mark these as not being used
  2856. pShell->m_pBeamEffect[2] = NULL;
  2857. pShell->m_pBeamEffect[3] = NULL;
  2858. }
  2859. pShell->m_vecFiredFrom = vecStart;
  2860. pShell->m_flLifespan = flImpactDelay;
  2861. pShell->m_flImpactTime = gpGlobals->curtime + flImpactDelay;
  2862. pShell->m_flWarnTime = pShell->m_flImpactTime - flWarnDelay;
  2863. pShell->m_flNPCWarnTime = pShell->m_flWarnTime - 0.5;
  2864. pShell->m_warnSound = warnSound;
  2865. pShell->Spawn();
  2866. // Save off the impact normal
  2867. pShell->m_vecSurfaceNormal = targetNormal;
  2868. pShell->m_flRadius = MORTAR_BLAST_RADIUS;
  2869. return pShell;
  2870. }
  2871. //---------------------------------------------------------
  2872. //---------------------------------------------------------
  2873. void CMortarShell::Precache()
  2874. {
  2875. m_iSpriteTexture = PrecacheModel( "sprites/physbeam.vmt" );
  2876. PrecacheScriptSound( "Weapon_Mortar.Impact" );
  2877. PrecacheMaterial( "effects/ar2ground2" );
  2878. if ( NULL_STRING != m_warnSound )
  2879. {
  2880. PrecacheScriptSound( STRING( m_warnSound ) );
  2881. }
  2882. }
  2883. //------------------------------------------------------------------------------
  2884. // Purpose : Send even though we don't have a model
  2885. //------------------------------------------------------------------------------
  2886. int CMortarShell::UpdateTransmitState( void )
  2887. {
  2888. return SetTransmitState( FL_EDICT_PVSCHECK );
  2889. }
  2890. //---------------------------------------------------------
  2891. //---------------------------------------------------------
  2892. void CMortarShell::Spawn()
  2893. {
  2894. Precache();
  2895. AddEffects( EF_NODRAW );
  2896. AddSolidFlags( FSOLID_NOT_SOLID );
  2897. Vector mins( -MORTAR_BLAST_RADIUS, -MORTAR_BLAST_RADIUS, -MORTAR_BLAST_RADIUS );
  2898. Vector maxs( MORTAR_BLAST_RADIUS, MORTAR_BLAST_RADIUS, MORTAR_BLAST_RADIUS );
  2899. UTIL_SetSize( this, mins, maxs );
  2900. m_vecFlyDir = GetAbsOrigin() - m_vecFiredFrom;
  2901. VectorNormalize( m_vecFlyDir );
  2902. m_flSpawnedTime = gpGlobals->curtime;
  2903. SetThink( &CMortarShell::FlyThink );
  2904. SetNextThink( gpGlobals->curtime );
  2905. // No model but we still need to force this!
  2906. AddEFlags( EFL_FORCE_CHECK_TRANSMIT );
  2907. }
  2908. //-----------------------------------------------------------------------------
  2909. // Purpose:
  2910. // Input : type -
  2911. // steps -
  2912. // bias -
  2913. //-----------------------------------------------------------------------------
  2914. ConVar curve_bias( "curve_bias", "0.5" );
  2915. enum
  2916. {
  2917. CURVE_BIAS,
  2918. CURVE_GAIN,
  2919. CURVE_SMOOTH,
  2920. CURVE_SMOOTH_TWEAK,
  2921. };
  2922. void UTIL_VisualizeCurve( int type, int steps, float bias )
  2923. {
  2924. CBasePlayer *pPlayer = UTIL_PlayerByIndex( 1 );
  2925. Vector vForward, vRight, vUp;
  2926. pPlayer->EyeVectors( &vForward, &vRight, &vUp );
  2927. Vector renderOrigin = pPlayer->EyePosition() + ( vForward * 512.0f );
  2928. float renderScale = 8.0f;
  2929. float lastPerc, perc;
  2930. Vector renderOffs, lastRenderOffs = vec3_origin;
  2931. for ( int i = 0; i < steps; i++ )
  2932. {
  2933. perc = RemapValClamped( i, 0, steps-1, 0.0f, 1.0f );
  2934. switch( type )
  2935. {
  2936. case CURVE_BIAS:
  2937. perc = Bias( perc, bias );
  2938. break;
  2939. case CURVE_GAIN:
  2940. perc = Gain( perc, bias );
  2941. break;
  2942. case CURVE_SMOOTH:
  2943. perc = SmoothCurve( perc );
  2944. break;
  2945. case CURVE_SMOOTH_TWEAK:
  2946. perc = SmoothCurve_Tweak( perc, bias, 0.9f );
  2947. break;
  2948. }
  2949. renderOffs = ( vRight * (-steps*0.5f) * renderScale ) + ( vUp * (renderScale*-(steps*0.5f)) )+ ( vRight * i * renderScale ) + ( vUp * perc * (renderScale*steps) );
  2950. NDebugOverlay::Cross3D( renderOrigin + renderOffs, -Vector(2,2,2), Vector(2,2,2), 255, 0, 0, true, 0.05f );
  2951. if ( i > 0 )
  2952. {
  2953. NDebugOverlay::Line( renderOrigin + renderOffs, renderOrigin + lastRenderOffs, 255, 0, 0, true, 0.05f );
  2954. }
  2955. lastRenderOffs = renderOffs;
  2956. lastPerc = perc;
  2957. }
  2958. }
  2959. //---------------------------------------------------------
  2960. //---------------------------------------------------------
  2961. void CMortarShell::FlyThink()
  2962. {
  2963. SetNextThink( gpGlobals->curtime + 0.05 );
  2964. if ( gpGlobals->curtime > m_flNPCWarnTime )
  2965. {
  2966. // Warn the AI. Make this radius a little larger than the explosion will be, and make the sound last a little longer.
  2967. CSoundEnt::InsertSound ( SOUND_DANGER | SOUND_CONTEXT_MORTAR, GetAbsOrigin(), MORTAR_BLAST_RADIUS * 1.25, (m_flImpactTime - m_flNPCWarnTime) + 0.15 );
  2968. m_flNPCWarnTime = FLT_MAX;
  2969. }
  2970. //UTIL_VisualizeCurve( CURVE_GAIN, 64, curve_bias.GetFloat() );
  2971. float lifePerc = 1.0f - ( ( m_flImpactTime - gpGlobals->curtime ) / ( m_flImpactTime - m_flSpawnedTime ) );
  2972. lifePerc = clamp( lifePerc, 0.0f, 1.0f );
  2973. float curve1 = Bias( lifePerc, 0.75f );
  2974. // Beam updates START
  2975. m_pBeamEffect[0]->SetBrightness( 255 * curve1 );
  2976. m_pBeamEffect[0]->SetWidth( 64.0f * curve1 );
  2977. m_pBeamEffect[0]->SetEndWidth( 64.0f * curve1 );
  2978. m_pBeamEffect[1]->SetBrightness( 255 * curve1 );
  2979. m_pBeamEffect[1]->SetWidth( 8.0f * curve1 );
  2980. m_pBeamEffect[1]->SetEndWidth( 8.0f * curve1 );
  2981. float curve2 = Bias( lifePerc, 0.1f );
  2982. if ( m_pBeamEffect[2] )
  2983. {
  2984. m_pBeamEffect[2]->SetBrightness( 255 * curve2 );
  2985. m_pBeamEffect[2]->SetWidth( 32.0f * curve2 );
  2986. m_pBeamEffect[2]->SetEndWidth( 32.0f * curve2 );
  2987. }
  2988. if ( m_pBeamEffect[3] )
  2989. {
  2990. m_pBeamEffect[3]->SetBrightness( 255 * curve2 );
  2991. m_pBeamEffect[3]->SetWidth( 8.0f * curve2 );
  2992. m_pBeamEffect[3]->SetEndWidth( 8.0f * curve2 );
  2993. }
  2994. // Beam updates END
  2995. if( !m_bHasWarned && gpGlobals->curtime > m_flWarnTime )
  2996. {
  2997. Warn();
  2998. }
  2999. if( gpGlobals->curtime > m_flImpactTime )
  3000. {
  3001. Impact();
  3002. }
  3003. }
  3004. //---------------------------------------------------------
  3005. //---------------------------------------------------------
  3006. void CMortarShell::Warn( void )
  3007. {
  3008. if ( m_warnSound != NULL_STRING )
  3009. {
  3010. CPASAttenuationFilter filter( this );
  3011. EmitSound_t ep;
  3012. ep.m_nChannel = CHAN_WEAPON;
  3013. ep.m_pSoundName = (char*)STRING(m_warnSound);
  3014. ep.m_flVolume = 1.0f;
  3015. ep.m_SoundLevel = SNDLVL_NONE;
  3016. EmitSound( filter, entindex(), ep );
  3017. }
  3018. m_bHasWarned = true;
  3019. }
  3020. //---------------------------------------------------------
  3021. //---------------------------------------------------------
  3022. void CMortarShell::Impact( void )
  3023. {
  3024. // Fire the bullets
  3025. Vector vecSrc, vecShootDir;
  3026. float flRadius = MORTAR_BLAST_RADIUS;
  3027. trace_t tr;
  3028. UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() - Vector( 0, 0, 128 ), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr );
  3029. UTIL_DecalTrace( &tr, "Scorch" );
  3030. // Send the effect over
  3031. CEffectData data;
  3032. // Do an extra effect if we struck the world
  3033. if ( tr.m_pEnt && tr.m_pEnt->IsWorld() )
  3034. {
  3035. data.m_flRadius = flRadius * 0.5f;
  3036. data.m_vNormal = tr.plane.normal;
  3037. data.m_vOrigin = tr.endpos;
  3038. DispatchEffect( "AR2Explosion", data );
  3039. }
  3040. //Shockring
  3041. CBroadcastRecipientFilter filter2;
  3042. te->BeamRingPoint( filter2, 0, GetAbsOrigin(), //origin
  3043. 8.0f, //start radius
  3044. flRadius * 2, //end radius
  3045. m_iSpriteTexture, //texture
  3046. 0, //halo index
  3047. 0, //start frame
  3048. 2, //framerate
  3049. 0.2f, //life
  3050. 32, //width
  3051. 0, //spread
  3052. 0, //amplitude
  3053. 255, //r
  3054. 255, //g
  3055. 225, //b
  3056. 32, //a
  3057. 0, //speed
  3058. FBEAM_FADEOUT
  3059. );
  3060. //Shockring
  3061. te->BeamRingPoint( filter2, 0, GetAbsOrigin(), //origin
  3062. 8.0f, //start radius
  3063. flRadius, //end radius
  3064. m_iSpriteTexture, //texture
  3065. 0, //halo index
  3066. 0, //start frame
  3067. 2, //framerate
  3068. 0.2f, //life
  3069. 64, //width
  3070. 0, //spread
  3071. 0, //amplitude
  3072. 255, //r
  3073. 255, //g
  3074. 225, //b
  3075. 64, //a
  3076. 0, //speed
  3077. FBEAM_FADEOUT
  3078. );
  3079. RadiusDamage( CTakeDamageInfo( this, GetOwnerEntity(), MORTAR_BLAST_DAMAGE, (DMG_BLAST|DMG_DISSOLVE) ), GetAbsOrigin(), MORTAR_BLAST_RADIUS, CLASS_NONE, NULL );
  3080. EmitSound( "Weapon_Mortar.Impact" );
  3081. UTIL_ScreenShake( GetAbsOrigin(), 10, 60, 1.0, 550, SHAKE_START, false );
  3082. //Fade the beams over time!
  3083. m_flFadeTime = gpGlobals->curtime;
  3084. SetThink( &CMortarShell::FadeThink );
  3085. SetNextThink( gpGlobals->curtime + 0.05f );
  3086. }
  3087. #define MORTAR_FADE_LENGTH 1.0f
  3088. //-----------------------------------------------------------------------------
  3089. // Purpose:
  3090. //-----------------------------------------------------------------------------
  3091. void CMortarShell::FadeThink( void )
  3092. {
  3093. SetNextThink( gpGlobals->curtime + 0.05f );
  3094. float lifePerc = 1.0f - ( ( gpGlobals->curtime - m_flFadeTime ) / MORTAR_FADE_LENGTH );
  3095. lifePerc = clamp( lifePerc, 0.0f, 1.0f );
  3096. float curve1 = Bias( lifePerc, 0.1f );
  3097. // Beam updates START
  3098. m_pBeamEffect[0]->SetBrightness( 255 * curve1 );
  3099. m_pBeamEffect[0]->SetWidth( 64.0f * curve1 );
  3100. m_pBeamEffect[0]->SetEndWidth( 64.0f * curve1 );
  3101. m_pBeamEffect[1]->SetBrightness( 255 * curve1 );
  3102. m_pBeamEffect[1]->SetWidth( 8.0f * curve1 );
  3103. m_pBeamEffect[1]->SetEndWidth( 8.0f * curve1 );
  3104. float curve2 = Bias( lifePerc, 0.25f );
  3105. if ( m_pBeamEffect[2] )
  3106. {
  3107. m_pBeamEffect[2]->SetBrightness( 255 * curve2 );
  3108. m_pBeamEffect[2]->SetWidth( 32.0f * curve2 );
  3109. m_pBeamEffect[2]->SetEndWidth( 32.0f * curve2 );
  3110. }
  3111. if ( m_pBeamEffect[3] )
  3112. {
  3113. m_pBeamEffect[3]->SetBrightness( 255 * curve2 );
  3114. m_pBeamEffect[3]->SetWidth( 8.0f * curve2 );
  3115. m_pBeamEffect[3]->SetEndWidth( 8.0f * curve2 );
  3116. }
  3117. // Beam updates END
  3118. if ( gpGlobals->curtime > ( m_flFadeTime + MORTAR_FADE_LENGTH ) )
  3119. {
  3120. UTIL_Remove( m_pBeamEffect[0] );
  3121. UTIL_Remove( m_pBeamEffect[1] );
  3122. UTIL_Remove( m_pBeamEffect[2] );
  3123. UTIL_Remove( m_pBeamEffect[3] );
  3124. SetThink(NULL);
  3125. UTIL_Remove( this );
  3126. }
  3127. }
  3128. //=========================================================
  3129. //=========================================================
  3130. class CFuncTankMortar : public CFuncTank
  3131. {
  3132. public:
  3133. DECLARE_CLASS( CFuncTankMortar, CFuncTank );
  3134. CFuncTankMortar() { m_fLastShotMissed = false; }
  3135. void Precache( void );
  3136. void FiringSequence( const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker );
  3137. void Fire( int bulletCount, const Vector &barrelEnd, const Vector &vecForward, CBaseEntity *pAttacker, bool bIgnoreSpread );
  3138. void ShootGun(void);
  3139. void Spawn();
  3140. void SetNextAttack( float flWait );
  3141. // Input handlers.
  3142. void InputShootGun( inputdata_t &inputdata );
  3143. void InputFireAtWill( inputdata_t &inputdata );
  3144. DECLARE_DATADESC();
  3145. int m_Magnitude;
  3146. float m_fireDelay;
  3147. string_t m_fireStartSound;
  3148. //string_t m_fireEndSound;
  3149. string_t m_incomingSound;
  3150. float m_flWarningTime;
  3151. float m_flFireVariance;
  3152. bool m_fLastShotMissed;
  3153. // store future firing event
  3154. CBaseEntity *m_pAttacker;
  3155. };
  3156. LINK_ENTITY_TO_CLASS( func_tankmortar, CFuncTankMortar );
  3157. BEGIN_DATADESC( CFuncTankMortar )
  3158. DEFINE_KEYFIELD( m_Magnitude, FIELD_INTEGER, "iMagnitude" ),
  3159. DEFINE_KEYFIELD( m_fireDelay, FIELD_FLOAT, "firedelay" ),
  3160. DEFINE_KEYFIELD( m_fireStartSound, FIELD_STRING, "firestartsound" ),
  3161. //DEFINE_KEYFIELD( m_fireEndSound, FIELD_STRING, "fireendsound" ),
  3162. DEFINE_KEYFIELD( m_incomingSound, FIELD_STRING, "incomingsound" ),
  3163. DEFINE_KEYFIELD( m_flWarningTime, FIELD_TIME, "warningtime" ),
  3164. DEFINE_KEYFIELD( m_flFireVariance, FIELD_TIME, "firevariance" ),
  3165. DEFINE_FIELD( m_fLastShotMissed, FIELD_BOOLEAN ),
  3166. DEFINE_FIELD( m_pAttacker, FIELD_CLASSPTR ),
  3167. // Inputs
  3168. DEFINE_INPUTFUNC( FIELD_VOID, "ShootGun", InputShootGun ),
  3169. DEFINE_INPUTFUNC( FIELD_VOID, "FireAtWill", InputFireAtWill ),
  3170. END_DATADESC()
  3171. void CFuncTankMortar::Spawn()
  3172. {
  3173. BaseClass::Spawn();
  3174. m_takedamage = DAMAGE_NO;
  3175. }
  3176. void CFuncTankMortar::Precache( void )
  3177. {
  3178. if ( m_fireStartSound != NULL_STRING )
  3179. PrecacheScriptSound( STRING(m_fireStartSound) );
  3180. //if ( m_fireEndSound != NULL_STRING )
  3181. // PrecacheScriptSound( STRING(m_fireEndSound) );
  3182. if ( m_incomingSound != NULL_STRING )
  3183. PrecacheScriptSound( STRING(m_incomingSound) );
  3184. BaseClass::Precache();
  3185. }
  3186. //-----------------------------------------------------------------------------
  3187. //-----------------------------------------------------------------------------
  3188. void CFuncTankMortar::SetNextAttack( float flWait )
  3189. {
  3190. if ( m_flFireVariance > 0.09 )
  3191. flWait += random->RandomFloat( -m_flFireVariance, m_flFireVariance );
  3192. BaseClass::SetNextAttack( flWait );
  3193. }
  3194. //-----------------------------------------------------------------------------
  3195. // Purpose: Input handler to make the tank shoot.
  3196. //-----------------------------------------------------------------------------
  3197. void CFuncTankMortar::InputShootGun( inputdata_t &inputdata )
  3198. {
  3199. ShootGun();
  3200. }
  3201. //-----------------------------------------------------------------------------
  3202. // This mortar can fire the next round as soon as it is ready. This is not a
  3203. // 'sticky' state, it just allows us to get the next shot off as soon as the
  3204. // tank is on target. great for scripted applications where you need a shot as
  3205. // soon as you can get it.
  3206. //-----------------------------------------------------------------------------
  3207. void CFuncTankMortar::InputFireAtWill( inputdata_t &inputdata )
  3208. {
  3209. SetNextAttack( gpGlobals->curtime );
  3210. }
  3211. //-----------------------------------------------------------------------------
  3212. // Purpose:
  3213. //-----------------------------------------------------------------------------
  3214. void CFuncTankMortar::ShootGun( void )
  3215. {
  3216. Vector forward;
  3217. AngleVectors( GetLocalAngles(), &forward );
  3218. UpdateMatrix();
  3219. forward = m_parentMatrix.ApplyRotation( forward );
  3220. Fire( 1, WorldBarrelPosition(), forward, m_pAttacker, false );
  3221. }
  3222. void CFuncTankMortar::FiringSequence( const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker )
  3223. {
  3224. if ( gpGlobals->curtime > GetNextAttack() )
  3225. {
  3226. ShootGun();
  3227. m_fireLast = gpGlobals->curtime;
  3228. SetNextAttack( gpGlobals->curtime + (1.0 / m_fireRate ) );
  3229. }
  3230. else
  3231. {
  3232. m_fireLast = gpGlobals->curtime;
  3233. }
  3234. }
  3235. void CFuncTankMortar::Fire( int bulletCount, const Vector &barrelEnd, const Vector &vecForward, CBaseEntity *pAttacker, bool bIgnoreSpread )
  3236. {
  3237. Vector vecProjectedPosition = vec3_invalid;
  3238. trace_t tr;
  3239. if ( m_hTarget )
  3240. {
  3241. float leadTime = (m_fireDelay * 1.1);
  3242. if ( m_hTarget->IsNPC() ) // Give NPCs a little extra grace
  3243. leadTime = 1.25;
  3244. Vector vLead = m_hTarget->GetSmoothedVelocity() * leadTime;
  3245. Vector vNoise;
  3246. vecProjectedPosition = m_hTarget->WorldSpaceCenter() + vLead;
  3247. vNoise.AsVector2D().Random( -6*12, 6*12);
  3248. vNoise.z = 0;
  3249. if( m_hTarget->Classify() != CLASS_BULLSEYE )
  3250. {
  3251. // Don't apply noise when attacking a bullseye.
  3252. vecProjectedPosition += vNoise;
  3253. }
  3254. }
  3255. else if ( IsPlayerManned() )
  3256. {
  3257. CalcPlayerCrosshairTarget( &vecProjectedPosition );
  3258. }
  3259. else if ( IsNPCManned() )
  3260. {
  3261. CalcNPCEnemyTarget( &vecProjectedPosition );
  3262. //vecProjectedPosition += GetEnemy()->GetSmoothedVelocity() * (m_fireDelay * 1.1);
  3263. }
  3264. else
  3265. return;
  3266. #define TARGET_SEARCH_DEPTH 100
  3267. // find something interesting to shoot at near the projected position.
  3268. Vector delta;
  3269. // Make a really rough approximation of the last half of the mortar trajectory and trace it.
  3270. // Do this so that mortars fired into windows land on rooftops, and that targets projected
  3271. // inside buildings (or out of the world) clip to the world. (usually a building facade)
  3272. // Find halfway between the mortar and the target.
  3273. Vector vecSpot = ( vecProjectedPosition + GetAbsOrigin() ) * 0.5;
  3274. vecSpot.z = GetAbsOrigin().z;
  3275. // Trace up to find the fake 'apex' of the shell. The skybox or 1024 units, whichever comes first.
  3276. UTIL_TraceLine( vecSpot, vecSpot + Vector(0, 0, 1024), MASK_SOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &tr );
  3277. vecSpot = tr.endpos;
  3278. //NDebugOverlay::Line( tr.startpos, tr.endpos, 0,255,0, false, 5 );
  3279. // Now trace from apex to target
  3280. UTIL_TraceLine( vecSpot, vecProjectedPosition, MASK_SOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &tr );
  3281. if( mortar_visualize.GetBool() )
  3282. {
  3283. NDebugOverlay::Line( tr.startpos, tr.endpos, 255,0,0, false, 5 );
  3284. }
  3285. if ( m_fireStartSound != NULL_STRING )
  3286. {
  3287. CPASAttenuationFilter filter( this );
  3288. EmitSound_t ep;
  3289. ep.m_nChannel = CHAN_WEAPON;
  3290. ep.m_pSoundName = (char*)STRING(m_fireStartSound);
  3291. ep.m_flVolume = 1.0f;
  3292. ep.m_SoundLevel = SNDLVL_NONE;
  3293. EmitSound( filter, entindex(), ep );
  3294. }
  3295. Vector vecFinalDir = tr.endpos - tr.startpos;
  3296. VectorNormalize( vecFinalDir );
  3297. CMortarShell::Create( barrelEnd, tr.endpos, vecFinalDir, m_fireDelay, m_flWarningTime, m_incomingSound );
  3298. BaseClass::Fire( bulletCount, barrelEnd, vecForward, this, bIgnoreSpread );
  3299. }
  3300. //-----------------------------------------------------------------------------
  3301. // Purpose: Func tank that fires physics cannisters placed on it
  3302. //-----------------------------------------------------------------------------
  3303. class CFuncTankPhysCannister : public CFuncTank
  3304. {
  3305. public:
  3306. DECLARE_CLASS( CFuncTankPhysCannister, CFuncTank );
  3307. DECLARE_DATADESC();
  3308. void Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker, bool bIgnoreSpread );
  3309. protected:
  3310. string_t m_iszBarrelVolume;
  3311. CHandle<CBaseTrigger> m_hBarrelVolume;
  3312. };
  3313. LINK_ENTITY_TO_CLASS( func_tankphyscannister, CFuncTankPhysCannister );
  3314. BEGIN_DATADESC( CFuncTankPhysCannister )
  3315. DEFINE_KEYFIELD( m_iszBarrelVolume, FIELD_STRING, "barrel_volume" ),
  3316. DEFINE_FIELD( m_hBarrelVolume, FIELD_EHANDLE ),
  3317. END_DATADESC()
  3318. //-----------------------------------------------------------------------------
  3319. // Purpose:
  3320. //-----------------------------------------------------------------------------
  3321. void CFuncTankPhysCannister::Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker, bool bIgnoreSpread )
  3322. {
  3323. // Find our barrel volume
  3324. if ( !m_hBarrelVolume )
  3325. {
  3326. if ( m_iszBarrelVolume != NULL_STRING )
  3327. {
  3328. m_hBarrelVolume = dynamic_cast<CBaseTrigger*>( gEntList.FindEntityByName( NULL, m_iszBarrelVolume ) );
  3329. }
  3330. if ( !m_hBarrelVolume )
  3331. {
  3332. Msg("ERROR: Couldn't find barrel volume for func_tankphyscannister %s.\n", STRING(GetEntityName()) );
  3333. return;
  3334. }
  3335. }
  3336. // Do we have a cannister in our barrel volume?
  3337. CPhysicsCannister *pCannister = (CPhysicsCannister *)m_hBarrelVolume->GetTouchedEntityOfType( "physics_cannister" );
  3338. if ( !pCannister )
  3339. {
  3340. // Play a no-ammo sound
  3341. return;
  3342. }
  3343. // Fire the cannister!
  3344. pCannister->CannisterFire( pAttacker );
  3345. }
  3346. //=========================================================
  3347. //=========================================================
  3348. static const char *s_pUpdateBeamThinkContext = "UpdateBeamThinkContext";
  3349. #define COMBINE_CANNON_BEAM "effects/blueblacklargebeam.vmt"
  3350. //#define COMBINE_CANNON_BEAM "sprites/strider_bluebeam.vmt"
  3351. class CFuncTankCombineCannon : public CFuncTankGun
  3352. {
  3353. DECLARE_CLASS( CFuncTankCombineCannon, CFuncTankGun );
  3354. void Precache();
  3355. void Spawn();
  3356. void CreateBeam();
  3357. void DestroyBeam();
  3358. void FuncTankPostThink();
  3359. void AdjustRateOfFire();
  3360. void UpdateBeamThink( void );
  3361. void Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker, bool bIgnoreSpread );
  3362. void MakeTracer( const Vector &vecTracerSrc, const trace_t &tr, int iTracerType );
  3363. void TankDeactivate();
  3364. void InputSetTargetEntity( inputdata_t &inputdata );
  3365. void InputClearTargetEntity( inputdata_t &inputdata );
  3366. void InputEnableHarrass( inputdata_t &inputdata );
  3367. void InputDisableHarrass( inputdata_t &inputdata );
  3368. COutputEvent m_OnShotAtPlayer;
  3369. CHandle<CBeam> m_hBeam;
  3370. DECLARE_DATADESC();
  3371. private:
  3372. float m_originalFireRate;
  3373. float m_flTimeNextSweep;
  3374. float m_flTimeBeamOn;
  3375. Vector m_vecTrueForward;
  3376. bool m_bShouldHarrass;
  3377. bool m_bLastTargetWasNPC; // Tells whether the last entity we fired a shot at was an NPC (otherwise it was the player)
  3378. };
  3379. BEGIN_DATADESC( CFuncTankCombineCannon )
  3380. DEFINE_FIELD( m_originalFireRate, FIELD_FLOAT ),
  3381. DEFINE_THINKFUNC( UpdateBeamThink ),
  3382. DEFINE_FIELD( m_flTimeNextSweep, FIELD_TIME ),
  3383. DEFINE_FIELD( m_flTimeBeamOn, FIELD_TIME ),
  3384. DEFINE_FIELD( m_hBeam, FIELD_EHANDLE ),
  3385. DEFINE_FIELD( m_vecTrueForward, FIELD_VECTOR ),
  3386. DEFINE_FIELD( m_bShouldHarrass, FIELD_BOOLEAN ),
  3387. DEFINE_FIELD( m_bLastTargetWasNPC, FIELD_BOOLEAN ),
  3388. DEFINE_INPUTFUNC( FIELD_VOID, "EnableHarrass", InputEnableHarrass ),
  3389. DEFINE_INPUTFUNC( FIELD_VOID, "DisableHarrass", InputDisableHarrass ),
  3390. DEFINE_OUTPUT( m_OnShotAtPlayer, "OnShotAtPlayer" ),
  3391. END_DATADESC()
  3392. //---------------------------------------------------------
  3393. //---------------------------------------------------------
  3394. void CFuncTankCombineCannon::Precache()
  3395. {
  3396. m_originalFireRate = m_fireRate;
  3397. PrecacheModel(COMBINE_CANNON_BEAM);
  3398. PrecacheParticleSystem( "Weapon_Combine_Ion_Cannon" );
  3399. BaseClass::Precache();
  3400. }
  3401. //---------------------------------------------------------
  3402. //---------------------------------------------------------
  3403. void CFuncTankCombineCannon::Spawn()
  3404. {
  3405. BaseClass::Spawn();
  3406. m_flTimeBeamOn = gpGlobals->curtime;
  3407. CreateBeam();
  3408. m_bShouldHarrass = true;
  3409. GetVectors( &m_vecTrueForward, NULL, NULL );
  3410. m_bLastTargetWasNPC = false;
  3411. }
  3412. //---------------------------------------------------------
  3413. //---------------------------------------------------------
  3414. void CFuncTankCombineCannon::CreateBeam()
  3415. {
  3416. if (!m_hBeam && gpGlobals->curtime >= m_flTimeBeamOn )
  3417. {
  3418. m_hBeam = CBeam::BeamCreate( COMBINE_CANNON_BEAM, 1.0f );
  3419. m_hBeam->SetColor( 255, 255, 255 );
  3420. SetContextThink( &CFuncTankCombineCannon::UpdateBeamThink, gpGlobals->curtime, s_pUpdateBeamThinkContext );
  3421. }
  3422. else
  3423. {
  3424. // Beam seems to be on, or I'm not supposed to have it on at the moment.
  3425. return;
  3426. }
  3427. Vector vecInitialAim;
  3428. AngleVectors( GetAbsAngles(), &vecInitialAim, NULL, NULL );
  3429. m_hBeam->PointsInit( WorldBarrelPosition(), WorldBarrelPosition() + vecInitialAim );
  3430. m_hBeam->SetBrightness( 255 );
  3431. m_hBeam->SetNoise( 0 );
  3432. m_hBeam->SetWidth( 3.0f );
  3433. m_hBeam->SetEndWidth( 0 );
  3434. m_hBeam->SetScrollRate( 0 );
  3435. m_hBeam->SetFadeLength( 60 ); // five feet to fade out
  3436. //m_hBeam->SetHaloTexture( sHaloSprite );
  3437. m_hBeam->SetHaloScale( 4.0f );
  3438. }
  3439. //---------------------------------------------------------
  3440. //---------------------------------------------------------
  3441. void CFuncTankCombineCannon::DestroyBeam()
  3442. {
  3443. if( m_hBeam )
  3444. {
  3445. UTIL_Remove( m_hBeam );
  3446. m_hBeam.Set(NULL);
  3447. }
  3448. }
  3449. //---------------------------------------------------------
  3450. //---------------------------------------------------------
  3451. void CFuncTankCombineCannon::AdjustRateOfFire()
  3452. {
  3453. // Maintain 1.5 rounds per second rate of fire.
  3454. m_fireRate = 1.5;
  3455. /*
  3456. if( m_hTarget.Get() != NULL && m_hTarget->IsPlayer() )
  3457. {
  3458. if( m_bLastTargetWasNPC )
  3459. {
  3460. // Cheat, and be able to fire RIGHT NOW if the target is a player and the
  3461. // last target I fired at was an NPC. This prevents the player from running
  3462. // for it while the gun is busy dealing with NPCs
  3463. SetNextAttack( gpGlobals->curtime );
  3464. }
  3465. }
  3466. */
  3467. }
  3468. //---------------------------------------------------------
  3469. //---------------------------------------------------------
  3470. #define COMBINE_CANNON_BEAM_MAX_DIST 1900.0f
  3471. void CFuncTankCombineCannon::UpdateBeamThink()
  3472. {
  3473. SetContextThink( &CFuncTankCombineCannon::UpdateBeamThink, gpGlobals->curtime + 0.025, s_pUpdateBeamThinkContext );
  3474. // Always try to create the beam.
  3475. CreateBeam();
  3476. if( !m_hBeam )
  3477. return;
  3478. trace_t trBeam;
  3479. trace_t trShot;
  3480. trace_t trBlockLOS;
  3481. Vector vecBarrel = WorldBarrelPosition();
  3482. Vector vecAim;
  3483. AngleVectors( GetAbsAngles(), &vecAim, NULL, NULL );
  3484. AI_TraceLine( vecBarrel, vecBarrel + vecAim * COMBINE_CANNON_BEAM_MAX_DIST, MASK_SHOT, this, COLLISION_GROUP_NONE, &trBeam );
  3485. m_hBeam->SetStartPos( trBeam.startpos );
  3486. m_hBeam->SetEndPos( trBeam.endpos );
  3487. if( !(m_spawnflags & SF_TANK_AIM_AT_POS) )
  3488. {
  3489. SetTargetPosition( trBeam.endpos );
  3490. }
  3491. }
  3492. //---------------------------------------------------------
  3493. //---------------------------------------------------------
  3494. void CFuncTankCombineCannon::FuncTankPostThink()
  3495. {
  3496. AdjustRateOfFire();
  3497. if( m_hTarget.Get() == NULL )
  3498. {
  3499. if( gpGlobals->curtime > m_flTimeNextSweep )
  3500. {
  3501. AddSpawnFlags( SF_TANK_AIM_AT_POS );
  3502. Vector vecTargetPosition = GetTargetPosition();
  3503. CBasePlayer *pPlayer = AI_GetSinglePlayer();
  3504. Vector vecToPlayer = pPlayer->WorldSpaceCenter() - GetAbsOrigin();
  3505. vecToPlayer.NormalizeInPlace();
  3506. bool bHarass = false;
  3507. float flDot = DotProduct( m_vecTrueForward, vecToPlayer );
  3508. if( flDot >= 0.9f && m_bShouldHarrass )
  3509. {
  3510. //Msg("%s Harrassing player\n", GetDebugName() );
  3511. vecTargetPosition = pPlayer->EyePosition();
  3512. bHarass = true;
  3513. }
  3514. else
  3515. {
  3516. //Msg( "%s Bored\n", GetDebugName() );
  3517. // Just point off in the distance, more or less directly ahead of me.
  3518. vecTargetPosition = GetAbsOrigin() + m_vecTrueForward * 1900.0f;
  3519. }
  3520. int i;
  3521. Vector vecTest;
  3522. bool bFoundPoint = false;
  3523. for( i = 0 ; i < 5 ; i++ )
  3524. {
  3525. vecTest = vecTargetPosition;
  3526. if( bHarass )
  3527. {
  3528. vecTest.x += random->RandomFloat( -48, 48 );
  3529. vecTest.y += random->RandomFloat( -48, 48 );
  3530. vecTest.z += random->RandomFloat( 16, 48 );
  3531. }
  3532. else
  3533. {
  3534. vecTest.x += random->RandomFloat( -48, 48 );
  3535. vecTest.y += random->RandomFloat( -48, 48 );
  3536. vecTest.z += random->RandomFloat( -48, 48 );
  3537. }
  3538. // Get the barrel position
  3539. Vector vecBarrelEnd = WorldBarrelPosition();
  3540. trace_t trLOS;
  3541. trace_t trShoot;
  3542. // Ignore the func_tank and any prop it's parented to, and check line of sight to the point
  3543. // Trace to the point. If an opaque trace doesn't reach the point, that means the beam hit
  3544. // something closer, (including a blockLOS), so try again.
  3545. CTraceFilterSkipTwoEntities traceFilter( this, GetParent(), COLLISION_GROUP_NONE );
  3546. AI_TraceLine( vecBarrelEnd, vecTest, MASK_BLOCKLOS_AND_NPCS, &traceFilter, &trLOS );
  3547. AI_TraceLine( vecBarrelEnd, vecTest, MASK_SHOT, &traceFilter, &trShoot );
  3548. if( trLOS.fraction < trShoot.fraction )
  3549. {
  3550. // Damn block LOS brushes.
  3551. continue;
  3552. }
  3553. //Msg("Point is visible in %d tries\n", i);
  3554. bFoundPoint = true;
  3555. break;
  3556. }
  3557. if( bFoundPoint )
  3558. {
  3559. vecTargetPosition = vecTest;
  3560. SetTargetPosition( vecTargetPosition );
  3561. //Msg("New place\n");
  3562. }
  3563. if( bHarass )
  3564. {
  3565. m_flTimeNextSweep = gpGlobals->curtime + random->RandomFloat( 0.25f, 0.75f );
  3566. }
  3567. else
  3568. {
  3569. m_flTimeNextSweep = gpGlobals->curtime + random->RandomFloat( 1, 3 );
  3570. }
  3571. }
  3572. }
  3573. else
  3574. {
  3575. //Msg("%d engaging: %s\n", entindex(), m_hTarget->GetClassname() );
  3576. RemoveSpawnFlags( SF_TANK_AIM_AT_POS );
  3577. }
  3578. }
  3579. //---------------------------------------------------------
  3580. // A normal func_tank uses a method of aiming the gun that will
  3581. // always follow a fast-moving player. This is because the func_tank
  3582. // turns the weapon by applying angular velocities in the early
  3583. // phase of the func_tank's Think(). Because the bullet is fired
  3584. // later in the same think, it is fired before the game physics have
  3585. // updated the func_tank's angles using the newly-computed angular
  3586. // velocity, so the bullet always trails the target slightly.
  3587. // This is unacceptable for the Combine Cannon, as the cannon MUST
  3588. // strike a moving player with absolute certainty. As a quick
  3589. // remedy, this code allows the combine cannon to fire a bullet
  3590. // at a slightly different angle than the gun is aiming, to
  3591. // ensure a hit. Large discrepancies are ignored and we accept
  3592. // the miss instead of presenting a bullet fired at an obviously
  3593. // adjusted angle.
  3594. //---------------------------------------------------------
  3595. void CFuncTankCombineCannon::Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker, bool bIgnoreSpread )
  3596. {
  3597. // Specifically do NOT fire in aim at pos mode. This is just for show.
  3598. if( HasSpawnFlags(SF_TANK_AIM_AT_POS) )
  3599. return;
  3600. Vector vecAdjustedForward = forward;
  3601. if( m_hTarget != NULL )
  3602. {
  3603. Vector vecToTarget = m_hTarget->BodyTarget( barrelEnd, false ) - barrelEnd;
  3604. VectorNormalize( vecToTarget );
  3605. float flDot = DotProduct( vecToTarget, forward );
  3606. if( flDot >= 0.97 )
  3607. {
  3608. vecAdjustedForward = vecToTarget;
  3609. }
  3610. if( m_hTarget->IsNPC() )
  3611. m_bLastTargetWasNPC = true;
  3612. else
  3613. m_bLastTargetWasNPC = false;
  3614. if( m_hTarget->IsPlayer() )
  3615. m_OnShotAtPlayer.FireOutput( this, this );
  3616. }
  3617. BaseClass::Fire( bulletCount, barrelEnd, vecAdjustedForward, pAttacker, bIgnoreSpread );
  3618. // Turn off the beam and tell it to stay off for a bit. We want it to look like the beam became the
  3619. // ion cannon 'rail gun' effect.
  3620. DestroyBeam();
  3621. m_flTimeBeamOn = gpGlobals->curtime + 0.2f;
  3622. m_flTimeNextSweep = gpGlobals->curtime + random->RandomInt( 1.0f, 2.0f );
  3623. }
  3624. //---------------------------------------------------------
  3625. //---------------------------------------------------------
  3626. void CFuncTankCombineCannon::MakeTracer( const Vector &vecTracerSrc, const trace_t &tr, int iTracerType )
  3627. {
  3628. // If the shot passed near the player, shake the screen.
  3629. if( AI_IsSinglePlayer() )
  3630. {
  3631. Vector vecPlayer = AI_GetSinglePlayer()->EyePosition();
  3632. Vector vecNearestPoint = PointOnLineNearestPoint( vecTracerSrc, tr.endpos, vecPlayer );
  3633. float flDist = vecPlayer.DistTo( vecNearestPoint );
  3634. if( flDist >= 10.0f && flDist <= 120.0f )
  3635. {
  3636. // Don't shake the screen if we're hit (within 10 inches), but do shake if a shot otherwise comes within 10 feet.
  3637. UTIL_ScreenShake( vecNearestPoint, 10, 60, 0.3, 120.0f, SHAKE_START, false );
  3638. }
  3639. }
  3640. // Send the railgun effect
  3641. DispatchParticleEffect( "Weapon_Combine_Ion_Cannon", vecTracerSrc, tr.endpos, vec3_angle, NULL );
  3642. }
  3643. //---------------------------------------------------------
  3644. //---------------------------------------------------------
  3645. void CFuncTankCombineCannon::TankDeactivate()
  3646. {
  3647. DestroyBeam();
  3648. m_flTimeBeamOn = gpGlobals->curtime + 1.0f;
  3649. SetContextThink( NULL, 0, s_pUpdateBeamThinkContext );
  3650. BaseClass::TankDeactivate();
  3651. }
  3652. //---------------------------------------------------------
  3653. //---------------------------------------------------------
  3654. void CFuncTankCombineCannon::InputSetTargetEntity( inputdata_t &inputdata )
  3655. {
  3656. BaseClass::InputSetTargetEntity( inputdata );
  3657. }
  3658. //---------------------------------------------------------
  3659. //---------------------------------------------------------
  3660. void CFuncTankCombineCannon::InputClearTargetEntity( inputdata_t &inputdata )
  3661. {
  3662. /*
  3663. m_targetEntityName = NULL_STRING;
  3664. m_hTarget = NULL;
  3665. // No longer aim at target position if have one
  3666. m_spawnflags &= ~SF_TANK_AIM_AT_POS;
  3667. */
  3668. BaseClass::InputClearTargetEntity( inputdata );
  3669. }
  3670. //---------------------------------------------------------
  3671. //---------------------------------------------------------
  3672. void CFuncTankCombineCannon::InputEnableHarrass( inputdata_t &inputdata )
  3673. {
  3674. m_bShouldHarrass = true;
  3675. }
  3676. //---------------------------------------------------------
  3677. //---------------------------------------------------------
  3678. void CFuncTankCombineCannon::InputDisableHarrass( inputdata_t &inputdata )
  3679. {
  3680. m_bShouldHarrass = false;
  3681. }
  3682. LINK_ENTITY_TO_CLASS( func_tank_combine_cannon, CFuncTankCombineCannon );