diff options
Diffstat (limited to 'game/server/tf/tf_obj.cpp')
| -rw-r--r-- | game/server/tf/tf_obj.cpp | 3736 |
1 files changed, 3736 insertions, 0 deletions
diff --git a/game/server/tf/tf_obj.cpp b/game/server/tf/tf_obj.cpp new file mode 100644 index 0000000..d1de8d4 --- /dev/null +++ b/game/server/tf/tf_obj.cpp @@ -0,0 +1,3736 @@ +//========= 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_weaponbase.h" +#include "rope.h" +#include "rope_shared.h" +#include "bone_setup.h" +#include "ndebugoverlay.h" +#include "rope_helpers.h" +#include "IEffects.h" +#include "vstdlib/random.h" +#include "tier1/strtools.h" +#include "basegrenade_shared.h" +#include "tf_gamerules.h" +#include "engine/IEngineSound.h" +#include "tf_shareddefs.h" +#include "vguiscreen.h" +#include "hierarchy.h" +#include "func_no_build.h" +#include "func_respawnroom.h" +#include <KeyValues.h> +#include "ihasbuildpoints.h" +#include "utldict.h" +#include "filesystem.h" +#include "npcevent.h" +#include "tf_shareddefs.h" +#include "animation.h" +#include "effect_dispatch_data.h" +#include "te_effect_dispatch.h" +#include "tf_gamestats.h" +#include "tf_ammo_pack.h" +#include "tf_obj_sapper.h" +#include "particle_parse.h" +#include "tf_fx.h" +#include "trains.h" +#include "serverbenchmark_base.h" +#include "tf_weapon_wrench.h" +#include "tf_weapon_grenade_pipebomb.h" +#include "tf_weapon_builder.h" + +#include "player_vs_environment/tf_population_manager.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +// Control panels +#define SCREEN_OVERLAY_MATERIAL "vgui/screens/vgui_overlay" + +#define ROPE_HANG_DIST 150 +#define UPGRADE_LEVEL_HEALTH_MULTIPLIER 1.2f + + +ConVar tf_obj_gib_velocity_min( "tf_obj_gib_velocity_min", "100", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY ); +ConVar tf_obj_gib_velocity_max( "tf_obj_gib_velocity_max", "450", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY ); +ConVar tf_obj_gib_maxspeed( "tf_obj_gib_maxspeed", "800", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY ); +ConVar tf_obj_upgrade_per_hit( "tf_obj_upgrade_per_hit", "25", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY ); + +ConVar object_verbose( "object_verbose", "0", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Debug object system." ); +ConVar obj_damage_factor( "obj_damage_factor","0", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Factor applied to all damage done to objects" ); +ConVar obj_child_damage_factor( "obj_child_damage_factor","0.25", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Factor applied to damage done to objects that are built on a buildpoint" ); +ConVar tf_fastbuild("tf_fastbuild", "0", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY ); +ConVar tf_obj_ground_clearance( "tf_obj_ground_clearance", "32", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Object corners can be this high above the ground" ); + +ConVar tf_obj_damage_tank_achievement_amount( "tf_obj_damage_tank_achievement_amount", "2000", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY ); + +extern short g_sModelIndexFireball; +extern ConVar tf_cheapobjects; + +// 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 PLASMA_DISABLE_TIME 4 + +IMPLEMENT_AUTO_LIST( IBaseObjectAutoList ); + +BEGIN_DATADESC( CBaseObject ) + // keys + DEFINE_KEYFIELD_NOT_SAVED( m_SolidToPlayers, FIELD_INTEGER, "SolidToPlayer" ), + DEFINE_KEYFIELD( m_nDefaultUpgradeLevel, FIELD_INTEGER, "defaultupgrade" ), + + DEFINE_THINKFUNC( UpgradeThink ), + + // Inputs + DEFINE_INPUTFUNC( FIELD_INTEGER, "SetHealth", InputSetHealth ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "AddHealth", InputAddHealth ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "RemoveHealth", InputRemoveHealth ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "SetSolidToPlayer", InputSetSolidToPlayer ), + DEFINE_INPUTFUNC( FIELD_STRING, "SetBuilder", InputSetBuilder ), + DEFINE_INPUTFUNC( FIELD_VOID, "Show", InputShow ), + DEFINE_INPUTFUNC( FIELD_VOID, "Hide", InputHide ), + DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), + DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), + + // 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), -1, SPROP_VARINT ), + SendPropInt(SENDINFO(m_iMaxHealth), -1, SPROP_VARINT ), + SendPropBool(SENDINFO(m_bHasSapper) ), + SendPropInt(SENDINFO(m_iObjectType), Q_log2( OBJ_LAST ) + 1, SPROP_UNSIGNED ), + SendPropBool(SENDINFO(m_bBuilding) ), + SendPropBool(SENDINFO(m_bPlacing) ), + SendPropBool(SENDINFO(m_bCarried) ), + SendPropBool(SENDINFO(m_bCarryDeploy) ), + SendPropBool(SENDINFO(m_bMiniBuilding) ), + SendPropFloat(SENDINFO(m_flPercentageConstructed), 8, 0, 0.0, 1.0f ), + SendPropInt(SENDINFO(m_fObjectFlags), OF_BIT_COUNT, SPROP_UNSIGNED ), + SendPropEHandle(SENDINFO(m_hBuiltOnEntity)), + SendPropBool( SENDINFO( m_bDisabled ) ), + SendPropEHandle( SENDINFO( m_hBuilder ) ), + SendPropVector( SENDINFO( m_vecBuildMaxs ), -1, SPROP_COORD ), + SendPropVector( SENDINFO( m_vecBuildMins ), -1, SPROP_COORD ), + SendPropInt( SENDINFO( m_iDesiredBuildRotations ), 2, SPROP_UNSIGNED ), + SendPropBool( SENDINFO( m_bServerOverridePlacement ) ), + SendPropInt( SENDINFO(m_iUpgradeLevel), 3 ), + SendPropInt( SENDINFO(m_iUpgradeMetal), 10 ), + SendPropInt( SENDINFO(m_iUpgradeMetalRequired), 10 ), + SendPropInt( SENDINFO(m_iHighestUpgradeLevel), 3 ), + SendPropInt( SENDINFO(m_iObjectMode), 2, SPROP_UNSIGNED ), + SendPropBool( SENDINFO( m_bDisposableBuilding ) ), + SendPropBool( SENDINFO( m_bWasMapPlaced ) ), + SendPropBool( SENDINFO( m_bPlasmaDisable ) ), +END_SEND_TABLE(); + + +bool PlayerIndexLessFunc( const int &lhs, const int &rhs ) +{ + return lhs < rhs; +} + +// 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_iHealth = m_iMaxHealth = m_flHealth = 0; + m_flPercentageConstructed = 0; + m_bPlacing = false; + m_bBuilding = false; + m_Activity = ACT_INVALID; + m_bDisabled = false; + m_SolidToPlayers = SOLID_TO_PLAYER_USE_DEFAULT; + m_bPlacementOK = false; + m_aGibs.Purge(); + m_iHighestUpgradeLevel = 1; + m_bCarryDeploy = false; + m_flCarryDeployTime = 0; + m_iHealthOnPickup = 0; + m_iLifetimeDamage = 0; + m_bCannotDie = false; + m_bMiniBuilding = false; + m_flPlasmaDisableTime = 0; + m_bPlasmaDisable = false; + + m_bDisposableBuilding = false; + + m_vecBuildForward = vec3_origin; + m_flBuildDistance = 0.0f; + + m_bForceQuickBuild = false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseObject::UpdateOnRemove( void ) +{ + m_bDying = true; + + // check for sapper crits + CObjectSapper *pSapper = GetSapper(); + if ( pSapper ) + { + // give an assist to the sapper's owner + CTFPlayer *pSapperOwner = pSapper->GetOwner(); + if ( pSapperOwner ) + { + pSapperOwner->m_Shared.IncrementRevengeCrits(); + } + } + + DestroyObject(); + + if ( GetTeam() ) + { + ((CTFTeam*)GetTeam())->RemoveObject( this ); + } + + DetachObjectFromObject(); + + // 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::UpdateTransmitState() +{ + return SetTransmitState( FL_EDICT_FULLCHECK ); +} + +//----------------------------------------------------------------------------- +// 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; + + if ( pInfo->m_pClientEnt ) + { + CBaseEntity *pRecipientEntity = CBaseEntity::Instance( pInfo->m_pClientEnt ); + if ( pRecipientEntity && pRecipientEntity->ShouldForceTransmitsForTeam( GetTeamNumber() ) ) + return FL_EDICT_ALWAYS; + } + + 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() +{ + PrecacheMaterial( SCREEN_OVERLAY_MATERIAL ); + + PrecacheScriptSound( GetObjectInfo( ObjectType() )->m_pExplodeSound ); + PrecacheScriptSound( GetObjectInfo( ObjectType() )->m_pUpgradeSound ); + + const char *pEffect = GetObjectInfo( ObjectType() )->m_pExplosionParticleEffect; + + if ( pEffect && pEffect[0] != '\0' ) + { + PrecacheParticleSystem( pEffect ); + } + + PrecacheParticleSystem( "nutsnbolts_build" ); + PrecacheParticleSystem( "nutsnbolts_upgrade" ); + PrecacheParticleSystem( "nutsnbolts_repair" ); + + PrecacheModel( "models/weapons/w_models/w_toolbox.mdl" ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseObject::Spawn( void ) +{ + Precache(); + + CollisionProp()->SetSurroundingBoundsType( USE_BEST_COLLISION_BOUNDS ); + SetSolidToPlayers( m_SolidToPlayers, true ); + + m_bWasMapPlaced = false; + m_bHasSapper = false; + if ( HasSpawnFlags(SF_BASEOBJ_INVULN) ) + { + m_takedamage = DAMAGE_NO; + } + else + { + m_takedamage = DAMAGE_YES; + } + + AddFlag( FL_OBJECT ); // So NPCs will notice it + SetViewOffset( WorldSpaceCenter() - GetAbsOrigin() ); + + m_iDesiredBuildRotations = 0; + m_flCurrentBuildRotation = 0; + + if ( MustBeBuiltOnAttachmentPoint() ) + { + AddEffects( EF_NODRAW ); + } + + // assume valid placement + m_bServerOverridePlacement = true; + + m_iUpgradeLevel = 1; + m_iUpgradeMetalRequired = GetUpgradeMetalRequired(); + + if ( !IsCarried() ) + { + FirstSpawn(); + } + + UpdateLastKnownArea(); +} + +//----------------------------------------------------------------------------- +// Purpose: Initialization that should only be done when the object is first created. +//----------------------------------------------------------------------------- +void CBaseObject::FirstSpawn() +{ + if ( !VPhysicsGetObject() ) + VPhysicsInitStatic(); + + m_iUpgradeMetal = 0; + m_iKills = 0; + m_iAssists = 0; + m_ConstructorList.SetLessFunc( PlayerIndexLessFunc ); + m_flHealth = m_iMaxHealth = m_iHealth; + + SetContextThink( &CBaseObject::BaseObjectThink, gpGlobals->curtime + 0.1, OBJ_BASE_THINK_CONTEXT ); +} + +//----------------------------------------------------------------------------- +// 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; + const char *pOrgLL = "controlpanel%d_ll"; + const char *pOrgUR = "controlpanel%d_ur"; + const char *pAttachmentNameLL = pOrgLL; + const 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 = NULL; + 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 ); + pScreen->SetTransparency( true ); + + // for now, only input by the owning player + pScreen->SetPlayerOwner( GetBuilder(), true ); + + int nScreen = m_hScreens.AddToTail( ); + m_hScreens[nScreen].Set( pScreen ); + } +} + +//----------------------------------------------------------------------------- +// Handle commands sent from vgui panels on the client +//----------------------------------------------------------------------------- +bool CBaseObject::ClientCommand( CTFPlayer *pSender, const CCommand &args ) +{ + //const char *pCmd = args[0]; + return false; +} + +#define BASE_OBJECT_THINK_DELAY 0.1 +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseObject::BaseObjectThink( void ) +{ + SetNextThink( gpGlobals->curtime + BASE_OBJECT_THINK_DELAY, OBJ_BASE_THINK_CONTEXT ); + + // Make sure animation is up to date + DetermineAnimation(); + + DeterminePlaybackRate(); + + if ( m_bPlasmaDisable ) + { + if ( gpGlobals->curtime > (m_flPlasmaDisableTime ) ) + { + m_bPlasmaDisable = false; + UpdateDisabledState(); + } + } + + // Do nothing while we're being placed + if ( IsPlacing() ) + { + if ( MustBeBuiltOnAttachmentPoint() ) + { + UpdateAttachmentPlacement(); + m_bServerOverridePlacement = true; + } + else + { + m_bServerOverridePlacement = IsPlacementPosValid(); + + UpdateDesiredBuildRotation( BASE_OBJECT_THINK_DELAY ); + } + + return; + } + + // If we're building, keep going + if ( IsBuilding() ) + { + BuildingThink(); + return; + } + + if ( IsUpgrading() ) + { + UpgradeThink(); + } + else + { + if ( GetReversesBuildingConstructionSpeed() > 0.0f ) + { + DoReverseBuild(); + } + else + { + if ( GetUpgradeLevel() < GetHighestUpgradeLevel() ) + { + // Keep moving up levels until we reach the level we were at before. + StartUpgrading(); + } + else + { + m_bCarryDeploy = false; + } + } + } +} + +void CBaseObject::ResetPlacement( void ) +{ + m_bPlacementOK = false; + + // Clear out previous parent + if ( m_hBuiltOnEntity.Get() ) + { + m_hBuiltOnEntity = NULL; + m_iBuiltOnPoint = 0; + SetParent( NULL ); + } + + // teleport to builder's origin + CTFPlayer *pPlayer = GetOwner(); + + if ( pPlayer ) + { + Teleport( &pPlayer->WorldSpaceCenter(), &GetLocalAngles(), NULL ); + } +} + +bool CBaseObject::UpdateAttachmentPlacement( CBaseObject *pObjectOverride ) +{ + // See if we should snap to a build position + // finding one implies it is a valid position + if ( FindSnapToBuildPos( pObjectOverride ) ) + { + m_bPlacementOK = true; + + Teleport( &m_vecBuildOrigin, &GetLocalAngles(), NULL ); + } + else + { + ResetPlacement(); + } + + return m_bPlacementOK; +} + +//----------------------------------------------------------------------------- +// Purpose: Cheap check to see if we are in any server-defined No-build areas. +//----------------------------------------------------------------------------- +bool CBaseObject::EstimateValidBuildPos( void ) +{ + // Make sure CalculatePlacementPos() has been called to setup the member variables used below + + CTFPlayer *pPlayer = GetOwner(); + + if ( !pPlayer ) + return false; + + // Cannot build inside a nobuild brush + if ( PointInNoBuild( m_vecBuildOrigin, this ) ) + return false; + + if ( PointInNoBuild( m_vecBuildCenterOfMass, this ) ) + return false; + + // If we're receiving trigger hurt damage, don't allow building here. + if ( IsTakingTriggerHurtDamageAtPoint( m_vecBuildOrigin ) ) + return false; + + if ( IsTakingTriggerHurtDamageAtPoint( m_vecBuildCenterOfMass ) ) + return false; + + if ( PointInRespawnRoom( NULL, m_vecBuildOrigin ) && !g_pServerBenchmark->IsBenchmarkRunning() ) + return false; + + if ( PointInRespawnRoom( NULL, m_vecBuildCenterOfMass ) && !g_pServerBenchmark->IsBenchmarkRunning() ) + return false; + + Vector vecBuildFarEdge = m_vecBuildOrigin + m_vecBuildForward * ( m_flBuildDistance + 8.0f ); + if ( PointsCrossRespawnRoomVisualizer( pPlayer->WorldSpaceCenter(), vecBuildFarEdge ) ) + return false; + + return true; +} + + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseObject::DeterminePlaybackRate( void ) +{ + float flReverseBuildingConstructionSpeed = GetReversesBuildingConstructionSpeed(); + if ( flReverseBuildingConstructionSpeed == 0.0f ) + { + flReverseBuildingConstructionSpeed = 1.0f; + } + else + { + flReverseBuildingConstructionSpeed *= -1.0f; + } + + // If a sapper was added or removed part way through construction we need to invert the time to completion + bool bAdjustCompleteTime = ( flReverseBuildingConstructionSpeed > 0.0f && GetPlaybackRate() < 0.0f ) || + ( flReverseBuildingConstructionSpeed < 0.0f && GetPlaybackRate() >= 0.0f ); + + if ( IsBuilding() ) + { + // Default half rate, author build anim as if one player is building + // ConstructionMultiplier already contains the reverse + SetPlaybackRate( GetConstructionMultiplier() * 0.5 ); + } + else + { + SetPlaybackRate( 1.0 * flReverseBuildingConstructionSpeed ); + } + + if ( bAdjustCompleteTime ) + { + float fRelativeCycle = ( ( flReverseBuildingConstructionSpeed > 0.0f ) ? ( 1.0f - GetCycle() ) : ( GetCycle() ) ); + + float flUpgradeTime = ( ShouldQuickBuild() ? 0 : GetObjectInfo( ObjectType() )->m_flUpgradeDuration ); + flUpgradeTime /= ( ( flReverseBuildingConstructionSpeed < 0.0f ) ? ( flReverseBuildingConstructionSpeed * -1.0 ) : 1.0f ); + m_flUpgradeCompleteTime = gpGlobals->curtime + flUpgradeTime * fRelativeCycle; + m_flTotalConstructionTime = m_flConstructionTimeLeft = GetTotalTime(); + + float flNewConstructionTimeLeft = m_flConstructionTimeLeft * fRelativeCycle; + m_flConstructionTimeLeft *= fRelativeCycle; + + m_flConstructionStartTime += m_flConstructionTimeLeft - flNewConstructionTimeLeft; + m_flConstructionTimeLeft = flNewConstructionTimeLeft; + } + + if ( !(m_fObjectFlags & OF_DOESNT_HAVE_A_MODEL) ) + { + StudioFrameAdvance(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTFPlayer *CBaseObject::GetOwner() +{ + return m_hBuilder; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseObject::SetBuilder( CTFPlayer *pBuilder ) +{ + TRACE_OBJECT( UTIL_VarArgs( "%0.2f CBaseObject::SetBuilder builder %s\n", gpGlobals->curtime, + pBuilder ? pBuilder->GetPlayerName() : "NULL" ) ); + + m_hBuilder = pBuilder; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CBaseObject::ObjectType( ) const +{ + return m_iObjectType; +} + +//----------------------------------------------------------------------------- +// Purpose: Destroys the object, gives a chance to spawn an explosion +//----------------------------------------------------------------------------- +void CBaseObject::DetonateObject( void ) +{ + // Blow us up. + CTakeDamageInfo info( this, this, vec3_origin, GetAbsOrigin(), 0, DMG_GENERIC ); + Killed( info ); +} + +//----------------------------------------------------------------------------- +// 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() ) ); + + // If we are carried, uncarry us before destruction. + if ( IsCarried() && GetBuilder() ) + { + DropCarriedObject( GetBuilder() ); + + CTFWeaponBuilder *pBuilder = dynamic_cast<CTFWeaponBuilder*>( GetBuilder()->Weapon_OwnsThisID( TF_WEAPON_BUILDER ) ); + if ( pBuilder ) + { + pBuilder->SwitchOwnersWeaponToLast(); + } + } + + if ( GetBuilder() ) + { + GetBuilder()->OwnedObjectDestroyed( this ); + } + + UTIL_Remove( this ); + + DestroyScreens(); +} + +//----------------------------------------------------------------------------- +// Purpose: Remove any screens that are active on this object +//----------------------------------------------------------------------------- +void CBaseObject::DestroyScreens( void ) +{ + // Kill the control panels + int i; + for ( i = m_hScreens.Count(); --i >= 0; ) + { + DestroyVGuiScreen( m_hScreens[i].Get() ); + } + m_hScreens.RemoveAll(); +} + +//----------------------------------------------------------------------------- +// Purpose: Get the total time it will take to build this object +//----------------------------------------------------------------------------- +float CBaseObject::GetTotalTime( void ) +{ + float flBuildTime = GetObjectInfo( ObjectType() )->m_flBuildTime; + + CTFPlayer *pTFBuilder= GetBuilder(); + if ( pTFBuilder ) + { + CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pTFBuilder, flBuildTime, mod_build_rate ); + } + + if ( tf_fastbuild.GetInt() ) + { + flBuildTime = MIN( 2.f, flBuildTime ); + } + + // quick builds for engineers in Mann Vs Machine mode during setup time + if ( TFGameRules()->IsQuickBuildTime() ) + { + flBuildTime = MIN( 1.0f, flBuildTime ); + } + + return flBuildTime; +} + +//----------------------------------------------------------------------------- +// Purpose: Start placing the object +//----------------------------------------------------------------------------- +void CBaseObject::StartPlacement( CTFPlayer *pPlayer ) +{ + AddSolidFlags( FSOLID_NOT_SOLID ); + + m_bPlacing = true; + m_bBuilding = false; + if ( pPlayer ) + { + SetBuilder( pPlayer ); + ChangeTeam( pPlayer->GetTeamNumber() ); + } + + // needed? + m_nRenderMode = kRenderNormal; + + // Set my build size + CollisionProp()->WorldSpaceAABB( &m_vecBuildMins.GetForModify(), &m_vecBuildMaxs.GetForModify() ); + m_vecBuildMins -= Vector( 4,4,0 ); + m_vecBuildMaxs += Vector( 4,4,0 ); + m_vecBuildMins -= GetAbsOrigin(); + m_vecBuildMaxs -= GetAbsOrigin(); + + // Set the skin + m_nSkin = ( GetTeamNumber() == TF_TEAM_RED ) ? 0 : 1; +} + +//----------------------------------------------------------------------------- +// 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, CBasePlayer *pBuilder, float &flNearestPoint, Vector &vecNearestBuildPoint, bool bIgnoreChecks ) +{ + 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) ) + { + if ( !bIgnoreChecks ) + { + // ignore build points outside our view + if ( !pBuilder->FInViewCone( vecBPOrigin ) ) + continue; + + // Do a trace to make sure we don't place attachments through things (players, world, etc...) + Vector vecStart = pBuilder->EyePosition(); + trace_t trace; + CTraceFilterNoNPCsOrPlayer ignorePlayersFilter( pBuilder, COLLISION_GROUP_NONE ); + UTIL_TraceLine( vecStart, vecBPOrigin, MASK_SOLID, &ignorePlayersFilter, &trace ); + if ( trace.m_pEnt != pEntity && trace.fraction != 1.0 ) + continue; + } + + float flDist = (vecBPOrigin - pBuilder->GetAbsOrigin()).Length(); + + // if this is closer, or is the first one in our view, check it out + if ( bIgnoreChecks || ( 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: Find a buildpoint on the specified player +//----------------------------------------------------------------------------- +bool CBaseObject::FindBuildPointOnPlayer( CTFPlayer *pTFPlayer, CBasePlayer *pBuilder, float &flNearestPoint, Vector &vecNearestBuildPoint ) +{ + bool bFoundPoint = false; + + if ( !pTFPlayer ) + return false; + + if ( pTFPlayer->m_Shared.InCond( TF_COND_SAPPED ) ) + return false; + + if ( pTFPlayer->m_Shared.IsInvulnerable() ) + return false; + + if ( pTFPlayer->m_Shared.InCond( TF_COND_PHASE ) ) + return false; + + Vector vecOrigin = pTFPlayer->GetAbsOrigin(); + QAngle vecAngles = pTFPlayer->GetAbsAngles(); + float flDist = ( vecOrigin - pBuilder->GetAbsOrigin() ).Length(); + if ( flDist <= 160.f ) + { + flNearestPoint = flDist; + vecNearestBuildPoint = vecOrigin; + m_hBuiltOnEntity = (CBaseEntity *)pTFPlayer; + + // Set our angles to the buildpoint's angles + SetAbsAngles( vecAngles ); + + bFoundPoint = true; + } + + return bFoundPoint; +} + +/* +class CTraceFilterIgnorePlayers : public CTraceFilterSimple +{ +public: + // It does have a base, but we'll never network anything below here.. + DECLARE_CLASS( CTraceFilterIgnorePlayers, CTraceFilterSimple ); + + CTraceFilterIgnorePlayers( const IHandleEntity *passentity, int collisionGroup ) + : CTraceFilterSimple( passentity, collisionGroup ) + { + } + + virtual bool ShouldHitEntity( IHandleEntity *pServerEntity, int contentsMask ) + { + CBaseEntity *pEntity = EntityFromEntityHandle( pServerEntity ); + + if ( pEntity->IsPlayer() ) + { + return false; + } + + return true; + } +}; + +//----------------------------------------------------------------------------- +// Purpose: Test around this build position to make sure it does not block a path +//----------------------------------------------------------------------------- +bool CBaseObject::TestPositionForPlayerBlock( Vector vecBuildOrigin, CBasePlayer *pPlayer ) +{ + // find out the status of the 8 regions around this position + int i; + bool bNodeVisited[8]; + bool bNodeClear[8]; + + // The first zone that is clear of obstructions + int iFirstClear = -1; + + Vector vHalfPlayerDims = (VEC_HULL_MAX - VEC_HULL_MIN) * 0.5f; + + Vector vBuildDims = m_vecBuildMaxs - m_vecBuildMins; + Vector vHalfBuildDims = vBuildDims * 0.5; + + + // the locations of the 8 test positions + // boxes are adjacent to the object box and are at least as large as + // a player to ensure that a player can pass this location + + // 0 1 2 + // 7 X 3 + // 6 5 4 + + static int iPositions[8][2] = + { + { -1, -1 }, + { 0, -1 }, + { 1, -1 }, + { 1, 0 }, + { 1, 1 }, + { 0, 1 }, + { -1, 1 }, + { -1, 0 } + }; + + CTraceFilterIgnorePlayers traceFilter( this, COLLISION_GROUP_NONE ); + + for ( i=0;i<8;i++ ) + { + // mark them all as unvisited + bNodeVisited[i] = false; + + Vector vecTest = vecBuildOrigin; + vecTest.x += ( iPositions[i][0] * ( vHalfBuildDims.x + vHalfPlayerDims.x ) ); + vecTest.y += ( iPositions[i][1] * ( vHalfBuildDims.y + vHalfPlayerDims.y ) ); + + trace_t trace; + UTIL_TraceHull( vecTest, vecTest, VEC_HULL_MIN, VEC_HULL_MAX, MASK_SOLID_BRUSHONLY, &traceFilter, &trace ); + + bNodeClear[i] = ( trace.fraction == 1 && trace.allsolid != 1 && (trace.startsolid != 1) ); + + // NDebugOverlay::Box( vecTest, VEC_HULL_MIN, VEC_HULL_MAX, bNodeClear[i] ? 0 : 255, bNodeClear[i] ? 255 : 0, 0, 20, 0.1 ); + + // Store off the first clear location + if ( iFirstClear < 0 && bNodeClear[i] ) + { + iFirstClear = i; + } + } + + if ( iFirstClear < 0 ) + { + // no clear space + return false; + } + + // visit all nodes that are adjacent + RecursiveTestBuildSpace( iFirstClear, bNodeClear, bNodeVisited ); + + // if we still have unvisited nodes, return false + // unvisited nodes means that one or more nodes was unreachable from our start position + // ie, two places the player might want to traverse but would not be able to if we built here + for ( i=0;i<8;i++ ) + { + if ( bNodeVisited[i] == false && bNodeClear[i] == true ) + { + return false; + } + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Test around the build position, one quadrant at a time +//----------------------------------------------------------------------------- +void CBaseObject::RecursiveTestBuildSpace( int iNode, bool *bNodeClear, bool *bNodeVisited ) +{ + // if the node is visited already + if ( bNodeVisited[iNode] == true ) + return; + + // if the test node is blocked + if ( bNodeClear[iNode] == false ) + return; + + bNodeVisited[iNode] = true; + + int iLeftNode = iNode - 1; + if ( iLeftNode < 0 ) + iLeftNode = 7; + + RecursiveTestBuildSpace( iLeftNode, bNodeClear, bNodeVisited ); + + int iRightNode = ( iNode + 1 ) % 8; + + RecursiveTestBuildSpace( iRightNode, bNodeClear, bNodeVisited ); +} +*/ + +//----------------------------------------------------------------------------- +// Purpose: Move the placement model to the current position. Return false if it's an invalid position +//----------------------------------------------------------------------------- +bool CBaseObject::UpdatePlacement( void ) +{ + if ( MustBeBuiltOnAttachmentPoint() ) + { + return UpdateAttachmentPlacement(); + } + + // Finds bsp-valid place for building to be built + // Checks for validity, nearby to other entities, in line of sight + m_bPlacementOK = IsPlacementPosValid(); + + Teleport( &m_vecBuildOrigin, &GetLocalAngles(), NULL ); + + return m_bPlacementOK; +} + +//----------------------------------------------------------------------------- +// Purpose: See if we should be snapping to a build position +//----------------------------------------------------------------------------- +bool CBaseObject::FindSnapToBuildPos( CBaseObject *pObjectOverride ) +{ + if ( !MustBeBuiltOnAttachmentPoint() ) + return false; + + CTFPlayer *pPlayer = GetOwner(); + + if ( !pPlayer ) + { + return false; + } + + bool bSnappedToPoint = false; + bool bShouldAttachToParent = false; + + Vector vecNearestBuildPoint = vec3_origin; + + // See if there are any nearby build positions to snap to + float flNearestPoint = 9999; + int i; + + bool bHostileAttachment = IsHostileUpgrade(); + int iMyTeam = GetTeamNumber(); + + if ( !pObjectOverride ) + { + int nTeamCount = TFTeamMgr()->GetTeamCount(); + for ( int iTeam = FIRST_GAME_TEAM; iTeam < nTeamCount; ++iTeam ) + { + // Hostile attachments look for enemy objects only + if ( bHostileAttachment ) + { + if ( iTeam == iMyTeam ) + { + continue; + } + } + // Friendly attachments look for friendly objects only + else if ( iTeam != iMyTeam ) + { + continue; + } + + CTFTeam *pTeam = ( CTFTeam * )GetGlobalTeam( iTeam ); + if ( !pTeam ) + continue; + + // See if we're allowed to build on Robots + if ( TFGameRules() && TFGameRules()->GameModeUsesMiniBosses() && + GetType() == OBJ_ATTACHMENT_SAPPER && !pPlayer->IsBot() ) + { + CUtlVector< CTFPlayer * > playerVector; + CollectPlayers( &playerVector, pPlayer->GetOpposingTFTeam()->GetTeamNumber(), COLLECT_ONLY_LIVING_PLAYERS ); + FOR_EACH_VEC( playerVector, i ) + { + if ( !playerVector[i]->IsBot() ) + continue; + + if ( FindBuildPointOnPlayer( playerVector[i], pPlayer, flNearestPoint, vecNearestBuildPoint ) ) + { + bSnappedToPoint = true; + bShouldAttachToParent = true; + } + } + } + + // look for nearby buildpoints on other objects + for ( i = 0; i < pTeam->GetNumObjects(); i++ ) + { + CBaseObject *pObject = pTeam->GetObject(i); + Assert( pObject ); + if ( pObject && !pObject->IsPlacing() ) + { + if ( FindNearestBuildPoint( pObject, pPlayer, flNearestPoint, vecNearestBuildPoint ) ) + { + bSnappedToPoint = true; + bShouldAttachToParent = true; + } + } + } + } + } + else + { + if ( !pObjectOverride->IsPlacing() ) + { + if ( FindNearestBuildPoint( pObjectOverride, pPlayer, flNearestPoint, vecNearestBuildPoint, true ) ) + { + bSnappedToPoint = true; + bShouldAttachToParent = true; + } + } + } + + if ( !bSnappedToPoint ) + { + AddEffects( EF_NODRAW ); + } + else + { + RemoveEffects( EF_NODRAW ); + + if ( bShouldAttachToParent ) + { + AttachObjectToObject( m_hBuiltOnEntity.Get(), m_iBuiltOnPoint, vecNearestBuildPoint ); + } + + m_vecBuildOrigin = vecNearestBuildPoint; + } + + return bSnappedToPoint; +} + +//----------------------------------------------------------------------------- +// Purpose: Are we currently in a buildable position +//----------------------------------------------------------------------------- +bool CBaseObject::IsValidPlacement( void ) const +{ + return m_bPlacementOK; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const char *CBaseObject::GetResponseRulesModifier( void ) +{ + switch ( GetType() ) + { + case OBJ_DISPENSER: return "objtype:dispenser"; break; + case OBJ_TELEPORTER: return "objtype:teleporter"; break; + case OBJ_SENTRYGUN: return "objtype:sentrygun"; break; + case OBJ_ATTACHMENT_SAPPER: return "objtype:sapper"; break; + default: + break; + } + return NULL; +} + +//----------------------------------------------------------------------------- +// 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() ) + { + /* + if ( ((CTFPlayer*)pBuilder)->IsPlayerClass( TF_CLASS_ENGINEER ) ) + { + ((CTFPlayer*)pBuilder)->HintMessage( HINT_ENGINEER_USE_WRENCH_ONOWN ); + } + */ + + if ( !IsCarried() ) + { + if ( !ShouldQuickBuild() ) + { + int iAmountPlayerPaidForMe = ((CTFPlayer*)pBuilder)->StartedBuildingObject( m_iObjectType ); + if ( !iAmountPlayerPaidForMe ) + { + // Player couldn't afford to pay for me, so abort + ClientPrint( (CBasePlayer*)pBuilder, HUD_PRINTCENTER, "Not enough resources.\n" ); + StopPlacement(); + return false; + } + } + + ((CTFPlayer*)pBuilder)->SpeakConceptIfAllowed( MP_CONCEPT_BUILDING_OBJECT, GetResponseRulesModifier() ); + } + else + { + m_bCarried = false; + m_bCarryDeploy = true; + m_flCarryDeployTime = gpGlobals->curtime; + SetActivity( ACT_OBJ_ASSEMBLING ); + + ((CTFPlayer*)pBuilder)->m_flCommentOnCarrying = 0.f; + ((CTFPlayer*)pBuilder)->SpeakConceptIfAllowed( MP_CONCEPT_REDEPLOY_BUILDING, GetResponseRulesModifier() ); + } + } + + // Check to see if we need to add this to a hierarchy. We can just do a simple ray trace from the center as + // the placement code has guarenteed we are in a valid position. + trace_t trace; + UTIL_TraceHull( GetAbsOrigin() + Vector( 0.0f, 0.0f, 2.0f ), GetAbsOrigin() - Vector( 0.0f, 0.0f, 2.0f ), vec3_origin, vec3_origin, MASK_PLAYERSOLID_BRUSHONLY, this, COLLISION_GROUP_PLAYER_MOVEMENT, &trace ); + if ( trace.m_pEnt && trace.m_pEnt->IsBSPModel() ) + { + CFuncTrackTrain *pTrain = dynamic_cast<CFuncTrackTrain*>( trace.m_pEnt ); + if ( pTrain ) + { + SetParent( pTrain ); + } + } + + // 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; + if ( m_bCarryDeploy ) + { + SetHealth( m_iHealthOnPickup ); + + IGameEvent * event = gameeventmanager->CreateEvent( "player_dropobject" ); + if ( event ) + { + CTFPlayer *pTFPlayer = ToTFPlayer( pBuilder ); + event->SetInt( "userid", pTFPlayer ? pTFPlayer->GetUserID() : 0 ); + event->SetInt( "object", GetType() ); + event->SetInt( "index", entindex() ); // object entity index + + gameeventmanager->FireEvent( event, true ); // don't send to clients + } + } + else if ( IsMiniBuilding() ) + { + int iHealth = GetMaxHealthForCurrentLevel(); + if ( !IsDisposableBuilding() ) + { + iHealth /= 2.0f; + } + SetHealth( iHealth ); + } + else + { + SetHealth( OBJECT_CONSTRUCTION_STARTINGHEALTH ); + } + m_flPercentageConstructed = 0; + + 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 + if ( !(m_fObjectFlags & OF_DOESNT_HAVE_A_MODEL) ) + { + SpawnControlPanels(); + } + + // Tell the object we've been built on that we exist + if ( IsBuiltOnAttachment() && !m_hBuiltOnEntity->IsPlayer() ) + { + 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(); + m_flConstructionStartTime = gpGlobals->curtime; + + if ( pBuilder && pBuilder->IsPlayer() ) + { + CTFPlayer *pTFBuilder = ToTFPlayer( pBuilder ); + pTFBuilder->FinishedObject( this ); + IGameEvent * event = gameeventmanager->CreateEvent( "player_builtobject" ); + if ( event ) + { + event->SetInt( "userid", pTFBuilder->GetUserID() ); + event->SetInt( "object", ObjectType() ); + event->SetInt( "index", entindex() ); // object entity index + gameeventmanager->FireEvent( event, true ); // don't send to clients + } + } + + m_vecBuildOrigin = GetAbsOrigin(); + + int contents = UTIL_PointContents( m_vecBuildOrigin ); + if ( contents & MASK_WATER ) + { + SetWaterLevel( 3 ); + } + + // instantly play the build anim + DetermineAnimation(); + + if ( IsMiniBuilding() && ( GetType() != OBJ_DISPENSER ) ) + { + // Set the skin after placement mode. + m_nSkin = ( GetTeamNumber() == TF_TEAM_RED ) ? 2 : 3; + } + + if ( ShouldQuickBuild() ) + { + DoQuickBuild(); + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CBaseObject::ShouldBeMiniBuilding( CTFPlayer* pPlayer ) +{ + if ( !pPlayer ) + return false; + + CTFWrench* pWrench = dynamic_cast<CTFWrench*>( pPlayer->Weapon_OwnsThisID( TF_WEAPON_WRENCH ) ); + if ( !pWrench ) + return false; + + if ( TFGameRules()->GameModeUsesUpgrades() ) + { + if ( pPlayer->GetNumObjects( OBJ_SENTRYGUN ) && pPlayer->CanBuild( OBJ_SENTRYGUN ) == CB_CAN_BUILD && !IsCarried() ) + return true; + } + + if ( !pWrench->IsPDQ() ) + return false; + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseObject::MakeMiniBuilding( CTFPlayer* pPlayer ) +{ + if ( !ShouldBeMiniBuilding( pPlayer ) || IsMiniBuilding() ) + return; + + m_bMiniBuilding = true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseObject::MakeDisposableBuilding( CTFPlayer *pPlayer ) +{ + m_bDisposableBuilding = true; +} + +//----------------------------------------------------------------------------- +// Purpose: Continue construction of this object +//----------------------------------------------------------------------------- +void CBaseObject::BuildingThink( void ) +{ + // Continue construction + Construct( (GetMaxHealth() - OBJECT_CONSTRUCTION_STARTINGHEALTH) / m_flTotalConstructionTime * OBJECT_CONSTRUCTION_INTERVAL ); +} + +//----------------------------------------------------------------------------- +// 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; + + OnGoActive(); + + // 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(); + + if ( IsUsingReverseBuild() ) + { + // if we don't have a sapper (but we should!) then set ourselves as the damager + CObjectSapper *pSapper = GetSapper(); + CBaseEntity *pDamager = pSapper ? pSapper : this; + int iCustomDamageType = pSapper ? TF_DMG_CUSTOM_SAPPER_RECORDER_DEATH : 0; + + CTakeDamageInfo info; + info.SetInflictor( pDamager ); + info.SetAttacker( pDamager ); + info.SetDamageForce( vec3_origin ); + info.SetDamagePosition( GetAbsOrigin() ); + info.SetDamage( 0 ); + info.SetDamageType( DMG_CRUSH ); + info.SetDamageCustom( iCustomDamageType ); + Killed( info ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Objects store health in hacky ways +//----------------------------------------------------------------------------- +void CBaseObject::SetHealth( float flHealth ) +{ + if ( m_bCarryDeploy && (flHealth>m_iHealthOnPickup) ) + { + // If we are re-deploying after being carried we shouldn't gain more health than we had + // on pickup until the deploy process is finished. + flHealth = m_iHealthOnPickup; + } + + bool changed = m_flHealth != flHealth; + + m_flHealth = flHealth; + m_iHealth = ceil(m_flHealth); + + + /* + // If we a pose parameter, set the pose parameter to reflect our health + 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: Override base traceattack to prevent visible effects from team members shooting me +//----------------------------------------------------------------------------- +void CBaseObject::TraceAttack( const CTakeDamageInfo &inputInfo, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator ) +{ + // Prevent team damage here so blood doesn't appear + if ( inputInfo.GetAttacker() ) + { + if ( InSameTeam(inputInfo.GetAttacker()) ) + { + // Pass Damage to enemy attachments + int iNumObjects = GetNumObjectsOnMe(); + for ( int iPoint=iNumObjects-1;iPoint >= 0; --iPoint ) + { + CBaseObject *pObject = GetBuildPointObject( iPoint ); + + if ( pObject && pObject->IsHostileUpgrade() ) + { + pObject->TraceAttack(inputInfo, vecDir, ptr, pAccumulator ); + } + } + return; + } + } + + SpawnBlood( ptr->endpos, vecDir, BloodColor(), inputInfo.GetDamage() ); + + AddMultiDamage( inputInfo, 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; + int idx; + + // Build the unique columns: + for( 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: Return the first non-hostile object build on this object +//----------------------------------------------------------------------------- +CBaseEntity *CBaseObject::GetFirstFriendlyObjectOnMe( void ) +{ + CBaseObject *pFirstObject = NULL; + + IHasBuildPoints *pBPInterface = dynamic_cast<IHasBuildPoints*>(this); + int iNumObjects = pBPInterface->GetNumObjectsOnMe(); + for ( int iPoint=0;iPoint<iNumObjects;iPoint++ ) + { + CBaseObject *pObject = GetBuildPointObject( iPoint ); + + if ( pObject && !pObject->IsHostileUpgrade() ) + { + pFirstObject = pObject; + break; + } + } + + return pFirstObject; +} + +//----------------------------------------------------------------------------- +// Purpose: Pass the specified amount of damage through to any objects I have built on me +//----------------------------------------------------------------------------- +bool CBaseObject::PassDamageOntoChildren( const CTakeDamageInfo &info, float *flDamageLeftOver ) +{ + 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 = GetFirstFriendlyObjectOnMe(); + 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() ) + { + const 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 = GetFirstFriendlyObjectOnMe(); + } + + *flDamageLeftOver = flDamage; + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CBaseObject::OnTakeDamage( const CTakeDamageInfo &info ) +{ + if ( !IsAlive() ) + return info.GetDamage(); + + if ( m_takedamage == DAMAGE_NO ) + return 0; + + if ( IsPlacing() ) + return 0; + + // Check teams + if ( info.GetAttacker() ) + { + if ( InSameTeam(info.GetAttacker()) ) + return 0; + + if ( TFGameRules() && TFGameRules()->IsTruceActive() ) + { + // players cannot damage buildings while a truce is active + if ( info.GetAttacker()->IsPlayer() && info.GetAttacker()->IsTruceValidForEnt() && ( ( info.GetAttacker()->GetTeamNumber() == TF_TEAM_RED ) || ( info.GetAttacker()->GetTeamNumber() == TF_TEAM_BLUE ) ) ) + return 0; + } + } + + m_AchievementData.AddDamagerToHistory( info.GetAttacker() ); + if ( info.GetAttacker()->IsPlayer() ) + { + ToTFPlayer( info.GetAttacker() )->m_AchievementData.AddTargetToHistory( this ); + } + + IHasBuildPoints *pBPInterface = dynamic_cast<IHasBuildPoints*>(this); + + float flDamage = info.GetDamage(); + + // Buildings are resistant to plasma damage. + if ( info.GetDamageCustom() == TF_DMG_CUSTOM_PLASMA ) + { + flDamage *= 0.2f; + } + + // Charged plasma damage disables buildings for a short time. + if ( info.GetDamageCustom() == TF_DMG_CUSTOM_PLASMA_CHARGED ) + { + flDamage *= 0.2f; + m_flPlasmaDisableTime = gpGlobals->curtime + PLASMA_DISABLE_TIME; + m_bPlasmaDisable = true; + UpdateDisabledState(); + } + + // 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(); + } + + + CTFWeaponBase *pWeapon = dynamic_cast<CTFWeaponBase *>(info.GetWeapon()); + if ( pWeapon ) + { +#ifdef STAGING_ONLY + // Attacker has building disabling properties + float flDisablingAttack = 0.0f; + CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pWeapon, flDisablingAttack, disable_buildings_on_hit ); + if ( flDisablingAttack ) + { + // do not override if existing time is longer + m_flPlasmaDisableTime = Max( gpGlobals->curtime + flDisablingAttack, m_flPlasmaDisableTime ); + m_bPlasmaDisable = true; + UpdateDisabledState(); + } +#endif // STAGING_ONLY + + // Apply attributes that increase damage vs buildings + CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pWeapon, flDamage, mult_dmg_vs_buildings ); + CTFPlayer *pAttacker = ToTFPlayer( info.GetAttacker() ); + if ( pAttacker ) + { + pWeapon->ApplyOnHitAttributes( NULL, pAttacker, info ); + } + } + + if ( TFGameRules()->IsPowerupMode() ) + { + CTFPlayer *pAttacker = ToTFPlayer( info.GetAttacker() ); + if ( pAttacker ) + { + if ( pAttacker->m_Shared.GetCarryingRuneType() == RUNE_STRENGTH || pAttacker->m_Shared.InCond( TF_COND_RUNE_IMBALANCE ) ) + { + flDamage *= 2.f; + } + + if ( pAttacker->m_Shared.GetCarryingRuneType() == RUNE_KNOCKOUT ) + { + flDamage *= 4.f; + } + + if ( pAttacker->m_Shared.GetCarryingRuneType() == RUNE_VAMPIRE ) + { + int iModHealthOnHit = flDamage; + + if ( iModHealthOnHit > 0 ) + { + pAttacker->TakeHealth( iModHealthOnHit, DMG_GENERIC ); + } + } + } + } + + bool bFriendlyObjectsAttached = false; + int iNumObjects = pBPInterface->GetNumObjectsOnMe(); + for ( int iPoint=0;iPoint<iNumObjects;iPoint++ ) + { + CBaseObject *pObject = GetBuildPointObject( iPoint ); + + if ( !pObject || pObject->IsHostileUpgrade() ) + { + continue; + } + + bFriendlyObjectsAttached = true; + break; + } + + // Don't look, Tom Bui! + static struct + { + bool operator()( const float flHealth, const float flDamage ) const + { + return ( ( flHealth - flDamage ) < 1 ); + } + } IsDamageFatal; + + // Only track actual damage - not overkill + m_AchievementData.AddDamageEventToHistory( info.GetAttacker(), ( IsDamageFatal( m_flHealth, flDamage ) ) ? m_flHealth : flDamage ); + + // if we cannot die + if ( m_bCannotDie && IsDamageFatal( m_flHealth, flDamage ) ) + { + flDamage = m_flHealth - 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 = ( bFriendlyObjectsAttached ) && IsDamageFatal( m_flHealth, flDamage ); + 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 ) + { + m_iLifetimeDamage += floor( MIN( flDamage, m_flHealth ) ); + if ( m_iLifetimeDamage > tf_obj_damage_tank_achievement_amount.GetInt() && GetBuilder() ) + { + GetBuilder()->AwardAchievement( ACHIEVEMENT_TF_ENGINEER_TANK_DAMAGE ); + } + + // Recheck our death possibility, because our objects may have all been blown off us by now + bWillDieButCant = ( bFriendlyObjectsAttached ) && IsDamageFatal( m_flHealth, flDamage ); + if ( !bWillDieButCant ) + { + // Reduce health + SetHealth( m_flHealth - flDamage ); + } + } + + m_OnDamaged.FireOutput(info.GetAttacker(), this); + + 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( info ); + + // Tell our builder to speak about it + if ( m_hBuilder ) + { + m_hBuilder->SpeakConceptIfAllowed( MP_CONCEPT_LOST_OBJECT, GetResponseRulesModifier() ); + } + } + + const char* szInflictor = "unknown"; + if( info.GetInflictor() ) + szInflictor = (char*)info.GetInflictor()->GetClassname(); + + ReportDamage( szInflictor, GetClassname(), flDamage, GetHealth(), GetMaxHealth() ); + + IGameEvent *event = gameeventmanager->CreateEvent( "npc_hurt" ); + if ( event ) + { + event->SetInt( "entindex", entindex() ); + event->SetInt( "health", Max( 0, (int)GetHealth() ) ); + event->SetInt( "damageamount", flDamage ); + event->SetBool( "crit", ( info.GetDamageType() & DMG_CRITICAL ) ? true : false ); + + CTFPlayer *pTFAttacker = ToTFPlayer( info.GetAttacker() ); + if ( pTFAttacker ) + { + event->SetInt( "attacker_player", pTFAttacker->GetUserID() ); + + if ( pTFAttacker->GetActiveTFWeapon() ) + { + event->SetInt( "weaponid", pTFAttacker->GetActiveTFWeapon()->GetWeaponID() ); + } + else + { + event->SetInt( "weaponid", 0 ); + } + } + else + { + // hurt by world + event->SetInt( "attacker_player", 0 ); + event->SetInt( "weaponid", 0 ); + } + + gameeventmanager->FireEvent( event ); + } + + return flDamage; +} + +//----------------------------------------------------------------------------- +// Purpose: Repair / Help-Construct this object the specified amount +//----------------------------------------------------------------------------- +bool CBaseObject::Construct( float flHealth ) +{ + // Multiply it by the repair rate + flHealth *= GetConstructionMultiplier(); + if ( !flHealth ) + return false; + + if ( IsBuilding() ) + { + // Reduce the construction time by the correct amount for the health passed in + float flConstructionTime = flHealth / ((GetMaxHealth() - OBJECT_CONSTRUCTION_STARTINGHEALTH) / m_flTotalConstructionTime); + if ( flConstructionTime < 0.0f ) + { + flConstructionTime *= -1.0f; + } + + m_flConstructionTimeLeft = MAX( 0, m_flConstructionTimeLeft - flConstructionTime); + m_flConstructionTimeLeft = clamp( m_flConstructionTimeLeft, 0.0f, m_flTotalConstructionTime ); + + m_flPercentageConstructed = m_flConstructionTimeLeft / m_flTotalConstructionTime; + + if ( flHealth >= 0.0f ) + { + // Only do this if we're not reversing construction + m_flPercentageConstructed = 1.0f - m_flPercentageConstructed; + } + m_flPercentageConstructed = clamp( (float) m_flPercentageConstructed, 0.0f, 1.0f ); + + // Increase health (unless it's a mini-building, which start at max health) + // Minibuildings build health at a reduced rate + // Staging_engy + { + SetHealth( MIN( GetMaxHealth(), m_flHealth + (IsMiniBuilding() ? (flHealth * 0.5f) : 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(), MAX( 1, m_flHealth + flHealth ) ) ); + + m_OnRepaired.FireOutput( this, this); + + // Return true if we're fully healed now + if ( GetHealth() == GetMaxHealth() ) + return true; + } + + return false; +} + +//---------------------------------------------------------------------------------------------------------------------------------------- +void CBaseObject::OnConstructionHit( CTFPlayer *pPlayer, CTFWrench *pWrench, Vector hitLoc ) +{ + // Get the player index + int iPlayerIndex = pPlayer->entindex(); + + // The time the repair is going to expire + float flRepairExpireTime = gpGlobals->curtime + 1.0; + + // Update or Add the expire time to the list + int index = m_ConstructorList.Find( iPlayerIndex ); + if ( index == m_ConstructorList.InvalidIndex() ) + { + index = m_ConstructorList.Insert( iPlayerIndex ); + m_ConstructorList[index].flValue = pWrench->GetConstructionValue(); + } + + m_ConstructorList[index].flHitTime = flRepairExpireTime; + + // Play a construction hit effect. + CPVSFilter filter( hitLoc ); + TE_TFParticleEffect( filter, 0.0f, "nutsnbolts_build", hitLoc, QAngle(0,0,0) ); +} + +//---------------------------------------------------------------------------------------------------------------------------------------- +float CBaseObject::GetConstructionMultiplier( void ) +{ + if ( IsUsingReverseBuild() ) + return -1.0f; + + float flMultiplier = 1.0; + + // expire all the old + int i = m_ConstructorList.LastInorder(); + while ( i != m_ConstructorList.InvalidIndex() ) + { + int iThis = i; + i = m_ConstructorList.PrevInorder( i ); + if ( m_ConstructorList[iThis].flHitTime < gpGlobals->curtime ) + { + m_ConstructorList.RemoveAt( iThis ); + } + else + { + // STAGING_ENGY + // each Player adds a fixed amount of speed boost + // Carry deploy hits add more + flMultiplier += ( m_ConstructorList[iThis].flValue ); + } + } + + // See if we have any attributes that want to modify our build rate + CTFPlayer* pBuilder = GetOwner(); + if( pBuilder ) + { + flMultiplier += pBuilder->GetObjectBuildSpeedMultiplier( ObjectType(), m_bCarryDeploy ); + } + + return flMultiplier; +} + +//----------------------------------------------------------------------------- +// Purpose: Object is exploding because it was killed or detonate +//----------------------------------------------------------------------------- +void CBaseObject::Explode( void ) +{ + const char *pExplodeSound = GetObjectInfo( ObjectType() )->m_pExplodeSound; + + if ( pExplodeSound && Q_strlen(pExplodeSound) > 0 ) + { + EmitSound( pExplodeSound ); + } + + const char *pExplodeEffect = GetObjectInfo( ObjectType() )->m_pExplosionParticleEffect; + if ( pExplodeEffect && pExplodeEffect[0] != '\0' ) + { + // Send to everyone - we're inside prediction for the engy who hit this off, but we + // don't predict that the hit will kill this object. + CDisablePredictionFiltering disabler; + + Vector origin = GetAbsOrigin(); + QAngle up(-90,0,0); + + CPVSFilter filter( origin ); + TE_TFParticleEffect( filter, 0.0f, pExplodeEffect, origin, up ); + } + + // create some delicious, metal filled gibs + CreateObjectGibs(); +} + +void CBaseObject::CreateObjectGibs( void ) +{ + if ( m_aGibs.Count() <= 0 ) + { + return; + } + + const CObjectInfo *pObjectInfo = GetObjectInfo( ObjectType() ); + + // grant some percentage of the cost to build if number of metal to drop is not specified + const float flMetalCostPercentage = 0.5f; + const int nTotalMetal = pObjectInfo->m_iMetalToDropInGibs == 0 ? pObjectInfo->m_Cost * flMetalCostPercentage : pObjectInfo->m_iMetalToDropInGibs; + + + int nMetalPerGib = nTotalMetal / m_aGibs.Count(); + int nLeftOver = nTotalMetal % m_aGibs.Count(); + + if ( IsMiniBuilding() ) + { + // STAGING_ENGY + nMetalPerGib = 0; + nLeftOver = 0; + } + + int i; + for ( i=0; i<m_aGibs.Count(); i++ ) + { + // make sure we drop all metal include left over from int math + CreateAmmoPack( m_aGibs[i].modelName, i == 0 ? nMetalPerGib + nLeftOver : nMetalPerGib ); + } +} + +CTFAmmoPack* CBaseObject::CreateAmmoPack( const char *pchModel, int nMetal ) +{ + CTFAmmoPack *pAmmoPack = CTFAmmoPack::Create( GetAbsOrigin(), GetAbsAngles(), this, pchModel ); + Assert( pAmmoPack ); + if ( pAmmoPack ) + { + pAmmoPack->ActivateWhenAtRest(); + + // Fill up the ammo pack. + pAmmoPack->GiveAmmo( nMetal, TF_AMMO_METAL ); + + // Calculate the initial impulse on the weapon. + Vector vecImpulse( random->RandomFloat( -0.5, 0.5 ), random->RandomFloat( -0.5, 0.5 ), random->RandomFloat( 0.75, 1.25 ) ); + VectorNormalize( vecImpulse ); + vecImpulse *= random->RandomFloat( tf_obj_gib_velocity_min.GetFloat(), tf_obj_gib_velocity_max.GetFloat() ); + + // Cap the impulse. + float flSpeed = vecImpulse.Length(); + if ( flSpeed > tf_obj_gib_maxspeed.GetFloat() ) + { + VectorScale( vecImpulse, tf_obj_gib_maxspeed.GetFloat() / flSpeed, vecImpulse ); + } + + if ( pAmmoPack->VPhysicsGetObject() ) + { + // We can probably remove this when the mass on the weapons is correct! + //pAmmoPack->VPhysicsGetObject()->SetMass( 25.0f ); + AngularImpulse angImpulse( 0, random->RandomFloat( 0, 100 ), 0 ); + pAmmoPack->VPhysicsGetObject()->SetVelocityInstantaneous( &vecImpulse, &angImpulse ); + } + + pAmmoPack->SetInitialVelocity( vecImpulse ); + + pAmmoPack->m_nSkin = ( GetTeamNumber() == TF_TEAM_RED ) ? 0 : 1; + + // Give the ammo pack some health, so that trains can destroy it. + pAmmoPack->SetCollisionGroup( COLLISION_GROUP_DEBRIS ); + pAmmoPack->m_takedamage = DAMAGE_YES; + pAmmoPack->SetHealth( 900 ); + pAmmoPack->m_bObjGib = true; + + if ( IsMiniBuilding() ) + { + pAmmoPack->SetModelScale( 0.6f ); + } + } + + return pAmmoPack; +} + +//----------------------------------------------------------------------------- +// Purpose: Object has been blown up. Drop resource chunks upto the value of my max health. +//----------------------------------------------------------------------------- +void CBaseObject::Killed( const CTakeDamageInfo &info ) +{ + m_bDying = true; + + // Find the killer & the scorer + CBaseEntity *pInflictor = info.GetInflictor(); + CBaseEntity *pKiller = info.GetAttacker(); + CTFPlayer *pScorer = ToTFPlayer( TFGameRules()->GetDeathScorer( pKiller, pInflictor, this ) ); + CTFPlayer *pAssister = NULL; + + // If we are being carried, + + // if this object has a sapper on it, and was not killed by the sapper (killed by damage other than crush, since sapper does crushing damage), + // award an assist to the owner of the sapper since it probably contributed to destroying this object + CObjectSapper *pSapper = GetSapper(); + if ( pSapper && !( DMG_CRUSH & info.GetDamageType() ) && !m_bPlasmaDisable ) + { + // give an assist to the sapper's owner + pAssister = pSapper->GetOwner(); + if ( pAssister ) + { + CTF_GameStats.Event_AssistDestroyBuilding( pAssister, this ); + + // Also increment the SapBuildings grind achievement + pAssister->AwardAchievement( ACHIEVEMENT_TF_SPY_SAPPER_GRIND ); + } + } + else if ( pScorer ) + { + // If a player is healing the scorer, give that player credit for the assist + CTFPlayer *pHealer = ToTFPlayer( static_cast<CBaseEntity *>( pScorer->m_Shared.GetFirstHealer() ) ); + // Must be a medic to receive a healing assist, otherwise engineers get credit for assists from dispensers doing healing. + // Also don't give an assist for healing if the inflictor was a sentry gun, otherwise medics healing engineers get assists for the engineer's sentry kills. + if ( pHealer && ( pHealer->GetPlayerClass()->GetClassIndex() == TF_CLASS_MEDIC ) ) + { + pAssister = pHealer; + } + } + + // Don't do anything if we were detonated or dismantled + if ( pScorer && pInflictor != this ) + { + IGameEvent * event = gameeventmanager->CreateEvent( "object_destroyed" ); + + // Work out what killed the player, and send a message to all clients about it + int iWeaponID; + const char *killer_weapon_name = TFGameRules()->GetKillingWeaponName( info, NULL, &iWeaponID ); + const char *killer_weapon_log_name = killer_weapon_name; + + CTFPlayer *pTFPlayer = GetOwner(); + + CTFWeaponBase *pWeapon = dynamic_cast< CTFWeaponBase * >( pScorer->Weapon_OwnsThisID( iWeaponID ) ); + if ( pWeapon ) + { + CEconItemView *pItem = pWeapon->GetAttributeContainer()->GetItem(); + + if ( pItem ) + { + if ( pItem->GetStaticData()->GetIconClassname() ) + { + killer_weapon_name = pItem->GetStaticData()->GetIconClassname(); + } + + if ( pItem->GetStaticData()->GetLogClassname() ) + { + killer_weapon_log_name = pItem->GetStaticData()->GetLogClassname(); + } + } + } + + if ( event ) + { + if ( pTFPlayer ) + { + event->SetInt( "userid", pTFPlayer->GetUserID() ); + } + if ( pAssister && ( pAssister != pScorer ) ) + { + event->SetInt( "assister", pAssister->GetUserID() ); + } + + event->SetInt( "attacker", pScorer->GetUserID() ); // attacker + event->SetString( "weapon", killer_weapon_name ); + event->SetString( "weapon_logclassname", killer_weapon_log_name ); + event->SetInt( "weaponid", iWeaponID ); + event->SetInt( "priority", 6 ); // HLTV event priority, not transmitted + event->SetInt( "objecttype", GetType() ); + event->SetInt( "index", entindex() ); // object entity index + event->SetBool( "was_building", m_bBuilding ); + + gameeventmanager->FireEvent( event ); + } + + CTF_GameStats.Event_PlayerDestroyedBuilding( pScorer, this ); + pScorer->Event_KilledOther(this, info); + + // Also track stats for strange sappers. + if ( pSapper ) + { + CTFPlayer *pSapperOwner = pSapper->GetOwner(); + Assert( pSapperOwner ); + + if ( pSapperOwner ) + { + EconEntity_OnOwnerKillEaterEvent( dynamic_cast<CEconEntity *>( pSapperOwner->GetEntityForLoadoutSlot( LOADOUT_POSITION_BUILDING ) ), + pSapperOwner, + GetOwner(), + kKillEaterEvent_BuildingSapped ); + } + } + + // Check for Demo achievement: + // Kill an Engineer building that you can't see with a direct hit from a Grenade Launcher + + if ( pScorer && pScorer->IsPlayerClass( TF_CLASS_DEMOMAN) ) + { + if ( pScorer->GetActiveTFWeapon() && ( pScorer->GetActiveTFWeapon()->GetWeaponID() == TF_WEAPON_GRENADELAUNCHER) ) + { + if ( pInflictor && pInflictor->IsPlayer() == false ) + { + CTFGrenadePipebombProjectile *pBaseGrenade = dynamic_cast< CTFGrenadePipebombProjectile* >( pInflictor ); + if ( pBaseGrenade && pBaseGrenade->m_bTouched == false ) + { + if ( pScorer->FVisible( this ) == false ) + { + pScorer->AwardAchievement( ACHIEVEMENT_TF_DEMOMAN_KILL_BUILDING_DIRECT_HIT ); + } + } + } + } + } + } + else + { + IGameEvent * event = gameeventmanager->CreateEvent( "object_detonated" ); + + if ( event ) + { + CTFPlayer *pTFPlayer = GetOwner(); + if ( pTFPlayer ) + { + event->SetInt( "userid", pTFPlayer->GetUserID() ); + } + event->SetInt( "objecttype", GetType() ); // object type + event->SetInt( "index", entindex() ); // object entity index + + gameeventmanager->FireEvent( event ); + } + } + + // Don't create gibs if it reversed back to a toolbox + if ( IsUsingReverseBuild() && ( DMG_CRUSH & info.GetDamageType() ) != 0 ) + { + CTFAmmoPack *pAmmoPack = CreateAmmoPack( "models/weapons/w_models/w_toolbox.mdl", GetObjectInfo( ObjectType() )->m_iMetalToDropInGibs ); + if ( pAmmoPack ) + { + pAmmoPack->SetBodygroup( 1, 1 ); + } + + CObjectSapper *pSapper = GetSapper(); + if ( pSapper ) + { + pSapper->Explode(); + } + } + else + { + // Do an explosion. + Explode(); + } + + // Stats tracking for strange items. + EconEntity_OnOwnerKillEaterEvent( dynamic_cast<CEconEntity *>( info.GetWeapon() ), + pScorer, + GetOwner(), + kKillEaterEvent_BuildingDestroyed ); + + UTIL_Remove( this ); +} + +//----------------------------------------------------------------------------- +// Purpose: Indicates this NPC's place in the relationship table. +//----------------------------------------------------------------------------- +Class_T CBaseObject::Classify( void ) +{ + return CLASS_NONE; +} + +//----------------------------------------------------------------------------- +// Purpose: Get the type of this object +//----------------------------------------------------------------------------- +int CBaseObject::GetType() const +{ + return m_iObjectType; +} + +//----------------------------------------------------------------------------- +// Purpose: Get the builder of this object +//----------------------------------------------------------------------------- +CTFPlayer *CBaseObject::GetBuilder( void ) const +{ + return m_hBuilder; +} + +//----------------------------------------------------------------------------- +// Purpose: Return true if the Owning CTeam should clean this object up automatically +//----------------------------------------------------------------------------- +bool CBaseObject::ShouldAutoRemove( void ) +{ + 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 + CreateBuildPoints(); +} + +CObjectSapper* CBaseObject::GetSapper( void ) +{ + if ( !HasSapper() ) + return NULL; + + return dynamic_cast< CObjectSapper* >( FirstMoveChild() ); +} + +//----------------------------------------------------------------------------- +// Purpose: Return true if I have at least 1 sapper on me +//----------------------------------------------------------------------------- +bool CBaseObject::HasSapper( void ) +{ + return m_bHasSapper; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CBaseObject::IsPlasmaDisabled( void ) +{ + return m_bPlasmaDisable; +} + +//----------------------------------------------------------------------------- +void CBaseObject::OnAddSapper( void ) +{ + // Assume we can only build 1 sapper per object + Assert( m_bHasSapper == false ); + + m_bHasSapper = true; + + CTFPlayer *pPlayer = GetBuilder(); + + if ( pPlayer ) + { + //pPlayer->HintMessage( HINT_OBJECT_YOUR_OBJECT_SAPPED, true ); + pPlayer->SpeakConceptIfAllowed( MP_CONCEPT_SPY_SAPPER, GetResponseRulesModifier() ); + } + + UpdateDisabledState(); +} + +//----------------------------------------------------------------------------- +void CBaseObject::OnRemoveSapper( void ) +{ + m_bHasSapper = false; + UpdateDisabledState(); +} + +//----------------------------------------------------------------------------- +int CBaseObject::GetUpgradeMetalRequired() +{ + return GetObjectInfo( GetType() )->m_UpgradeCost; +} + +//----------------------------------------------------------------------------- +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: 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); + + CTakeDamageInfo info( inputdata.pCaller, inputdata.pActivator, vec3_origin, GetAbsOrigin(), iDamage, DMG_GENERIC ); + Killed( info ); + } +} + +//----------------------------------------------------------------------------- +// 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: +// Input : &inputdata - +//----------------------------------------------------------------------------- +void CBaseObject::InputSetBuilder( inputdata_t &inputdata ) +{ + CTFPlayer *pPlayer = ToTFPlayer( inputdata.pActivator ); + if ( GetBuilder() == NULL && pPlayer != NULL ) + { + SetBuilder( pPlayer ); + ChangeTeam( pPlayer->GetTeamNumber() ); + pPlayer->AddObject( this ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &inputdata - +//----------------------------------------------------------------------------- +void CBaseObject::InputShow( inputdata_t &inputdata ) +{ + RemoveEffects( EF_NODRAW ); + UpdateDisabledState(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &inputdata - +//----------------------------------------------------------------------------- +void CBaseObject::InputHide( inputdata_t &inputdata ) +{ + AddEffects( EF_NODRAW ); + SetDisabled( true ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : +// Output : did this wrench hit do any work on the object? +//----------------------------------------------------------------------------- +bool CBaseObject::InputWrenchHit( CTFPlayer *pPlayer, CTFWrench *pWrench, Vector hitLoc ) +{ + Assert( pPlayer ); + if ( !pPlayer ) + return false; + + bool bDidWork = false; + + if ( HasSapper() ) + { + // do damage to any attached buildings + CTakeDamageInfo info( pPlayer, pPlayer, pWrench, WRENCH_DMG_VS_SAPPER, DMG_CLUB, TF_DMG_WRENCH_FIX ); + + IHasBuildPoints *pBPInterface = dynamic_cast< IHasBuildPoints * >( this ); + int iNumObjects = pBPInterface->GetNumObjectsOnMe(); + for ( int iPoint=0;iPoint<iNumObjects;iPoint++ ) + { + CBaseObject *pObject = GetBuildPointObject( iPoint ); + + if ( pObject && pObject->IsHostileUpgrade() ) + { + int iBeforeHealth = pObject->GetHealth(); + + pObject->TakeDamage( info ); + + // This should always be true + if ( iBeforeHealth != pObject->GetHealth() ) + { + bDidWork = true; + Assert( bDidWork ); + } + } + } + } + else if ( IsUpgrading() ) + { + bDidWork = OnWrenchHit( pPlayer, pWrench, hitLoc ); +// bDidWork = false; + } + else if ( IsBuilding() ) + { + OnConstructionHit( pPlayer, pWrench, hitLoc ); + bDidWork = true; + } + else + { + // upgrade, refill, repair damage + bDidWork = OnWrenchHit( pPlayer, pWrench, hitLoc ); + } + + if ( bDidWork ) + { + pPlayer->m_AchievementData.AddTargetToHistory( this ); + } + + return bDidWork; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CBaseObject::OnWrenchHit( CTFPlayer *pPlayer, CTFWrench *pWrench, Vector hitLoc ) +{ + bool bRepairHit = false; + bool bUpgradeHit = false; + + bRepairHit = Command_Repair( pPlayer, pWrench->GetRepairValue() ); + + if ( !bRepairHit ) + { + bUpgradeHit = CheckUpgradeOnHit( pPlayer ); + } + + DoWrenchHitEffect( hitLoc, bRepairHit, bUpgradeHit ); + + return bUpgradeHit || bRepairHit; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseObject::DoWrenchHitEffect( Vector hitLoc, bool bRepairHit, bool bUpgradeHit ) +{ + if ( bRepairHit ) + { + // Play a repair hit effect. + CPVSFilter filter( hitLoc ); + TE_TFParticleEffect( filter, 0.0f, "nutsnbolts_repair", hitLoc, QAngle(0,0,0) ); + } + else if ( bUpgradeHit ) + { + // Play an upgrade hit effect. + CPVSFilter filter( hitLoc ); + TE_TFParticleEffect( filter, 0.0f, "nutsnbolts_upgrade", hitLoc, QAngle(0,0,0) ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CBaseObject::CheckUpgradeOnHit( CTFPlayer *pPlayer ) +{ + if ( !CanBeUpgraded() ) + return false; + + if ( m_bCarryDeploy ) + return false; + + if ( CanBeUpgraded( pPlayer ) ) + { + int iPlayerMetal = pPlayer->GetAmmoCount( TF_AMMO_METAL ); + int nMaxToAdd = tf_obj_upgrade_per_hit.GetInt(); + if ( TFGameRules() && TFGameRules()->IsPowerupMode() ) + { + nMaxToAdd *= 2; + } + CALL_ATTRIB_HOOK_INT_ON_OTHER( pPlayer, nMaxToAdd, upgrade_rate_mod ); + int iAmountToAdd = MIN( nMaxToAdd, iPlayerMetal ); + + if ( iAmountToAdd > ( m_iUpgradeMetalRequired - m_iUpgradeMetal ) ) + iAmountToAdd = ( m_iUpgradeMetalRequired - m_iUpgradeMetal ); + + if ( tf_cheapobjects.GetBool() == false && !ShouldQuickBuild() ) + { + pPlayer->RemoveAmmo( iAmountToAdd, TF_AMMO_METAL ); + } + + // testing quick builds for engineers in Raid mode + if ( TFGameRules() && !TFGameRules()->IsPVEModeControlled( pPlayer ) ) + { +#ifdef TF_RAID_MODE + if ( TFGameRules()->IsRaidMode() ) + { + iAmountToAdd = 200; + } +#endif + + if ( TFGameRules()->GameModeUsesUpgrades() && TFGameRules()->IsQuickBuildTime() ) + { + iAmountToAdd = 200; + } + } + + m_iUpgradeMetal += iAmountToAdd; + + bool bDidWork = false; + if ( iAmountToAdd > 0 ) + { + bDidWork = true; + } + + if ( m_iUpgradeMetal >= m_iUpgradeMetalRequired ) + { + IGameEvent * event = gameeventmanager->CreateEvent( "player_upgradedobject" ); + if ( event ) + { + event->SetInt( "userid", pPlayer->GetUserID() ); + event->SetInt( "object", ObjectType() ); + event->SetInt( "index", entindex() ); + event->SetBool( "isbuilder", pPlayer == GetBuilder() ); + + gameeventmanager->FireEvent( event ); + } + + StartUpgrading(); + m_iUpgradeMetal = 0; + } + + return bDidWork; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CBaseObject::CanBeUpgraded( CTFPlayer *pPlayer ) +{ + // Already upgrading + if ( IsUpgrading() ) + return false; + + if ( IsMiniBuilding() || IsDisposableBuilding() ) + return false; + + // only engineers + if ( !ClassCanBuild( pPlayer->GetPlayerClass()->GetClassIndex(), GetType() ) ) + return false; + + // max upgraded + if ( m_iUpgradeLevel >= OBJ_MAX_UPGRADE_LEVEL ) + return false; + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Separated so it can be triggered by wrench hit or by vgui screen +//----------------------------------------------------------------------------- +bool CBaseObject::Command_Repair( CTFPlayer *pActivator, float flRepairMod ) +{ + if ( !CanBeRepaired() ) + return false; + + const float flRepairToMetalRatio = 3.0f; // Amount Repaired per metal + float flTargetHeal = 100.0f * flRepairMod; + int iAmountToHeal = MIN( flTargetHeal, GetMaxHealth() - RoundFloatToInt( GetHealth() ) ); + + // repair the building + int iRepairCost = ceil( (float)( iAmountToHeal ) / flRepairToMetalRatio ); + + TRACE_OBJECT( UTIL_VarArgs( "%0.2f CBaseObject::Command_Repair ( %f / %d ) - cost = %d\n", gpGlobals->curtime, + GetHealth(), + GetMaxHealth(), + iRepairCost ) ); + + if ( iRepairCost > 0 ) + { + if ( iRepairCost > pActivator->GetBuildResources() ) + { + iRepairCost = pActivator->GetBuildResources(); + } + + pActivator->RemoveBuildResources( iRepairCost ); + + float flNewHealth = MIN( GetMaxHealth(), m_flHealth + ( iRepairCost * flRepairToMetalRatio ) ); + + if ( pActivator != GetBuilder() ) + { + float flAmountRepaired = flNewHealth - m_flHealth; + pActivator->AwardAchievement( ACHIEVEMENT_TF_ENGINEER_REPAIR_TEAM_GRIND, floor( flAmountRepaired ) ); + } + + SetHealth( flNewHealth ); + + return ( iRepairCost > 0 ); + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Upgrade this object a single level +//----------------------------------------------------------------------------- +void CBaseObject::StartUpgrading( void ) +{ + // Increase level + m_iUpgradeLevel++; + + if ( GetHighestUpgradeLevel() < m_iUpgradeLevel ) + { + m_iHighestUpgradeLevel = m_iUpgradeLevel; + } + + // more health + if ( !m_bCarryDeploy && !IsUsingReverseBuild() ) + { + int iMaxHealth = GetMaxHealthForCurrentLevel(); + SetMaxHealth( iMaxHealth ); + SetHealth( iMaxHealth ); + } + + const char *pUpgradeSound = GetObjectInfo( ObjectType() )->m_pUpgradeSound; + if ( pUpgradeSound && *pUpgradeSound ) + { + EmitSound( pUpgradeSound ); + } + + if ( ( !m_bWasMapPlaced || ( m_iUpgradeLevel > (m_nDefaultUpgradeLevel+1) ) ) ) + { + SetActivity( ACT_OBJ_UPGRADING ); + + float flConstructionTime = ( ShouldQuickBuild() ? 0 : GetObjectInfo( ObjectType() )->m_flUpgradeDuration ); + float flReverseBuildingConstructionSpeed = GetReversesBuildingConstructionSpeed(); + flConstructionTime /= ( flReverseBuildingConstructionSpeed == 0.0f ? 1.0f : flReverseBuildingConstructionSpeed ); + + m_flUpgradeCompleteTime = gpGlobals->curtime + flConstructionTime; + } + else + { + m_flUpgradeCompleteTime = gpGlobals->curtime; //asap + } + + RemoveAllGestures(); + + if ( TFGameRules() && TFGameRules()->IsInTraining() && + TFGameRules()->GetTrainingModeLogic() && + GetOwner() && GetOwner()->IsFakeClient() == false ) + { + TFGameRules()->GetTrainingModeLogic()->OnPlayerUpgradedBuilding( GetOwner(), this ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseObject::FinishUpgrading( void ) +{ + const char *pUpgradeSound = GetObjectInfo( ObjectType() )->m_pUpgradeSound; + if ( pUpgradeSound && *pUpgradeSound ) + { + EmitSound( pUpgradeSound ); + } + + if ( IsUsingReverseBuild() ) + { + m_iUpgradeLevel--; + DoReverseBuild(); + } +} + +//----------------------------------------------------------------------------- +// Playing the upgrade animation +//----------------------------------------------------------------------------- +void CBaseObject::UpgradeThink( void ) +{ + if ( gpGlobals->curtime > m_flUpgradeCompleteTime ) + { + FinishUpgrading(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Handles health upgrade for objects we've already built +//----------------------------------------------------------------------------- +void CBaseObject::ApplyHealthUpgrade( void ) +{ + CTFPlayer *pTFPlayer = GetOwner(); + if ( !pTFPlayer ) + return; + + int iHealth = GetMaxHealthForCurrentLevel(); + SetMaxHealth( iHealth ); + SetHealth( iHealth ); + + //DevMsg( "%i\n", GetMaxHealth() ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseObject::PlayStartupAnimation( void ) +{ + SetActivity( ACT_OBJ_STARTUP ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseObject::DetermineAnimation( void ) +{ + Activity desiredActivity = m_Activity; + + switch ( m_Activity ) + { + default: + { + if ( IsUpgrading() ) + { + desiredActivity = ACT_OBJ_UPGRADING; + } + else if ( IsPlacing() ) + { + /* + if (1 || m_bPlacementOK ) + { + desiredActivity = ACT_OBJ_PLACING; + } + else + { + desiredActivity = ACT_OBJ_IDLE; + } + */ + } + else if ( IsBuilding() ) + { + desiredActivity = ACT_OBJ_ASSEMBLING; + } + 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( CBaseEntity *pEntity, int iPoint, Vector &vecOrigin ) +{ + m_hBuiltOnEntity = pEntity; + m_iBuiltOnPoint = iPoint; + + int iAttachment = 0; + + if ( m_hBuiltOnEntity.Get() ) + { + CTFPlayer *pTFPlayer = ToTFPlayer( pEntity ); + if ( pTFPlayer ) + { + iAttachment = pTFPlayer->LookupAttachment( "head" ); + } + else + { + CBaseAnimating *pAnimate = dynamic_cast<CBaseAnimating*>( pEntity ); + if ( pAnimate ) + { + iAttachment = pAnimate->LookupBone( "weapon_bone" ); + if ( iAttachment >= 1 ) + { + FollowEntity( m_hBuiltOnEntity.Get() ); + } + } + + // Parent ourselves to the object + IHasBuildPoints *pBPInterface = dynamic_cast<IHasBuildPoints*>( pEntity ); + Assert( pBPInterface ); + if ( pBPInterface ) + { + iAttachment = pBPInterface->GetBuildPointAttachmentIndex( iPoint ); + + // re-link to the build points if the sapper is already built + if ( !( IsPlacing() || IsBuilding() ) ) + { + pBPInterface->SetObjectOnBuildPoint( m_iBuiltOnPoint, this ); + } + } + } + + 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(); + } + + SetupAttachedVersion(); + } + + Assert( m_hBuiltOnEntity.Get() == 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() ); + if ( !(pObject->m_fObjectFlags & OF_DOESNT_HAVE_A_MODEL) ) + { + pObject->SpawnControlPanels(); + } + pObject->SetHealth( pObject->GetMaxHealth() ); + pObject->FinishedBuilding(); + pObject->AttachObjectToObject( this, iPoint, vecOrigin ); + //pObject->m_fObjectFlags |= OF_CANNOT_BE_DISMANTLED; + + 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(); +} + +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 ); + } +} + +int CBaseObject::DrawDebugTextOverlays(void) +{ + int text_offset = BaseClass::DrawDebugTextOverlays(); + + if (m_debugOverlays & OVERLAY_TEXT_BIT) + { + char tempstr[512]; + + Q_snprintf( tempstr, sizeof( tempstr ),"Health: %f / %d ( %.1f )", GetHealth(), GetMaxHealth(), (float)GetHealth() / (float)GetMaxHealth() ); + EntityText(text_offset,tempstr,0); + text_offset++; + + CTFPlayer *pBuilder = GetBuilder(); + + Q_snprintf( tempstr, sizeof( tempstr ),"Built by: (%d) %s", + pBuilder ? pBuilder->entindex() : -1, + pBuilder ? pBuilder->GetPlayerName() : "invalid builder" ); + EntityText(text_offset,tempstr,0); + text_offset++; + + if ( IsBuilding() ) + { + Q_snprintf( tempstr, sizeof( tempstr ),"Build Rate: %.1f", GetConstructionMultiplier() ); + EntityText(text_offset,tempstr,0); + text_offset++; + } + } + return text_offset; + +} + +//----------------------------------------------------------------------------- +// Purpose: Change build orientation +//----------------------------------------------------------------------------- +void CBaseObject::RotateBuildAngles( void ) +{ + // rotate the build angles by 90 degrees ( final angle calculated after we network this ) + m_iDesiredBuildRotations++; + m_iDesiredBuildRotations = m_iDesiredBuildRotations % 4; +} + +//----------------------------------------------------------------------------- +// Purpose: called on edge cases to see if we need to change our disabled state +//----------------------------------------------------------------------------- +void CBaseObject::UpdateDisabledState( void ) +{ + const bool bShouldBeEnabled = !m_bHasSapper + && !m_bPlasmaDisable + && (!TFGameRules()->RoundHasBeenWon() || TFGameRules()->GetWinningTeam() == GetTeamNumber()); + + SetDisabled( !bShouldBeEnabled ); +} + +//----------------------------------------------------------------------------- +// Purpose: called when our disabled state changes +//----------------------------------------------------------------------------- +void CBaseObject::SetDisabled( bool bDisabled ) +{ + if ( bDisabled && !m_bDisabled ) + { + OnStartDisabled(); + } + else if ( !bDisabled && m_bDisabled ) + { + OnEndDisabled(); + } + + m_bDisabled = bDisabled; +} + +//----------------------------------------------------------------------------- +void CBaseObject::SetPlasmaDisabled( float flDuration ) +{ + m_bPlasmaDisable = true; + m_flPlasmaDisableTime = gpGlobals->curtime + flDuration; + UpdateDisabledState(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseObject::OnStartDisabled( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseObject::OnEndDisabled( void ) +{ +} + + +//----------------------------------------------------------------------------- +// Purpose: Called when the model changes, find new attachments for the children +//----------------------------------------------------------------------------- +void CBaseObject::ReattachChildren( void ) +{ + // Go through and store the children one by one, then reattach them. We need + // to store them like this because if we didnt and instead went through and + // reattached them as we iterated over them we could get into a state where we have + // children A and B and A has B as a sibling and B has NULL has a sibling, + // but we reattach B first which set's B's sibling to A creating a infinite loop + CUtlVector<CBaseEntity*> vecChildren; + for (CBaseEntity *pChild = FirstMoveChild(); pChild; pChild = pChild->NextMovePeer()) + { + if( vecChildren.Find( pChild ) != vecChildren.InvalidIndex() ) + { + AssertMsg( 0, "Cyclic siblings found when reattaching children!" ); + break; + } + + vecChildren.AddToTail( pChild ); + } + + int iNumBuildPoints = GetNumBuildPoints(); + FOR_EACH_VEC( vecChildren, i ) + { + CBaseObject *pObject = dynamic_cast<CBaseObject *>( vecChildren[i] ); + + if ( !pObject ) + { + continue; + } + + Assert( pObject->GetParent() == this ); + + // get the type + int iObjectType = pObject->GetType(); + + bool bReattached = false; + + Vector vecDummy; + + for ( int j = 0; j < iNumBuildPoints && bReattached == false; j++ ) + { + // Can this object build on this point? + if ( CanBuildObjectOnBuildPoint( j, iObjectType ) ) + { + pObject->AttachObjectToObject( this, j, vecDummy ); + bReattached = true; + } + } + + // if we can't find an attach for the child, remove it and print an error + if ( bReattached == false ) + { + if ( m_bCarried && ( pObject->GetType() == OBJ_ATTACHMENT_SAPPER ) ) + { + pObject->ResetPlacement(); + } + else + { + pObject->DestroyObject(); + Assert( !"Couldn't find attachment point on upgraded object for existing child.\n" ); + } + } + } +} + +void CBaseObject::SetModel( const char *pModel ) +{ + // Skip if we're already the proper model + if ( V_strcmp( GetModelName().ToCStr(), pModel ) == 0 ) + return; + + BaseClass::SetModel( pModel ); + + // Clear out the gib list and create a new one. + m_aGibs.Purge(); + BuildGibList( m_aGibs, GetModelIndex(), 1.0f, COLLISION_GROUP_NONE ); + + CObjectSapper *pSapper = GetSapper(); + if ( pSapper ) + { + pSapper->OnGoActive(); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseObject::Activate( void ) +{ + BaseClass::Activate(); + + InitializeMapPlacedObject(); +} + +//----------------------------------------------------------------------------- +// 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(); + + // Set the skin + m_nSkin = ( GetTeamNumber() == TF_TEAM_RED ) ? 0 : 1; +} + +//----------------------------------------------------------------------------- +// Purpose: Turns the object into one carried by someone. +//----------------------------------------------------------------------------- +void CBaseObject::MakeCarriedObject( CTFPlayer *pCarrier ) +{ + if ( pCarrier ) + { + // Make the object inactive. + m_bCarried = true; + m_bCarryDeploy = false; + pCarrier->m_Shared.SetCarriedObject( this ); + m_iHealthOnPickup = m_iHealth; // If we are damaged, we want to remember how much damage we had sustained. + + // Remove screens. + DestroyScreens(); + + // Mount it to the player. + FollowEntity( pCarrier ); + + IGameEvent * event = gameeventmanager->CreateEvent( "player_carryobject" ); + if ( event ) + { + event->SetInt( "userid", pCarrier->GetUserID() ); + event->SetInt( "object", GetType() ); + event->SetInt( "index", entindex() ); // object entity index + + gameeventmanager->FireEvent( event, true ); // don't send to clients + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Turns the object into one carried by someone. +//----------------------------------------------------------------------------- +void CBaseObject::DropCarriedObject( CTFPlayer* pCarrier ) +{ + m_bCarried = false; + m_bCarryDeploy = false; + + if ( pCarrier ) + { + pCarrier->m_Shared.SetCarriedObject( NULL ); + } + + StopFollowingEntity(); +} + +//----------------------------------------------------------------------------- +// Purpose: Instantly build and upgrade this object +//----------------------------------------------------------------------------- +void CBaseObject::DoQuickBuild( bool bForceMax /* = false */ ) +{ + if ( IsBuilding() ) + { + FinishedBuilding(); + } + + int iTargetLevel = ( ( ( TFGameRules() && TFGameRules()->IsQuickBuildTime() ) || bForceMax ) ? OBJ_MAX_UPGRADE_LEVEL : GetUpgradeLevel() ); + + if ( CanBeUpgraded( GetOwner() ) ) + { + for ( int i = GetUpgradeLevel(); i < iTargetLevel; i++ ) + { + StartUpgrading(); + } + } + else + { + int iMaxHealth = GetMaxHealthForCurrentLevel(); + SetMaxHealth( iMaxHealth ); + SetHealth( iMaxHealth ); + } +} + +//----------------------------------------------------------------------------- +// Builds instantly under certain conditions/modes +//----------------------------------------------------------------------------- +bool CBaseObject::ShouldQuickBuild( void ) +{ + if ( TFGameRules() ) + { + if ( GetType() == OBJ_ATTACHMENT_SAPPER ) + return false; + +#ifdef STAGING_ONLY + if ( GetType() == OBJ_SPY_TRAP ) + return false; +#endif + + if ( TFGameRules()->IsQuickBuildTime() ) + { + return true; + } + + if ( TFGameRules()->IsMannVsMachineMode() ) + { + if ( GetTeamNumber() == TF_TEAM_PVE_INVADERS ) + { + // Engineer bots in MvM deploy pre-built sentries that build up at the normal rate + return m_bForceQuickBuild; + } + + if ( m_bCarryDeploy || TFGameRules()->State_Get() == GR_STATE_BETWEEN_RNDS ) + { + return true; + } + } + } + + return m_bForceQuickBuild; +} + +void CBaseObject::DoReverseBuild( void ) +{ + m_iHighestUpgradeLevel = m_iUpgradeLevel; + m_iUpgradeMetal = 0; + + int iMaxHealth = GetMaxHealthForCurrentLevel(); + SetMaxHealth( iMaxHealth ); + if ( GetHealth() > iMaxHealth ) + { + SetHealth( iMaxHealth ); + } + + if ( m_iUpgradeLevel > 1 ) + { + m_iUpgradeLevel--; + StartUpgrading(); + } + else + { + m_bBuilding = true; + m_bCarryDeploy = false; + m_flTotalConstructionTime = m_flConstructionTimeLeft = GetTotalTime(); + m_flConstructionStartTime = gpGlobals->curtime; + SetStartBuildingModel(); + SetControlPanelsActive( false ); + } +} + +float CBaseObject::GetReversesBuildingConstructionSpeed( void ) +{ + CObjectSapper *pSapper = GetSapper(); + if ( !pSapper ) + return 0.0f; + + return pSapper->GetReversesBuildingConstructionSpeed(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseObject::InputEnable( inputdata_t &inputdata ) +{ + if ( IsDisabled() ) + { + UpdateDisabledState(); + if ( !IsDisabled() ) + { + OnGoActive(); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseObject::InputDisable( inputdata_t &inputdata ) +{ + if ( !IsDisabled() ) + { + SetDisabled( true ); + OnGoInactive(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CBaseObject::GetMaxHealthForCurrentLevel( void ) +{ + int iMaxHealth = IsMiniBuilding() ? GetMiniBuildingStartingHealth() : GetBaseHealth(); + if ( GetOwner() && !m_bDisposableBuilding ) + { + CALL_ATTRIB_HOOK_INT_ON_OTHER( GetOwner(), iMaxHealth, mult_engy_building_health ); + } + + if ( !IsMiniBuilding() && ( GetUpgradeLevel() > 1 ) ) + { + float flMultiplier = pow( UPGRADE_LEVEL_HEALTH_MULTIPLIER, GetUpgradeLevel() - 1 ); + iMaxHealth = (int)( iMaxHealth * flMultiplier ); + } + + return iMaxHealth; +} |