Team Fortress 2 Source Code as on 22/4/2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

498 lines
14 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================//
  7. #include "cbase.h"
  8. #include "basecombatweapon.h"
  9. #include "explode.h"
  10. #include "eventqueue.h"
  11. #include "gamerules.h"
  12. #include "ammodef.h"
  13. #include "in_buttons.h"
  14. #include "soundent.h"
  15. #include "ndebugoverlay.h"
  16. #include "vstdlib/random.h"
  17. #include "engine/IEngineSound.h"
  18. #include "player.h"
  19. #include "entitylist.h"
  20. #include "iservervehicle.h"
  21. // memdbgon must be the last include file in a .cpp file!!!
  22. #include "tier0/memdbgon.h"
  23. #define SF_TANK_ACTIVE 0x0001
  24. class CAPCController : public CPointEntity
  25. {
  26. typedef CPointEntity BaseClass;
  27. public:
  28. ~CAPCController( void );
  29. void Spawn( void );
  30. void Precache( void );
  31. bool KeyValue( const char *szKeyName, const char *szValue );
  32. void Think( void );
  33. void TrackTarget( void );
  34. void StartRotSound( void );
  35. void StopRotSound( void );
  36. // Bmodels don't go across transitions
  37. virtual int ObjectCaps( void ) { return BaseClass::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; }
  38. inline bool IsActive( void ) { return (m_spawnflags & SF_TANK_ACTIVE)?TRUE:FALSE; }
  39. // Input handlers.
  40. void InputActivate( inputdata_t &inputdata );
  41. void InputDeactivate( inputdata_t &inputdata );
  42. void ActivateRocketGuidance(void);
  43. void DeactivateRocketGuidance(void);
  44. bool InRange( float range );
  45. Vector WorldBarrelPosition( void )
  46. {
  47. EntityMatrix tmp;
  48. tmp.InitFromEntity( this );
  49. return tmp.LocalToWorld( m_barrelPos );
  50. }
  51. void UpdateMatrix( void )
  52. {
  53. m_parentMatrix.InitFromEntity( GetParent() ? GetParent() : NULL );
  54. }
  55. QAngle AimBarrelAt( const Vector &parentTarget );
  56. bool ShouldSavePhysics() { return false; }
  57. DECLARE_DATADESC();
  58. CBaseEntity *FindTarget( string_t targetName, CBaseEntity *pActivator );
  59. protected:
  60. float m_yawCenter; // "Center" yaw
  61. float m_yawRate; // Max turn rate to track targets
  62. // Zero is full rotation
  63. float m_yawTolerance; // Tolerance angle
  64. float m_pitchCenter; // "Center" pitch
  65. float m_pitchRate; // Max turn rate on pitch
  66. float m_pitchTolerance; // Tolerance angle
  67. float m_minRange; // Minimum range to aim/track
  68. float m_maxRange; // Max range to aim/track
  69. Vector m_barrelPos; // Length of the barrel
  70. Vector m_sightOrigin; // Last sight of target
  71. string_t m_soundStartRotate;
  72. string_t m_soundStopRotate;
  73. string_t m_soundLoopRotate;
  74. string_t m_targetEntityName;
  75. EHANDLE m_hTarget;
  76. EntityMatrix m_parentMatrix;
  77. COutputVector m_OnFireAtTarget;
  78. float m_flFiringDelay;
  79. bool m_bFireDelayed;
  80. };
  81. LINK_ENTITY_TO_CLASS( point_apc_controller, CAPCController );
  82. BEGIN_DATADESC( CAPCController )
  83. DEFINE_FIELD( m_yawCenter, FIELD_FLOAT ),
  84. DEFINE_KEYFIELD( m_yawRate, FIELD_FLOAT, "yawrate" ),
  85. DEFINE_KEYFIELD( m_yawTolerance, FIELD_FLOAT, "yawtolerance" ),
  86. DEFINE_FIELD( m_pitchCenter, FIELD_FLOAT ),
  87. DEFINE_KEYFIELD( m_pitchRate, FIELD_FLOAT, "pitchrate" ),
  88. DEFINE_KEYFIELD( m_pitchTolerance, FIELD_FLOAT, "pitchtolerance" ),
  89. DEFINE_KEYFIELD( m_minRange, FIELD_FLOAT, "minRange" ),
  90. DEFINE_KEYFIELD( m_maxRange, FIELD_FLOAT, "maxRange" ),
  91. DEFINE_FIELD( m_barrelPos, FIELD_VECTOR ),
  92. DEFINE_FIELD( m_sightOrigin, FIELD_VECTOR ),
  93. DEFINE_KEYFIELD( m_soundStartRotate, FIELD_SOUNDNAME, "rotatestartsound" ),
  94. DEFINE_KEYFIELD( m_soundStopRotate, FIELD_SOUNDNAME, "rotatestopsound" ),
  95. DEFINE_KEYFIELD( m_soundLoopRotate, FIELD_SOUNDNAME, "rotatesound" ),
  96. DEFINE_KEYFIELD( m_targetEntityName, FIELD_STRING, "targetentityname" ),
  97. DEFINE_FIELD( m_hTarget, FIELD_EHANDLE ),
  98. DEFINE_FIELD( m_parentMatrix, FIELD_VMATRIX_WORLDSPACE ),
  99. DEFINE_FIELD( m_flFiringDelay, FIELD_FLOAT ),
  100. DEFINE_FIELD( m_bFireDelayed, FIELD_BOOLEAN ),
  101. // Inputs
  102. DEFINE_INPUTFUNC( FIELD_VOID, "Activate", InputActivate ),
  103. DEFINE_INPUTFUNC( FIELD_VOID, "Deactivate", InputDeactivate ),
  104. // Outputs
  105. DEFINE_OUTPUT(m_OnFireAtTarget, "OnFireAtTarget"),
  106. END_DATADESC()
  107. //-----------------------------------------------------------------------------
  108. // Purpose:
  109. //-----------------------------------------------------------------------------
  110. CAPCController::~CAPCController( void )
  111. {
  112. if ( m_soundLoopRotate != NULL_STRING )
  113. {
  114. StopSound( entindex(), CHAN_STATIC, STRING(m_soundLoopRotate) );
  115. }
  116. }
  117. //------------------------------------------------------------------------------
  118. // Purpose: Input handler for activating the tank.
  119. //------------------------------------------------------------------------------
  120. void CAPCController::InputActivate( inputdata_t &inputdata )
  121. {
  122. ActivateRocketGuidance();
  123. }
  124. //-----------------------------------------------------------------------------
  125. // Purpose:
  126. //-----------------------------------------------------------------------------
  127. void CAPCController::ActivateRocketGuidance(void)
  128. {
  129. m_spawnflags |= SF_TANK_ACTIVE;
  130. SetNextThink( gpGlobals->curtime + 0.1f );
  131. }
  132. //-----------------------------------------------------------------------------
  133. // Purpose: Input handler for deactivating the tank.
  134. //-----------------------------------------------------------------------------
  135. void CAPCController::InputDeactivate( inputdata_t &inputdata )
  136. {
  137. DeactivateRocketGuidance();
  138. }
  139. //-----------------------------------------------------------------------------
  140. // Purpose:
  141. //-----------------------------------------------------------------------------
  142. void CAPCController::DeactivateRocketGuidance(void)
  143. {
  144. m_spawnflags &= ~SF_TANK_ACTIVE;
  145. StopRotSound();
  146. }
  147. //-----------------------------------------------------------------------------
  148. // Purpose:
  149. // Input : targetName -
  150. // pActivator -
  151. //-----------------------------------------------------------------------------
  152. CBaseEntity *CAPCController::FindTarget( string_t targetName, CBaseEntity *pActivator )
  153. {
  154. return gEntList.FindEntityGenericNearest( STRING( targetName ), GetAbsOrigin(), 0, this, pActivator );
  155. }
  156. //-----------------------------------------------------------------------------
  157. // Purpose: Caches entity key values until spawn is called.
  158. // Input : szKeyName -
  159. // szValue -
  160. // Output :
  161. //-----------------------------------------------------------------------------
  162. bool CAPCController::KeyValue( const char *szKeyName, const char *szValue )
  163. {
  164. if (FStrEq(szKeyName, "barrel"))
  165. {
  166. m_barrelPos.x = atof(szValue);
  167. }
  168. else if (FStrEq(szKeyName, "barrely"))
  169. {
  170. m_barrelPos.y = atof(szValue);
  171. }
  172. else if (FStrEq(szKeyName, "barrelz"))
  173. {
  174. m_barrelPos.z = atof(szValue);
  175. }
  176. else
  177. return BaseClass::KeyValue( szKeyName, szValue );
  178. return true;
  179. }
  180. //-----------------------------------------
  181. // Spawn
  182. //-----------------------------------------
  183. void CAPCController::Spawn( void )
  184. {
  185. Precache();
  186. m_yawCenter = GetLocalAngles().y;
  187. m_pitchCenter = GetLocalAngles().x;
  188. if ( IsActive() )
  189. {
  190. SetNextThink( gpGlobals->curtime + 1.0f );
  191. }
  192. UpdateMatrix();
  193. }
  194. //-----------------------------------------
  195. // Precache
  196. //-----------------------------------------
  197. void CAPCController::Precache( void )
  198. {
  199. if ( m_soundStartRotate != NULL_STRING )
  200. PrecacheScriptSound( STRING(m_soundStartRotate) );
  201. if ( m_soundStopRotate != NULL_STRING )
  202. PrecacheScriptSound( STRING(m_soundStopRotate) );
  203. if ( m_soundLoopRotate != NULL_STRING )
  204. PrecacheScriptSound( STRING(m_soundLoopRotate) );
  205. }
  206. //-----------------------------------------
  207. // InRange
  208. //-----------------------------------------
  209. bool CAPCController::InRange( float range )
  210. {
  211. if ( range < m_minRange )
  212. return FALSE;
  213. if ( m_maxRange > 0 && range > m_maxRange )
  214. return FALSE;
  215. return TRUE;
  216. }
  217. //-----------------------------------------
  218. // Think
  219. //-----------------------------------------
  220. void CAPCController::Think( void )
  221. {
  222. // refresh the matrix
  223. UpdateMatrix();
  224. SetLocalAngularVelocity( vec3_angle );
  225. TrackTarget();
  226. if ( fabs(GetLocalAngularVelocity().x) > 1 || fabs(GetLocalAngularVelocity().y) > 1 )
  227. StartRotSound();
  228. else
  229. StopRotSound();
  230. }
  231. //-----------------------------------------------------------------------------
  232. // Purpose: Aim the offset barrel at a position in parent space
  233. // Input : parentTarget - the position of the target in parent space
  234. // Output : Vector - angles in local space
  235. //-----------------------------------------------------------------------------
  236. QAngle CAPCController::AimBarrelAt( const Vector &parentTarget )
  237. {
  238. Vector target = parentTarget - GetLocalOrigin();
  239. float quadTarget = target.LengthSqr();
  240. float quadTargetXY = target.x*target.x + target.y*target.y;
  241. // We're trying to aim the offset barrel at an arbitrary point.
  242. // To calculate this, I think of the target as being on a sphere with
  243. // it's center at the origin of the gun.
  244. // The rotation we need is the opposite of the rotation that moves the target
  245. // along the surface of that sphere to intersect with the gun's shooting direction
  246. // To calculate that rotation, we simply calculate the intersection of the ray
  247. // coming out of the barrel with the target sphere (that's the new target position)
  248. // and use atan2() to get angles
  249. // angles from target pos to center
  250. float targetToCenterYaw = atan2( target.y, target.x );
  251. float centerToGunYaw = atan2( m_barrelPos.y, sqrt( quadTarget - (m_barrelPos.y*m_barrelPos.y) ) );
  252. float targetToCenterPitch = atan2( target.z, sqrt( quadTargetXY ) );
  253. float centerToGunPitch = atan2( -m_barrelPos.z, sqrt( quadTarget - (m_barrelPos.z*m_barrelPos.z) ) );
  254. return QAngle( -RAD2DEG(targetToCenterPitch+centerToGunPitch), RAD2DEG( targetToCenterYaw + centerToGunYaw ), 0 );
  255. }
  256. void CAPCController::TrackTarget( void )
  257. {
  258. trace_t tr;
  259. bool updateTime = FALSE, lineOfSight;
  260. QAngle angles;
  261. Vector barrelEnd;
  262. CBaseEntity *pTarget = NULL;
  263. barrelEnd.Init();
  264. if ( IsActive() )
  265. {
  266. SetNextThink( gpGlobals->curtime + 0.1f );
  267. }
  268. else
  269. {
  270. return;
  271. }
  272. // -----------------------------------
  273. // Get world target position
  274. // -----------------------------------
  275. barrelEnd = WorldBarrelPosition();
  276. Vector worldTargetPosition;
  277. CBaseEntity *pEntity = (CBaseEntity *)m_hTarget;
  278. if ( !pEntity || ( pEntity->GetFlags() & FL_NOTARGET ) )
  279. {
  280. m_hTarget = FindTarget( m_targetEntityName, NULL );
  281. if ( IsActive() )
  282. {
  283. SetNextThink( gpGlobals->curtime + 2 ); // Wait 2 sec s
  284. }
  285. return;
  286. }
  287. pTarget = pEntity;
  288. // Calculate angle needed to aim at target
  289. worldTargetPosition = pEntity->EyePosition();
  290. float range = (worldTargetPosition - barrelEnd).Length();
  291. if ( !InRange( range ) )
  292. {
  293. m_bFireDelayed = false;
  294. return;
  295. }
  296. UTIL_TraceLine( barrelEnd, worldTargetPosition, MASK_BLOCKLOS, this, COLLISION_GROUP_NONE, &tr );
  297. lineOfSight = FALSE;
  298. // No line of sight, don't track
  299. if ( tr.fraction == 1.0 || tr.m_pEnt == pTarget )
  300. {
  301. lineOfSight = TRUE;
  302. CBaseEntity *pInstance = pTarget;
  303. if ( InRange( range ) && pInstance && pInstance->IsAlive() )
  304. {
  305. updateTime = TRUE;
  306. // Sight position is BodyTarget with no noise (so gun doesn't bob up and down)
  307. m_sightOrigin = pInstance->BodyTarget( GetLocalOrigin(), false );
  308. }
  309. }
  310. // Convert targetPosition to parent
  311. angles = AimBarrelAt( m_parentMatrix.WorldToLocal( m_sightOrigin ) );
  312. // Force the angles to be relative to the center position
  313. float offsetY = UTIL_AngleDistance( angles.y, m_yawCenter );
  314. float offsetX = UTIL_AngleDistance( angles.x, m_pitchCenter );
  315. angles.y = m_yawCenter + offsetY;
  316. angles.x = m_pitchCenter + offsetX;
  317. // Move toward target at rate or less
  318. float distY = UTIL_AngleDistance( angles.y, GetLocalAngles().y );
  319. QAngle vecAngVel = GetLocalAngularVelocity();
  320. vecAngVel.y = distY * 10;
  321. vecAngVel.y = clamp( vecAngVel.y, -m_yawRate, m_yawRate );
  322. // Move toward target at rate or less
  323. float distX = UTIL_AngleDistance( angles.x, GetLocalAngles().x );
  324. vecAngVel.x = distX * 10;
  325. vecAngVel.x = clamp( vecAngVel.x, -m_pitchRate, m_pitchRate );
  326. SetLocalAngularVelocity( vecAngVel );
  327. SetMoveDoneTime( 0.1 );
  328. Vector forward;
  329. AngleVectors( GetLocalAngles(), &forward );
  330. forward = m_parentMatrix.ApplyRotation( forward );
  331. AngleVectors(angles, &forward);
  332. if ( lineOfSight == TRUE )
  333. {
  334. // FIXME: This will ultimately have to deal with NPCs being in the vehicle as well
  335. // See if the target is in a vehicle. If so, check its relationship
  336. CBasePlayer *pPlayer = ToBasePlayer( pTarget );
  337. if ( pPlayer && pPlayer->IsInAVehicle() )
  338. {
  339. IServerVehicle *pVehicle = pPlayer->GetVehicle();
  340. if ( pVehicle->ClassifyPassenger( pPlayer, CLASS_PLAYER ) == CLASS_PLAYER)
  341. {
  342. if ( !m_bFireDelayed )
  343. {
  344. m_bFireDelayed = true;
  345. m_flFiringDelay = gpGlobals->curtime + 1.5; // setup delay time before we start firing
  346. return;
  347. }
  348. if ( gpGlobals->curtime > m_flFiringDelay )
  349. {
  350. m_OnFireAtTarget.Set(forward, this, this); // tell apc to fire rockets, and what direction
  351. }
  352. }
  353. }
  354. }
  355. else
  356. {
  357. m_bFireDelayed = false; // reset flag since we can no longer see target
  358. }
  359. }
  360. void CAPCController::StartRotSound( void )
  361. {
  362. if ( m_soundLoopRotate != NULL_STRING )
  363. {
  364. CPASAttenuationFilter filter( this );
  365. filter.MakeReliable();
  366. EmitSound_t ep;
  367. ep.m_nChannel = CHAN_STATIC;
  368. ep.m_pSoundName = (char*)STRING(m_soundLoopRotate);
  369. ep.m_SoundLevel = SNDLVL_NORM;
  370. ep.m_flVolume = 0.85;
  371. EmitSound( filter, entindex(), ep );
  372. }
  373. if ( m_soundStartRotate != NULL_STRING )
  374. {
  375. CPASAttenuationFilter filter( this );
  376. EmitSound_t ep;
  377. ep.m_nChannel = CHAN_BODY;
  378. ep.m_pSoundName = (char*)STRING(m_soundStartRotate);
  379. ep.m_SoundLevel = SNDLVL_NORM;
  380. ep.m_flVolume = 1.0f;
  381. EmitSound( filter, entindex(), ep );
  382. }
  383. }
  384. void CAPCController::StopRotSound( void )
  385. {
  386. if ( m_soundLoopRotate != NULL_STRING )
  387. {
  388. StopSound( entindex(), CHAN_STATIC, (char*)STRING(m_soundLoopRotate) );
  389. }
  390. if ( m_soundStopRotate != NULL_STRING )
  391. {
  392. CPASAttenuationFilter filter( this );
  393. EmitSound_t ep;
  394. ep.m_nChannel = CHAN_BODY;
  395. ep.m_pSoundName = (char*)STRING(m_soundStopRotate);
  396. ep.m_SoundLevel = SNDLVL_NORM;
  397. EmitSound( filter, entindex(), ep );
  398. }
  399. }