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.

722 lines
22 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. //=============================================================================//
  6. #include "cbase.h"
  7. #include "baseobject_shared.h"
  8. #include <KeyValues.h>
  9. #include "tf_shareddefs.h"
  10. #include "engine/ivmodelinfo.h"
  11. #ifdef GAME_DLL
  12. #include "func_no_build.h"
  13. #include "tf_player.h"
  14. #include "tf_team.h"
  15. #include "func_no_build.h"
  16. #include "func_respawnroom.h"
  17. #else
  18. #include "c_tf_player.h"
  19. #include "c_tf_team.h"
  20. #endif
  21. // memdbgon must be the last include file in a .cpp file!!!
  22. #include "tier0/memdbgon.h"
  23. ConVar tf_obj_build_rotation_speed( "tf_obj_build_rotation_speed", "250", FCVAR_REPLICATED | FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Degrees per second to rotate building when player alt-fires during placement." );
  24. //-----------------------------------------------------------------------------
  25. // Purpose: Parse our model and create the buildpoints in it
  26. //-----------------------------------------------------------------------------
  27. void CBaseObject::CreateBuildPoints( void )
  28. {
  29. // Clear out any existing build points
  30. m_BuildPoints.RemoveAll();
  31. KeyValues * modelKeyValues = new KeyValues("");
  32. if ( !modelKeyValues->LoadFromBuffer( modelinfo->GetModelName( GetModel() ), modelinfo->GetModelKeyValueText( GetModel() ) ) )
  33. {
  34. return;
  35. }
  36. // Do we have a build point section?
  37. KeyValues *pkvAllBuildPoints = modelKeyValues->FindKey("build_points");
  38. if ( pkvAllBuildPoints )
  39. {
  40. KeyValues *pkvBuildPoint = pkvAllBuildPoints->GetFirstSubKey();
  41. while ( pkvBuildPoint )
  42. {
  43. // Find the attachment first
  44. const char *sAttachment = pkvBuildPoint->GetName();
  45. int iAttachmentNumber = LookupAttachment( sAttachment );
  46. if ( iAttachmentNumber > 0 )
  47. {
  48. AddAndParseBuildPoint( iAttachmentNumber, pkvBuildPoint );
  49. }
  50. else
  51. {
  52. Msg( "ERROR: Model %s specifies buildpoint %s, but has no attachment named %s.\n", STRING(GetModelName()), pkvBuildPoint->GetString(), pkvBuildPoint->GetString() );
  53. }
  54. pkvBuildPoint = pkvBuildPoint->GetNextKey();
  55. }
  56. }
  57. // Any virtual build points (build points that aren't on an attachment)?
  58. pkvAllBuildPoints = modelKeyValues->FindKey("virtual_build_points");
  59. if ( pkvAllBuildPoints )
  60. {
  61. KeyValues *pkvBuildPoint = pkvAllBuildPoints->GetFirstSubKey();
  62. while ( pkvBuildPoint )
  63. {
  64. AddAndParseBuildPoint( -1, pkvBuildPoint );
  65. pkvBuildPoint = pkvBuildPoint->GetNextKey();
  66. }
  67. }
  68. modelKeyValues->deleteThis();
  69. }
  70. //-----------------------------------------------------------------------------
  71. // Purpose:
  72. //-----------------------------------------------------------------------------
  73. void CBaseObject::AddAndParseBuildPoint( int iAttachmentNumber, KeyValues *pkvBuildPoint )
  74. {
  75. int iPoint = AddBuildPoint( iAttachmentNumber );
  76. m_BuildPoints[iPoint].m_bPutInAttachmentSpace = (pkvBuildPoint->GetInt( "PutInAttachmentSpace", 0 ) != 0);
  77. // Now see if we've got a set of valid objects specified
  78. KeyValues *pkvValidObjects = pkvBuildPoint->FindKey( "valid_objects" );
  79. if ( pkvValidObjects )
  80. {
  81. KeyValues *pkvObject = pkvValidObjects->GetFirstSubKey();
  82. while ( pkvObject )
  83. {
  84. const char *pSpecifiedObject = pkvObject->GetName();
  85. int iLenObjName = Q_strlen( pSpecifiedObject );
  86. // Find the object index for the name
  87. for ( int i = 0; i < OBJ_LAST; i++ )
  88. {
  89. if ( !Q_strncasecmp( GetObjectInfo( i )->m_pClassName, pSpecifiedObject, iLenObjName) )
  90. {
  91. AddValidObjectToBuildPoint( iPoint, i );
  92. break;
  93. }
  94. }
  95. pkvObject = pkvObject->GetNextKey();
  96. }
  97. }
  98. }
  99. //-----------------------------------------------------------------------------
  100. // Purpose: Add a new buildpoint to my list of buildpoints
  101. //-----------------------------------------------------------------------------
  102. int CBaseObject::AddBuildPoint( int iAttachmentNum )
  103. {
  104. // Make a new buildpoint
  105. BuildPoint_t sNewPoint;
  106. sNewPoint.m_hObject = NULL;
  107. sNewPoint.m_iAttachmentNum = iAttachmentNum;
  108. sNewPoint.m_bPutInAttachmentSpace = false;
  109. Q_memset( sNewPoint.m_bValidObjects, 0, sizeof( sNewPoint.m_bValidObjects ) );
  110. // Insert it into our list
  111. return m_BuildPoints.AddToTail( sNewPoint );
  112. }
  113. //-----------------------------------------------------------------------------
  114. // Purpose:
  115. //-----------------------------------------------------------------------------
  116. void CBaseObject::AddValidObjectToBuildPoint( int iPoint, int iObjectType )
  117. {
  118. Assert( iPoint <= GetNumBuildPoints() );
  119. m_BuildPoints[iPoint].m_bValidObjects[ iObjectType ] = true;
  120. }
  121. //-----------------------------------------------------------------------------
  122. // Purpose:
  123. //-----------------------------------------------------------------------------
  124. int CBaseObject::GetNumBuildPoints( void ) const
  125. {
  126. return m_BuildPoints.Size();
  127. }
  128. //-----------------------------------------------------------------------------
  129. // Purpose:
  130. //-----------------------------------------------------------------------------
  131. CBaseObject* CBaseObject::GetBuildPointObject( int iPoint )
  132. {
  133. Assert( iPoint >= 0 && iPoint <= GetNumBuildPoints() );
  134. return m_BuildPoints[iPoint].m_hObject;
  135. }
  136. //-----------------------------------------------------------------------------
  137. // Purpose: Return true if the specified object type can be built on this point
  138. //-----------------------------------------------------------------------------
  139. bool CBaseObject::CanBuildObjectOnBuildPoint( int iPoint, int iObjectType )
  140. {
  141. Assert( iPoint >= 0 && iPoint <= GetNumBuildPoints() );
  142. // Allowed to build here?
  143. if ( !m_BuildPoints[iPoint].m_bValidObjects[ iObjectType ] )
  144. return false;
  145. // Buildpoint empty?
  146. return ( m_BuildPoints[iPoint].m_hObject == NULL );
  147. }
  148. //-----------------------------------------------------------------------------
  149. // Purpose:
  150. //-----------------------------------------------------------------------------
  151. bool CBaseObject::GetBuildPoint( int iPoint, Vector &vecOrigin, QAngle &vecAngles )
  152. {
  153. Assert( iPoint >= 0 && iPoint <= GetNumBuildPoints() );
  154. int iAttachmentNum = m_BuildPoints[iPoint].m_iAttachmentNum;
  155. if ( iAttachmentNum == -1 )
  156. {
  157. vecOrigin = GetAbsOrigin();
  158. vecAngles = GetAbsAngles();
  159. return true;
  160. }
  161. else
  162. {
  163. return GetAttachment( m_BuildPoints[iPoint].m_iAttachmentNum, vecOrigin, vecAngles );
  164. }
  165. }
  166. int CBaseObject::GetBuildPointAttachmentIndex( int iPoint ) const
  167. {
  168. Assert( iPoint >= 0 && iPoint <= GetNumBuildPoints() );
  169. if ( m_BuildPoints[iPoint].m_bPutInAttachmentSpace )
  170. {
  171. return m_BuildPoints[iPoint].m_iAttachmentNum;
  172. }
  173. else
  174. {
  175. return 0;
  176. }
  177. }
  178. //-----------------------------------------------------------------------------
  179. // Purpose:
  180. //-----------------------------------------------------------------------------
  181. void CBaseObject::SetObjectOnBuildPoint( int iPoint, CBaseObject *pObject )
  182. {
  183. Assert( iPoint >= 0 && iPoint <= GetNumBuildPoints() );
  184. m_BuildPoints[iPoint].m_hObject = pObject;
  185. }
  186. ConVar tf_obj_max_attach_dist( "tf_obj_max_attach_dist", "160", FCVAR_REPLICATED | FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY );
  187. //-----------------------------------------------------------------------------
  188. // Purpose:
  189. //-----------------------------------------------------------------------------
  190. float CBaseObject::GetMaxSnapDistance( int iPoint )
  191. {
  192. return tf_obj_max_attach_dist.GetFloat();
  193. }
  194. //-----------------------------------------------------------------------------
  195. // Purpose: Return the number of objects on my build points
  196. //-----------------------------------------------------------------------------
  197. int CBaseObject::GetNumObjectsOnMe( void )
  198. {
  199. int iObjects = 0;
  200. for ( int i = 0; i < GetNumBuildPoints(); i++ )
  201. {
  202. if ( m_BuildPoints[i].m_hObject )
  203. {
  204. iObjects++;
  205. }
  206. }
  207. return iObjects;
  208. }
  209. //-----------------------------------------------------------------------------
  210. // I've finished building the specified object on the specified build point
  211. //-----------------------------------------------------------------------------
  212. int CBaseObject::FindObjectOnBuildPoint( CBaseObject *pObject )
  213. {
  214. for (int i = m_BuildPoints.Count(); --i >= 0; )
  215. {
  216. if (m_BuildPoints[i].m_hObject == pObject)
  217. return i;
  218. }
  219. return -1;
  220. }
  221. //-----------------------------------------------------------------------------
  222. // Purpose:
  223. //-----------------------------------------------------------------------------
  224. CBaseObject *CBaseObject::GetObjectOfTypeOnMe( int iObjectType )
  225. {
  226. for ( int iObject = 0; iObject < GetNumObjectsOnMe(); ++iObject )
  227. {
  228. CBaseObject *pObject = dynamic_cast<CBaseObject*>( m_BuildPoints[iObject].m_hObject.Get() );
  229. if ( pObject )
  230. {
  231. if ( pObject->GetType() == iObjectType )
  232. return pObject;
  233. }
  234. }
  235. return NULL;
  236. }
  237. //-----------------------------------------------------------------------------
  238. // Purpose:
  239. //-----------------------------------------------------------------------------
  240. void CBaseObject::RemoveAllObjects( void )
  241. {
  242. #ifndef CLIENT_DLL
  243. for ( int i = 0; i < GetNumBuildPoints(); i++ )
  244. {
  245. if ( m_BuildPoints[i].m_hObject )
  246. {
  247. UTIL_Remove( m_BuildPoints[i].m_hObject );
  248. }
  249. }
  250. #endif // !CLIENT_DLL
  251. }
  252. //-----------------------------------------------------------------------------
  253. // Purpose:
  254. //-----------------------------------------------------------------------------
  255. CBaseObject *CBaseObject::GetParentObject( void )
  256. {
  257. if ( GetMoveParent() )
  258. return dynamic_cast<CBaseObject*>(GetMoveParent());
  259. return NULL;
  260. }
  261. //-----------------------------------------------------------------------------
  262. // Purpose:
  263. //-----------------------------------------------------------------------------
  264. CBaseEntity *CBaseObject::GetParentEntity( void )
  265. {
  266. if ( GetMoveParent() )
  267. return GetMoveParent();
  268. return NULL;
  269. }
  270. static ConVar sv_ignore_hitboxes( "sv_ignore_hitboxes", "0", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY, "Disable hitboxes" );
  271. bool CBaseObject::TestHitboxes( const Ray_t &ray, unsigned int fContentsMask, trace_t& tr )
  272. {
  273. bool bReturn = BaseClass::TestHitboxes( ray, fContentsMask, tr );
  274. if( !sv_ignore_hitboxes.GetBool() )
  275. return bReturn;
  276. if( !bReturn )
  277. {
  278. return false;
  279. }
  280. if( tr.fraction == 1.f && !tr.allsolid && !tr.startsolid )
  281. {
  282. return false;
  283. }
  284. return bReturn;
  285. }
  286. //-----------------------------------------------------------------------------
  287. // Purpose: Return true if this object should be active
  288. //-----------------------------------------------------------------------------
  289. bool CBaseObject::ShouldBeActive( void )
  290. {
  291. if ( IsDisabled() )
  292. return false;
  293. // Placing and/or constructing objects shouldn't be active
  294. if ( IsPlacing() || IsBuilding() || IsCarried() )
  295. return false;
  296. return true;
  297. }
  298. //-----------------------------------------------------------------------------
  299. // Purpose: Set the object's type
  300. //-----------------------------------------------------------------------------
  301. void CBaseObject::SetType( int iObjectType )
  302. {
  303. m_iObjectType = iObjectType;
  304. }
  305. //-----------------------------------------------------------------------------
  306. // Purpose:
  307. // Input : act -
  308. //-----------------------------------------------------------------------------
  309. void CBaseObject::SetActivity( Activity act )
  310. {
  311. // Hrm, it's not actually a studio model...
  312. if ( !GetModelPtr() )
  313. return;
  314. int sequence = SelectWeightedSequence( act );
  315. if ( sequence != ACTIVITY_NOT_AVAILABLE )
  316. {
  317. m_Activity = act;
  318. SetObjectSequence( sequence );
  319. }
  320. else
  321. {
  322. m_Activity = ACT_INVALID;
  323. }
  324. }
  325. //-----------------------------------------------------------------------------
  326. // Purpose:
  327. // Output : Activity
  328. //-----------------------------------------------------------------------------
  329. Activity CBaseObject::GetActivity( ) const
  330. {
  331. return m_Activity;
  332. }
  333. //-----------------------------------------------------------------------------
  334. // Purpose: Thin wrapper over CBaseAnimating::SetSequence to do bookkeeping.
  335. // Input : sequence -
  336. //-----------------------------------------------------------------------------
  337. void CBaseObject::SetObjectSequence( int sequence )
  338. {
  339. ResetSequence( sequence );
  340. SetCycle( GetReversesBuildingConstructionSpeed() != 0.0f ? 1.0f : 0.0f );
  341. #if !defined( CLIENT_DLL )
  342. if ( IsUsingClientSideAnimation() )
  343. {
  344. ResetClientsideFrame();
  345. }
  346. #endif
  347. }
  348. //-----------------------------------------------------------------------------
  349. // Purpose:
  350. //-----------------------------------------------------------------------------
  351. void CBaseObject::OnGoActive( void )
  352. {
  353. #ifndef CLIENT_DLL
  354. while ( m_nDefaultUpgradeLevel + 1 > m_iUpgradeLevel )
  355. {
  356. StartUpgrading();
  357. }
  358. // Play startup animation
  359. PlayStartupAnimation();
  360. // Switch to the on state
  361. if ( GetModelPtr() )
  362. {
  363. int index = FindBodygroupByName( "powertoggle" );
  364. if ( index >= 0 )
  365. {
  366. SetBodygroup( index, 1 );
  367. }
  368. }
  369. UpdateDisabledState();
  370. #endif
  371. }
  372. //-----------------------------------------------------------------------------
  373. // Purpose:
  374. //-----------------------------------------------------------------------------
  375. void CBaseObject::OnGoInactive( void )
  376. {
  377. #ifndef CLIENT_DLL
  378. if ( GetModelPtr() )
  379. {
  380. // Switch to the off state
  381. int index = FindBodygroupByName( "powertoggle" );
  382. if ( index >= 0 )
  383. {
  384. SetBodygroup( index, 0 );
  385. }
  386. }
  387. #endif
  388. }
  389. //-----------------------------------------------------------------------------
  390. // Purpose:
  391. // Input : collisionGroup -
  392. // Output : Returns true on success, false on failure.
  393. //-----------------------------------------------------------------------------
  394. bool CBaseObject::ShouldCollide( int collisionGroup, int contentsMask ) const
  395. {
  396. if ( collisionGroup == COLLISION_GROUP_PLAYER_MOVEMENT )
  397. {
  398. if ( GetCollisionGroup() == TFCOLLISION_GROUP_OBJECT_SOLIDTOPLAYERMOVEMENT )
  399. {
  400. return true;
  401. }
  402. switch( GetTeamNumber() )
  403. {
  404. case TF_TEAM_RED:
  405. if ( !( contentsMask & CONTENTS_REDTEAM ) )
  406. return false;
  407. break;
  408. case TF_TEAM_BLUE:
  409. if ( !( contentsMask & CONTENTS_BLUETEAM ) )
  410. return false;
  411. break;
  412. }
  413. }
  414. return BaseClass::ShouldCollide( collisionGroup, contentsMask );
  415. }
  416. //-----------------------------------------------------------------------------
  417. // Purpose: Should objects repel players on the same team
  418. //-----------------------------------------------------------------------------
  419. bool CBaseObject::ShouldPlayersAvoid( void )
  420. {
  421. return ( GetCollisionGroup() == TFCOLLISION_GROUP_OBJECT );
  422. }
  423. //-----------------------------------------------------------------------------
  424. // Do we have to be built on an attachment point
  425. //-----------------------------------------------------------------------------
  426. bool CBaseObject::MustBeBuiltOnAttachmentPoint( void ) const
  427. {
  428. return (m_fObjectFlags & OF_MUST_BE_BUILT_ON_ATTACHMENT) != 0;
  429. }
  430. //-----------------------------------------------------------------------------
  431. // Purpose: Find a place in the world where we should try to build this object
  432. //-----------------------------------------------------------------------------
  433. bool CBaseObject::CalculatePlacementPos( void )
  434. {
  435. CTFPlayer *pPlayer = GetOwner();
  436. if ( !pPlayer )
  437. return false;
  438. // Calculate build angles
  439. QAngle vecAngles = vec3_angle;
  440. vecAngles.y = pPlayer->EyeAngles().y;
  441. QAngle objAngles = vecAngles;
  442. SetAbsAngles( objAngles );
  443. UpdateDesiredBuildRotation( gpGlobals->frametime );
  444. objAngles.y = objAngles.y + m_flCurrentBuildRotation;
  445. SetLocalAngles( objAngles );
  446. AngleVectors( vecAngles, &m_vecBuildForward );
  447. // Adjust build distance based upon object size
  448. Vector2D vecObjectRadius;
  449. vecObjectRadius.x = MAX( fabs( m_vecBuildMins.m_Value.x ), fabs( m_vecBuildMaxs.m_Value.x ) );
  450. vecObjectRadius.y = MAX( fabs( m_vecBuildMins.m_Value.y ), fabs( m_vecBuildMaxs.m_Value.y ) );
  451. Vector2D vecPlayerRadius;
  452. Vector vecPlayerMins = pPlayer->WorldAlignMins();
  453. Vector vecPlayerMaxs = pPlayer->WorldAlignMaxs();
  454. vecPlayerRadius.x = MAX( fabs( vecPlayerMins.x ), fabs( vecPlayerMaxs.x ) );
  455. vecPlayerRadius.y = MAX( fabs( vecPlayerMins.y ), fabs( vecPlayerMaxs.y ) );
  456. m_flBuildDistance = vecObjectRadius.Length() + vecPlayerRadius.Length() + 4; // small safety buffer
  457. Vector vecBuildOrigin = pPlayer->WorldSpaceCenter() + m_vecBuildForward * m_flBuildDistance;
  458. m_vecBuildOrigin = vecBuildOrigin;
  459. Vector vErrorOrigin = vecBuildOrigin - (m_vecBuildMaxs - m_vecBuildMins) * 0.5f - m_vecBuildMins;
  460. Vector vBuildDims = m_vecBuildMaxs - m_vecBuildMins;
  461. Vector vHalfBuildDims = vBuildDims * 0.5;
  462. Vector vHalfBuildDimsXY( vHalfBuildDims.x, vHalfBuildDims.y, 0 );
  463. // Here, we start at the highest Z we'll allow for the top of the object. Then
  464. // we sweep an XY cross section downwards until it hits the ground.
  465. //
  466. // The rule is that the top of to box can't go lower than the player's feet, and the bottom of the
  467. // box can't go higher than the player's head.
  468. //
  469. // To simplify things in here, we treat the box as though it's symmetrical about all axes
  470. // (so mins = -maxs), then reoffset the box at the very end.
  471. Vector vHalfPlayerDims = (pPlayer->WorldAlignMaxs() - pPlayer->WorldAlignMins()) * 0.5f;
  472. float flBoxTopZ = pPlayer->WorldSpaceCenter().z + vHalfPlayerDims.z + vBuildDims.z;
  473. float flBoxBottomZ = pPlayer->WorldSpaceCenter().z - vHalfPlayerDims.z - vBuildDims.z;
  474. // First, find the ground (ie: where the bottom of the box goes).
  475. trace_t tr;
  476. float bottomZ = 0;
  477. int nIterations = 8;
  478. float topZ = flBoxTopZ;
  479. float topZInc = (flBoxBottomZ - flBoxTopZ) / (nIterations-1);
  480. int iIteration;
  481. for ( iIteration = 0; iIteration < nIterations; iIteration++ )
  482. {
  483. UTIL_TraceHull(
  484. Vector( m_vecBuildOrigin.x, m_vecBuildOrigin.y, topZ ),
  485. Vector( m_vecBuildOrigin.x, m_vecBuildOrigin.y, flBoxBottomZ ),
  486. -vHalfBuildDimsXY, vHalfBuildDimsXY, MASK_PLAYERSOLID_BRUSHONLY, this, COLLISION_GROUP_PLAYER_MOVEMENT, &tr );
  487. bottomZ = tr.endpos.z;
  488. // If there is no ground, then we can't place here.
  489. if ( tr.fraction == 1 )
  490. {
  491. m_vecBuildOrigin = vErrorOrigin;
  492. return false;
  493. }
  494. // if we found enough space to fit our object, place here
  495. if ( topZ - bottomZ > vBuildDims.z && !tr.startsolid )
  496. {
  497. break;
  498. }
  499. topZ += topZInc;
  500. }
  501. if ( iIteration == nIterations )
  502. {
  503. m_vecBuildOrigin = vErrorOrigin;
  504. return false;
  505. }
  506. // Now see if the range we've got leaves us room for our box.
  507. if ( topZ - bottomZ < vBuildDims.z )
  508. {
  509. m_vecBuildOrigin = vErrorOrigin;
  510. return false;
  511. }
  512. // Don't allow buildables on the train just yet.
  513. if ( tr.m_pEnt && tr.m_pEnt->IsBSPModel() )
  514. {
  515. if ( FClassnameIs( tr.m_pEnt, "func_tracktrain" ) )
  516. return false;
  517. }
  518. // Verify that it's not on too much of a slope by seeing how far the corners are from the ground.
  519. Vector vBottomCenter( m_vecBuildOrigin.x, m_vecBuildOrigin.y, bottomZ );
  520. if ( !VerifyCorner( vBottomCenter, -vHalfBuildDims.x, -vHalfBuildDims.y ) ||
  521. !VerifyCorner( vBottomCenter, +vHalfBuildDims.x, +vHalfBuildDims.y ) ||
  522. !VerifyCorner( vBottomCenter, +vHalfBuildDims.x, -vHalfBuildDims.y ) ||
  523. !VerifyCorner( vBottomCenter, -vHalfBuildDims.x, +vHalfBuildDims.y ) )
  524. {
  525. m_vecBuildOrigin = vErrorOrigin;
  526. return false;
  527. }
  528. // Ok, now we know the Z range where this box can fit.
  529. Vector vBottomLeft = m_vecBuildOrigin - vHalfBuildDims;
  530. vBottomLeft.z = bottomZ;
  531. m_vecBuildOrigin = vBottomLeft - m_vecBuildMins;
  532. m_vecBuildCenterOfMass = m_vecBuildOrigin + Vector( 0, 0, vHalfBuildDims.z );
  533. return true;
  534. }
  535. //-----------------------------------------------------------------------------
  536. // Purpose: Checks a position to make sure a corner of a building can live there
  537. //-----------------------------------------------------------------------------
  538. bool CBaseObject::VerifyCorner( const Vector &vBottomCenter, float xOffset, float yOffset )
  539. {
  540. // NOTE: I am changing the 0.1 on the bottom start to 2.0 to deal with the epsilon differnece
  541. // between the trace hull and trace line version of collision against a rotated bsp object.
  542. // I will probably want to change the code if we find more bugs around this, but for now as
  543. // a test changing it hear should be fine.
  544. // Start slightly above the surface
  545. Vector vStart( vBottomCenter.x + xOffset, vBottomCenter.y + yOffset, vBottomCenter.z + 2.0 );
  546. trace_t tr;
  547. UTIL_TraceLine(
  548. vStart,
  549. vStart - Vector( 0, 0, TF_OBJ_GROUND_CLEARANCE ),
  550. MASK_PLAYERSOLID_BRUSHONLY, this, COLLISION_GROUP_PLAYER_MOVEMENT, &tr );
  551. // Cannot build on very steep slopes ( > 45 degrees )
  552. if ( tr.fraction < 1.0f )
  553. {
  554. Vector vecUp(0,0,1);
  555. tr.plane.normal.NormalizeInPlace();
  556. float flDot = DotProduct( tr.plane.normal, vecUp );
  557. if ( flDot < 0.65 )
  558. {
  559. // Too steep
  560. return false;
  561. }
  562. }
  563. return !tr.startsolid && tr.fraction < 1;
  564. }
  565. //-----------------------------------------------------------------------------
  566. // Purpose: Check that the selected position is buildable
  567. //-----------------------------------------------------------------------------
  568. bool CBaseObject::IsPlacementPosValid( void )
  569. {
  570. bool bValid = CalculatePlacementPos();
  571. if ( !bValid )
  572. {
  573. return false;
  574. }
  575. CTFPlayer *pPlayer = GetOwner();
  576. if ( !pPlayer )
  577. {
  578. return false;
  579. }
  580. #ifndef CLIENT_DLL
  581. if ( !EstimateValidBuildPos() )
  582. return false;
  583. #endif
  584. // Verify that the entire object can fit here
  585. // Important! here we want to collide with players and other buildings, but not dropped weapons
  586. trace_t tr;
  587. UTIL_TraceEntity( this, m_vecBuildOrigin, m_vecBuildOrigin, MASK_SOLID, NULL, COLLISION_GROUP_PLAYER, &tr );
  588. if ( tr.fraction < 1.0f )
  589. return false;
  590. // Make sure we can see the final position
  591. UTIL_TraceLine( pPlayer->EyePosition(), m_vecBuildOrigin + Vector(0,0,m_vecBuildMaxs[2] * 0.5), MASK_PLAYERSOLID_BRUSHONLY, pPlayer, COLLISION_GROUP_NONE, &tr );
  592. if ( tr.fraction < 1.0 )
  593. {
  594. return false;
  595. }
  596. return true;
  597. }
  598. //-----------------------------------------------------------------------------
  599. // Purpose: Shared, update the build rotation
  600. //-----------------------------------------------------------------------------
  601. void CBaseObject::UpdateDesiredBuildRotation( float flFrameTime )
  602. {
  603. // approach desired build rotation
  604. float flBuildRotation = 90.0f * m_iDesiredBuildRotations;
  605. m_flCurrentBuildRotation = ApproachAngle( flBuildRotation, m_flCurrentBuildRotation, tf_obj_build_rotation_speed.GetFloat() * flFrameTime );
  606. }