summaryrefslogtreecommitdiff
path: root/game/server/tf2/tf_obj.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'game/server/tf2/tf_obj.cpp')
-rw-r--r--game/server/tf2/tf_obj.cpp3243
1 files changed, 3243 insertions, 0 deletions
diff --git a/game/server/tf2/tf_obj.cpp b/game/server/tf2/tf_obj.cpp
new file mode 100644
index 0000000..cd07213
--- /dev/null
+++ b/game/server/tf2/tf_obj.cpp
@@ -0,0 +1,3243 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Base Object built by players
+//
+// $NoKeywords: $
+//=============================================================================//
+#include "cbase.h"
+#include "tf_player.h"
+#include "tf_team.h"
+#include "tf_obj.h"
+#include "tf_basecombatweapon.h"
+#include "rope.h"
+#include "rope_shared.h"
+#include "bone_setup.h"
+#include "tf_func_resource.h"
+#include "ndebugoverlay.h"
+#include "rope_helpers.h"
+#include "IEffects.h"
+#include "vstdlib/random.h"
+#include "tier1/strtools.h"
+#include "basegrenade_shared.h"
+#include "grenade_objectsapper.h"
+#include "tf_stats.h"
+#include "tf_gamerules.h"
+#include "engine/IEngineSound.h"
+#include "tf_obj_sentrygun.h"
+#include "tf_obj_powerpack.h"
+#include "tf_shareddefs.h"
+#include "VGuiScreen.h"
+#include "resource_chunk.h"
+#include "hierarchy.h"
+#include "tf_func_construction_yard.h"
+#include "tf_func_no_build.h"
+#include <KeyValues.h>
+#include "team_messages.h"
+#include "info_act.h"
+#include "info_vehicle_bay.h"
+#include "ihasbuildpoints.h"
+#include "tf_obj_buff_station.h"
+#include "info_buildpoint.h"
+#include "utldict.h"
+#include "filesystem.h"
+#include "npcevent.h"
+#include "tf_shareddefs.h"
+#include "animation.h"
+
+// Control panels
+#define SCREEN_OVERLAY_MATERIAL "vgui/screens/vgui_overlay"
+
+#define ROPE_HANG_DIST 150
+
+
+ConVar object_verbose( "object_verbose", "0", 0, "Debug object system." );
+ConVar obj_damage_factor( "obj_damage_factor","0", FCVAR_NONE, "Factor applied to all damage done to objects" );
+ConVar obj_child_damage_factor( "obj_child_damage_factor","0.25", FCVAR_NONE, "Factor applied to damage done to objects that are built on a buildpoint" );
+ConVar tf_fastbuild("tf_fastbuild", "0", FCVAR_CHEAT);
+ConVar tf_obj_ground_clearance( "tf_obj_ground_clearance", "60", 0, "Object corners can be this high above the ground" );
+
+extern short g_sModelIndexFireball;
+
+// Minimum distance between 2 objects to ensure player movement between them
+#define MINIMUM_OBJECT_SAFE_DISTANCE 100
+
+// Maximum number of a type of objects on a single resource zone
+#define MAX_OBJECTS_PER_ZONE 1
+
+// Time it takes a fully healed object to deteriorate
+ConVar object_deterioration_time( "object_deterioration_time", "30", 0, "Time it takes for a fully-healed object to deteriorate." );
+
+// Time after taking damage that an object will still drop resources on death
+#define MAX_DROP_TIME_AFTER_DAMAGE 5
+
+#define OBJ_BASE_THINK_CONTEXT "BaseObjectThink"
+#define OBJ_LOSTPOWER_THINK_CONTEXT "LostPowerThink"
+
+BEGIN_DATADESC( CBaseObject )
+ // keys
+ DEFINE_KEYFIELD_NOT_SAVED( m_bInvulnerable, FIELD_BOOLEAN, "Invulnerable" ),
+ DEFINE_KEYFIELD_NOT_SAVED( m_flRepairMultiplier, FIELD_FLOAT, "RepairMult" ),
+ DEFINE_KEYFIELD_NOT_SAVED( m_iszUnderAttackSound, FIELD_STRING, "AttackNotify" ),
+ DEFINE_KEYFIELD_NOT_SAVED( m_flMinDisableHealth, FIELD_FLOAT, "MinDisabledHealth" ),
+ DEFINE_KEYFIELD_NOT_SAVED( m_SolidToPlayers, FIELD_INTEGER, "SolidToPlayer" ),
+ DEFINE_KEYFIELD_NOT_SAVED( m_iszDisabledModel, FIELD_STRING, "DisabledModel" ),
+ DEFINE_KEYFIELD_NOT_SAVED( m_bCantDie, FIELD_BOOLEAN, "CantDie" ),
+
+ // Inputs
+ DEFINE_INPUTFUNC( FIELD_INTEGER, "SetHealth", InputSetHealth ),
+ DEFINE_INPUTFUNC( FIELD_INTEGER, "AddHealth", InputAddHealth ),
+ DEFINE_INPUTFUNC( FIELD_INTEGER, "RemoveHealth", InputRemoveHealth ),
+ DEFINE_INPUTFUNC( FIELD_INTEGER, "SetMinDisabledHealth", InputSetMinDisabledHealth ),
+ DEFINE_INPUTFUNC( FIELD_INTEGER, "SetSolidToPlayer", InputSetSolidToPlayer ),
+
+ // Outputs
+ DEFINE_OUTPUT( m_OnDestroyed, "OnDestroyed" ),
+ DEFINE_OUTPUT( m_OnDamaged, "OnDamaged" ),
+ DEFINE_OUTPUT( m_OnRepaired, "OnRepaired" ),
+ DEFINE_OUTPUT( m_OnBecomingDisabled, "OnDisabled" ),
+ DEFINE_OUTPUT( m_OnBecomingReenabled, "OnReenabled" ),
+ DEFINE_OUTPUT( m_OnObjectHealthChanged, "OnObjectHealthChanged" )
+END_DATADESC()
+
+
+IMPLEMENT_SERVERCLASS_ST(CBaseObject, DT_BaseObject)
+ SendPropInt(SENDINFO(m_iHealth), 13 ),
+ SendPropInt(SENDINFO(m_iMaxHealth), 13 ),
+ SendPropInt(SENDINFO(m_bHasSapper), 1, SPROP_UNSIGNED ),
+ SendPropInt(SENDINFO(m_iObjectType), 6, SPROP_UNSIGNED ),
+ SendPropInt(SENDINFO(m_bBuilding), 1, SPROP_UNSIGNED ),
+ SendPropInt(SENDINFO(m_bPlacing), 1, SPROP_UNSIGNED ),
+ SendPropFloat(SENDINFO(m_flPercentageConstructed), 8, 0, 0.0, 1.0f ),
+ SendPropInt(SENDINFO(m_fObjectFlags), OF_BIT_COUNT, SPROP_UNSIGNED ),
+ SendPropInt(SENDINFO(m_bDeteriorating), 1, SPROP_UNSIGNED ),
+ SendPropEHandle(SENDINFO(m_hBuiltOnEntity)),
+ SendPropInt( SENDINFO( m_takedamage ), 2, SPROP_UNSIGNED ),
+ SendPropInt( SENDINFO( m_bDisabled ), 1, SPROP_UNSIGNED ),
+ SendPropEHandle( SENDINFO( m_hBuilder ) ),
+END_SEND_TABLE();
+
+
+// This controls whether ropes attached to objects are transmitted or not. It's important that
+// ropes aren't transmitted to guys who don't own them.
+class CObjectRopeTransmitProxy : public CBaseTransmitProxy
+{
+public:
+ CObjectRopeTransmitProxy( CBaseEntity *pRope ) : CBaseTransmitProxy( pRope )
+ {
+ }
+
+ virtual int ShouldTransmit( const CCheckTransmitInfo *pInfo, int nPrevShouldTransmitResult )
+ {
+ // Don't transmit the rope if it's not even visible.
+ if ( !nPrevShouldTransmitResult )
+ return FL_EDICT_DONTSEND;
+
+ // This proxy only wants to be active while one of the two objects is being placed.
+ // When they're done being placed, the proxy goes away and the rope draws like normal.
+ bool bAnyObjectPlacing = (m_hObj1 && m_hObj1->IsPlacing()) || (m_hObj2 && m_hObj2->IsPlacing());
+ if ( !bAnyObjectPlacing )
+ {
+ Release();
+ return nPrevShouldTransmitResult;
+ }
+
+ // Give control to whichever object is being placed.
+ if ( m_hObj1 && m_hObj1->IsPlacing() )
+ return m_hObj1->ShouldTransmit( pInfo );
+
+ else if ( m_hObj2 && m_hObj2->IsPlacing() )
+ return m_hObj2->ShouldTransmit( pInfo );
+
+ else
+ return FL_EDICT_ALWAYS;
+ }
+
+
+ CHandle<CBaseObject> m_hObj1;
+ CHandle<CBaseObject> m_hObj2;
+};
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CBaseObject::CBaseObject()
+{
+ m_hPowerPack = NULL;
+ m_iHealth = m_iMaxHealth = m_flHealth = 0;
+ m_aRopes.Purge();
+ m_flPercentageConstructed = 0;
+ m_bPlacing = false;
+ m_bBuilding = false;
+ m_bInvulnerable = false;
+ m_bCantDie = false;
+ m_bDeteriorating = false;
+ m_flRepairMultiplier = 1;
+ m_hBuffStation = NULL;
+ m_bBuffActivated = false;
+ m_Activity = ACT_OBJ_IDLE;
+ m_bDisabled = false;
+ m_hVehicleBay = NULL;
+ m_flLastRepairTime = 0;
+ m_flNextRepairMultiplier = 0;
+ m_flRepairedSinceLastTime = 0;
+ m_iszUnderAttackSound = NULL_STRING;
+ m_SolidToPlayers = SOLID_TO_PLAYER_USE_DEFAULT;
+ m_iszDisabledModel = NULL_STRING;
+ m_iszEnabledModel = NULL_STRING;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseObject::UpdateOnRemove( void )
+{
+ m_bDying = true;
+
+ // Remove anything left on me
+ IHasBuildPoints *pBPInterface = dynamic_cast<IHasBuildPoints*>(this);
+ if ( pBPInterface && pBPInterface->GetNumObjectsOnMe() )
+ {
+ pBPInterface->RemoveAllObjects();
+ }
+
+ DestroyObject();
+
+ if ( GetTeam() )
+ {
+ ((CTFTeam*)GetTeam())->RemoveObject( this );
+ }
+
+ // Make sure the object isn't in either team's list of objects...
+ Assert( !GetGlobalTFTeam(1)->IsObjectOnTeam( this ) );
+ Assert( !GetGlobalTFTeam(2)->IsObjectOnTeam( this ) );
+
+ // Chain at end to mimic destructor unwind order
+ BaseClass::UpdateOnRemove();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CBaseObject::ShouldTransmit( const CCheckTransmitInfo *pInfo )
+{
+ // Always transmit to owner
+ if ( GetBuilder() && pInfo->m_pClientEnt == GetBuilder()->edict() )
+ return FL_EDICT_ALWAYS;
+
+ // Placement models only transmit to owners
+ if ( IsPlacing() )
+ return FL_EDICT_DONTSEND;
+
+ return BaseClass::ShouldTransmit( pInfo );
+}
+
+
+void CBaseObject::SetTransmit( CCheckTransmitInfo *pInfo, bool bAlways )
+{
+ // Are we already marked for transmission?
+ if ( pInfo->m_pTransmitEdict->Get( entindex() ) )
+ return;
+
+ BaseClass::SetTransmit( pInfo, bAlways );
+
+ // Force our screens to be sent too.
+ int nTeam = CBaseEntity::Instance( pInfo->m_pClientEnt )->GetTeamNumber();
+ for ( int i=0; i < m_hScreens.Count(); i++ )
+ {
+ CVGuiScreen *pScreen = m_hScreens[i].Get();
+ if ( pScreen && pScreen->IsVisibleToTeam( nTeam ) )
+ pScreen->SetTransmit( pInfo, bAlways );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseObject::Precache()
+{
+ PrecacheVGuiScreen( "screen_basic_with_disable" );
+
+ if ( m_iszUnderAttackSound != NULL_STRING )
+ {
+ PrecacheScriptSound( STRING(m_iszUnderAttackSound) );
+ }
+ PrecacheMaterial( SCREEN_OVERLAY_MATERIAL );
+
+ if ( m_iszDisabledModel != NULL_STRING )
+ {
+ PrecacheModel( STRING( m_iszDisabledModel ) );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseObject::Spawn( void )
+{
+ Precache();
+
+ CollisionProp()->SetSurroundingBoundsType( USE_BEST_COLLISION_BOUNDS );
+ SetSolidToPlayers( m_SolidToPlayers, true );
+
+ m_bWasMapPlaced = false;
+ m_bHasSapper = false;
+ m_takedamage = DAMAGE_YES;
+ m_flHealth = m_iMaxHealth = m_iHealth;
+ m_iAmountPlayerPaidForMe = CalculateObjectCost( GetType(), 0, GetTeamNumber() );
+
+ SetContextThink( BaseObjectThink, gpGlobals->curtime + 0.1, OBJ_BASE_THINK_CONTEXT );
+ m_szAmmoName = NULL;
+
+ AddFlag( FL_OBJECT ); // So NPCs will notice it
+ SetViewOffset( WorldSpaceCenter() - GetAbsOrigin() );
+
+ // Don't take damage if we're invulnerable, and don't require power either
+ if ( m_bInvulnerable )
+ {
+ m_takedamage = DAMAGE_NO;
+ m_fObjectFlags |= OF_DOESNT_NEED_POWER;
+ AddFlag( FL_NOTARGET );
+ }
+
+ // Cache off the normal model name
+ m_iszEnabledModel = GetModelName();
+}
+
+
+//-----------------------------------------------------------------------------
+// Returns information about the various control panels
+//-----------------------------------------------------------------------------
+void CBaseObject::GetControlPanelInfo( int nPanelIndex, const char *&pPanelName )
+{
+ pPanelName = NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Returns information about the various control panels
+//-----------------------------------------------------------------------------
+void CBaseObject::GetControlPanelClassName( int nPanelIndex, const char *&pPanelName )
+{
+ pPanelName = "vgui_screen";
+}
+
+//-----------------------------------------------------------------------------
+// This is called by the base object when it's time to spawn the control panels
+//-----------------------------------------------------------------------------
+void CBaseObject::SpawnControlPanels()
+{
+ char buf[64];
+
+ // FIXME: Deal with dynamically resizing control panels?
+
+ // If we're attached to an entity, spawn control panels on it instead of use
+ CBaseAnimating *pEntityToSpawnOn = this;
+ char *pOrgLL = "controlpanel%d_ll";
+ char *pOrgUR = "controlpanel%d_ur";
+ char *pAttachmentNameLL = pOrgLL;
+ char *pAttachmentNameUR = pOrgUR;
+ if ( IsBuiltOnAttachment() )
+ {
+ pEntityToSpawnOn = dynamic_cast<CBaseAnimating*>((CBaseEntity*)m_hBuiltOnEntity.Get());
+ if ( pEntityToSpawnOn )
+ {
+ char sBuildPointLL[64];
+ char sBuildPointUR[64];
+ Q_snprintf( sBuildPointLL, sizeof( sBuildPointLL ), "bp%d_controlpanel%%d_ll", m_iBuiltOnPoint );
+ Q_snprintf( sBuildPointUR, sizeof( sBuildPointUR ), "bp%d_controlpanel%%d_ur", m_iBuiltOnPoint );
+ pAttachmentNameLL = sBuildPointLL;
+ pAttachmentNameUR = sBuildPointUR;
+ }
+ else
+ {
+ pEntityToSpawnOn = this;
+ }
+ }
+
+ Assert( pEntityToSpawnOn );
+
+ // Lookup the attachment point...
+ int nPanel;
+ for ( nPanel = 0; true; ++nPanel )
+ {
+ Q_snprintf( buf, sizeof( buf ), pAttachmentNameLL, nPanel );
+ int nLLAttachmentIndex = pEntityToSpawnOn->LookupAttachment(buf);
+ if (nLLAttachmentIndex <= 0)
+ {
+ // Try and use my panels then
+ pEntityToSpawnOn = this;
+ Q_snprintf( buf, sizeof( buf ), pOrgLL, nPanel );
+ nLLAttachmentIndex = pEntityToSpawnOn->LookupAttachment(buf);
+ if (nLLAttachmentIndex <= 0)
+ return;
+ }
+
+ Q_snprintf( buf, sizeof( buf ), pAttachmentNameUR, nPanel );
+ int nURAttachmentIndex = pEntityToSpawnOn->LookupAttachment(buf);
+ if (nURAttachmentIndex <= 0)
+ {
+ // Try and use my panels then
+ Q_snprintf( buf, sizeof( buf ), pOrgUR, nPanel );
+ nURAttachmentIndex = pEntityToSpawnOn->LookupAttachment(buf);
+ if (nURAttachmentIndex <= 0)
+ return;
+ }
+
+ const char *pScreenName;
+ GetControlPanelInfo( nPanel, pScreenName );
+ if (!pScreenName)
+ continue;
+
+ const char *pScreenClassname;
+ GetControlPanelClassName( nPanel, pScreenClassname );
+ if ( !pScreenClassname )
+ continue;
+
+ // Compute the screen size from the attachment points...
+ matrix3x4_t panelToWorld;
+ pEntityToSpawnOn->GetAttachment( nLLAttachmentIndex, panelToWorld );
+
+ matrix3x4_t worldToPanel;
+ MatrixInvert( panelToWorld, worldToPanel );
+
+ // Now get the lower right position + transform into panel space
+ Vector lr, lrlocal;
+ pEntityToSpawnOn->GetAttachment( nURAttachmentIndex, panelToWorld );
+ MatrixGetColumn( panelToWorld, 3, lr );
+ VectorTransform( lr, worldToPanel, lrlocal );
+
+ float flWidth = lrlocal.x;
+ float flHeight = lrlocal.y;
+
+ CVGuiScreen *pScreen = CreateVGuiScreen( pScreenClassname, pScreenName, pEntityToSpawnOn, this, nLLAttachmentIndex );
+ pScreen->ChangeTeam( GetTeamNumber() );
+ pScreen->SetActualSize( flWidth, flHeight );
+ pScreen->SetActive( false );
+ pScreen->MakeVisibleOnlyToTeammates( true );
+ pScreen->SetOverlayMaterial( SCREEN_OVERLAY_MATERIAL );
+ int nScreen = m_hScreens.AddToTail( );
+ m_hScreens[nScreen].Set( pScreen );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Various commands sent by control panels
+//-----------------------------------------------------------------------------
+void CBaseObject::DismantleCommand( CBaseTFPlayer *pSender )
+{
+ if (CanBeRemovedBy( pSender ))
+ {
+ PickupObject();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseObject::YawCommand( CBaseTFPlayer *pSender, float flYaw )
+{
+ if ( CanBeRotatedBy(pSender) )
+ {
+ QAngle angles = GetAbsAngles();
+
+ angles.y = anglemod( flYaw );
+ SetLocalAngles( ConvertAbsAnglesToLocal( angles ) );
+ Teleport( NULL, &GetLocalAngles(), NULL );
+
+ // Notify the object that it moved
+ ObjectMoved();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseObject::TakeControlCommand( CBaseTFPlayer *pSender )
+{
+ // Deteriorating objects can be bought
+ if ( InSameTeam( pSender ) && IsDeteriorating() )
+ {
+ if ( ClassCanBuild( pSender->PlayerClass(), GetType() ) )
+ {
+ // Make sure he has the resources
+ int iCost = CalculateObjectCost( GetType(), pSender->GetNumObjects( GetType() ), GetTeamNumber() );
+ if ( pSender->GetBankResources() >= iCost )
+ {
+ pSender->RemoveBankResources( iCost );
+ SetBuilder( pSender );
+ pSender->AddObject( this );
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Handle commands sent from vgui panels on the client
+//-----------------------------------------------------------------------------
+bool CBaseObject::ClientCommand( CBaseTFPlayer *pSender, const char *pCmd, ICommandArguments *pArg )
+{
+ if ( FStrEq( pCmd, "dismantle" ) )
+ {
+ DismantleCommand( pSender );
+ return true;
+ }
+
+ if ( FStrEq( pCmd, "yaw" ) )
+ {
+ if ( pArg->Argc() == 2 )
+ {
+ float flYaw = atof( pArg->Argv(1) );
+ YawCommand( pSender, flYaw );
+ }
+ return true;
+ }
+
+ if ( FStrEq( pCmd, "takecontrol" ) )
+ {
+ TakeControlCommand( pSender );
+ return true;
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseObject::BaseObjectThink( void )
+{
+ SetNextThink( gpGlobals->curtime + 0.1, OBJ_BASE_THINK_CONTEXT );
+
+ // Make sure animation is up to date
+ DetermineAnimation();
+
+ // Can't animate without a model
+ if ( !(m_fObjectFlags & OF_DOESNT_HAVE_A_MODEL) )
+ {
+ StudioFrameAdvance();
+ }
+
+ /*
+ ROBIN: Hierarchy should do this for us
+
+ // If we were built on an attachment that's moved, update our position
+ if ( !IsPlacing() && IsBuiltOnAttachment() )
+ {
+ IHasBuildPoints *pBPInterface = dynamic_cast<IHasBuildPoints*>((CBaseEntity*)m_hBuiltOnEntity);
+ Assert( pBPInterface );
+
+ if ( pBPInterface->ShouldCheckForMovement() )
+ {
+ Vector vecOrigin;
+ QAngle vecAngles;
+ pBPInterface->GetBuildPoint( m_iBuiltOnPoint, vecOrigin, vecAngles );
+
+ EntityMatrix vehicleToWorld, childMatrix;
+ vehicleToWorld.InitFromEntity( GetMoveParent() ); // parent->world
+ vecOrigin = vehicleToWorld.WorldToLocal( vecOrigin );
+
+ if ( vecOrigin != GetLocalOrigin() )
+ {
+ Teleport( &vecOrigin, NULL, NULL );
+ }
+ }
+ }
+ */
+
+ // Do nothing while we're being placed
+ if ( IsPlacing() )
+ {
+ for ( int i=0; i < m_aRopes.Count(); i++ )
+ {
+ if ( m_aRopes[i].Get() )
+ m_aRopes[i]->SetupHangDistance( ROPE_HANG_DIST );
+ }
+
+ return;
+ }
+
+ // If we're deteriorating, keep going
+ if ( IsDeteriorating() )
+ {
+ DeterioratingThink();
+ }
+
+ // If we're building, keep going
+ if ( IsBuilding() )
+ {
+ BuildingThink();
+ return;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CBaseTFPlayer *CBaseObject::GetOwner()
+{
+ return m_hBuilder;
+}
+
+
+//-----------------------------------------------------------------------------
+// Do we have to be built in a resource zone?
+//-----------------------------------------------------------------------------
+bool CBaseObject::MustBeBuiltInResourceZone( void ) const
+{
+ return (m_fObjectFlags & OF_MUST_BE_BUILT_IN_RESOURCE_ZONE) != 0;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CBaseObject::MustBeBuiltInConstructionYard( ) const
+{
+ return (m_fObjectFlags & OF_MUST_BE_BUILT_IN_CONSTRUCTION_YARD) != 0;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CBaseObject::MustNotBeBuiltInConstructionYard( void ) const
+{
+ return !MustBeBuiltInConstructionYard();
+}
+
+//-----------------------------------------------------------------------------
+// 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: Map placed objects need to setup here.
+//-----------------------------------------------------------------------------
+void CBaseObject::InitializeMapPlacedObject( void )
+{
+ m_bWasMapPlaced = true;
+ m_fObjectFlags |= OF_CANNOT_BE_DISMANTLED;
+
+ // If a map-placed object spawns child objects with their own control
+ // panels, all of this lovely code will already have been run
+ if (m_hBuiltOnEntity.Get())
+ return;
+
+ SetBuilder( NULL );
+
+ // NOTE: We must spawn the control panels now, instead of during
+ // Spawn, because until placement is started, we don't actually know
+ // the position of the control panel because we don't know what it's
+ // been attached to (could be a vehicle which supplies a different
+ // place for the control panel)
+
+ if ( !(m_fObjectFlags & OF_DOESNT_HAVE_A_MODEL) )
+ {
+ SpawnControlPanels();
+ }
+
+ SetHealth( GetMaxHealth() );
+
+ AlignToGround( GetAbsOrigin() );
+ FinishedBuilding();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseObject::Activate( void )
+{
+ BaseClass::Activate();
+
+ // Add myself to the team
+ InitializeMapPlacedObject();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseObject::SetBuilder( CBaseTFPlayer *pBuilder, bool moveobjects )
+{
+ TRACE_OBJECT( UTIL_VarArgs( "%0.2f CBaseObject::SetBuilder builder %s, moveobjects == %s\n", gpGlobals->curtime,
+ pBuilder ? pBuilder->GetPlayerName() : "NULL",
+ moveobjects ? "true" : "false" ) );
+
+ ChangeBuilder( pBuilder, moveobjects );
+}
+
+
+//-----------------------------------------------------------------------------
+// Called when the builder rotates this object...
+//-----------------------------------------------------------------------------
+void CBaseObject::ObjectMoved( )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CBaseObject::ObjectType( ) const
+{
+ return m_iObjectType;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Remove this object from it's team and mark for deletion
+//-----------------------------------------------------------------------------
+void CBaseObject::DestroyObject( void )
+{
+ TRACE_OBJECT( UTIL_VarArgs( "%0.2f CBaseObject::DestroyObject %p:%s\n", gpGlobals->curtime, this, GetClassname() ) );
+
+ COrderEvent_ObjectDestroyed order( this );
+ GlobalOrderEvent( &order );
+
+ if ( GetBuilder() )
+ {
+ GetBuilder()->OwnedObjectDestroyed( this );
+ }
+
+ // Tell my powerpack that I'm gone
+ if ( m_hPowerPack != NULL )
+ {
+ m_hPowerPack->UnPowerObject( this );
+ }
+
+ // Tell my power up source that I have been destroyed.
+ if ( GetBuffStation() )
+ {
+ GetBuffStation()->DeBuffObject( this );
+ }
+
+ // Detach all my ropes
+ int i;
+ for ( i = 0; i < m_aRopes.Size(); i++ )
+ {
+ if ( m_aRopes[i] )
+ {
+ m_aRopes[i]->DieAtNextRest();
+ }
+ }
+
+ UTIL_Remove( this );
+
+ // Kill the control panels
+ for ( i = m_hScreens.Count(); --i >= 0; )
+ {
+ DestroyVGuiScreen( m_hScreens[i].Get() );
+ }
+ m_hScreens.RemoveAll();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: My builder's switched class/team, so start deteriorating.
+//-----------------------------------------------------------------------------
+void CBaseObject::StartDeteriorating( void )
+{
+ if ( tf_fastbuild.GetInt() )
+ return;
+
+ m_bDeteriorating = true;
+ m_flStartedDeterioratingAt = gpGlobals->curtime;
+ SetBuilder( NULL, true );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseObject::StopDeteriorating( void )
+{
+ m_bDeteriorating = false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Continue deterioration of this object
+//-----------------------------------------------------------------------------
+void CBaseObject::DeterioratingThink( void )
+{
+ // Calculate damage. The longer we've lasted, the faster we should go.
+ float flDamage;
+ float flDeteriorationTime = (gpGlobals->curtime - m_flStartedDeterioratingAt);
+ // If we've lasted less than the base time, we want to take the base time to die
+ flDamage = 0.1 * ( GetMaxHealth() / object_deterioration_time.GetFloat() ) * ceil(flDeteriorationTime / object_deterioration_time.GetFloat());
+ // Hax0r the damage to get around the object damage reduction
+ if ( obj_damage_factor.GetFloat() )
+ {
+ flDamage *= 1 / obj_damage_factor.GetFloat();
+ }
+
+ // Apply the damage
+ OnTakeDamage( CTakeDamageInfo( GetContainingEntity(INDEXENT(0)), GetContainingEntity(INDEXENT(0)), flDamage, DMG_GENERIC ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Get the total time it will take to build this object
+//-----------------------------------------------------------------------------
+float CBaseObject::GetTotalTime( void )
+{
+ if (tf_fastbuild.GetInt())
+ return 2.f;
+
+ // If it's in a construction yard, don't take more than 5 seconds to build
+ if ( PointInConstructionYard( GetAbsOrigin() ) )
+ {
+ if ( GetObjectInfo( ObjectType() )->m_flBuildTime > 5.0 )
+ return 5.0;
+ }
+
+ return GetObjectInfo( ObjectType() )->m_flBuildTime;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Start placing the object
+//-----------------------------------------------------------------------------
+void CBaseObject::StartPlacement( CBaseTFPlayer *pPlayer )
+{
+ AddSolidFlags( FSOLID_NOT_SOLID );
+
+ m_bPlacing = true;
+ m_bBuilding = false;
+ if ( pPlayer )
+ {
+ SetBuilder( pPlayer );
+ ChangeTeam( pPlayer->GetTeamNumber() );
+ }
+
+ // Make it semi-transparent
+ m_nRenderMode = kRenderTransAlpha;
+ SetRenderColorA( 128 );
+
+ // Set my build size
+ CollisionProp()->WorldSpaceAABB( &m_vecBuildMins, &m_vecBuildMaxs );
+ m_vecBuildMins -= Vector( 4,4,0 );
+ m_vecBuildMaxs += Vector( 4,4,0 );
+ m_vecBuildMins -= GetAbsOrigin();
+ m_vecBuildMaxs -= GetAbsOrigin();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Stop placing the object
+//-----------------------------------------------------------------------------
+void CBaseObject::StopPlacement( void )
+{
+ UTIL_Remove( this );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Find the nearest buildpoint on the specified entity
+//-----------------------------------------------------------------------------
+bool CBaseObject::FindNearestBuildPoint( CBaseEntity *pEntity, Vector vecBuildOrigin, float &flNearestPoint, Vector &vecNearestBuildPoint )
+{
+ bool bFoundPoint = false;
+
+ IHasBuildPoints *pBPInterface = dynamic_cast<IHasBuildPoints*>(pEntity);
+ Assert( pBPInterface );
+
+ // Any empty buildpoints?
+ for ( int i = 0; i < pBPInterface->GetNumBuildPoints(); i++ )
+ {
+ // Can this object build on this point?
+ if ( pBPInterface->CanBuildObjectOnBuildPoint( i, GetType() ) )
+ {
+ // Close to this point?
+ Vector vecBPOrigin;
+ QAngle vecBPAngles;
+ if ( pBPInterface->GetBuildPoint(i, vecBPOrigin, vecBPAngles) )
+ {
+ float flDist = (vecBPOrigin - vecBuildOrigin).Length();
+ if ( flDist < MIN(flNearestPoint, pBPInterface->GetMaxSnapDistance( i )) )
+ {
+ flNearestPoint = flDist;
+ vecNearestBuildPoint = vecBPOrigin;
+ m_hBuiltOnEntity = pEntity;
+ m_iBuiltOnPoint = i;
+
+ // Set our angles to the buildpoint's angles
+ SetAbsAngles( vecBPAngles );
+
+ bFoundPoint = true;
+ }
+ }
+ }
+ }
+
+ return bFoundPoint;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Calculate the placement model's position
+//-----------------------------------------------------------------------------
+bool CBaseObject::CalculatePlacement( CBaseTFPlayer *pPlayer )
+{
+ // Calculate build position
+ Vector forward;
+ QAngle vecAngles = vec3_angle;
+ vecAngles.y = pPlayer->EyeAngles().y;
+ SetLocalAngles( vecAngles );
+ AngleVectors(vecAngles, &forward );
+
+ // Adjust build distance based upon object size
+ Vector2D xyDims;
+ xyDims.x = MAX( fabs( m_vecBuildMins.x ), fabs( m_vecBuildMaxs.x ) );
+ xyDims.y = MAX( fabs( m_vecBuildMins.y ), fabs( m_vecBuildMaxs.y ) );
+ float flDistance = xyDims.Length() + 16; // small safety buffer
+ Vector vecBuildOrigin = pPlayer->WorldSpaceCenter() + forward * flDistance;
+
+ bool bSnappedToPoint = false;
+ bool bShouldAttachToParent = false;
+
+ // See if there are any nearby build positions to snap to
+ Vector vecNearestBuildPoint = vec3_origin;
+ float flNearestPoint = 9999;
+ // First, look for nearby buildpoints on other objects
+ for ( int i = 0; i < GetTFTeam()->GetNumObjects(); i++ )
+ {
+ CBaseObject *pObject = GetTFTeam()->GetObject(i);
+ if ( pObject && !pObject->IsPlacing() )
+ {
+ if ( FindNearestBuildPoint( pObject, vecBuildOrigin, flNearestPoint, vecNearestBuildPoint ) )
+ {
+ bSnappedToPoint = true;
+
+ // If I'm a vehicle, I'm being built on an MCV. Don't attach to the parent.
+ if ( ShouldAttachToParent() )
+ {
+ bShouldAttachToParent = true;
+ }
+ }
+ }
+ }
+
+ // If we're a vehicle, look for vehicle build points
+ if ( IsAVehicle() )
+ {
+ CBaseEntity *pEntity = NULL;
+ while ((pEntity = gEntList.FindEntityByClassname( pEntity, "info_vehicle_bay" )) != NULL)
+ {
+ if ( FindNearestBuildPoint( pEntity, vecBuildOrigin, flNearestPoint, vecNearestBuildPoint ) )
+ {
+ bSnappedToPoint = true;
+ }
+ }
+ }
+
+ // Check for resource zones for resource pumps
+ if ( GetType() == OBJ_RESOURCEPUMP )
+ {
+ CBaseEntity *pEntity = NULL;
+ while ((pEntity = gEntList.FindEntityByClassname( pEntity, "trigger_resourcezone" )) != NULL)
+ {
+ if ( FindNearestBuildPoint( pEntity, vecBuildOrigin, flNearestPoint, vecNearestBuildPoint ) )
+ {
+ bSnappedToPoint = true;
+ }
+ }
+ }
+
+ // See if there's any mapdefined build points near me
+ int iCount = g_MapDefinedBuildPoints.Count();
+ for ( i = 0; i < iCount; i++ )
+ {
+ if ( !InSameTeam(g_MapDefinedBuildPoints[i]) )
+ continue;
+
+ if ( FindNearestBuildPoint( g_MapDefinedBuildPoints[i], vecBuildOrigin, flNearestPoint, vecNearestBuildPoint ) )
+ {
+ bSnappedToPoint = true;
+ }
+ }
+
+ // Upgrades become invisible if the player's not attaching them to a snap pint
+ if ( IsAnUpgrade() )
+ {
+ if ( MustBeBuiltOnAttachmentPoint() && !bSnappedToPoint )
+ {
+ AddEffects( EF_NODRAW );
+ return false;
+ }
+ else
+ {
+ RemoveEffects( EF_NODRAW );
+ }
+ }
+
+ // Did we find a snap point?
+ if ( bSnappedToPoint )
+ {
+ if ( bShouldAttachToParent )
+ {
+ AttachObjectToObject( m_hBuiltOnEntity.Get(), m_iBuiltOnPoint, vecNearestBuildPoint );
+ }
+
+ return CheckBuildOrigin( pPlayer, vecNearestBuildPoint, true );
+ }
+
+ // Clear out previous parent
+ if ( m_hBuiltOnEntity.Get() )
+ {
+ m_hBuiltOnEntity = NULL;
+ m_iBuiltOnPoint = 0;
+ SetParent( NULL );
+
+ SetupUnattachedVersion();
+ }
+
+ // Check the build position
+ return CheckBuildOrigin( pPlayer, vecBuildOrigin, false );
+}
+
+
+bool CBaseObject::VerifyCorner( const Vector &vBottomCenter, float xOffset, float yOffset )
+{
+ Vector vStart( vBottomCenter.x + xOffset, vBottomCenter.y + yOffset, vBottomCenter.z );
+
+ trace_t tr;
+ UTIL_TraceLine(
+ vStart,
+ vStart - Vector( 0, 0, tf_obj_ground_clearance.GetFloat() ),
+ MASK_SOLID, this, COLLISION_GROUP_PLAYER_MOVEMENT, &tr );
+
+ return !tr.startsolid && tr.fraction < 1;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Check under a build point to ensure it's buildable on
+//-----------------------------------------------------------------------------
+bool CBaseObject::CheckBuildPoint( Vector vecPoint, Vector &vecTrace, Vector *vecOutPoint )
+{
+ trace_t tr;
+
+ bool bClear = true;
+ Vector vecEnd;
+
+ // Ensure that this point isn't in a no-build zone:
+ if( !tf_fastbuild.GetInt() && NoBuildPreventsBuild(this, vecPoint ) )
+ bClear = false;
+
+ // If the point isn't in solid, trace down until we find the ground
+ if ( enginetrace->GetPointContents( vecPoint ) == CONTENTS_EMPTY )
+ {
+ vecEnd = vecPoint - vecTrace;
+ UTIL_TraceLine( vecPoint, vecEnd, MASK_SOLID, this, COLLISION_GROUP_PLAYER_MOVEMENT, &tr);
+
+ // Can't find ground to build on?
+ if ( tr.fraction == 1.0 )
+ {
+ bClear = false;
+ }
+ }
+ else
+ {
+ // If the point's solid, trace up until we find empty air
+ vecEnd = vecPoint + vecTrace;
+ UTIL_TraceLine( vecPoint, vecEnd, MASK_SOLID, this, COLLISION_GROUP_PLAYER_MOVEMENT, &tr);
+
+ // Can't find ground to build on?
+ if ( tr.allsolid )
+ {
+ bClear = false;
+ }
+ }
+
+ // FIXME: HACK! This is a test to try to make mud non-buildable!!
+ const surfacedata_t *pSurfaceProp = physprops->GetSurfaceData( tr.surface.surfaceProps );
+ if (pSurfaceProp->game.maxSpeedFactor < 1.0f)
+ bClear = false;
+
+ if ( vecOutPoint )
+ {
+ *vecOutPoint = tr.endpos;
+ }
+
+ return bClear;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+
+bool CBaseObject::CheckBuildOrigin( CBaseTFPlayer *pPlayer, const Vector &vecInitialBuildOrigin, bool bSnappedToPoint )
+{
+ // By default, use the vecBuildOrigin..
+ bool bResult = true;
+ m_vecBuildOrigin = vecInitialBuildOrigin;
+ Vector vErrorOrigin = vecInitialBuildOrigin - (m_vecBuildMaxs - m_vecBuildMins) * 0.5f - m_vecBuildMins;
+
+ // If we're snapping to a build point, don't bother performing area checks
+ if ( !bSnappedToPoint )
+ {
+ 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 = 6;
+ float topZ = flBoxTopZ;
+ float topZInc = (flBoxBottomZ - flBoxTopZ) / (nIterations-1);
+ for ( int 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_SOLID, 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 it started in solid, keep moving down.
+ // Note that a working CGameTrace::fractionleftsolid would make this trivial, but it isn't
+ // working now so we must resort to rubitry.
+ if ( !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;
+ }
+
+ // 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;
+ }
+
+ Vector vecForward, vecRight, vecUp;
+ AngleVectors( GetLocalAngles(), &vecForward, &vecRight, &vecUp );
+ AttemptToFindPower();
+ AttemptToFindBuffStation();
+
+ // Make sure construction yards don't screw us up (tf_fastbuild allows builds anywhere)
+ if ( !tf_fastbuild.GetInt() && ConstructionYardPreventsBuild( this, m_vecBuildOrigin ))
+ return false;
+
+ // If we have to be attached to something, and we're not, abort
+ if ( !tf_fastbuild.GetInt() && MustBeBuiltOnAttachmentPoint() && !bSnappedToPoint )
+ return false;
+
+ // Make sure there aren't any solid objects in the area
+ if ( !bSnappedToPoint || IsAVehicle() )
+ {
+ if ( !(m_fObjectFlags & OF_DONT_PREVENT_BUILD_NEAR_OBJ) )
+ {
+ // Get a list of nearby entities
+ CBaseEntity *pListOfNearbyEntities[100];
+ int iNumberOfNearbyEntities = UTIL_EntitiesInSphere( pListOfNearbyEntities, 100, m_vecBuildOrigin, GetNearbyObjectCheckRadius(), 0 );
+ for ( int i = 0; i < iNumberOfNearbyEntities; i++ )
+ {
+ CBaseEntity *pEntity = pListOfNearbyEntities[i];
+ if ( pEntity->IsSolid( ) )
+ {
+ // Ignore shields..
+ if ( pEntity->GetCollisionGroup() == TFCOLLISION_GROUP_SHIELD )
+ continue;
+
+ // Ignore func brushes
+ // BUGBUG: Shouldn't this test against MOVETYPE_PUSH instead of SOLID_BSP?
+ if ( pEntity->GetSolid() == SOLID_BSP )
+ continue;
+
+ // Ignore the player who's building
+ if ( pEntity == GetBuilder() )
+ continue;
+
+ // YWB: Ignore other players
+ if ( pEntity->IsPlayer() )
+ continue;
+
+ // Ignore map placed objects
+ if ( pEntity->GetTeamNumber() == 0 )
+ continue;
+
+ //NDebugOverlay::EntityBounds( pEntity, 0,255,0,8, 0.1 );
+ return false;
+ }
+
+ // Sentryguns may be turtled, and non-solid
+ if ( pEntity->Classify() == CLASS_MILITARY )
+ {
+ CObjectSentrygun *pSentry = dynamic_cast<CObjectSentrygun*>(pEntity);
+ if ( pSentry && pSentry->IsTurtled() )
+ return false;
+ }
+ }
+ }
+ }
+
+ if ( !bSnappedToPoint )
+ {
+ AlignToGround( m_vecBuildOrigin );
+ }
+
+ return bResult;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Align myself to the ground below the specified point
+//-----------------------------------------------------------------------------
+void CBaseObject::AlignToGround( Vector vecOrigin )
+{
+ if ( !(m_fObjectFlags & OF_ALIGN_TO_GROUND) )
+ return;
+
+ trace_t tr;
+ Vector vecWorldMins, vecWorldMaxs;
+ CollisionProp()->WorldSpaceAABB( &vecWorldMins, &vecWorldMaxs );
+ float flHeight = MAX( vecWorldMaxs.z - vecWorldMins.z, 60 );
+ UTIL_TraceLine( vecOrigin, vecOrigin + Vector(0,0,-flHeight), MASK_SOLID, this, COLLISION_GROUP_PLAYER_MOVEMENT, &tr );
+ if ( tr.fraction != 1.0 )
+ {
+ // Orient the *up* axis to be along the plane normal
+ Vector perp( 1, 0, 0 );
+ Vector forward, right;
+ CrossProduct( perp, tr.plane.normal, forward );
+ if (forward.LengthSqr() < 0.1f)
+ {
+ perp.Init( 0, 1, 0 );
+ CrossProduct( perp, tr.plane.normal, forward );
+ }
+ VectorNormalize( forward );
+ CrossProduct( tr.plane.normal, forward, right );
+
+ VMatrix orientation( forward, right, tr.plane.normal );
+
+ QAngle angles;
+ MatrixToAngles( orientation, angles );
+ SetAbsAngles( angles );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Exit points for mounted vehicles....
+//-----------------------------------------------------------------------------
+void CBaseObject::GetExitPoint( CBaseEntity *pPlayer, int nBuildPoint, Vector *pAbsPosition, QAngle *pAbsAngles )
+{
+ // Deal with hierarchy...
+ IHasBuildPoints *pMount = dynamic_cast<IHasBuildPoints*>(GetMoveParent());
+ if (pMount)
+ {
+ int nBuildPoint = pMount->FindObjectOnBuildPoint( this );
+ if (nBuildPoint >= 0)
+ {
+ pMount->GetExitPoint( pPlayer, nBuildPoint, pAbsPosition, pAbsAngles );
+ return;
+ }
+ }
+
+ // FIXME: In future, we may well want to use specific exit attachments here...
+ GetBuildPoint( nBuildPoint, *pAbsPosition, *pAbsAngles );
+
+ // Move back along the forward direction a bit...
+ Vector vecForward, vecUp;
+ AngleVectors( *pAbsAngles, &vecForward, NULL, &vecUp );
+ *pAbsPosition -= vecForward * 60;
+ *pAbsPosition += vecUp * 30;
+
+ // Now select a good spot to drop onto
+ Vector vNewPos;
+ if ( !EntityPlacementTest(pPlayer, *pAbsPosition, vNewPos, true) )
+ {
+ Warning("Can't find valid place to exit object.\n");
+ return;
+ }
+
+ *pAbsPosition = vNewPos;
+}
+
+
+void CBaseObject::AdjustInitialBuildAngles()
+{
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Try and find power for this object during placement
+//-----------------------------------------------------------------------------
+void CBaseObject::AttemptToFindPower( void )
+{
+ // Human objects need power, so show the player if the current position will have power, but don't prevent building.
+ if ( !CanPowerupEver( POWERUP_POWER ) )
+ return;
+
+ // If I have a powerpack, see if I'm unable to keep power, or not needed.
+ // This is done before checking to see if the object needs power, because it may
+ // have once needed power, but doesn't anymore (i.e. snapped to an attachment point)
+ if ( m_hPowerPack )
+ {
+ m_hPowerPack->EnsureObjectPower( this );
+ }
+
+ // If I don't have a powerpack, or I just moved too far from it, look for a powerpack
+ if ( !m_hPowerPack )
+ {
+ GetTFTeam()->UpdatePowerpacks( NULL, this );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseObject::AttemptToFindBuffStation( void )
+{
+ // Check to see if this object can be connected to a buff station.
+ if ( !CanBeHookedToBuffStation() )
+ return;
+
+ // We have already found a buff station, we want to use - check distances.
+ if ( GetBuffStation() )
+ {
+ GetBuffStation()->CheckBuffConnection( this );
+ }
+ // Look for a buff station to use.
+ else
+ {
+ GetTFTeam()->UpdateBuffStations( NULL, this, true );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Move the placement model to the current position. Return false if it's an invalid position
+//-----------------------------------------------------------------------------
+bool CBaseObject::UpdatePlacement( CBaseTFPlayer *pPlayer )
+{
+ bool placementOk = CalculatePlacement( pPlayer );
+ if ( placementOk )
+ {
+ SetRenderColor( 255, 255, 255, GetRenderColor().a );
+ }
+ else
+ {
+ SetRenderColor( 255, 0, 0, GetRenderColor().a );
+ }
+
+ Teleport( &m_vecBuildOrigin, &GetLocalAngles(), NULL );
+
+ return placementOk;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CBaseObject::PreStartBuilding()
+{
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Start building the object
+//-----------------------------------------------------------------------------
+bool CBaseObject::StartBuilding( CBaseEntity *pBuilder )
+{
+ // Need to add the object to the team now...
+ CTFTeam *pTFTeam = ( CTFTeam * )GetGlobalTeam( GetTeamNumber() );
+
+ // Deduct the cost from the player
+ if ( pBuilder && pBuilder->IsPlayer() )
+ {
+ m_iAmountPlayerPaidForMe = ((CBaseTFPlayer*)pBuilder)->StartedBuildingObject( m_iObjectType );
+ if ( !m_iAmountPlayerPaidForMe )
+ {
+ // Player couldn't afford to pay for me, so abort
+ ClientPrint( (CBasePlayer*)pBuilder, HUD_PRINTCENTER, "Not enough resources.\n" );
+ StopPlacement();
+ return false;
+ }
+ }
+
+ // Add this object to the team's list (because we couldn't add it during
+ // placement mode)
+ if ( pTFTeam && !pTFTeam->IsObjectOnTeam( this ) )
+ {
+ pTFTeam->AddObject( this );
+ }
+
+ m_bPlacing = false;
+ m_bBuilding = true;
+ SetHealth( OBJECT_CONSTRUCTION_STARTINGHEALTH );
+ m_flPercentageConstructed = 0;
+
+ // Compute a good fitting AABB since we know where this thing belongs
+ if ( VPhysicsGetObject() && !IsBuiltOnAttachment() )
+ {
+ Vector absmins, absmaxs;
+ physcollision->CollideGetAABB( &absmins, &absmaxs, VPhysicsGetObject()->GetCollide(), GetAbsOrigin(), GetAbsAngles() );
+
+ // This is required to get the client + server looking the same
+ // since the client uses the mins to compute absmins + absmaxs
+ SetCollisionBounds( absmins - GetAbsOrigin(), absmaxs - GetAbsOrigin() );
+ }
+
+ m_nRenderMode = kRenderNormal;
+ RemoveSolidFlags( FSOLID_NOT_SOLID );
+
+ // NOTE: We must spawn the control panels now, instead of during
+ // Spawn, because until placement is started, we don't actually know
+ // the position of the control panel because we don't know what it's
+ // been attached to (could be a vehicle which supplies a different
+ // place for the control panel)
+ // NOTE: We must also spawn it before FinishedBuilding can be called
+ SpawnControlPanels();
+
+ // Tell the object we've been built on that we exist
+ if ( IsBuiltOnAttachment() && ShouldAttachToParent() )
+ {
+ IHasBuildPoints *pBPInterface = dynamic_cast<IHasBuildPoints*>((CBaseEntity*)m_hBuiltOnEntity.Get());
+ Assert( pBPInterface );
+ pBPInterface->SetObjectOnBuildPoint( m_iBuiltOnPoint, this );
+ }
+
+ // Start the build animations
+ m_flTotalConstructionTime = m_flConstructionTimeLeft = GetTotalTime();
+
+ if ( pBuilder && pBuilder->IsPlayer() )
+ {
+ ((CBaseTFPlayer*)pBuilder)->FinishedObject( this );
+ }
+
+ m_vecBuildOrigin = GetAbsOrigin();
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Continue construction of this object
+//-----------------------------------------------------------------------------
+void CBaseObject::BuildingThink( void )
+{
+ // Continue construction
+ Repair( (GetMaxHealth() - OBJECT_CONSTRUCTION_STARTINGHEALTH) / m_flTotalConstructionTime * OBJECT_CONSTRUCTION_INTERVAL );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseObject::AttemptToActivateBuffStation( void )
+{
+ if ( !GetBuffStation() )
+ return;
+
+ if ( GetBuffStation()->IsPlacing() || GetBuffStation()->IsBuilding() ||
+ !GetBuffStation()->IsPowered() )
+ return;
+
+ if ( m_bBuffActivated )
+ return;
+
+ BuffStationActivate();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseObject::SetControlPanelsActive( bool bState )
+{
+ // Activate control panel screens
+ for ( int i = m_hScreens.Count(); --i >= 0; )
+ {
+ if (m_hScreens[i].Get())
+ {
+ m_hScreens[i]->SetActive( bState );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseObject::FinishedBuilding( void )
+{
+ SetControlPanelsActive( true );
+
+ // Only make a shadow if the object doesn't use vphysics
+ if (!VPhysicsGetObject())
+ {
+ VPhysicsInitStatic();
+ }
+
+ m_bBuilding = false;
+
+ AttemptToGoActive();
+ AttemptToActivateBuffStation();
+
+ // We're done building, add in the stat...
+ TFStats()->IncrementStat( (TFStatId_t)(TF_STAT_FIRST_OBJECT_BUILT + ObjectType()), 1 );
+
+ // Spawn any objects on this one
+ SpawnObjectPoints();
+
+ // Let our vehicle bay know, if we have one
+ if ( m_hVehicleBay )
+ {
+ m_hVehicleBay->FinishedBuildVehicle( this );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Objects store health in hacky ways
+//-----------------------------------------------------------------------------
+void CBaseObject::SetHealth( float flHealth )
+{
+ if ( IsDisabled() )
+ {
+ if ( ( m_flMinDisableHealth != 0.0f && flHealth > m_flMinDisableHealth ) ||
+ ( flHealth > 1 ) )
+ {
+ // Reenable and fire output
+ SetDisabled( false );
+
+ m_OnBecomingReenabled.FireOutput( this, this );
+ }
+ }
+
+ bool changed = m_flHealth != flHealth;
+
+ m_flHealth = flHealth;
+ m_iHealth = ceil(m_flHealth);
+
+ // If we have a model, and a pose parameter, set the pose parameter to reflect our health
+ if ( !(m_fObjectFlags & OF_DOESNT_HAVE_A_MODEL) )
+ {
+ if (LookupPoseParameter( "object_health") >= 0 && GetMaxHealth() > 0 )
+ {
+ SetPoseParameter( "object_health", 100 * ( GetHealth() / (float)GetMaxHealth() ) );
+ }
+ }
+
+ if ( changed )
+ {
+ // Set value and fire output
+ m_OnObjectHealthChanged.Set( m_flHealth, this, this );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Powerup has just started
+//-----------------------------------------------------------------------------
+void CBaseObject::PowerupStart( int iPowerup, float flAmount, CBaseEntity *pAttacker, CDamageModifier *pDamageModifier )
+{
+ switch( iPowerup )
+ {
+ case POWERUP_BOOST:
+ {
+ // Can we boost health further?
+ if ( GetHealth() < GetMaxHealth() )
+ {
+ /*
+ if ( (gpGlobals->curtime - m_flLastRepairTime) > 0.01 )
+ {
+ Msg("TOTAL REPAIR: %.2f in %.2f\n\n", m_flRepairedSinceLastTime, (gpGlobals->curtime - m_flLastRepairTime) );
+ }
+
+ if ( pAttacker->IsPlayer() )
+ {
+ CBaseTFPlayer *pPlayer = (CBaseTFPlayer *)pAttacker;
+ Msg("(%.2f) %s repaired %s for %.2f health.\n", gpGlobals->curtime, pPlayer->GetPlayerName(), GetClassname(), flAmount );
+ }
+ */
+ // Is this repair happening at the same time as other repairing on me?
+ if ( (gpGlobals->curtime - m_flLastRepairTime) < 0.01 )
+ {
+ //Msg(" ->Reducing repair by %.2f\n", m_flNextRepairMultiplier );
+ flAmount *= m_flNextRepairMultiplier;
+ m_flNextRepairMultiplier *= 0.5;
+ }
+ else
+ {
+ m_flLastRepairTime = gpGlobals->curtime;
+ m_flNextRepairMultiplier = 0.5;
+ m_flRepairedSinceLastTime = 0;
+ }
+
+ //Msg(" REPAIRED: %.2f\n", flAmount );
+
+ Repair( flAmount );
+
+ m_flRepairedSinceLastTime += flAmount;
+ }
+
+ // Prevent callback to base class, since we handled it here
+ return;
+ }
+ break;
+
+ case POWERUP_POWER:
+ {
+ Assert( m_hPowerPack );
+ AttemptToGoActive();
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ BaseClass::PowerupStart( iPowerup, flAmount, pAttacker, pDamageModifier );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Powerup has just started
+//-----------------------------------------------------------------------------
+void CBaseObject::PowerupEnd( int iPowerup )
+{
+ switch( iPowerup )
+ {
+ case POWERUP_POWER:
+ {
+ OnGoInactive();
+ m_hPowerPack = NULL;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ BaseClass::PowerupEnd( iPowerup );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Override base traceattack to prevent visible effects from team members shooting me
+//-----------------------------------------------------------------------------
+void CBaseObject::TraceAttack( const CTakeDamageInfo &inputInfo, const Vector &vecDir, trace_t *ptr )
+{
+ // Prevent team damage here so blood doesn't appear
+ if ( inputInfo.GetAttacker() )
+ {
+ if ( InSameTeam(inputInfo.GetAttacker()) )
+ return;
+ }
+
+ float fVulnerableMultiplier = FindVulnerablePointMultiplier( ptr->hitgroup, ptr->hitbox );
+
+ CTakeDamageInfo info = inputInfo;
+ info.ScaleDamage( fVulnerableMultiplier );
+
+ SpawnBlood( ptr->endpos, vecDir, BloodColor(), info.GetDamage() );
+ AddMultiDamage( info, this );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Prevent Team Damage
+//-----------------------------------------------------------------------------
+ConVar object_show_damage( "obj_show_damage", "0", 0, "Show all damage taken by objects." );
+ConVar object_capture_damage( "obj_capture_damage", "0", 0, "Captures all damage taken by objects for dumping later." );
+
+CUtlDict<int,int> g_DamageMap;
+
+void Cmd_DamageDump_f(void)
+{
+ CUtlDict<bool,int> g_UniqueColumns;
+
+ // Build the unique columns:
+ for( int idx = g_DamageMap.First(); idx != g_DamageMap.InvalidIndex(); idx = g_DamageMap.Next(idx) )
+ {
+ char* szColumnName = strchr(g_DamageMap.GetElementName(idx),',') + 1;
+
+ int ColumnIdx = g_UniqueColumns.Find( szColumnName );
+
+ if( ColumnIdx == g_UniqueColumns.InvalidIndex() )
+ {
+ g_UniqueColumns.Insert( szColumnName, false );
+ }
+ }
+
+ // Dump the column names:
+ FileHandle_t f = filesystem->Open("damage.txt","wt+");
+
+ for( idx = g_UniqueColumns.First(); idx != g_UniqueColumns.InvalidIndex(); idx = g_UniqueColumns.Next(idx) )
+ {
+ filesystem->FPrintf(f,"\t%s",g_UniqueColumns.GetElementName(idx));
+ }
+
+ filesystem->FPrintf(f,"\n");
+
+
+ CUtlDict<bool,int> g_CompletedRows;
+
+ // Dump each row:
+ bool bDidRow;
+
+ do
+ {
+ bDidRow = false;
+
+ for( idx = g_DamageMap.First(); idx != g_DamageMap.InvalidIndex(); idx = g_DamageMap.Next(idx) )
+ {
+ char szRowName[256];
+
+ // Check the Row name of each entry to see if I've done this row yet.
+ Q_strncpy(szRowName, g_DamageMap.GetElementName(idx), sizeof( szRowName ) );
+ *strchr(szRowName,',') = '\0';
+
+ char szRowNameComma[256];
+ Q_snprintf( szRowNameComma, sizeof( szRowNameComma ), "%s,", szRowName );
+
+ if( g_CompletedRows.Find(szRowName) == g_CompletedRows.InvalidIndex() )
+ {
+ bDidRow = true;
+ g_CompletedRows.Insert(szRowName,false);
+
+
+ // Output the row name:
+ filesystem->FPrintf(f,szRowName);
+
+ for( int ColumnIdx = g_UniqueColumns.First(); ColumnIdx != g_UniqueColumns.InvalidIndex(); ColumnIdx = g_UniqueColumns.Next( ColumnIdx ) )
+ {
+ char szRowNameCommaColumn[256];
+ Q_strncpy( szRowNameCommaColumn, szRowNameComma, sizeof( szRowNameCommaColumn ) );
+ Q_strncat( szRowNameCommaColumn, g_UniqueColumns.GetElementName( ColumnIdx ), sizeof( szRowNameCommaColumn ), COPY_ALL_CHARACTERS );
+
+ int nDamageAmount = 0;
+ // Fine to reuse idx since we are going to break anyways.
+ for( idx = g_DamageMap.First(); idx != g_DamageMap.InvalidIndex(); idx = g_DamageMap.Next(idx) )
+ {
+ if( !stricmp( g_DamageMap.GetElementName(idx), szRowNameCommaColumn ) )
+ {
+ nDamageAmount = g_DamageMap[idx];
+ break;
+ }
+ }
+
+ filesystem->FPrintf(f,"\t%i",nDamageAmount);
+
+ }
+
+ filesystem->FPrintf(f,"\n");
+ break;
+ }
+ }
+ // Grab the row name:
+
+ } while(bDidRow);
+
+ // close the file:
+ filesystem->Close(f);
+}
+
+static ConCommand obj_dump_damage( "obj_dump_damage", Cmd_DamageDump_f );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void ReportDamage( const char* szInflictor, const char* szVictim, float fAmount, int nCurrent, int nMax )
+{
+ int iAmount = (int)fAmount;
+
+ if( object_show_damage.GetBool() && iAmount )
+ {
+ Msg( "ShowDamage: Object %s taking %0.1f damage from %s ( %i / %i )\n", szVictim, fAmount, szInflictor, nCurrent, nMax );
+ }
+
+ if( object_capture_damage.GetBool() )
+ {
+ char szMangledKey[256];
+
+ Q_snprintf(szMangledKey,sizeof(szMangledKey)/sizeof(szMangledKey[0]),"%s,%s",szInflictor,szVictim);
+ int idx = g_DamageMap.Find( szMangledKey );
+
+ if( idx == g_DamageMap.InvalidIndex() )
+ {
+ g_DamageMap.Insert( szMangledKey, iAmount );
+
+ } else
+ {
+ g_DamageMap[idx] += iAmount;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Pass the specified amount of damage through to any objects I have built on me
+//-----------------------------------------------------------------------------
+bool CBaseObject::PassDamageOntoChildren( const CTakeDamageInfo &info, float *flDamageLeftOver )
+{
+ IHasBuildPoints *pBPInterface = dynamic_cast<IHasBuildPoints*>(this);
+ Assert( pBPInterface );
+
+ float flDamage = info.GetDamage();
+
+ // Double the amount of damage done (and get around the child damage modifier)
+ flDamage *= 2;
+ if ( obj_child_damage_factor.GetFloat() )
+ {
+ flDamage *= (1 / obj_child_damage_factor.GetFloat());
+ }
+
+ // Remove blast damage because child objects (well specifically upgrades)
+ // want to ignore direct blast damage but still take damage from parent
+ CTakeDamageInfo childInfo = info;
+ childInfo.SetDamage( flDamage );
+ childInfo.SetDamageType( info.GetDamageType() & (~DMG_BLAST) );
+
+ CBaseEntity *pEntity = pBPInterface->GetFirstObjectOnMe();
+ while ( pEntity )
+ {
+ Assert( pEntity->m_takedamage != DAMAGE_NO );
+ // Do damage to the next object
+ float flDamageTaken = pEntity->OnTakeDamage( childInfo );
+ // If we didn't kill it, abort
+ CBaseObject *pObject = dynamic_cast<CBaseObject*>(pEntity);
+ if ( !pObject || !pObject->IsDying() )
+ {
+ char* szInflictor = "unknown";
+ if( info.GetInflictor() )
+ szInflictor = (char*)info.GetInflictor()->GetClassname();
+
+ ReportDamage( szInflictor, GetClassname(), flDamageTaken, GetHealth(), GetMaxHealth() );
+
+ *flDamageLeftOver = flDamage;
+ return true;
+ }
+ // Reduce the damage and move on to the next
+ flDamage -= flDamageTaken;
+ pEntity = pBPInterface->GetFirstObjectOnMe();
+ }
+
+ *flDamageLeftOver = flDamage;
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CBaseObject::OnTakeDamage( const CTakeDamageInfo &info )
+{
+ // Prevent damage if the game hasn't started yet
+ if ( CurrentActIsAWaitingAct() )
+ return 0;
+ if ( !IsAlive() )
+ return info.GetDamage();
+ if (m_bInvulnerable)
+ return 0;
+ if ( m_takedamage == DAMAGE_NO )
+ return 0;
+ if ( IsPlacing() )
+ return 0;
+
+ // Check teams
+ if ( info.GetAttacker() )
+ {
+ if ( InSameTeam(info.GetAttacker()) )
+ return 0;
+ }
+
+ IHasBuildPoints *pBPInterface = dynamic_cast<IHasBuildPoints*>(this);
+
+ float flDamage = info.GetDamage();
+
+ // Objects take half damage from bullets
+ if ( info.GetDamageType() & DMG_BULLET )
+ {
+ flDamage *= 0.5;
+ }
+
+ // Objects build on other objects take less damage
+ if ( !IsAnUpgrade() && GetParentObject() )
+ {
+ flDamage *= obj_child_damage_factor.GetFloat();
+ }
+
+ if (obj_damage_factor.GetFloat())
+ {
+ flDamage *= obj_damage_factor.GetFloat();
+ }
+
+ // Constructing objects take extra damage
+ if ( IsBuilding() )
+ {
+ flDamage *= 3;
+ }
+
+ // If has min health, and damage would put it below min health disable it if not already disabled
+ bool bShouldBeDisabled = false;
+ if ( m_flMinDisableHealth != 0 && ( m_flHealth - flDamage ) < m_flMinDisableHealth )
+ {
+ bShouldBeDisabled = true;
+ }
+ else if ( pBPInterface && pBPInterface->GetNumObjectsOnMe() && (( m_flHealth - flDamage ) < 1) )
+ {
+ bShouldBeDisabled = true;
+ }
+
+ // Make sure we're disabled if we're supposed to be
+ if ( bShouldBeDisabled )
+ {
+ // Remove any sappers on me
+ if ( m_bCantDie )
+ {
+ RemoveAllSappers( this );
+ }
+
+ // Make sure this only fires first time we cross the threshold and go disabled
+ if ( !IsDisabled() )
+ {
+ SetDisabled( true );
+ m_OnBecomingDisabled.FireOutput( info.GetAttacker(), this );
+
+ // Special case: If we have a min disabled health, and we're set to not die, immediately fall to 1 health
+ if ( m_bCantDie )
+ {
+ SetHealth( 1 );
+ }
+ }
+ }
+
+ // If I have objects on me, I can't be destroyed until they're gone. Ditto if I can't be killed.
+ bool bWillDieButCant = (m_bCantDie || pBPInterface->GetNumObjectsOnMe()) && (( m_flHealth - flDamage ) < 1);
+ if ( bWillDieButCant )
+ {
+ // Soak up the damage it would take to drop us to 1 health
+ flDamage = flDamage - m_flHealth;
+ SetHealth( 1 );
+
+ // Pass leftover damage
+ if ( flDamage )
+ {
+ if ( PassDamageOntoChildren( info, &flDamage ) )
+ return flDamage;
+ }
+ }
+
+ if ( flDamage )
+ {
+ // Recheck our death possibility, because our objects may have all been blown off us by now
+ bWillDieButCant = (m_bCantDie || pBPInterface->GetNumObjectsOnMe()) && (( m_flHealth - flDamage ) < 1);
+ if ( !bWillDieButCant )
+ {
+ // Reduce health
+ SetHealth( m_flHealth - flDamage );
+ }
+ }
+
+ m_OnDamaged.FireOutput(info.GetAttacker(), this);
+
+ // Hurt by an enemy?
+ if ( info.GetAttacker() && info.GetAttacker()->entindex() > 0 )
+ {
+ m_flLastRealDamage = gpGlobals->curtime;
+ }
+
+ if ( GetHealth() <= 0 )
+ {
+ if ( info.GetAttacker() )
+ {
+ TFStats()->IncrementTeamStat( info.GetAttacker()->GetTeamNumber(), TF_TEAM_STAT_DESTROYED_OBJECT_COUNT, 1 );
+ TFStats()->IncrementPlayerStat( info.GetAttacker(), TF_PLAYER_STAT_DESTROYED_OBJECT_COUNT, 1 );
+ }
+
+ m_lifeState = LIFE_DEAD;
+ m_OnDestroyed.FireOutput( info.GetAttacker(), this);
+ Killed();
+ }
+ else
+ {
+ // Notify team about interesting stuff going on with this object
+ if ( !(m_fObjectFlags & OF_SUPPRESS_NOTIFY_UNDER_ATTACK) && ( m_iszUnderAttackSound != NULL_STRING ) )
+ {
+ CTFTeam *pTeam = GetTFTeam();
+ if ( pTeam )
+ {
+ Vector vecPosition = GetAbsOrigin();
+
+ // Tell everyone on the team that this object's underattack
+ CRecipientFilter myteam;
+ myteam.MakeReliable();
+ myteam.AddRecipientsByTeam( pTeam );
+ UserMessageBegin( myteam, "MinimapPulse" );
+ WRITE_VEC3COORD( vecPosition );
+ MessageEnd();
+
+ GetTFTeam()->PostMessage( TEAMMSG_CUSTOM_SOUND, NULL, (char*)STRING(m_iszUnderAttackSound) );
+ }
+ }
+ }
+
+ {
+ char* szInflictor = "unknown";
+ if( info.GetInflictor() )
+ szInflictor = (char*)info.GetInflictor()->GetClassname();
+
+ ReportDamage( szInflictor, GetClassname(), flDamage, GetHealth(), GetMaxHealth() );
+ }
+
+ return flDamage;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Get the time it will take to repair this object
+//-----------------------------------------------------------------------------
+float CBaseObject::GetRepairTime( void )
+{
+ // Can't be repaired while being constructed
+ if ( IsBuilding() )
+ return 0;
+
+ int iRepairHealth = GetMaxHealth() - GetHealth();
+ if ( iRepairHealth )
+ {
+ return ((float)iRepairHealth / OBJECT_REPAIR_RATE);
+ }
+
+ return 0;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Repair myself for the time period passed in. Return true if I'm fully repaired.
+//-----------------------------------------------------------------------------
+bool CBaseObject::UpdateRepair( float flRepairTime )
+{
+ return Repair( (flRepairTime * OBJECT_REPAIR_RATE) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Repair / Help-Construct this object the specified amount
+//-----------------------------------------------------------------------------
+bool CBaseObject::Repair( float flHealth )
+{
+ // Multiply it by the repair rate
+ flHealth *= m_flRepairMultiplier;
+ if ( !flHealth )
+ return false;
+
+ if ( IsBuilding() )
+ {
+ if ( HasPowerup(POWERUP_EMP) )
+ return false;
+
+ // Reduce the construction time by the correct amount for the health passed in
+ float flConstructionTime = flHealth / ((GetMaxHealth() - OBJECT_CONSTRUCTION_STARTINGHEALTH) / m_flTotalConstructionTime);
+ m_flConstructionTimeLeft = MAX( 0, m_flConstructionTimeLeft - flConstructionTime);
+ m_flConstructionTimeLeft = clamp( m_flConstructionTimeLeft, 0.0f, m_flTotalConstructionTime );
+ m_flPercentageConstructed = 1 - (m_flConstructionTimeLeft / m_flTotalConstructionTime);
+ m_flPercentageConstructed = clamp( m_flPercentageConstructed, 0.0f, 1.0f );
+
+ // Increase health.
+ SetHealth( MIN( GetMaxHealth(), m_flHealth + flHealth ) );
+
+ // Return true if we're constructed now
+ if ( m_flConstructionTimeLeft <= 0.0f )
+ {
+ FinishedBuilding();
+ return true;
+ }
+ }
+ else
+ {
+ // Return true if we're already fully healed
+ if ( GetHealth() >= GetMaxHealth() )
+ return true;
+
+ // Increase health.
+ SetHealth( MIN( GetMaxHealth(), m_flHealth + flHealth ) );
+
+ m_OnRepaired.FireOutput( this, this);
+
+ // Return true if we're fully healed now
+ if ( GetHealth() == GetMaxHealth() )
+ return true;
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Object has been blown up. Drop resource chunks upto the value of my max health.
+//-----------------------------------------------------------------------------
+void CBaseObject::Killed( void )
+{
+ m_bDying = true;
+
+ // Do an explosion.
+ CPASFilter filter( GetAbsOrigin() );
+ te->Explosion(
+ filter,
+ 0.0,
+ &GetAbsOrigin(),
+ g_sModelIndexFireball,
+ 5.4, // radius
+ 15,
+ TE_EXPLFLAG_NODLIGHTS,
+ 256,
+ 200);
+
+ Vector vecOrigin = WorldSpaceCenter() + Vector(0,0,32);
+
+ bool bDropResources = true;
+
+ // Don't drop resources if I'm built out of brushes, or I'm an upgrade
+ if ( m_fObjectFlags & OF_DOESNT_HAVE_A_MODEL || IsAnUpgrade() )
+ {
+ bDropResources = false;
+ }
+
+ // Don't drop resources if I haven't taken damage from an enemy for a while (i.e. I've deteriorated instead)
+ if ( gpGlobals->curtime > (m_flLastRealDamage + MAX_DROP_TIME_AFTER_DAMAGE) )
+ {
+ bDropResources = false;
+ }
+
+ // Drop resources based upon our base cost
+ int iCost = CalculateObjectCost( GetType(), 0, GetTeamNumber() );
+ iCost *= 0.5;
+ if ( bDropResources && iCost )
+ {
+ // Convert value to chunks.
+ int nProcessedChunks = 0;
+ int nNormalChunks = 0;
+ ConvertResourceValueToChunks( iCost, &nProcessedChunks, &nNormalChunks );
+
+ // Make everything drop at least 1 chunk
+ if ( !nProcessedChunks && !nNormalChunks )
+ {
+ nNormalChunks++;
+ }
+
+ // Drop processed chunks.
+ int iChunk;
+ for ( iChunk = 0; iChunk < nProcessedChunks; iChunk++ )
+ {
+ // Generate a random velocity vector.
+ Vector vecVelocity = Vector( random->RandomFloat( -250,250 ), random->RandomFloat( -250,250 ), random->RandomFloat( 200,450 ) );
+
+ // Create a processed chunk.
+ CResourceChunk *pChunk = CResourceChunk::Create( true, vecOrigin, vecVelocity );
+ pChunk->ChangeTeam( GetTeamNumber() );
+ }
+
+ // Drop normal chunks
+ for ( iChunk = 0; iChunk < nNormalChunks; iChunk++ )
+ {
+ // Generate a random velocity vector.
+ Vector vecVelocity = Vector( random->RandomFloat( -250,250 ), random->RandomFloat( -250,250 ), random->RandomFloat( 200,450 ) );
+
+ // Create a processed chunk.
+ CResourceChunk *pChunk = CResourceChunk::Create( false, vecOrigin, vecVelocity );
+ pChunk->ChangeTeam( GetTeamNumber() );
+ }
+
+ TFStats()->IncrementTeamStat( GetTeamNumber(), TF_TEAM_STAT_RESOURCE_CHUNKS_DROPPED, (resource_chunk_processed_value.GetFloat() * nProcessedChunks) + (resource_chunk_value.GetFloat() * nNormalChunks) );
+ }
+
+ DetachObjectFromObject();
+
+ UTIL_Remove( this );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Indicates this NPC's place in the relationship table.
+//-----------------------------------------------------------------------------
+Class_T CBaseObject::Classify( void )
+{
+ return (CLASS_MILITARY);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Get the type of this object
+//-----------------------------------------------------------------------------
+int CBaseObject::GetType()
+{
+ return m_iObjectType;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Get the builder of this object
+//-----------------------------------------------------------------------------
+CBaseTFPlayer *CBaseObject::GetBuilder( void )
+{
+ return m_hBuilder;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Get the original builder of this object
+// Used to get the builder of a deteriorating object
+//-----------------------------------------------------------------------------
+CBaseTFPlayer *CBaseObject::GetOriginalBuilder( void )
+{
+ return m_hOriginalBuilder;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return true if the Owning CTeam should clean this object up automatically
+//-----------------------------------------------------------------------------
+bool CBaseObject::ShouldAutoRemove( void )
+{
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: If the object's still being built, it's not usable
+//-----------------------------------------------------------------------------
+int CBaseObject::ObjectCaps( void )
+{
+ if ( IsPlacing() )
+ return 0;
+
+ // If I'm being built, only allow +use if I don't have a sapper on me and I haven't been disabled by a plasma weapon
+ if ( IsBuilding() && !HasSapper() && !IsPlasmaDisabled() )
+ return 0;
+
+ return FCAP_ONOFF_USE;
+};
+
+//-----------------------------------------------------------------------------
+// Clean off the object of offensive material...
+//-----------------------------------------------------------------------------
+bool CBaseObject::RemoveEnemyAttachments( CBaseEntity *pActivator )
+{
+ bool bRemoved = false;
+
+ // Sapper removal
+ if ( pActivator->IsPlayer() )
+ {
+ if ( HasSapper() )
+ {
+ RemoveAllSappers( pActivator );
+ bRemoved = true;
+ }
+ }
+
+ return bRemoved;
+}
+
+
+//-----------------------------------------------------------------------------
+// Object using!
+//-----------------------------------------------------------------------------
+void CBaseObject::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
+{
+ // If we're friendly, pickup / remove sappers
+ // If we're an enemy, plant a sapper
+ if ( pActivator->IsPlayer() )
+ {
+ if ( InSameTeam( pActivator ) )
+ {
+ if ( useType == USE_ON )
+ {
+ // Some combat objects can be picked up
+ if ( m_fObjectFlags & OF_CAN_BE_PICKED_UP )
+ {
+ if ( GetBuilder() == pActivator )
+ {
+ if ( GetBuilder()->GetPlayerClass()->ResupplyAmmoType( 1, m_szAmmoName ) )
+ {
+ PickupObject();
+ }
+ return;
+ }
+ }
+
+ // Sapper removal
+ if ( RemoveEnemyAttachments( pActivator ) )
+ return;
+ }
+ }
+ else
+ {
+ CBaseTFPlayer *pPlayer = (CBaseTFPlayer *)pActivator;
+
+ // If we're already planting a sapper, abort
+ if ( useType == USE_OFF || pPlayer->IsAttachingSapper() )
+ {
+ // Don't abort if we just started placing it. This is to catch people who'd like to +use toggle instead of hold down
+ if ( pPlayer->GetSapperAttachmentTime() > 0.2 && pPlayer->IsAttachingSapper() )
+ {
+ pPlayer->StopAttaching();
+ }
+ }
+ else if ( useType == USE_ON )
+ {
+ // Don't allow sappers to be planted on invulnerable objects
+ if ( m_bInvulnerable )
+ return;
+
+ // If the object's already got a sapper from me on it, I can't put another
+ if ( HasSapperFromPlayer( ((CBaseTFPlayer*)pActivator ) ) )
+ return;
+
+ Vector vecAiming;
+ pPlayer->EyeVectors( &vecAiming );
+ // Trace from the player to the object to find an attachment position
+ trace_t tr;
+ Vector vecStart = pPlayer->EyePosition();
+ UTIL_TraceLine( vecStart, vecStart + (vecAiming * 256), MASK_SOLID, pPlayer, COLLISION_GROUP_NONE, &tr );
+ if ( tr.fraction < 1.0 && tr.m_pEnt == this )
+ {
+ CGrenadeObjectSapper *sapper = CGrenadeObjectSapper::Create( tr.endpos, vecAiming, pPlayer, this );
+ pPlayer->StartAttachingSapper( this, sapper );
+ }
+ }
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Builder has picked up the object
+//-----------------------------------------------------------------------------
+void CBaseObject::PickupObject( void )
+{
+ // Tell the playerclass
+ if ( GetBuilder() && GetBuilder()->GetPlayerClass() )
+ {
+ GetBuilder()->GetPlayerClass()->PickupObject( this );
+ }
+
+ UTIL_Remove( this );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Return true if the specified player's allowed to remove this object
+//-----------------------------------------------------------------------------
+bool CBaseObject::CanBeRemovedBy( CBaseTFPlayer *pPlayer )
+{
+ if ( m_fObjectFlags & OF_CANNOT_BE_DISMANTLED )
+ return false;
+
+ // If I'm a map-defined object, I'm not removable by anyone
+ if ( WasMapPlaced() )
+ return false;
+
+ // If I have an owner, only he can remove me
+ if ( GetBuilder() != pPlayer )
+ return false;
+
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Return true if the specified player's allowed to rotate this object
+//-----------------------------------------------------------------------------
+bool CBaseObject::CanBeRotatedBy( CBaseTFPlayer *pPlayer )
+{
+ // If I'm a map-defined object, I'm not removable by anyone
+ if ( WasMapPlaced() )
+ return false;
+
+ // If I have an owner, only he can remove me
+ if ( GetBuilder() != pPlayer )
+ return false;
+
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : iTeamNum -
+//-----------------------------------------------------------------------------
+void CBaseObject::ChangeTeam( int iTeamNum )
+{
+ CTFTeam *pTeam = ( CTFTeam * )GetGlobalTeam( iTeamNum );
+ CTFTeam *pExisting = ( CTFTeam * )GetTeam();
+
+ TRACE_OBJECT( UTIL_VarArgs( "%0.2f CBaseObject::ChangeTeam old %s new %s\n", gpGlobals->curtime,
+ pExisting ? pExisting->GetName() : "NULL",
+ pTeam ? pTeam->GetName() : "NULL" ) );
+
+ // Already on this team
+ if ( GetTeamNumber() == iTeamNum )
+ return;
+
+ if ( pExisting )
+ {
+ // Remove it from current team ( if it's in one ) and give it to new team
+ pExisting->RemoveObject( this );
+ }
+
+ // Change to new team
+ BaseClass::ChangeTeam( iTeamNum );
+
+ // Add this object to the team's list
+ // But only if we're not placing it
+ if ( pTeam && (!m_bPlacing) )
+ {
+ pTeam->AddObject( this );
+ }
+
+ // Setup for our new team's model
+ SetupTeamModel();
+ CreateBuildPoints();
+ CreateVulnerablePoints();
+
+ // Alien buildings never need power
+ if ( GetTeamNumber() == TEAM_ALIENS )
+ {
+ m_fObjectFlags |= OF_DOESNT_NEED_POWER;
+ }
+
+ GainedNewTechnology( NULL );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : const char
+//-----------------------------------------------------------------------------
+const char *CBaseObject::GetWeaponClassnameForObject( void )
+{
+ return NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pNewOwner -
+//-----------------------------------------------------------------------------
+void CBaseObject::AddItemsNeededForObject( CBaseTFPlayer *pNewOwner )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Derived classes might want to give the new builder the appropriate
+// items needed to own this object and move the objects owned over as well
+// Input : *pNewOwner -
+//-----------------------------------------------------------------------------
+void CBaseObject::ChangeBuilder( CBaseTFPlayer *pNewBuilder, bool moveobjects )
+{
+ CBaseTFPlayer *oldBuilder = GetOwner();
+
+ TRACE_OBJECT( UTIL_VarArgs( "%0.2f CBaseObject::ChangeBuilder old %s, new %s, moveobjects %s\n", gpGlobals->curtime,
+ oldBuilder ? oldBuilder->GetPlayerName() : "NULL",
+ pNewBuilder ? pNewBuilder->GetPlayerName() : "NULL",
+ moveobjects ? "true" : "false" ) );
+
+ // Store off original builder
+ if ( GetOwner() )
+ {
+ m_hOriginalBuilder = GetOwner();
+ }
+
+ m_hBuilder = pNewBuilder;
+
+ if ( !moveobjects )
+ return;
+
+ if ( oldBuilder )
+ {
+ oldBuilder->OwnedObjectChangeTeam( this, pNewBuilder );
+ }
+
+ // For instance, if this is a mortar being added to a technician via subversion, then
+ // the "weapon_mortar" will be added to the player if the player doesn't have it.
+ AddItemsNeededForObject( pNewBuilder );
+
+ const char *classname = GetWeaponClassnameForObject();
+ if ( !classname )
+ return;
+
+ TRACE_OBJECT( UTIL_VarArgs( "%0.2f CBaseObject::ChangeBuilder moving associated objects %s\n", gpGlobals->curtime,
+ classname ) );
+
+ // Find the old player who owned a weapon that owned this object type and remove it
+ // Then add to current player under the approrpriate weapon ( same classname ) which
+ // should have been added in ChangeBuilder
+ for ( int i = 1; i <= gpGlobals->maxClients; i++ )
+ {
+ CBaseTFPlayer *player = static_cast< CBaseTFPlayer *>( UTIL_PlayerByIndex( i ) );
+ if ( !player )
+ continue;
+
+ // Cycle through weapons
+ for ( int j = 0; j < player->WeaponCount(); j++ )
+ {
+ if ( !player->GetWeapon( j ) )
+ continue;
+
+ if ( !FClassnameIs( player->GetWeapon( j ), classname ) )
+ continue;
+
+ // Add to this player
+ if ( player == pNewBuilder )
+ {
+ ( ( CBaseTFCombatWeapon * )player->GetWeapon( j ))->AddAssociatedObject( this );
+ }
+ // Remove from any other
+ else
+ {
+ ( ( CBaseTFCombatWeapon * )player->GetWeapon( j ))->RemoveAssociatedObject( this );
+ }
+ }
+
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Return true if I have at least 1 sapper on me
+//-----------------------------------------------------------------------------
+bool CBaseObject::HasSapper( void )
+{
+ return ( m_hSappers.Size() > 0 );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Return true if the specified player has attached a sapper to me
+//-----------------------------------------------------------------------------
+bool CBaseObject::HasSapperFromPlayer( CBaseTFPlayer *pPlayer )
+{
+ for ( int i = 0; i < m_hSappers.Size(); i++ )
+ {
+ if ( m_hSappers[i] == NULL )
+ continue;
+
+ if ( m_hSappers[i]->GetThrower() == pPlayer )
+ return true;
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Add a sapper to this object
+//-----------------------------------------------------------------------------
+void CBaseObject::AddSapper( CGrenadeObjectSapper *pSapper )
+{
+ SapperHandle hSapper;
+ hSapper = pSapper;
+ m_hSappers.AddToTail( hSapper );
+ m_bHasSapper = true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Tell all sappers on this object to remove themselves
+//-----------------------------------------------------------------------------
+void CBaseObject::RemoveAllSappers( CBaseEntity *pRemovingEntity )
+{
+ // Loop through all the sappers and fire a +use on them (backwards because list will change)
+ int iSize = m_hSappers.Size();
+ for (int i = iSize-1; i >= 0; i--)
+ {
+ m_hSappers[i]->Use( pRemovingEntity, pRemovingEntity, USE_TOGGLE, 0 );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Remove a sapper from this object
+//-----------------------------------------------------------------------------
+void CBaseObject::RemoveSapper( CGrenadeObjectSapper *pSapper )
+{
+ SapperHandle hSapper;
+ hSapper = pSapper;
+ m_hSappers.FindAndRemove( hSapper );
+ m_bHasSapper = HasSapper();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: My owner's just received a new technology, see if it affects me
+//-----------------------------------------------------------------------------
+void CBaseObject::GainedNewTechnology( CBaseTechnology *pTechnology )
+{
+ // Base object doesn't respond to tech
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pRecipient -
+// *techname -
+//-----------------------------------------------------------------------------
+void CBaseObject::GiveNamedTechnology( CBaseTFPlayer *pRecipient, const char *techname )
+{
+ CTFTeam *team = static_cast< CTFTeam * >( pRecipient->GetTeam() );
+ if ( !team )
+ return;
+
+ CBaseTechnology *tech = team->m_pTechnologyTree->GetTechnology( techname );
+ if ( tech )
+ {
+ team->EnableTechnology( tech, true );
+ }
+}
+
+
+bool CBaseObject::ShowVGUIScreen( int panelIndex, bool bShow )
+{
+ Assert( panelIndex >= 0 && panelIndex < m_hScreens.Count() );
+ if ( m_hScreens[panelIndex].Get() )
+ {
+ m_hScreens[panelIndex]->SetActive( bShow );
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns true if this object was placed in the map, not built by a player
+//-----------------------------------------------------------------------------
+bool CBaseObject::WasMapPlaced( void )
+{
+ return m_bWasMapPlaced;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Find nearby objects on my team and connect to them
+//-----------------------------------------------------------------------------
+CRopeKeyframe *CBaseObject::ConnectCableTo( CBaseObject *pObject, int iLocalAttachment, int iTargetAttachment )
+{
+ // Connect to it
+ CRopeKeyframe *pRope = CRopeKeyframe::Create( this, pObject, iLocalAttachment, iTargetAttachment );
+ if ( pRope )
+ {
+ pRope->m_Width = 3;
+ pRope->m_nSegments = ROPE_MAX_SEGMENTS;
+ //pRope->m_RopeFlags |= (ROPE_RESIZE | ROPE_COLLIDE);
+ pRope->EnableCollision();
+ pRope->EnableWind( false );
+ pRope->SetupHangDistance( ROPE_HANG_DIST );
+ pRope->ActivateStartDirectionConstraints( true );
+ pRope->ActivateEndDirectionConstraints( true );
+ }
+
+ // Add the rope to both Object's lists
+ CHandle< CRopeKeyframe > hHandle;
+ hHandle = pRope;
+ m_aRopes.AddToTail( hHandle );
+ pObject->m_aRopes.AddToTail( hHandle );
+
+ // During placement, the rules for whether the rope is transmitted or not are
+ // tricky, so we make a proxy here to control it.
+ if ( IsPlacing() || pObject->IsPlacing() )
+ {
+ CObjectRopeTransmitProxy *pProxy = new CObjectRopeTransmitProxy( pRope );
+ pProxy->m_hObj1 = this;
+ pProxy->m_hObj2 = pObject;
+ // pRope->NetworkProp()->SetTransmitProxy( pProxy ); TODO
+ }
+
+ return pRope;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return true if I have a cable to the specified object
+//-----------------------------------------------------------------------------
+bool CBaseObject::HasCableTo( CBaseObject *pObject )
+{
+ for (int i = 0; i < m_aRopes.Size(); i++)
+ {
+ CHandle< CRopeKeyframe > hHandle;
+ hHandle = m_aRopes[i];
+ if ( hHandle )
+ {
+ if ( m_aRopes[i]->GetEndPoint() == pObject )
+ return true;
+ }
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return an attachment point for a cable
+//-----------------------------------------------------------------------------
+int CBaseObject::GetCableAttachment( void )
+{
+ Vector vecOrigin, vecAngles;
+ // If I already have a rope attached, try and use a different attachment point
+ if ( m_aRopes.Size() )
+ {
+ // First, check to see if we've lost any ropes (this can happen because
+ // the other object it was attached to has been destroyed.
+ int iSize = m_aRopes.Size();
+ for (int i = iSize-1; i >= 0; i--)
+ {
+ CHandle< CRopeKeyframe > hHandle;
+ hHandle = m_aRopes[i];
+ if ( hHandle == NULL )
+ {
+ m_aRopes.Remove(i);
+ }
+ }
+
+ // If I have enough connections, tell 'em I don't want no more
+ if ( m_aRopes.Size() >= MAX_CABLE_CONNECTIONS )
+ return -1;
+
+ char sAttachment[32];
+ Q_snprintf( sAttachment,sizeof(sAttachment), "cablepoint%d", m_aRopes.Size() + 1 );
+ int iPoint = LookupAttachment( sAttachment );
+ if ( iPoint > 0 )
+ return iPoint;
+ }
+
+
+ return LookupAttachment( "cablepoint1" );
+}
+
+
+//====================================================================================================================
+// POWER PACKS
+//====================================================================================================================
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseObject::SetPowerPack( CObjectPowerPack *pPack )
+{
+ bool bHadPower = HasPowerup( POWERUP_POWER );
+ CObjectPowerPack *pOldPack = m_hPowerPack;
+
+ m_hPowerPack = pPack;
+
+ // If it's placing, I don't get power yet
+ if ( m_hPowerPack && !m_hPowerPack->IsPlacing() )
+ {
+ SetPowerup( POWERUP_POWER, true );
+ }
+ else
+ {
+ // Lose power in a second, to give any nearby powerpacks time to connect to me and replace the power
+ if ( bHadPower )
+ {
+ SetContextThink( LostPowerThink, gpGlobals->curtime + 1.0, OBJ_LOSTPOWER_THINK_CONTEXT );
+ if ( GetTFTeam() )
+ {
+ // Dirty hack to make powerpack think I need power
+ m_iPowerups &= ~(1 << POWERUP_POWER);
+ GetTFTeam()->UpdatePowerpacks( pOldPack, this );
+ }
+ }
+ else
+ {
+ SetPowerup( POWERUP_POWER, false );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: We've lost power fully
+//-----------------------------------------------------------------------------
+void CBaseObject::LostPowerThink( void )
+{
+ // We may have found another powerpack
+ if ( !m_hPowerPack )
+ {
+ // Dirty hack to get our powerup removed properly
+ m_iPowerups |= (1 << POWERUP_POWER);
+ SetPowerup( POWERUP_POWER, false );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Set the health of the object
+//-----------------------------------------------------------------------------
+void CBaseObject::InputSetHealth( inputdata_t &inputdata )
+{
+ m_iMaxHealth = inputdata.value.Int();
+ SetHealth( m_iMaxHealth );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Add health to the object
+//-----------------------------------------------------------------------------
+void CBaseObject::InputAddHealth( inputdata_t &inputdata )
+{
+ int iHealth = inputdata.value.Int();
+ SetHealth( MIN( GetMaxHealth(), m_flHealth + iHealth ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Remove health from the object
+//-----------------------------------------------------------------------------
+void CBaseObject::InputRemoveHealth( inputdata_t &inputdata )
+{
+ int iDamage = inputdata.value.Int();
+
+ SetHealth( m_flHealth - iDamage );
+ if ( GetHealth() <= 0 )
+ {
+ m_lifeState = LIFE_DEAD;
+ m_OnDestroyed.FireOutput(this, this);
+ Killed();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &inputdata -
+//-----------------------------------------------------------------------------
+void CBaseObject::InputSetMinDisabledHealth( inputdata_t &inputdata )
+{
+ float minhealth = inputdata.value.Float();
+
+ bool wasdisabled = IsDisabled();
+
+ if ( m_flHealth < minhealth )
+ {
+ SetDisabled( true );
+ // NOTE: This could theoretically add health, sigh.
+ SetHealth( minhealth );
+
+ // Disable it if not already disabled
+ if ( !wasdisabled )
+ {
+ m_OnBecomingDisabled.FireOutput( inputdata.pActivator, this );
+ }
+ }
+ else if ( wasdisabled && ( m_flHealth > minhealth ) )
+ {
+ SetDisabled( false );
+
+ m_OnBecomingReenabled.FireOutput( inputdata.pActivator, this );
+ }
+
+ m_flMinDisableHealth = minhealth;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &inputdata -
+//-----------------------------------------------------------------------------
+void CBaseObject::InputSetSolidToPlayer( inputdata_t &inputdata )
+{
+ int ival = inputdata.value.Int();
+ ival = clamp( ival, (int)SOLID_TO_PLAYER_USE_DEFAULT, (int)SOLID_TO_PLAYER_NO );
+ OBJSOLIDTYPE stp = (OBJSOLIDTYPE)ival;
+ SetSolidToPlayers( stp );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseObject::PlayStartupAnimation( void )
+{
+ SetActivity( ACT_OBJ_STARTUP );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseObject::DetermineAnimation( void )
+{
+ Activity desiredActivity = m_Activity;
+
+ switch ( m_Activity )
+ {
+ default:
+ {
+ if ( IsPlacing() )
+ {
+ desiredActivity = ACT_OBJ_PLACING;
+ }
+ else if ( IsBuilding() )
+ {
+ desiredActivity = ACT_OBJ_ASSEMBLING;
+ }
+ /*
+ TODO:
+ ACT_OBJ_DISMANTLING;
+ ACT_OBJ_DETERIORATING;
+ */
+ else
+ {
+ desiredActivity = ACT_OBJ_RUNNING;
+ }
+ }
+ break;
+ case ACT_OBJ_STARTUP:
+ {
+ if ( IsActivityFinished() )
+ {
+ desiredActivity = ACT_OBJ_RUNNING;
+ }
+ }
+ break;
+ }
+
+ if ( desiredActivity == m_Activity )
+ return;
+
+ SetActivity( desiredActivity );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Attach this object to the specified object
+//-----------------------------------------------------------------------------
+
+void CBaseObject::AttachObjectToObject( const CBaseEntity *pEntity, int iPoint, Vector &vecOrigin )
+{
+ SetupAttachedVersion();
+
+ m_hBuiltOnEntity = pEntity;
+ m_iBuiltOnPoint = iPoint;
+
+ if ( m_hBuiltOnEntity.Get() )
+ {
+ // Parent ourselves to the object
+ int iAttachment = 0;
+ const IHasBuildPoints *pBPInterface = dynamic_cast<const IHasBuildPoints*>( pEntity );
+ if ( pBPInterface )
+ iAttachment = pBPInterface->GetBuildPointAttachmentIndex( iPoint );
+
+ SetParent( m_hBuiltOnEntity.Get(), iAttachment );
+
+ if ( iAttachment >= 1 )
+ {
+ // Stick right onto the attachment point.
+ vecOrigin.Init();
+ SetLocalOrigin( vecOrigin );
+ SetLocalAngles( QAngle(0,0,0) );
+ }
+ else
+ {
+ SetAbsOrigin( vecOrigin );
+ vecOrigin = GetLocalOrigin();
+ }
+ }
+
+ Assert( m_hBuiltOnEntity == GetMoveParent() );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Detach this object from its parent, if it has one
+//-----------------------------------------------------------------------------
+void CBaseObject::DetachObjectFromObject( void )
+{
+ if ( !GetParentObject() )
+ return;
+
+ // Clear the build point
+ IHasBuildPoints *pBPInterface = dynamic_cast<IHasBuildPoints*>(GetParentObject() );
+ Assert( pBPInterface );
+ pBPInterface->SetObjectOnBuildPoint( m_iBuiltOnPoint, NULL );
+
+ SetParent( NULL );
+ m_hBuiltOnEntity = NULL;
+ m_iBuiltOnPoint = 0;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Spawn any objects specified inside the mdl
+//-----------------------------------------------------------------------------
+void CBaseObject::SpawnEntityOnBuildPoint( const char *pEntityName, int iAttachmentNumber )
+{
+ // Try and spawn the object
+ CBaseEntity *pEntity = CreateEntityByName( pEntityName );
+ if ( !pEntity )
+ return;
+
+ Vector vecOrigin;
+ QAngle vecAngles;
+ GetAttachment( iAttachmentNumber, vecOrigin, vecAngles );
+ pEntity->SetAbsOrigin( vecOrigin );
+ pEntity->SetAbsAngles( vecAngles );
+ pEntity->Spawn();
+
+ // If it's an object, finish setting it up
+ CBaseObject *pObject = dynamic_cast<CBaseObject*>(pEntity);
+ if ( !pObject )
+ return;
+
+ // Add a buildpoint here
+ int iPoint = AddBuildPoint( iAttachmentNumber );
+ AddValidObjectToBuildPoint( iPoint, pObject->GetType() );
+ pObject->SetBuilder( GetBuilder() );
+ pObject->ChangeTeam( GetTeamNumber() );
+ pObject->SpawnControlPanels();
+ pObject->SetHealth( pObject->GetMaxHealth() );
+ pObject->FinishedBuilding();
+ pObject->AttachObjectToObject( this, iPoint, vecOrigin );
+ pObject->m_fObjectFlags |= OF_CANNOT_BE_DISMANTLED;
+ pObject->AttemptToFindPower();
+
+ IHasBuildPoints *pBPInterface = dynamic_cast<IHasBuildPoints*>(this);
+ Assert( pBPInterface );
+ pBPInterface->SetObjectOnBuildPoint( iPoint, pObject );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Spawn any objects specified inside the mdl
+//-----------------------------------------------------------------------------
+void CBaseObject::SpawnObjectPoints( void )
+{
+ KeyValues *modelKeyValues = new KeyValues("");
+ if ( !modelKeyValues->LoadFromBuffer( modelinfo->GetModelName( GetModel() ), modelinfo->GetModelKeyValueText( GetModel() ) ) )
+ {
+ modelKeyValues->deleteThis();
+ return;
+ }
+
+ // Do we have a build point section?
+ KeyValues *pkvAllObjectPoints = modelKeyValues->FindKey("object_points");
+ if ( !pkvAllObjectPoints )
+ {
+ modelKeyValues->deleteThis();
+ return;
+ }
+
+ // Start grabbing the sounds and slotting them in
+ KeyValues *pkvObjectPoint;
+ for ( pkvObjectPoint = pkvAllObjectPoints->GetFirstSubKey(); pkvObjectPoint; pkvObjectPoint = pkvObjectPoint->GetNextKey() )
+ {
+ // Find the attachment first
+ const char *sAttachment = pkvObjectPoint->GetName();
+ int iAttachmentNumber = LookupAttachment( sAttachment );
+ if ( iAttachmentNumber == 0 )
+ {
+ Msg( "ERROR: Model %s specifies object point %s, but has no attachment named %s.\n", STRING(GetModelName()), pkvObjectPoint->GetString(), pkvObjectPoint->GetString() );
+ continue;
+ }
+
+ // Now see what we're supposed to spawn there
+ // The count check is because it seems wrong to emit multiple entities on the same point
+ int nCount = 0;
+ KeyValues *pkvObject;
+ for ( pkvObject = pkvObjectPoint->GetFirstSubKey(); pkvObject; pkvObject = pkvObject->GetNextKey() )
+ {
+ SpawnEntityOnBuildPoint( pkvObject->GetName(), iAttachmentNumber );
+ ++nCount;
+ Assert( nCount <= 1 );
+ }
+ }
+
+ modelKeyValues->deleteThis();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseObject::CreateVulnerablePoints()
+{
+ KeyValues *modelKeyValues = new KeyValues("");
+ if ( !modelKeyValues->LoadFromBuffer( modelinfo->GetModelName( GetModel() ), modelinfo->GetModelKeyValueText( GetModel() ) ) )
+ return;
+
+ // Do we have a build point section?
+ KeyValues *pkvAllVulnerablePoints = modelKeyValues->FindKey("vulnerable_points");
+ if ( !pkvAllVulnerablePoints )
+ return;
+
+ // Start grabbing the sounds and slotting them in
+ KeyValues *pkvVulnerablePoint = pkvAllVulnerablePoints->GetFirstSubKey();
+ while ( pkvVulnerablePoint )
+ {
+ AddVulnerablePoint( pkvVulnerablePoint->GetName(), pkvVulnerablePoint->GetFloat() );
+ pkvVulnerablePoint = pkvVulnerablePoint->GetNextKey();
+ }
+
+}
+
+ConVar obj_debug_vulnerable( "obj_debug_vulnerable","0", FCVAR_NONE, "Show vulnerable points" );
+
+void CBaseObject::AddVulnerablePoint( const char* szName, float fMultiplier )
+{
+ // Make a new vulnerable point
+ VulnerablePoint_t v;
+ Q_memset(&v,0,sizeof(v));
+ v.m_fDamageMultiplier = fMultiplier;
+
+ int nSet, nBox;
+
+ if( !LookupHitbox(szName, nSet, nBox) )
+ {
+ Msg( "Error: Vulnerable point on model %s unable to locate hitbox %s\n", STRING(GetModelName()), szName);
+ return;
+ }
+
+ v.m_nSet = nSet;
+ v.m_nBox = nBox;
+
+ // Insert it into our list
+ if( obj_debug_vulnerable.GetBool() )
+ {
+ Msg( "Vulnerable point %s on model %s added with a damage multiplier of %f. (%i, %i)\n", szName, STRING(GetModelName()), fMultiplier, nSet, nBox);
+ }
+
+ m_VulnerablePoints.AddToTail( v );
+}
+
+
+float CBaseObject::FindVulnerablePointMultiplier( int nGroup, int nBox )
+{
+ for( int i=0; i < m_VulnerablePoints.Count(); i++ )
+ {
+ VulnerablePoint_t& v = m_VulnerablePoints[i];
+
+ if( v.m_nBox == nBox /* && v.m_nSet == nGroup */)
+ {
+ if( obj_debug_vulnerable.GetBool() )
+ {
+ Msg("VulnerablePoint: %f\n",v.m_fDamageMultiplier);
+ }
+ return v.m_fDamageMultiplier;
+ }
+ }
+
+ if( obj_debug_vulnerable.GetBool() )
+ {
+ Msg("Couldn't find vulnerable point: %i %i\n",nGroup,nBox);
+ }
+ return 1.f;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+QAngle CBaseObject::ConvertAbsAnglesToLocal( QAngle vecLocalAngles )
+{
+ if ( !GetMoveParent() )
+ return vecLocalAngles;
+
+ EntityMatrix vehicleToWorld, childMatrix;
+ vehicleToWorld.InitFromEntity( GetMoveParent() ); // parent->world
+
+ // Calculate the build point angles in vehicle space
+ VMatrix attachmentToWorld;
+ MatrixFromAngles( vecLocalAngles, attachmentToWorld );
+
+ VMatrix worldToVehicle = vehicleToWorld.Transpose();
+ VMatrix attachmentToVehicle;
+ MatrixMultiply( worldToVehicle, attachmentToWorld, attachmentToVehicle );
+ QAngle vecAbsAngles;
+ MatrixToAngles( attachmentToVehicle, vecAbsAngles );
+
+ return vecAbsAngles;
+}
+
+bool CBaseObject::IsSolidToPlayers( void ) const
+{
+ switch ( m_SolidToPlayers )
+ {
+ default:
+ break;
+ case SOLID_TO_PLAYER_USE_DEFAULT:
+ {
+ if ( GetObjectInfo( ObjectType() ) )
+ {
+ return GetObjectInfo( ObjectType() )->m_bSolidToPlayerMovement;
+ }
+ }
+ break;
+ case SOLID_TO_PLAYER_YES:
+ return true;
+ case SOLID_TO_PLAYER_NO:
+ return false;
+ }
+
+ return false;
+}
+
+void CBaseObject::SetSolidToPlayers( OBJSOLIDTYPE stp, bool force )
+{
+ bool changed = stp != m_SolidToPlayers;
+ m_SolidToPlayers = stp;
+
+ if ( changed || force )
+ {
+ SetCollisionGroup(
+ IsSolidToPlayers() ?
+ TFCOLLISION_GROUP_OBJECT_SOLIDTOPLAYERMOVEMENT :
+ TFCOLLISION_GROUP_OBJECT );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Hooks to support swapping out model if the object is damaged
+// Input : bDisabled -
+//-----------------------------------------------------------------------------
+void CBaseObject::SetDisabled( bool bDisabled )
+{
+ bool changed = m_bDisabled != bDisabled;
+ m_bDisabled = bDisabled;
+
+ // value changed and mapper specified a "disabled"/damaged model
+ if ( changed && NULL_STRING != m_iszDisabledModel )
+ {
+ // Change model
+ SetModel( STRING( m_bDisabled ? m_iszDisabledModel : m_iszEnabledModel ) );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseObject::SetBuffStation( CObjectBuffStation *pBuffStation, bool bPlacing )
+{
+ // Activate
+ if ( pBuffStation && !GetBuffStation() && !bPlacing && !IsBuilding() && IsPowered() )
+ {
+ BuffStationActivate();
+ }
+
+ if ( GetBuffStation() && ( pBuffStation == GetBuffStation() ) && !m_bBuffActivated && !bPlacing && !IsBuilding() && IsPowered() )
+ {
+ BuffStationActivate();
+ }
+
+ // Deactivate
+ if ( !pBuffStation && GetBuffStation() )
+ {
+ BuffStationDeactivate();
+ }
+
+ m_hBuffStation = pBuffStation;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CBaseObject::IsHookedAndBuffed( void )
+{
+ if ( GetBuffStation() && HasPowerup( POWERUP_BOOST ) )
+ {
+ if ( GetBuffStation()->IsPowered() && !GetBuffStation()->IsPlacing() &&
+ !GetBuffStation()->IsBuilding() )
+ return true;
+ }
+
+ return false;
+}