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.

1261 lines
36 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: Engineer's Dispenser
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================//
  7. #include "cbase.h"
  8. #include "tf_obj_dispenser.h"
  9. #include "engine/IEngineSound.h"
  10. #include "tf_player.h"
  11. #include "tf_team.h"
  12. #include "tf_gamerules.h"
  13. #include "vguiscreen.h"
  14. #include "world.h"
  15. #include "explode.h"
  16. #include "tf_gamestats.h"
  17. #include "tf_halloween_souls_pickup.h"
  18. #include "tf_fx.h"
  19. // memdbgon must be the last include file in a .cpp file!!!
  20. #include "tier0/memdbgon.h"
  21. #define DISPENSER_MINS Vector( -20, -20, 0 )
  22. #define DISPENSER_MAXS Vector( 20, 20, 55 ) // tweak me
  23. #define MINI_DISPENSER_MINS Vector( -20, -20, 0 )
  24. #define MINI_DISPENSER_MAXS Vector( 20, 20, 20 )
  25. #define DISPENSER_TRIGGER_MINS Vector( -70, -70, 0 )
  26. #define DISPENSER_TRIGGER_MAXS Vector( 70, 70, 50 ) // tweak me
  27. #define REFILL_CONTEXT "RefillContext"
  28. #define DISPENSE_CONTEXT "DispenseContext"
  29. ConVar tf_cart_spell_drop_rate( "tf_cart_spell_drop_rate", "4" );
  30. ConVar tf_cart_duck_drop_rate( "tf_cart_duck_drop_rate", "10", FCVAR_DEVELOPMENTONLY );
  31. ConVar tf_cart_soul_drop_rate( "tf_cart_soul_drop_rate", "10", FCVAR_DEVELOPMENTONLY );
  32. //-----------------------------------------------------------------------------
  33. // Purpose: SendProxy that converts the Healing list UtlVector to entindices
  34. //-----------------------------------------------------------------------------
  35. void SendProxy_HealingList( const SendProp *pProp, const void *pStruct, const void *pData, DVariant *pOut, int iElement, int objectID )
  36. {
  37. CObjectDispenser *pDispenser = (CObjectDispenser*)pStruct;
  38. // If this assertion fails, then SendProxyArrayLength_HealingArray must have failed.
  39. Assert( iElement < pDispenser->m_hHealingTargets.Size() );
  40. CBaseEntity *pEnt = pDispenser->m_hHealingTargets[iElement].Get();
  41. EHANDLE hOther = pEnt;
  42. SendProxy_EHandleToInt( pProp, pStruct, &hOther, pOut, iElement, objectID );
  43. }
  44. int SendProxyArrayLength_HealingArray( const void *pStruct, int objectID )
  45. {
  46. CObjectDispenser *pDispenser = (CObjectDispenser*)pStruct;
  47. return pDispenser->m_hHealingTargets.Count();
  48. }
  49. IMPLEMENT_SERVERCLASS_ST( CObjectDispenser, DT_ObjectDispenser )
  50. SendPropInt( SENDINFO( m_iState ), 2 ),
  51. SendPropInt( SENDINFO( m_iAmmoMetal ), -1, SPROP_VARINT ),
  52. SendPropInt( SENDINFO( m_iMiniBombCounter ), -1, SPROP_VARINT ),
  53. SendPropArray2(
  54. SendProxyArrayLength_HealingArray,
  55. SendPropInt("healing_array_element", 0, SIZEOF_IGNORE, NUM_NETWORKED_EHANDLE_BITS, SPROP_UNSIGNED, SendProxy_HealingList),
  56. MAX_PLAYERS,
  57. 0,
  58. "healing_array"
  59. )
  60. END_SEND_TABLE()
  61. BEGIN_DATADESC( CObjectDispenser )
  62. DEFINE_THINKFUNC( RefillThink ),
  63. DEFINE_THINKFUNC( DispenseThink ),
  64. // key
  65. DEFINE_KEYFIELD( m_iszCustomTouchTrigger, FIELD_STRING, "touch_trigger" ),
  66. END_DATADESC()
  67. LINK_ENTITY_TO_CLASS(obj_dispenser, CObjectDispenser);
  68. PRECACHE_REGISTER(obj_dispenser);
  69. // How much ammo is given our per use
  70. #define DISPENSER_DROP_METAL 40
  71. float g_flDispenserHealRates[4] =
  72. {
  73. 0,
  74. 10.0,
  75. 15.0,
  76. 20.0
  77. };
  78. float g_flDispenserAmmoRates[4] =
  79. {
  80. 0,
  81. 0.2,
  82. 0.3,
  83. 0.4
  84. };
  85. LINK_ENTITY_TO_CLASS( dispenser_touch_trigger, CDispenserTouchTrigger );
  86. //-----------------------------------------------------------------------------
  87. // Purpose:
  88. //-----------------------------------------------------------------------------
  89. CObjectDispenser::CObjectDispenser()
  90. {
  91. int iHealth = GetMaxHealthForCurrentLevel();
  92. m_hTouchTrigger = NULL;
  93. SetMaxHealth( iHealth );
  94. SetHealth( iHealth );
  95. UseClientSideAnimation();
  96. m_hTouchingEntities.Purge();
  97. m_bUseGenerateMetalSound = true;
  98. m_bThrown = false;
  99. m_bPlayAmmoPickupSound = true;
  100. SetType( OBJ_DISPENSER );
  101. }
  102. CObjectDispenser::~CObjectDispenser()
  103. {
  104. if ( m_hTouchTrigger.Get() )
  105. {
  106. UTIL_Remove( m_hTouchTrigger );
  107. }
  108. ResetHealingTargets();
  109. StopSound( "Building_Dispenser.Idle" );
  110. }
  111. //-----------------------------------------------------------------------------
  112. // Purpose:
  113. //-----------------------------------------------------------------------------
  114. void CObjectDispenser::DetonateObject( void )
  115. {
  116. // We already dying?
  117. if ( m_bDying )
  118. return;
  119. #ifdef STAGING_ONLY
  120. // If we're built, explode for damage
  121. if ( IsMiniBuilding() && !IsCarried() && !IsBuilding() && !IsPlacing() )
  122. {
  123. Vector vecOrigin = GetAbsOrigin();
  124. CTraceFilterIgnorePlayers traceFilter( NULL, COLLISION_GROUP_PROJECTILE );
  125. // base 50 damage, scale by metal amount
  126. float flDamage = RemapValClamped( m_iAmmoMetal, 0, MINI_DISPENSER_MAX_METAL, 50.0f, 300.0f );
  127. CTakeDamageInfo info( this, GetOwner(), flDamage, DMG_BLAST );
  128. // Scale blast radius
  129. float flRadius = RemapValClamped( m_iAmmoMetal, 0, MINI_DISPENSER_MAX_METAL, 150.0f, 200.0f );
  130. CTFRadiusDamageInfo radiusinfo( &info, vecOrigin, flRadius, NULL, flRadius );
  131. TFGameRules()->RadiusDamage( radiusinfo );
  132. CPVSFilter filter( vecOrigin );
  133. TE_TFExplosion( filter, 0.0f, vecOrigin, Vector(0,0,0), TF_WEAPON_GRENADE_PIPEBOMB, kInvalidEHandleExplosion, -1, SPECIAL1, INVALID_STRING_INDEX );
  134. }
  135. #endif
  136. TFGameRules()->OnDispenserDestroyed( this );
  137. BaseClass::DetonateObject();
  138. }
  139. //-----------------------------------------------------------------------------
  140. void CObjectDispenser::DestroyObject( void )
  141. {
  142. if ( TFGameRules() )
  143. {
  144. TFGameRules()->OnDispenserDestroyed( this );
  145. }
  146. BaseClass::DestroyObject();
  147. }
  148. //-----------------------------------------------------------------------------
  149. // Purpose:
  150. //-----------------------------------------------------------------------------
  151. void CObjectDispenser::Spawn()
  152. {
  153. SetModel( GetPlacementModel() );
  154. m_iState.Set( DISPENSER_STATE_IDLE );
  155. SetTouch( &CObjectDispenser::Touch );
  156. BaseClass::Spawn();
  157. }
  158. //-----------------------------------------------------------------------------
  159. // Purpose:
  160. //-----------------------------------------------------------------------------
  161. void CObjectDispenser::FirstSpawn()
  162. {
  163. SetSolid( SOLID_BBOX );
  164. bool bShouldBeMini = ShouldBeMiniBuilding( GetOwner() );
  165. UTIL_SetSize(this,
  166. bShouldBeMini ? MINI_DISPENSER_MINS : DISPENSER_MINS,
  167. bShouldBeMini ? MINI_DISPENSER_MAXS : DISPENSER_MAXS );
  168. m_takedamage = DAMAGE_YES;
  169. m_iAmmoMetal = 0;
  170. int iHealth = GetMaxHealthForCurrentLevel();
  171. SetMaxHealth( iHealth );
  172. SetHealth( iHealth );
  173. BaseClass::FirstSpawn();
  174. }
  175. //-----------------------------------------------------------------------------
  176. // Purpose:
  177. //-----------------------------------------------------------------------------
  178. const char* CObjectDispenser::GetBuildingModel( int iLevel )
  179. {
  180. #ifdef STAGING_ONLY
  181. if ( ShouldBeMiniBuilding( GetOwner() ) )
  182. {
  183. return MINI_DISPENSER_MODEL_BUILDING;
  184. }
  185. else
  186. #endif // STAGING_ONLY
  187. {
  188. switch ( iLevel )
  189. {
  190. case 1:
  191. return DISPENSER_MODEL_BUILDING;
  192. break;
  193. case 2:
  194. return DISPENSER_MODEL_BUILDING_LVL2;
  195. break;
  196. case 3:
  197. return DISPENSER_MODEL_BUILDING_LVL3;
  198. break;
  199. default:
  200. return DISPENSER_MODEL_BUILDING;
  201. break;
  202. }
  203. }
  204. Assert( 0 );
  205. return DISPENSER_MODEL;
  206. }
  207. //-----------------------------------------------------------------------------
  208. // Purpose:
  209. //-----------------------------------------------------------------------------
  210. const char* CObjectDispenser::GetFinishedModel( int iLevel )
  211. {
  212. #ifdef STAGING_ONLY
  213. if ( IsMiniBuilding() )
  214. {
  215. return MINI_DISPENSER_MODEL;
  216. }
  217. else
  218. #endif // STAGING_ONLY
  219. {
  220. switch ( iLevel )
  221. {
  222. case 1:
  223. return DISPENSER_MODEL;
  224. break;
  225. case 2:
  226. return DISPENSER_MODEL_LVL2;
  227. break;
  228. case 3:
  229. return DISPENSER_MODEL_LVL3;
  230. break;
  231. default:
  232. return DISPENSER_MODEL;
  233. break;
  234. }
  235. }
  236. Assert( 0 );
  237. return DISPENSER_MODEL;
  238. }
  239. const char* CObjectDispenser::GetPlacementModel()
  240. {
  241. return /*IsMiniBuilding() ? MINI_DISPENSER_MODEL_PLACEMENT :*/ DISPENSER_MODEL_PLACEMENT;
  242. }
  243. void CObjectDispenser::StartPlacement( CTFPlayer *pPlayer )
  244. {
  245. BaseClass::StartPlacement( pPlayer );
  246. }
  247. //-----------------------------------------------------------------------------
  248. // Purpose: Start building the object
  249. //-----------------------------------------------------------------------------
  250. bool CObjectDispenser::StartBuilding( CBaseEntity *pBuilder )
  251. {
  252. SetStartBuildingModel();
  253. // Have to re-call this in case the player changed their weapon
  254. // between StartPlacement and StartBuilding.
  255. MakeMiniBuilding( GetBuilder() );
  256. CreateBuildPoints();
  257. TFGameRules()->OnDispenserBuilt( this );
  258. bool bIsCarried = IsCarried();
  259. bool bStartBuildingResult = BaseClass::StartBuilding( pBuilder );
  260. if ( bIsCarried )
  261. {
  262. OnEndBeingCarried( pBuilder );
  263. }
  264. return bStartBuildingResult;
  265. }
  266. void CObjectDispenser::SetStartBuildingModel( void )
  267. {
  268. SetModel( GetBuildingModel( 1 ) );
  269. }
  270. //-----------------------------------------------------------------------------
  271. // Purpose:
  272. //-----------------------------------------------------------------------------
  273. void CObjectDispenser::MakeMiniBuilding( CTFPlayer* pPlayer )
  274. {
  275. if ( !ShouldBeMiniBuilding( pPlayer ) || IsMiniBuilding() )
  276. return;
  277. BaseClass::MakeMiniBuilding( pPlayer );
  278. int iHealth = GetMaxHealthForCurrentLevel();
  279. SetMaxHealth( iHealth );
  280. SetHealth( iHealth );
  281. }
  282. //-----------------------------------------------------------------------------
  283. // Purpose: Raises the Dispenser one level
  284. //-----------------------------------------------------------------------------
  285. void CObjectDispenser::StartUpgrading( void )
  286. {
  287. // m_iUpgradeLevel is incremented in BaseClass::StartUpgrading(),
  288. // but we need to set the model before calling it so SetActivity() will be successful
  289. SetModel( GetBuildingModel( m_iUpgradeLevel+1 ) );
  290. // clear our healing list, everyone will be
  291. // added again at the new heal rate in DispenseThink()
  292. ResetHealingTargets();
  293. BaseClass::StartUpgrading();
  294. m_iState.Set( DISPENSER_STATE_UPGRADING );
  295. }
  296. //-----------------------------------------------------------------------------
  297. // Purpose:
  298. //-----------------------------------------------------------------------------
  299. void CObjectDispenser::FinishUpgrading( void )
  300. {
  301. m_iState.Set( DISPENSER_STATE_IDLE );
  302. BaseClass::FinishUpgrading();
  303. if ( m_iUpgradeLevel > 1 )
  304. {
  305. SetModel( GetFinishedModel( m_iUpgradeLevel ) );
  306. }
  307. }
  308. //-----------------------------------------------------------------------------
  309. // Purpose:
  310. //-----------------------------------------------------------------------------
  311. void CObjectDispenser::SetModel( const char *pModel )
  312. {
  313. BaseClass::SetModel( pModel );
  314. // Reset this after model change
  315. #ifdef STAGING_ONLY
  316. UTIL_SetSize(this,
  317. IsMiniBuilding() ? MINI_DISPENSER_MINS : DISPENSER_MINS,
  318. IsMiniBuilding() ? MINI_DISPENSER_MAXS : DISPENSER_MAXS );
  319. #else
  320. UTIL_SetSize( this,
  321. DISPENSER_MINS,
  322. DISPENSER_MAXS );
  323. #endif // STAGING_ONLY
  324. ResetSequenceInfo();
  325. }
  326. //-----------------------------------------------------------------------------
  327. // Purpose:
  328. //-----------------------------------------------------------------------------
  329. void CObjectDispenser::InitializeMapPlacedObject( void )
  330. {
  331. // make sure we are using an appropriate model so that the control panels are in the right place
  332. SetModel( GetFinishedModel( 1 ) );
  333. BaseClass::InitializeMapPlacedObject();
  334. }
  335. bool CObjectDispenser::ShouldBeMiniBuilding( CTFPlayer* pPlayer )
  336. {
  337. #ifdef STAGING_ONLY
  338. int nMiniDispenserEnabled = 0;
  339. CALL_ATTRIB_HOOK_INT_ON_OTHER( GetOwner(), nMiniDispenserEnabled, allows_building_mini_dispenser );
  340. return nMiniDispenserEnabled != 0;
  341. #else
  342. return false;
  343. #endif // STAGING_ONLY
  344. }
  345. //-----------------------------------------------------------------------------
  346. // Purpose:
  347. //-----------------------------------------------------------------------------
  348. int CObjectDispenser::GetMaxUpgradeLevel()
  349. {
  350. #ifdef STAGING_ONLY
  351. if ( IsMiniBuilding() )
  352. return DISPENSER_MINI_MAX_LEVEL;
  353. #endif // STAGING_ONLY
  354. return BaseClass::GetMaxUpgradeLevel();
  355. }
  356. //-----------------------------------------------------------------------------
  357. // Purpose: Finished building
  358. //-----------------------------------------------------------------------------
  359. void CObjectDispenser::OnGoActive( void )
  360. {
  361. SetModel( GetFinishedModel( 1 ) );
  362. CreateBuildPoints();
  363. ReattachChildren();
  364. // Put some ammo in the Dispenser
  365. if ( !m_bCarryDeploy && !IsMiniBuilding() )
  366. {
  367. m_iAmmoMetal = TFGameRules()->IsQuickBuildTime() ? DISPENSER_MAX_METAL_AMMO : 25;
  368. }
  369. // Begin thinking
  370. SetContextThink( &CObjectDispenser::RefillThink, gpGlobals->curtime + 3, REFILL_CONTEXT );
  371. SetContextThink( &CObjectDispenser::DispenseThink, gpGlobals->curtime + 0.1, DISPENSE_CONTEXT );
  372. m_flNextAmmoDispense = gpGlobals->curtime + 0.5;
  373. if ( m_hTouchTrigger.Get() && dynamic_cast<CObjectCartDispenser*>(this) != NULL )
  374. {
  375. UTIL_Remove( m_hTouchTrigger.Get() );
  376. m_hTouchTrigger = NULL;
  377. }
  378. float flRadius = GetDispenserRadius();
  379. if ( m_iszCustomTouchTrigger != NULL_STRING )
  380. {
  381. m_hTouchTrigger = dynamic_cast<CDispenserTouchTrigger *> ( gEntList.FindEntityByName( NULL, m_iszCustomTouchTrigger ) );
  382. if ( m_hTouchTrigger.Get() != NULL )
  383. {
  384. m_hTouchTrigger->SetOwnerEntity( this ); //owned
  385. }
  386. }
  387. if ( m_hTouchTrigger.Get() == NULL )
  388. {
  389. m_hTouchTrigger = CBaseEntity::Create( "dispenser_touch_trigger", GetAbsOrigin(), vec3_angle, this );
  390. UTIL_SetSize(m_hTouchTrigger.Get(), Vector(-flRadius,-flRadius,-flRadius), Vector(flRadius,flRadius,flRadius) );
  391. m_hTouchTrigger->SetSolid(SOLID_BBOX);
  392. }
  393. Assert( m_hTouchTrigger.Get() );
  394. BaseClass::OnGoActive();
  395. PlayActiveSound();
  396. }
  397. void CObjectDispenser::PlayActiveSound()
  398. {
  399. EmitSound( "Building_Dispenser.Idle" );
  400. }
  401. //-----------------------------------------------------------------------------
  402. // Spawn the vgui control screens on the object
  403. //-----------------------------------------------------------------------------
  404. void CObjectDispenser::GetControlPanelInfo( int nPanelIndex, const char *&pPanelName )
  405. {
  406. // Panels 0 and 1 are both control panels for now
  407. if ( nPanelIndex == 0 || nPanelIndex == 1 )
  408. {
  409. if ( GetTeamNumber() == TF_TEAM_RED )
  410. {
  411. pPanelName = "screen_obj_dispenser_red";
  412. }
  413. else
  414. {
  415. pPanelName = "screen_obj_dispenser_blue";
  416. }
  417. }
  418. else
  419. {
  420. BaseClass::GetControlPanelInfo( nPanelIndex, pPanelName );
  421. }
  422. }
  423. //-----------------------------------------------------------------------------
  424. // Purpose:
  425. //-----------------------------------------------------------------------------
  426. void CObjectDispenser::Precache()
  427. {
  428. BaseClass::Precache();
  429. int iModelIndex;
  430. PrecacheModel( DISPENSER_MODEL_PLACEMENT );
  431. iModelIndex = PrecacheModel( DISPENSER_MODEL_BUILDING );
  432. PrecacheGibsForModel( iModelIndex );
  433. iModelIndex = PrecacheModel( DISPENSER_MODEL );
  434. PrecacheGibsForModel( iModelIndex );
  435. iModelIndex = PrecacheModel( DISPENSER_MODEL_BUILDING_LVL2 );
  436. PrecacheGibsForModel( iModelIndex );
  437. iModelIndex = PrecacheModel( DISPENSER_MODEL_LVL2 );
  438. PrecacheGibsForModel( iModelIndex );
  439. iModelIndex = PrecacheModel( DISPENSER_MODEL_BUILDING_LVL3 );
  440. PrecacheGibsForModel( iModelIndex );
  441. iModelIndex = PrecacheModel( DISPENSER_MODEL_LVL3 );
  442. PrecacheGibsForModel( iModelIndex );
  443. #ifdef STAGING_ONLY
  444. PrecacheGibsForModel( PrecacheModel( MINI_DISPENSER_MODEL_PLACEMENT ) );
  445. PrecacheGibsForModel( PrecacheModel( MINI_DISPENSER_MODEL_BUILDING ) );
  446. PrecacheGibsForModel( PrecacheModel( MINI_DISPENSER_MODEL ) );
  447. #endif // STAGING_ONLY
  448. PrecacheVGuiScreen( "screen_obj_dispenser_blue" );
  449. PrecacheVGuiScreen( "screen_obj_dispenser_red" );
  450. PrecacheScriptSound( "Building_Dispenser.Idle" );
  451. PrecacheScriptSound( "Building_Dispenser.GenerateMetal" );
  452. PrecacheScriptSound( "Building_Dispenser.Heal" );
  453. PrecacheParticleSystem( "dispenser_heal_red" );
  454. PrecacheParticleSystem( "dispenser_heal_blue" );
  455. }
  456. //-----------------------------------------------------------------------------
  457. // Purpose:
  458. //-----------------------------------------------------------------------------
  459. bool CObjectDispenser::DispenseAmmo( CTFPlayer *pPlayer )
  460. {
  461. int iTotalPickedUp = 0;
  462. int iAmmoToAdd = 0;
  463. int nNoPrimaryAmmoFromDispensersWhileActive = 0;
  464. CALL_ATTRIB_HOOK_INT_ON_OTHER( pPlayer->GetActiveWeapon(), nNoPrimaryAmmoFromDispensersWhileActive, no_primary_ammo_from_dispensers );
  465. #ifdef STAGING_ONLY
  466. float flAmmoRate = IsMiniBuilding() ? DISPENSER_MINI_AMMO_RATE : g_flDispenserAmmoRates[GetUpgradeLevel()];
  467. #else
  468. float flAmmoRate = g_flDispenserAmmoRates[GetUpgradeLevel()];
  469. #endif
  470. CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( GetBuilder(), flAmmoRate, mult_dispenser_rate );
  471. if ( nNoPrimaryAmmoFromDispensersWhileActive == 0 )
  472. {
  473. // primary
  474. iAmmoToAdd = (int)( pPlayer->GetMaxAmmo( TF_AMMO_PRIMARY ) * flAmmoRate );
  475. iTotalPickedUp += pPlayer->GiveAmmo( iAmmoToAdd, TF_AMMO_PRIMARY, !m_bPlayAmmoPickupSound, kAmmoSource_DispenserOrCart );
  476. }
  477. // secondary
  478. iAmmoToAdd = (int)( pPlayer->GetMaxAmmo( TF_AMMO_SECONDARY ) * flAmmoRate );
  479. iTotalPickedUp += pPlayer->GiveAmmo( iAmmoToAdd, TF_AMMO_SECONDARY, !m_bPlayAmmoPickupSound, kAmmoSource_DispenserOrCart );
  480. // metal
  481. int iNoMetalFromDispenserWhileActive = 0;
  482. CALL_ATTRIB_HOOK_INT_ON_OTHER( pPlayer->GetActiveWeapon(), iNoMetalFromDispenserWhileActive, no_metal_from_dispensers_while_active );
  483. if ( iNoMetalFromDispenserWhileActive == 0 )
  484. {
  485. iTotalPickedUp += DispenseMetal( pPlayer );
  486. }
  487. if ( iTotalPickedUp > 0 )
  488. {
  489. if ( m_bPlayAmmoPickupSound )
  490. {
  491. EmitSound( "BaseCombatCharacter.AmmoPickup" );
  492. }
  493. CTFPlayer *pOwner = GetOwner();
  494. if ( pOwner && pOwner != pPlayer )
  495. {
  496. // This is crude; it doesn't account for the value difference in resupplying rockets vs pistol bullets.
  497. // Still, it's better than nothing when trying to measure the value classes generate.
  498. if ( TFGameRules() &&
  499. TFGameRules()->GameModeUsesUpgrades() &&
  500. TFGameRules()->State_Get() == GR_STATE_RND_RUNNING )
  501. {
  502. CTF_GameStats.Event_PlayerAwardBonusPoints( pOwner, pPlayer, 1 );
  503. }
  504. }
  505. return true;
  506. }
  507. // return false if we didn't pick up anything
  508. return false;
  509. }
  510. //-----------------------------------------------------------------------------
  511. // Purpose:
  512. //-----------------------------------------------------------------------------
  513. int CObjectDispenser::DispenseMetal( CTFPlayer *pPlayer )
  514. {
  515. int iMetalToGive = DISPENSER_DROP_METAL + ((GetUpgradeLevel()-1) * 10);
  516. int iMetal = pPlayer->GiveAmmo( Min( m_iAmmoMetal.Get(), iMetalToGive ), TF_AMMO_METAL, false, kAmmoSource_DispenserOrCart );
  517. m_iAmmoMetal -= iMetal;
  518. return iMetal;
  519. }
  520. //-----------------------------------------------------------------------------
  521. // Purpose:
  522. //-----------------------------------------------------------------------------
  523. void CObjectDispenser::RefillThink( void )
  524. {
  525. if ( IsCarried() )
  526. return;
  527. SetContextThink( &CObjectDispenser::RefillThink, gpGlobals->curtime + 6, REFILL_CONTEXT );
  528. if ( IsDisabled() )
  529. {
  530. return;
  531. }
  532. // Auto-refill half the amount as tfc, but twice as often
  533. if ( m_iAmmoMetal < DISPENSER_MAX_METAL_AMMO )
  534. {
  535. int iMetal = (DISPENSER_MAX_METAL_AMMO * 0.1) + ((GetUpgradeLevel()-1) * 10);
  536. CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( GetBuilder(), iMetal, mult_dispenser_rate );
  537. m_iAmmoMetal = Min( m_iAmmoMetal + iMetal, DISPENSER_MAX_METAL_AMMO );
  538. if ( m_bUseGenerateMetalSound )
  539. {
  540. EmitSound( "Building_Dispenser.GenerateMetal" );
  541. }
  542. }
  543. }
  544. //-----------------------------------------------------------------------------
  545. // Generate ammo over time
  546. //-----------------------------------------------------------------------------
  547. void CObjectDispenser::DispenseThink( void )
  548. {
  549. if ( IsCarried() )
  550. return;
  551. if ( IsDisabled() )
  552. {
  553. // Don't heal or dispense ammo
  554. SetContextThink( &CObjectDispenser::DispenseThink, gpGlobals->curtime + 0.1, DISPENSE_CONTEXT );
  555. // stop healing everyone
  556. ResetHealingTargets();
  557. return;
  558. }
  559. float flRadius = GetDispenserRadius();
  560. if ( m_flNextAmmoDispense <= gpGlobals->curtime )
  561. {
  562. int iNumNearbyPlayers = 0;
  563. if ( GetOwner() )
  564. {
  565. // find players in sphere, that are visible
  566. if ( ( flRadius != m_flPrevRadius ) && m_hTouchTrigger.Get() )
  567. {
  568. UTIL_SetSize( m_hTouchTrigger.Get(), Vector( -flRadius, -flRadius, -flRadius ), Vector( flRadius, flRadius, flRadius ) );
  569. }
  570. }
  571. m_flPrevRadius = flRadius;
  572. Vector vecOrigin = GetAbsOrigin() + Vector(0,0,32);
  573. CBaseEntity *pListOfNearbyEntities[32];
  574. int iNumberOfNearbyEntities = UTIL_EntitiesInSphere( pListOfNearbyEntities, ARRAYSIZE( pListOfNearbyEntities ), vecOrigin, flRadius, FL_CLIENT );
  575. for ( int i=0;i<iNumberOfNearbyEntities;i++ )
  576. {
  577. CTFPlayer *pPlayer = ToTFPlayer( pListOfNearbyEntities[i] );
  578. if ( !pPlayer || !pPlayer->IsAlive() )
  579. continue;
  580. if ( pPlayer->GetTeamNumber() != GetTeamNumber() )
  581. {
  582. if ( !pPlayer->IsPlayerClass( TF_CLASS_SPY ) || ( pPlayer->m_Shared.GetDisguiseTeam() != GetTeamNumber() ) )
  583. continue;
  584. }
  585. DispenseAmmo( pPlayer );
  586. iNumNearbyPlayers++;
  587. }
  588. // Try to dispense more often when no players are around so we
  589. // give it as soon as possible when a new player shows up
  590. #ifdef STAGING_ONLY
  591. float flNextAmmoDelay = IsMiniBuilding() ? DISPENSER_MINI_AMMO_THINK : 1.0;
  592. #else
  593. float flNextAmmoDelay = 1.0;
  594. #endif
  595. m_flNextAmmoDispense = gpGlobals->curtime + ( ( iNumNearbyPlayers > 0 ) ? flNextAmmoDelay : 0.1 );
  596. }
  597. // for each player in touching list
  598. int iSize = m_hTouchingEntities.Count();
  599. bool bIsAnyTeammateTouching = false;
  600. if ( m_hTouchTrigger )
  601. {
  602. for ( int i = iSize-1; i >= 0; i-- )
  603. {
  604. EHANDLE hOther = m_hTouchingEntities[i];
  605. CBaseEntity *pEnt = hOther.Get();
  606. if ( !pEnt )
  607. continue;
  608. // stop touching and healing a dead entity, or one that is grossly out of range (EndTouch() can be flakey)
  609. float flDistSqr = (m_hTouchTrigger->WorldSpaceCenter() - pEnt->WorldSpaceCenter()).LengthSqr();
  610. Vector vecMins, vecMaxs;
  611. m_hTouchTrigger->GetCollideable()->WorldSpaceSurroundingBounds( &vecMins, &vecMaxs );
  612. float flDoubleRadiusSqr = ( vecMaxs - vecMins ).LengthSqr();
  613. if ( !pEnt->IsAlive() || ( flDistSqr > flDoubleRadiusSqr ) )
  614. {
  615. m_hTouchingEntities.FindAndRemove( hOther );
  616. StopHealing( pEnt );
  617. continue;
  618. }
  619. bIsAnyTeammateTouching |= ( pEnt->IsPlayer() && pEnt->GetTeamNumber() == GetTeamNumber() );
  620. bool bHealingTarget = IsHealingTarget( pEnt );
  621. bool bValidHealTarget = CouldHealTarget( pEnt );
  622. if ( bHealingTarget && !bValidHealTarget )
  623. {
  624. // if we can't see them, remove them from healing list
  625. // does nothing if we are not healing them already
  626. StopHealing( pEnt );
  627. }
  628. else if ( !bHealingTarget && bValidHealTarget )
  629. {
  630. // if we can see them, add to healing list
  631. // does nothing if we are healing them already
  632. StartHealing( pEnt );
  633. }
  634. }
  635. }
  636. if ( bIsAnyTeammateTouching )
  637. {
  638. if ( !m_spellTimer.HasStarted() )
  639. {
  640. m_spellTimer.Start( tf_cart_spell_drop_rate.GetFloat() );
  641. }
  642. if ( !m_duckTimer.HasStarted() )
  643. {
  644. m_duckTimer.Start( tf_cart_duck_drop_rate.GetFloat() );
  645. }
  646. if ( !m_soulTimer.HasStarted() )
  647. {
  648. m_soulTimer.Start( tf_cart_soul_drop_rate.GetFloat() );
  649. }
  650. }
  651. else
  652. {
  653. m_spellTimer.Invalidate();
  654. m_duckTimer.Invalidate();
  655. m_soulTimer.Invalidate();
  656. }
  657. if ( m_spellTimer.HasStarted() && m_spellTimer.IsElapsed() )
  658. {
  659. m_spellTimer.Start( tf_cart_spell_drop_rate.GetFloat() );
  660. DropSpellPickup();
  661. }
  662. if ( m_duckTimer.HasStarted() && m_duckTimer.IsElapsed() )
  663. {
  664. m_duckTimer.Start( tf_cart_duck_drop_rate.GetFloat() );
  665. DropDuckPickup();
  666. }
  667. if ( m_soulTimer.HasStarted() && m_soulTimer.IsElapsed() )
  668. {
  669. m_soulTimer.Start( tf_cart_soul_drop_rate.GetFloat() );
  670. DispenseSouls();
  671. }
  672. SetContextThink( &CObjectDispenser::DispenseThink, gpGlobals->curtime + 0.1, DISPENSE_CONTEXT );
  673. }
  674. //-----------------------------------------------------------------------------
  675. // Purpose:
  676. //-----------------------------------------------------------------------------
  677. void CObjectDispenser::StartTouch( CBaseEntity *pOther )
  678. {
  679. // add to touching entities
  680. EHANDLE hOther = pOther;
  681. m_hTouchingEntities.AddToTail( hOther );
  682. if ( !IsBuilding() && !IsDisabled() && CouldHealTarget( pOther ) && !IsHealingTarget( pOther ) )
  683. {
  684. // try to start healing them
  685. StartHealing( pOther );
  686. }
  687. }
  688. //-----------------------------------------------------------------------------
  689. // Purpose:
  690. //-----------------------------------------------------------------------------
  691. void CObjectDispenser::Touch( CBaseEntity *pOther )
  692. {
  693. // We dont want to touch these
  694. if ( pOther->IsSolidFlagSet( FSOLID_TRIGGER | FSOLID_VOLUME_CONTENTS ) )
  695. return;
  696. // Handle hitting skybox (disappear).
  697. const trace_t *pTrace = &CBaseEntity::GetTouchTrace();
  698. if( pTrace->surface.flags & SURF_SKY )
  699. {
  700. UTIL_Remove( this );
  701. return;
  702. }
  703. }
  704. //-----------------------------------------------------------------------------
  705. // Purpose:
  706. //-----------------------------------------------------------------------------
  707. void CObjectDispenser::EndTouch( CBaseEntity *pOther )
  708. {
  709. // remove from touching entities
  710. EHANDLE hOther = pOther;
  711. m_hTouchingEntities.FindAndRemove( hOther );
  712. // remove from healing list
  713. StopHealing( pOther );
  714. }
  715. //-----------------------------------------------------------------------------
  716. // Purpose:
  717. //-----------------------------------------------------------------------------
  718. void CObjectDispenser::ResetHealingTargets( void )
  719. {
  720. // tell all the players we're not healing them anymore
  721. for ( int i = m_hHealingTargets.Count()-1 ; i >= 0 ; i-- )
  722. {
  723. EHANDLE hEnt = m_hHealingTargets[i];
  724. CBaseEntity *pOther = hEnt.Get();
  725. if ( pOther )
  726. {
  727. StopHealing( pOther );
  728. }
  729. }
  730. }
  731. //-----------------------------------------------------------------------------
  732. // Purpose: Try to start healing this target
  733. //-----------------------------------------------------------------------------
  734. float CObjectDispenser::GetHealRate() const
  735. {
  736. #ifdef STAGING_ONLY
  737. float flHealRate = IsMiniBuilding() ? DISPENSER_MINI_HEAL_RATE : g_flDispenserHealRates[GetUpgradeLevel()];
  738. #else
  739. float flHealRate = g_flDispenserHealRates[GetUpgradeLevel()];
  740. #endif
  741. CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( GetBuilder(), flHealRate, mult_dispenser_rate );
  742. return flHealRate;
  743. }
  744. //-----------------------------------------------------------------------------
  745. // Purpose: Try to start healing this target
  746. //-----------------------------------------------------------------------------
  747. void CObjectDispenser::StartHealing( CBaseEntity *pOther )
  748. {
  749. if ( IsCarried() )
  750. return;
  751. AddHealingTarget( pOther );
  752. CTFPlayer *pPlayer = ToTFPlayer( pOther );
  753. if ( pPlayer )
  754. {
  755. float flHealRate = GetHealRate();
  756. float flOverhealBonus = 1.0;
  757. pPlayer->m_Shared.Heal( this, flHealRate, flOverhealBonus, 1.0, true, GetBuilder() );
  758. }
  759. }
  760. //-----------------------------------------------------------------------------
  761. // Purpose: Stop healing this target
  762. //-----------------------------------------------------------------------------
  763. void CObjectDispenser::StopHealing( CBaseEntity *pOther )
  764. {
  765. if ( RemoveHealingTarget( pOther ) )
  766. {
  767. CTFPlayer *pPlayer = ToTFPlayer( pOther );
  768. if ( pPlayer )
  769. {
  770. float flHealingDone = pPlayer->m_Shared.StopHealing( this );
  771. if ( GetBuilder() && pOther != GetBuilder() && flHealingDone > 0 )
  772. {
  773. GetBuilder()->AwardAchievement( ACHIEVEMENT_TF_ENGINEER_DISPENSER_HEAL_GRIND, floor( flHealingDone ) );
  774. if ( GetBuilder()->GetTeam() == pOther->GetTeam() )
  775. {
  776. // Strange Health Provided to Allies
  777. EconEntity_OnOwnerKillEaterEvent(
  778. dynamic_cast<CEconEntity *>( GetBuilder()->GetEntityForLoadoutSlot( LOADOUT_POSITION_PDA ) ),
  779. GetBuilder(),
  780. pPlayer,
  781. kKillEaterEvent_HealingProvided,
  782. (int)flHealingDone
  783. );
  784. }
  785. }
  786. }
  787. }
  788. }
  789. //-----------------------------------------------------------------------------
  790. // Purpose: Is this a valid heal target? and not already healing them?
  791. //-----------------------------------------------------------------------------
  792. bool CObjectDispenser::CouldHealTarget( CBaseEntity *pTarget )
  793. {
  794. if ( !HasSpawnFlags( SF_DISPENSER_IGNORE_LOS ) && !pTarget->FVisible( this, MASK_BLOCKLOS ) )
  795. return false;
  796. if ( pTarget->IsPlayer() && pTarget->IsAlive() )
  797. {
  798. CTFPlayer *pTFPlayer = ToTFPlayer( pTarget );
  799. // Don't heal while in purgatory. Players take damage constantly while down there in
  800. // order to flush them out. If we're healing players down there, that goes against
  801. // the purpose of the damage.
  802. if ( pTFPlayer->IsInPurgatory() )
  803. return false;
  804. // don't heal enemies unless they are disguised as our team
  805. int iTeam = GetTeamNumber();
  806. int iPlayerTeam = pTFPlayer->GetTeamNumber();
  807. if ( iPlayerTeam != iTeam && pTFPlayer->m_Shared.InCond( TF_COND_DISGUISED ) )
  808. {
  809. iPlayerTeam = pTFPlayer->m_Shared.GetDisguiseTeam();
  810. }
  811. if ( iPlayerTeam != iTeam )
  812. {
  813. return false;
  814. }
  815. if ( HasSpawnFlags( SF_DISPENSER_DONT_HEAL_DISGUISED_SPIES ) )
  816. {
  817. // if they're invis, no heals
  818. if ( pTFPlayer->m_Shared.IsStealthed() )
  819. {
  820. return false;
  821. }
  822. // if they're disguised as enemy
  823. if ( pTFPlayer->m_Shared.InCond( TF_COND_DISGUISED ) &&
  824. pTFPlayer->m_Shared.GetDisguiseTeam() != iTeam )
  825. {
  826. return false;
  827. }
  828. }
  829. return true;
  830. }
  831. return false;
  832. }
  833. //-----------------------------------------------------------------------------
  834. // Purpose:
  835. //-----------------------------------------------------------------------------
  836. float CObjectDispenser::GetDispenserRadius( void )
  837. {
  838. float flRadius = 64.f;
  839. if ( GetOwner() )
  840. {
  841. CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( GetOwner(), flRadius, mult_dispenser_radius );
  842. }
  843. return flRadius;
  844. }
  845. //-----------------------------------------------------------------------------
  846. // Purpose:
  847. //-----------------------------------------------------------------------------
  848. void CObjectDispenser::AddHealingTarget( CBaseEntity *pOther )
  849. {
  850. // add to tail
  851. EHANDLE hOther = pOther;
  852. m_hHealingTargets.AddToTail( hOther );
  853. NetworkStateChanged();
  854. // check how many healing targets we now have and possibly award an achievement
  855. if ( m_hHealingTargets.Count() >= 3 && GetBuilder() )
  856. {
  857. GetBuilder()->AwardAchievement( ACHIEVEMENT_TF_ENGINEER_DISPENSER_HEAL_GROUP );
  858. }
  859. }
  860. //-----------------------------------------------------------------------------
  861. // Purpose:
  862. //-----------------------------------------------------------------------------
  863. bool CObjectDispenser::RemoveHealingTarget( CBaseEntity *pOther )
  864. {
  865. // remove
  866. EHANDLE hOther = pOther;
  867. bool bFound = m_hHealingTargets.FindAndRemove( hOther );
  868. NetworkStateChanged();
  869. return bFound;
  870. }
  871. //-----------------------------------------------------------------------------
  872. // Purpose: Are we healing this target already
  873. //-----------------------------------------------------------------------------
  874. bool CObjectDispenser::IsHealingTarget( CBaseEntity *pTarget )
  875. {
  876. EHANDLE hOther = pTarget;
  877. return m_hHealingTargets.HasElement( hOther );
  878. }
  879. //-----------------------------------------------------------------------------
  880. // Purpose:
  881. //-----------------------------------------------------------------------------
  882. int CObjectDispenser::DrawDebugTextOverlays(void)
  883. {
  884. int text_offset = BaseClass::DrawDebugTextOverlays();
  885. if (m_debugOverlays & OVERLAY_TEXT_BIT)
  886. {
  887. char tempstr[512];
  888. Q_snprintf( tempstr, sizeof( tempstr ),"Metal: %d", m_iAmmoMetal.Get() );
  889. EntityText(text_offset,tempstr,0);
  890. text_offset++;
  891. }
  892. return text_offset;
  893. }
  894. //-----------------------------------------------------------------------------
  895. //
  896. //-----------------------------------------------------------------------------
  897. void CObjectDispenser::MakeCarriedObject( CTFPlayer *pCarrier )
  898. {
  899. if ( m_hTouchTrigger.Get() )
  900. {
  901. UTIL_Remove( m_hTouchTrigger );
  902. }
  903. ResetHealingTargets();
  904. m_hTouchingEntities.Purge();
  905. StopSound( "Building_Dispenser.Idle" );
  906. BaseClass::MakeCarriedObject( pCarrier );
  907. }
  908. //-----------------------------------------------------------------------------
  909. // Cart Dispenser
  910. //-----------------------------------------------------------------------------
  911. BEGIN_DATADESC( CObjectCartDispenser )
  912. DEFINE_INPUTFUNC( FIELD_INTEGER, "FireHalloweenBonus", InputFireHalloweenBonus ),
  913. DEFINE_INPUTFUNC( FIELD_INTEGER, "SetDispenserLevel", InputSetDispenserLevel ),
  914. DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ),
  915. DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ),
  916. END_DATADESC()
  917. IMPLEMENT_SERVERCLASS_ST( CObjectCartDispenser, DT_ObjectCartDispenser )
  918. END_SEND_TABLE()
  919. LINK_ENTITY_TO_CLASS( mapobj_cart_dispenser, CObjectCartDispenser );
  920. //-----------------------------------------------------------------------------
  921. // Purpose:
  922. //-----------------------------------------------------------------------------
  923. CObjectCartDispenser::CObjectCartDispenser()
  924. {
  925. m_bUseGenerateMetalSound = false;
  926. }
  927. //-----------------------------------------------------------------------------
  928. // Purpose:
  929. //-----------------------------------------------------------------------------
  930. void CObjectCartDispenser::Spawn( void )
  931. {
  932. // This cast is for the benefit of GCC
  933. m_fObjectFlags |= (int)OF_DOESNT_HAVE_A_MODEL;
  934. m_takedamage = DAMAGE_NO;
  935. m_iUpgradeLevel = 1;
  936. TFGameRules()->OnDispenserBuilt( this );
  937. }
  938. //-----------------------------------------------------------------------------
  939. // Purpose: Finished building
  940. //-----------------------------------------------------------------------------
  941. void CObjectCartDispenser::OnGoActive( void )
  942. {
  943. BaseClass::OnGoActive();
  944. SetModel( "" );
  945. }
  946. //-----------------------------------------------------------------------------
  947. // Spawn the vgui control screens on the object
  948. //-----------------------------------------------------------------------------
  949. void CObjectCartDispenser::GetControlPanelInfo( int nPanelIndex, const char *&pPanelName )
  950. {
  951. // no panels
  952. return;
  953. }
  954. //-----------------------------------------------------------------------------
  955. // Don't decrement our metal count
  956. //-----------------------------------------------------------------------------
  957. int CObjectCartDispenser::DispenseMetal( CTFPlayer *pPlayer )
  958. {
  959. int iMetal = pPlayer->GiveAmmo( MIN( m_iAmmoMetal, DISPENSER_DROP_METAL ), TF_AMMO_METAL, false, kAmmoSource_DispenserOrCart );
  960. return iMetal;
  961. }
  962. void CObjectCartDispenser::DropSpellPickup()
  963. {
  964. if ( TFGameRules()->IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_HIGHTOWER ) )
  965. {
  966. TFGameRules()->DropSpellPickup( GetAbsOrigin() );
  967. }
  968. }
  969. void CObjectCartDispenser::DropDuckPickup()
  970. {
  971. if ( TFGameRules()->IsHolidayActive( kHoliday_EOTL ) && TFGameRules()->ShouldDropBonusDuck() )
  972. {
  973. TFGameRules()->DropBonusDuck( GetAbsOrigin() );
  974. }
  975. }
  976. void CObjectCartDispenser::DispenseSouls()
  977. {
  978. // Give a soul to the entire team
  979. if ( TFGameRules() && TFGameRules()->IsHolidayActive( kHoliday_Halloween ) )
  980. {
  981. TFGameRules()->DropHalloweenSoulPackToTeam( 1, GetAbsOrigin(), GetTeamNumber(), TEAM_SPECTATOR );
  982. }
  983. }
  984. //-----------------------------------------------------------------------------
  985. // Purpose:
  986. //-----------------------------------------------------------------------------
  987. void CObjectCartDispenser::SetModel( const char *pModel )
  988. {
  989. CBaseObject::SetModel( pModel );
  990. }
  991. //-----------------------------------------------------------------------------
  992. // Purpose: Give players near the dispenser a bonus
  993. //-----------------------------------------------------------------------------
  994. void CObjectCartDispenser::InputFireHalloweenBonus( inputdata_t &inputdata )
  995. {
  996. for ( int i = m_hHealingTargets.Count()-1 ; i >= 0 ; i-- )
  997. {
  998. EHANDLE hEnt = m_hHealingTargets[i];
  999. CTFPlayer *pTFPlayer = ToTFPlayer( hEnt.Get() );
  1000. if ( pTFPlayer )
  1001. {
  1002. pTFPlayer->m_Shared.AddCond( TF_COND_CRITBOOSTED_CTF_CAPTURE, inputdata.value.Int() );
  1003. }
  1004. }
  1005. }
  1006. //-----------------------------------------------------------------------------
  1007. // Purpose:
  1008. //-----------------------------------------------------------------------------
  1009. void CObjectCartDispenser::InputSetDispenserLevel( inputdata_t &inputdata )
  1010. {
  1011. int iLevel = inputdata.value.Int();
  1012. if ( iLevel >= 1 && iLevel <= 3 )
  1013. {
  1014. m_iUpgradeLevel = iLevel;
  1015. }
  1016. }
  1017. //-----------------------------------------------------------------------------
  1018. // Purpose:
  1019. //-----------------------------------------------------------------------------
  1020. void CObjectCartDispenser::InputEnable( inputdata_t &inputdata )
  1021. {
  1022. SetDisabled( false );
  1023. }
  1024. //-----------------------------------------------------------------------------
  1025. // Purpose:
  1026. //-----------------------------------------------------------------------------
  1027. void CObjectCartDispenser::InputDisable( inputdata_t &inputdata )
  1028. {
  1029. SetDisabled( true );
  1030. }