Counter Strike : Global Offensive Source Code
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.

3860 lines
109 KiB

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