source-engine/game/server/tf2/tf_obj_buff_station.cpp

930 lines
29 KiB
C++
Raw Normal View History

2020-04-22 16:56:21 +00:00
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Medic's portable power generator
//
//=============================================================================//
#include "cbase.h"
#include "tf_obj_buff_station.h"
#include "tf_player.h"
#include "rope.h"
#include "rope_shared.h"
#include "entitylist.h"
#include "VGuiScreen.h"
#include "engine/IEngineSound.h"
#include "tf_team.h"
//=============================================================================
//
// Console Variables
//
static ConVar obj_buff_station_damage_modifier( "obj_buff_station_damage_modifier", "1.5", 0, "Scales the damage a player does while connected to the buff station." );
static ConVar obj_buff_station_heal_rate( "obj_buff_station_heal_rate", "10" );
static ConVar obj_buff_station_range( "obj_buff_station_range", "300" );
static ConVar obj_buff_station_obj_range( "obj_buff_station_obj_range", "800" );
static ConVar obj_buff_station_health( "obj_buff_station_health","100", FCVAR_NONE, "Buff Station health" );
//-----------------------------------------------------------------------------
// Buff Station defines
//-----------------------------------------------------------------------------
#define BUFF_STATION_MINS Vector( -30, -30, 0 )
#define BUFF_STATION_MAXS Vector( 30, 30, 50 )
#define BUFF_STATION_HUMAN_MODEL "models/objects/human_obj_buffstation.mdl"
#define BUFF_STATION_HUMAN_ASSEMBLING_MODEL "models/objects/human_obj_buffstation_build.mdl"
#define BUFF_STATION_ALIEN_MODEL "models/objects/alien_obj_buffstation.mdl"
#define BUFF_STATION_ALIEN_ASSEMBLING_MODEL "models/objects/alien_obj_buffstation_build.mdl"
#define BUFF_STATION_VGUI_SCREEN "screen_obj_buffstation"
#define BUFF_STATION_BOOST_PLAYER_THINK_CONTEXT "BoostPlayerThink"
#define BUFF_STATION_BOOST_OBJECT_THINK_CONTEXT "BoostObjectThink"
#define BUFF_STATION_BOOST_PLAYER_THINK_INTERVAL 0.1f
#define BUFF_STATION_BOOST_OBJECT_THINK_INTERVAL 2.0f
#define BUFF_STATION_BUFF_RANGE ( 600 * 600 )
//=============================================================================
//
// Data Description
//
BEGIN_DATADESC( CObjectBuffStation )
DEFINE_INPUTFUNC( FIELD_VOID, "PlayerSpawned", InputPlayerSpawned ),
DEFINE_INPUTFUNC( FIELD_VOID, "PlayerAttachedToGenerator", InputPlayerAttachedToGenerator ),
DEFINE_INPUTFUNC( FIELD_VOID, "PlayerEnteredVehicle", InputPlayerSpawned ), // NJS: Detach player from buff pack.
END_DATADESC()
//=============================================================================
//
// Server Class
//
IMPLEMENT_SERVERCLASS_ST( CObjectBuffStation, DT_ObjectBuffStation )
SendPropInt( SENDINFO( m_nPlayerCount ), BUFF_STATION_MAX_PLAYER_BITS, SPROP_UNSIGNED ),
SendPropArray( SendPropEHandle( SENDINFO_ARRAY( m_hPlayers ) ), m_hPlayers ),
SendPropInt( SENDINFO( m_nObjectCount ), BUFF_STATION_MAX_OBJECT_BITS, SPROP_UNSIGNED ),
SendPropArray( SendPropEHandle( SENDINFO_ARRAY( m_hObjects ) ), m_hObjects ),
END_SEND_TABLE()
//=============================================================================
//
// Linking and Precache
//
LINK_ENTITY_TO_CLASS( obj_buff_station, CObjectBuffStation );
PRECACHE_REGISTER( obj_buff_station );
// Backwards compatability...
LINK_ENTITY_TO_CLASS( obj_portable_power_generator, CObjectBuffStation );
//-----------------------------------------------------------------------------
// Purpose: Constructor
//-----------------------------------------------------------------------------
CObjectBuffStation::CObjectBuffStation()
{
// Verify networking data.
COMPILE_TIME_ASSERT( BUFF_STATION_MAX_PLAYERS < ( 1 << BUFF_STATION_MAX_PLAYER_BITS ) );
COMPILE_TIME_ASSERT( BUFF_STATION_MAX_PLAYERS >= ( 1 << ( BUFF_STATION_MAX_PLAYER_BITS - 1 ) ) );
COMPILE_TIME_ASSERT( BUFF_STATION_MAX_OBJECTS < ( 1 << BUFF_STATION_MAX_OBJECT_BITS ) );
COMPILE_TIME_ASSERT( BUFF_STATION_MAX_OBJECTS >= ( 1 << ( BUFF_STATION_MAX_OBJECT_BITS - 1 ) ) );
// Uses the client-side animation system.
UseClientSideAnimation();
}
//-----------------------------------------------------------------------------
// Purpose: Spawn
//-----------------------------------------------------------------------------
void CObjectBuffStation::Spawn()
{
// This must be set before calling the base class spawn.
m_iHealth = obj_buff_station_health.GetInt();
BaseClass::Spawn();
SetModel( BUFF_STATION_HUMAN_MODEL );
SetSolid( SOLID_BBOX );
SetType( OBJ_BUFF_STATION );
UTIL_SetSize( this, BUFF_STATION_MINS, BUFF_STATION_MAXS );
m_takedamage = DAMAGE_YES;
// Initialize buff station attachment data.
InitAttachmentData();
// Thinking
SetContextThink( BoostPlayerThink, 1.0f, BUFF_STATION_BOOST_PLAYER_THINK_CONTEXT );
SetContextThink( BoostObjectThink, 2.0f, BUFF_STATION_BOOST_OBJECT_THINK_CONTEXT );
m_bBuilding = false;
}
//-----------------------------------------------------------------------------
// Purpose: Precache model, vgui elements, and sound.
//-----------------------------------------------------------------------------
void CObjectBuffStation::Precache()
{
// Models
PrecacheModel( BUFF_STATION_HUMAN_MODEL );
PrecacheModel( BUFF_STATION_ALIEN_MODEL );
// Build models
PrecacheModel( BUFF_STATION_HUMAN_ASSEMBLING_MODEL );
PrecacheModel( BUFF_STATION_ALIEN_ASSEMBLING_MODEL );
// VGUI Screen
PrecacheVGuiScreen( BUFF_STATION_VGUI_SCREEN );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CObjectBuffStation::SetupTeamModel( void )
{
if ( GetTeamNumber() == TEAM_HUMANS )
{
if ( m_bBuilding )
{
SetModel( BUFF_STATION_HUMAN_ASSEMBLING_MODEL );
}
else
{
SetModel( BUFF_STATION_HUMAN_MODEL );
}
}
else
{
if ( m_bBuilding )
{
SetModel( BUFF_STATION_ALIEN_ASSEMBLING_MODEL );
}
else
{
SetModel( BUFF_STATION_ALIEN_MODEL );
}
}
}
//-----------------------------------------------------------------------------
// Purpose: Gets info about the control panels
//-----------------------------------------------------------------------------
void CObjectBuffStation::GetControlPanelInfo( int nControlPanelIndex, const char *&pPanelName )
{
pPanelName = BUFF_STATION_VGUI_SCREEN;
}
//-----------------------------------------------------------------------------
// Purpose: Remove this object from it's team and mark it for deletion.
//-----------------------------------------------------------------------------
void CObjectBuffStation::DestroyObject( void )
{
// Detach all players.
for ( int iPlayer = 0; iPlayer < BUFF_STATION_MAX_PLAYERS; iPlayer++ )
{
DetachPlayerByIndex( iPlayer );
}
// Detach all objects.
for( int iObject = m_nObjectCount - 1; iObject >= 0; --iObject )
{
DetachObjectByIndex( iObject );
}
// Inform all other buff stations on this team to attempt to power object (cover for this one).
if ( GetTFTeam() )
{
GetTFTeam()->UpdateBuffStations( this, NULL, false );
}
// We shouldn't get any more messages
g_pNotify->ClearEntity( this );
BaseClass::DestroyObject();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CObjectBuffStation::OnGoInactive( void )
{
BaseClass::OnGoInactive();
// Detach all players.
for ( int iPlayer = 0; iPlayer < BUFF_STATION_MAX_PLAYERS; iPlayer++ )
{
CBaseTFPlayer *pPlayer = m_hPlayers[iPlayer].Get();
if ( pPlayer )
{
ClientPrint( pPlayer, HUD_PRINTCENTER, "Lost power to Buff Station!" );
}
DetachPlayerByIndex( iPlayer );
}
// Detach all objects.
for ( int iObject = m_nObjectCount - 1; iObject >= 0; --iObject )
{
DetachObjectByIndex( iObject );
}
}
//-----------------------------------------------------------------------------
// Purpose: Attach to players who touch me
//-----------------------------------------------------------------------------
void CObjectBuffStation::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
{
if ( useType == USE_ON )
{
// See if the activator is a player
if ( !pActivator->IsPlayer() || !InSameTeam( pActivator ) || !pActivator->CanBePoweredUp() )
return;
CBaseTFPlayer *pPlayer = static_cast<CBaseTFPlayer*>(pActivator);
if ( pPlayer )
{
UpdatePlayerAttachment( pPlayer );
}
}
BaseClass::Use( pActivator, pCaller, useType, value );
}
//-----------------------------------------------------------------------------
// Purpose: Handle commands sent from vgui panels on the client
//-----------------------------------------------------------------------------
bool CObjectBuffStation::ClientCommand( CBaseTFPlayer *pPlayer, const char *pCmd, ICommandArguments *pArg )
{
if ( FStrEq( pCmd, "toggle_connect" ) )
{
UpdatePlayerAttachment( pPlayer );
return true;
}
return BaseClass::ClientCommand( pPlayer, pCmd, pArg );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CObjectBuffStation::InitAttachmentData( void )
{
// Initialize the attachment data.
char szAttachName[13];
m_nPlayerCount = 0;
Q_strncpy( szAttachName, "playercable1", 13 );
for ( int iPlayer = 0; iPlayer < BUFF_STATION_MAX_PLAYERS; ++iPlayer )
{
m_hPlayers.Set( iPlayer, NULL );
szAttachName[11] = '1' + iPlayer;
m_aPlayerAttachInfo[iPlayer].m_iAttachPoint = LookupAttachment( szAttachName );
}
m_nObjectCount = 0;
Q_strncpy( szAttachName, "objectcable1", 13 );
for ( int iObject = 0; iObject < BUFF_STATION_MAX_OBJECTS; ++iObject )
{
m_hObjects.Set( iObject, NULL );
szAttachName[11] = '1' + iObject;
m_aObjectAttachInfo[iObject].m_iAttachPoint = LookupAttachment( szAttachName );
}
}
//-----------------------------------------------------------------------------
// Purpose: Create "Buff Station" cable (rope).
//-----------------------------------------------------------------------------
CRopeKeyframe *CObjectBuffStation::CreateRope( CBaseTFPlayer *pPlayer, int iAttachPoint )
{
CRopeKeyframe *pRope = CRopeKeyframe::Create( this, pPlayer, iAttachPoint, 0 );
if ( pRope )
{
pRope->m_RopeLength = obj_buff_station_range.GetFloat();
pRope->m_Slack = 0.0f;
pRope->m_Width = 2;
pRope->m_nSegments = ROPE_MAX_SEGMENTS;
pRope->m_RopeFlags |= ROPE_COLLIDE;
pRope->EnablePlayerWeaponAttach( true );
pRope->ActivateStartDirectionConstraints( true );
if ( GetTeamNumber() == TEAM_HUMANS )
{
pRope->SetMaterial( "cable/human_buffcable.vmt" );
}
else
{
pRope->SetMaterial( "cable/alien_buffcable.vmt" );
}
}
return pRope;
}
//-----------------------------------------------------------------------------
// Purpose: Create "Buff Station" cable (rope).
//-----------------------------------------------------------------------------
CRopeKeyframe *CObjectBuffStation::CreateRope( CBaseObject *pObject, int iAttachPoint, int iObjectAttachPoint )
{
CRopeKeyframe *pRope = CRopeKeyframe::Create( this, pObject, iAttachPoint, iObjectAttachPoint );
if ( pRope )
{
pRope->m_RopeLength = obj_buff_station_obj_range.GetFloat();
pRope->m_Slack = 0.0f;
pRope->m_Width = 2;
pRope->m_nSegments = ROPE_MAX_SEGMENTS;
pRope->m_RopeFlags |= ROPE_COLLIDE;
pRope->EnablePlayerWeaponAttach( true );
pRope->ActivateStartDirectionConstraints( true );
if ( GetTeamNumber() == TEAM_HUMANS )
{
pRope->SetMaterial( "cable/human_buffcable.vmt" );
}
else
{
pRope->SetMaterial( "cable/alien_buffcable.vmt" );
}
}
return pRope;
}
//-----------------------------------------------------------------------------
// Purpose: Is a particular player attached?
//-----------------------------------------------------------------------------
bool CObjectBuffStation::IsPlayerAttached( CBaseTFPlayer *pPlayer )
{
for ( int iPlayer = 0; iPlayer < BUFF_STATION_MAX_PLAYERS; iPlayer++ )
{
if ( m_hPlayers[iPlayer].Get() == pPlayer )
return true;
}
return false;
}
//-----------------------------------------------------------------------------
// Purpose: Is a particular object attached?
//-----------------------------------------------------------------------------
bool CObjectBuffStation::IsObjectAttached( CBaseObject *pObject )
{
for ( int iObject = 0; iObject < m_nObjectCount; ++iObject )
{
if ( m_hObjects[iObject].Get() == pObject )
return true;
}
return false;
}
//-----------------------------------------------------------------------------
// Purpose: Attach the player to the "Buff Station."
//-----------------------------------------------------------------------------
void CObjectBuffStation::AttachPlayer( CBaseTFPlayer *pPlayer )
{
// Player shouldn't already be attached.
Assert( !IsPlayerAttached( pPlayer ) );
// Check to see if the player is alive and on the correct team.
if ( !pPlayer->IsAlive() || !pPlayer->InSameTeam( this ) )
return;
// Check attachment availability.
if ( m_nPlayerCount == BUFF_STATION_MAX_PLAYERS )
{
// Unless the player is the owner he cannot connect.
if ( pPlayer != GetOwner() )
return;
// Kick a non-owning player off.
DetachPlayerByIndex( BUFF_STATION_MAX_PLAYERS - 1 );
}
// This will disconnect the player from other Buff Stations, and keep track of important player events.
g_pNotify->ReportNamedEvent( pPlayer, "PlayerAttachedToGenerator" );
g_pNotify->AddEntity( this, pPlayer );
// Connect player.
// Find the nearest empty slot
int iNearest = BUFF_STATION_MAX_PLAYERS;
float flNearestDist = 9999*9999;
for ( int iPlayer = 0; iPlayer < BUFF_STATION_MAX_PLAYERS; iPlayer++ )
{
if ( !m_hPlayers[iPlayer] )
{
Vector vecPoint;
QAngle angPoint;
GetAttachment( m_aPlayerAttachInfo[iPlayer].m_iAttachPoint, vecPoint, angPoint );
float flDistance = ( vecPoint - pPlayer->GetAbsOrigin() ).LengthSqr();
if ( flDistance < flNearestDist )
{
flNearestDist = flDistance;
iNearest = iPlayer;
}
}
}
Assert( iNearest != BUFF_STATION_MAX_PLAYERS );
m_hPlayers.Set( iNearest, pPlayer );
m_aPlayerAttachInfo[iNearest].m_DamageModifier.SetModifier( obj_buff_station_damage_modifier.GetFloat() );
m_aPlayerAttachInfo[iNearest].m_hRope = CreateRope( pPlayer, m_aPlayerAttachInfo[iNearest].m_iAttachPoint );
m_nPlayerCount++;
// Tell the player to constrain his movement.
pPlayer->ActivateMovementConstraint( this, GetAbsOrigin(), obj_buff_station_range.GetFloat(), 75.0f, 0.15f );
// Update think.
if ( GetNextThink(BUFF_STATION_BOOST_PLAYER_THINK_CONTEXT) > gpGlobals->curtime + BUFF_STATION_BOOST_PLAYER_THINK_INTERVAL )
{
SetNextThink( gpGlobals->curtime + BUFF_STATION_BOOST_PLAYER_THINK_INTERVAL, BUFF_STATION_BOOST_PLAYER_THINK_CONTEXT );
}
}
//-----------------------------------------------------------------------------
// Purpose: Detach the player from the "Buff Station."
//-----------------------------------------------------------------------------
void CObjectBuffStation::DetachPlayer( CBaseTFPlayer *pPlayer )
{
for ( int iPlayer = 0; iPlayer < BUFF_STATION_MAX_PLAYERS; iPlayer++ )
{
if ( m_hPlayers[iPlayer].Get() == pPlayer )
{
DetachPlayerByIndex( iPlayer );
return;
}
}
}
//-----------------------------------------------------------------------------
// Purpose: Detach the player from the "Buff Station."
//-----------------------------------------------------------------------------
void CObjectBuffStation::DetachPlayerByIndex( int nIndex )
{
// Valid index?
Assert( nIndex < BUFF_STATION_MAX_PLAYERS );
// Get the player.
CBaseTFPlayer *pPlayer = m_hPlayers[nIndex].Get();
if ( !pPlayer )
{
m_hPlayers.Set( nIndex, NULL );
return;
}
// Remove the damage modifier.
m_aPlayerAttachInfo[nIndex].m_DamageModifier.RemoveModifier();
// Remove the rope (cable).
if ( m_aPlayerAttachInfo[nIndex].m_hRope.Get() )
{
m_aPlayerAttachInfo[nIndex].m_hRope->DetachPoint( 1 );
m_aPlayerAttachInfo[nIndex].m_hRope->DieAtNextRest();
}
// Unconstrain the player movement.
pPlayer->DeactivateMovementConstraint();
// Keep track of player events.
g_pNotify->RemoveEntity( this, pPlayer );
// Reduce player count.
m_nPlayerCount--;
m_hPlayers.Set( nIndex, NULL );
}
//-----------------------------------------------------------------------------
// Purpose: Attach the object to the "Buff Station."
//-----------------------------------------------------------------------------
void CObjectBuffStation::AttachObject( CBaseObject *pObject, bool bPlacing )
{
// Check to see if the object is already attached.
if ( IsObjectAttached( pObject ) )
return;
// Check to see if the object is on the correct team.
if ( !pObject->InSameTeam( this ) )
return;
// Check to see if the object is already being buffed by another station.
if ( pObject->IsHookedAndBuffed() )
return;
// Check attachment availability.
if ( m_nObjectCount == BUFF_STATION_MAX_OBJECTS )
return;
// Attach cable to object - get the attachment point.
int nObjectAttachPoint = pObject->LookupAttachment( "boostpoint" );
if ( nObjectAttachPoint <= 0 )
nObjectAttachPoint = 1;
// Connect object.
m_hObjects.Set( m_nObjectCount, pObject );
m_aObjectAttachInfo[m_nObjectCount].m_DamageModifier.SetModifier( obj_buff_station_damage_modifier.GetFloat() );
m_aObjectAttachInfo[m_nObjectCount].m_hRope = CreateRope( pObject, m_aObjectAttachInfo[m_nObjectCount].m_iAttachPoint, nObjectAttachPoint );
m_nObjectCount += 1;
// If we're placing, we're pretending to buff objects, but not really powering them
pObject->SetBuffStation( this, bPlacing );
// Update think.
if ( GetNextThink(BUFF_STATION_BOOST_OBJECT_THINK_CONTEXT) > gpGlobals->curtime + BUFF_STATION_BOOST_OBJECT_THINK_INTERVAL )
{
SetNextThink( gpGlobals->curtime + BUFF_STATION_BOOST_OBJECT_THINK_INTERVAL, BUFF_STATION_BOOST_OBJECT_THINK_CONTEXT );
}
}
//-----------------------------------------------------------------------------
// Purpose: Detach the object from the "Buff Station."
//-----------------------------------------------------------------------------
void CObjectBuffStation::DetachObject( CBaseObject *pObject )
{
for ( int iObject = 0; iObject < m_nObjectCount; ++iObject )
{
if ( m_hObjects[iObject].Get() == pObject )
{
DetachObjectByIndex( iObject );
return;
}
}
}
//-----------------------------------------------------------------------------
// Purpose: Detach the object from the "Buff Station."
//-----------------------------------------------------------------------------
void CObjectBuffStation::DetachObjectByIndex( int nIndex )
{
// Valid index?
Assert( nIndex >= 0 );
Assert( nIndex < m_nObjectCount );
// Get the object.
CBaseObject *pObject = m_hObjects[nIndex].Get();
if ( !pObject )
return;
// Remove the damage modifier.
m_aObjectAttachInfo[nIndex].m_DamageModifier.RemoveModifier();
// Remove the rope (cable).
if ( m_aObjectAttachInfo[nIndex].m_hRope.Get() )
{
m_aObjectAttachInfo[nIndex].m_hRope->DetachPoint( 1 );
m_aObjectAttachInfo[nIndex].m_hRope->DieAtNextRest();
}
// Reduce object count.
m_nObjectCount -= 1;
// Set the object as unbuffed.
pObject->SetBuffStation( NULL, false );
// If the detached object wasn't the last object in the list, swap placement.
if ( nIndex != m_nObjectCount )
{
SwapObjectAttachment( nIndex );
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CObjectBuffStation::UpdatePlayerAttachment( CBaseTFPlayer *pPlayer )
{
// Valid player?
if ( !pPlayer )
return;
// Attach/Detach (toggle).
if ( IsPlayerAttached( pPlayer ) )
{
DetachPlayer( pPlayer );
}
else
{
// Check for power, do not attach to unpowered generator.
if ( !IsPowered() )
{
ClientPrint( pPlayer, HUD_PRINTCENTER, "No power source for the Buff Station!" );
}
else
{
AttachPlayer( pPlayer );
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CObjectBuffStation::SwapObjectAttachment( int nIndex )
{
bool bModifierActive = m_aObjectAttachInfo[m_nObjectCount].m_DamageModifier.GetCharacter() != NULL;
m_aObjectAttachInfo[m_nObjectCount].m_DamageModifier.RemoveModifier();
m_hObjects.Set( nIndex, m_hObjects[m_nObjectCount] );
m_aObjectAttachInfo[nIndex] = m_aObjectAttachInfo[m_nObjectCount];
if ( bModifierActive )
{
m_aObjectAttachInfo[nIndex].m_DamageModifier.AddModifierToEntity( m_hObjects[nIndex].Get() );
}
}
//-----------------------------------------------------------------------------
// Purpose: Input handler
//-----------------------------------------------------------------------------
void CObjectBuffStation::InputPlayerSpawned( inputdata_t &inputdata )
{
if ( inputdata.pActivator->IsPlayer() )
{
CBaseTFPlayer *pPlayer = static_cast<CBaseTFPlayer*>( inputdata.pActivator );
if ( IsPlayerAttached( pPlayer ) )
{
DetachPlayer( pPlayer );
}
}
}
//-----------------------------------------------------------------------------
// Purpose: Input handler
//-----------------------------------------------------------------------------
void CObjectBuffStation::InputPlayerAttachedToGenerator( inputdata_t &inputdata )
{
if ( inputdata.pActivator->IsPlayer() )
{
CBaseTFPlayer *pPlayer = static_cast<CBaseTFPlayer*>( inputdata.pActivator );
if ( IsPlayerAttached( pPlayer ) )
{
DetachPlayer( pPlayer );
}
}
}
//-----------------------------------------------------------------------------
// Boost those attached to me as long as I'm not EMPed
//-----------------------------------------------------------------------------
void CObjectBuffStation::BoostPlayerThink( void )
{
// Are we emped?
bool bIsEmped = HasPowerup( POWERUP_EMP );
// Get range (squared = faster test).
float flMaxRangeSq = obj_buff_station_range.GetFloat();
flMaxRangeSq *= flMaxRangeSq;
// Boost all attached players and objects.
for ( int iPlayer = 0; iPlayer < BUFF_STATION_MAX_PLAYERS; iPlayer++ )
{
// Clean up dangling pointers + dead players, subversion, disconnection
CBaseTFPlayer *pPlayer = m_hPlayers[iPlayer].Get();
if ( !pPlayer || !pPlayer->IsAlive() || !InSameTeam( pPlayer ) || !pPlayer->PlayerClass() )
{
DetachPlayerByIndex( iPlayer );
continue;
}
// Check for out of range.
float flDistSq = GetAbsOrigin().DistToSqr( pPlayer->GetAbsOrigin() );
if ( flDistSq > flMaxRangeSq )
{
DetachPlayerByIndex( iPlayer );
continue;
}
bool bBoosted = false;
if ( !bIsEmped )
{
float flHealAmount = obj_buff_station_heal_rate.GetFloat() * BUFF_STATION_BOOST_PLAYER_THINK_INTERVAL;
bBoosted = pPlayer->AttemptToPowerup( POWERUP_BOOST, 0, flHealAmount, this, &m_aPlayerAttachInfo[iPlayer].m_DamageModifier );
}
if ( !bBoosted )
{
m_aPlayerAttachInfo[iPlayer].m_DamageModifier.RemoveModifier();
}
}
// Set next think time.
if ( m_nPlayerCount > 0 )
{
SetNextThink( gpGlobals->curtime + BUFF_STATION_BOOST_PLAYER_THINK_INTERVAL,
BUFF_STATION_BOOST_PLAYER_THINK_CONTEXT );
}
else
{
SetNextThink( gpGlobals->curtime + 1.0f, BUFF_STATION_BOOST_PLAYER_THINK_CONTEXT );
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CObjectBuffStation::BoostObjectThink( void )
{
// Set next boost object think time.
SetNextThink( gpGlobals->curtime + BUFF_STATION_BOOST_OBJECT_THINK_INTERVAL,
BUFF_STATION_BOOST_OBJECT_THINK_CONTEXT );
// If we're emped, placing, or building, we're not ready to powerup
if ( IsPlacing() || IsBuilding() || HasPowerup( POWERUP_EMP ) )
return;
float flMaxRangeSq = obj_buff_station_obj_range.GetFloat();
flMaxRangeSq *= flMaxRangeSq;
// Boost objects.
for ( int iObject = m_nObjectCount; --iObject >= 0; )
{
CBaseObject *pObject = m_hObjects[iObject].Get();
if ( !pObject || !InSameTeam( pObject ) )
{
DetachObjectByIndex( iObject );
continue;
}
// Check for out of range.
float flDistSq = GetAbsOrigin().DistToSqr( pObject->GetAbsOrigin() );
if ( flDistSq > flMaxRangeSq )
{
DetachObjectByIndex( iObject );
continue;
}
// Don't powerup it until it's finished building
if ( pObject->IsPlacing() || pObject->IsBuilding() )
continue;
// Boost it
if ( !pObject->AttemptToPowerup( POWERUP_BOOST, BUFF_STATION_BOOST_OBJECT_THINK_INTERVAL, 0,
this, &m_aObjectAttachInfo[iObject].m_DamageModifier ) )
{
m_aObjectAttachInfo[iObject].m_DamageModifier.RemoveModifier();
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CObjectBuffStation::DeBuffObject( CBaseObject *pObject )
{
DetachObject( pObject );
}
//-----------------------------------------------------------------------------
// Purpose: Find nearby objects and buff them.
//-----------------------------------------------------------------------------
void CObjectBuffStation::BuffNearbyObjects( CBaseObject *pObjectToTarget, bool bPlacing )
{
// ROBIN: Disabled object buffing for now
return;
// Check for a team.
if ( !GetTFTeam() )
return;
// Am I ready to power anything?
if ( IsBuilding() || ( !bPlacing && IsPlacing() ) )
return;
// Am I already full?
if ( m_nObjectCount >= BUFF_STATION_MAX_OBJECTS )
return;
// Do we have a specific target?
if ( pObjectToTarget )
{
if( !pObjectToTarget->CanBeHookedToBuffStation() || pObjectToTarget->GetBuffStation() )
return;
if ( IsWithinBuffRange( pObjectToTarget ) )
{
AttachObject( pObjectToTarget, bPlacing );
}
}
else
{
// Find nearby objects
for ( int iObject = 0; iObject < GetTFTeam()->GetNumObjects(); iObject++ )
{
CBaseObject *pObject = GetTFTeam()->GetObject( iObject );
assert(pObject);
if ( pObject == this || !pObject->CanBeHookedToBuffStation() || pObject->GetBuffStation() )
continue;
// Make sure it's within range
if ( IsWithinBuffRange( pObject ) )
{
AttachObject( pObject, bPlacing );
// Am I now full?
if ( m_nObjectCount >= BUFF_STATION_MAX_OBJECTS )
break;
}
}
}
}
//-----------------------------------------------------------------------------
// Purpose: Update buff connections on the fly while placing
//-----------------------------------------------------------------------------
bool CObjectBuffStation::CalculatePlacement( CBaseTFPlayer *pPlayer )
{
bool bReturn = BaseClass::CalculatePlacement( pPlayer );
// First, disconnect any connections that should break (too far away).
for ( int iObject = m_nObjectCount - 1; iObject >= 0; --iObject )
{
if ( GetBuffedObject( iObject ) )
{
CheckBuffConnection( GetBuffedObject( iObject ) );
}
}
// If we have any spare connections, look for nearby objects to buff
if ( m_nObjectCount < BUFF_STATION_MAX_OBJECTS )
{
BuffNearbyObjects( NULL, true );
}
return bReturn;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CObjectBuffStation::FinishedBuilding( void )
{
BaseClass::FinishedBuilding();
for( int iObject = 0; iObject < m_nObjectCount; ++iObject )
{
if ( GetBuffedObject( iObject ) )
{
GetBuffedObject( iObject )->SetBuffStation( this, false );
}
}
BuffNearbyObjects( NULL, false );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CObjectBuffStation::CheckBuffConnection( CBaseObject *pObject )
{
if ( !pObject->CanBeHookedToBuffStation() )
return;
// Check to see if the object is within the buff range.
if ( IsWithinBuffRange( pObject ) )
return;
// It's obscured, or out of range. Remove it.
DetachObject( pObject );
}
//-----------------------------------------------------------------------------
// Purpose: Return true if this object is powerable
//-----------------------------------------------------------------------------
bool CObjectBuffStation::IsWithinBuffRange( CBaseObject *pObject )
{
if ( ( pObject->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr() < BUFF_STATION_BUFF_RANGE )
{
// Can I see it?
// Ignore things we're attached to
trace_t tr;
CTraceFilterWorldAndPropsOnly buffFilter;
UTIL_TraceLine( WorldSpaceCenter(), pObject->WorldSpaceCenter(), MASK_SOLID_BRUSHONLY, &buffFilter, &tr );
CBaseEntity *pEntity = tr.m_pEnt;
if ( ( tr.fraction == 1.0 ) || ( pEntity == pObject ) )
return true;
}
return false;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : act -
//-----------------------------------------------------------------------------
void CObjectBuffStation::OnActivityChanged( Activity act )
{
BaseClass::OnActivityChanged( act );
switch ( act )
{
case ACT_OBJ_ASSEMBLING:
m_bBuilding = true;
break;
default:
m_bBuilding = false;
break;
}
SetupTeamModel();
}