You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
468 lines
14 KiB
468 lines
14 KiB
//========= Copyright Valve Corporation, All rights reserved. ============//
|
|
//
|
|
// Purpose: Entities for use in the Robot Destruction TF2 game mode.
|
|
//
|
|
//=========================================================================//
|
|
|
|
#include "cbase.h"
|
|
#include "tf_logic_player_destruction.h"
|
|
|
|
#ifdef GAME_DLL
|
|
#include "tf_player.h"
|
|
#include "entity_capture_flag.h"
|
|
#include "tf_obj_dispenser.h"
|
|
#include "tf_gamerules.h"
|
|
#else
|
|
#include "c_tf_player.h"
|
|
#endif // GAME_DLL
|
|
|
|
BEGIN_DATADESC( CPlayerDestructionDispenser )
|
|
END_DATADESC()
|
|
|
|
IMPLEMENT_NETWORKCLASS_ALIASED( PlayerDestructionDispenser, DT_PlayerDestructionDispenser )
|
|
LINK_ENTITY_TO_CLASS( pd_dispenser, CPlayerDestructionDispenser );
|
|
|
|
BEGIN_NETWORK_TABLE( CPlayerDestructionDispenser, DT_PlayerDestructionDispenser )
|
|
END_NETWORK_TABLE()
|
|
|
|
#ifdef GAME_DLL
|
|
BEGIN_DATADESC( CTFPlayerDestructionLogic )
|
|
DEFINE_KEYFIELD( m_iszPropModelName, FIELD_STRING, "prop_model_name" ),
|
|
DEFINE_KEYFIELD( m_iszPropDropSound, FIELD_STRING, "prop_drop_sound" ),
|
|
DEFINE_KEYFIELD( m_iszPropPickupSound, FIELD_STRING, "prop_pickup_sound" ),
|
|
DEFINE_KEYFIELD( m_nMinPoints, FIELD_INTEGER, "min_points" ),
|
|
DEFINE_KEYFIELD( m_nPointsPerPlayer, FIELD_INTEGER, "points_per_player" ),
|
|
DEFINE_KEYFIELD( m_nFlagResetDelay, FIELD_INTEGER, "flag_reset_delay" ),
|
|
DEFINE_KEYFIELD( m_nHealDistance, FIELD_INTEGER, "heal_distance" ),
|
|
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "ScoreRedPoints", InputScoreRedPoints ),
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "ScoreBluePoints", InputScoreBluePoints ),
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "EnableMaxScoreUpdating", InputEnableMaxScoreUpdating ),
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "DisableMaxScoreUpdating", InputDisableMaxScoreUpdating ),
|
|
DEFINE_INPUTFUNC( FIELD_INTEGER, "SetCountdownTimer", InputSetCountdownTimer ),
|
|
DEFINE_INPUTFUNC( FIELD_STRING, "SetCountdownImage", InputSetCountdownImage ),
|
|
DEFINE_INPUTFUNC( FIELD_INTEGER, "SetFlagResetDelay", InputSetFlagResetDelay ),
|
|
DEFINE_INPUTFUNC( FIELD_INTEGER, "SetPointsOnPlayerDeath", InputSetPointsOnPlayerDeath ),
|
|
|
|
DEFINE_OUTPUT( m_OnRedScoreChanged, "OnRedScoreChanged" ),
|
|
DEFINE_OUTPUT( m_OnBlueScoreChanged, "OnBlueScoreChanged" ),
|
|
DEFINE_OUTPUT( m_OnCountdownTimerExpired, "OnCountdownTimerExpired" ),
|
|
END_DATADESC()
|
|
#endif
|
|
|
|
LINK_ENTITY_TO_CLASS( tf_logic_player_destruction, CTFPlayerDestructionLogic );
|
|
IMPLEMENT_NETWORKCLASS_ALIASED( TFPlayerDestructionLogic, DT_TFPlayerDestructionLogic )
|
|
|
|
BEGIN_NETWORK_TABLE( CTFPlayerDestructionLogic, DT_TFPlayerDestructionLogic )
|
|
#ifdef CLIENT_DLL
|
|
RecvPropEHandle( RECVINFO( m_hRedTeamLeader ) ),
|
|
RecvPropEHandle( RECVINFO( m_hBlueTeamLeader ) ),
|
|
RecvPropString( RECVINFO( m_iszCountdownImage ) ),
|
|
RecvPropBool( RECVINFO( m_bUsingCountdownImage ) ),
|
|
#else
|
|
SendPropEHandle( SENDINFO( m_hRedTeamLeader ) ),
|
|
SendPropEHandle( SENDINFO( m_hBlueTeamLeader ) ),
|
|
SendPropStringT( SENDINFO( m_iszCountdownImage ) ),
|
|
SendPropBool( SENDINFO( m_bUsingCountdownImage ) ),
|
|
#endif
|
|
END_NETWORK_TABLE()
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
CTFPlayerDestructionLogic::CTFPlayerDestructionLogic()
|
|
{
|
|
#ifdef GAME_DLL
|
|
m_iszPropModelName = MAKE_STRING( "models/flag/flag.mdl" );
|
|
ListenForGameEvent( "player_disconnect" );
|
|
m_bMaxScoreUpdatingAllowed = false;
|
|
m_nFlagResetDelay = 60;
|
|
m_nHealDistance = 450;
|
|
m_nPointsOnPlayerDeath = 1;
|
|
#endif // GAME_DLL
|
|
|
|
m_hRedTeamLeader = NULL;
|
|
m_hBlueTeamLeader = NULL;
|
|
|
|
m_bUsingCountdownImage = false;
|
|
|
|
#ifdef CLIENT_DLL
|
|
m_iszCountdownImage[0] = '\0';
|
|
#else
|
|
m_iszCountdownImage.Set( NULL_STRING );
|
|
#endif
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
CTFPlayerDestructionLogic* CTFPlayerDestructionLogic::GetPlayerDestructionLogic()
|
|
{
|
|
return assert_cast< CTFPlayerDestructionLogic* >( CTFRobotDestructionLogic::GetRobotDestructionLogic() );
|
|
}
|
|
|
|
|
|
#ifdef GAME_DLL
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CTFPlayerDestructionLogic::Precache()
|
|
{
|
|
BaseClass::Precache();
|
|
|
|
PrecacheModel( GetPropModelName() );
|
|
PrecacheScriptSound( STRING( m_iszPropDropSound ) );
|
|
PrecacheScriptSound( STRING( m_iszPropPickupSound ) );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
const char *CTFPlayerDestructionLogic::GetPropModelName() const
|
|
{
|
|
return STRING( m_iszPropModelName );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CTFPlayerDestructionLogic::CalcTeamLeader( int iTeam )
|
|
{
|
|
// team leader's changed team, recalculate team leader for that team
|
|
if ( m_hRedTeamLeader.Get() && m_hRedTeamLeader.Get()->GetTeamNumber() != TF_TEAM_RED )
|
|
{
|
|
m_hRedTeamLeader = NULL;
|
|
CalcTeamLeader( TF_TEAM_RED );
|
|
}
|
|
if ( m_hBlueTeamLeader.Get() && m_hBlueTeamLeader.Get()->GetTeamNumber() != TF_TEAM_BLUE )
|
|
{
|
|
m_hBlueTeamLeader = NULL;
|
|
CalcTeamLeader( TF_TEAM_BLUE );
|
|
}
|
|
|
|
CUtlVector< CTFPlayer * > playerVector;
|
|
CollectPlayers( &playerVector, iTeam, COLLECT_ONLY_LIVING_PLAYERS );
|
|
|
|
CTFPlayer *pTeamLeader = iTeam == TF_TEAM_RED ? m_hRedTeamLeader.Get() : m_hBlueTeamLeader.Get();
|
|
int iCurrentLeadingPoint = 0;
|
|
if ( pTeamLeader && pTeamLeader->HasItem() )
|
|
{
|
|
CCaptureFlag *pFlag = dynamic_cast<CCaptureFlag*>( pTeamLeader->GetItem() );
|
|
if ( pFlag )
|
|
{
|
|
iCurrentLeadingPoint = pFlag->GetPointValue();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// reset team leader
|
|
pTeamLeader = NULL;
|
|
if ( iTeam == TF_TEAM_RED )
|
|
{
|
|
m_hRedTeamLeader = NULL;
|
|
UTIL_Remove( m_hRedDispenser );
|
|
m_hRedDispenser = NULL;
|
|
}
|
|
else
|
|
{
|
|
m_hBlueTeamLeader = NULL;
|
|
UTIL_Remove( m_hBlueDispenser );
|
|
m_hBlueDispenser = NULL;
|
|
}
|
|
}
|
|
|
|
// find new team leader
|
|
CTFPlayer *pNewTeamLeader = NULL;
|
|
FOR_EACH_VEC( playerVector, i )
|
|
{
|
|
CTFPlayer *pPlayer = playerVector[i];
|
|
if ( pPlayer == pTeamLeader )
|
|
continue;
|
|
|
|
// community request from Watergate author to never have a SPY be the team leader
|
|
if ( pPlayer->HasItem() && !pPlayer->IsPlayerClass( TF_CLASS_SPY ) )
|
|
{
|
|
CCaptureFlag *pFlag = dynamic_cast< CCaptureFlag* >( pPlayer->GetItem() );
|
|
if ( pFlag && pFlag->GetPointValue() > iCurrentLeadingPoint )
|
|
{
|
|
iCurrentLeadingPoint = pFlag->GetPointValue();
|
|
pNewTeamLeader = pPlayer;
|
|
}
|
|
}
|
|
}
|
|
|
|
// set new leader
|
|
if ( pNewTeamLeader )
|
|
{
|
|
CObjectDispenser *pDispenser = NULL;
|
|
if ( iTeam == TF_TEAM_RED )
|
|
{
|
|
m_hRedTeamLeader = pNewTeamLeader;
|
|
|
|
if ( !m_hRedDispenser )
|
|
{
|
|
m_hRedDispenser = CreateDispenser( iTeam );
|
|
}
|
|
pDispenser = m_hRedDispenser;
|
|
}
|
|
else
|
|
{
|
|
m_hBlueTeamLeader = pNewTeamLeader;
|
|
|
|
if ( !m_hBlueDispenser )
|
|
{
|
|
m_hBlueDispenser = CreateDispenser( iTeam );
|
|
}
|
|
pDispenser = m_hBlueDispenser;
|
|
}
|
|
|
|
if ( pDispenser )
|
|
{
|
|
pDispenser->SetOwnerEntity( pNewTeamLeader );
|
|
pDispenser->FollowEntity( pNewTeamLeader );
|
|
pDispenser->SetBuilder( pNewTeamLeader );
|
|
}
|
|
}
|
|
}
|
|
|
|
void CTFPlayerDestructionLogic::FireGameEvent( IGameEvent *pEvent )
|
|
{
|
|
const char* pszName = pEvent->GetName();
|
|
if ( FStrEq( pszName, "player_spawn" ) || FStrEq( pszName, "player_disconnect" ) )
|
|
{
|
|
EvaluatePlayerCount();
|
|
return;
|
|
}
|
|
else if( FStrEq( pszName, "teamplay_pre_round_time_left" ) )
|
|
{
|
|
// Eat this event so the RD logic doesn't talk
|
|
return;
|
|
}
|
|
|
|
BaseClass::FireGameEvent( pEvent );
|
|
}
|
|
|
|
void CTFPlayerDestructionLogic::OnRedScoreChanged()
|
|
{
|
|
m_OnRedScoreChanged.Set( (float)m_nRedScore / m_nMaxPoints, this, this );
|
|
}
|
|
|
|
void CTFPlayerDestructionLogic::OnBlueScoreChanged()
|
|
{
|
|
m_OnBlueScoreChanged.Set( (float)m_nBlueScore / m_nMaxPoints, this, this );
|
|
}
|
|
|
|
void CTFPlayerDestructionLogic::EvaluatePlayerCount()
|
|
{
|
|
// Bail if we're not allowed
|
|
if ( !m_bMaxScoreUpdatingAllowed )
|
|
return;
|
|
|
|
CUtlVector< CTFPlayer* > vecAllPlayers;
|
|
CollectPlayers( &vecAllPlayers );
|
|
|
|
m_nMaxPoints = Max( m_nMinPoints, m_nPointsPerPlayer * vecAllPlayers.Count() );
|
|
}
|
|
|
|
void CTFPlayerDestructionLogic::InputScoreRedPoints( inputdata_t& inputdata )
|
|
{
|
|
ScorePoints( TF_TEAM_RED, 1, SCORE_CORES_COLLECTED, NULL );
|
|
}
|
|
|
|
void CTFPlayerDestructionLogic::InputScoreBluePoints( inputdata_t& inputdata )
|
|
{
|
|
ScorePoints( TF_TEAM_BLUE, 1, SCORE_CORES_COLLECTED, NULL );
|
|
}
|
|
|
|
void CTFPlayerDestructionLogic::InputEnableMaxScoreUpdating( inputdata_t& inputdata )
|
|
{
|
|
m_bMaxScoreUpdatingAllowed = true;
|
|
EvaluatePlayerCount();
|
|
}
|
|
|
|
void CTFPlayerDestructionLogic::InputDisableMaxScoreUpdating( inputdata_t& inputdata )
|
|
{
|
|
EvaluatePlayerCount();
|
|
m_bMaxScoreUpdatingAllowed = false;
|
|
}
|
|
|
|
void CTFPlayerDestructionLogic::InputSetCountdownTimer( inputdata_t& inputdata )
|
|
{
|
|
int nTime = inputdata.value.Int();
|
|
|
|
if ( nTime > 0 )
|
|
{
|
|
SetCountdownEndTime( gpGlobals->curtime + nTime );
|
|
SetThink( &CTFPlayerDestructionLogic::CountdownThink );
|
|
SetNextThink( gpGlobals->curtime + 0.05f );
|
|
}
|
|
else
|
|
{
|
|
SetCountdownEndTime( -1.f );
|
|
SetThink( NULL );
|
|
}
|
|
}
|
|
|
|
void CTFPlayerDestructionLogic::CountdownThink( void )
|
|
{
|
|
if ( m_flCountdownEndTime > -1.f )
|
|
{
|
|
// if we're done, just reset the end time
|
|
if ( m_flCountdownEndTime < gpGlobals->curtime )
|
|
{
|
|
m_OnCountdownTimerExpired.FireOutput( this, this );
|
|
m_flCountdownEndTime = -1.f;
|
|
SetThink( NULL );
|
|
return;
|
|
}
|
|
}
|
|
|
|
SetNextThink( gpGlobals->curtime + 0.05f );
|
|
}
|
|
|
|
void CTFPlayerDestructionLogic::InputSetCountdownImage( inputdata_t& inputdata )
|
|
{
|
|
m_bUsingCountdownImage = true;
|
|
m_iszCountdownImage = inputdata.value.StringID();
|
|
}
|
|
|
|
|
|
void CTFPlayerDestructionLogic::InputSetFlagResetDelay( inputdata_t& inputdata )
|
|
{
|
|
int nDelay = inputdata.value.Int();
|
|
if ( nDelay < 0 )
|
|
{
|
|
nDelay = 0;
|
|
}
|
|
|
|
m_nFlagResetDelay = nDelay;
|
|
}
|
|
|
|
void CTFPlayerDestructionLogic::InputSetPointsOnPlayerDeath( inputdata_t& inputdata )
|
|
{
|
|
int nPointsOnPlayerDeath = inputdata.value.Int();
|
|
if ( nPointsOnPlayerDeath < 0 )
|
|
{
|
|
nPointsOnPlayerDeath = 0;
|
|
}
|
|
|
|
m_nPointsOnPlayerDeath = nPointsOnPlayerDeath;
|
|
}
|
|
|
|
CObjectDispenser *CTFPlayerDestructionLogic::CreateDispenser( int iTeam )
|
|
{
|
|
CPlayerDestructionDispenser *pDispenser = static_cast< CPlayerDestructionDispenser* >( CBaseEntity::CreateNoSpawn( "pd_dispenser", vec3_origin, vec3_angle, NULL ) );
|
|
pDispenser->ChangeTeam( iTeam );
|
|
pDispenser->SetObjectFlags( pDispenser->GetObjectFlags() | OF_DOESNT_HAVE_A_MODEL | OF_PLAYER_DESTRUCTION );
|
|
pDispenser->m_iUpgradeLevel = 1;
|
|
DispatchSpawn( pDispenser );
|
|
pDispenser->FinishedBuilding();
|
|
pDispenser->AddEffects( EF_NODRAW );
|
|
pDispenser->DisableAmmoPickupSound();
|
|
pDispenser->DisableGenerateMetalSound();
|
|
pDispenser->m_takedamage = DAMAGE_NO;
|
|
|
|
CBaseEntity *pTouchTrigger = pDispenser->GetTouchTrigger();
|
|
if ( pTouchTrigger )
|
|
{
|
|
pTouchTrigger->FollowEntity( pDispenser );
|
|
}
|
|
|
|
return pDispenser;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CTFPlayerDestructionLogic::PlayPropDropSound( CTFPlayer *pPlayer )
|
|
{
|
|
PlaySound( STRING( m_iszPropDropSound ), pPlayer );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CTFPlayerDestructionLogic::PlayPropPickupSound( CTFPlayer *pPlayer )
|
|
{
|
|
PlaySound( STRING( m_iszPropPickupSound ), pPlayer );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CTFPlayerDestructionLogic::PlaySound( const char *pszSound, CTFPlayer *pPlayer )
|
|
{
|
|
EmitSound_t params;
|
|
params.m_pSoundName = pszSound;
|
|
params.m_flSoundTime = 0;
|
|
params.m_pflSoundDuration = 0;
|
|
params.m_SoundLevel = SNDLVL_70dB;
|
|
CPASFilter filter( pPlayer->GetAbsOrigin() );
|
|
pPlayer->EmitSound( filter, pPlayer->entindex(), params );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CPlayerDestructionDispenser::Spawn( void )
|
|
{
|
|
// This cast is for the benefit of GCC
|
|
m_fObjectFlags |= (int)OF_DOESNT_HAVE_A_MODEL;
|
|
m_takedamage = DAMAGE_NO;
|
|
m_iUpgradeLevel = 1;
|
|
|
|
TFGameRules()->OnDispenserBuilt( this );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Finished building
|
|
//-----------------------------------------------------------------------------
|
|
void CPlayerDestructionDispenser::OnGoActive( void )
|
|
{
|
|
BaseClass::OnGoActive();
|
|
|
|
if ( m_hTouchTrigger )
|
|
{
|
|
m_hTouchTrigger->SetParent( GetParent() );
|
|
}
|
|
|
|
SetModel( "" );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Spawn the vgui control screens on the object
|
|
//-----------------------------------------------------------------------------
|
|
void CPlayerDestructionDispenser::GetControlPanelInfo( int nPanelIndex, const char *&pPanelName )
|
|
{
|
|
// no panels
|
|
return;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CTFPlayerDestructionLogic::TeamWin( int nTeam )
|
|
{
|
|
if ( TFGameRules() )
|
|
{
|
|
TFGameRules()->SetWinningTeam( nTeam, WINREASON_PD_POINTS );
|
|
}
|
|
}
|
|
#endif // GAME_DLL
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
CTFPlayer *CTFPlayerDestructionLogic::GetTeamLeader( int iTeam ) const
|
|
{
|
|
return iTeam == TF_TEAM_RED ? m_hRedTeamLeader.Get() : m_hBlueTeamLeader.Get();
|
|
}
|
|
|