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.

2440 lines
69 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: Engineer's Sentrygun OMG
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================//
  7. #include "cbase.h"
  8. #include "tf_obj_sentrygun.h"
  9. #include "engine/IEngineSound.h"
  10. #include "tf_player.h"
  11. #include "tf_team.h"
  12. #include "world.h"
  13. #include "tf_projectile_rocket.h"
  14. #include "te_effect_dispatch.h"
  15. #include "tf_gamerules.h"
  16. #include "ammodef.h"
  17. #include "tf_weapon_wrench.h"
  18. #include "tf_weapon_laser_pointer.h"
  19. #include "tf_weapon_shotgun.h"
  20. #include "bot/map_entities/tf_bot_hint_sentrygun.h"
  21. #include "bot/tf_bot.h"
  22. #include "nav_mesh/tf_nav_mesh.h"
  23. #include "nav_pathfind.h"
  24. #include "tf_weapon_knife.h"
  25. #include "tf_logic_robot_destruction.h"
  26. #include "tf_target_dummy.h"
  27. // memdbgon must be the last include file in a .cpp file!!!
  28. #include "tier0/memdbgon.h"
  29. extern bool IsInCommentaryMode();
  30. extern ConVar tf_nav_in_combat_range;
  31. // Ground placed version
  32. #define SENTRY_MODEL_PLACEMENT "models/buildables/sentry1_blueprint.mdl"
  33. #define SENTRY_MODEL_LEVEL_1 "models/buildables/sentry1.mdl"
  34. #define SENTRY_MODEL_LEVEL_1_UPGRADE "models/buildables/sentry1_heavy.mdl"
  35. #define SENTRY_MODEL_LEVEL_2 "models/buildables/sentry2.mdl"
  36. #define SENTRY_MODEL_LEVEL_2_UPGRADE "models/buildables/sentry2_heavy.mdl"
  37. #define SENTRY_MODEL_LEVEL_3 "models/buildables/sentry3.mdl"
  38. #define SENTRY_MODEL_LEVEL_3_UPGRADE "models/buildables/sentry3_heavy.mdl"
  39. #define SENTRY_ROCKET_MODEL "models/buildables/sentry3_rockets.mdl"
  40. #define SENTRYGUN_MINS Vector(-20, -20, 0)
  41. #define SENTRYGUN_MAXS Vector( 20, 20, 66)
  42. #define SENTRYGUN_ADD_SHELLS 40
  43. #define SENTRYGUN_ADD_ROCKETS 8
  44. #define SENTRY_THINK_DELAY 0.05
  45. #define SENTRYGUN_CONTEXT "SentrygunContext"
  46. #define SENTRYGUN_RECENTLY_ATTACKED_TIME 2.0
  47. #define SENTRYGUN_MINIGUN_RESIST_LVL_1 0.0
  48. #define SENTRYGUN_MINIGUN_RESIST_LVL_2 0.15
  49. #define SENTRYGUN_MINIGUN_RESIST_LVL_3 0.20
  50. #define SENTRYGUN_SAPPER_OWNER_DAMAGE_MODIFIER 0.66f
  51. #define SENTRYGUN_MAX_LEVEL_MINI 1
  52. #define MINI_SENTRY_SCALE 0.75f
  53. #define DISPOSABLE_SCALE 0.65f
  54. #define SMALL_SENTRY_SCALE 0.80f
  55. #define WRANGLER_DISABLE_TIME 3.0f
  56. enum
  57. {
  58. SENTRYGUN_ATTACHMENT_MUZZLE = 0,
  59. SENTRYGUN_ATTACHMENT_MUZZLE_ALT,
  60. SENTRYGUN_ATTACHMENT_ROCKET,
  61. };
  62. enum target_ranges
  63. {
  64. RANGE_MELEE,
  65. RANGE_NEAR,
  66. RANGE_MID,
  67. RANGE_FAR,
  68. };
  69. #define VECTOR_CONE_TF_SENTRY Vector( 0.1, 0.1, 0 )
  70. //-----------------------------------------------------------------------------
  71. // Purpose: Only send the LocalWeaponData to the player carrying the weapon
  72. //-----------------------------------------------------------------------------
  73. void* SendProxy_SendLocalObjectDataTable( const SendProp *pProp, const void *pStruct, const void *pVarData, CSendProxyRecipients *pRecipients, int objectID )
  74. {
  75. // Get the weapon entity
  76. CBaseObject *pObject = (CBaseObject*)pVarData;
  77. if ( pObject )
  78. {
  79. // Only send this chunk of data to the player carrying this weapon
  80. CBasePlayer *pPlayer = ToBasePlayer( pObject->GetOwner() );
  81. if ( pPlayer )
  82. {
  83. pRecipients->SetOnly( pPlayer->GetClientIndex() );
  84. return (void*)pVarData;
  85. }
  86. }
  87. return NULL;
  88. }
  89. REGISTER_SEND_PROXY_NON_MODIFIED_POINTER( SendProxy_SendLocalObjectDataTable );
  90. BEGIN_NETWORK_TABLE_NOBASE( CObjectSentrygun, DT_SentrygunLocalData )
  91. SendPropInt( SENDINFO(m_iKills), -1, SPROP_VARINT | SPROP_CHANGES_OFTEN ),
  92. SendPropInt( SENDINFO(m_iAssists), -1, SPROP_VARINT | SPROP_CHANGES_OFTEN ),
  93. END_NETWORK_TABLE()
  94. IMPLEMENT_SERVERCLASS_ST( CObjectSentrygun, DT_ObjectSentrygun )
  95. SendPropInt( SENDINFO(m_iAmmoShells), -1, SPROP_VARINT | SPROP_CHANGES_OFTEN ),
  96. SendPropInt( SENDINFO(m_iAmmoRockets), -1, SPROP_VARINT | SPROP_CHANGES_OFTEN ),
  97. SendPropInt( SENDINFO(m_iState), Q_log2( SENTRY_NUM_STATES ) + 1, SPROP_UNSIGNED ),
  98. SendPropBool( SENDINFO( m_bPlayerControlled ) ),
  99. SendPropInt( SENDINFO( m_nShieldLevel ), 4, SPROP_UNSIGNED ),
  100. SendPropEHandle( SENDINFO( m_hEnemy ) ),
  101. SendPropEHandle( SENDINFO( m_hAutoAimTarget ) ),
  102. SendPropDataTable( "SentrygunLocalData", 0, &REFERENCE_SEND_TABLE( DT_SentrygunLocalData ), SendProxy_SendLocalObjectDataTable ),
  103. END_SEND_TABLE()
  104. BEGIN_DATADESC( CObjectSentrygun )
  105. END_DATADESC()
  106. LINK_ENTITY_TO_CLASS(obj_sentrygun, CObjectSentrygun);
  107. PRECACHE_REGISTER(obj_sentrygun);
  108. ConVar tf_sentrygun_damage( "tf_sentrygun_damage", "16", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY );
  109. ConVar tf_sentrygun_mini_damage( "tf_sentrygun_mini_damage", "8", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY );
  110. ConVar tf_sentrygun_ammocheat( "tf_sentrygun_ammocheat", "0", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY );
  111. extern ConVar tf_obj_upgrade_per_hit;
  112. ConVar tf_sentrygun_newtarget_dist( "tf_sentrygun_newtarget_dist", "200", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY );
  113. ConVar tf_sentrygun_metal_per_shell( "tf_sentrygun_metal_per_shell", "1", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY );
  114. ConVar tf_sentrygun_metal_per_rocket( "tf_sentrygun_metal_per_rocket", "2", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY );
  115. ConVar tf_sentrygun_notarget( "tf_sentrygun_notarget", "0", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY );
  116. ConVar tf_sentrygun_max_absorbed_damage_while_controlled_for_achievement( "tf_sentrygun_max_absorbed_damage_while_controlled_for_achievement", "500", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY );
  117. ConVar tf_sentrygun_kill_after_redeploy_time_achievement( "tf_sentrygun_kill_after_redeploy_time_achievement", "10", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY );
  118. extern ConVar tf_cheapobjects;
  119. //-----------------------------------------------------------------------------
  120. // Purpose:
  121. //-----------------------------------------------------------------------------
  122. CObjectSentrygun::CObjectSentrygun()
  123. {
  124. // Don't bother with health modifying attributes here, because we don't have an owner yet, and it'll be stomped in FirstSpawn()
  125. int iHealth = GetMaxHealthForCurrentLevel();
  126. SetMaxHealth( iHealth );
  127. SetHealth( iHealth );
  128. SetType( OBJ_SENTRYGUN );
  129. m_bFireNextFrame = false;
  130. m_bFireRocketNextFrame = false;
  131. m_flAutoAimStartTime = 0.f;
  132. m_bPlayerControlled = false;
  133. m_iLifetimeShieldedDamage = 0;
  134. m_flFireRate = 1.f;
  135. m_flSentryRange = SENTRY_MAX_RANGE;
  136. m_nShieldLevel.Set( SHIELD_NONE );
  137. m_lastTeammateWrenchHit = NULL;
  138. m_lastTeammateWrenchHitTimer.Invalidate();
  139. m_flScaledSentry = 1.0f;
  140. }
  141. //-----------------------------------------------------------------------------
  142. // Purpose:
  143. //-----------------------------------------------------------------------------
  144. void CObjectSentrygun::Spawn()
  145. {
  146. m_iPitchPoseParameter = -1;
  147. m_iYawPoseParameter = -1;
  148. SetModel( SENTRY_MODEL_PLACEMENT );
  149. // Rotate Details
  150. m_iRightBound = 45;
  151. m_iLeftBound = 315;
  152. m_iBaseTurnRate = 6;
  153. m_flFieldOfView = VIEW_FIELD_NARROW;
  154. // Give the Gun some ammo
  155. m_iAmmoShells = 0;
  156. m_iAmmoRockets = 0;
  157. float flMaxAmmoMult = 1.f;
  158. if ( GetOwner() )
  159. {
  160. CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( GetOwner(), flMaxAmmoMult, mvm_sentry_ammo );
  161. }
  162. m_iMaxAmmoShells = SENTRYGUN_MAX_SHELLS_1 * flMaxAmmoMult;
  163. m_iMaxAmmoRockets = SENTRYGUN_MAX_ROCKETS * flMaxAmmoMult;
  164. m_iAmmoType = GetAmmoDef()->Index( "TF_AMMO_PRIMARY" );
  165. // Start searching for enemies
  166. m_hEnemy = NULL;
  167. m_flHeavyBulletResist = SENTRYGUN_MINIGUN_RESIST_LVL_1;
  168. m_lastTeammateWrenchHit = NULL;
  169. m_lastTeammateWrenchHitTimer.Invalidate();
  170. BaseClass::Spawn();
  171. SetViewOffset( SENTRYGUN_EYE_OFFSET_LEVEL_1 );
  172. SetBuildingSize();
  173. m_iState.Set( SENTRY_STATE_INACTIVE );
  174. SetContextThink( &CObjectSentrygun::SentryThink, gpGlobals->curtime + SENTRY_THINK_DELAY, SENTRYGUN_CONTEXT );
  175. }
  176. //-----------------------------------------------------------------------------
  177. // Purpose:
  178. //-----------------------------------------------------------------------------
  179. void CObjectSentrygun::FirstSpawn()
  180. {
  181. m_flLastAttackedTime = 0;
  182. int iHealth = GetMaxHealthForCurrentLevel();
  183. SetMaxHealth( iHealth );
  184. SetHealth( iHealth );
  185. BaseClass::FirstSpawn();
  186. }
  187. Vector CObjectSentrygun::GetEnemyAimPosition( CBaseEntity* pEnemy ) const
  188. {
  189. // Default to pointing to the origin
  190. Vector vecPos = pEnemy->WorldSpaceCenter();
  191. CTFPlayer* pTFEnemy = ToTFPlayer( pEnemy );
  192. // This is expensive, so only do it if our target is in a state that requires it
  193. if ( pTFEnemy )
  194. {
  195. bool bShouldUseAccurateMethod = false;
  196. int playerFlags = pTFEnemy->GetFlags();
  197. // Crouch jumping makes your box weird
  198. bShouldUseAccurateMethod |= !( playerFlags & FL_ONGROUND ) && ( playerFlags & FL_DUCKING );
  199. // Taunting can make your box weird
  200. bShouldUseAccurateMethod |= pTFEnemy->m_Shared.InCond( TF_COND_TAUNTING );
  201. if ( bShouldUseAccurateMethod )
  202. {
  203. // Use this bone as the the aim target
  204. int iSpineBone = pTFEnemy->LookupBone( "bip_spine_2" );
  205. if ( iSpineBone != -1 )
  206. {
  207. QAngle angles;
  208. pTFEnemy->GetBonePosition( iSpineBone, vecPos, angles );
  209. }
  210. }
  211. }
  212. return vecPos;
  213. }
  214. void CObjectSentrygun::SentryThink( void )
  215. {
  216. m_flSentryRange = SENTRY_MAX_RANGE;
  217. if ( !IsDisposableBuilding() )
  218. {
  219. CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( GetOwner(), m_flSentryRange, mult_sentry_range );
  220. }
  221. switch( m_iState )
  222. {
  223. case SENTRY_STATE_INACTIVE:
  224. case SENTRY_STATE_UPGRADING: // Base class handles this
  225. break;
  226. case SENTRY_STATE_SEARCHING:
  227. SentryRotate();
  228. break;
  229. case SENTRY_STATE_ATTACKING:
  230. Attack();
  231. break;
  232. default:
  233. Assert( 0 );
  234. break;
  235. }
  236. SetContextThink( &CObjectSentrygun::SentryThink, gpGlobals->curtime + SENTRY_THINK_DELAY, SENTRYGUN_CONTEXT );
  237. if ( m_nShieldLevel > 0 && (gpGlobals->curtime > m_flShieldFadeTime) )
  238. {
  239. m_nShieldLevel.Set( SHIELD_NONE );
  240. m_vecGoalAngles.x = 0;
  241. }
  242. // infinite ammo for enemy team in MvM mode
  243. if ( TFGameRules()->IsMannVsMachineMode() && GetTeamNumber() == TF_TEAM_PVE_INVADERS )
  244. {
  245. m_iAmmoRockets = SENTRYGUN_MAX_ROCKETS;
  246. m_iMaxAmmoRockets = SENTRYGUN_MAX_ROCKETS;
  247. m_iAmmoShells = SENTRYGUN_MAX_SHELLS_3;
  248. m_iMaxAmmoShells = SENTRYGUN_MAX_SHELLS_3;
  249. }
  250. }
  251. void CObjectSentrygun::StartPlacement( CTFPlayer *pPlayer )
  252. {
  253. BaseClass::StartPlacement( pPlayer );
  254. // Set my build size
  255. m_vecBuildMins = SENTRYGUN_MINS;
  256. m_vecBuildMaxs = SENTRYGUN_MAXS;
  257. m_vecBuildMins -= Vector( 4,4,0 );
  258. m_vecBuildMaxs += Vector( 4,4,0 );
  259. MakeMiniBuilding( pPlayer );
  260. MakeDisposableBuilding( pPlayer );
  261. MakeScaledBuilding( GetBuilder() );
  262. }
  263. //-----------------------------------------------------------------------------
  264. // Purpose: Start building the object
  265. //-----------------------------------------------------------------------------
  266. bool CObjectSentrygun::StartBuilding( CBaseEntity *pBuilder )
  267. {
  268. SetStartBuildingModel();
  269. // Have to re-call this in case the player changed their weapon
  270. // between StartPlacement and StartBuilding.
  271. MakeMiniBuilding( GetBuilder() );
  272. MakeDisposableBuilding( GetBuilder() );
  273. MakeScaledBuilding( GetBuilder() );
  274. if ( IsMiniBuilding() )
  275. {
  276. SetBodygroup( FindBodygroupByName( "mini_sentry_light" ), 1 );
  277. }
  278. CreateBuildPoints();
  279. SetPoseParameter( m_iPitchPoseParameter, 0.0 );
  280. SetPoseParameter( m_iYawPoseParameter, 0.0 );
  281. SetObjectMode( IsDisposableBuilding() ? MODE_SENTRYGUN_DISPOSABLE : MODE_SENTRYGUN_NORMAL );
  282. return BaseClass::StartBuilding( pBuilder );
  283. }
  284. void CObjectSentrygun::SetStartBuildingModel( void )
  285. {
  286. SetModel( SENTRY_MODEL_LEVEL_1_UPGRADE );
  287. m_iState.Set( SENTRY_STATE_INACTIVE );
  288. }
  289. //-----------------------------------------------------------------------------
  290. // Purpose:
  291. //-----------------------------------------------------------------------------
  292. void CObjectSentrygun::MakeMiniBuilding( CTFPlayer* pPlayer )
  293. {
  294. if ( !ShouldBeMiniBuilding( pPlayer ) || IsMiniBuilding() )
  295. return;
  296. BaseClass::MakeMiniBuilding( pPlayer );
  297. SetModelScale( MINI_SENTRY_SCALE );
  298. int iHealth = GetMaxHealthForCurrentLevel();
  299. SetMaxHealth( iHealth );
  300. SetHealth( iHealth / 2.0f );
  301. SetBuildingSize();
  302. }
  303. //-----------------------------------------------------------------------------
  304. int CObjectSentrygun::GetMaxUpgradeLevel( )
  305. {
  306. if ( IsDisposableBuilding() || IsMiniBuilding() )
  307. return SENTRYGUN_MAX_LEVEL_MINI;
  308. return BaseClass::GetMaxUpgradeLevel();
  309. }
  310. //-----------------------------------------------------------------------------
  311. // Purpose:
  312. //-----------------------------------------------------------------------------
  313. void CObjectSentrygun::OnGoActive( void )
  314. {
  315. SetModel( SENTRY_MODEL_LEVEL_1 );
  316. if ( IsMiniBuilding() )
  317. {
  318. SetBodygroup( FindBodygroupByName( "mini_sentry_light" ), 1 );
  319. }
  320. m_iState.Set( SENTRY_STATE_SEARCHING );
  321. // Orient it
  322. QAngle angles = GetAbsAngles();
  323. m_vecCurAngles.y = UTIL_AngleMod( angles.y );
  324. m_iRightBound = UTIL_AngleMod( (int)angles.y - 50 );
  325. m_iLeftBound = UTIL_AngleMod( (int)angles.y + 50 );
  326. if ( m_iRightBound > m_iLeftBound )
  327. {
  328. m_iRightBound = m_iLeftBound;
  329. m_iLeftBound = UTIL_AngleMod( (int)angles.y - 50);
  330. }
  331. // Start it rotating
  332. m_vecGoalAngles.y = m_iRightBound;
  333. m_vecGoalAngles.x = m_vecCurAngles.x = 0;
  334. m_bTurningRight = true;
  335. EmitSound( "Building_Sentrygun.Built" );
  336. // if our eye pos is underwater, we're waterlevel 3, else 0
  337. bool bUnderwater = ( UTIL_PointContents( EyePosition() ) & MASK_WATER ) ? true : false;
  338. SetWaterLevel( ( bUnderwater ) ? 3 : 0 );
  339. if ( m_bCarryDeploy )
  340. {
  341. m_iAmmoShells = m_iOldAmmoShells;
  342. m_iAmmoRockets = m_iOldAmmoRockets;
  343. }
  344. else
  345. {
  346. m_iAmmoShells = m_iMaxAmmoShells;
  347. m_iAmmoRockets = m_iMaxAmmoRockets;
  348. }
  349. // Init attachments for level 1 sentry gun
  350. m_iAttachments[SENTRYGUN_ATTACHMENT_MUZZLE] = LookupAttachment( "muzzle" );
  351. m_iAttachments[SENTRYGUN_ATTACHMENT_MUZZLE_ALT] = 0;
  352. m_iAttachments[SENTRYGUN_ATTACHMENT_ROCKET] = 0;
  353. BaseClass::OnGoActive();
  354. IGameEvent * event = gameeventmanager->CreateEvent( "sentry_on_go_active" );
  355. if ( event )
  356. {
  357. event->SetInt( "index", entindex() ); // object entity index
  358. gameeventmanager->FireEvent( event, true ); // don't send to clients
  359. }
  360. }
  361. //-----------------------------------------------------------------------------
  362. // Purpose:
  363. //-----------------------------------------------------------------------------
  364. void CObjectSentrygun::Precache()
  365. {
  366. BaseClass::Precache();
  367. int iModelIndex;
  368. // Models
  369. PrecacheModel( SENTRY_MODEL_PLACEMENT );
  370. iModelIndex = PrecacheModel( SENTRY_MODEL_LEVEL_1 );
  371. PrecacheGibsForModel( iModelIndex );
  372. iModelIndex = PrecacheModel( SENTRY_MODEL_LEVEL_1_UPGRADE );
  373. PrecacheGibsForModel( iModelIndex );
  374. iModelIndex = PrecacheModel( SENTRY_MODEL_LEVEL_2 );
  375. PrecacheGibsForModel( iModelIndex );
  376. iModelIndex = PrecacheModel( SENTRY_MODEL_LEVEL_2_UPGRADE );
  377. PrecacheGibsForModel( iModelIndex );
  378. iModelIndex = PrecacheModel( SENTRY_MODEL_LEVEL_3 );
  379. PrecacheGibsForModel( iModelIndex );
  380. iModelIndex = PrecacheModel( SENTRY_MODEL_LEVEL_3_UPGRADE );
  381. PrecacheGibsForModel( iModelIndex );
  382. PrecacheModel( SENTRY_ROCKET_MODEL );
  383. PrecacheModel( "models/effects/sentry1_muzzle/sentry1_muzzle.mdl" );
  384. PrecacheModel( "models/buildables/sentry_shield.mdl" );
  385. // Sounds
  386. PrecacheScriptSound( "Building_Sentrygun.Fire" );
  387. PrecacheScriptSound( "Building_Sentrygun.Fire2" ); // level 2 sentry
  388. PrecacheScriptSound( "Building_Sentrygun.Fire3" ); // level 3 sentry
  389. PrecacheScriptSound( "Building_Sentrygun.FireRocket" );
  390. PrecacheScriptSound( "Building_Sentrygun.Alert" );
  391. PrecacheScriptSound( "Building_Sentrygun.AlertTarget" );
  392. PrecacheScriptSound( "Building_Sentrygun.Idle" );
  393. PrecacheScriptSound( "Building_Sentrygun.Idle2" ); // level 2 sentry
  394. PrecacheScriptSound( "Building_Sentrygun.Idle3" ); // level 3 sentry
  395. PrecacheScriptSound( "Building_Sentrygun.Built" );
  396. PrecacheScriptSound( "Building_Sentrygun.Empty" );
  397. PrecacheScriptSound( "Building_Sentrygun.ShaftFire" );
  398. PrecacheScriptSound( "Building_Sentrygun.ShaftFire2" );
  399. PrecacheScriptSound( "Building_Sentrygun.ShaftFire3" );
  400. PrecacheScriptSound( "Building_Sentrygun.ShaftLaserPass" );
  401. PrecacheScriptSound( "Building_MiniSentrygun.Fire" );
  402. PrecacheParticleSystem( "sentrydamage_1" );
  403. PrecacheParticleSystem( "sentrydamage_2" );
  404. PrecacheParticleSystem( "sentrydamage_3" );
  405. PrecacheParticleSystem( "sentrydamage_4" );
  406. }
  407. //-----------------------------------------------------------------------------
  408. //
  409. //-----------------------------------------------------------------------------
  410. bool CObjectSentrygun::CanBeUpgraded( CTFPlayer *pPlayer )
  411. {
  412. if ( m_bWasMapPlaced && !HasSpawnFlags(SF_SENTRY_UPGRADEABLE) )
  413. {
  414. return false;
  415. }
  416. return BaseClass::CanBeUpgraded( pPlayer );
  417. }
  418. //-----------------------------------------------------------------------------
  419. // Purpose: Raises the Sentrygun one level
  420. //-----------------------------------------------------------------------------
  421. void CObjectSentrygun::StartUpgrading( void )
  422. {
  423. BaseClass::StartUpgrading();
  424. float flMaxAmmoMult = 1.f;
  425. if ( GetOwner() )
  426. {
  427. CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( GetOwner(), flMaxAmmoMult, mvm_sentry_ammo );
  428. }
  429. switch( m_iUpgradeLevel )
  430. {
  431. case 2:
  432. SetModel( SENTRY_MODEL_LEVEL_2_UPGRADE );
  433. m_flHeavyBulletResist = SENTRYGUN_MINIGUN_RESIST_LVL_2;
  434. SetViewOffset( SENTRYGUN_EYE_OFFSET_LEVEL_2 );
  435. m_iMaxAmmoShells = SENTRYGUN_MAX_SHELLS_2 * flMaxAmmoMult;
  436. break;
  437. case 3:
  438. SetModel( SENTRY_MODEL_LEVEL_3_UPGRADE );
  439. if ( !m_bCarryDeploy )
  440. {
  441. m_iAmmoRockets = SENTRYGUN_MAX_ROCKETS;
  442. }
  443. m_flHeavyBulletResist = SENTRYGUN_MINIGUN_RESIST_LVL_3;
  444. SetViewOffset( SENTRYGUN_EYE_OFFSET_LEVEL_3 );
  445. m_iMaxAmmoShells = SENTRYGUN_MAX_SHELLS_3 * flMaxAmmoMult;
  446. break;
  447. default:
  448. Assert(0);
  449. break;
  450. }
  451. // more ammo capability
  452. if ( !m_bCarryDeploy )
  453. {
  454. m_iAmmoShells = m_iMaxAmmoShells;
  455. }
  456. m_iState.Set( SENTRY_STATE_UPGRADING );
  457. }
  458. //-----------------------------------------------------------------------------
  459. // Purpose:
  460. //-----------------------------------------------------------------------------
  461. void CObjectSentrygun::FinishUpgrading( void )
  462. {
  463. BaseClass::FinishUpgrading();
  464. m_iState.Set( SENTRY_STATE_SEARCHING );
  465. m_hEnemy = NULL;
  466. switch( m_iUpgradeLevel )
  467. {
  468. case 1:
  469. // This can happen when a saper downgrades a sentry
  470. // No need to do anything here
  471. break;
  472. case 2:
  473. SetModel( SENTRY_MODEL_LEVEL_2 );
  474. break;
  475. case 3:
  476. SetModel( SENTRY_MODEL_LEVEL_3 );
  477. break;
  478. default:
  479. Assert(0);
  480. break;
  481. }
  482. // Look up the new attachments
  483. m_iAttachments[SENTRYGUN_ATTACHMENT_MUZZLE] = LookupAttachment( "muzzle_l" );
  484. m_iAttachments[SENTRYGUN_ATTACHMENT_MUZZLE_ALT] = LookupAttachment( "muzzle_r" );
  485. m_iAttachments[SENTRYGUN_ATTACHMENT_ROCKET] = LookupAttachment( "rocket_l" );
  486. }
  487. //-----------------------------------------------------------------------------
  488. // Hit by a friendly engineer's wrench
  489. //-----------------------------------------------------------------------------
  490. bool CObjectSentrygun::OnWrenchHit( CTFPlayer *pPlayer, CTFWrench *pWrench, Vector hitLoc )
  491. {
  492. if ( IsDisposableBuilding() )
  493. return false;
  494. bool bDidWork = false;
  495. // If the player repairs it at all, we're done
  496. if ( GetHealth() < GetMaxHealth() )
  497. {
  498. // STAGING_ENGY
  499. // Mod repair value by shield value
  500. float flRepairValue = pWrench->GetRepairValue();
  501. if ( m_nShieldLevel == SHIELD_NORMAL )
  502. {
  503. flRepairValue *= SHIELD_NORMAL_VALUE;
  504. }
  505. if ( Command_Repair( pPlayer, flRepairValue ) )
  506. {
  507. DoWrenchHitEffect( hitLoc, true, false );
  508. bDidWork = true;
  509. }
  510. }
  511. // Don't put in upgrade metal until the sentry is fully healed
  512. if ( !bDidWork )
  513. {
  514. if ( CheckUpgradeOnHit( pPlayer ) )
  515. {
  516. DoWrenchHitEffect( hitLoc, false, true );
  517. bDidWork = true;
  518. }
  519. }
  520. if ( !IsUpgrading() )
  521. {
  522. // player ammo into rockets
  523. // 1 ammo = 1 shell
  524. // 2 ammo = 1 rocket
  525. // only fill rockets if we have extra shells
  526. int iPlayerMetal = pPlayer->GetAmmoCount( TF_AMMO_METAL );
  527. // If the sentry has less that 100% ammo, put some ammo in it
  528. if ( m_iAmmoShells < m_iMaxAmmoShells && iPlayerMetal > 0 )
  529. {
  530. int iMaxShellsPlayerCanAfford = (int)( (float)iPlayerMetal / tf_sentrygun_metal_per_shell.GetFloat() );
  531. // cap the amount we can add
  532. int iAmountToAdd = MIN( SENTRYGUN_ADD_SHELLS, iMaxShellsPlayerCanAfford );
  533. iAmountToAdd = MIN( ( m_iMaxAmmoShells - m_iAmmoShells ), iAmountToAdd );
  534. // STAGING_ENGY
  535. // Mod Ammo if shielded
  536. if ( m_nShieldLevel == SHIELD_NORMAL )
  537. {
  538. iAmountToAdd *= SHIELD_NORMAL_VALUE;
  539. }
  540. pPlayer->RemoveAmmo( iAmountToAdd * tf_sentrygun_metal_per_shell.GetInt(), TF_AMMO_METAL );
  541. m_iAmmoShells += iAmountToAdd;
  542. if ( iAmountToAdd > 0 )
  543. {
  544. bDidWork = true;
  545. }
  546. }
  547. // One rocket per two ammo
  548. iPlayerMetal = pPlayer->GetAmmoCount( TF_AMMO_METAL );
  549. if ( m_iAmmoRockets < m_iMaxAmmoRockets && m_iUpgradeLevel == 3 && iPlayerMetal > 0 )
  550. {
  551. int iMaxRocketsPlayerCanAfford = (int)( (float)iPlayerMetal / tf_sentrygun_metal_per_rocket.GetFloat() );
  552. int iAmountToAdd = MIN( ( SENTRYGUN_ADD_ROCKETS ), iMaxRocketsPlayerCanAfford );
  553. iAmountToAdd = MIN( ( m_iMaxAmmoRockets - m_iAmmoRockets ), iAmountToAdd );
  554. // STAGING_ENGY
  555. // Mod Ammo if shielded
  556. if ( m_nShieldLevel == SHIELD_NORMAL )
  557. {
  558. iAmountToAdd *= SHIELD_NORMAL_VALUE;
  559. }
  560. pPlayer->RemoveAmmo( iAmountToAdd * tf_sentrygun_metal_per_rocket.GetFloat(), TF_AMMO_METAL );
  561. m_iAmmoRockets += iAmountToAdd;
  562. if ( iAmountToAdd > 0 )
  563. {
  564. bDidWork = true;
  565. }
  566. }
  567. }
  568. if ( GetOwner() != pPlayer )
  569. {
  570. if ( bDidWork && m_bPlayerControlled )
  571. {
  572. pPlayer->AwardAchievement( ACHIEVEMENT_TF_ENGINEER_HELP_MANUAL_SENTRY, 1 );
  573. }
  574. // keep track of who last hit us with a wrench for kill assists
  575. m_lastTeammateWrenchHit = pPlayer;
  576. m_lastTeammateWrenchHitTimer.Start();
  577. }
  578. return bDidWork;
  579. }
  580. //-----------------------------------------------------------------------------
  581. // Debug infos
  582. //-----------------------------------------------------------------------------
  583. int CObjectSentrygun::DrawDebugTextOverlays(void)
  584. {
  585. int text_offset = BaseClass::DrawDebugTextOverlays();
  586. if (m_debugOverlays & OVERLAY_TEXT_BIT)
  587. {
  588. char tempstr[512];
  589. Q_snprintf( tempstr, sizeof( tempstr ), "Level: %d", m_iUpgradeLevel.Get() );
  590. EntityText(text_offset,tempstr,0);
  591. text_offset++;
  592. Q_snprintf( tempstr, sizeof( tempstr ), "Shells: %d / %d", m_iAmmoShells.Get(), m_iMaxAmmoShells.Get() );
  593. EntityText(text_offset,tempstr,0);
  594. text_offset++;
  595. if ( m_iUpgradeLevel == 3 )
  596. {
  597. Q_snprintf( tempstr, sizeof( tempstr ), "Rockets: %d / %d", m_iAmmoRockets.Get(), m_iMaxAmmoRockets.Get() );
  598. EntityText(text_offset,tempstr,0);
  599. text_offset++;
  600. }
  601. Q_snprintf( tempstr, sizeof( tempstr ), "Upgrade metal %d", m_iUpgradeMetal.Get() );
  602. EntityText(text_offset,tempstr,0);
  603. text_offset++;
  604. Vector vecSrc = EyePosition();
  605. Vector forward;
  606. // m_vecCurAngles
  607. AngleVectors( m_vecCurAngles, &forward );
  608. NDebugOverlay::Line( vecSrc, vecSrc + forward * 200, 0, 255, 0, false, 0.1 );
  609. // m_vecGoalAngles
  610. AngleVectors( m_vecGoalAngles, &forward );
  611. NDebugOverlay::Line( vecSrc, vecSrc + forward * 200, 0, 0, 255, false, 0.1 );
  612. }
  613. return text_offset;
  614. }
  615. //-----------------------------------------------------------------------------
  616. // Returns the sentry targeting range the target is in
  617. //-----------------------------------------------------------------------------
  618. int CObjectSentrygun::Range( CBaseEntity *pTarget )
  619. {
  620. Vector vecOrg = EyePosition();
  621. Vector vecTargetOrg = pTarget->EyePosition();
  622. int iDist = ( vecTargetOrg - vecOrg ).Length();
  623. if (iDist < 132)
  624. return RANGE_MELEE;
  625. if (iDist < 550)
  626. return RANGE_NEAR;
  627. if (iDist < m_flSentryRange)
  628. return RANGE_MID;
  629. return RANGE_FAR;
  630. }
  631. //-----------------------------------------------------------------------------
  632. // Look for a target
  633. //-----------------------------------------------------------------------------
  634. bool CObjectSentrygun::FindTarget()
  635. {
  636. if ( m_bPlayerControlled )
  637. {
  638. m_flShieldFadeTime = gpGlobals->curtime + WRANGLER_DISABLE_TIME;
  639. }
  640. m_bPlayerControlled = false;
  641. // Disable the sentry guns for ifm.
  642. if ( tf_sentrygun_notarget.GetBool() )
  643. return false;
  644. if ( IsInCommentaryMode() )
  645. return false;
  646. // Sapper, etc.
  647. if ( IsDisabled() )
  648. return false;
  649. // Loop through players within SENTRY_MAX_RANGE units (sentry range).
  650. Vector vecSentryOrigin = EyePosition();
  651. // find the enemy team
  652. int iEnemyTeam = ( GetTeamNumber() == TF_TEAM_BLUE ) ? TF_TEAM_RED : TF_TEAM_BLUE;
  653. CTFTeam *pTeam = TFTeamMgr()->GetTeam( iEnemyTeam );
  654. if ( !pTeam )
  655. return false;
  656. // If we have an enemy get his minimum distance to check against.
  657. Vector vecSegment;
  658. Vector vecTargetCenter;
  659. float flMinDist2 = m_flSentryRange * m_flSentryRange;
  660. CBaseEntity *pTargetCurrent = NULL;
  661. CBaseEntity *pTargetOld = m_hEnemy.Get();
  662. float flOldTargetDist2 = FLT_MAX;
  663. bool bDummyTarget = false;
  664. // Sentry Decoy
  665. // If we already have a sentry decoy target, keep shooting at it
  666. // Otherwise look for a sentry decoy's first
  667. if ( pTargetCurrent == NULL )
  668. {
  669. CTFTargetDummy *pDummy = dynamic_cast<CTFTargetDummy*>( pTargetOld );
  670. if ( pDummy )
  671. {
  672. pTargetCurrent = pDummy;
  673. bDummyTarget = true;
  674. }
  675. else
  676. {
  677. // Search through all dummies and find one in range
  678. for ( int i = 0; i < ITFTargetDummy::AutoList().Count(); ++i )
  679. {
  680. pDummy = static_cast<CTFTargetDummy*>( ITFTargetDummy::AutoList()[i] );
  681. if ( InSameTeam( pDummy ) )
  682. continue;
  683. vecTargetCenter = pDummy->GetAbsOrigin();
  684. vecTargetCenter += pDummy->GetViewOffset();
  685. VectorSubtract( vecTargetCenter, vecSentryOrigin, vecSegment );
  686. float flDist2 = vecSegment.LengthSqr();
  687. // Check to see if the target is closer than the already validated target.
  688. if ( flDist2 > flMinDist2 )
  689. continue;
  690. // Ray trace!!!
  691. if ( FVisible( pDummy, MASK_SHOT | CONTENTS_GRATE ) )
  692. {
  693. pTargetCurrent = pDummy;
  694. bDummyTarget = true;
  695. }
  696. }
  697. }
  698. }
  699. // If our builder has an active laser pointer we don't seek targets.
  700. CTFPlayer* pBuilder = GetBuilder();
  701. if ( pBuilder )
  702. {
  703. // CTFLaserPointer* pPointer = static_cast<CTFLaserPointer*>( pBuilder->Weapon_OwnsThisID( TF_WEAPON_LASER_POINTER ) );
  704. // FIX ME: Temp fix until we find out why the pointer thinks its deployed after spawn
  705. CTFLaserPointer* pPointer = dynamic_cast<CTFLaserPointer*>( pBuilder->GetActiveWeapon() );
  706. if ( pPointer && pPointer->HasLaserDot() && !IsDisposableBuilding() )
  707. {
  708. m_bPlayerControlled = true;
  709. m_nShieldLevel.Set( SHIELD_NORMAL );
  710. m_flShieldFadeTime = gpGlobals->curtime + WRANGLER_DISABLE_TIME;
  711. // If not target dummy, use laserdot, otherwise targetdummy overrides
  712. if ( !bDummyTarget || !pTargetCurrent )
  713. {
  714. pTargetCurrent = pPointer->GetLaserDot();
  715. // Are we in our brief auto aim period?
  716. float flAutoAimTime = gpGlobals->curtime - m_flAutoAimStartTime;
  717. if ( m_hAutoAimTarget && (flAutoAimTime < 0.2f) )
  718. {
  719. // Only use the auto aim target if we can actually range to him.
  720. Vector vecSrc;
  721. QAngle vecAng;
  722. GetAttachment( GetFireAttachment(), vecSrc, vecAng );
  723. Vector vecEnemy = GetEnemyAimPosition( m_hAutoAimTarget );
  724. trace_t trace;
  725. CTraceFilterIgnoreTeammatesAndTeamObjects filter( pBuilder, COLLISION_GROUP_NONE, pBuilder->GetTeamNumber() );
  726. UTIL_TraceLine( vecSrc, vecEnemy, MASK_SOLID, &filter, &trace );
  727. if ( trace.m_pEnt == m_hAutoAimTarget )
  728. {
  729. pTargetCurrent = m_hAutoAimTarget;
  730. }
  731. else
  732. {
  733. m_hAutoAimTarget = NULL;
  734. }
  735. }
  736. else
  737. {
  738. m_hAutoAimTarget = NULL;
  739. }
  740. }
  741. if ( pTargetCurrent->GetAbsOrigin().DistTo( vecSentryOrigin ) > 30.f )
  742. {
  743. if ( pTargetCurrent != pTargetOld )
  744. {
  745. FoundTarget( pTargetCurrent, vecSentryOrigin, true );
  746. }
  747. return true;
  748. }
  749. else
  750. {
  751. pTargetCurrent = NULL;
  752. }
  753. }
  754. }
  755. // Don't auto track to targets while under the effects of the player shield.
  756. // The shield fades 3 seconds after we disengage from player control.
  757. if ( m_nShieldLevel == SHIELD_NORMAL )
  758. return false;
  759. // is there an active truce?
  760. bool bTruceActive = TFGameRules() && TFGameRules()->IsTruceActive();
  761. if ( ( pTargetCurrent == NULL ) && !bTruceActive )
  762. {
  763. // Sentries will try to target players first, then objects. However, if the enemy held was an object it will continue
  764. // to try and attack it first.
  765. int nTeamCount = pTeam->GetNumPlayers();
  766. for ( int iPlayer = 0; iPlayer < nTeamCount; ++iPlayer )
  767. {
  768. CTFPlayer *pTargetPlayer = static_cast<CTFPlayer*>( pTeam->GetPlayer( iPlayer ) );
  769. if ( pTargetPlayer == NULL )
  770. continue;
  771. // Make sure the player is alive.
  772. if ( !pTargetPlayer->IsAlive() )
  773. continue;
  774. if ( pTargetPlayer->GetFlags() & FL_NOTARGET )
  775. continue;
  776. vecTargetCenter = pTargetPlayer->GetAbsOrigin();
  777. vecTargetCenter += pTargetPlayer->GetViewOffset();
  778. VectorSubtract( vecTargetCenter, vecSentryOrigin, vecSegment );
  779. float flDist2 = vecSegment.LengthSqr();
  780. // Check to see if the target is closer than the already validated target.
  781. if ( flDist2 > flMinDist2 )
  782. continue;
  783. // It is closer, check to see if the target is valid.
  784. if ( ValidTargetPlayer( pTargetPlayer, vecSentryOrigin, vecTargetCenter ) )
  785. {
  786. flMinDist2 = flDist2;
  787. pTargetCurrent = pTargetPlayer;
  788. // Store the current target distance if we come across it
  789. if ( pTargetPlayer == pTargetOld )
  790. {
  791. flOldTargetDist2 = flDist2;
  792. }
  793. }
  794. }
  795. }
  796. // If we already have a target, don't check objects.
  797. if ( pTargetCurrent == NULL )
  798. {
  799. // target non-player bots
  800. CUtlVector< INextBot * > botVector;
  801. TheNextBots().CollectAllBots( &botVector );
  802. float closeBotRangeSq = m_flSentryRange * m_flSentryRange;
  803. for( int b=0; b<botVector.Count(); ++b )
  804. {
  805. CBaseCombatCharacter *bot = botVector[b]->GetEntity();
  806. Vector vecBotTarget = GetEnemyAimPosition( bot );
  807. float rangeSq = ( vecBotTarget - vecSentryOrigin ).LengthSqr();
  808. if ( rangeSq < closeBotRangeSq )
  809. {
  810. if ( ValidTargetBot( bot, vecSentryOrigin, vecBotTarget ) )
  811. {
  812. closeBotRangeSq = rangeSq;
  813. pTargetCurrent = bot;
  814. }
  815. }
  816. }
  817. if ( ( pTargetCurrent == NULL ) && !bTruceActive )
  818. {
  819. // target objects
  820. int nTeamObjectCount = pTeam->GetNumObjects();
  821. for ( int iObject = 0; iObject < nTeamObjectCount; ++iObject )
  822. {
  823. CBaseObject *pTargetObject = pTeam->GetObject( iObject );
  824. if ( !pTargetObject )
  825. continue;
  826. vecTargetCenter = pTargetObject->GetAbsOrigin();
  827. vecTargetCenter += pTargetObject->GetViewOffset();
  828. VectorSubtract( vecTargetCenter, vecSentryOrigin, vecSegment );
  829. float flDist2 = vecSegment.LengthSqr();
  830. // Store the current target distance if we come across it
  831. if ( pTargetObject == pTargetOld )
  832. {
  833. flOldTargetDist2 = flDist2;
  834. }
  835. // Check to see if the target is closer than the already validated target.
  836. if ( flDist2 > flMinDist2 )
  837. continue;
  838. // It is closer, check to see if the target is valid.
  839. if ( ValidTargetObject( pTargetObject, vecSentryOrigin, vecTargetCenter ) )
  840. {
  841. flMinDist2 = flDist2;
  842. pTargetCurrent = pTargetObject;
  843. }
  844. }
  845. }
  846. }
  847. // We have a target.
  848. if ( pTargetCurrent )
  849. {
  850. if ( pTargetCurrent != pTargetOld )
  851. {
  852. // Always target dummies
  853. // flMinDist2 is the new target's distance
  854. // flOldTargetDist2 is the old target's distance
  855. // Don't switch unless the new target is closer by some percentage
  856. if ( bDummyTarget || flMinDist2 < ( flOldTargetDist2 * 0.75f ) )
  857. {
  858. FoundTarget( pTargetCurrent, vecSentryOrigin );
  859. }
  860. }
  861. return true;
  862. }
  863. return false;
  864. }
  865. //-----------------------------------------------------------------------------
  866. // Purpose:
  867. //-----------------------------------------------------------------------------
  868. bool CObjectSentrygun::ValidTargetPlayer( CTFPlayer *pPlayer, const Vector &vecStart, const Vector &vecEnd )
  869. {
  870. // Keep shooting at spies that go invisible after we acquire them as a target.
  871. if ( pPlayer->m_Shared.GetPercentInvisible() > 0.5 )
  872. return false;
  873. // Keep shooting at spies that disguise after we acquire them as at a target.
  874. if ( pPlayer->m_Shared.InCond( TF_COND_DISGUISED ) && pPlayer->m_Shared.GetDisguiseTeam() == GetTeamNumber() && pPlayer != m_hEnemy )
  875. return false;
  876. // Don't shoot spys that are pretending to be a dispenser
  877. if ( pPlayer->m_Shared.InCond( TF_COND_DISGUISED_AS_DISPENSER ) )
  878. return false;
  879. // Don't target spies after they OnKill disguise with 'Your Eternal Reward'
  880. if ( ( pPlayer->m_Shared.InCond( TF_COND_DISGUISING ) || pPlayer->m_Shared.InCond( TF_COND_DISGUISED ) )
  881. && pPlayer->m_Shared.GetDisguiseTeam() == GetTeamNumber() )
  882. {
  883. CTFKnife *pKnife = dynamic_cast<CTFKnife *>( pPlayer->GetActiveTFWeapon() );
  884. if ( pKnife && pKnife->GetKnifeType() == KNIFE_DISGUISE_ONKILL )
  885. return false;
  886. }
  887. // Not across water boundary.
  888. if ( ( GetWaterLevel() == 0 && pPlayer->GetWaterLevel() >= 3 ) || ( GetWaterLevel() == 3 && pPlayer->GetWaterLevel() <= 0 ) )
  889. return false;
  890. // Ray trace!!!
  891. return FVisible( pPlayer, MASK_SHOT | CONTENTS_GRATE );
  892. }
  893. //-----------------------------------------------------------------------------
  894. // Purpose:
  895. //-----------------------------------------------------------------------------
  896. bool CObjectSentrygun::ValidTargetObject( CBaseObject *pObject, const Vector &vecStart, const Vector &vecEnd )
  897. {
  898. // Ignore objects being placed, they are not real objects yet.
  899. if ( pObject->IsPlacing() )
  900. return false;
  901. // Ignore sappers.
  902. if ( pObject->MustBeBuiltOnAttachmentPoint() )
  903. return false;
  904. // Not across water boundary.
  905. if ( ( GetWaterLevel() == 0 && pObject->GetWaterLevel() >= 3 ) || ( GetWaterLevel() == 3 && pObject->GetWaterLevel() <= 0 ) )
  906. return false;
  907. if ( pObject->GetObjectFlags() & OF_DOESNT_HAVE_A_MODEL )
  908. return false;
  909. // Ray trace.
  910. return FVisible( pObject, MASK_SHOT | CONTENTS_GRATE );
  911. }
  912. //-----------------------------------------------------------------------------
  913. // Purpose:
  914. //-----------------------------------------------------------------------------
  915. bool CObjectSentrygun::ValidTargetBot( CBaseCombatCharacter *pBot, const Vector &vecStart, const Vector &vecEnd )
  916. {
  917. // Already collected all of the players in FindTarget()
  918. if ( pBot->IsPlayer() )
  919. return false;
  920. // Don't want to shoot bots that are dead, on the same team, or aren't solid (they won't take damage anyway)
  921. if ( !pBot->IsAlive() || pBot->InSameTeam( this ) || pBot->IsSolidFlagSet( FSOLID_NOT_SOLID ) )
  922. return false;
  923. // Not across water boundary.
  924. if ( ( GetWaterLevel() == 0 && pBot->GetWaterLevel() >= 3 ) || ( GetWaterLevel() == 3 && pBot->GetWaterLevel() <= 0 ) )
  925. return false;
  926. if ( TFGameRules() && TFGameRules()->IsPlayingRobotDestructionMode() )
  927. {
  928. CTFRobotDestruction_Robot *pRobot = dynamic_cast< CTFRobotDestruction_Robot* >( pBot );
  929. if ( pRobot && pRobot->GetShieldedState() )
  930. return false;
  931. }
  932. // Ray trace.
  933. CBaseEntity *pBlocker;
  934. bool bVisible = FVisible( pBot, MASK_SHOT | CONTENTS_GRATE, &pBlocker );
  935. if ( bVisible )
  936. return true;
  937. // Also valid if it's parented to the blocker
  938. if ( pBlocker == pBot->GetParent() )
  939. return true;
  940. return false;
  941. }
  942. //-----------------------------------------------------------------------------
  943. // Found a Target
  944. //-----------------------------------------------------------------------------
  945. void CObjectSentrygun::FoundTarget( CBaseEntity *pTarget, const Vector &vecSoundCenter, bool bNoSound )
  946. {
  947. m_hEnemy = pTarget;
  948. if ( ( m_iAmmoShells > 0 ) || ( m_iAmmoRockets > 0 && m_iUpgradeLevel == 3 ) )
  949. {
  950. // Play one sound to everyone but the target.
  951. CPASFilter filter( vecSoundCenter );
  952. if ( pTarget->IsPlayer() )
  953. {
  954. CTFPlayer *pPlayer = ToTFPlayer( pTarget );
  955. // Play a specific sound just to the target and remove it from the general recipient list.
  956. if ( !bNoSound )
  957. {
  958. CSingleUserRecipientFilter singleFilter( pPlayer );
  959. EmitSentrySound( singleFilter, entindex(), "Building_Sentrygun.AlertTarget" );
  960. filter.RemoveRecipient( pPlayer );
  961. // if the target is a bot, alert it
  962. CTFBot *bot = ToTFBot( pPlayer );
  963. if ( bot )
  964. {
  965. bot->GetVisionInterface()->AddKnownEntity( this );
  966. bot->RememberEnemySentry( this, bot->GetAbsOrigin() );
  967. }
  968. }
  969. }
  970. if ( !bNoSound )
  971. {
  972. EmitSentrySound( filter, entindex(), "Building_Sentrygun.Alert" );
  973. }
  974. }
  975. // Update timers, we are attacking now!
  976. m_iState.Set( SENTRY_STATE_ATTACKING );
  977. m_flNextAttack = gpGlobals->curtime + SENTRY_THINK_DELAY;
  978. if ( m_flNextRocketAttack < gpGlobals->curtime )
  979. {
  980. m_flNextRocketAttack = gpGlobals->curtime;// + 0.5;
  981. }
  982. }
  983. //-----------------------------------------------------------------------------
  984. // FInViewCone - returns true is the passed ent is in
  985. // the caller's forward view cone. The dot product is performed
  986. // in 2d, making the view cone infinitely tall.
  987. //-----------------------------------------------------------------------------
  988. bool CObjectSentrygun::FInViewCone ( CBaseEntity *pEntity )
  989. {
  990. Vector forward;
  991. AngleVectors( m_vecCurAngles, &forward );
  992. Vector2D vec2LOS = ( pEntity->GetAbsOrigin() - GetAbsOrigin() ).AsVector2D();
  993. vec2LOS.NormalizeInPlace();
  994. float flDot = vec2LOS.Dot( forward.AsVector2D() );
  995. if ( flDot > m_flFieldOfView )
  996. {
  997. return true;
  998. }
  999. else
  1000. {
  1001. return false;
  1002. }
  1003. }
  1004. //-----------------------------------------------------------------------------
  1005. // Make sure our target is still valid, and if so, fire at it
  1006. //-----------------------------------------------------------------------------
  1007. void CObjectSentrygun::Attack()
  1008. {
  1009. StudioFrameAdvance( );
  1010. if ( IsUsingReverseBuild() || !FindTarget() )
  1011. {
  1012. m_iState.Set( SENTRY_STATE_SEARCHING );
  1013. m_hEnemy = NULL;
  1014. return;
  1015. }
  1016. // Track enemy
  1017. Vector vecMid = EyePosition();
  1018. Vector vecMidEnemy = GetEnemyAimPosition( m_hEnemy );
  1019. Vector vecDirToEnemy = vecMidEnemy - vecMid;
  1020. QAngle angToTarget;
  1021. VectorAngles( vecDirToEnemy, angToTarget );
  1022. angToTarget.y = UTIL_AngleMod( angToTarget.y );
  1023. if (angToTarget.x < -180)
  1024. angToTarget.x += 360;
  1025. if (angToTarget.x > 180)
  1026. angToTarget.x -= 360;
  1027. // now all numbers should be in [1...360]
  1028. // pin to turret limitations to [-50...50]
  1029. if (angToTarget.x > 50)
  1030. angToTarget.x = 50;
  1031. else if (angToTarget.x < -50)
  1032. angToTarget.x = -50;
  1033. m_vecGoalAngles.y = angToTarget.y;
  1034. m_vecGoalAngles.x = angToTarget.x;
  1035. MoveTurret();
  1036. // Fire on the target if it's within 10 units of being aimed right at it
  1037. if ( m_flNextAttack <= gpGlobals->curtime && (m_vecGoalAngles - m_vecCurAngles).Length() <= 10 )
  1038. {
  1039. if ( !m_bPlayerControlled || m_bFireNextFrame )
  1040. {
  1041. m_bFireNextFrame = false;
  1042. Fire();
  1043. }
  1044. m_flFireRate = 1.f;
  1045. CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( GetOwner(), m_flFireRate, mult_sentry_firerate );
  1046. if ( m_bPlayerControlled )
  1047. {
  1048. m_flFireRate *= 0.5f;
  1049. }
  1050. if ( IsMiniBuilding() && !IsDisposableBuilding() )
  1051. {
  1052. m_flFireRate *= 0.75f;
  1053. }
  1054. if ( GetBuilder() && GetBuilder()->m_Shared.InCond( TF_COND_CRITBOOSTED_USER_BUFF ) )
  1055. {
  1056. m_flFireRate *= 0.4f;
  1057. }
  1058. if ( m_iUpgradeLevel == 1 )
  1059. {
  1060. // Level 1 sentries fire slower
  1061. m_flNextAttack = gpGlobals->curtime + (0.2*m_flFireRate);
  1062. }
  1063. else
  1064. {
  1065. m_flNextAttack = gpGlobals->curtime + (0.1*m_flFireRate);
  1066. }
  1067. }
  1068. else
  1069. {
  1070. // SetSentryAnim( TFTURRET_ANIM_SPIN );
  1071. }
  1072. if ( m_bPlayerControlled && m_bFireRocketNextFrame )
  1073. {
  1074. m_bFireRocketNextFrame = false;
  1075. FireRocket();
  1076. }
  1077. }
  1078. //-----------------------------------------------------------------------------
  1079. //
  1080. //-----------------------------------------------------------------------------
  1081. bool CObjectSentrygun::FireRocket()
  1082. {
  1083. if ( m_flNextRocketAttack >= gpGlobals->curtime || m_iAmmoRockets <= 0 )
  1084. return false;
  1085. if ( m_hEnemy.Get() == NULL )
  1086. return false;
  1087. Vector vecAimDir;
  1088. Vector vecSrc;
  1089. QAngle vecAng;
  1090. GetAttachment( m_iAttachments[SENTRYGUN_ATTACHMENT_ROCKET], vecSrc, vecAng );
  1091. Vector vecEnemyPos = GetEnemyAimPosition( m_hEnemy );
  1092. vecAimDir = vecEnemyPos - vecSrc;
  1093. vecAimDir.NormalizeInPlace();
  1094. // If we cannot see their WorldSpaceCenter ( possible, as we do our target finding based
  1095. // on the eye position of the target ) then fire at the eye position
  1096. trace_t tr;
  1097. CTraceFilterSimple traceFilter( this, COLLISION_GROUP_NONE );
  1098. ITraceFilter *pFilterChain = NULL;
  1099. CTraceFilterIgnoreFriendlyCombatItems traceFilterCombatItem( this, COLLISION_GROUP_NONE, GetTeamNumber() );
  1100. if ( TFGameRules() && TFGameRules()->GameModeUsesUpgrades() )
  1101. {
  1102. // Ignore teammates and their (physical) upgrade items in MvM
  1103. pFilterChain = &traceFilterCombatItem;
  1104. }
  1105. CTraceFilterChain traceFilterChain( &traceFilter, pFilterChain );
  1106. UTIL_TraceLine( vecSrc, vecEnemyPos, MASK_SOLID, &traceFilterChain, &tr);
  1107. if ( m_bPlayerControlled || (tr.m_pEnt && !tr.m_pEnt->IsWorld()) )
  1108. {
  1109. // NOTE: vecAng is not actually set by GetAttachment!!!
  1110. QAngle angDir;
  1111. VectorAngles( vecAimDir, angDir );
  1112. EmitSentrySound( "Building_Sentrygun.FireRocket" );
  1113. QAngle angAimDir;
  1114. VectorAngles( vecAimDir, angAimDir );
  1115. CTFProjectile_SentryRocket *pProjectile = CTFProjectile_SentryRocket::Create( vecSrc, angAimDir, this, GetBuilder() );
  1116. if ( pProjectile )
  1117. {
  1118. int iDamage = 100;
  1119. CALL_ATTRIB_HOOK_INT_ON_OTHER( GetOwner(), iDamage, mult_engy_sentry_damage );
  1120. pProjectile->SetDamage( iDamage );
  1121. }
  1122. // Setup next rocket shot
  1123. if ( m_bPlayerControlled )
  1124. {
  1125. m_flNextRocketAttack = gpGlobals->curtime + 2.25;
  1126. }
  1127. else
  1128. {
  1129. AddGesture( ACT_RANGE_ATTACK2 );
  1130. m_flNextRocketAttack = gpGlobals->curtime + 3;
  1131. }
  1132. if ( !tf_sentrygun_ammocheat.GetBool() && !HasSpawnFlags( SF_SENTRY_INFINITE_AMMO ) )
  1133. {
  1134. m_iAmmoRockets--;
  1135. }
  1136. }
  1137. m_timeSinceLastFired.Start();
  1138. return true;
  1139. }
  1140. //-----------------------------------------------------------------------------
  1141. //
  1142. //-----------------------------------------------------------------------------
  1143. int CObjectSentrygun::GetFireAttachment()
  1144. {
  1145. int iAttachment;
  1146. if ( m_iUpgradeLevel > 1 && m_iLastMuzzleAttachmentFired == m_iAttachments[SENTRYGUN_ATTACHMENT_MUZZLE] )
  1147. {
  1148. // level 2 and 3 turrets alternate muzzles each time they fizzy fizzy fire.
  1149. iAttachment = m_iAttachments[SENTRYGUN_ATTACHMENT_MUZZLE_ALT];
  1150. }
  1151. else
  1152. {
  1153. iAttachment = m_iAttachments[SENTRYGUN_ATTACHMENT_MUZZLE];
  1154. }
  1155. m_iLastMuzzleAttachmentFired = iAttachment;
  1156. return iAttachment;
  1157. }
  1158. //-----------------------------------------------------------------------------
  1159. //
  1160. //-----------------------------------------------------------------------------
  1161. void CObjectSentrygun::OnKilledEnemy(CBasePlayer* pVictim)
  1162. {
  1163. if ( !pVictim )
  1164. return;
  1165. CTFPlayer *pOwner = GetOwner();
  1166. if ( !pOwner )
  1167. return;
  1168. if ( m_bPlayerControlled && pVictim->GetAbsOrigin().DistToSqr( GetAbsOrigin() ) > ( m_flSentryRange * m_flSentryRange ) )
  1169. {
  1170. pOwner->AwardAchievement( ACHIEVEMENT_TF_ENGINEER_MANUAL_SENTRY_KILLS_BEYOND_RANGE );
  1171. }
  1172. CTFPlayer *pCTFVictim = static_cast<CTFPlayer *>( pVictim );
  1173. if ( pCTFVictim->GetControlPointStandingOn() != NULL )
  1174. {
  1175. pOwner->AwardAchievement( ACHIEVEMENT_TF_ENGINEER_SENTRY_KILL_CAPS, 1 );
  1176. }
  1177. if ( (gpGlobals->curtime - GetCarryDeployTime() < tf_sentrygun_kill_after_redeploy_time_achievement.GetInt()) &&
  1178. GetUpgradeLevel() == 3 )
  1179. {
  1180. pOwner->AwardAchievement( ACHIEVEMENT_TF_ENGINEER_MOVE_SENTRY_GET_KILL );
  1181. }
  1182. }
  1183. //-----------------------------------------------------------------------------
  1184. // Fire on our target
  1185. //-----------------------------------------------------------------------------
  1186. bool CObjectSentrygun::Fire()
  1187. {
  1188. //NDebugOverlay::Cross3D( m_hEnemy->WorldSpaceCenter(), 10, 255, 0, 0, false, 0.1 );
  1189. Vector vecAimDir;
  1190. // Level 3 Turrets fire rockets every 3 seconds
  1191. if ( m_iUpgradeLevel == 3 &&
  1192. m_iAmmoRockets > 0 &&
  1193. m_flNextRocketAttack < gpGlobals->curtime &&
  1194. !m_bPlayerControlled )
  1195. {
  1196. FireRocket();
  1197. }
  1198. // All turrets fire shells
  1199. if ( m_iAmmoShells > 0 )
  1200. {
  1201. if ( !IsPlayingGesture( ACT_RANGE_ATTACK1 ) )
  1202. {
  1203. RemoveGesture( ACT_RANGE_ATTACK1_LOW );
  1204. AddGesture( ACT_RANGE_ATTACK1 );
  1205. }
  1206. if ( m_hEnemy.Get() == NULL )
  1207. return false;
  1208. Vector vecSrc;
  1209. QAngle vecAng;
  1210. int iAttachment = GetFireAttachment();
  1211. GetAttachment( iAttachment, vecSrc, vecAng );
  1212. Vector vecMidEnemy = GetEnemyAimPosition( m_hEnemy );
  1213. // If we cannot see their WorldSpaceCenter ( possible, as we do our target finding based
  1214. // on the eye position of the target ) then fire at the eye position
  1215. trace_t tr;
  1216. CTraceFilterSimple traceFilter( this, COLLISION_GROUP_NONE );
  1217. ITraceFilter *pFilterChain = NULL;
  1218. CTraceFilterIgnoreFriendlyCombatItems traceFilterCombatItem( this, COLLISION_GROUP_NONE, GetTeamNumber() );
  1219. if ( TFGameRules() && TFGameRules()->GameModeUsesUpgrades() )
  1220. {
  1221. // Ignore teammates and their (physical) upgrade items in MvM
  1222. pFilterChain = &traceFilterCombatItem;
  1223. }
  1224. CTraceFilterChain traceFilterChain( &traceFilter, pFilterChain );
  1225. UTIL_TraceLine( vecSrc, vecMidEnemy, MASK_SOLID, &traceFilterChain, &tr);
  1226. if ( !tr.m_pEnt || tr.m_pEnt->IsWorld() )
  1227. {
  1228. // Hack it lower a little bit..
  1229. // The eye position is not always within the hitboxes for a standing TF Player
  1230. vecMidEnemy = m_hEnemy->EyePosition() + Vector(0,0,-5);
  1231. }
  1232. vecAimDir = vecMidEnemy - vecSrc;
  1233. float flDistToTarget = vecAimDir.Length();
  1234. vecAimDir.NormalizeInPlace();
  1235. //NDebugOverlay::Cross3D( vecSrc, 10, 255, 0, 0, false, 0.1 );
  1236. FireBulletsInfo_t info;
  1237. info.m_vecSrc = vecSrc;
  1238. info.m_vecDirShooting = vecAimDir;
  1239. info.m_iTracerFreq = 1;
  1240. info.m_iShots = 1;
  1241. info.m_pAttacker = GetBuilder();
  1242. if ( info.m_pAttacker == NULL )
  1243. {
  1244. info.m_pAttacker = this;
  1245. }
  1246. if ( m_bPlayerControlled )
  1247. {
  1248. info.m_vecSpread = VECTOR_CONE_3DEGREES;
  1249. }
  1250. else
  1251. {
  1252. info.m_vecSpread = vec3_origin;
  1253. }
  1254. info.m_flDistance = flDistToTarget + 100;
  1255. info.m_iAmmoType = m_iAmmoType;
  1256. if ( IsMiniBuilding() )
  1257. {
  1258. info.m_flDamage = tf_sentrygun_mini_damage.GetFloat();
  1259. info.m_flDamageForceScale = 0.0f;
  1260. }
  1261. else
  1262. {
  1263. info.m_flDamage = tf_sentrygun_damage.GetFloat();
  1264. }
  1265. CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( GetOwner(), info.m_flDamage, mult_engy_sentry_damage );
  1266. FireBullets( info );
  1267. // sentry gun fire 'heats up' the nav mesh around it
  1268. UpdateNavMeshCombatStatus();
  1269. //NDebugOverlay::Line( vecSrc, vecSrc + vecAimDir * 1000, 255, 0, 0, false, 0.1 );
  1270. CEffectData data;
  1271. data.m_nEntIndex = entindex();
  1272. data.m_nAttachmentIndex = iAttachment;
  1273. data.m_fFlags = m_iUpgradeLevel;
  1274. data.m_vOrigin = vecSrc;
  1275. DispatchEffect( "TF_3rdPersonMuzzleFlash_SentryGun", data );
  1276. if ( IsMiniBuilding() )
  1277. {
  1278. EmitSound_t params;
  1279. params.m_pSoundName = "Building_MiniSentrygun.Fire";
  1280. params.m_flSoundTime = 0;
  1281. params.m_pflSoundDuration = 0;
  1282. params.m_bWarnOnDirectWaveReference = true;
  1283. CPASAttenuationFilter filter( this, "Building_MiniSentrygun.Fire" );
  1284. EmitSound( filter, entindex(), params );
  1285. }
  1286. else
  1287. {
  1288. if ( !m_bPlayerControlled )
  1289. {
  1290. switch( m_iUpgradeLevel )
  1291. {
  1292. case 1:
  1293. default:
  1294. EmitSentrySound( "Building_Sentrygun.Fire" );
  1295. break;
  1296. case 2:
  1297. EmitSentrySound( "Building_Sentrygun.Fire2" );
  1298. break;
  1299. case 3:
  1300. EmitSentrySound( "Building_Sentrygun.Fire3" );
  1301. break;
  1302. }
  1303. }
  1304. else
  1305. {
  1306. switch ( m_iUpgradeLevel )
  1307. {
  1308. case 1:
  1309. EmitSentrySound( "Building_Sentrygun.ShaftFire" );
  1310. break;
  1311. case 2:
  1312. EmitSentrySound( "Building_Sentrygun.ShaftFire2" );
  1313. break;
  1314. case 3:
  1315. EmitSentrySound( "Building_Sentrygun.ShaftFire3" );
  1316. break;
  1317. }
  1318. }
  1319. }
  1320. if ( !tf_sentrygun_ammocheat.GetBool() && !HasSpawnFlags( SF_SENTRY_INFINITE_AMMO ) )
  1321. {
  1322. m_iAmmoShells--;
  1323. }
  1324. }
  1325. else
  1326. {
  1327. if ( m_iUpgradeLevel > 1 )
  1328. {
  1329. if ( !IsPlayingGesture( ACT_RANGE_ATTACK1_LOW ) )
  1330. {
  1331. RemoveGesture( ACT_RANGE_ATTACK1 );
  1332. AddGesture( ACT_RANGE_ATTACK1_LOW );
  1333. }
  1334. }
  1335. // Out of ammo, play a click
  1336. EmitSound( "Building_Sentrygun.Empty" );
  1337. // Disposable sentries blow up when their ammo runs out
  1338. if ( IsDisposableBuilding() )
  1339. {
  1340. DetonateObject();
  1341. }
  1342. m_flNextAttack = gpGlobals->curtime + 0.2;
  1343. }
  1344. // note when we last fired at en enemy (or tried to)
  1345. m_timeSinceLastFired.Start();
  1346. return true;
  1347. }
  1348. //-----------------------------------------------------------------------------
  1349. // Purpose:
  1350. //-----------------------------------------------------------------------------
  1351. void CObjectSentrygun::ModifyFireBulletsDamage( CTakeDamageInfo* dmgInfo )
  1352. {
  1353. if ( m_bPlayerControlled && dmgInfo )
  1354. {
  1355. dmgInfo->SetDamageCustom( TF_DMG_CUSTOM_PLAYER_SENTRY );
  1356. }
  1357. }
  1358. //-----------------------------------------------------------------------------
  1359. // Purpose:
  1360. //-----------------------------------------------------------------------------
  1361. float CObjectSentrygun::GetPushMultiplier()
  1362. {
  1363. if ( IsMiniBuilding() )
  1364. return 8.f;
  1365. else
  1366. return 16.f;
  1367. }
  1368. //-----------------------------------------------------------------------------
  1369. // Purpose:
  1370. //-----------------------------------------------------------------------------
  1371. void CObjectSentrygun::MakeTracer( const Vector &vecTracerSrc, const trace_t &tr, int iTracerType )
  1372. {
  1373. trace_t tmptrace;
  1374. tmptrace.endpos = tr.endpos + RandomVector(-10,10);
  1375. // Sentryguns are perfectly accurate, but this doesn't look good for tracers.
  1376. // Add a little noise to them, but not enough so that it looks like they're missing.
  1377. BaseClass::MakeTracer( vecTracerSrc, tmptrace, iTracerType );
  1378. }
  1379. //-----------------------------------------------------------------------------
  1380. // Purpose: MakeTracer asks back for the attachment index
  1381. //-----------------------------------------------------------------------------
  1382. int CObjectSentrygun::GetTracerAttachment( void )
  1383. {
  1384. return m_iAttachments[SENTRYGUN_ATTACHMENT_MUZZLE];
  1385. }
  1386. //-----------------------------------------------------------------------------
  1387. // Rotate and scan for targets
  1388. //-----------------------------------------------------------------------------
  1389. void CObjectSentrygun::SentryRotate( void )
  1390. {
  1391. if ( GetReversesBuildingConstructionSpeed() )
  1392. {
  1393. m_iState.Set( SENTRY_STATE_INACTIVE );
  1394. return;
  1395. }
  1396. // if we're playing a fire gesture, stop it
  1397. if ( IsPlayingGesture( ACT_RANGE_ATTACK1 ) )
  1398. {
  1399. RemoveGesture( ACT_RANGE_ATTACK1 );
  1400. }
  1401. if ( IsPlayingGesture( ACT_RANGE_ATTACK1_LOW ) )
  1402. {
  1403. RemoveGesture( ACT_RANGE_ATTACK1_LOW );
  1404. }
  1405. // animate
  1406. StudioFrameAdvance();
  1407. // Look for a target
  1408. if ( FindTarget() )
  1409. return;
  1410. // Rotate
  1411. if ( !MoveTurret() )
  1412. {
  1413. // Change direction
  1414. if ( IsDisabled() || m_nShieldLevel == SHIELD_NORMAL )
  1415. {
  1416. EmitSound( "Building_Sentrygun.Disabled" );
  1417. m_vecGoalAngles.x = 30;
  1418. }
  1419. else
  1420. {
  1421. switch( m_iUpgradeLevel )
  1422. {
  1423. case 1:
  1424. default:
  1425. EmitSentrySound( "Building_Sentrygun.Idle" );
  1426. break;
  1427. case 2:
  1428. EmitSound( "Building_Sentrygun.Idle2" );
  1429. break;
  1430. case 3:
  1431. EmitSound( "Building_Sentrygun.Idle3" );
  1432. break;
  1433. }
  1434. // Switch rotation direction
  1435. if ( m_bTurningRight )
  1436. {
  1437. m_bTurningRight = false;
  1438. m_vecGoalAngles.y = m_iLeftBound;
  1439. }
  1440. else
  1441. {
  1442. m_bTurningRight = true;
  1443. m_vecGoalAngles.y = m_iRightBound;
  1444. }
  1445. // Randomly look up and down a bit
  1446. if (random->RandomFloat(0, 1) < 0.3)
  1447. {
  1448. m_vecGoalAngles.x = (int)random->RandomFloat(-10,10);
  1449. }
  1450. }
  1451. }
  1452. }
  1453. //-----------------------------------------------------------------------------
  1454. // Purpose: Add the EMP effect
  1455. //-----------------------------------------------------------------------------
  1456. void CObjectSentrygun::OnStartDisabled( void )
  1457. {
  1458. // stay at current rotation, angle down
  1459. m_vecGoalAngles.x = m_vecCurAngles.x;
  1460. m_vecGoalAngles.y = m_vecCurAngles.y;
  1461. // target = nULL
  1462. BaseClass::OnStartDisabled();
  1463. }
  1464. //-----------------------------------------------------------------------------
  1465. // Purpose: Remove the EMP effect
  1466. //-----------------------------------------------------------------------------
  1467. void CObjectSentrygun::OnEndDisabled( void )
  1468. {
  1469. // return to normal rotations
  1470. if ( m_bTurningRight )
  1471. {
  1472. m_bTurningRight = false;
  1473. m_vecGoalAngles.y = m_iLeftBound;
  1474. }
  1475. else
  1476. {
  1477. m_bTurningRight = true;
  1478. m_vecGoalAngles.y = m_iRightBound;
  1479. }
  1480. m_vecGoalAngles.x = 0;
  1481. BaseClass::OnEndDisabled();
  1482. }
  1483. //-----------------------------------------------------------------------------
  1484. // Purpose:
  1485. //-----------------------------------------------------------------------------
  1486. int CObjectSentrygun::GetBaseTurnRate( void )
  1487. {
  1488. if ( m_bPlayerControlled )
  1489. {
  1490. return m_iBaseTurnRate * 100;
  1491. }
  1492. else
  1493. {
  1494. return m_iBaseTurnRate;
  1495. }
  1496. }
  1497. //-----------------------------------------------------------------------------
  1498. //
  1499. //-----------------------------------------------------------------------------
  1500. bool CObjectSentrygun::MoveTurret( void )
  1501. {
  1502. bool bMoved = false;
  1503. int iBaseTurnRate = GetBaseTurnRate();
  1504. if ( IsMiniBuilding() )
  1505. {
  1506. iBaseTurnRate *= 1.35f;
  1507. }
  1508. // any x movement?
  1509. if ( m_vecCurAngles.x != m_vecGoalAngles.x )
  1510. {
  1511. float flDir = m_vecGoalAngles.x > m_vecCurAngles.x ? 1 : -1 ;
  1512. m_vecCurAngles.x += SENTRY_THINK_DELAY * ( iBaseTurnRate * 5 ) * flDir;
  1513. // if we started below the goal, and now we're past, peg to goal
  1514. if ( flDir == 1 )
  1515. {
  1516. if (m_vecCurAngles.x > m_vecGoalAngles.x)
  1517. m_vecCurAngles.x = m_vecGoalAngles.x;
  1518. }
  1519. else
  1520. {
  1521. if (m_vecCurAngles.x < m_vecGoalAngles.x)
  1522. m_vecCurAngles.x = m_vecGoalAngles.x;
  1523. }
  1524. SetPoseParameter( m_iPitchPoseParameter, -m_vecCurAngles.x );
  1525. bMoved = true;
  1526. }
  1527. if ( m_vecCurAngles.y != m_vecGoalAngles.y )
  1528. {
  1529. float flDir = m_vecGoalAngles.y > m_vecCurAngles.y ? 1 : -1 ;
  1530. float flDist = fabs( m_vecGoalAngles.y - m_vecCurAngles.y );
  1531. bool bReversed = false;
  1532. if ( flDist > 180 )
  1533. {
  1534. flDist = 360 - flDist;
  1535. flDir = -flDir;
  1536. bReversed = true;
  1537. }
  1538. if ( m_hEnemy.Get() == NULL )
  1539. {
  1540. if ( flDist > 30 )
  1541. {
  1542. if ( m_flTurnRate < iBaseTurnRate * 10 )
  1543. {
  1544. m_flTurnRate += iBaseTurnRate;
  1545. }
  1546. }
  1547. else
  1548. {
  1549. // Slow down
  1550. if ( m_flTurnRate > (iBaseTurnRate * 5) )
  1551. m_flTurnRate -= iBaseTurnRate;
  1552. }
  1553. }
  1554. else
  1555. {
  1556. // When tracking enemies, move faster and don't slow
  1557. if ( flDist > 30 )
  1558. {
  1559. if (m_flTurnRate < iBaseTurnRate * 30)
  1560. {
  1561. m_flTurnRate += iBaseTurnRate * 3;
  1562. }
  1563. }
  1564. }
  1565. m_vecCurAngles.y += SENTRY_THINK_DELAY * m_flTurnRate * flDir;
  1566. // if we passed over the goal, peg right to it now
  1567. if (flDir == -1)
  1568. {
  1569. if ( (bReversed == false && m_vecGoalAngles.y > m_vecCurAngles.y) ||
  1570. (bReversed == true && m_vecGoalAngles.y < m_vecCurAngles.y) )
  1571. {
  1572. m_vecCurAngles.y = m_vecGoalAngles.y;
  1573. }
  1574. }
  1575. else
  1576. {
  1577. if ( (bReversed == false && m_vecGoalAngles.y < m_vecCurAngles.y) ||
  1578. (bReversed == true && m_vecGoalAngles.y > m_vecCurAngles.y) )
  1579. {
  1580. m_vecCurAngles.y = m_vecGoalAngles.y;
  1581. }
  1582. }
  1583. if ( m_vecCurAngles.y < 0 )
  1584. {
  1585. m_vecCurAngles.y += 360;
  1586. }
  1587. else if ( m_vecCurAngles.y >= 360 )
  1588. {
  1589. m_vecCurAngles.y -= 360;
  1590. }
  1591. if ( flDist < ( SENTRY_THINK_DELAY * 0.5 * iBaseTurnRate ) )
  1592. {
  1593. m_vecCurAngles.y = m_vecGoalAngles.y;
  1594. }
  1595. QAngle angles = GetAbsAngles();
  1596. float flYaw = m_vecCurAngles.y - angles.y;
  1597. SetPoseParameter( m_iYawPoseParameter, -flYaw );
  1598. InvalidatePhysicsRecursive( ANIMATION_CHANGED );
  1599. bMoved = true;
  1600. }
  1601. if ( !bMoved || m_flTurnRate <= 0 )
  1602. {
  1603. m_flTurnRate = iBaseTurnRate * 5;
  1604. }
  1605. return bMoved;
  1606. }
  1607. //-----------------------------------------------------------------------------
  1608. // Purpose: Note our last attacked time
  1609. //-----------------------------------------------------------------------------
  1610. int CObjectSentrygun::OnTakeDamage( const CTakeDamageInfo &info )
  1611. {
  1612. CTakeDamageInfo newInfo = info;
  1613. // As we increase in level, we get more resistant to minigun bullets, to compensate for
  1614. // our increased surface area taking more minigun hits.
  1615. if ( ( info.GetDamageType() & DMG_BULLET ) && ( info.GetDamageCustom() == TF_DMG_CUSTOM_MINIGUN ) )
  1616. {
  1617. float flDamage = newInfo.GetDamage();
  1618. flDamage *= ( 1.0 - m_flHeavyBulletResist );
  1619. newInfo.SetDamage( flDamage );
  1620. }
  1621. // If we are shielded due to player control, we take less damage.
  1622. bool bFullyShielded = ( m_nShieldLevel > 0 ) && !HasSapper() && !IsPlasmaDisabled();
  1623. if ( bFullyShielded )
  1624. {
  1625. float flDamage = newInfo.GetDamage();
  1626. flDamage *= ( m_nShieldLevel == SHIELD_NORMAL ) ? SHIELD_NORMAL_VALUE : SHIELD_MAX_VALUE;
  1627. newInfo.SetDamage( flDamage );
  1628. }
  1629. // Check to see if we are being sapped.
  1630. if ( HasSapper() )
  1631. {
  1632. // Get the sapper owner.
  1633. CBaseObject *pSapper = GetObjectOfTypeOnMe( OBJ_ATTACHMENT_SAPPER );
  1634. // Take less damage if the owner is causing additional damage.
  1635. if ( pSapper && ( info.GetAttacker() == pSapper->GetOwner() ) )
  1636. {
  1637. float flDamage = newInfo.GetDamage() * SENTRYGUN_SAPPER_OWNER_DAMAGE_MODIFIER;
  1638. newInfo.SetDamage( flDamage );
  1639. }
  1640. }
  1641. int iDamageTaken = BaseClass::OnTakeDamage( newInfo );
  1642. if ( iDamageTaken > 0 )
  1643. {
  1644. m_flLastAttackedTime = gpGlobals->curtime;
  1645. // check for achievement
  1646. if ( bFullyShielded )
  1647. {
  1648. int iPrevLifetimeShieldedDamage = m_iLifetimeShieldedDamage;
  1649. m_iLifetimeShieldedDamage += iDamageTaken;
  1650. const int kMaxDamageForAchievement = tf_sentrygun_max_absorbed_damage_while_controlled_for_achievement.GetInt();
  1651. if ( iPrevLifetimeShieldedDamage <= kMaxDamageForAchievement && m_iLifetimeShieldedDamage > kMaxDamageForAchievement )
  1652. {
  1653. CTFPlayer *pOwner = ToTFPlayer( GetOwner() );
  1654. if ( pOwner && pOwner->IsPlayerClass( TF_CLASS_ENGINEER ) )
  1655. {
  1656. pOwner->AwardAchievement( ACHIEVEMENT_TF_ENGINEER_MANUAL_SENTRY_ABSORB_DMG );
  1657. }
  1658. }
  1659. }
  1660. }
  1661. return iDamageTaken;
  1662. }
  1663. //-----------------------------------------------------------------------------
  1664. // Purpose: Called when this object is destroyed
  1665. //-----------------------------------------------------------------------------
  1666. void CObjectSentrygun::Killed( const CTakeDamageInfo &info )
  1667. {
  1668. CTFPlayer *pTFKiller = ToTFPlayer( info.GetAttacker() );
  1669. if ( pTFKiller && pTFKiller->IsPlayerClass( TF_CLASS_SOLDIER ) )
  1670. {
  1671. if ( pTFKiller->GetAbsOrigin().DistTo( GetAbsOrigin() ) > SENTRY_MAX_RANGE )
  1672. {
  1673. pTFKiller->AwardAchievement( ACHIEVEMENT_TF_SOLDIER_DESTROY_SENTRY_OUT_OF_RANGE );
  1674. }
  1675. //If we are in the corridor map, then we check for the achievement for it.
  1676. else if ( m_hEnemy && !( pTFKiller->GetFlags() & FL_ONGROUND ) )
  1677. {
  1678. CBaseEntity *pDamager = GetBuilder();
  1679. if ( NULL == pDamager )
  1680. {
  1681. pDamager = this;
  1682. }
  1683. static const float DAMAGE_INTERVAL = 2.0f;
  1684. if ( pTFKiller->m_AchievementData.IsDamagerInHistory( pDamager, DAMAGE_INTERVAL ) )
  1685. {
  1686. //Check the map.
  1687. if ( 0 == Q_stricmp( "tra_sol_corridor", STRING( gpGlobals->mapname ) ) )
  1688. {
  1689. #ifdef TF_SOLDIER_TRAINING_ACHIEVEMENTS
  1690. //If the attacker was in the air when this sentry died, give him an achievement.
  1691. pTFKiller->AwardAchievement( ACHIEVEMENT_TF_SOLDIER_TRAINING_COR_SENTRY_FROM_AIR );
  1692. #endif // TF_SOLDIER_TRAINING_ACHIEVEMENTS
  1693. }
  1694. }
  1695. }
  1696. }
  1697. // Tell our owner's shotgun the sentry died.
  1698. CTFPlayer *pOwner = ToTFPlayer( GetOwner() );
  1699. if ( pOwner )
  1700. {
  1701. CTFShotgun_Revenge* pShotgun = dynamic_cast<CTFShotgun_Revenge*>( pOwner->Weapon_OwnsThisID( TF_WEAPON_SENTRY_REVENGE ) );
  1702. if ( pShotgun )
  1703. {
  1704. pShotgun->SentryKilled( GetKills() * 2 + GetAssists() );
  1705. }
  1706. }
  1707. // find nearby sentry hint
  1708. if ( TFGameRules() && TFGameRules()->IsInTraining() )
  1709. {
  1710. CTFBotHintSentrygun *sentryHint;
  1711. for( sentryHint = static_cast< CTFBotHintSentrygun * >( gEntList.FindEntityByClassname( NULL, "bot_hint_sentrygun" ) );
  1712. sentryHint;
  1713. sentryHint = static_cast< CTFBotHintSentrygun * >( gEntList.FindEntityByClassname( sentryHint, "bot_hint_sentrygun" ) ) )
  1714. {
  1715. if ( sentryHint->IsEnabled() && sentryHint->InSameTeam( this ) )
  1716. {
  1717. Vector toMe = GetAbsOrigin() - sentryHint->GetAbsOrigin();
  1718. float dist2 = toMe.LengthSqr();
  1719. if ( dist2 < 1.0f )
  1720. {
  1721. sentryHint->OnSentryGunDestroyed( this );
  1722. sentryHint->DecrementUseCount();
  1723. break;
  1724. }
  1725. }
  1726. }
  1727. }
  1728. // Engineers destroying their own sentry don't escape the buster.
  1729. // Destroying disposable sentries doesn't reset the buster.
  1730. if ( info.GetAttacker() != this && !IsDisposableBuilding() )
  1731. {
  1732. // Sentry Buster mission accomplished
  1733. if ( pOwner )
  1734. {
  1735. pOwner->ResetAccumulatedSentryGunDamageDealt();
  1736. pOwner->ResetAccumulatedSentryGunKillCount();
  1737. }
  1738. }
  1739. // do normal handling
  1740. BaseClass::Killed( info );
  1741. }
  1742. //-----------------------------------------------------------------------------
  1743. // Purpose:
  1744. //-----------------------------------------------------------------------------
  1745. void CObjectSentrygun::SetModel( const char *pModel )
  1746. {
  1747. float flPoseParam0 = 0.0;
  1748. float flPoseParam1 = 0.0;
  1749. // Save pose parameters across model change
  1750. if ( m_iPitchPoseParameter >= 0 )
  1751. {
  1752. flPoseParam0 = GetPoseParameter( m_iPitchPoseParameter );
  1753. }
  1754. if ( m_iYawPoseParameter >= 0 )
  1755. {
  1756. flPoseParam1 = GetPoseParameter( m_iYawPoseParameter );
  1757. }
  1758. BaseClass::SetModel( pModel );
  1759. // Reset this after model change
  1760. SetBuildingSize();
  1761. SetSolid( SOLID_BBOX );
  1762. // Restore pose parameters
  1763. m_iPitchPoseParameter = LookupPoseParameter( "aim_pitch" );
  1764. m_iYawPoseParameter = LookupPoseParameter( "aim_yaw" );
  1765. SetPoseParameter( m_iPitchPoseParameter, flPoseParam0 );
  1766. SetPoseParameter( m_iYawPoseParameter, flPoseParam1 );
  1767. CreateBuildPoints();
  1768. ReattachChildren();
  1769. ResetSequenceInfo();
  1770. }
  1771. //-----------------------------------------------------------------------------
  1772. // Purpose:
  1773. //-----------------------------------------------------------------------------
  1774. void CObjectSentrygun::SetBuildingSize()
  1775. {
  1776. // Mini's do NOT need to have their size set here, SetModelScale already handles scaling for hulls (change from MvM)
  1777. UTIL_SetSize( this, SENTRYGUN_MINS, SENTRYGUN_MAXS );
  1778. }
  1779. //-----------------------------------------------------------------------------
  1780. // Purpose:
  1781. //-----------------------------------------------------------------------------
  1782. void CObjectSentrygun::MakeCarriedObject( CTFPlayer *pCarrier )
  1783. {
  1784. BaseClass::MakeCarriedObject( pCarrier );
  1785. m_iOldAmmoShells = m_iAmmoShells;
  1786. m_iOldAmmoRockets = m_iAmmoRockets;
  1787. m_nShieldLevel.Set( SHIELD_NONE );
  1788. }
  1789. //-----------------------------------------------------------------------------
  1790. // Purpose:
  1791. //-----------------------------------------------------------------------------
  1792. void CObjectSentrygun::MakeDisposableBuilding( CTFPlayer* pPlayer )
  1793. {
  1794. // We don't have our main gun
  1795. if ( !( pPlayer->GetNumObjects( OBJ_SENTRYGUN ) && pPlayer->CanBuild( OBJ_SENTRYGUN ) == CB_CAN_BUILD ) )
  1796. return;
  1797. // We're carrying our main gun
  1798. if ( pPlayer->m_Shared.IsCarryingObject() && pPlayer->m_Shared.GetCarriedObject() && !pPlayer->m_Shared.GetCarriedObject()->IsDisposableBuilding() )
  1799. return;
  1800. if ( IsDisposableBuilding() )
  1801. return;
  1802. SetMaxHealth( SENTRYGUN_MINI_MAX_HEALTH );
  1803. SetHealth( SENTRYGUN_MINI_MAX_HEALTH );
  1804. SetModelScale( DISPOSABLE_SCALE );
  1805. BaseClass::MakeDisposableBuilding( pPlayer );
  1806. }
  1807. //-----------------------------------------------------------------------------
  1808. // Purpose:
  1809. //-----------------------------------------------------------------------------
  1810. void CObjectSentrygun::RemoveAllAmmo()
  1811. {
  1812. m_iOldAmmoShells = m_iAmmoShells;
  1813. m_iOldAmmoRockets = m_iAmmoRockets;
  1814. m_iAmmoShells = 0;
  1815. m_iAmmoRockets = 0;
  1816. }
  1817. //-----------------------------------------------------------------------------
  1818. // Purpose:
  1819. //-----------------------------------------------------------------------------
  1820. void CObjectSentrygun::EmitSentrySound( IRecipientFilter& filter, int iEntIndex, const char *soundname )
  1821. {
  1822. EmitSound_t params;
  1823. params.m_pSoundName = soundname;
  1824. params.m_flSoundTime = 0;
  1825. params.m_pflSoundDuration = 0;
  1826. params.m_bWarnOnDirectWaveReference = true;
  1827. if ( IsMiniBuilding() )
  1828. {
  1829. StopSound( soundname );
  1830. params.m_nPitch = PITCH_HIGH;
  1831. params.m_nFlags = SND_CHANGE_PITCH;
  1832. }
  1833. EmitSound( filter, entindex(), params );
  1834. }
  1835. //-----------------------------------------------------------------------------
  1836. // Purpose:
  1837. //-----------------------------------------------------------------------------
  1838. void CObjectSentrygun::EmitSentrySound( const char* soundname )
  1839. {
  1840. CPASAttenuationFilter filter( this, soundname );
  1841. EmitSound_t params;
  1842. params.m_pSoundName = soundname;
  1843. params.m_flSoundTime = 0;
  1844. params.m_pflSoundDuration = 0;
  1845. params.m_bWarnOnDirectWaveReference = true;
  1846. if ( IsMiniBuilding() || m_flFireRate != 1.f )
  1847. {
  1848. StopSound( soundname );
  1849. params.m_nPitch = IsMiniBuilding() ? PITCH_HIGH : RemapValClamped( m_flFireRate, 1.0f, 0.5f, 100.f, 120.f );
  1850. params.m_nFlags = SND_CHANGE_PITCH;
  1851. }
  1852. EmitSound( filter, entindex(), params );
  1853. }
  1854. //-----------------------------------------------------------------------------
  1855. // Purpose:
  1856. //-----------------------------------------------------------------------------
  1857. CTFPlayer *CObjectSentrygun::GetAssistingTeammate( float maxAssistDuration ) const
  1858. {
  1859. if ( maxAssistDuration > 0.0f && ( !m_lastTeammateWrenchHitTimer.HasStarted() || m_lastTeammateWrenchHitTimer.IsGreaterThen( maxAssistDuration ) ) )
  1860. return NULL;
  1861. return m_lastTeammateWrenchHit;
  1862. }
  1863. //-----------------------------------------------------------------------------
  1864. // Purpose:
  1865. //-----------------------------------------------------------------------------
  1866. void CObjectSentrygun::SetAutoAimTarget( CTFPlayer* pPlayer )
  1867. {
  1868. if ( !pPlayer )
  1869. return;
  1870. // No auto aim target if a dummy is found
  1871. CBaseEntity *pTargetOld = m_hEnemy.Get();
  1872. if ( pTargetOld )
  1873. {
  1874. CTFTargetDummy *pDummy = dynamic_cast<CTFTargetDummy*>( pTargetOld );
  1875. if ( pDummy )
  1876. {
  1877. m_hAutoAimTarget = NULL;
  1878. return;
  1879. }
  1880. }
  1881. m_hAutoAimTarget = pPlayer;
  1882. m_flAutoAimStartTime = gpGlobals->curtime;
  1883. }
  1884. //-----------------------------------------------------------------------------
  1885. void CObjectSentrygun::UpdateNavMeshCombatStatus( void )
  1886. {
  1887. // mark region as 'in combat'
  1888. if ( m_inCombatThrottleTimer.IsElapsed() )
  1889. {
  1890. // important to keep this at one second, so rate cvars make sense (units/sec)
  1891. m_inCombatThrottleTimer.Start( 1.0f );
  1892. UpdateLastKnownArea();
  1893. // only search up/down StepHeight as a cheap substitute for line of sight
  1894. CUtlVector< CNavArea * > nearbyAreaVector;
  1895. CollectSurroundingAreas( &nearbyAreaVector, GetLastKnownArea(), tf_nav_in_combat_range.GetFloat(), StepHeight, StepHeight );
  1896. for( int i=0; i<nearbyAreaVector.Count(); ++i )
  1897. {
  1898. CTFNavArea *area = static_cast< CTFNavArea * >( nearbyAreaVector[i] );
  1899. // hacky - we want sentry gunfire to immediately heat the area since it is so dangerous
  1900. area->OnCombat();
  1901. area->OnCombat();
  1902. area->OnCombat();
  1903. area->OnCombat();
  1904. area->OnCombat();
  1905. }
  1906. }
  1907. }
  1908. //-------------------------------------------------------------------------------------------------------------------------------
  1909. int CObjectSentrygun::GetUpgradeMetalRequired()
  1910. {
  1911. int iMetal = BaseClass::GetUpgradeMetalRequired();
  1912. int iSmallSentry = 0;
  1913. CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( GetOwner(), iSmallSentry, build_small_sentries );
  1914. if ( iSmallSentry )
  1915. {
  1916. iMetal *= 0.75f;
  1917. }
  1918. return iMetal;
  1919. }
  1920. //-------------------------------------------------------------------------------------------------------------------------------
  1921. int CObjectSentrygun::GetMaxHealthForCurrentLevel( void )
  1922. {
  1923. int iHealth = BaseClass::GetMaxHealthForCurrentLevel();
  1924. if ( IsScaledSentry() )
  1925. {
  1926. iHealth *= 0.66f;
  1927. }
  1928. return iHealth;
  1929. }
  1930. //-------------------------------------------------------------------------------------------------------------------------------
  1931. void CObjectSentrygun::MakeScaledBuilding( CTFPlayer *pPlayer )
  1932. {
  1933. int iSmallSentry = 0;
  1934. CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( GetOwner(), iSmallSentry, build_small_sentries );
  1935. if ( iSmallSentry )
  1936. {
  1937. m_flScaledSentry = iSmallSentry ? SMALL_SENTRY_SCALE : 1.0f;
  1938. SetModelScale( m_flScaledSentry );
  1939. int iHealth = GetMaxHealthForCurrentLevel();
  1940. SetMaxHealth( iHealth );
  1941. SetHealth( iHealth );
  1942. SetBuildingSize();
  1943. }
  1944. }
  1945. //-----------------------------------------------------------------------------
  1946. //-----------------------------------------------------------------------------
  1947. LINK_ENTITY_TO_CLASS( tf_projectile_sentryrocket, CTFProjectile_SentryRocket );
  1948. IMPLEMENT_NETWORKCLASS_ALIASED( TFProjectile_SentryRocket, DT_TFProjectile_SentryRocket )
  1949. BEGIN_NETWORK_TABLE( CTFProjectile_SentryRocket, DT_TFProjectile_SentryRocket )
  1950. END_NETWORK_TABLE()
  1951. //-----------------------------------------------------------------------------
  1952. // Purpose: Creation
  1953. //-----------------------------------------------------------------------------
  1954. CTFProjectile_SentryRocket *CTFProjectile_SentryRocket::Create( const Vector &vecOrigin, const QAngle &vecAngles, CBaseEntity *pOwner, CBaseEntity *pScorer )
  1955. {
  1956. CTFProjectile_SentryRocket *pRocket = static_cast<CTFProjectile_SentryRocket*>( CTFBaseRocket::Create( NULL, "tf_projectile_sentryrocket", vecOrigin, vecAngles, pOwner ) );
  1957. if ( pRocket )
  1958. {
  1959. pRocket->SetScorer( pScorer );
  1960. }
  1961. return pRocket;
  1962. }
  1963. CTFProjectile_SentryRocket::CTFProjectile_SentryRocket()
  1964. {
  1965. UseClientSideAnimation();
  1966. }
  1967. //-----------------------------------------------------------------------------
  1968. // Purpose:
  1969. //-----------------------------------------------------------------------------
  1970. void CTFProjectile_SentryRocket::Spawn()
  1971. {
  1972. BaseClass::Spawn();
  1973. SetModel( SENTRY_ROCKET_MODEL );
  1974. UTIL_SetSize( this, vec3_origin, vec3_origin );
  1975. ResetSequence( LookupSequence("idle") );
  1976. }
  1977. #ifdef STAGING_ONLY
  1978. //-----------------------------------------------------------------------------
  1979. // Purpose: Directly create a sentry gun at the precise position and orientation desired
  1980. //-----------------------------------------------------------------------------
  1981. void CC_SentrygunSpawn( const CCommand& args )
  1982. {
  1983. if ( !UTIL_IsCommandIssuedByServerAdmin() )
  1984. return;
  1985. CObjectSentrygun *sentry = (CObjectSentrygun *)CreateEntityByName( "obj_sentrygun" );
  1986. if ( sentry )
  1987. {
  1988. CBasePlayer* pPlayer = UTIL_GetCommandClient();
  1989. trace_t tr;
  1990. Vector forward;
  1991. pPlayer->EyeVectors( &forward );
  1992. UTIL_TraceLine( pPlayer->EyePosition(),
  1993. pPlayer->EyePosition() + forward * MAX_TRACE_LENGTH,MASK_SOLID,
  1994. pPlayer, COLLISION_GROUP_NONE, &tr );
  1995. if ( tr.fraction != 1.0 )
  1996. {
  1997. sentry->SetAbsOrigin( tr.endpos );
  1998. QAngle angles = pPlayer->BodyAngles();
  1999. angles.x = 0.0f;
  2000. angles.z = 0.0f;
  2001. sentry->SetAbsAngles( angles );
  2002. }
  2003. int iSentryLevel = 2;
  2004. int iTeamNum = pPlayer->GetTeamNumber();
  2005. if ( args.ArgC() > 1 )
  2006. {
  2007. int i = atoi(args[1]);
  2008. if ( abs(i) >= 1 && abs(i) <= 3)
  2009. {
  2010. iSentryLevel = abs(i)-1;
  2011. }
  2012. if ( i < 0)
  2013. {
  2014. iTeamNum = GetEnemyTeam( iTeamNum );
  2015. }
  2016. }
  2017. sentry->m_nDefaultUpgradeLevel = iSentryLevel;
  2018. sentry->Spawn();
  2019. sentry->ChangeTeam( iTeamNum );
  2020. sentry->InitializeMapPlacedObject();
  2021. }
  2022. }
  2023. static ConCommand sentrygun_spawn( "sentrygun_spawn", CC_SentrygunSpawn, "Spawns a Sentrygun where the player is looking. Takes a parameter for level of sentry [1-3: default 3]. If the passed sentry level < 0, an enemy sentry is spawned.", FCVAR_GAMEDLL | FCVAR_CHEAT );
  2024. #endif // STAGING_ONLY