summaryrefslogtreecommitdiff
path: root/game/shared/tf/baseobject_shared.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'game/shared/tf/baseobject_shared.cpp')
-rw-r--r--game/shared/tf/baseobject_shared.cpp723
1 files changed, 723 insertions, 0 deletions
diff --git a/game/shared/tf/baseobject_shared.cpp b/game/shared/tf/baseobject_shared.cpp
new file mode 100644
index 0000000..36fe6e7
--- /dev/null
+++ b/game/shared/tf/baseobject_shared.cpp
@@ -0,0 +1,723 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include "baseobject_shared.h"
+#include <KeyValues.h>
+#include "tf_shareddefs.h"
+#include "engine/ivmodelinfo.h"
+
+#ifdef GAME_DLL
+ #include "func_no_build.h"
+ #include "tf_player.h"
+ #include "tf_team.h"
+ #include "func_no_build.h"
+ #include "func_respawnroom.h"
+#else
+ #include "c_tf_player.h"
+ #include "c_tf_team.h"
+#endif
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+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." );
+
+//-----------------------------------------------------------------------------
+// Purpose: Parse our model and create the buildpoints in it
+//-----------------------------------------------------------------------------
+void CBaseObject::CreateBuildPoints( void )
+{
+ // Clear out any existing build points
+ m_BuildPoints.RemoveAll();
+
+ KeyValues * modelKeyValues = new KeyValues("");
+ if ( !modelKeyValues->LoadFromBuffer( modelinfo->GetModelName( GetModel() ), modelinfo->GetModelKeyValueText( GetModel() ) ) )
+ {
+ return;
+ }
+
+ // Do we have a build point section?
+ KeyValues *pkvAllBuildPoints = modelKeyValues->FindKey("build_points");
+ if ( pkvAllBuildPoints )
+ {
+ KeyValues *pkvBuildPoint = pkvAllBuildPoints->GetFirstSubKey();
+ while ( pkvBuildPoint )
+ {
+ // Find the attachment first
+ const char *sAttachment = pkvBuildPoint->GetName();
+ int iAttachmentNumber = LookupAttachment( sAttachment );
+ if ( iAttachmentNumber > 0 )
+ {
+ AddAndParseBuildPoint( iAttachmentNumber, pkvBuildPoint );
+ }
+ else
+ {
+ Msg( "ERROR: Model %s specifies buildpoint %s, but has no attachment named %s.\n", STRING(GetModelName()), pkvBuildPoint->GetString(), pkvBuildPoint->GetString() );
+ }
+
+ pkvBuildPoint = pkvBuildPoint->GetNextKey();
+ }
+ }
+
+ // Any virtual build points (build points that aren't on an attachment)?
+ pkvAllBuildPoints = modelKeyValues->FindKey("virtual_build_points");
+ if ( pkvAllBuildPoints )
+ {
+ KeyValues *pkvBuildPoint = pkvAllBuildPoints->GetFirstSubKey();
+ while ( pkvBuildPoint )
+ {
+ AddAndParseBuildPoint( -1, pkvBuildPoint );
+ pkvBuildPoint = pkvBuildPoint->GetNextKey();
+ }
+ }
+
+ modelKeyValues->deleteThis();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseObject::AddAndParseBuildPoint( int iAttachmentNumber, KeyValues *pkvBuildPoint )
+{
+ int iPoint = AddBuildPoint( iAttachmentNumber );
+
+
+ m_BuildPoints[iPoint].m_bPutInAttachmentSpace = (pkvBuildPoint->GetInt( "PutInAttachmentSpace", 0 ) != 0);
+
+ // Now see if we've got a set of valid objects specified
+ KeyValues *pkvValidObjects = pkvBuildPoint->FindKey( "valid_objects" );
+ if ( pkvValidObjects )
+ {
+ KeyValues *pkvObject = pkvValidObjects->GetFirstSubKey();
+ while ( pkvObject )
+ {
+ const char *pSpecifiedObject = pkvObject->GetName();
+ int iLenObjName = Q_strlen( pSpecifiedObject );
+
+ // Find the object index for the name
+ for ( int i = 0; i < OBJ_LAST; i++ )
+ {
+ if ( !Q_strncasecmp( GetObjectInfo( i )->m_pClassName, pSpecifiedObject, iLenObjName) )
+ {
+ AddValidObjectToBuildPoint( iPoint, i );
+ break;
+ }
+ }
+
+ pkvObject = pkvObject->GetNextKey();
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Add a new buildpoint to my list of buildpoints
+//-----------------------------------------------------------------------------
+int CBaseObject::AddBuildPoint( int iAttachmentNum )
+{
+ // Make a new buildpoint
+ BuildPoint_t sNewPoint;
+ sNewPoint.m_hObject = NULL;
+ sNewPoint.m_iAttachmentNum = iAttachmentNum;
+ sNewPoint.m_bPutInAttachmentSpace = false;
+ Q_memset( sNewPoint.m_bValidObjects, 0, sizeof( sNewPoint.m_bValidObjects ) );
+
+ // Insert it into our list
+ return m_BuildPoints.AddToTail( sNewPoint );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseObject::AddValidObjectToBuildPoint( int iPoint, int iObjectType )
+{
+ Assert( iPoint <= GetNumBuildPoints() );
+ m_BuildPoints[iPoint].m_bValidObjects[ iObjectType ] = true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CBaseObject::GetNumBuildPoints( void ) const
+{
+ return m_BuildPoints.Size();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CBaseObject* CBaseObject::GetBuildPointObject( int iPoint )
+{
+ Assert( iPoint >= 0 && iPoint <= GetNumBuildPoints() );
+
+ return m_BuildPoints[iPoint].m_hObject;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return true if the specified object type can be built on this point
+//-----------------------------------------------------------------------------
+bool CBaseObject::CanBuildObjectOnBuildPoint( int iPoint, int iObjectType )
+{
+ Assert( iPoint >= 0 && iPoint <= GetNumBuildPoints() );
+
+ // Allowed to build here?
+ if ( !m_BuildPoints[iPoint].m_bValidObjects[ iObjectType ] )
+ return false;
+
+ // Buildpoint empty?
+ return ( m_BuildPoints[iPoint].m_hObject == NULL );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CBaseObject::GetBuildPoint( int iPoint, Vector &vecOrigin, QAngle &vecAngles )
+{
+ Assert( iPoint >= 0 && iPoint <= GetNumBuildPoints() );
+
+ int iAttachmentNum = m_BuildPoints[iPoint].m_iAttachmentNum;
+ if ( iAttachmentNum == -1 )
+ {
+ vecOrigin = GetAbsOrigin();
+ vecAngles = GetAbsAngles();
+ return true;
+ }
+ else
+ {
+ return GetAttachment( m_BuildPoints[iPoint].m_iAttachmentNum, vecOrigin, vecAngles );
+ }
+}
+
+
+int CBaseObject::GetBuildPointAttachmentIndex( int iPoint ) const
+{
+ Assert( iPoint >= 0 && iPoint <= GetNumBuildPoints() );
+
+ if ( m_BuildPoints[iPoint].m_bPutInAttachmentSpace )
+ {
+ return m_BuildPoints[iPoint].m_iAttachmentNum;
+ }
+ else
+ {
+ return 0;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseObject::SetObjectOnBuildPoint( int iPoint, CBaseObject *pObject )
+{
+ Assert( iPoint >= 0 && iPoint <= GetNumBuildPoints() );
+ m_BuildPoints[iPoint].m_hObject = pObject;
+}
+
+ConVar tf_obj_max_attach_dist( "tf_obj_max_attach_dist", "160", FCVAR_REPLICATED | FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+float CBaseObject::GetMaxSnapDistance( int iPoint )
+{
+ return tf_obj_max_attach_dist.GetFloat();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return the number of objects on my build points
+//-----------------------------------------------------------------------------
+int CBaseObject::GetNumObjectsOnMe( void )
+{
+ int iObjects = 0;
+ for ( int i = 0; i < GetNumBuildPoints(); i++ )
+ {
+ if ( m_BuildPoints[i].m_hObject )
+ {
+ iObjects++;
+ }
+ }
+
+ return iObjects;
+}
+
+
+//-----------------------------------------------------------------------------
+// I've finished building the specified object on the specified build point
+//-----------------------------------------------------------------------------
+int CBaseObject::FindObjectOnBuildPoint( CBaseObject *pObject )
+{
+ for (int i = m_BuildPoints.Count(); --i >= 0; )
+ {
+ if (m_BuildPoints[i].m_hObject == pObject)
+ return i;
+ }
+ return -1;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CBaseObject *CBaseObject::GetObjectOfTypeOnMe( int iObjectType )
+{
+ for ( int iObject = 0; iObject < GetNumObjectsOnMe(); ++iObject )
+ {
+ CBaseObject *pObject = dynamic_cast<CBaseObject*>( m_BuildPoints[iObject].m_hObject.Get() );
+ if ( pObject )
+ {
+ if ( pObject->GetType() == iObjectType )
+ return pObject;
+ }
+ }
+
+ return NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseObject::RemoveAllObjects( void )
+{
+#ifndef CLIENT_DLL
+ for ( int i = 0; i < GetNumBuildPoints(); i++ )
+ {
+ if ( m_BuildPoints[i].m_hObject )
+ {
+
+ UTIL_Remove( m_BuildPoints[i].m_hObject );
+ }
+ }
+#endif // !CLIENT_DLL
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CBaseObject *CBaseObject::GetParentObject( void )
+{
+ if ( GetMoveParent() )
+ return dynamic_cast<CBaseObject*>(GetMoveParent());
+
+ return NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CBaseEntity *CBaseObject::GetParentEntity( void )
+{
+ if ( GetMoveParent() )
+ return GetMoveParent();
+
+ return NULL;
+}
+
+static ConVar sv_ignore_hitboxes( "sv_ignore_hitboxes", "0", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY, "Disable hitboxes" );
+
+bool CBaseObject::TestHitboxes( const Ray_t &ray, unsigned int fContentsMask, trace_t& tr )
+{
+ bool bReturn = BaseClass::TestHitboxes( ray, fContentsMask, tr );
+
+ if( !sv_ignore_hitboxes.GetBool() )
+ return bReturn;
+
+
+ if( !bReturn )
+ {
+ return false;
+ }
+
+ if( tr.fraction == 1.f && !tr.allsolid && !tr.startsolid )
+ {
+ return false;
+ }
+
+ return bReturn;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return true if this object should be active
+//-----------------------------------------------------------------------------
+bool CBaseObject::ShouldBeActive( void )
+{
+ if ( IsDisabled() )
+ return false;
+
+ // Placing and/or constructing objects shouldn't be active
+ if ( IsPlacing() || IsBuilding() || IsCarried() )
+ return false;
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Set the object's type
+//-----------------------------------------------------------------------------
+void CBaseObject::SetType( int iObjectType )
+{
+ m_iObjectType = iObjectType;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : act -
+//-----------------------------------------------------------------------------
+void CBaseObject::SetActivity( Activity act )
+{
+ // Hrm, it's not actually a studio model...
+ if ( !GetModelPtr() )
+ return;
+
+ int sequence = SelectWeightedSequence( act );
+ if ( sequence != ACTIVITY_NOT_AVAILABLE )
+ {
+ m_Activity = act;
+ SetObjectSequence( sequence );
+ }
+ else
+ {
+ m_Activity = ACT_INVALID;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : Activity
+//-----------------------------------------------------------------------------
+Activity CBaseObject::GetActivity( ) const
+{
+ return m_Activity;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Thin wrapper over CBaseAnimating::SetSequence to do bookkeeping.
+// Input : sequence -
+//-----------------------------------------------------------------------------
+void CBaseObject::SetObjectSequence( int sequence )
+{
+ ResetSequence( sequence );
+
+ SetCycle( GetReversesBuildingConstructionSpeed() != 0.0f ? 1.0f : 0.0f );
+
+#if !defined( CLIENT_DLL )
+ if ( IsUsingClientSideAnimation() )
+ {
+ ResetClientsideFrame();
+ }
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseObject::OnGoActive( void )
+{
+#ifndef CLIENT_DLL
+ while ( m_nDefaultUpgradeLevel + 1 > m_iUpgradeLevel )
+ {
+ StartUpgrading();
+ }
+
+ // Play startup animation
+ PlayStartupAnimation();
+
+ // Switch to the on state
+ if ( GetModelPtr() )
+ {
+ int index = FindBodygroupByName( "powertoggle" );
+ if ( index >= 0 )
+ {
+ SetBodygroup( index, 1 );
+ }
+ }
+
+ UpdateDisabledState();
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseObject::OnGoInactive( void )
+{
+#ifndef CLIENT_DLL
+ if ( GetModelPtr() )
+ {
+ // Switch to the off state
+ int index = FindBodygroupByName( "powertoggle" );
+ if ( index >= 0 )
+ {
+ SetBodygroup( index, 0 );
+ }
+ }
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : collisionGroup -
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CBaseObject::ShouldCollide( int collisionGroup, int contentsMask ) const
+{
+ if ( collisionGroup == COLLISION_GROUP_PLAYER_MOVEMENT )
+ {
+ if ( GetCollisionGroup() == TFCOLLISION_GROUP_OBJECT_SOLIDTOPLAYERMOVEMENT )
+ {
+ return true;
+ }
+
+ switch( GetTeamNumber() )
+ {
+ case TF_TEAM_RED:
+ if ( !( contentsMask & CONTENTS_REDTEAM ) )
+ return false;
+ break;
+
+ case TF_TEAM_BLUE:
+ if ( !( contentsMask & CONTENTS_BLUETEAM ) )
+ return false;
+ break;
+ }
+ }
+
+ return BaseClass::ShouldCollide( collisionGroup, contentsMask );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Should objects repel players on the same team
+//-----------------------------------------------------------------------------
+bool CBaseObject::ShouldPlayersAvoid( void )
+{
+ return ( GetCollisionGroup() == TFCOLLISION_GROUP_OBJECT );
+}
+
+//-----------------------------------------------------------------------------
+// Do we have to be built on an attachment point
+//-----------------------------------------------------------------------------
+bool CBaseObject::MustBeBuiltOnAttachmentPoint( void ) const
+{
+ return (m_fObjectFlags & OF_MUST_BE_BUILT_ON_ATTACHMENT) != 0;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Find a place in the world where we should try to build this object
+//-----------------------------------------------------------------------------
+bool CBaseObject::CalculatePlacementPos( void )
+{
+ CTFPlayer *pPlayer = GetOwner();
+
+ if ( !pPlayer )
+ return false;
+
+ // Calculate build angles
+ QAngle vecAngles = vec3_angle;
+ vecAngles.y = pPlayer->EyeAngles().y;
+
+ QAngle objAngles = vecAngles;
+
+ SetAbsAngles( objAngles );
+
+ UpdateDesiredBuildRotation( gpGlobals->frametime );
+
+ objAngles.y = objAngles.y + m_flCurrentBuildRotation;
+
+ SetLocalAngles( objAngles );
+ AngleVectors( vecAngles, &m_vecBuildForward );
+
+ // Adjust build distance based upon object size
+ Vector2D vecObjectRadius;
+ vecObjectRadius.x = MAX( fabs( m_vecBuildMins.m_Value.x ), fabs( m_vecBuildMaxs.m_Value.x ) );
+ vecObjectRadius.y = MAX( fabs( m_vecBuildMins.m_Value.y ), fabs( m_vecBuildMaxs.m_Value.y ) );
+
+ Vector2D vecPlayerRadius;
+ Vector vecPlayerMins = pPlayer->WorldAlignMins();
+ Vector vecPlayerMaxs = pPlayer->WorldAlignMaxs();
+ vecPlayerRadius.x = MAX( fabs( vecPlayerMins.x ), fabs( vecPlayerMaxs.x ) );
+ vecPlayerRadius.y = MAX( fabs( vecPlayerMins.y ), fabs( vecPlayerMaxs.y ) );
+
+ m_flBuildDistance = vecObjectRadius.Length() + vecPlayerRadius.Length() + 4; // small safety buffer
+ Vector vecBuildOrigin = pPlayer->WorldSpaceCenter() + m_vecBuildForward * m_flBuildDistance;
+
+ m_vecBuildOrigin = vecBuildOrigin;
+ Vector vErrorOrigin = vecBuildOrigin - (m_vecBuildMaxs - m_vecBuildMins) * 0.5f - m_vecBuildMins;
+
+ Vector vBuildDims = m_vecBuildMaxs - m_vecBuildMins;
+ Vector vHalfBuildDims = vBuildDims * 0.5;
+ Vector vHalfBuildDimsXY( vHalfBuildDims.x, vHalfBuildDims.y, 0 );
+
+ // Here, we start at the highest Z we'll allow for the top of the object. Then
+ // we sweep an XY cross section downwards until it hits the ground.
+ //
+ // The rule is that the top of to box can't go lower than the player's feet, and the bottom of the
+ // box can't go higher than the player's head.
+ //
+ // To simplify things in here, we treat the box as though it's symmetrical about all axes
+ // (so mins = -maxs), then reoffset the box at the very end.
+ Vector vHalfPlayerDims = (pPlayer->WorldAlignMaxs() - pPlayer->WorldAlignMins()) * 0.5f;
+ float flBoxTopZ = pPlayer->WorldSpaceCenter().z + vHalfPlayerDims.z + vBuildDims.z;
+ float flBoxBottomZ = pPlayer->WorldSpaceCenter().z - vHalfPlayerDims.z - vBuildDims.z;
+
+ // First, find the ground (ie: where the bottom of the box goes).
+ trace_t tr;
+ float bottomZ = 0;
+ int nIterations = 8;
+ float topZ = flBoxTopZ;
+ float topZInc = (flBoxBottomZ - flBoxTopZ) / (nIterations-1);
+ int iIteration;
+ for ( iIteration = 0; iIteration < nIterations; iIteration++ )
+ {
+ UTIL_TraceHull(
+ Vector( m_vecBuildOrigin.x, m_vecBuildOrigin.y, topZ ),
+ Vector( m_vecBuildOrigin.x, m_vecBuildOrigin.y, flBoxBottomZ ),
+ -vHalfBuildDimsXY, vHalfBuildDimsXY, MASK_PLAYERSOLID_BRUSHONLY, this, COLLISION_GROUP_PLAYER_MOVEMENT, &tr );
+ bottomZ = tr.endpos.z;
+
+ // If there is no ground, then we can't place here.
+ if ( tr.fraction == 1 )
+ {
+ m_vecBuildOrigin = vErrorOrigin;
+ return false;
+ }
+
+ // if we found enough space to fit our object, place here
+ if ( topZ - bottomZ > vBuildDims.z && !tr.startsolid )
+ {
+ break;
+ }
+
+ topZ += topZInc;
+ }
+
+ if ( iIteration == nIterations )
+ {
+ m_vecBuildOrigin = vErrorOrigin;
+ return false;
+ }
+
+ // Now see if the range we've got leaves us room for our box.
+ if ( topZ - bottomZ < vBuildDims.z )
+ {
+ m_vecBuildOrigin = vErrorOrigin;
+ return false;
+ }
+
+ // Don't allow buildables on the train just yet.
+ if ( tr.m_pEnt && tr.m_pEnt->IsBSPModel() )
+ {
+ if ( FClassnameIs( tr.m_pEnt, "func_tracktrain" ) )
+ return false;
+ }
+
+ // Verify that it's not on too much of a slope by seeing how far the corners are from the ground.
+ Vector vBottomCenter( m_vecBuildOrigin.x, m_vecBuildOrigin.y, bottomZ );
+ if ( !VerifyCorner( vBottomCenter, -vHalfBuildDims.x, -vHalfBuildDims.y ) ||
+ !VerifyCorner( vBottomCenter, +vHalfBuildDims.x, +vHalfBuildDims.y ) ||
+ !VerifyCorner( vBottomCenter, +vHalfBuildDims.x, -vHalfBuildDims.y ) ||
+ !VerifyCorner( vBottomCenter, -vHalfBuildDims.x, +vHalfBuildDims.y ) )
+ {
+ m_vecBuildOrigin = vErrorOrigin;
+ return false;
+ }
+
+ // Ok, now we know the Z range where this box can fit.
+ Vector vBottomLeft = m_vecBuildOrigin - vHalfBuildDims;
+ vBottomLeft.z = bottomZ;
+ m_vecBuildOrigin = vBottomLeft - m_vecBuildMins;
+
+ m_vecBuildCenterOfMass = m_vecBuildOrigin + Vector( 0, 0, vHalfBuildDims.z );
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Checks a position to make sure a corner of a building can live there
+//-----------------------------------------------------------------------------
+bool CBaseObject::VerifyCorner( const Vector &vBottomCenter, float xOffset, float yOffset )
+{
+ // NOTE: I am changing the 0.1 on the bottom start to 2.0 to deal with the epsilon differnece
+ // between the trace hull and trace line version of collision against a rotated bsp object.
+ // I will probably want to change the code if we find more bugs around this, but for now as
+ // a test changing it hear should be fine.
+ // Start slightly above the surface
+ Vector vStart( vBottomCenter.x + xOffset, vBottomCenter.y + yOffset, vBottomCenter.z + 2.0 );
+
+ trace_t tr;
+ UTIL_TraceLine(
+ vStart,
+ vStart - Vector( 0, 0, TF_OBJ_GROUND_CLEARANCE ),
+ MASK_PLAYERSOLID_BRUSHONLY, this, COLLISION_GROUP_PLAYER_MOVEMENT, &tr );
+
+ // Cannot build on very steep slopes ( > 45 degrees )
+ if ( tr.fraction < 1.0f )
+ {
+ Vector vecUp(0,0,1);
+ tr.plane.normal.NormalizeInPlace();
+ float flDot = DotProduct( tr.plane.normal, vecUp );
+
+ if ( flDot < 0.65 )
+ {
+ // Too steep
+ return false;
+ }
+ }
+
+ return !tr.startsolid && tr.fraction < 1;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Check that the selected position is buildable
+//-----------------------------------------------------------------------------
+bool CBaseObject::IsPlacementPosValid( void )
+{
+ bool bValid = CalculatePlacementPos();
+
+ if ( !bValid )
+ {
+ return false;
+ }
+
+ CTFPlayer *pPlayer = GetOwner();
+
+ if ( !pPlayer )
+ {
+ return false;
+ }
+
+#ifndef CLIENT_DLL
+ if ( !EstimateValidBuildPos() )
+ return false;
+#endif
+
+ // Verify that the entire object can fit here
+ // Important! here we want to collide with players and other buildings, but not dropped weapons
+ trace_t tr;
+ UTIL_TraceEntity( this, m_vecBuildOrigin, m_vecBuildOrigin, MASK_SOLID, NULL, COLLISION_GROUP_PLAYER, &tr );
+
+ if ( tr.fraction < 1.0f )
+ return false;
+
+ // Make sure we can see the final position
+ UTIL_TraceLine( pPlayer->EyePosition(), m_vecBuildOrigin + Vector(0,0,m_vecBuildMaxs[2] * 0.5), MASK_PLAYERSOLID_BRUSHONLY, pPlayer, COLLISION_GROUP_NONE, &tr );
+ if ( tr.fraction < 1.0 )
+ {
+ return false;
+ }
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Shared, update the build rotation
+//-----------------------------------------------------------------------------
+
+void CBaseObject::UpdateDesiredBuildRotation( float flFrameTime )
+{
+ // approach desired build rotation
+ float flBuildRotation = 90.0f * m_iDesiredBuildRotations;
+
+ m_flCurrentBuildRotation = ApproachAngle( flBuildRotation, m_flCurrentBuildRotation, tf_obj_build_rotation_speed.GetFloat() * flFrameTime );
+} \ No newline at end of file