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.

3736 lines
107 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: Base Object built by players
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================//
  7. #include "cbase.h"
  8. #include "tf_player.h"
  9. #include "tf_team.h"
  10. #include "tf_obj.h"
  11. #include "tf_weaponbase.h"
  12. #include "rope.h"
  13. #include "rope_shared.h"
  14. #include "bone_setup.h"
  15. #include "ndebugoverlay.h"
  16. #include "rope_helpers.h"
  17. #include "IEffects.h"
  18. #include "vstdlib/random.h"
  19. #include "tier1/strtools.h"
  20. #include "basegrenade_shared.h"
  21. #include "tf_gamerules.h"
  22. #include "engine/IEngineSound.h"
  23. #include "tf_shareddefs.h"
  24. #include "vguiscreen.h"
  25. #include "hierarchy.h"
  26. #include "func_no_build.h"
  27. #include "func_respawnroom.h"
  28. #include <KeyValues.h>
  29. #include "ihasbuildpoints.h"
  30. #include "utldict.h"
  31. #include "filesystem.h"
  32. #include "npcevent.h"
  33. #include "tf_shareddefs.h"
  34. #include "animation.h"
  35. #include "effect_dispatch_data.h"
  36. #include "te_effect_dispatch.h"
  37. #include "tf_gamestats.h"
  38. #include "tf_ammo_pack.h"
  39. #include "tf_obj_sapper.h"
  40. #include "particle_parse.h"
  41. #include "tf_fx.h"
  42. #include "trains.h"
  43. #include "serverbenchmark_base.h"
  44. #include "tf_weapon_wrench.h"
  45. #include "tf_weapon_grenade_pipebomb.h"
  46. #include "tf_weapon_builder.h"
  47. #include "player_vs_environment/tf_population_manager.h"
  48. // memdbgon must be the last include file in a .cpp file!!!
  49. #include "tier0/memdbgon.h"
  50. // Control panels
  51. #define SCREEN_OVERLAY_MATERIAL "vgui/screens/vgui_overlay"
  52. #define ROPE_HANG_DIST 150
  53. #define UPGRADE_LEVEL_HEALTH_MULTIPLIER 1.2f
  54. ConVar tf_obj_gib_velocity_min( "tf_obj_gib_velocity_min", "100", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY );
  55. ConVar tf_obj_gib_velocity_max( "tf_obj_gib_velocity_max", "450", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY );
  56. ConVar tf_obj_gib_maxspeed( "tf_obj_gib_maxspeed", "800", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY );
  57. ConVar tf_obj_upgrade_per_hit( "tf_obj_upgrade_per_hit", "25", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY );
  58. ConVar object_verbose( "object_verbose", "0", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Debug object system." );
  59. ConVar obj_damage_factor( "obj_damage_factor","0", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Factor applied to all damage done to objects" );
  60. ConVar obj_child_damage_factor( "obj_child_damage_factor","0.25", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Factor applied to damage done to objects that are built on a buildpoint" );
  61. ConVar tf_fastbuild("tf_fastbuild", "0", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY );
  62. ConVar tf_obj_ground_clearance( "tf_obj_ground_clearance", "32", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Object corners can be this high above the ground" );
  63. ConVar tf_obj_damage_tank_achievement_amount( "tf_obj_damage_tank_achievement_amount", "2000", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY );
  64. extern short g_sModelIndexFireball;
  65. extern ConVar tf_cheapobjects;
  66. // Minimum distance between 2 objects to ensure player movement between them
  67. #define MINIMUM_OBJECT_SAFE_DISTANCE 100
  68. // Maximum number of a type of objects on a single resource zone
  69. #define MAX_OBJECTS_PER_ZONE 1
  70. // Time it takes a fully healed object to deteriorate
  71. ConVar object_deterioration_time( "object_deterioration_time", "30", 0, "Time it takes for a fully-healed object to deteriorate." );
  72. // Time after taking damage that an object will still drop resources on death
  73. #define MAX_DROP_TIME_AFTER_DAMAGE 5
  74. #define OBJ_BASE_THINK_CONTEXT "BaseObjectThink"
  75. #define PLASMA_DISABLE_TIME 4
  76. IMPLEMENT_AUTO_LIST( IBaseObjectAutoList );
  77. BEGIN_DATADESC( CBaseObject )
  78. // keys
  79. DEFINE_KEYFIELD_NOT_SAVED( m_SolidToPlayers, FIELD_INTEGER, "SolidToPlayer" ),
  80. DEFINE_KEYFIELD( m_nDefaultUpgradeLevel, FIELD_INTEGER, "defaultupgrade" ),
  81. DEFINE_THINKFUNC( UpgradeThink ),
  82. // Inputs
  83. DEFINE_INPUTFUNC( FIELD_INTEGER, "SetHealth", InputSetHealth ),
  84. DEFINE_INPUTFUNC( FIELD_INTEGER, "AddHealth", InputAddHealth ),
  85. DEFINE_INPUTFUNC( FIELD_INTEGER, "RemoveHealth", InputRemoveHealth ),
  86. DEFINE_INPUTFUNC( FIELD_INTEGER, "SetSolidToPlayer", InputSetSolidToPlayer ),
  87. DEFINE_INPUTFUNC( FIELD_STRING, "SetBuilder", InputSetBuilder ),
  88. DEFINE_INPUTFUNC( FIELD_VOID, "Show", InputShow ),
  89. DEFINE_INPUTFUNC( FIELD_VOID, "Hide", InputHide ),
  90. DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ),
  91. DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ),
  92. // Outputs
  93. DEFINE_OUTPUT( m_OnDestroyed, "OnDestroyed" ),
  94. DEFINE_OUTPUT( m_OnDamaged, "OnDamaged" ),
  95. DEFINE_OUTPUT( m_OnRepaired, "OnRepaired" ),
  96. DEFINE_OUTPUT( m_OnBecomingDisabled, "OnDisabled" ),
  97. DEFINE_OUTPUT( m_OnBecomingReenabled, "OnReenabled" ),
  98. DEFINE_OUTPUT( m_OnObjectHealthChanged, "OnObjectHealthChanged" )
  99. END_DATADESC()
  100. IMPLEMENT_SERVERCLASS_ST(CBaseObject, DT_BaseObject)
  101. SendPropInt(SENDINFO(m_iHealth), -1, SPROP_VARINT ),
  102. SendPropInt(SENDINFO(m_iMaxHealth), -1, SPROP_VARINT ),
  103. SendPropBool(SENDINFO(m_bHasSapper) ),
  104. SendPropInt(SENDINFO(m_iObjectType), Q_log2( OBJ_LAST ) + 1, SPROP_UNSIGNED ),
  105. SendPropBool(SENDINFO(m_bBuilding) ),
  106. SendPropBool(SENDINFO(m_bPlacing) ),
  107. SendPropBool(SENDINFO(m_bCarried) ),
  108. SendPropBool(SENDINFO(m_bCarryDeploy) ),
  109. SendPropBool(SENDINFO(m_bMiniBuilding) ),
  110. SendPropFloat(SENDINFO(m_flPercentageConstructed), 8, 0, 0.0, 1.0f ),
  111. SendPropInt(SENDINFO(m_fObjectFlags), OF_BIT_COUNT, SPROP_UNSIGNED ),
  112. SendPropEHandle(SENDINFO(m_hBuiltOnEntity)),
  113. SendPropBool( SENDINFO( m_bDisabled ) ),
  114. SendPropEHandle( SENDINFO( m_hBuilder ) ),
  115. SendPropVector( SENDINFO( m_vecBuildMaxs ), -1, SPROP_COORD ),
  116. SendPropVector( SENDINFO( m_vecBuildMins ), -1, SPROP_COORD ),
  117. SendPropInt( SENDINFO( m_iDesiredBuildRotations ), 2, SPROP_UNSIGNED ),
  118. SendPropBool( SENDINFO( m_bServerOverridePlacement ) ),
  119. SendPropInt( SENDINFO(m_iUpgradeLevel), 3 ),
  120. SendPropInt( SENDINFO(m_iUpgradeMetal), 10 ),
  121. SendPropInt( SENDINFO(m_iUpgradeMetalRequired), 10 ),
  122. SendPropInt( SENDINFO(m_iHighestUpgradeLevel), 3 ),
  123. SendPropInt( SENDINFO(m_iObjectMode), 2, SPROP_UNSIGNED ),
  124. SendPropBool( SENDINFO( m_bDisposableBuilding ) ),
  125. SendPropBool( SENDINFO( m_bWasMapPlaced ) ),
  126. SendPropBool( SENDINFO( m_bPlasmaDisable ) ),
  127. END_SEND_TABLE();
  128. bool PlayerIndexLessFunc( const int &lhs, const int &rhs )
  129. {
  130. return lhs < rhs;
  131. }
  132. // This controls whether ropes attached to objects are transmitted or not. It's important that
  133. // ropes aren't transmitted to guys who don't own them.
  134. class CObjectRopeTransmitProxy : public CBaseTransmitProxy
  135. {
  136. public:
  137. CObjectRopeTransmitProxy( CBaseEntity *pRope ) : CBaseTransmitProxy( pRope )
  138. {
  139. }
  140. virtual int ShouldTransmit( const CCheckTransmitInfo *pInfo, int nPrevShouldTransmitResult )
  141. {
  142. // Don't transmit the rope if it's not even visible.
  143. if ( !nPrevShouldTransmitResult )
  144. return FL_EDICT_DONTSEND;
  145. // This proxy only wants to be active while one of the two objects is being placed.
  146. // When they're done being placed, the proxy goes away and the rope draws like normal.
  147. bool bAnyObjectPlacing = (m_hObj1 && m_hObj1->IsPlacing()) || (m_hObj2 && m_hObj2->IsPlacing());
  148. if ( !bAnyObjectPlacing )
  149. {
  150. Release();
  151. return nPrevShouldTransmitResult;
  152. }
  153. // Give control to whichever object is being placed.
  154. if ( m_hObj1 && m_hObj1->IsPlacing() )
  155. return m_hObj1->ShouldTransmit( pInfo );
  156. else if ( m_hObj2 && m_hObj2->IsPlacing() )
  157. return m_hObj2->ShouldTransmit( pInfo );
  158. else
  159. return FL_EDICT_ALWAYS;
  160. }
  161. CHandle<CBaseObject> m_hObj1;
  162. CHandle<CBaseObject> m_hObj2;
  163. };
  164. //-----------------------------------------------------------------------------
  165. // Purpose:
  166. //-----------------------------------------------------------------------------
  167. CBaseObject::CBaseObject()
  168. {
  169. m_iHealth = m_iMaxHealth = m_flHealth = 0;
  170. m_flPercentageConstructed = 0;
  171. m_bPlacing = false;
  172. m_bBuilding = false;
  173. m_Activity = ACT_INVALID;
  174. m_bDisabled = false;
  175. m_SolidToPlayers = SOLID_TO_PLAYER_USE_DEFAULT;
  176. m_bPlacementOK = false;
  177. m_aGibs.Purge();
  178. m_iHighestUpgradeLevel = 1;
  179. m_bCarryDeploy = false;
  180. m_flCarryDeployTime = 0;
  181. m_iHealthOnPickup = 0;
  182. m_iLifetimeDamage = 0;
  183. m_bCannotDie = false;
  184. m_bMiniBuilding = false;
  185. m_flPlasmaDisableTime = 0;
  186. m_bPlasmaDisable = false;
  187. m_bDisposableBuilding = false;
  188. m_vecBuildForward = vec3_origin;
  189. m_flBuildDistance = 0.0f;
  190. m_bForceQuickBuild = false;
  191. }
  192. //-----------------------------------------------------------------------------
  193. // Purpose:
  194. //-----------------------------------------------------------------------------
  195. void CBaseObject::UpdateOnRemove( void )
  196. {
  197. m_bDying = true;
  198. // check for sapper crits
  199. CObjectSapper *pSapper = GetSapper();
  200. if ( pSapper )
  201. {
  202. // give an assist to the sapper's owner
  203. CTFPlayer *pSapperOwner = pSapper->GetOwner();
  204. if ( pSapperOwner )
  205. {
  206. pSapperOwner->m_Shared.IncrementRevengeCrits();
  207. }
  208. }
  209. DestroyObject();
  210. if ( GetTeam() )
  211. {
  212. ((CTFTeam*)GetTeam())->RemoveObject( this );
  213. }
  214. DetachObjectFromObject();
  215. // Make sure the object isn't in either team's list of objects...
  216. //Assert( !GetGlobalTFTeam(1)->IsObjectOnTeam( this ) );
  217. //Assert( !GetGlobalTFTeam(2)->IsObjectOnTeam( this ) );
  218. // Chain at end to mimic destructor unwind order
  219. BaseClass::UpdateOnRemove();
  220. }
  221. //-----------------------------------------------------------------------------
  222. // Purpose:
  223. //-----------------------------------------------------------------------------
  224. int CBaseObject::UpdateTransmitState()
  225. {
  226. return SetTransmitState( FL_EDICT_FULLCHECK );
  227. }
  228. //-----------------------------------------------------------------------------
  229. // Purpose:
  230. //-----------------------------------------------------------------------------
  231. int CBaseObject::ShouldTransmit( const CCheckTransmitInfo *pInfo )
  232. {
  233. // Always transmit to owner
  234. if ( GetBuilder() && pInfo->m_pClientEnt == GetBuilder()->edict() )
  235. return FL_EDICT_ALWAYS;
  236. // Placement models only transmit to owners
  237. if ( IsPlacing() )
  238. return FL_EDICT_DONTSEND;
  239. if ( pInfo->m_pClientEnt )
  240. {
  241. CBaseEntity *pRecipientEntity = CBaseEntity::Instance( pInfo->m_pClientEnt );
  242. if ( pRecipientEntity && pRecipientEntity->ShouldForceTransmitsForTeam( GetTeamNumber() ) )
  243. return FL_EDICT_ALWAYS;
  244. }
  245. return BaseClass::ShouldTransmit( pInfo );
  246. }
  247. void CBaseObject::SetTransmit( CCheckTransmitInfo *pInfo, bool bAlways )
  248. {
  249. // Are we already marked for transmission?
  250. if ( pInfo->m_pTransmitEdict->Get( entindex() ) )
  251. return;
  252. BaseClass::SetTransmit( pInfo, bAlways );
  253. // Force our screens to be sent too.
  254. int nTeam = CBaseEntity::Instance( pInfo->m_pClientEnt )->GetTeamNumber();
  255. for ( int i=0; i < m_hScreens.Count(); i++ )
  256. {
  257. CVGuiScreen *pScreen = m_hScreens[i].Get();
  258. if ( pScreen && pScreen->IsVisibleToTeam( nTeam ) )
  259. pScreen->SetTransmit( pInfo, bAlways );
  260. }
  261. }
  262. //-----------------------------------------------------------------------------
  263. // Purpose:
  264. //-----------------------------------------------------------------------------
  265. void CBaseObject::Precache()
  266. {
  267. PrecacheMaterial( SCREEN_OVERLAY_MATERIAL );
  268. PrecacheScriptSound( GetObjectInfo( ObjectType() )->m_pExplodeSound );
  269. PrecacheScriptSound( GetObjectInfo( ObjectType() )->m_pUpgradeSound );
  270. const char *pEffect = GetObjectInfo( ObjectType() )->m_pExplosionParticleEffect;
  271. if ( pEffect && pEffect[0] != '\0' )
  272. {
  273. PrecacheParticleSystem( pEffect );
  274. }
  275. PrecacheParticleSystem( "nutsnbolts_build" );
  276. PrecacheParticleSystem( "nutsnbolts_upgrade" );
  277. PrecacheParticleSystem( "nutsnbolts_repair" );
  278. PrecacheModel( "models/weapons/w_models/w_toolbox.mdl" );
  279. }
  280. //-----------------------------------------------------------------------------
  281. // Purpose:
  282. //-----------------------------------------------------------------------------
  283. void CBaseObject::Spawn( void )
  284. {
  285. Precache();
  286. CollisionProp()->SetSurroundingBoundsType( USE_BEST_COLLISION_BOUNDS );
  287. SetSolidToPlayers( m_SolidToPlayers, true );
  288. m_bWasMapPlaced = false;
  289. m_bHasSapper = false;
  290. if ( HasSpawnFlags(SF_BASEOBJ_INVULN) )
  291. {
  292. m_takedamage = DAMAGE_NO;
  293. }
  294. else
  295. {
  296. m_takedamage = DAMAGE_YES;
  297. }
  298. AddFlag( FL_OBJECT ); // So NPCs will notice it
  299. SetViewOffset( WorldSpaceCenter() - GetAbsOrigin() );
  300. m_iDesiredBuildRotations = 0;
  301. m_flCurrentBuildRotation = 0;
  302. if ( MustBeBuiltOnAttachmentPoint() )
  303. {
  304. AddEffects( EF_NODRAW );
  305. }
  306. // assume valid placement
  307. m_bServerOverridePlacement = true;
  308. m_iUpgradeLevel = 1;
  309. m_iUpgradeMetalRequired = GetUpgradeMetalRequired();
  310. if ( !IsCarried() )
  311. {
  312. FirstSpawn();
  313. }
  314. UpdateLastKnownArea();
  315. }
  316. //-----------------------------------------------------------------------------
  317. // Purpose: Initialization that should only be done when the object is first created.
  318. //-----------------------------------------------------------------------------
  319. void CBaseObject::FirstSpawn()
  320. {
  321. if ( !VPhysicsGetObject() )
  322. VPhysicsInitStatic();
  323. m_iUpgradeMetal = 0;
  324. m_iKills = 0;
  325. m_iAssists = 0;
  326. m_ConstructorList.SetLessFunc( PlayerIndexLessFunc );
  327. m_flHealth = m_iMaxHealth = m_iHealth;
  328. SetContextThink( &CBaseObject::BaseObjectThink, gpGlobals->curtime + 0.1, OBJ_BASE_THINK_CONTEXT );
  329. }
  330. //-----------------------------------------------------------------------------
  331. // Returns information about the various control panels
  332. //-----------------------------------------------------------------------------
  333. void CBaseObject::GetControlPanelInfo( int nPanelIndex, const char *&pPanelName )
  334. {
  335. pPanelName = NULL;
  336. }
  337. //-----------------------------------------------------------------------------
  338. // Returns information about the various control panels
  339. //-----------------------------------------------------------------------------
  340. void CBaseObject::GetControlPanelClassName( int nPanelIndex, const char *&pPanelName )
  341. {
  342. pPanelName = "vgui_screen";
  343. }
  344. //-----------------------------------------------------------------------------
  345. // This is called by the base object when it's time to spawn the control panels
  346. //-----------------------------------------------------------------------------
  347. void CBaseObject::SpawnControlPanels()
  348. {
  349. char buf[64];
  350. // FIXME: Deal with dynamically resizing control panels?
  351. // If we're attached to an entity, spawn control panels on it instead of use
  352. CBaseAnimating *pEntityToSpawnOn = this;
  353. const char *pOrgLL = "controlpanel%d_ll";
  354. const char *pOrgUR = "controlpanel%d_ur";
  355. const char *pAttachmentNameLL = pOrgLL;
  356. const char *pAttachmentNameUR = pOrgUR;
  357. if ( IsBuiltOnAttachment() )
  358. {
  359. pEntityToSpawnOn = dynamic_cast<CBaseAnimating*>((CBaseEntity*)m_hBuiltOnEntity.Get());
  360. if ( pEntityToSpawnOn )
  361. {
  362. char sBuildPointLL[64];
  363. char sBuildPointUR[64];
  364. Q_snprintf( sBuildPointLL, sizeof( sBuildPointLL ), "bp%d_controlpanel%%d_ll", m_iBuiltOnPoint );
  365. Q_snprintf( sBuildPointUR, sizeof( sBuildPointUR ), "bp%d_controlpanel%%d_ur", m_iBuiltOnPoint );
  366. pAttachmentNameLL = sBuildPointLL;
  367. pAttachmentNameUR = sBuildPointUR;
  368. }
  369. else
  370. {
  371. pEntityToSpawnOn = this;
  372. }
  373. }
  374. Assert( pEntityToSpawnOn );
  375. // Lookup the attachment point...
  376. int nPanel;
  377. for ( nPanel = 0; true; ++nPanel )
  378. {
  379. Q_snprintf( buf, sizeof( buf ), pAttachmentNameLL, nPanel );
  380. int nLLAttachmentIndex = pEntityToSpawnOn->LookupAttachment(buf);
  381. if (nLLAttachmentIndex <= 0)
  382. {
  383. // Try and use my panels then
  384. pEntityToSpawnOn = this;
  385. Q_snprintf( buf, sizeof( buf ), pOrgLL, nPanel );
  386. nLLAttachmentIndex = pEntityToSpawnOn->LookupAttachment(buf);
  387. if (nLLAttachmentIndex <= 0)
  388. return;
  389. }
  390. Q_snprintf( buf, sizeof( buf ), pAttachmentNameUR, nPanel );
  391. int nURAttachmentIndex = pEntityToSpawnOn->LookupAttachment(buf);
  392. if (nURAttachmentIndex <= 0)
  393. {
  394. // Try and use my panels then
  395. Q_snprintf( buf, sizeof( buf ), pOrgUR, nPanel );
  396. nURAttachmentIndex = pEntityToSpawnOn->LookupAttachment(buf);
  397. if (nURAttachmentIndex <= 0)
  398. return;
  399. }
  400. const char *pScreenName = NULL;
  401. GetControlPanelInfo( nPanel, pScreenName );
  402. if (!pScreenName)
  403. continue;
  404. const char *pScreenClassname;
  405. GetControlPanelClassName( nPanel, pScreenClassname );
  406. if ( !pScreenClassname )
  407. continue;
  408. // Compute the screen size from the attachment points...
  409. matrix3x4_t panelToWorld;
  410. pEntityToSpawnOn->GetAttachment( nLLAttachmentIndex, panelToWorld );
  411. matrix3x4_t worldToPanel;
  412. MatrixInvert( panelToWorld, worldToPanel );
  413. // Now get the lower right position + transform into panel space
  414. Vector lr, lrlocal;
  415. pEntityToSpawnOn->GetAttachment( nURAttachmentIndex, panelToWorld );
  416. MatrixGetColumn( panelToWorld, 3, lr );
  417. VectorTransform( lr, worldToPanel, lrlocal );
  418. float flWidth = lrlocal.x;
  419. float flHeight = lrlocal.y;
  420. CVGuiScreen *pScreen = CreateVGuiScreen( pScreenClassname, pScreenName, pEntityToSpawnOn, this, nLLAttachmentIndex );
  421. pScreen->ChangeTeam( GetTeamNumber() );
  422. pScreen->SetActualSize( flWidth, flHeight );
  423. pScreen->SetActive( false );
  424. pScreen->MakeVisibleOnlyToTeammates( true );
  425. pScreen->SetOverlayMaterial( SCREEN_OVERLAY_MATERIAL );
  426. pScreen->SetTransparency( true );
  427. // for now, only input by the owning player
  428. pScreen->SetPlayerOwner( GetBuilder(), true );
  429. int nScreen = m_hScreens.AddToTail( );
  430. m_hScreens[nScreen].Set( pScreen );
  431. }
  432. }
  433. //-----------------------------------------------------------------------------
  434. // Handle commands sent from vgui panels on the client
  435. //-----------------------------------------------------------------------------
  436. bool CBaseObject::ClientCommand( CTFPlayer *pSender, const CCommand &args )
  437. {
  438. //const char *pCmd = args[0];
  439. return false;
  440. }
  441. #define BASE_OBJECT_THINK_DELAY 0.1
  442. //-----------------------------------------------------------------------------
  443. // Purpose:
  444. //-----------------------------------------------------------------------------
  445. void CBaseObject::BaseObjectThink( void )
  446. {
  447. SetNextThink( gpGlobals->curtime + BASE_OBJECT_THINK_DELAY, OBJ_BASE_THINK_CONTEXT );
  448. // Make sure animation is up to date
  449. DetermineAnimation();
  450. DeterminePlaybackRate();
  451. if ( m_bPlasmaDisable )
  452. {
  453. if ( gpGlobals->curtime > (m_flPlasmaDisableTime ) )
  454. {
  455. m_bPlasmaDisable = false;
  456. UpdateDisabledState();
  457. }
  458. }
  459. // Do nothing while we're being placed
  460. if ( IsPlacing() )
  461. {
  462. if ( MustBeBuiltOnAttachmentPoint() )
  463. {
  464. UpdateAttachmentPlacement();
  465. m_bServerOverridePlacement = true;
  466. }
  467. else
  468. {
  469. m_bServerOverridePlacement = IsPlacementPosValid();
  470. UpdateDesiredBuildRotation( BASE_OBJECT_THINK_DELAY );
  471. }
  472. return;
  473. }
  474. // If we're building, keep going
  475. if ( IsBuilding() )
  476. {
  477. BuildingThink();
  478. return;
  479. }
  480. if ( IsUpgrading() )
  481. {
  482. UpgradeThink();
  483. }
  484. else
  485. {
  486. if ( GetReversesBuildingConstructionSpeed() > 0.0f )
  487. {
  488. DoReverseBuild();
  489. }
  490. else
  491. {
  492. if ( GetUpgradeLevel() < GetHighestUpgradeLevel() )
  493. {
  494. // Keep moving up levels until we reach the level we were at before.
  495. StartUpgrading();
  496. }
  497. else
  498. {
  499. m_bCarryDeploy = false;
  500. }
  501. }
  502. }
  503. }
  504. void CBaseObject::ResetPlacement( void )
  505. {
  506. m_bPlacementOK = false;
  507. // Clear out previous parent
  508. if ( m_hBuiltOnEntity.Get() )
  509. {
  510. m_hBuiltOnEntity = NULL;
  511. m_iBuiltOnPoint = 0;
  512. SetParent( NULL );
  513. }
  514. // teleport to builder's origin
  515. CTFPlayer *pPlayer = GetOwner();
  516. if ( pPlayer )
  517. {
  518. Teleport( &pPlayer->WorldSpaceCenter(), &GetLocalAngles(), NULL );
  519. }
  520. }
  521. bool CBaseObject::UpdateAttachmentPlacement( CBaseObject *pObjectOverride )
  522. {
  523. // See if we should snap to a build position
  524. // finding one implies it is a valid position
  525. if ( FindSnapToBuildPos( pObjectOverride ) )
  526. {
  527. m_bPlacementOK = true;
  528. Teleport( &m_vecBuildOrigin, &GetLocalAngles(), NULL );
  529. }
  530. else
  531. {
  532. ResetPlacement();
  533. }
  534. return m_bPlacementOK;
  535. }
  536. //-----------------------------------------------------------------------------
  537. // Purpose: Cheap check to see if we are in any server-defined No-build areas.
  538. //-----------------------------------------------------------------------------
  539. bool CBaseObject::EstimateValidBuildPos( void )
  540. {
  541. // Make sure CalculatePlacementPos() has been called to setup the member variables used below
  542. CTFPlayer *pPlayer = GetOwner();
  543. if ( !pPlayer )
  544. return false;
  545. // Cannot build inside a nobuild brush
  546. if ( PointInNoBuild( m_vecBuildOrigin, this ) )
  547. return false;
  548. if ( PointInNoBuild( m_vecBuildCenterOfMass, this ) )
  549. return false;
  550. // If we're receiving trigger hurt damage, don't allow building here.
  551. if ( IsTakingTriggerHurtDamageAtPoint( m_vecBuildOrigin ) )
  552. return false;
  553. if ( IsTakingTriggerHurtDamageAtPoint( m_vecBuildCenterOfMass ) )
  554. return false;
  555. if ( PointInRespawnRoom( NULL, m_vecBuildOrigin ) && !g_pServerBenchmark->IsBenchmarkRunning() )
  556. return false;
  557. if ( PointInRespawnRoom( NULL, m_vecBuildCenterOfMass ) && !g_pServerBenchmark->IsBenchmarkRunning() )
  558. return false;
  559. Vector vecBuildFarEdge = m_vecBuildOrigin + m_vecBuildForward * ( m_flBuildDistance + 8.0f );
  560. if ( PointsCrossRespawnRoomVisualizer( pPlayer->WorldSpaceCenter(), vecBuildFarEdge ) )
  561. return false;
  562. return true;
  563. }
  564. //-----------------------------------------------------------------------------
  565. // Purpose:
  566. //-----------------------------------------------------------------------------
  567. void CBaseObject::DeterminePlaybackRate( void )
  568. {
  569. float flReverseBuildingConstructionSpeed = GetReversesBuildingConstructionSpeed();
  570. if ( flReverseBuildingConstructionSpeed == 0.0f )
  571. {
  572. flReverseBuildingConstructionSpeed = 1.0f;
  573. }
  574. else
  575. {
  576. flReverseBuildingConstructionSpeed *= -1.0f;
  577. }
  578. // If a sapper was added or removed part way through construction we need to invert the time to completion
  579. bool bAdjustCompleteTime = ( flReverseBuildingConstructionSpeed > 0.0f && GetPlaybackRate() < 0.0f ) ||
  580. ( flReverseBuildingConstructionSpeed < 0.0f && GetPlaybackRate() >= 0.0f );
  581. if ( IsBuilding() )
  582. {
  583. // Default half rate, author build anim as if one player is building
  584. // ConstructionMultiplier already contains the reverse
  585. SetPlaybackRate( GetConstructionMultiplier() * 0.5 );
  586. }
  587. else
  588. {
  589. SetPlaybackRate( 1.0 * flReverseBuildingConstructionSpeed );
  590. }
  591. if ( bAdjustCompleteTime )
  592. {
  593. float fRelativeCycle = ( ( flReverseBuildingConstructionSpeed > 0.0f ) ? ( 1.0f - GetCycle() ) : ( GetCycle() ) );
  594. float flUpgradeTime = ( ShouldQuickBuild() ? 0 : GetObjectInfo( ObjectType() )->m_flUpgradeDuration );
  595. flUpgradeTime /= ( ( flReverseBuildingConstructionSpeed < 0.0f ) ? ( flReverseBuildingConstructionSpeed * -1.0 ) : 1.0f );
  596. m_flUpgradeCompleteTime = gpGlobals->curtime + flUpgradeTime * fRelativeCycle;
  597. m_flTotalConstructionTime = m_flConstructionTimeLeft = GetTotalTime();
  598. float flNewConstructionTimeLeft = m_flConstructionTimeLeft * fRelativeCycle;
  599. m_flConstructionTimeLeft *= fRelativeCycle;
  600. m_flConstructionStartTime += m_flConstructionTimeLeft - flNewConstructionTimeLeft;
  601. m_flConstructionTimeLeft = flNewConstructionTimeLeft;
  602. }
  603. if ( !(m_fObjectFlags & OF_DOESNT_HAVE_A_MODEL) )
  604. {
  605. StudioFrameAdvance();
  606. }
  607. }
  608. //-----------------------------------------------------------------------------
  609. // Purpose:
  610. //-----------------------------------------------------------------------------
  611. CTFPlayer *CBaseObject::GetOwner()
  612. {
  613. return m_hBuilder;
  614. }
  615. //-----------------------------------------------------------------------------
  616. // Purpose:
  617. //-----------------------------------------------------------------------------
  618. void CBaseObject::SetBuilder( CTFPlayer *pBuilder )
  619. {
  620. TRACE_OBJECT( UTIL_VarArgs( "%0.2f CBaseObject::SetBuilder builder %s\n", gpGlobals->curtime,
  621. pBuilder ? pBuilder->GetPlayerName() : "NULL" ) );
  622. m_hBuilder = pBuilder;
  623. }
  624. //-----------------------------------------------------------------------------
  625. // Purpose:
  626. //-----------------------------------------------------------------------------
  627. int CBaseObject::ObjectType( ) const
  628. {
  629. return m_iObjectType;
  630. }
  631. //-----------------------------------------------------------------------------
  632. // Purpose: Destroys the object, gives a chance to spawn an explosion
  633. //-----------------------------------------------------------------------------
  634. void CBaseObject::DetonateObject( void )
  635. {
  636. // Blow us up.
  637. CTakeDamageInfo info( this, this, vec3_origin, GetAbsOrigin(), 0, DMG_GENERIC );
  638. Killed( info );
  639. }
  640. //-----------------------------------------------------------------------------
  641. // Purpose: Remove this object from it's team and mark for deletion
  642. //-----------------------------------------------------------------------------
  643. void CBaseObject::DestroyObject( void )
  644. {
  645. TRACE_OBJECT( UTIL_VarArgs( "%0.2f CBaseObject::DestroyObject %p:%s\n", gpGlobals->curtime, this, GetClassname() ) );
  646. // If we are carried, uncarry us before destruction.
  647. if ( IsCarried() && GetBuilder() )
  648. {
  649. DropCarriedObject( GetBuilder() );
  650. CTFWeaponBuilder *pBuilder = dynamic_cast<CTFWeaponBuilder*>( GetBuilder()->Weapon_OwnsThisID( TF_WEAPON_BUILDER ) );
  651. if ( pBuilder )
  652. {
  653. pBuilder->SwitchOwnersWeaponToLast();
  654. }
  655. }
  656. if ( GetBuilder() )
  657. {
  658. GetBuilder()->OwnedObjectDestroyed( this );
  659. }
  660. UTIL_Remove( this );
  661. DestroyScreens();
  662. }
  663. //-----------------------------------------------------------------------------
  664. // Purpose: Remove any screens that are active on this object
  665. //-----------------------------------------------------------------------------
  666. void CBaseObject::DestroyScreens( void )
  667. {
  668. // Kill the control panels
  669. int i;
  670. for ( i = m_hScreens.Count(); --i >= 0; )
  671. {
  672. DestroyVGuiScreen( m_hScreens[i].Get() );
  673. }
  674. m_hScreens.RemoveAll();
  675. }
  676. //-----------------------------------------------------------------------------
  677. // Purpose: Get the total time it will take to build this object
  678. //-----------------------------------------------------------------------------
  679. float CBaseObject::GetTotalTime( void )
  680. {
  681. float flBuildTime = GetObjectInfo( ObjectType() )->m_flBuildTime;
  682. CTFPlayer *pTFBuilder= GetBuilder();
  683. if ( pTFBuilder )
  684. {
  685. CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pTFBuilder, flBuildTime, mod_build_rate );
  686. }
  687. if ( tf_fastbuild.GetInt() )
  688. {
  689. flBuildTime = MIN( 2.f, flBuildTime );
  690. }
  691. // quick builds for engineers in Mann Vs Machine mode during setup time
  692. if ( TFGameRules()->IsQuickBuildTime() )
  693. {
  694. flBuildTime = MIN( 1.0f, flBuildTime );
  695. }
  696. return flBuildTime;
  697. }
  698. //-----------------------------------------------------------------------------
  699. // Purpose: Start placing the object
  700. //-----------------------------------------------------------------------------
  701. void CBaseObject::StartPlacement( CTFPlayer *pPlayer )
  702. {
  703. AddSolidFlags( FSOLID_NOT_SOLID );
  704. m_bPlacing = true;
  705. m_bBuilding = false;
  706. if ( pPlayer )
  707. {
  708. SetBuilder( pPlayer );
  709. ChangeTeam( pPlayer->GetTeamNumber() );
  710. }
  711. // needed?
  712. m_nRenderMode = kRenderNormal;
  713. // Set my build size
  714. CollisionProp()->WorldSpaceAABB( &m_vecBuildMins.GetForModify(), &m_vecBuildMaxs.GetForModify() );
  715. m_vecBuildMins -= Vector( 4,4,0 );
  716. m_vecBuildMaxs += Vector( 4,4,0 );
  717. m_vecBuildMins -= GetAbsOrigin();
  718. m_vecBuildMaxs -= GetAbsOrigin();
  719. // Set the skin
  720. m_nSkin = ( GetTeamNumber() == TF_TEAM_RED ) ? 0 : 1;
  721. }
  722. //-----------------------------------------------------------------------------
  723. // Purpose: Stop placing the object
  724. //-----------------------------------------------------------------------------
  725. void CBaseObject::StopPlacement( void )
  726. {
  727. UTIL_Remove( this );
  728. }
  729. //-----------------------------------------------------------------------------
  730. // Purpose: Find the nearest buildpoint on the specified entity
  731. //-----------------------------------------------------------------------------
  732. bool CBaseObject::FindNearestBuildPoint( CBaseEntity *pEntity, CBasePlayer *pBuilder, float &flNearestPoint, Vector &vecNearestBuildPoint, bool bIgnoreChecks )
  733. {
  734. bool bFoundPoint = false;
  735. IHasBuildPoints *pBPInterface = dynamic_cast<IHasBuildPoints*>(pEntity);
  736. Assert( pBPInterface );
  737. // Any empty buildpoints?
  738. for ( int i = 0; i < pBPInterface->GetNumBuildPoints(); i++ )
  739. {
  740. // Can this object build on this point?
  741. if ( pBPInterface->CanBuildObjectOnBuildPoint( i, GetType() ) )
  742. {
  743. // Close to this point?
  744. Vector vecBPOrigin;
  745. QAngle vecBPAngles;
  746. if ( pBPInterface->GetBuildPoint(i, vecBPOrigin, vecBPAngles) )
  747. {
  748. if ( !bIgnoreChecks )
  749. {
  750. // ignore build points outside our view
  751. if ( !pBuilder->FInViewCone( vecBPOrigin ) )
  752. continue;
  753. // Do a trace to make sure we don't place attachments through things (players, world, etc...)
  754. Vector vecStart = pBuilder->EyePosition();
  755. trace_t trace;
  756. CTraceFilterNoNPCsOrPlayer ignorePlayersFilter( pBuilder, COLLISION_GROUP_NONE );
  757. UTIL_TraceLine( vecStart, vecBPOrigin, MASK_SOLID, &ignorePlayersFilter, &trace );
  758. if ( trace.m_pEnt != pEntity && trace.fraction != 1.0 )
  759. continue;
  760. }
  761. float flDist = (vecBPOrigin - pBuilder->GetAbsOrigin()).Length();
  762. // if this is closer, or is the first one in our view, check it out
  763. if ( bIgnoreChecks || ( flDist < MIN(flNearestPoint, pBPInterface->GetMaxSnapDistance( i )) ) )
  764. {
  765. flNearestPoint = flDist;
  766. vecNearestBuildPoint = vecBPOrigin;
  767. m_hBuiltOnEntity = pEntity;
  768. m_iBuiltOnPoint = i;
  769. // Set our angles to the buildpoint's angles
  770. SetAbsAngles( vecBPAngles );
  771. bFoundPoint = true;
  772. }
  773. }
  774. }
  775. }
  776. return bFoundPoint;
  777. }
  778. //-----------------------------------------------------------------------------
  779. // Purpose: Find a buildpoint on the specified player
  780. //-----------------------------------------------------------------------------
  781. bool CBaseObject::FindBuildPointOnPlayer( CTFPlayer *pTFPlayer, CBasePlayer *pBuilder, float &flNearestPoint, Vector &vecNearestBuildPoint )
  782. {
  783. bool bFoundPoint = false;
  784. if ( !pTFPlayer )
  785. return false;
  786. if ( pTFPlayer->m_Shared.InCond( TF_COND_SAPPED ) )
  787. return false;
  788. if ( pTFPlayer->m_Shared.IsInvulnerable() )
  789. return false;
  790. if ( pTFPlayer->m_Shared.InCond( TF_COND_PHASE ) )
  791. return false;
  792. Vector vecOrigin = pTFPlayer->GetAbsOrigin();
  793. QAngle vecAngles = pTFPlayer->GetAbsAngles();
  794. float flDist = ( vecOrigin - pBuilder->GetAbsOrigin() ).Length();
  795. if ( flDist <= 160.f )
  796. {
  797. flNearestPoint = flDist;
  798. vecNearestBuildPoint = vecOrigin;
  799. m_hBuiltOnEntity = (CBaseEntity *)pTFPlayer;
  800. // Set our angles to the buildpoint's angles
  801. SetAbsAngles( vecAngles );
  802. bFoundPoint = true;
  803. }
  804. return bFoundPoint;
  805. }
  806. /*
  807. class CTraceFilterIgnorePlayers : public CTraceFilterSimple
  808. {
  809. public:
  810. // It does have a base, but we'll never network anything below here..
  811. DECLARE_CLASS( CTraceFilterIgnorePlayers, CTraceFilterSimple );
  812. CTraceFilterIgnorePlayers( const IHandleEntity *passentity, int collisionGroup )
  813. : CTraceFilterSimple( passentity, collisionGroup )
  814. {
  815. }
  816. virtual bool ShouldHitEntity( IHandleEntity *pServerEntity, int contentsMask )
  817. {
  818. CBaseEntity *pEntity = EntityFromEntityHandle( pServerEntity );
  819. if ( pEntity->IsPlayer() )
  820. {
  821. return false;
  822. }
  823. return true;
  824. }
  825. };
  826. //-----------------------------------------------------------------------------
  827. // Purpose: Test around this build position to make sure it does not block a path
  828. //-----------------------------------------------------------------------------
  829. bool CBaseObject::TestPositionForPlayerBlock( Vector vecBuildOrigin, CBasePlayer *pPlayer )
  830. {
  831. // find out the status of the 8 regions around this position
  832. int i;
  833. bool bNodeVisited[8];
  834. bool bNodeClear[8];
  835. // The first zone that is clear of obstructions
  836. int iFirstClear = -1;
  837. Vector vHalfPlayerDims = (VEC_HULL_MAX - VEC_HULL_MIN) * 0.5f;
  838. Vector vBuildDims = m_vecBuildMaxs - m_vecBuildMins;
  839. Vector vHalfBuildDims = vBuildDims * 0.5;
  840. // the locations of the 8 test positions
  841. // boxes are adjacent to the object box and are at least as large as
  842. // a player to ensure that a player can pass this location
  843. // 0 1 2
  844. // 7 X 3
  845. // 6 5 4
  846. static int iPositions[8][2] =
  847. {
  848. { -1, -1 },
  849. { 0, -1 },
  850. { 1, -1 },
  851. { 1, 0 },
  852. { 1, 1 },
  853. { 0, 1 },
  854. { -1, 1 },
  855. { -1, 0 }
  856. };
  857. CTraceFilterIgnorePlayers traceFilter( this, COLLISION_GROUP_NONE );
  858. for ( i=0;i<8;i++ )
  859. {
  860. // mark them all as unvisited
  861. bNodeVisited[i] = false;
  862. Vector vecTest = vecBuildOrigin;
  863. vecTest.x += ( iPositions[i][0] * ( vHalfBuildDims.x + vHalfPlayerDims.x ) );
  864. vecTest.y += ( iPositions[i][1] * ( vHalfBuildDims.y + vHalfPlayerDims.y ) );
  865. trace_t trace;
  866. UTIL_TraceHull( vecTest, vecTest, VEC_HULL_MIN, VEC_HULL_MAX, MASK_SOLID_BRUSHONLY, &traceFilter, &trace );
  867. bNodeClear[i] = ( trace.fraction == 1 && trace.allsolid != 1 && (trace.startsolid != 1) );
  868. // NDebugOverlay::Box( vecTest, VEC_HULL_MIN, VEC_HULL_MAX, bNodeClear[i] ? 0 : 255, bNodeClear[i] ? 255 : 0, 0, 20, 0.1 );
  869. // Store off the first clear location
  870. if ( iFirstClear < 0 && bNodeClear[i] )
  871. {
  872. iFirstClear = i;
  873. }
  874. }
  875. if ( iFirstClear < 0 )
  876. {
  877. // no clear space
  878. return false;
  879. }
  880. // visit all nodes that are adjacent
  881. RecursiveTestBuildSpace( iFirstClear, bNodeClear, bNodeVisited );
  882. // if we still have unvisited nodes, return false
  883. // unvisited nodes means that one or more nodes was unreachable from our start position
  884. // ie, two places the player might want to traverse but would not be able to if we built here
  885. for ( i=0;i<8;i++ )
  886. {
  887. if ( bNodeVisited[i] == false && bNodeClear[i] == true )
  888. {
  889. return false;
  890. }
  891. }
  892. return true;
  893. }
  894. //-----------------------------------------------------------------------------
  895. // Purpose: Test around the build position, one quadrant at a time
  896. //-----------------------------------------------------------------------------
  897. void CBaseObject::RecursiveTestBuildSpace( int iNode, bool *bNodeClear, bool *bNodeVisited )
  898. {
  899. // if the node is visited already
  900. if ( bNodeVisited[iNode] == true )
  901. return;
  902. // if the test node is blocked
  903. if ( bNodeClear[iNode] == false )
  904. return;
  905. bNodeVisited[iNode] = true;
  906. int iLeftNode = iNode - 1;
  907. if ( iLeftNode < 0 )
  908. iLeftNode = 7;
  909. RecursiveTestBuildSpace( iLeftNode, bNodeClear, bNodeVisited );
  910. int iRightNode = ( iNode + 1 ) % 8;
  911. RecursiveTestBuildSpace( iRightNode, bNodeClear, bNodeVisited );
  912. }
  913. */
  914. //-----------------------------------------------------------------------------
  915. // Purpose: Move the placement model to the current position. Return false if it's an invalid position
  916. //-----------------------------------------------------------------------------
  917. bool CBaseObject::UpdatePlacement( void )
  918. {
  919. if ( MustBeBuiltOnAttachmentPoint() )
  920. {
  921. return UpdateAttachmentPlacement();
  922. }
  923. // Finds bsp-valid place for building to be built
  924. // Checks for validity, nearby to other entities, in line of sight
  925. m_bPlacementOK = IsPlacementPosValid();
  926. Teleport( &m_vecBuildOrigin, &GetLocalAngles(), NULL );
  927. return m_bPlacementOK;
  928. }
  929. //-----------------------------------------------------------------------------
  930. // Purpose: See if we should be snapping to a build position
  931. //-----------------------------------------------------------------------------
  932. bool CBaseObject::FindSnapToBuildPos( CBaseObject *pObjectOverride )
  933. {
  934. if ( !MustBeBuiltOnAttachmentPoint() )
  935. return false;
  936. CTFPlayer *pPlayer = GetOwner();
  937. if ( !pPlayer )
  938. {
  939. return false;
  940. }
  941. bool bSnappedToPoint = false;
  942. bool bShouldAttachToParent = false;
  943. Vector vecNearestBuildPoint = vec3_origin;
  944. // See if there are any nearby build positions to snap to
  945. float flNearestPoint = 9999;
  946. int i;
  947. bool bHostileAttachment = IsHostileUpgrade();
  948. int iMyTeam = GetTeamNumber();
  949. if ( !pObjectOverride )
  950. {
  951. int nTeamCount = TFTeamMgr()->GetTeamCount();
  952. for ( int iTeam = FIRST_GAME_TEAM; iTeam < nTeamCount; ++iTeam )
  953. {
  954. // Hostile attachments look for enemy objects only
  955. if ( bHostileAttachment )
  956. {
  957. if ( iTeam == iMyTeam )
  958. {
  959. continue;
  960. }
  961. }
  962. // Friendly attachments look for friendly objects only
  963. else if ( iTeam != iMyTeam )
  964. {
  965. continue;
  966. }
  967. CTFTeam *pTeam = ( CTFTeam * )GetGlobalTeam( iTeam );
  968. if ( !pTeam )
  969. continue;
  970. // See if we're allowed to build on Robots
  971. if ( TFGameRules() && TFGameRules()->GameModeUsesMiniBosses() &&
  972. GetType() == OBJ_ATTACHMENT_SAPPER && !pPlayer->IsBot() )
  973. {
  974. CUtlVector< CTFPlayer * > playerVector;
  975. CollectPlayers( &playerVector, pPlayer->GetOpposingTFTeam()->GetTeamNumber(), COLLECT_ONLY_LIVING_PLAYERS );
  976. FOR_EACH_VEC( playerVector, i )
  977. {
  978. if ( !playerVector[i]->IsBot() )
  979. continue;
  980. if ( FindBuildPointOnPlayer( playerVector[i], pPlayer, flNearestPoint, vecNearestBuildPoint ) )
  981. {
  982. bSnappedToPoint = true;
  983. bShouldAttachToParent = true;
  984. }
  985. }
  986. }
  987. // look for nearby buildpoints on other objects
  988. for ( i = 0; i < pTeam->GetNumObjects(); i++ )
  989. {
  990. CBaseObject *pObject = pTeam->GetObject(i);
  991. Assert( pObject );
  992. if ( pObject && !pObject->IsPlacing() )
  993. {
  994. if ( FindNearestBuildPoint( pObject, pPlayer, flNearestPoint, vecNearestBuildPoint ) )
  995. {
  996. bSnappedToPoint = true;
  997. bShouldAttachToParent = true;
  998. }
  999. }
  1000. }
  1001. }
  1002. }
  1003. else
  1004. {
  1005. if ( !pObjectOverride->IsPlacing() )
  1006. {
  1007. if ( FindNearestBuildPoint( pObjectOverride, pPlayer, flNearestPoint, vecNearestBuildPoint, true ) )
  1008. {
  1009. bSnappedToPoint = true;
  1010. bShouldAttachToParent = true;
  1011. }
  1012. }
  1013. }
  1014. if ( !bSnappedToPoint )
  1015. {
  1016. AddEffects( EF_NODRAW );
  1017. }
  1018. else
  1019. {
  1020. RemoveEffects( EF_NODRAW );
  1021. if ( bShouldAttachToParent )
  1022. {
  1023. AttachObjectToObject( m_hBuiltOnEntity.Get(), m_iBuiltOnPoint, vecNearestBuildPoint );
  1024. }
  1025. m_vecBuildOrigin = vecNearestBuildPoint;
  1026. }
  1027. return bSnappedToPoint;
  1028. }
  1029. //-----------------------------------------------------------------------------
  1030. // Purpose: Are we currently in a buildable position
  1031. //-----------------------------------------------------------------------------
  1032. bool CBaseObject::IsValidPlacement( void ) const
  1033. {
  1034. return m_bPlacementOK;
  1035. }
  1036. //-----------------------------------------------------------------------------
  1037. // Purpose:
  1038. //-----------------------------------------------------------------------------
  1039. const char *CBaseObject::GetResponseRulesModifier( void )
  1040. {
  1041. switch ( GetType() )
  1042. {
  1043. case OBJ_DISPENSER: return "objtype:dispenser"; break;
  1044. case OBJ_TELEPORTER: return "objtype:teleporter"; break;
  1045. case OBJ_SENTRYGUN: return "objtype:sentrygun"; break;
  1046. case OBJ_ATTACHMENT_SAPPER: return "objtype:sapper"; break;
  1047. default:
  1048. break;
  1049. }
  1050. return NULL;
  1051. }
  1052. //-----------------------------------------------------------------------------
  1053. // Purpose: Start building the object
  1054. //-----------------------------------------------------------------------------
  1055. bool CBaseObject::StartBuilding( CBaseEntity *pBuilder )
  1056. {
  1057. // Need to add the object to the team now...
  1058. CTFTeam *pTFTeam = ( CTFTeam * )GetGlobalTeam( GetTeamNumber() );
  1059. // Deduct the cost from the player
  1060. if ( pBuilder && pBuilder->IsPlayer() )
  1061. {
  1062. /*
  1063. if ( ((CTFPlayer*)pBuilder)->IsPlayerClass( TF_CLASS_ENGINEER ) )
  1064. {
  1065. ((CTFPlayer*)pBuilder)->HintMessage( HINT_ENGINEER_USE_WRENCH_ONOWN );
  1066. }
  1067. */
  1068. if ( !IsCarried() )
  1069. {
  1070. if ( !ShouldQuickBuild() )
  1071. {
  1072. int iAmountPlayerPaidForMe = ((CTFPlayer*)pBuilder)->StartedBuildingObject( m_iObjectType );
  1073. if ( !iAmountPlayerPaidForMe )
  1074. {
  1075. // Player couldn't afford to pay for me, so abort
  1076. ClientPrint( (CBasePlayer*)pBuilder, HUD_PRINTCENTER, "Not enough resources.\n" );
  1077. StopPlacement();
  1078. return false;
  1079. }
  1080. }
  1081. ((CTFPlayer*)pBuilder)->SpeakConceptIfAllowed( MP_CONCEPT_BUILDING_OBJECT, GetResponseRulesModifier() );
  1082. }
  1083. else
  1084. {
  1085. m_bCarried = false;
  1086. m_bCarryDeploy = true;
  1087. m_flCarryDeployTime = gpGlobals->curtime;
  1088. SetActivity( ACT_OBJ_ASSEMBLING );
  1089. ((CTFPlayer*)pBuilder)->m_flCommentOnCarrying = 0.f;
  1090. ((CTFPlayer*)pBuilder)->SpeakConceptIfAllowed( MP_CONCEPT_REDEPLOY_BUILDING, GetResponseRulesModifier() );
  1091. }
  1092. }
  1093. // Check to see if we need to add this to a hierarchy. We can just do a simple ray trace from the center as
  1094. // the placement code has guarenteed we are in a valid position.
  1095. trace_t trace;
  1096. UTIL_TraceHull( GetAbsOrigin() + Vector( 0.0f, 0.0f, 2.0f ), GetAbsOrigin() - Vector( 0.0f, 0.0f, 2.0f ), vec3_origin, vec3_origin, MASK_PLAYERSOLID_BRUSHONLY, this, COLLISION_GROUP_PLAYER_MOVEMENT, &trace );
  1097. if ( trace.m_pEnt && trace.m_pEnt->IsBSPModel() )
  1098. {
  1099. CFuncTrackTrain *pTrain = dynamic_cast<CFuncTrackTrain*>( trace.m_pEnt );
  1100. if ( pTrain )
  1101. {
  1102. SetParent( pTrain );
  1103. }
  1104. }
  1105. // Add this object to the team's list (because we couldn't add it during
  1106. // placement mode)
  1107. if ( pTFTeam && !pTFTeam->IsObjectOnTeam( this ) )
  1108. {
  1109. pTFTeam->AddObject( this );
  1110. }
  1111. m_bPlacing = false;
  1112. m_bBuilding = true;
  1113. if ( m_bCarryDeploy )
  1114. {
  1115. SetHealth( m_iHealthOnPickup );
  1116. IGameEvent * event = gameeventmanager->CreateEvent( "player_dropobject" );
  1117. if ( event )
  1118. {
  1119. CTFPlayer *pTFPlayer = ToTFPlayer( pBuilder );
  1120. event->SetInt( "userid", pTFPlayer ? pTFPlayer->GetUserID() : 0 );
  1121. event->SetInt( "object", GetType() );
  1122. event->SetInt( "index", entindex() ); // object entity index
  1123. gameeventmanager->FireEvent( event, true ); // don't send to clients
  1124. }
  1125. }
  1126. else if ( IsMiniBuilding() )
  1127. {
  1128. int iHealth = GetMaxHealthForCurrentLevel();
  1129. if ( !IsDisposableBuilding() )
  1130. {
  1131. iHealth /= 2.0f;
  1132. }
  1133. SetHealth( iHealth );
  1134. }
  1135. else
  1136. {
  1137. SetHealth( OBJECT_CONSTRUCTION_STARTINGHEALTH );
  1138. }
  1139. m_flPercentageConstructed = 0;
  1140. m_nRenderMode = kRenderNormal;
  1141. RemoveSolidFlags( FSOLID_NOT_SOLID );
  1142. // NOTE: We must spawn the control panels now, instead of during
  1143. // Spawn, because until placement is started, we don't actually know
  1144. // the position of the control panel because we don't know what it's
  1145. // been attached to (could be a vehicle which supplies a different
  1146. // place for the control panel)
  1147. // NOTE: We must also spawn it before FinishedBuilding can be called
  1148. if ( !(m_fObjectFlags & OF_DOESNT_HAVE_A_MODEL) )
  1149. {
  1150. SpawnControlPanels();
  1151. }
  1152. // Tell the object we've been built on that we exist
  1153. if ( IsBuiltOnAttachment() && !m_hBuiltOnEntity->IsPlayer() )
  1154. {
  1155. IHasBuildPoints *pBPInterface = dynamic_cast<IHasBuildPoints*>((CBaseEntity*)m_hBuiltOnEntity.Get());
  1156. Assert( pBPInterface );
  1157. pBPInterface->SetObjectOnBuildPoint( m_iBuiltOnPoint, this );
  1158. }
  1159. // Start the build animations
  1160. m_flTotalConstructionTime = m_flConstructionTimeLeft = GetTotalTime();
  1161. m_flConstructionStartTime = gpGlobals->curtime;
  1162. if ( pBuilder && pBuilder->IsPlayer() )
  1163. {
  1164. CTFPlayer *pTFBuilder = ToTFPlayer( pBuilder );
  1165. pTFBuilder->FinishedObject( this );
  1166. IGameEvent * event = gameeventmanager->CreateEvent( "player_builtobject" );
  1167. if ( event )
  1168. {
  1169. event->SetInt( "userid", pTFBuilder->GetUserID() );
  1170. event->SetInt( "object", ObjectType() );
  1171. event->SetInt( "index", entindex() ); // object entity index
  1172. gameeventmanager->FireEvent( event, true ); // don't send to clients
  1173. }
  1174. }
  1175. m_vecBuildOrigin = GetAbsOrigin();
  1176. int contents = UTIL_PointContents( m_vecBuildOrigin );
  1177. if ( contents & MASK_WATER )
  1178. {
  1179. SetWaterLevel( 3 );
  1180. }
  1181. // instantly play the build anim
  1182. DetermineAnimation();
  1183. if ( IsMiniBuilding() && ( GetType() != OBJ_DISPENSER ) )
  1184. {
  1185. // Set the skin after placement mode.
  1186. m_nSkin = ( GetTeamNumber() == TF_TEAM_RED ) ? 2 : 3;
  1187. }
  1188. if ( ShouldQuickBuild() )
  1189. {
  1190. DoQuickBuild();
  1191. }
  1192. return true;
  1193. }
  1194. //-----------------------------------------------------------------------------
  1195. // Purpose:
  1196. //-----------------------------------------------------------------------------
  1197. bool CBaseObject::ShouldBeMiniBuilding( CTFPlayer* pPlayer )
  1198. {
  1199. if ( !pPlayer )
  1200. return false;
  1201. CTFWrench* pWrench = dynamic_cast<CTFWrench*>( pPlayer->Weapon_OwnsThisID( TF_WEAPON_WRENCH ) );
  1202. if ( !pWrench )
  1203. return false;
  1204. if ( TFGameRules()->GameModeUsesUpgrades() )
  1205. {
  1206. if ( pPlayer->GetNumObjects( OBJ_SENTRYGUN ) && pPlayer->CanBuild( OBJ_SENTRYGUN ) == CB_CAN_BUILD && !IsCarried() )
  1207. return true;
  1208. }
  1209. if ( !pWrench->IsPDQ() )
  1210. return false;
  1211. return true;
  1212. }
  1213. //-----------------------------------------------------------------------------
  1214. // Purpose:
  1215. //-----------------------------------------------------------------------------
  1216. void CBaseObject::MakeMiniBuilding( CTFPlayer* pPlayer )
  1217. {
  1218. if ( !ShouldBeMiniBuilding( pPlayer ) || IsMiniBuilding() )
  1219. return;
  1220. m_bMiniBuilding = true;
  1221. }
  1222. //-----------------------------------------------------------------------------
  1223. // Purpose:
  1224. //-----------------------------------------------------------------------------
  1225. void CBaseObject::MakeDisposableBuilding( CTFPlayer *pPlayer )
  1226. {
  1227. m_bDisposableBuilding = true;
  1228. }
  1229. //-----------------------------------------------------------------------------
  1230. // Purpose: Continue construction of this object
  1231. //-----------------------------------------------------------------------------
  1232. void CBaseObject::BuildingThink( void )
  1233. {
  1234. // Continue construction
  1235. Construct( (GetMaxHealth() - OBJECT_CONSTRUCTION_STARTINGHEALTH) / m_flTotalConstructionTime * OBJECT_CONSTRUCTION_INTERVAL );
  1236. }
  1237. //-----------------------------------------------------------------------------
  1238. // Purpose:
  1239. //-----------------------------------------------------------------------------
  1240. void CBaseObject::SetControlPanelsActive( bool bState )
  1241. {
  1242. // Activate control panel screens
  1243. for ( int i = m_hScreens.Count(); --i >= 0; )
  1244. {
  1245. if (m_hScreens[i].Get())
  1246. {
  1247. m_hScreens[i]->SetActive( bState );
  1248. }
  1249. }
  1250. }
  1251. //-----------------------------------------------------------------------------
  1252. // Purpose:
  1253. //-----------------------------------------------------------------------------
  1254. void CBaseObject::FinishedBuilding( void )
  1255. {
  1256. SetControlPanelsActive( true );
  1257. // Only make a shadow if the object doesn't use vphysics
  1258. if (!VPhysicsGetObject())
  1259. {
  1260. VPhysicsInitStatic();
  1261. }
  1262. m_bBuilding = false;
  1263. OnGoActive();
  1264. // We're done building, add in the stat...
  1265. ////TFStats()->IncrementStat( (TFStatId_t)(TF_STAT_FIRST_OBJECT_BUILT + ObjectType()), 1 );
  1266. // Spawn any objects on this one
  1267. SpawnObjectPoints();
  1268. if ( IsUsingReverseBuild() )
  1269. {
  1270. // if we don't have a sapper (but we should!) then set ourselves as the damager
  1271. CObjectSapper *pSapper = GetSapper();
  1272. CBaseEntity *pDamager = pSapper ? pSapper : this;
  1273. int iCustomDamageType = pSapper ? TF_DMG_CUSTOM_SAPPER_RECORDER_DEATH : 0;
  1274. CTakeDamageInfo info;
  1275. info.SetInflictor( pDamager );
  1276. info.SetAttacker( pDamager );
  1277. info.SetDamageForce( vec3_origin );
  1278. info.SetDamagePosition( GetAbsOrigin() );
  1279. info.SetDamage( 0 );
  1280. info.SetDamageType( DMG_CRUSH );
  1281. info.SetDamageCustom( iCustomDamageType );
  1282. Killed( info );
  1283. }
  1284. }
  1285. //-----------------------------------------------------------------------------
  1286. // Purpose: Objects store health in hacky ways
  1287. //-----------------------------------------------------------------------------
  1288. void CBaseObject::SetHealth( float flHealth )
  1289. {
  1290. if ( m_bCarryDeploy && (flHealth>m_iHealthOnPickup) )
  1291. {
  1292. // If we are re-deploying after being carried we shouldn't gain more health than we had
  1293. // on pickup until the deploy process is finished.
  1294. flHealth = m_iHealthOnPickup;
  1295. }
  1296. bool changed = m_flHealth != flHealth;
  1297. m_flHealth = flHealth;
  1298. m_iHealth = ceil(m_flHealth);
  1299. /*
  1300. // If we a pose parameter, set the pose parameter to reflect our health
  1301. if ( LookupPoseParameter( "object_health") >= 0 && GetMaxHealth() > 0 )
  1302. {
  1303. SetPoseParameter( "object_health", 100 * ( GetHealth() / (float)GetMaxHealth() ) );
  1304. }
  1305. */
  1306. if ( changed )
  1307. {
  1308. // Set value and fire output
  1309. m_OnObjectHealthChanged.Set( m_flHealth, this, this );
  1310. }
  1311. }
  1312. //-----------------------------------------------------------------------------
  1313. // Purpose: Override base traceattack to prevent visible effects from team members shooting me
  1314. //-----------------------------------------------------------------------------
  1315. void CBaseObject::TraceAttack( const CTakeDamageInfo &inputInfo, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator )
  1316. {
  1317. // Prevent team damage here so blood doesn't appear
  1318. if ( inputInfo.GetAttacker() )
  1319. {
  1320. if ( InSameTeam(inputInfo.GetAttacker()) )
  1321. {
  1322. // Pass Damage to enemy attachments
  1323. int iNumObjects = GetNumObjectsOnMe();
  1324. for ( int iPoint=iNumObjects-1;iPoint >= 0; --iPoint )
  1325. {
  1326. CBaseObject *pObject = GetBuildPointObject( iPoint );
  1327. if ( pObject && pObject->IsHostileUpgrade() )
  1328. {
  1329. pObject->TraceAttack(inputInfo, vecDir, ptr, pAccumulator );
  1330. }
  1331. }
  1332. return;
  1333. }
  1334. }
  1335. SpawnBlood( ptr->endpos, vecDir, BloodColor(), inputInfo.GetDamage() );
  1336. AddMultiDamage( inputInfo, this );
  1337. }
  1338. //-----------------------------------------------------------------------------
  1339. // Purpose: Prevent Team Damage
  1340. //-----------------------------------------------------------------------------
  1341. ConVar object_show_damage( "obj_show_damage", "0", 0, "Show all damage taken by objects." );
  1342. ConVar object_capture_damage( "obj_capture_damage", "0", 0, "Captures all damage taken by objects for dumping later." );
  1343. CUtlDict<int,int> g_DamageMap;
  1344. void Cmd_DamageDump_f(void)
  1345. {
  1346. CUtlDict<bool,int> g_UniqueColumns;
  1347. int idx;
  1348. // Build the unique columns:
  1349. for( idx = g_DamageMap.First(); idx != g_DamageMap.InvalidIndex(); idx = g_DamageMap.Next(idx) )
  1350. {
  1351. char* szColumnName = strchr(g_DamageMap.GetElementName(idx),',') + 1;
  1352. int ColumnIdx = g_UniqueColumns.Find( szColumnName );
  1353. if( ColumnIdx == g_UniqueColumns.InvalidIndex() )
  1354. {
  1355. g_UniqueColumns.Insert( szColumnName, false );
  1356. }
  1357. }
  1358. // Dump the column names:
  1359. FileHandle_t f = filesystem->Open("damage.txt","wt+");
  1360. for( idx = g_UniqueColumns.First(); idx != g_UniqueColumns.InvalidIndex(); idx = g_UniqueColumns.Next(idx) )
  1361. {
  1362. filesystem->FPrintf(f,"\t%s",g_UniqueColumns.GetElementName(idx));
  1363. }
  1364. filesystem->FPrintf(f,"\n");
  1365. CUtlDict<bool,int> g_CompletedRows;
  1366. // Dump each row:
  1367. bool bDidRow;
  1368. do
  1369. {
  1370. bDidRow = false;
  1371. for( idx = g_DamageMap.First(); idx != g_DamageMap.InvalidIndex(); idx = g_DamageMap.Next(idx) )
  1372. {
  1373. char szRowName[256];
  1374. // Check the Row name of each entry to see if I've done this row yet.
  1375. Q_strncpy(szRowName, g_DamageMap.GetElementName(idx), sizeof( szRowName ) );
  1376. *strchr(szRowName,',') = '\0';
  1377. char szRowNameComma[256];
  1378. Q_snprintf( szRowNameComma, sizeof( szRowNameComma ), "%s,", szRowName );
  1379. if( g_CompletedRows.Find(szRowName) == g_CompletedRows.InvalidIndex() )
  1380. {
  1381. bDidRow = true;
  1382. g_CompletedRows.Insert(szRowName,false);
  1383. // Output the row name:
  1384. filesystem->FPrintf(f,szRowName);
  1385. for( int ColumnIdx = g_UniqueColumns.First(); ColumnIdx != g_UniqueColumns.InvalidIndex(); ColumnIdx = g_UniqueColumns.Next( ColumnIdx ) )
  1386. {
  1387. char szRowNameCommaColumn[256];
  1388. Q_strncpy( szRowNameCommaColumn, szRowNameComma, sizeof( szRowNameCommaColumn ) );
  1389. Q_strncat( szRowNameCommaColumn, g_UniqueColumns.GetElementName( ColumnIdx ), sizeof( szRowNameCommaColumn ), COPY_ALL_CHARACTERS );
  1390. int nDamageAmount = 0;
  1391. // Fine to reuse idx since we are going to break anyways.
  1392. for( idx = g_DamageMap.First(); idx != g_DamageMap.InvalidIndex(); idx = g_DamageMap.Next(idx) )
  1393. {
  1394. if( !stricmp( g_DamageMap.GetElementName(idx), szRowNameCommaColumn ) )
  1395. {
  1396. nDamageAmount = g_DamageMap[idx];
  1397. break;
  1398. }
  1399. }
  1400. filesystem->FPrintf(f,"\t%i",nDamageAmount);
  1401. }
  1402. filesystem->FPrintf(f,"\n");
  1403. break;
  1404. }
  1405. }
  1406. // Grab the row name:
  1407. } while(bDidRow);
  1408. // close the file:
  1409. filesystem->Close(f);
  1410. }
  1411. static ConCommand obj_dump_damage( "obj_dump_damage", Cmd_DamageDump_f );
  1412. //-----------------------------------------------------------------------------
  1413. // Purpose:
  1414. //-----------------------------------------------------------------------------
  1415. void ReportDamage( const char* szInflictor, const char* szVictim, float fAmount, int nCurrent, int nMax )
  1416. {
  1417. int iAmount = (int)fAmount;
  1418. if( object_show_damage.GetBool() && iAmount )
  1419. {
  1420. Msg( "ShowDamage: Object %s taking %0.1f damage from %s ( %i / %i )\n", szVictim, fAmount, szInflictor, nCurrent, nMax );
  1421. }
  1422. if( object_capture_damage.GetBool() )
  1423. {
  1424. char szMangledKey[256];
  1425. Q_snprintf(szMangledKey,sizeof(szMangledKey)/sizeof(szMangledKey[0]),"%s,%s",szInflictor,szVictim);
  1426. int idx = g_DamageMap.Find( szMangledKey );
  1427. if( idx == g_DamageMap.InvalidIndex() )
  1428. {
  1429. g_DamageMap.Insert( szMangledKey, iAmount );
  1430. } else
  1431. {
  1432. g_DamageMap[idx] += iAmount;
  1433. }
  1434. }
  1435. }
  1436. //-----------------------------------------------------------------------------
  1437. // Purpose: Return the first non-hostile object build on this object
  1438. //-----------------------------------------------------------------------------
  1439. CBaseEntity *CBaseObject::GetFirstFriendlyObjectOnMe( void )
  1440. {
  1441. CBaseObject *pFirstObject = NULL;
  1442. IHasBuildPoints *pBPInterface = dynamic_cast<IHasBuildPoints*>(this);
  1443. int iNumObjects = pBPInterface->GetNumObjectsOnMe();
  1444. for ( int iPoint=0;iPoint<iNumObjects;iPoint++ )
  1445. {
  1446. CBaseObject *pObject = GetBuildPointObject( iPoint );
  1447. if ( pObject && !pObject->IsHostileUpgrade() )
  1448. {
  1449. pFirstObject = pObject;
  1450. break;
  1451. }
  1452. }
  1453. return pFirstObject;
  1454. }
  1455. //-----------------------------------------------------------------------------
  1456. // Purpose: Pass the specified amount of damage through to any objects I have built on me
  1457. //-----------------------------------------------------------------------------
  1458. bool CBaseObject::PassDamageOntoChildren( const CTakeDamageInfo &info, float *flDamageLeftOver )
  1459. {
  1460. float flDamage = info.GetDamage();
  1461. // Double the amount of damage done (and get around the child damage modifier)
  1462. flDamage *= 2;
  1463. if ( obj_child_damage_factor.GetFloat() )
  1464. {
  1465. flDamage *= (1 / obj_child_damage_factor.GetFloat());
  1466. }
  1467. // Remove blast damage because child objects (well specifically upgrades)
  1468. // want to ignore direct blast damage but still take damage from parent
  1469. CTakeDamageInfo childInfo = info;
  1470. childInfo.SetDamage( flDamage );
  1471. childInfo.SetDamageType( info.GetDamageType() & (~DMG_BLAST) );
  1472. CBaseEntity *pEntity = GetFirstFriendlyObjectOnMe();
  1473. while ( pEntity )
  1474. {
  1475. Assert( pEntity->m_takedamage != DAMAGE_NO );
  1476. // Do damage to the next object
  1477. float flDamageTaken = pEntity->OnTakeDamage( childInfo );
  1478. // If we didn't kill it, abort
  1479. CBaseObject *pObject = dynamic_cast<CBaseObject*>(pEntity);
  1480. if ( !pObject || !pObject->IsDying() )
  1481. {
  1482. const char* szInflictor = "unknown";
  1483. if( info.GetInflictor() )
  1484. szInflictor = (char*)info.GetInflictor()->GetClassname();
  1485. ReportDamage( szInflictor, GetClassname(), flDamageTaken, GetHealth(), GetMaxHealth() );
  1486. *flDamageLeftOver = flDamage;
  1487. return true;
  1488. }
  1489. // Reduce the damage and move on to the next
  1490. flDamage -= flDamageTaken;
  1491. pEntity = GetFirstFriendlyObjectOnMe();
  1492. }
  1493. *flDamageLeftOver = flDamage;
  1494. return false;
  1495. }
  1496. //-----------------------------------------------------------------------------
  1497. // Purpose:
  1498. //-----------------------------------------------------------------------------
  1499. int CBaseObject::OnTakeDamage( const CTakeDamageInfo &info )
  1500. {
  1501. if ( !IsAlive() )
  1502. return info.GetDamage();
  1503. if ( m_takedamage == DAMAGE_NO )
  1504. return 0;
  1505. if ( IsPlacing() )
  1506. return 0;
  1507. // Check teams
  1508. if ( info.GetAttacker() )
  1509. {
  1510. if ( InSameTeam(info.GetAttacker()) )
  1511. return 0;
  1512. if ( TFGameRules() && TFGameRules()->IsTruceActive() )
  1513. {
  1514. // players cannot damage buildings while a truce is active
  1515. if ( info.GetAttacker()->IsPlayer() && info.GetAttacker()->IsTruceValidForEnt() && ( ( info.GetAttacker()->GetTeamNumber() == TF_TEAM_RED ) || ( info.GetAttacker()->GetTeamNumber() == TF_TEAM_BLUE ) ) )
  1516. return 0;
  1517. }
  1518. }
  1519. m_AchievementData.AddDamagerToHistory( info.GetAttacker() );
  1520. if ( info.GetAttacker()->IsPlayer() )
  1521. {
  1522. ToTFPlayer( info.GetAttacker() )->m_AchievementData.AddTargetToHistory( this );
  1523. }
  1524. IHasBuildPoints *pBPInterface = dynamic_cast<IHasBuildPoints*>(this);
  1525. float flDamage = info.GetDamage();
  1526. // Buildings are resistant to plasma damage.
  1527. if ( info.GetDamageCustom() == TF_DMG_CUSTOM_PLASMA )
  1528. {
  1529. flDamage *= 0.2f;
  1530. }
  1531. // Charged plasma damage disables buildings for a short time.
  1532. if ( info.GetDamageCustom() == TF_DMG_CUSTOM_PLASMA_CHARGED )
  1533. {
  1534. flDamage *= 0.2f;
  1535. m_flPlasmaDisableTime = gpGlobals->curtime + PLASMA_DISABLE_TIME;
  1536. m_bPlasmaDisable = true;
  1537. UpdateDisabledState();
  1538. }
  1539. // Objects build on other objects take less damage
  1540. if ( !IsAnUpgrade() && GetParentObject() )
  1541. {
  1542. flDamage *= obj_child_damage_factor.GetFloat();
  1543. }
  1544. if (obj_damage_factor.GetFloat())
  1545. {
  1546. flDamage *= obj_damage_factor.GetFloat();
  1547. }
  1548. CTFWeaponBase *pWeapon = dynamic_cast<CTFWeaponBase *>(info.GetWeapon());
  1549. if ( pWeapon )
  1550. {
  1551. #ifdef STAGING_ONLY
  1552. // Attacker has building disabling properties
  1553. float flDisablingAttack = 0.0f;
  1554. CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pWeapon, flDisablingAttack, disable_buildings_on_hit );
  1555. if ( flDisablingAttack )
  1556. {
  1557. // do not override if existing time is longer
  1558. m_flPlasmaDisableTime = Max( gpGlobals->curtime + flDisablingAttack, m_flPlasmaDisableTime );
  1559. m_bPlasmaDisable = true;
  1560. UpdateDisabledState();
  1561. }
  1562. #endif // STAGING_ONLY
  1563. // Apply attributes that increase damage vs buildings
  1564. CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pWeapon, flDamage, mult_dmg_vs_buildings );
  1565. CTFPlayer *pAttacker = ToTFPlayer( info.GetAttacker() );
  1566. if ( pAttacker )
  1567. {
  1568. pWeapon->ApplyOnHitAttributes( NULL, pAttacker, info );
  1569. }
  1570. }
  1571. if ( TFGameRules()->IsPowerupMode() )
  1572. {
  1573. CTFPlayer *pAttacker = ToTFPlayer( info.GetAttacker() );
  1574. if ( pAttacker )
  1575. {
  1576. if ( pAttacker->m_Shared.GetCarryingRuneType() == RUNE_STRENGTH || pAttacker->m_Shared.InCond( TF_COND_RUNE_IMBALANCE ) )
  1577. {
  1578. flDamage *= 2.f;
  1579. }
  1580. if ( pAttacker->m_Shared.GetCarryingRuneType() == RUNE_KNOCKOUT )
  1581. {
  1582. flDamage *= 4.f;
  1583. }
  1584. if ( pAttacker->m_Shared.GetCarryingRuneType() == RUNE_VAMPIRE )
  1585. {
  1586. int iModHealthOnHit = flDamage;
  1587. if ( iModHealthOnHit > 0 )
  1588. {
  1589. pAttacker->TakeHealth( iModHealthOnHit, DMG_GENERIC );
  1590. }
  1591. }
  1592. }
  1593. }
  1594. bool bFriendlyObjectsAttached = false;
  1595. int iNumObjects = pBPInterface->GetNumObjectsOnMe();
  1596. for ( int iPoint=0;iPoint<iNumObjects;iPoint++ )
  1597. {
  1598. CBaseObject *pObject = GetBuildPointObject( iPoint );
  1599. if ( !pObject || pObject->IsHostileUpgrade() )
  1600. {
  1601. continue;
  1602. }
  1603. bFriendlyObjectsAttached = true;
  1604. break;
  1605. }
  1606. // Don't look, Tom Bui!
  1607. static struct
  1608. {
  1609. bool operator()( const float flHealth, const float flDamage ) const
  1610. {
  1611. return ( ( flHealth - flDamage ) < 1 );
  1612. }
  1613. } IsDamageFatal;
  1614. // Only track actual damage - not overkill
  1615. m_AchievementData.AddDamageEventToHistory( info.GetAttacker(), ( IsDamageFatal( m_flHealth, flDamage ) ) ? m_flHealth : flDamage );
  1616. // if we cannot die
  1617. if ( m_bCannotDie && IsDamageFatal( m_flHealth, flDamage ) )
  1618. {
  1619. flDamage = m_flHealth - 1;
  1620. }
  1621. // If I have objects on me, I can't be destroyed until they're gone. Ditto if I can't be killed.
  1622. bool bWillDieButCant = ( bFriendlyObjectsAttached ) && IsDamageFatal( m_flHealth, flDamage );
  1623. if ( bWillDieButCant )
  1624. {
  1625. // Soak up the damage it would take to drop us to 1 health
  1626. flDamage = flDamage - m_flHealth;
  1627. SetHealth( 1 );
  1628. // Pass leftover damage
  1629. if ( flDamage )
  1630. {
  1631. if ( PassDamageOntoChildren( info, &flDamage ) )
  1632. return flDamage;
  1633. }
  1634. }
  1635. if ( flDamage )
  1636. {
  1637. m_iLifetimeDamage += floor( MIN( flDamage, m_flHealth ) );
  1638. if ( m_iLifetimeDamage > tf_obj_damage_tank_achievement_amount.GetInt() && GetBuilder() )
  1639. {
  1640. GetBuilder()->AwardAchievement( ACHIEVEMENT_TF_ENGINEER_TANK_DAMAGE );
  1641. }
  1642. // Recheck our death possibility, because our objects may have all been blown off us by now
  1643. bWillDieButCant = ( bFriendlyObjectsAttached ) && IsDamageFatal( m_flHealth, flDamage );
  1644. if ( !bWillDieButCant )
  1645. {
  1646. // Reduce health
  1647. SetHealth( m_flHealth - flDamage );
  1648. }
  1649. }
  1650. m_OnDamaged.FireOutput(info.GetAttacker(), this);
  1651. if ( GetHealth() <= 0 )
  1652. {
  1653. if ( info.GetAttacker() )
  1654. {
  1655. //TFStats()->IncrementTeamStat( info.GetAttacker()->GetTeamNumber(), TF_TEAM_STAT_DESTROYED_OBJECT_COUNT, 1 );
  1656. //TFStats()->IncrementPlayerStat( info.GetAttacker(), TF_PLAYER_STAT_DESTROYED_OBJECT_COUNT, 1 );
  1657. }
  1658. m_lifeState = LIFE_DEAD;
  1659. m_OnDestroyed.FireOutput( info.GetAttacker(), this);
  1660. Killed( info );
  1661. // Tell our builder to speak about it
  1662. if ( m_hBuilder )
  1663. {
  1664. m_hBuilder->SpeakConceptIfAllowed( MP_CONCEPT_LOST_OBJECT, GetResponseRulesModifier() );
  1665. }
  1666. }
  1667. const char* szInflictor = "unknown";
  1668. if( info.GetInflictor() )
  1669. szInflictor = (char*)info.GetInflictor()->GetClassname();
  1670. ReportDamage( szInflictor, GetClassname(), flDamage, GetHealth(), GetMaxHealth() );
  1671. IGameEvent *event = gameeventmanager->CreateEvent( "npc_hurt" );
  1672. if ( event )
  1673. {
  1674. event->SetInt( "entindex", entindex() );
  1675. event->SetInt( "health", Max( 0, (int)GetHealth() ) );
  1676. event->SetInt( "damageamount", flDamage );
  1677. event->SetBool( "crit", ( info.GetDamageType() & DMG_CRITICAL ) ? true : false );
  1678. CTFPlayer *pTFAttacker = ToTFPlayer( info.GetAttacker() );
  1679. if ( pTFAttacker )
  1680. {
  1681. event->SetInt( "attacker_player", pTFAttacker->GetUserID() );
  1682. if ( pTFAttacker->GetActiveTFWeapon() )
  1683. {
  1684. event->SetInt( "weaponid", pTFAttacker->GetActiveTFWeapon()->GetWeaponID() );
  1685. }
  1686. else
  1687. {
  1688. event->SetInt( "weaponid", 0 );
  1689. }
  1690. }
  1691. else
  1692. {
  1693. // hurt by world
  1694. event->SetInt( "attacker_player", 0 );
  1695. event->SetInt( "weaponid", 0 );
  1696. }
  1697. gameeventmanager->FireEvent( event );
  1698. }
  1699. return flDamage;
  1700. }
  1701. //-----------------------------------------------------------------------------
  1702. // Purpose: Repair / Help-Construct this object the specified amount
  1703. //-----------------------------------------------------------------------------
  1704. bool CBaseObject::Construct( float flHealth )
  1705. {
  1706. // Multiply it by the repair rate
  1707. flHealth *= GetConstructionMultiplier();
  1708. if ( !flHealth )
  1709. return false;
  1710. if ( IsBuilding() )
  1711. {
  1712. // Reduce the construction time by the correct amount for the health passed in
  1713. float flConstructionTime = flHealth / ((GetMaxHealth() - OBJECT_CONSTRUCTION_STARTINGHEALTH) / m_flTotalConstructionTime);
  1714. if ( flConstructionTime < 0.0f )
  1715. {
  1716. flConstructionTime *= -1.0f;
  1717. }
  1718. m_flConstructionTimeLeft = MAX( 0, m_flConstructionTimeLeft - flConstructionTime);
  1719. m_flConstructionTimeLeft = clamp( m_flConstructionTimeLeft, 0.0f, m_flTotalConstructionTime );
  1720. m_flPercentageConstructed = m_flConstructionTimeLeft / m_flTotalConstructionTime;
  1721. if ( flHealth >= 0.0f )
  1722. {
  1723. // Only do this if we're not reversing construction
  1724. m_flPercentageConstructed = 1.0f - m_flPercentageConstructed;
  1725. }
  1726. m_flPercentageConstructed = clamp( (float) m_flPercentageConstructed, 0.0f, 1.0f );
  1727. // Increase health (unless it's a mini-building, which start at max health)
  1728. // Minibuildings build health at a reduced rate
  1729. // Staging_engy
  1730. {
  1731. SetHealth( MIN( GetMaxHealth(), m_flHealth + (IsMiniBuilding() ? (flHealth * 0.5f) : flHealth) ) );
  1732. }
  1733. // Return true if we're constructed now
  1734. if ( m_flConstructionTimeLeft <= 0.0f )
  1735. {
  1736. FinishedBuilding();
  1737. return true;
  1738. }
  1739. }
  1740. else
  1741. {
  1742. // Return true if we're already fully healed
  1743. if ( GetHealth() >= GetMaxHealth() )
  1744. return true;
  1745. // Increase health.
  1746. SetHealth( MIN( GetMaxHealth(), MAX( 1, m_flHealth + flHealth ) ) );
  1747. m_OnRepaired.FireOutput( this, this);
  1748. // Return true if we're fully healed now
  1749. if ( GetHealth() == GetMaxHealth() )
  1750. return true;
  1751. }
  1752. return false;
  1753. }
  1754. //----------------------------------------------------------------------------------------------------------------------------------------
  1755. void CBaseObject::OnConstructionHit( CTFPlayer *pPlayer, CTFWrench *pWrench, Vector hitLoc )
  1756. {
  1757. // Get the player index
  1758. int iPlayerIndex = pPlayer->entindex();
  1759. // The time the repair is going to expire
  1760. float flRepairExpireTime = gpGlobals->curtime + 1.0;
  1761. // Update or Add the expire time to the list
  1762. int index = m_ConstructorList.Find( iPlayerIndex );
  1763. if ( index == m_ConstructorList.InvalidIndex() )
  1764. {
  1765. index = m_ConstructorList.Insert( iPlayerIndex );
  1766. m_ConstructorList[index].flValue = pWrench->GetConstructionValue();
  1767. }
  1768. m_ConstructorList[index].flHitTime = flRepairExpireTime;
  1769. // Play a construction hit effect.
  1770. CPVSFilter filter( hitLoc );
  1771. TE_TFParticleEffect( filter, 0.0f, "nutsnbolts_build", hitLoc, QAngle(0,0,0) );
  1772. }
  1773. //----------------------------------------------------------------------------------------------------------------------------------------
  1774. float CBaseObject::GetConstructionMultiplier( void )
  1775. {
  1776. if ( IsUsingReverseBuild() )
  1777. return -1.0f;
  1778. float flMultiplier = 1.0;
  1779. // expire all the old
  1780. int i = m_ConstructorList.LastInorder();
  1781. while ( i != m_ConstructorList.InvalidIndex() )
  1782. {
  1783. int iThis = i;
  1784. i = m_ConstructorList.PrevInorder( i );
  1785. if ( m_ConstructorList[iThis].flHitTime < gpGlobals->curtime )
  1786. {
  1787. m_ConstructorList.RemoveAt( iThis );
  1788. }
  1789. else
  1790. {
  1791. // STAGING_ENGY
  1792. // each Player adds a fixed amount of speed boost
  1793. // Carry deploy hits add more
  1794. flMultiplier += ( m_ConstructorList[iThis].flValue );
  1795. }
  1796. }
  1797. // See if we have any attributes that want to modify our build rate
  1798. CTFPlayer* pBuilder = GetOwner();
  1799. if( pBuilder )
  1800. {
  1801. flMultiplier += pBuilder->GetObjectBuildSpeedMultiplier( ObjectType(), m_bCarryDeploy );
  1802. }
  1803. return flMultiplier;
  1804. }
  1805. //-----------------------------------------------------------------------------
  1806. // Purpose: Object is exploding because it was killed or detonate
  1807. //-----------------------------------------------------------------------------
  1808. void CBaseObject::Explode( void )
  1809. {
  1810. const char *pExplodeSound = GetObjectInfo( ObjectType() )->m_pExplodeSound;
  1811. if ( pExplodeSound && Q_strlen(pExplodeSound) > 0 )
  1812. {
  1813. EmitSound( pExplodeSound );
  1814. }
  1815. const char *pExplodeEffect = GetObjectInfo( ObjectType() )->m_pExplosionParticleEffect;
  1816. if ( pExplodeEffect && pExplodeEffect[0] != '\0' )
  1817. {
  1818. // Send to everyone - we're inside prediction for the engy who hit this off, but we
  1819. // don't predict that the hit will kill this object.
  1820. CDisablePredictionFiltering disabler;
  1821. Vector origin = GetAbsOrigin();
  1822. QAngle up(-90,0,0);
  1823. CPVSFilter filter( origin );
  1824. TE_TFParticleEffect( filter, 0.0f, pExplodeEffect, origin, up );
  1825. }
  1826. // create some delicious, metal filled gibs
  1827. CreateObjectGibs();
  1828. }
  1829. void CBaseObject::CreateObjectGibs( void )
  1830. {
  1831. if ( m_aGibs.Count() <= 0 )
  1832. {
  1833. return;
  1834. }
  1835. const CObjectInfo *pObjectInfo = GetObjectInfo( ObjectType() );
  1836. // grant some percentage of the cost to build if number of metal to drop is not specified
  1837. const float flMetalCostPercentage = 0.5f;
  1838. const int nTotalMetal = pObjectInfo->m_iMetalToDropInGibs == 0 ? pObjectInfo->m_Cost * flMetalCostPercentage : pObjectInfo->m_iMetalToDropInGibs;
  1839. int nMetalPerGib = nTotalMetal / m_aGibs.Count();
  1840. int nLeftOver = nTotalMetal % m_aGibs.Count();
  1841. if ( IsMiniBuilding() )
  1842. {
  1843. // STAGING_ENGY
  1844. nMetalPerGib = 0;
  1845. nLeftOver = 0;
  1846. }
  1847. int i;
  1848. for ( i=0; i<m_aGibs.Count(); i++ )
  1849. {
  1850. // make sure we drop all metal include left over from int math
  1851. CreateAmmoPack( m_aGibs[i].modelName, i == 0 ? nMetalPerGib + nLeftOver : nMetalPerGib );
  1852. }
  1853. }
  1854. CTFAmmoPack* CBaseObject::CreateAmmoPack( const char *pchModel, int nMetal )
  1855. {
  1856. CTFAmmoPack *pAmmoPack = CTFAmmoPack::Create( GetAbsOrigin(), GetAbsAngles(), this, pchModel );
  1857. Assert( pAmmoPack );
  1858. if ( pAmmoPack )
  1859. {
  1860. pAmmoPack->ActivateWhenAtRest();
  1861. // Fill up the ammo pack.
  1862. pAmmoPack->GiveAmmo( nMetal, TF_AMMO_METAL );
  1863. // Calculate the initial impulse on the weapon.
  1864. Vector vecImpulse( random->RandomFloat( -0.5, 0.5 ), random->RandomFloat( -0.5, 0.5 ), random->RandomFloat( 0.75, 1.25 ) );
  1865. VectorNormalize( vecImpulse );
  1866. vecImpulse *= random->RandomFloat( tf_obj_gib_velocity_min.GetFloat(), tf_obj_gib_velocity_max.GetFloat() );
  1867. // Cap the impulse.
  1868. float flSpeed = vecImpulse.Length();
  1869. if ( flSpeed > tf_obj_gib_maxspeed.GetFloat() )
  1870. {
  1871. VectorScale( vecImpulse, tf_obj_gib_maxspeed.GetFloat() / flSpeed, vecImpulse );
  1872. }
  1873. if ( pAmmoPack->VPhysicsGetObject() )
  1874. {
  1875. // We can probably remove this when the mass on the weapons is correct!
  1876. //pAmmoPack->VPhysicsGetObject()->SetMass( 25.0f );
  1877. AngularImpulse angImpulse( 0, random->RandomFloat( 0, 100 ), 0 );
  1878. pAmmoPack->VPhysicsGetObject()->SetVelocityInstantaneous( &vecImpulse, &angImpulse );
  1879. }
  1880. pAmmoPack->SetInitialVelocity( vecImpulse );
  1881. pAmmoPack->m_nSkin = ( GetTeamNumber() == TF_TEAM_RED ) ? 0 : 1;
  1882. // Give the ammo pack some health, so that trains can destroy it.
  1883. pAmmoPack->SetCollisionGroup( COLLISION_GROUP_DEBRIS );
  1884. pAmmoPack->m_takedamage = DAMAGE_YES;
  1885. pAmmoPack->SetHealth( 900 );
  1886. pAmmoPack->m_bObjGib = true;
  1887. if ( IsMiniBuilding() )
  1888. {
  1889. pAmmoPack->SetModelScale( 0.6f );
  1890. }
  1891. }
  1892. return pAmmoPack;
  1893. }
  1894. //-----------------------------------------------------------------------------
  1895. // Purpose: Object has been blown up. Drop resource chunks upto the value of my max health.
  1896. //-----------------------------------------------------------------------------
  1897. void CBaseObject::Killed( const CTakeDamageInfo &info )
  1898. {
  1899. m_bDying = true;
  1900. // Find the killer & the scorer
  1901. CBaseEntity *pInflictor = info.GetInflictor();
  1902. CBaseEntity *pKiller = info.GetAttacker();
  1903. CTFPlayer *pScorer = ToTFPlayer( TFGameRules()->GetDeathScorer( pKiller, pInflictor, this ) );
  1904. CTFPlayer *pAssister = NULL;
  1905. // If we are being carried,
  1906. // if this object has a sapper on it, and was not killed by the sapper (killed by damage other than crush, since sapper does crushing damage),
  1907. // award an assist to the owner of the sapper since it probably contributed to destroying this object
  1908. CObjectSapper *pSapper = GetSapper();
  1909. if ( pSapper && !( DMG_CRUSH & info.GetDamageType() ) && !m_bPlasmaDisable )
  1910. {
  1911. // give an assist to the sapper's owner
  1912. pAssister = pSapper->GetOwner();
  1913. if ( pAssister )
  1914. {
  1915. CTF_GameStats.Event_AssistDestroyBuilding( pAssister, this );
  1916. // Also increment the SapBuildings grind achievement
  1917. pAssister->AwardAchievement( ACHIEVEMENT_TF_SPY_SAPPER_GRIND );
  1918. }
  1919. }
  1920. else if ( pScorer )
  1921. {
  1922. // If a player is healing the scorer, give that player credit for the assist
  1923. CTFPlayer *pHealer = ToTFPlayer( static_cast<CBaseEntity *>( pScorer->m_Shared.GetFirstHealer() ) );
  1924. // Must be a medic to receive a healing assist, otherwise engineers get credit for assists from dispensers doing healing.
  1925. // Also don't give an assist for healing if the inflictor was a sentry gun, otherwise medics healing engineers get assists for the engineer's sentry kills.
  1926. if ( pHealer && ( pHealer->GetPlayerClass()->GetClassIndex() == TF_CLASS_MEDIC ) )
  1927. {
  1928. pAssister = pHealer;
  1929. }
  1930. }
  1931. // Don't do anything if we were detonated or dismantled
  1932. if ( pScorer && pInflictor != this )
  1933. {
  1934. IGameEvent * event = gameeventmanager->CreateEvent( "object_destroyed" );
  1935. // Work out what killed the player, and send a message to all clients about it
  1936. int iWeaponID;
  1937. const char *killer_weapon_name = TFGameRules()->GetKillingWeaponName( info, NULL, &iWeaponID );
  1938. const char *killer_weapon_log_name = killer_weapon_name;
  1939. CTFPlayer *pTFPlayer = GetOwner();
  1940. CTFWeaponBase *pWeapon = dynamic_cast< CTFWeaponBase * >( pScorer->Weapon_OwnsThisID( iWeaponID ) );
  1941. if ( pWeapon )
  1942. {
  1943. CEconItemView *pItem = pWeapon->GetAttributeContainer()->GetItem();
  1944. if ( pItem )
  1945. {
  1946. if ( pItem->GetStaticData()->GetIconClassname() )
  1947. {
  1948. killer_weapon_name = pItem->GetStaticData()->GetIconClassname();
  1949. }
  1950. if ( pItem->GetStaticData()->GetLogClassname() )
  1951. {
  1952. killer_weapon_log_name = pItem->GetStaticData()->GetLogClassname();
  1953. }
  1954. }
  1955. }
  1956. if ( event )
  1957. {
  1958. if ( pTFPlayer )
  1959. {
  1960. event->SetInt( "userid", pTFPlayer->GetUserID() );
  1961. }
  1962. if ( pAssister && ( pAssister != pScorer ) )
  1963. {
  1964. event->SetInt( "assister", pAssister->GetUserID() );
  1965. }
  1966. event->SetInt( "attacker", pScorer->GetUserID() ); // attacker
  1967. event->SetString( "weapon", killer_weapon_name );
  1968. event->SetString( "weapon_logclassname", killer_weapon_log_name );
  1969. event->SetInt( "weaponid", iWeaponID );
  1970. event->SetInt( "priority", 6 ); // HLTV event priority, not transmitted
  1971. event->SetInt( "objecttype", GetType() );
  1972. event->SetInt( "index", entindex() ); // object entity index
  1973. event->SetBool( "was_building", m_bBuilding );
  1974. gameeventmanager->FireEvent( event );
  1975. }
  1976. CTF_GameStats.Event_PlayerDestroyedBuilding( pScorer, this );
  1977. pScorer->Event_KilledOther(this, info);
  1978. // Also track stats for strange sappers.
  1979. if ( pSapper )
  1980. {
  1981. CTFPlayer *pSapperOwner = pSapper->GetOwner();
  1982. Assert( pSapperOwner );
  1983. if ( pSapperOwner )
  1984. {
  1985. EconEntity_OnOwnerKillEaterEvent( dynamic_cast<CEconEntity *>( pSapperOwner->GetEntityForLoadoutSlot( LOADOUT_POSITION_BUILDING ) ),
  1986. pSapperOwner,
  1987. GetOwner(),
  1988. kKillEaterEvent_BuildingSapped );
  1989. }
  1990. }
  1991. // Check for Demo achievement:
  1992. // Kill an Engineer building that you can't see with a direct hit from a Grenade Launcher
  1993. if ( pScorer && pScorer->IsPlayerClass( TF_CLASS_DEMOMAN) )
  1994. {
  1995. if ( pScorer->GetActiveTFWeapon() && ( pScorer->GetActiveTFWeapon()->GetWeaponID() == TF_WEAPON_GRENADELAUNCHER) )
  1996. {
  1997. if ( pInflictor && pInflictor->IsPlayer() == false )
  1998. {
  1999. CTFGrenadePipebombProjectile *pBaseGrenade = dynamic_cast< CTFGrenadePipebombProjectile* >( pInflictor );
  2000. if ( pBaseGrenade && pBaseGrenade->m_bTouched == false )
  2001. {
  2002. if ( pScorer->FVisible( this ) == false )
  2003. {
  2004. pScorer->AwardAchievement( ACHIEVEMENT_TF_DEMOMAN_KILL_BUILDING_DIRECT_HIT );
  2005. }
  2006. }
  2007. }
  2008. }
  2009. }
  2010. }
  2011. else
  2012. {
  2013. IGameEvent * event = gameeventmanager->CreateEvent( "object_detonated" );
  2014. if ( event )
  2015. {
  2016. CTFPlayer *pTFPlayer = GetOwner();
  2017. if ( pTFPlayer )
  2018. {
  2019. event->SetInt( "userid", pTFPlayer->GetUserID() );
  2020. }
  2021. event->SetInt( "objecttype", GetType() ); // object type
  2022. event->SetInt( "index", entindex() ); // object entity index
  2023. gameeventmanager->FireEvent( event );
  2024. }
  2025. }
  2026. // Don't create gibs if it reversed back to a toolbox
  2027. if ( IsUsingReverseBuild() && ( DMG_CRUSH & info.GetDamageType() ) != 0 )
  2028. {
  2029. CTFAmmoPack *pAmmoPack = CreateAmmoPack( "models/weapons/w_models/w_toolbox.mdl", GetObjectInfo( ObjectType() )->m_iMetalToDropInGibs );
  2030. if ( pAmmoPack )
  2031. {
  2032. pAmmoPack->SetBodygroup( 1, 1 );
  2033. }
  2034. CObjectSapper *pSapper = GetSapper();
  2035. if ( pSapper )
  2036. {
  2037. pSapper->Explode();
  2038. }
  2039. }
  2040. else
  2041. {
  2042. // Do an explosion.
  2043. Explode();
  2044. }
  2045. // Stats tracking for strange items.
  2046. EconEntity_OnOwnerKillEaterEvent( dynamic_cast<CEconEntity *>( info.GetWeapon() ),
  2047. pScorer,
  2048. GetOwner(),
  2049. kKillEaterEvent_BuildingDestroyed );
  2050. UTIL_Remove( this );
  2051. }
  2052. //-----------------------------------------------------------------------------
  2053. // Purpose: Indicates this NPC's place in the relationship table.
  2054. //-----------------------------------------------------------------------------
  2055. Class_T CBaseObject::Classify( void )
  2056. {
  2057. return CLASS_NONE;
  2058. }
  2059. //-----------------------------------------------------------------------------
  2060. // Purpose: Get the type of this object
  2061. //-----------------------------------------------------------------------------
  2062. int CBaseObject::GetType() const
  2063. {
  2064. return m_iObjectType;
  2065. }
  2066. //-----------------------------------------------------------------------------
  2067. // Purpose: Get the builder of this object
  2068. //-----------------------------------------------------------------------------
  2069. CTFPlayer *CBaseObject::GetBuilder( void ) const
  2070. {
  2071. return m_hBuilder;
  2072. }
  2073. //-----------------------------------------------------------------------------
  2074. // Purpose: Return true if the Owning CTeam should clean this object up automatically
  2075. //-----------------------------------------------------------------------------
  2076. bool CBaseObject::ShouldAutoRemove( void )
  2077. {
  2078. return true;
  2079. }
  2080. //-----------------------------------------------------------------------------
  2081. // Purpose:
  2082. // Input : iTeamNum -
  2083. //-----------------------------------------------------------------------------
  2084. void CBaseObject::ChangeTeam( int iTeamNum )
  2085. {
  2086. CTFTeam *pTeam = ( CTFTeam * )GetGlobalTeam( iTeamNum );
  2087. CTFTeam *pExisting = ( CTFTeam * )GetTeam();
  2088. TRACE_OBJECT( UTIL_VarArgs( "%0.2f CBaseObject::ChangeTeam old %s new %s\n", gpGlobals->curtime,
  2089. pExisting ? pExisting->GetName() : "NULL",
  2090. pTeam ? pTeam->GetName() : "NULL" ) );
  2091. // Already on this team
  2092. if ( GetTeamNumber() == iTeamNum )
  2093. return;
  2094. if ( pExisting )
  2095. {
  2096. // Remove it from current team ( if it's in one ) and give it to new team
  2097. pExisting->RemoveObject( this );
  2098. }
  2099. // Change to new team
  2100. BaseClass::ChangeTeam( iTeamNum );
  2101. // Add this object to the team's list
  2102. // But only if we're not placing it
  2103. if ( pTeam && (!m_bPlacing) )
  2104. {
  2105. pTeam->AddObject( this );
  2106. }
  2107. // Setup for our new team's model
  2108. CreateBuildPoints();
  2109. }
  2110. CObjectSapper* CBaseObject::GetSapper( void )
  2111. {
  2112. if ( !HasSapper() )
  2113. return NULL;
  2114. return dynamic_cast< CObjectSapper* >( FirstMoveChild() );
  2115. }
  2116. //-----------------------------------------------------------------------------
  2117. // Purpose: Return true if I have at least 1 sapper on me
  2118. //-----------------------------------------------------------------------------
  2119. bool CBaseObject::HasSapper( void )
  2120. {
  2121. return m_bHasSapper;
  2122. }
  2123. //-----------------------------------------------------------------------------
  2124. // Purpose:
  2125. //-----------------------------------------------------------------------------
  2126. bool CBaseObject::IsPlasmaDisabled( void )
  2127. {
  2128. return m_bPlasmaDisable;
  2129. }
  2130. //-----------------------------------------------------------------------------
  2131. void CBaseObject::OnAddSapper( void )
  2132. {
  2133. // Assume we can only build 1 sapper per object
  2134. Assert( m_bHasSapper == false );
  2135. m_bHasSapper = true;
  2136. CTFPlayer *pPlayer = GetBuilder();
  2137. if ( pPlayer )
  2138. {
  2139. //pPlayer->HintMessage( HINT_OBJECT_YOUR_OBJECT_SAPPED, true );
  2140. pPlayer->SpeakConceptIfAllowed( MP_CONCEPT_SPY_SAPPER, GetResponseRulesModifier() );
  2141. }
  2142. UpdateDisabledState();
  2143. }
  2144. //-----------------------------------------------------------------------------
  2145. void CBaseObject::OnRemoveSapper( void )
  2146. {
  2147. m_bHasSapper = false;
  2148. UpdateDisabledState();
  2149. }
  2150. //-----------------------------------------------------------------------------
  2151. int CBaseObject::GetUpgradeMetalRequired()
  2152. {
  2153. return GetObjectInfo( GetType() )->m_UpgradeCost;
  2154. }
  2155. //-----------------------------------------------------------------------------
  2156. bool CBaseObject::ShowVGUIScreen( int panelIndex, bool bShow )
  2157. {
  2158. Assert( panelIndex >= 0 && panelIndex < m_hScreens.Count() );
  2159. if ( m_hScreens[panelIndex].Get() )
  2160. {
  2161. m_hScreens[panelIndex]->SetActive( bShow );
  2162. return true;
  2163. }
  2164. else
  2165. {
  2166. return false;
  2167. }
  2168. }
  2169. //-----------------------------------------------------------------------------
  2170. // Purpose: Set the health of the object
  2171. //-----------------------------------------------------------------------------
  2172. void CBaseObject::InputSetHealth( inputdata_t &inputdata )
  2173. {
  2174. m_iMaxHealth = inputdata.value.Int();
  2175. SetHealth( m_iMaxHealth );
  2176. }
  2177. //-----------------------------------------------------------------------------
  2178. // Purpose: Add health to the object
  2179. //-----------------------------------------------------------------------------
  2180. void CBaseObject::InputAddHealth( inputdata_t &inputdata )
  2181. {
  2182. int iHealth = inputdata.value.Int();
  2183. SetHealth( MIN( GetMaxHealth(), m_flHealth + iHealth ) );
  2184. }
  2185. //-----------------------------------------------------------------------------
  2186. // Purpose: Remove health from the object
  2187. //-----------------------------------------------------------------------------
  2188. void CBaseObject::InputRemoveHealth( inputdata_t &inputdata )
  2189. {
  2190. int iDamage = inputdata.value.Int();
  2191. SetHealth( m_flHealth - iDamage );
  2192. if ( GetHealth() <= 0 )
  2193. {
  2194. m_lifeState = LIFE_DEAD;
  2195. m_OnDestroyed.FireOutput(this, this);
  2196. CTakeDamageInfo info( inputdata.pCaller, inputdata.pActivator, vec3_origin, GetAbsOrigin(), iDamage, DMG_GENERIC );
  2197. Killed( info );
  2198. }
  2199. }
  2200. //-----------------------------------------------------------------------------
  2201. // Purpose:
  2202. // Input : &inputdata -
  2203. //-----------------------------------------------------------------------------
  2204. void CBaseObject::InputSetSolidToPlayer( inputdata_t &inputdata )
  2205. {
  2206. int ival = inputdata.value.Int();
  2207. ival = clamp( ival, (int)SOLID_TO_PLAYER_USE_DEFAULT, (int)SOLID_TO_PLAYER_NO );
  2208. OBJSOLIDTYPE stp = (OBJSOLIDTYPE)ival;
  2209. SetSolidToPlayers( stp );
  2210. }
  2211. //-----------------------------------------------------------------------------
  2212. // Purpose:
  2213. // Input : &inputdata -
  2214. //-----------------------------------------------------------------------------
  2215. void CBaseObject::InputSetBuilder( inputdata_t &inputdata )
  2216. {
  2217. CTFPlayer *pPlayer = ToTFPlayer( inputdata.pActivator );
  2218. if ( GetBuilder() == NULL && pPlayer != NULL )
  2219. {
  2220. SetBuilder( pPlayer );
  2221. ChangeTeam( pPlayer->GetTeamNumber() );
  2222. pPlayer->AddObject( this );
  2223. }
  2224. }
  2225. //-----------------------------------------------------------------------------
  2226. // Purpose:
  2227. // Input : &inputdata -
  2228. //-----------------------------------------------------------------------------
  2229. void CBaseObject::InputShow( inputdata_t &inputdata )
  2230. {
  2231. RemoveEffects( EF_NODRAW );
  2232. UpdateDisabledState();
  2233. }
  2234. //-----------------------------------------------------------------------------
  2235. // Purpose:
  2236. // Input : &inputdata -
  2237. //-----------------------------------------------------------------------------
  2238. void CBaseObject::InputHide( inputdata_t &inputdata )
  2239. {
  2240. AddEffects( EF_NODRAW );
  2241. SetDisabled( true );
  2242. }
  2243. //-----------------------------------------------------------------------------
  2244. // Purpose:
  2245. // Input :
  2246. // Output : did this wrench hit do any work on the object?
  2247. //-----------------------------------------------------------------------------
  2248. bool CBaseObject::InputWrenchHit( CTFPlayer *pPlayer, CTFWrench *pWrench, Vector hitLoc )
  2249. {
  2250. Assert( pPlayer );
  2251. if ( !pPlayer )
  2252. return false;
  2253. bool bDidWork = false;
  2254. if ( HasSapper() )
  2255. {
  2256. // do damage to any attached buildings
  2257. CTakeDamageInfo info( pPlayer, pPlayer, pWrench, WRENCH_DMG_VS_SAPPER, DMG_CLUB, TF_DMG_WRENCH_FIX );
  2258. IHasBuildPoints *pBPInterface = dynamic_cast< IHasBuildPoints * >( this );
  2259. int iNumObjects = pBPInterface->GetNumObjectsOnMe();
  2260. for ( int iPoint=0;iPoint<iNumObjects;iPoint++ )
  2261. {
  2262. CBaseObject *pObject = GetBuildPointObject( iPoint );
  2263. if ( pObject && pObject->IsHostileUpgrade() )
  2264. {
  2265. int iBeforeHealth = pObject->GetHealth();
  2266. pObject->TakeDamage( info );
  2267. // This should always be true
  2268. if ( iBeforeHealth != pObject->GetHealth() )
  2269. {
  2270. bDidWork = true;
  2271. Assert( bDidWork );
  2272. }
  2273. }
  2274. }
  2275. }
  2276. else if ( IsUpgrading() )
  2277. {
  2278. bDidWork = OnWrenchHit( pPlayer, pWrench, hitLoc );
  2279. // bDidWork = false;
  2280. }
  2281. else if ( IsBuilding() )
  2282. {
  2283. OnConstructionHit( pPlayer, pWrench, hitLoc );
  2284. bDidWork = true;
  2285. }
  2286. else
  2287. {
  2288. // upgrade, refill, repair damage
  2289. bDidWork = OnWrenchHit( pPlayer, pWrench, hitLoc );
  2290. }
  2291. if ( bDidWork )
  2292. {
  2293. pPlayer->m_AchievementData.AddTargetToHistory( this );
  2294. }
  2295. return bDidWork;
  2296. }
  2297. //-----------------------------------------------------------------------------
  2298. // Purpose:
  2299. //-----------------------------------------------------------------------------
  2300. bool CBaseObject::OnWrenchHit( CTFPlayer *pPlayer, CTFWrench *pWrench, Vector hitLoc )
  2301. {
  2302. bool bRepairHit = false;
  2303. bool bUpgradeHit = false;
  2304. bRepairHit = Command_Repair( pPlayer, pWrench->GetRepairValue() );
  2305. if ( !bRepairHit )
  2306. {
  2307. bUpgradeHit = CheckUpgradeOnHit( pPlayer );
  2308. }
  2309. DoWrenchHitEffect( hitLoc, bRepairHit, bUpgradeHit );
  2310. return bUpgradeHit || bRepairHit;
  2311. }
  2312. //-----------------------------------------------------------------------------
  2313. // Purpose:
  2314. //-----------------------------------------------------------------------------
  2315. void CBaseObject::DoWrenchHitEffect( Vector hitLoc, bool bRepairHit, bool bUpgradeHit )
  2316. {
  2317. if ( bRepairHit )
  2318. {
  2319. // Play a repair hit effect.
  2320. CPVSFilter filter( hitLoc );
  2321. TE_TFParticleEffect( filter, 0.0f, "nutsnbolts_repair", hitLoc, QAngle(0,0,0) );
  2322. }
  2323. else if ( bUpgradeHit )
  2324. {
  2325. // Play an upgrade hit effect.
  2326. CPVSFilter filter( hitLoc );
  2327. TE_TFParticleEffect( filter, 0.0f, "nutsnbolts_upgrade", hitLoc, QAngle(0,0,0) );
  2328. }
  2329. }
  2330. //-----------------------------------------------------------------------------
  2331. // Purpose:
  2332. //-----------------------------------------------------------------------------
  2333. bool CBaseObject::CheckUpgradeOnHit( CTFPlayer *pPlayer )
  2334. {
  2335. if ( !CanBeUpgraded() )
  2336. return false;
  2337. if ( m_bCarryDeploy )
  2338. return false;
  2339. if ( CanBeUpgraded( pPlayer ) )
  2340. {
  2341. int iPlayerMetal = pPlayer->GetAmmoCount( TF_AMMO_METAL );
  2342. int nMaxToAdd = tf_obj_upgrade_per_hit.GetInt();
  2343. if ( TFGameRules() && TFGameRules()->IsPowerupMode() )
  2344. {
  2345. nMaxToAdd *= 2;
  2346. }
  2347. CALL_ATTRIB_HOOK_INT_ON_OTHER( pPlayer, nMaxToAdd, upgrade_rate_mod );
  2348. int iAmountToAdd = MIN( nMaxToAdd, iPlayerMetal );
  2349. if ( iAmountToAdd > ( m_iUpgradeMetalRequired - m_iUpgradeMetal ) )
  2350. iAmountToAdd = ( m_iUpgradeMetalRequired - m_iUpgradeMetal );
  2351. if ( tf_cheapobjects.GetBool() == false && !ShouldQuickBuild() )
  2352. {
  2353. pPlayer->RemoveAmmo( iAmountToAdd, TF_AMMO_METAL );
  2354. }
  2355. // testing quick builds for engineers in Raid mode
  2356. if ( TFGameRules() && !TFGameRules()->IsPVEModeControlled( pPlayer ) )
  2357. {
  2358. #ifdef TF_RAID_MODE
  2359. if ( TFGameRules()->IsRaidMode() )
  2360. {
  2361. iAmountToAdd = 200;
  2362. }
  2363. #endif
  2364. if ( TFGameRules()->GameModeUsesUpgrades() && TFGameRules()->IsQuickBuildTime() )
  2365. {
  2366. iAmountToAdd = 200;
  2367. }
  2368. }
  2369. m_iUpgradeMetal += iAmountToAdd;
  2370. bool bDidWork = false;
  2371. if ( iAmountToAdd > 0 )
  2372. {
  2373. bDidWork = true;
  2374. }
  2375. if ( m_iUpgradeMetal >= m_iUpgradeMetalRequired )
  2376. {
  2377. IGameEvent * event = gameeventmanager->CreateEvent( "player_upgradedobject" );
  2378. if ( event )
  2379. {
  2380. event->SetInt( "userid", pPlayer->GetUserID() );
  2381. event->SetInt( "object", ObjectType() );
  2382. event->SetInt( "index", entindex() );
  2383. event->SetBool( "isbuilder", pPlayer == GetBuilder() );
  2384. gameeventmanager->FireEvent( event );
  2385. }
  2386. StartUpgrading();
  2387. m_iUpgradeMetal = 0;
  2388. }
  2389. return bDidWork;
  2390. }
  2391. return false;
  2392. }
  2393. //-----------------------------------------------------------------------------
  2394. // Purpose:
  2395. //-----------------------------------------------------------------------------
  2396. bool CBaseObject::CanBeUpgraded( CTFPlayer *pPlayer )
  2397. {
  2398. // Already upgrading
  2399. if ( IsUpgrading() )
  2400. return false;
  2401. if ( IsMiniBuilding() || IsDisposableBuilding() )
  2402. return false;
  2403. // only engineers
  2404. if ( !ClassCanBuild( pPlayer->GetPlayerClass()->GetClassIndex(), GetType() ) )
  2405. return false;
  2406. // max upgraded
  2407. if ( m_iUpgradeLevel >= OBJ_MAX_UPGRADE_LEVEL )
  2408. return false;
  2409. return true;
  2410. }
  2411. //-----------------------------------------------------------------------------
  2412. // Purpose: Separated so it can be triggered by wrench hit or by vgui screen
  2413. //-----------------------------------------------------------------------------
  2414. bool CBaseObject::Command_Repair( CTFPlayer *pActivator, float flRepairMod )
  2415. {
  2416. if ( !CanBeRepaired() )
  2417. return false;
  2418. const float flRepairToMetalRatio = 3.0f; // Amount Repaired per metal
  2419. float flTargetHeal = 100.0f * flRepairMod;
  2420. int iAmountToHeal = MIN( flTargetHeal, GetMaxHealth() - RoundFloatToInt( GetHealth() ) );
  2421. // repair the building
  2422. int iRepairCost = ceil( (float)( iAmountToHeal ) / flRepairToMetalRatio );
  2423. TRACE_OBJECT( UTIL_VarArgs( "%0.2f CBaseObject::Command_Repair ( %f / %d ) - cost = %d\n", gpGlobals->curtime,
  2424. GetHealth(),
  2425. GetMaxHealth(),
  2426. iRepairCost ) );
  2427. if ( iRepairCost > 0 )
  2428. {
  2429. if ( iRepairCost > pActivator->GetBuildResources() )
  2430. {
  2431. iRepairCost = pActivator->GetBuildResources();
  2432. }
  2433. pActivator->RemoveBuildResources( iRepairCost );
  2434. float flNewHealth = MIN( GetMaxHealth(), m_flHealth + ( iRepairCost * flRepairToMetalRatio ) );
  2435. if ( pActivator != GetBuilder() )
  2436. {
  2437. float flAmountRepaired = flNewHealth - m_flHealth;
  2438. pActivator->AwardAchievement( ACHIEVEMENT_TF_ENGINEER_REPAIR_TEAM_GRIND, floor( flAmountRepaired ) );
  2439. }
  2440. SetHealth( flNewHealth );
  2441. return ( iRepairCost > 0 );
  2442. }
  2443. return false;
  2444. }
  2445. //-----------------------------------------------------------------------------
  2446. // Purpose: Upgrade this object a single level
  2447. //-----------------------------------------------------------------------------
  2448. void CBaseObject::StartUpgrading( void )
  2449. {
  2450. // Increase level
  2451. m_iUpgradeLevel++;
  2452. if ( GetHighestUpgradeLevel() < m_iUpgradeLevel )
  2453. {
  2454. m_iHighestUpgradeLevel = m_iUpgradeLevel;
  2455. }
  2456. // more health
  2457. if ( !m_bCarryDeploy && !IsUsingReverseBuild() )
  2458. {
  2459. int iMaxHealth = GetMaxHealthForCurrentLevel();
  2460. SetMaxHealth( iMaxHealth );
  2461. SetHealth( iMaxHealth );
  2462. }
  2463. const char *pUpgradeSound = GetObjectInfo( ObjectType() )->m_pUpgradeSound;
  2464. if ( pUpgradeSound && *pUpgradeSound )
  2465. {
  2466. EmitSound( pUpgradeSound );
  2467. }
  2468. if ( ( !m_bWasMapPlaced || ( m_iUpgradeLevel > (m_nDefaultUpgradeLevel+1) ) ) )
  2469. {
  2470. SetActivity( ACT_OBJ_UPGRADING );
  2471. float flConstructionTime = ( ShouldQuickBuild() ? 0 : GetObjectInfo( ObjectType() )->m_flUpgradeDuration );
  2472. float flReverseBuildingConstructionSpeed = GetReversesBuildingConstructionSpeed();
  2473. flConstructionTime /= ( flReverseBuildingConstructionSpeed == 0.0f ? 1.0f : flReverseBuildingConstructionSpeed );
  2474. m_flUpgradeCompleteTime = gpGlobals->curtime + flConstructionTime;
  2475. }
  2476. else
  2477. {
  2478. m_flUpgradeCompleteTime = gpGlobals->curtime; //asap
  2479. }
  2480. RemoveAllGestures();
  2481. if ( TFGameRules() && TFGameRules()->IsInTraining() &&
  2482. TFGameRules()->GetTrainingModeLogic() &&
  2483. GetOwner() && GetOwner()->IsFakeClient() == false )
  2484. {
  2485. TFGameRules()->GetTrainingModeLogic()->OnPlayerUpgradedBuilding( GetOwner(), this );
  2486. }
  2487. }
  2488. //-----------------------------------------------------------------------------
  2489. // Purpose:
  2490. //-----------------------------------------------------------------------------
  2491. void CBaseObject::FinishUpgrading( void )
  2492. {
  2493. const char *pUpgradeSound = GetObjectInfo( ObjectType() )->m_pUpgradeSound;
  2494. if ( pUpgradeSound && *pUpgradeSound )
  2495. {
  2496. EmitSound( pUpgradeSound );
  2497. }
  2498. if ( IsUsingReverseBuild() )
  2499. {
  2500. m_iUpgradeLevel--;
  2501. DoReverseBuild();
  2502. }
  2503. }
  2504. //-----------------------------------------------------------------------------
  2505. // Playing the upgrade animation
  2506. //-----------------------------------------------------------------------------
  2507. void CBaseObject::UpgradeThink( void )
  2508. {
  2509. if ( gpGlobals->curtime > m_flUpgradeCompleteTime )
  2510. {
  2511. FinishUpgrading();
  2512. }
  2513. }
  2514. //-----------------------------------------------------------------------------
  2515. // Purpose: Handles health upgrade for objects we've already built
  2516. //-----------------------------------------------------------------------------
  2517. void CBaseObject::ApplyHealthUpgrade( void )
  2518. {
  2519. CTFPlayer *pTFPlayer = GetOwner();
  2520. if ( !pTFPlayer )
  2521. return;
  2522. int iHealth = GetMaxHealthForCurrentLevel();
  2523. SetMaxHealth( iHealth );
  2524. SetHealth( iHealth );
  2525. //DevMsg( "%i\n", GetMaxHealth() );
  2526. }
  2527. //-----------------------------------------------------------------------------
  2528. // Purpose:
  2529. //-----------------------------------------------------------------------------
  2530. void CBaseObject::PlayStartupAnimation( void )
  2531. {
  2532. SetActivity( ACT_OBJ_STARTUP );
  2533. }
  2534. //-----------------------------------------------------------------------------
  2535. // Purpose:
  2536. //-----------------------------------------------------------------------------
  2537. void CBaseObject::DetermineAnimation( void )
  2538. {
  2539. Activity desiredActivity = m_Activity;
  2540. switch ( m_Activity )
  2541. {
  2542. default:
  2543. {
  2544. if ( IsUpgrading() )
  2545. {
  2546. desiredActivity = ACT_OBJ_UPGRADING;
  2547. }
  2548. else if ( IsPlacing() )
  2549. {
  2550. /*
  2551. if (1 || m_bPlacementOK )
  2552. {
  2553. desiredActivity = ACT_OBJ_PLACING;
  2554. }
  2555. else
  2556. {
  2557. desiredActivity = ACT_OBJ_IDLE;
  2558. }
  2559. */
  2560. }
  2561. else if ( IsBuilding() )
  2562. {
  2563. desiredActivity = ACT_OBJ_ASSEMBLING;
  2564. }
  2565. else
  2566. {
  2567. desiredActivity = ACT_OBJ_RUNNING;
  2568. }
  2569. }
  2570. break;
  2571. case ACT_OBJ_STARTUP:
  2572. {
  2573. if ( IsActivityFinished() )
  2574. {
  2575. desiredActivity = ACT_OBJ_RUNNING;
  2576. }
  2577. }
  2578. break;
  2579. }
  2580. if ( desiredActivity == m_Activity )
  2581. return;
  2582. SetActivity( desiredActivity );
  2583. }
  2584. //-----------------------------------------------------------------------------
  2585. // Purpose: Attach this object to the specified object
  2586. //-----------------------------------------------------------------------------
  2587. void CBaseObject::AttachObjectToObject( CBaseEntity *pEntity, int iPoint, Vector &vecOrigin )
  2588. {
  2589. m_hBuiltOnEntity = pEntity;
  2590. m_iBuiltOnPoint = iPoint;
  2591. int iAttachment = 0;
  2592. if ( m_hBuiltOnEntity.Get() )
  2593. {
  2594. CTFPlayer *pTFPlayer = ToTFPlayer( pEntity );
  2595. if ( pTFPlayer )
  2596. {
  2597. iAttachment = pTFPlayer->LookupAttachment( "head" );
  2598. }
  2599. else
  2600. {
  2601. CBaseAnimating *pAnimate = dynamic_cast<CBaseAnimating*>( pEntity );
  2602. if ( pAnimate )
  2603. {
  2604. iAttachment = pAnimate->LookupBone( "weapon_bone" );
  2605. if ( iAttachment >= 1 )
  2606. {
  2607. FollowEntity( m_hBuiltOnEntity.Get() );
  2608. }
  2609. }
  2610. // Parent ourselves to the object
  2611. IHasBuildPoints *pBPInterface = dynamic_cast<IHasBuildPoints*>( pEntity );
  2612. Assert( pBPInterface );
  2613. if ( pBPInterface )
  2614. {
  2615. iAttachment = pBPInterface->GetBuildPointAttachmentIndex( iPoint );
  2616. // re-link to the build points if the sapper is already built
  2617. if ( !( IsPlacing() || IsBuilding() ) )
  2618. {
  2619. pBPInterface->SetObjectOnBuildPoint( m_iBuiltOnPoint, this );
  2620. }
  2621. }
  2622. }
  2623. SetParent( m_hBuiltOnEntity.Get(), iAttachment );
  2624. if ( iAttachment >= 1 )
  2625. {
  2626. // Stick right onto the attachment point.
  2627. vecOrigin.Init();
  2628. SetLocalOrigin( vecOrigin );
  2629. SetLocalAngles( QAngle(0,0,0) );
  2630. }
  2631. else
  2632. {
  2633. SetAbsOrigin( vecOrigin );
  2634. vecOrigin = GetLocalOrigin();
  2635. }
  2636. SetupAttachedVersion();
  2637. }
  2638. Assert( m_hBuiltOnEntity.Get() == GetMoveParent() );
  2639. }
  2640. //-----------------------------------------------------------------------------
  2641. // Purpose: Detach this object from its parent, if it has one
  2642. //-----------------------------------------------------------------------------
  2643. void CBaseObject::DetachObjectFromObject( void )
  2644. {
  2645. if ( !GetParentObject() )
  2646. return;
  2647. // Clear the build point
  2648. IHasBuildPoints *pBPInterface = dynamic_cast<IHasBuildPoints*>(GetParentObject() );
  2649. Assert( pBPInterface );
  2650. pBPInterface->SetObjectOnBuildPoint( m_iBuiltOnPoint, NULL );
  2651. SetParent( NULL );
  2652. m_hBuiltOnEntity = NULL;
  2653. m_iBuiltOnPoint = 0;
  2654. }
  2655. //-----------------------------------------------------------------------------
  2656. // Purpose: Spawn any objects specified inside the mdl
  2657. //-----------------------------------------------------------------------------
  2658. void CBaseObject::SpawnEntityOnBuildPoint( const char *pEntityName, int iAttachmentNumber )
  2659. {
  2660. // Try and spawn the object
  2661. CBaseEntity *pEntity = CreateEntityByName( pEntityName );
  2662. if ( !pEntity )
  2663. return;
  2664. Vector vecOrigin;
  2665. QAngle vecAngles;
  2666. GetAttachment( iAttachmentNumber, vecOrigin, vecAngles );
  2667. pEntity->SetAbsOrigin( vecOrigin );
  2668. pEntity->SetAbsAngles( vecAngles );
  2669. pEntity->Spawn();
  2670. // If it's an object, finish setting it up
  2671. CBaseObject *pObject = dynamic_cast<CBaseObject*>(pEntity);
  2672. if ( !pObject )
  2673. return;
  2674. // Add a buildpoint here
  2675. int iPoint = AddBuildPoint( iAttachmentNumber );
  2676. AddValidObjectToBuildPoint( iPoint, pObject->GetType() );
  2677. pObject->SetBuilder( GetBuilder() );
  2678. pObject->ChangeTeam( GetTeamNumber() );
  2679. if ( !(pObject->m_fObjectFlags & OF_DOESNT_HAVE_A_MODEL) )
  2680. {
  2681. pObject->SpawnControlPanels();
  2682. }
  2683. pObject->SetHealth( pObject->GetMaxHealth() );
  2684. pObject->FinishedBuilding();
  2685. pObject->AttachObjectToObject( this, iPoint, vecOrigin );
  2686. //pObject->m_fObjectFlags |= OF_CANNOT_BE_DISMANTLED;
  2687. IHasBuildPoints *pBPInterface = dynamic_cast<IHasBuildPoints*>(this);
  2688. Assert( pBPInterface );
  2689. pBPInterface->SetObjectOnBuildPoint( iPoint, pObject );
  2690. }
  2691. //-----------------------------------------------------------------------------
  2692. // Purpose: Spawn any objects specified inside the mdl
  2693. //-----------------------------------------------------------------------------
  2694. void CBaseObject::SpawnObjectPoints( void )
  2695. {
  2696. KeyValues *modelKeyValues = new KeyValues("");
  2697. if ( !modelKeyValues->LoadFromBuffer( modelinfo->GetModelName( GetModel() ), modelinfo->GetModelKeyValueText( GetModel() ) ) )
  2698. {
  2699. modelKeyValues->deleteThis();
  2700. return;
  2701. }
  2702. // Do we have a build point section?
  2703. KeyValues *pkvAllObjectPoints = modelKeyValues->FindKey("object_points");
  2704. if ( !pkvAllObjectPoints )
  2705. {
  2706. modelKeyValues->deleteThis();
  2707. return;
  2708. }
  2709. // Start grabbing the sounds and slotting them in
  2710. KeyValues *pkvObjectPoint;
  2711. for ( pkvObjectPoint = pkvAllObjectPoints->GetFirstSubKey(); pkvObjectPoint; pkvObjectPoint = pkvObjectPoint->GetNextKey() )
  2712. {
  2713. // Find the attachment first
  2714. const char *sAttachment = pkvObjectPoint->GetName();
  2715. int iAttachmentNumber = LookupAttachment( sAttachment );
  2716. if ( iAttachmentNumber <= 0 )
  2717. {
  2718. Msg( "ERROR: Model %s specifies object point %s, but has no attachment named %s.\n", STRING(GetModelName()), pkvObjectPoint->GetString(), pkvObjectPoint->GetString() );
  2719. continue;
  2720. }
  2721. // Now see what we're supposed to spawn there
  2722. // The count check is because it seems wrong to emit multiple entities on the same point
  2723. int nCount = 0;
  2724. KeyValues *pkvObject;
  2725. for ( pkvObject = pkvObjectPoint->GetFirstSubKey(); pkvObject; pkvObject = pkvObject->GetNextKey() )
  2726. {
  2727. SpawnEntityOnBuildPoint( pkvObject->GetName(), iAttachmentNumber );
  2728. ++nCount;
  2729. Assert( nCount <= 1 );
  2730. }
  2731. }
  2732. modelKeyValues->deleteThis();
  2733. }
  2734. bool CBaseObject::IsSolidToPlayers( void ) const
  2735. {
  2736. switch ( m_SolidToPlayers )
  2737. {
  2738. default:
  2739. break;
  2740. case SOLID_TO_PLAYER_USE_DEFAULT:
  2741. {
  2742. if ( GetObjectInfo( ObjectType() ) )
  2743. {
  2744. return GetObjectInfo( ObjectType() )->m_bSolidToPlayerMovement;
  2745. }
  2746. }
  2747. break;
  2748. case SOLID_TO_PLAYER_YES:
  2749. return true;
  2750. case SOLID_TO_PLAYER_NO:
  2751. return false;
  2752. }
  2753. return false;
  2754. }
  2755. void CBaseObject::SetSolidToPlayers( OBJSOLIDTYPE stp, bool force )
  2756. {
  2757. bool changed = stp != m_SolidToPlayers;
  2758. m_SolidToPlayers = stp;
  2759. if ( changed || force )
  2760. {
  2761. SetCollisionGroup(
  2762. IsSolidToPlayers() ?
  2763. TFCOLLISION_GROUP_OBJECT_SOLIDTOPLAYERMOVEMENT :
  2764. TFCOLLISION_GROUP_OBJECT );
  2765. }
  2766. }
  2767. int CBaseObject::DrawDebugTextOverlays(void)
  2768. {
  2769. int text_offset = BaseClass::DrawDebugTextOverlays();
  2770. if (m_debugOverlays & OVERLAY_TEXT_BIT)
  2771. {
  2772. char tempstr[512];
  2773. Q_snprintf( tempstr, sizeof( tempstr ),"Health: %f / %d ( %.1f )", GetHealth(), GetMaxHealth(), (float)GetHealth() / (float)GetMaxHealth() );
  2774. EntityText(text_offset,tempstr,0);
  2775. text_offset++;
  2776. CTFPlayer *pBuilder = GetBuilder();
  2777. Q_snprintf( tempstr, sizeof( tempstr ),"Built by: (%d) %s",
  2778. pBuilder ? pBuilder->entindex() : -1,
  2779. pBuilder ? pBuilder->GetPlayerName() : "invalid builder" );
  2780. EntityText(text_offset,tempstr,0);
  2781. text_offset++;
  2782. if ( IsBuilding() )
  2783. {
  2784. Q_snprintf( tempstr, sizeof( tempstr ),"Build Rate: %.1f", GetConstructionMultiplier() );
  2785. EntityText(text_offset,tempstr,0);
  2786. text_offset++;
  2787. }
  2788. }
  2789. return text_offset;
  2790. }
  2791. //-----------------------------------------------------------------------------
  2792. // Purpose: Change build orientation
  2793. //-----------------------------------------------------------------------------
  2794. void CBaseObject::RotateBuildAngles( void )
  2795. {
  2796. // rotate the build angles by 90 degrees ( final angle calculated after we network this )
  2797. m_iDesiredBuildRotations++;
  2798. m_iDesiredBuildRotations = m_iDesiredBuildRotations % 4;
  2799. }
  2800. //-----------------------------------------------------------------------------
  2801. // Purpose: called on edge cases to see if we need to change our disabled state
  2802. //-----------------------------------------------------------------------------
  2803. void CBaseObject::UpdateDisabledState( void )
  2804. {
  2805. const bool bShouldBeEnabled = !m_bHasSapper
  2806. && !m_bPlasmaDisable
  2807. && (!TFGameRules()->RoundHasBeenWon() || TFGameRules()->GetWinningTeam() == GetTeamNumber());
  2808. SetDisabled( !bShouldBeEnabled );
  2809. }
  2810. //-----------------------------------------------------------------------------
  2811. // Purpose: called when our disabled state changes
  2812. //-----------------------------------------------------------------------------
  2813. void CBaseObject::SetDisabled( bool bDisabled )
  2814. {
  2815. if ( bDisabled && !m_bDisabled )
  2816. {
  2817. OnStartDisabled();
  2818. }
  2819. else if ( !bDisabled && m_bDisabled )
  2820. {
  2821. OnEndDisabled();
  2822. }
  2823. m_bDisabled = bDisabled;
  2824. }
  2825. //-----------------------------------------------------------------------------
  2826. void CBaseObject::SetPlasmaDisabled( float flDuration )
  2827. {
  2828. m_bPlasmaDisable = true;
  2829. m_flPlasmaDisableTime = gpGlobals->curtime + flDuration;
  2830. UpdateDisabledState();
  2831. }
  2832. //-----------------------------------------------------------------------------
  2833. // Purpose:
  2834. //-----------------------------------------------------------------------------
  2835. void CBaseObject::OnStartDisabled( void )
  2836. {
  2837. }
  2838. //-----------------------------------------------------------------------------
  2839. // Purpose:
  2840. //-----------------------------------------------------------------------------
  2841. void CBaseObject::OnEndDisabled( void )
  2842. {
  2843. }
  2844. //-----------------------------------------------------------------------------
  2845. // Purpose: Called when the model changes, find new attachments for the children
  2846. //-----------------------------------------------------------------------------
  2847. void CBaseObject::ReattachChildren( void )
  2848. {
  2849. // Go through and store the children one by one, then reattach them. We need
  2850. // to store them like this because if we didnt and instead went through and
  2851. // reattached them as we iterated over them we could get into a state where we have
  2852. // children A and B and A has B as a sibling and B has NULL has a sibling,
  2853. // but we reattach B first which set's B's sibling to A creating a infinite loop
  2854. CUtlVector<CBaseEntity*> vecChildren;
  2855. for (CBaseEntity *pChild = FirstMoveChild(); pChild; pChild = pChild->NextMovePeer())
  2856. {
  2857. if( vecChildren.Find( pChild ) != vecChildren.InvalidIndex() )
  2858. {
  2859. AssertMsg( 0, "Cyclic siblings found when reattaching children!" );
  2860. break;
  2861. }
  2862. vecChildren.AddToTail( pChild );
  2863. }
  2864. int iNumBuildPoints = GetNumBuildPoints();
  2865. FOR_EACH_VEC( vecChildren, i )
  2866. {
  2867. CBaseObject *pObject = dynamic_cast<CBaseObject *>( vecChildren[i] );
  2868. if ( !pObject )
  2869. {
  2870. continue;
  2871. }
  2872. Assert( pObject->GetParent() == this );
  2873. // get the type
  2874. int iObjectType = pObject->GetType();
  2875. bool bReattached = false;
  2876. Vector vecDummy;
  2877. for ( int j = 0; j < iNumBuildPoints && bReattached == false; j++ )
  2878. {
  2879. // Can this object build on this point?
  2880. if ( CanBuildObjectOnBuildPoint( j, iObjectType ) )
  2881. {
  2882. pObject->AttachObjectToObject( this, j, vecDummy );
  2883. bReattached = true;
  2884. }
  2885. }
  2886. // if we can't find an attach for the child, remove it and print an error
  2887. if ( bReattached == false )
  2888. {
  2889. if ( m_bCarried && ( pObject->GetType() == OBJ_ATTACHMENT_SAPPER ) )
  2890. {
  2891. pObject->ResetPlacement();
  2892. }
  2893. else
  2894. {
  2895. pObject->DestroyObject();
  2896. Assert( !"Couldn't find attachment point on upgraded object for existing child.\n" );
  2897. }
  2898. }
  2899. }
  2900. }
  2901. void CBaseObject::SetModel( const char *pModel )
  2902. {
  2903. // Skip if we're already the proper model
  2904. if ( V_strcmp( GetModelName().ToCStr(), pModel ) == 0 )
  2905. return;
  2906. BaseClass::SetModel( pModel );
  2907. // Clear out the gib list and create a new one.
  2908. m_aGibs.Purge();
  2909. BuildGibList( m_aGibs, GetModelIndex(), 1.0f, COLLISION_GROUP_NONE );
  2910. CObjectSapper *pSapper = GetSapper();
  2911. if ( pSapper )
  2912. {
  2913. pSapper->OnGoActive();
  2914. }
  2915. }
  2916. //-----------------------------------------------------------------------------
  2917. // Purpose:
  2918. //-----------------------------------------------------------------------------
  2919. void CBaseObject::Activate( void )
  2920. {
  2921. BaseClass::Activate();
  2922. InitializeMapPlacedObject();
  2923. }
  2924. //-----------------------------------------------------------------------------
  2925. // Purpose: Map placed objects need to setup here.
  2926. //-----------------------------------------------------------------------------
  2927. void CBaseObject::InitializeMapPlacedObject( void )
  2928. {
  2929. m_bWasMapPlaced = true;
  2930. //m_fObjectFlags |= OF_CANNOT_BE_DISMANTLED;
  2931. // If a map-placed object spawns child objects with their own control
  2932. // panels, all of this lovely code will already have been run
  2933. if ( m_hBuiltOnEntity.Get() )
  2934. return;
  2935. SetBuilder( NULL );
  2936. // NOTE: We must spawn the control panels now, instead of during
  2937. // Spawn, because until placement is started, we don't actually know
  2938. // the position of the control panel because we don't know what it's
  2939. // been attached to (could be a vehicle which supplies a different
  2940. // place for the control panel)
  2941. if ( !(m_fObjectFlags & OF_DOESNT_HAVE_A_MODEL) )
  2942. {
  2943. SpawnControlPanels();
  2944. }
  2945. SetHealth( GetMaxHealth() );
  2946. //AlignToGround( GetAbsOrigin() );
  2947. FinishedBuilding();
  2948. // Set the skin
  2949. m_nSkin = ( GetTeamNumber() == TF_TEAM_RED ) ? 0 : 1;
  2950. }
  2951. //-----------------------------------------------------------------------------
  2952. // Purpose: Turns the object into one carried by someone.
  2953. //-----------------------------------------------------------------------------
  2954. void CBaseObject::MakeCarriedObject( CTFPlayer *pCarrier )
  2955. {
  2956. if ( pCarrier )
  2957. {
  2958. // Make the object inactive.
  2959. m_bCarried = true;
  2960. m_bCarryDeploy = false;
  2961. pCarrier->m_Shared.SetCarriedObject( this );
  2962. m_iHealthOnPickup = m_iHealth; // If we are damaged, we want to remember how much damage we had sustained.
  2963. // Remove screens.
  2964. DestroyScreens();
  2965. // Mount it to the player.
  2966. FollowEntity( pCarrier );
  2967. IGameEvent * event = gameeventmanager->CreateEvent( "player_carryobject" );
  2968. if ( event )
  2969. {
  2970. event->SetInt( "userid", pCarrier->GetUserID() );
  2971. event->SetInt( "object", GetType() );
  2972. event->SetInt( "index", entindex() ); // object entity index
  2973. gameeventmanager->FireEvent( event, true ); // don't send to clients
  2974. }
  2975. }
  2976. }
  2977. //-----------------------------------------------------------------------------
  2978. // Purpose: Turns the object into one carried by someone.
  2979. //-----------------------------------------------------------------------------
  2980. void CBaseObject::DropCarriedObject( CTFPlayer* pCarrier )
  2981. {
  2982. m_bCarried = false;
  2983. m_bCarryDeploy = false;
  2984. if ( pCarrier )
  2985. {
  2986. pCarrier->m_Shared.SetCarriedObject( NULL );
  2987. }
  2988. StopFollowingEntity();
  2989. }
  2990. //-----------------------------------------------------------------------------
  2991. // Purpose: Instantly build and upgrade this object
  2992. //-----------------------------------------------------------------------------
  2993. void CBaseObject::DoQuickBuild( bool bForceMax /* = false */ )
  2994. {
  2995. if ( IsBuilding() )
  2996. {
  2997. FinishedBuilding();
  2998. }
  2999. int iTargetLevel = ( ( ( TFGameRules() && TFGameRules()->IsQuickBuildTime() ) || bForceMax ) ? OBJ_MAX_UPGRADE_LEVEL : GetUpgradeLevel() );
  3000. if ( CanBeUpgraded( GetOwner() ) )
  3001. {
  3002. for ( int i = GetUpgradeLevel(); i < iTargetLevel; i++ )
  3003. {
  3004. StartUpgrading();
  3005. }
  3006. }
  3007. else
  3008. {
  3009. int iMaxHealth = GetMaxHealthForCurrentLevel();
  3010. SetMaxHealth( iMaxHealth );
  3011. SetHealth( iMaxHealth );
  3012. }
  3013. }
  3014. //-----------------------------------------------------------------------------
  3015. // Builds instantly under certain conditions/modes
  3016. //-----------------------------------------------------------------------------
  3017. bool CBaseObject::ShouldQuickBuild( void )
  3018. {
  3019. if ( TFGameRules() )
  3020. {
  3021. if ( GetType() == OBJ_ATTACHMENT_SAPPER )
  3022. return false;
  3023. #ifdef STAGING_ONLY
  3024. if ( GetType() == OBJ_SPY_TRAP )
  3025. return false;
  3026. #endif
  3027. if ( TFGameRules()->IsQuickBuildTime() )
  3028. {
  3029. return true;
  3030. }
  3031. if ( TFGameRules()->IsMannVsMachineMode() )
  3032. {
  3033. if ( GetTeamNumber() == TF_TEAM_PVE_INVADERS )
  3034. {
  3035. // Engineer bots in MvM deploy pre-built sentries that build up at the normal rate
  3036. return m_bForceQuickBuild;
  3037. }
  3038. if ( m_bCarryDeploy || TFGameRules()->State_Get() == GR_STATE_BETWEEN_RNDS )
  3039. {
  3040. return true;
  3041. }
  3042. }
  3043. }
  3044. return m_bForceQuickBuild;
  3045. }
  3046. void CBaseObject::DoReverseBuild( void )
  3047. {
  3048. m_iHighestUpgradeLevel = m_iUpgradeLevel;
  3049. m_iUpgradeMetal = 0;
  3050. int iMaxHealth = GetMaxHealthForCurrentLevel();
  3051. SetMaxHealth( iMaxHealth );
  3052. if ( GetHealth() > iMaxHealth )
  3053. {
  3054. SetHealth( iMaxHealth );
  3055. }
  3056. if ( m_iUpgradeLevel > 1 )
  3057. {
  3058. m_iUpgradeLevel--;
  3059. StartUpgrading();
  3060. }
  3061. else
  3062. {
  3063. m_bBuilding = true;
  3064. m_bCarryDeploy = false;
  3065. m_flTotalConstructionTime = m_flConstructionTimeLeft = GetTotalTime();
  3066. m_flConstructionStartTime = gpGlobals->curtime;
  3067. SetStartBuildingModel();
  3068. SetControlPanelsActive( false );
  3069. }
  3070. }
  3071. float CBaseObject::GetReversesBuildingConstructionSpeed( void )
  3072. {
  3073. CObjectSapper *pSapper = GetSapper();
  3074. if ( !pSapper )
  3075. return 0.0f;
  3076. return pSapper->GetReversesBuildingConstructionSpeed();
  3077. }
  3078. //-----------------------------------------------------------------------------
  3079. // Purpose:
  3080. //-----------------------------------------------------------------------------
  3081. void CBaseObject::InputEnable( inputdata_t &inputdata )
  3082. {
  3083. if ( IsDisabled() )
  3084. {
  3085. UpdateDisabledState();
  3086. if ( !IsDisabled() )
  3087. {
  3088. OnGoActive();
  3089. }
  3090. }
  3091. }
  3092. //-----------------------------------------------------------------------------
  3093. // Purpose:
  3094. //-----------------------------------------------------------------------------
  3095. void CBaseObject::InputDisable( inputdata_t &inputdata )
  3096. {
  3097. if ( !IsDisabled() )
  3098. {
  3099. SetDisabled( true );
  3100. OnGoInactive();
  3101. }
  3102. }
  3103. //-----------------------------------------------------------------------------
  3104. // Purpose:
  3105. //-----------------------------------------------------------------------------
  3106. int CBaseObject::GetMaxHealthForCurrentLevel( void )
  3107. {
  3108. int iMaxHealth = IsMiniBuilding() ? GetMiniBuildingStartingHealth() : GetBaseHealth();
  3109. if ( GetOwner() && !m_bDisposableBuilding )
  3110. {
  3111. CALL_ATTRIB_HOOK_INT_ON_OTHER( GetOwner(), iMaxHealth, mult_engy_building_health );
  3112. }
  3113. if ( !IsMiniBuilding() && ( GetUpgradeLevel() > 1 ) )
  3114. {
  3115. float flMultiplier = pow( UPGRADE_LEVEL_HEALTH_MULTIPLIER, GetUpgradeLevel() - 1 );
  3116. iMaxHealth = (int)( iMaxHealth * flMultiplier );
  3117. }
  3118. return iMaxHealth;
  3119. }