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