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.
1078 lines
30 KiB
1078 lines
30 KiB
5 years ago
|
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||
|
#include "cbase.h"
|
||
|
|
||
|
#include "tf_weaponbase.h"
|
||
|
#include "eventqueue.h"
|
||
|
#include "particle_parse.h"
|
||
|
#include "tf_tank_boss.h"
|
||
|
#include "tf_objective_resource.h"
|
||
|
#include "engine/IEngineSound.h"
|
||
|
#include "logicrelay.h"
|
||
|
|
||
|
|
||
|
#define TANK_DAMAGE_MODEL_COUNT 4
|
||
|
#define TANK_DEFAULT_HEALTH 1000
|
||
|
|
||
|
|
||
|
static const char *s_TankModel[ TANK_DAMAGE_MODEL_COUNT ] =
|
||
|
{
|
||
|
"models/bots/boss_bot/boss_tank.mdl",
|
||
|
"models/bots/boss_bot/boss_tank_damage1.mdl",
|
||
|
"models/bots/boss_bot/boss_tank_damage2.mdl",
|
||
|
"models/bots/boss_bot/boss_tank_damage3.mdl"
|
||
|
};
|
||
|
|
||
|
static const char *s_TankModelRome[ TANK_DAMAGE_MODEL_COUNT ] =
|
||
|
{
|
||
|
"models/bots/tw2/boss_bot/boss_tank.mdl",
|
||
|
"models/bots/tw2/boss_bot/boss_tank_damage1.mdl",
|
||
|
"models/bots/tw2/boss_bot/boss_tank_damage2.mdl",
|
||
|
"models/bots/tw2/boss_bot/boss_tank_damage3.mdl"
|
||
|
};
|
||
|
|
||
|
#define TANK_LEFT_TRACK_MODEL "models/bots/boss_bot/tank_track_L.mdl"
|
||
|
#define TANK_RIGHT_TRACK_MODEL "models/bots/boss_bot/tank_track_R.mdl"
|
||
|
#define TANK_BOMB "models/bots/boss_bot/bomb_mechanism.mdl"
|
||
|
#define TANK_DESTRUCTION "models/bots/boss_bot/boss_tank_part1_destruction.mdl"
|
||
|
|
||
|
#define TANK_LEFT_TRACK_MODEL_ROME "models/bots/tw2/boss_bot/tank_track_L.mdl"
|
||
|
#define TANK_RIGHT_TRACK_MODEL_ROME "models/bots/tw2/boss_bot/tank_track_R.mdl"
|
||
|
#define TANK_BOMB_ROME "models/bots/boss_bot/bomb_mechanism.mdl"
|
||
|
#define TANK_DESTRUCTION_ROME "models/bots/tw2/boss_bot/boss_tank_part1_destruction.mdl"
|
||
|
|
||
|
|
||
|
#define MVM_DESTROY_TANK_QUICKLY_TIME 25.0f
|
||
|
|
||
|
float CTFTankBoss::m_flLastTankAlert = 0.0f;
|
||
|
|
||
|
|
||
|
class CTFTankDestruction : public CBaseAnimating
|
||
|
{
|
||
|
public:
|
||
|
DECLARE_CLASS( CTFTankDestruction, CBaseAnimating );
|
||
|
DECLARE_DATADESC();
|
||
|
|
||
|
CTFTankDestruction( void );
|
||
|
|
||
|
virtual void Precache( void );
|
||
|
virtual void Spawn( void );
|
||
|
|
||
|
void AnimThink( void );
|
||
|
|
||
|
private:
|
||
|
|
||
|
float m_flVanishTime;
|
||
|
|
||
|
public:
|
||
|
|
||
|
bool m_bIsAtCapturePoint;
|
||
|
int m_nDeathAnimPick;
|
||
|
char m_szDeathPostfix[ 8 ];
|
||
|
};
|
||
|
|
||
|
|
||
|
LINK_ENTITY_TO_CLASS( tank_destruction, CTFTankDestruction );
|
||
|
|
||
|
PRECACHE_REGISTER( tank_destruction );
|
||
|
|
||
|
BEGIN_DATADESC( CTFTankDestruction )
|
||
|
DEFINE_THINKFUNC( AnimThink ),
|
||
|
END_DATADESC();
|
||
|
|
||
|
|
||
|
CTFTankDestruction::CTFTankDestruction( void )
|
||
|
{
|
||
|
m_bIsAtCapturePoint = false;
|
||
|
m_nDeathAnimPick = 0;
|
||
|
m_szDeathPostfix[ 0 ] = '\0';
|
||
|
}
|
||
|
|
||
|
void CTFTankDestruction::Precache( void )
|
||
|
{
|
||
|
PrecacheModel( TANK_DESTRUCTION );
|
||
|
PrecacheModel( TANK_DESTRUCTION_ROME );
|
||
|
|
||
|
PrecacheParticleSystem( "explosionTrail_seeds_mvm" );
|
||
|
PrecacheParticleSystem( "fluidSmokeExpl_ring_mvm" );
|
||
|
|
||
|
PrecacheScriptSound( "MVM.TankExplodes" );
|
||
|
|
||
|
BaseClass::Precache();
|
||
|
}
|
||
|
|
||
|
void CTFTankDestruction::Spawn( void )
|
||
|
{
|
||
|
SetModel( TANK_DESTRUCTION );
|
||
|
SetModelIndexOverride( VISION_MODE_NONE, modelinfo->GetModelIndex( TANK_DESTRUCTION ) );
|
||
|
SetModelIndexOverride( VISION_MODE_ROME, modelinfo->GetModelIndex( TANK_DESTRUCTION_ROME ) );
|
||
|
|
||
|
BaseClass::Spawn();
|
||
|
|
||
|
int nDestroySequence = -1;
|
||
|
|
||
|
int nDeathAnimPick = ( m_nDeathAnimPick != 0 ? m_nDeathAnimPick : RandomInt( 1, 3 ) );
|
||
|
|
||
|
if ( m_bIsAtCapturePoint )
|
||
|
{
|
||
|
// Use map specific random
|
||
|
nDestroySequence = LookupSequence( UTIL_VarArgs( "destroy_%s%i%s", gpGlobals->mapname.ToCStr(), nDeathAnimPick, m_szDeathPostfix ) );
|
||
|
}
|
||
|
|
||
|
if ( nDestroySequence == -1 )
|
||
|
{
|
||
|
// Fallback to default random
|
||
|
nDestroySequence = LookupSequence( UTIL_VarArgs( "destroy%i", nDeathAnimPick ) );
|
||
|
}
|
||
|
|
||
|
if ( nDestroySequence != -1 )
|
||
|
{
|
||
|
SetSequence( nDestroySequence );
|
||
|
SetPlaybackRate( 1.0f );
|
||
|
SetCycle( 0 );
|
||
|
}
|
||
|
|
||
|
DispatchParticleEffect( "explosionTrail_seeds_mvm", GetAbsOrigin(), GetAbsAngles() );
|
||
|
DispatchParticleEffect( "fluidSmokeExpl_ring_mvm", GetAbsOrigin(), GetAbsAngles() );
|
||
|
|
||
|
StopSound( "MVM.TankEngineLoop" );
|
||
|
|
||
|
CBroadcastRecipientFilter filter;
|
||
|
const Vector originVector = GetAbsOrigin();
|
||
|
CBaseEntity::EmitSound( filter, SOUND_FROM_WORLD, "MVM.TankExplodes", &originVector );
|
||
|
CBaseEntity::EmitSound( filter, SOUND_FROM_WORLD, "MVM.TankEnd" );
|
||
|
|
||
|
UTIL_ScreenShake( GetAbsOrigin(), 25.0f, 5.0f, 5.0f, 1000.0f, SHAKE_START );
|
||
|
|
||
|
m_flVanishTime = gpGlobals->curtime + SequenceDuration();
|
||
|
|
||
|
SetThink( &CTFTankDestruction::AnimThink );
|
||
|
SetNextThink( gpGlobals->curtime + 0.1f );
|
||
|
|
||
|
if ( nDestroySequence == -1 )
|
||
|
{
|
||
|
SetThink( &CTFTankDestruction::SUB_FadeOut );
|
||
|
SetNextThink( gpGlobals->curtime );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void CTFTankDestruction::AnimThink( void )
|
||
|
{
|
||
|
if ( gpGlobals->curtime > m_flVanishTime )
|
||
|
{
|
||
|
SetThink( &CTFTankDestruction::SUB_FadeOut );
|
||
|
SetNextThink( gpGlobals->curtime );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
StudioFrameAdvance();
|
||
|
DispatchAnimEvents( this );
|
||
|
SetNextThink( gpGlobals->curtime + 0.1f );
|
||
|
}
|
||
|
|
||
|
|
||
|
LINK_ENTITY_TO_CLASS( tank_boss, CTFTankBoss );
|
||
|
|
||
|
PRECACHE_REGISTER( tank_boss );
|
||
|
|
||
|
IMPLEMENT_SERVERCLASS_ST( CTFTankBoss, DT_TFTankBoss)
|
||
|
//SendPropVector(SENDINFO(m_StartColor), 8, 0, 0, 1),
|
||
|
END_SEND_TABLE()
|
||
|
|
||
|
|
||
|
BEGIN_DATADESC( CTFTankBoss )
|
||
|
|
||
|
DEFINE_THINKFUNC( TankBossThink ),
|
||
|
DEFINE_INPUTFUNC( FIELD_INTEGER, "DestroyIfAtCapturePoint", InputDestroyIfAtCapturePoint ),
|
||
|
DEFINE_INPUTFUNC( FIELD_STRING, "AddCaptureDestroyPostfix", InputAddCaptureDestroyPostfix ),
|
||
|
|
||
|
END_DATADESC()
|
||
|
|
||
|
//-----------------------------------------------------------------------------------------------------
|
||
|
void CMD_TankKill( void )
|
||
|
{
|
||
|
CBasePlayer *player = UTIL_GetCommandClient();
|
||
|
if ( !player )
|
||
|
return;
|
||
|
|
||
|
CBaseEntity *tank = NULL;
|
||
|
while( ( tank = gEntList.FindEntityByClassname( tank, "tank_boss" ) ) != NULL )
|
||
|
{
|
||
|
CTakeDamageInfo info( player, player, 9999999.9f, DMG_CRUSH, TF_DMG_CUSTOM_NONE );
|
||
|
tank->TakeDamage( info );
|
||
|
}
|
||
|
}
|
||
|
static ConCommand tf_mvm_tank_kill( "tf_mvm_tank_kill", CMD_TankKill, "", FCVAR_GAMEDLL | FCVAR_CHEAT );
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------------------------------
|
||
|
void CMD_TankHealth( const CCommand& args )
|
||
|
{
|
||
|
CBasePlayer *player = UTIL_GetCommandClient();
|
||
|
if ( !player )
|
||
|
return;
|
||
|
|
||
|
if ( args.ArgC() < 2 )
|
||
|
{
|
||
|
Msg( "Usage: %s <health to set all active tanks to>\n", args[0] );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
CBaseEntity *tank = NULL;
|
||
|
while( ( tank = gEntList.FindEntityByClassname( tank, "tank_boss" ) ) != NULL )
|
||
|
{
|
||
|
tank->SetMaxHealth( atoi( args[1] ) );
|
||
|
tank->SetHealth( atoi( args[1] ) );
|
||
|
}
|
||
|
}
|
||
|
static ConCommand tf_mvm_tank_health( "tf_mvm_tank_health", CMD_TankHealth, "", FCVAR_GAMEDLL | FCVAR_CHEAT );
|
||
|
|
||
|
|
||
|
//--------------------------------------------------------------------------------------
|
||
|
//--------------------------------------------------------------------------------------
|
||
|
CTFTankBoss::CTFTankBoss()
|
||
|
{
|
||
|
m_goalNode = NULL;
|
||
|
m_body = new CTFTankBossBody( this );
|
||
|
m_exhaustAttachment = -1;
|
||
|
m_isSmoking = false;
|
||
|
m_bIsPlayerKilled = true;
|
||
|
m_bPlayedHalfwayAlert = false;
|
||
|
m_bPlayedNearAlert = false;
|
||
|
m_damageModelIndex = 0;
|
||
|
m_pWaveSpawnPopulator = NULL;
|
||
|
m_nDeathAnimPick = 0;
|
||
|
m_szDeathPostfix[ 0 ] = '\0';
|
||
|
m_flDroppingStart = 0.0f;
|
||
|
m_flSpawnTime = 0.0f;
|
||
|
}
|
||
|
|
||
|
|
||
|
//--------------------------------------------------------------------------------------
|
||
|
CTFTankBoss::~CTFTankBoss()
|
||
|
{
|
||
|
delete m_body;
|
||
|
}
|
||
|
|
||
|
|
||
|
//--------------------------------------------------------------------------------------
|
||
|
void CTFTankBoss::Precache( void )
|
||
|
{
|
||
|
for( int i=0; i<TANK_DAMAGE_MODEL_COUNT; ++i )
|
||
|
{
|
||
|
PrecacheModel( s_TankModel[i] );
|
||
|
PrecacheModel( s_TankModelRome[i] );
|
||
|
}
|
||
|
|
||
|
PrecacheModel( TANK_BOMB );
|
||
|
PrecacheModel( TANK_LEFT_TRACK_MODEL );
|
||
|
PrecacheModel( TANK_RIGHT_TRACK_MODEL );
|
||
|
|
||
|
PrecacheModel( TANK_BOMB_ROME );
|
||
|
PrecacheModel( TANK_LEFT_TRACK_MODEL_ROME );
|
||
|
PrecacheModel( TANK_RIGHT_TRACK_MODEL_ROME );
|
||
|
|
||
|
PrecacheParticleSystem( "smoke_train" );
|
||
|
PrecacheParticleSystem( "bot_impact_light" );
|
||
|
PrecacheParticleSystem( "bot_impact_heavy" );
|
||
|
|
||
|
PrecacheScriptSound( "MVM.TankEngineLoop" );
|
||
|
PrecacheScriptSound( "MVM.TankPing" );
|
||
|
PrecacheScriptSound( "MVM.TankDeploy" );
|
||
|
PrecacheScriptSound( "MVM.TankStart" );
|
||
|
PrecacheScriptSound( "MVM.TankEnd" );
|
||
|
PrecacheScriptSound( "MVM.TankSmash" );
|
||
|
|
||
|
BaseClass::Precache();
|
||
|
}
|
||
|
|
||
|
//--------------------------------------------------------------------------------------
|
||
|
int CTFTankBoss::GetCurrencyValue( void )
|
||
|
{
|
||
|
if ( m_goalNode == NULL && !m_bIsPlayerKilled )
|
||
|
{
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
if ( m_pWaveSpawnPopulator )
|
||
|
{
|
||
|
return m_pWaveSpawnPopulator->GetCurrencyAmountPerDeath();
|
||
|
}
|
||
|
|
||
|
return BaseClass::GetCurrencyValue();
|
||
|
}
|
||
|
|
||
|
void CTFTankBoss::InputDestroyIfAtCapturePoint( inputdata_t &inputdata )
|
||
|
{
|
||
|
m_nDeathAnimPick = inputdata.value.Int();
|
||
|
|
||
|
if ( m_goalNode == NULL )
|
||
|
{
|
||
|
TakeDamage( CTakeDamageInfo( GetContainingEntity(INDEXENT(0)), GetContainingEntity(INDEXENT(0)), 9999999.9f, DMG_CRUSH, TF_DMG_CUSTOM_NONE ) );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void CTFTankBoss::InputAddCaptureDestroyPostfix( inputdata_t &inputdata )
|
||
|
{
|
||
|
V_strncpy( m_szDeathPostfix, inputdata.value.String(), ARRAYSIZE( m_szDeathPostfix ) );
|
||
|
}
|
||
|
|
||
|
|
||
|
//--------------------------------------------------------------------------------------
|
||
|
void CTFTankBoss::Spawn( void )
|
||
|
{
|
||
|
if ( ( !TFGameRules() || !TFGameRules()->IsMannVsMachineMode() ) && GetInitialHealth() == 0 )
|
||
|
{
|
||
|
if ( GetHealth() > 0 )
|
||
|
SetInitialHealth( GetHealth() );
|
||
|
else
|
||
|
SetInitialHealth( TANK_DEFAULT_HEALTH );
|
||
|
}
|
||
|
|
||
|
BaseClass::Spawn();
|
||
|
m_vCollisionMins.Init();
|
||
|
m_vCollisionMaxs.Init();
|
||
|
|
||
|
ChangeTeam( TF_TEAM_PVE_INVADERS );
|
||
|
|
||
|
m_damageModelIndex = 0;
|
||
|
SetModel( s_TankModel[ m_damageModelIndex ] );
|
||
|
SetModelIndexOverride( VISION_MODE_NONE, modelinfo->GetModelIndex( s_TankModel[ m_damageModelIndex ] ) );
|
||
|
SetModelIndexOverride( VISION_MODE_ROME, modelinfo->GetModelIndex( s_TankModelRome[ m_damageModelIndex ] ) );
|
||
|
m_lastHealth = GetMaxHealth();
|
||
|
|
||
|
AddGlowEffect();
|
||
|
|
||
|
m_leftTracks = (CBaseAnimating *)CreateEntityByName( "prop_dynamic" );
|
||
|
if ( m_leftTracks )
|
||
|
{
|
||
|
m_leftTracks->SetModel( TANK_LEFT_TRACK_MODEL );
|
||
|
m_leftTracks->SetModelIndexOverride( VISION_MODE_NONE, modelinfo->GetModelIndex( TANK_LEFT_TRACK_MODEL ) );
|
||
|
m_leftTracks->SetModelIndexOverride( VISION_MODE_ROME, modelinfo->GetModelIndex( TANK_LEFT_TRACK_MODEL_ROME ) );
|
||
|
|
||
|
// bonemerge into our model
|
||
|
m_leftTracks->FollowEntity( this, true );
|
||
|
|
||
|
int animSequence = m_leftTracks->LookupSequence( "forward" );
|
||
|
if ( animSequence )
|
||
|
{
|
||
|
m_leftTracks->SetSequence( animSequence );
|
||
|
m_leftTracks->SetPlaybackRate( 1.0f );
|
||
|
m_leftTracks->SetCycle( 0 );
|
||
|
m_leftTracks->ResetSequenceInfo();
|
||
|
}
|
||
|
|
||
|
m_lastLeftTrackPos = m_leftTracks->GetAbsOrigin();
|
||
|
}
|
||
|
|
||
|
m_rightTracks = (CBaseAnimating *)CreateEntityByName( "prop_dynamic" );
|
||
|
if ( m_rightTracks )
|
||
|
{
|
||
|
m_rightTracks->SetModel( TANK_RIGHT_TRACK_MODEL );
|
||
|
m_rightTracks->SetModelIndexOverride( VISION_MODE_NONE, modelinfo->GetModelIndex( TANK_RIGHT_TRACK_MODEL ) );
|
||
|
m_rightTracks->SetModelIndexOverride( VISION_MODE_ROME, modelinfo->GetModelIndex( TANK_RIGHT_TRACK_MODEL_ROME ) );
|
||
|
|
||
|
// bonemerge into our model
|
||
|
m_rightTracks->FollowEntity( this, true );
|
||
|
|
||
|
int animSequence = m_rightTracks->LookupSequence( "forward" );
|
||
|
if ( animSequence )
|
||
|
{
|
||
|
m_rightTracks->SetSequence( animSequence );
|
||
|
m_rightTracks->SetPlaybackRate( 1.0f );
|
||
|
m_rightTracks->SetCycle( 0 );
|
||
|
m_rightTracks->ResetSequenceInfo();
|
||
|
}
|
||
|
|
||
|
m_lastRightTrackPos = m_rightTracks->GetAbsOrigin();
|
||
|
}
|
||
|
|
||
|
m_bomb = (CBaseAnimating *)CreateEntityByName( "prop_dynamic" );
|
||
|
if ( m_bomb )
|
||
|
{
|
||
|
m_bomb->SetModel( TANK_BOMB );
|
||
|
m_bomb->SetModelIndexOverride( VISION_MODE_NONE, modelinfo->GetModelIndex( TANK_BOMB ) );
|
||
|
m_bomb->SetModelIndexOverride( VISION_MODE_ROME, modelinfo->GetModelIndex( TANK_BOMB_ROME ) );
|
||
|
|
||
|
// bonemerge into our model
|
||
|
m_bomb->FollowEntity( this, true );
|
||
|
}
|
||
|
|
||
|
GetBodyInterface()->StartSequence( "movement" );
|
||
|
|
||
|
m_exhaustAttachment = LookupAttachment( "smoke_attachment" );
|
||
|
|
||
|
if ( m_goalNode == NULL )
|
||
|
{
|
||
|
m_goalNode = dynamic_cast< CPathTrack * >( gEntList.FindEntityByClassname( NULL, "path_track" ) );
|
||
|
|
||
|
if ( m_goalNode )
|
||
|
{
|
||
|
// find first node
|
||
|
while( m_goalNode->GetPrevious() )
|
||
|
{
|
||
|
m_goalNode = m_goalNode->GetPrevious();
|
||
|
}
|
||
|
|
||
|
SetAbsOrigin( m_goalNode->WorldSpaceCenter() );
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
SetAbsOrigin( m_goalNode->WorldSpaceCenter() );
|
||
|
}
|
||
|
|
||
|
// We've traveled nowhere if we're at the first node
|
||
|
m_fTotalDistance = 0.0f;
|
||
|
m_CumulativeDistances.AddToTail( m_fTotalDistance );
|
||
|
|
||
|
// Remember starting node
|
||
|
m_startNode = m_goalNode;
|
||
|
m_endNode = m_startNode;
|
||
|
m_nNodeNumber = 0;
|
||
|
|
||
|
// Orient the Tank along the path
|
||
|
if ( m_goalNode != NULL )
|
||
|
{
|
||
|
CPathTrack *pPrevNode = m_goalNode;
|
||
|
CPathTrack *pNextNode = m_goalNode->GetNext();
|
||
|
|
||
|
if ( pNextNode )
|
||
|
{
|
||
|
Vector along = pNextNode->GetAbsOrigin() - m_goalNode->GetAbsOrigin();
|
||
|
|
||
|
QAngle angles;
|
||
|
VectorAngles( along, angles );
|
||
|
|
||
|
SetAbsAngles( angles );
|
||
|
|
||
|
// Find last node and calculate cumulative distance
|
||
|
while( pNextNode )
|
||
|
{
|
||
|
along = pNextNode->GetAbsOrigin() - pPrevNode->GetAbsOrigin();
|
||
|
along.z = 0.0f;
|
||
|
|
||
|
m_fTotalDistance += along.Length();
|
||
|
m_CumulativeDistances.AddToTail( m_fTotalDistance );
|
||
|
|
||
|
pPrevNode = pNextNode;
|
||
|
pNextNode = pNextNode->GetNext();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
SetBloodColor( DONT_BLEED );
|
||
|
|
||
|
m_flLastPingTime = gpGlobals->curtime;
|
||
|
|
||
|
CBroadcastRecipientFilter filter;
|
||
|
EmitSound( filter, entindex(), "MVM.TankEngineLoop" );
|
||
|
EmitSound( "MVM.TankStart" );
|
||
|
|
||
|
if ( TFGameRules() )
|
||
|
{
|
||
|
int nTankCount = 0;
|
||
|
|
||
|
CBaseEntity *tank = NULL;
|
||
|
while( ( tank = gEntList.FindEntityByClassname( tank, "tank_boss" ) ) != NULL )
|
||
|
{
|
||
|
nTankCount++;
|
||
|
}
|
||
|
|
||
|
if ( nTankCount <= 1 )
|
||
|
{
|
||
|
if ( m_flLastTankAlert + 5.0f < gpGlobals->curtime )
|
||
|
{
|
||
|
CWave *pWave = g_pPopulationManager ? g_pPopulationManager->GetCurrentWave() : NULL;
|
||
|
if ( pWave && pWave->NumTanksSpawned() > 1 )
|
||
|
{
|
||
|
TFGameRules()->BroadcastSound( 255, "Announcer.MVM_Tank_Alert_Another" );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
TFGameRules()->BroadcastSound( 255, "Announcer.MVM_Tank_Alert_Spawn" );
|
||
|
}
|
||
|
|
||
|
m_flLastTankAlert = gpGlobals->curtime;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Don't worry about when the last alert was in this case because 2 tanks can spawn at once
|
||
|
TFGameRules()->BroadcastSound( 255, "Announcer.MVM_Tank_Alert_Multiple" );
|
||
|
m_flLastTankAlert = gpGlobals->curtime;
|
||
|
}
|
||
|
|
||
|
TFGameRules()->HaveAllPlayersSpeakConceptIfAllowed( MP_CONCEPT_MVM_TANK_CALLOUT, TF_TEAM_PVE_DEFENDERS );
|
||
|
}
|
||
|
|
||
|
m_isDroppingBomb = false;
|
||
|
m_flDroppingStart = 0.0f;
|
||
|
m_flSpawnTime = gpGlobals->curtime;
|
||
|
|
||
|
SetThink( &CTFTankBoss::TankBossThink );
|
||
|
SetNextThink( gpGlobals->curtime );
|
||
|
}
|
||
|
|
||
|
|
||
|
//--------------------------------------------------------------------------------------
|
||
|
void CTFTankBoss::UpdateOnRemove( void )
|
||
|
{
|
||
|
StopSound( "MVM.TankEngineLoop" );
|
||
|
|
||
|
if ( TFObjectiveResource() )
|
||
|
{
|
||
|
TFObjectiveResource()->DecrementMannVsMachineWaveClassCount( MAKE_STRING( "tank" ), MVM_CLASS_FLAG_NORMAL | MVM_CLASS_FLAG_MINIBOSS );
|
||
|
}
|
||
|
|
||
|
BaseClass::UpdateOnRemove();
|
||
|
}
|
||
|
|
||
|
|
||
|
int CTFTankBoss::OnTakeDamage_Alive( const CTakeDamageInfo &rawInfo )
|
||
|
{
|
||
|
if ( static_cast< float >( GetHealth() ) / GetMaxHealth() > 0.3f )
|
||
|
{
|
||
|
DispatchParticleEffect( "bot_impact_light", rawInfo.GetDamagePosition(), vec3_angle );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
DispatchParticleEffect( "bot_impact_heavy", rawInfo.GetDamagePosition(), vec3_angle );
|
||
|
}
|
||
|
|
||
|
// Calculate Final Damage values
|
||
|
if ( BaseClass::OnTakeDamage_Alive( rawInfo ) && rawInfo.GetAttacker() )
|
||
|
{
|
||
|
// track who damaged us
|
||
|
CTFPlayer *pTFPlayer = dynamic_cast< CTFPlayer* >( rawInfo.GetAttacker() );
|
||
|
if ( pTFPlayer )
|
||
|
{
|
||
|
// is the attacker being healed by any Medic(s)?
|
||
|
CUtlVector<CTFPlayer*> pTempPlayerQueue;
|
||
|
pTFPlayer->AddConnectedPlayers( pTempPlayerQueue, pTFPlayer );
|
||
|
|
||
|
for ( int i = 0 ; i < pTempPlayerQueue.Count() ; i++ )
|
||
|
{
|
||
|
EntityHistory_t newHist;
|
||
|
newHist.hEntity = pTempPlayerQueue[i];
|
||
|
newHist.flTimeDamage = gpGlobals->curtime;
|
||
|
m_vecDamagers.InsertHistory( newHist );
|
||
|
}
|
||
|
|
||
|
// Report Tank dmg to Stats
|
||
|
CMannVsMachineStats *pStats = MannVsMachineStats_GetInstance();
|
||
|
if ( pStats )
|
||
|
{
|
||
|
pStats->PlayerEvent_DealtDamageToTanks( pTFPlayer, rawInfo.GetDamage() );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
//--------------------------------------------------------------------------------------
|
||
|
void CTFTankBoss::Event_Killed( const CTakeDamageInfo &info )
|
||
|
{
|
||
|
m_bIsPlayerKilled = ( info.GetDamageType() & DMG_CRUSH ) == 0;
|
||
|
|
||
|
Explode();
|
||
|
|
||
|
// check for MvM achievement
|
||
|
if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() )
|
||
|
{
|
||
|
if ( FStrEq( "mvm_rottenburg", STRING( gpGlobals->mapname ) ) )
|
||
|
{
|
||
|
CLogicRelay *pLogicRelay = dynamic_cast< CLogicRelay* >( gEntList.FindEntityByName( NULL, "Barricade_Achievement_Check" ) );
|
||
|
if ( pLogicRelay && !pLogicRelay->IsDisabled() )
|
||
|
{
|
||
|
CUtlVector<CTFPlayer *> playerVector;
|
||
|
CollectPlayers( &playerVector, TF_TEAM_PVE_DEFENDERS );
|
||
|
FOR_EACH_VEC( playerVector, i )
|
||
|
{
|
||
|
if ( !playerVector[i] )
|
||
|
continue;
|
||
|
|
||
|
if ( playerVector[i]->IsBot() )
|
||
|
continue;
|
||
|
|
||
|
playerVector[i]->AwardAchievement( ACHIEVEMENT_TF_MVM_MAPS_ROTTENBURG_TANK );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
BaseClass::Event_Killed( info );
|
||
|
}
|
||
|
|
||
|
|
||
|
//--------------------------------------------------------------------------------------
|
||
|
void CTFTankBoss::SetStartingPathTrackNode( char *name )
|
||
|
{
|
||
|
m_goalNode = dynamic_cast< CPathTrack * >( gEntList.FindEntityByName( NULL, name ) );
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------------------------------
|
||
|
void CTFTankBoss::TankBossThink( void )
|
||
|
{
|
||
|
// damage states
|
||
|
if ( GetHealth() != m_lastHealth )
|
||
|
{
|
||
|
// health changed - potentially change damage model
|
||
|
m_lastHealth = GetHealth();
|
||
|
|
||
|
int healthPerModel = GetMaxHealth() / TANK_DAMAGE_MODEL_COUNT;
|
||
|
int healthThreshold = GetMaxHealth() - healthPerModel;
|
||
|
|
||
|
int desiredModelIndex;
|
||
|
for( desiredModelIndex = 0; desiredModelIndex < TANK_DAMAGE_MODEL_COUNT; ++desiredModelIndex )
|
||
|
{
|
||
|
if ( GetHealth() > healthThreshold )
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
healthThreshold -= healthPerModel;
|
||
|
}
|
||
|
|
||
|
if ( desiredModelIndex >= TANK_DAMAGE_MODEL_COUNT )
|
||
|
{
|
||
|
desiredModelIndex = TANK_DAMAGE_MODEL_COUNT-1;
|
||
|
}
|
||
|
|
||
|
if ( desiredModelIndex != m_damageModelIndex )
|
||
|
{
|
||
|
// update model
|
||
|
const char *pchSequence = GetSequenceName( GetSequence() );
|
||
|
float fCycle = GetCycle();
|
||
|
|
||
|
m_damageModelIndex = desiredModelIndex;
|
||
|
SetModel( s_TankModel[ m_damageModelIndex ] );
|
||
|
SetModelIndexOverride( VISION_MODE_NONE, modelinfo->GetModelIndex( s_TankModel[ m_damageModelIndex ] ) );
|
||
|
SetModelIndexOverride( VISION_MODE_ROME, modelinfo->GetModelIndex( s_TankModelRome[ m_damageModelIndex ] ) );
|
||
|
|
||
|
int nAnimSequence = LookupSequence( pchSequence );
|
||
|
if ( nAnimSequence > 0 )
|
||
|
{
|
||
|
SetSequence( nAnimSequence );
|
||
|
SetPlaybackRate( 1.0f );
|
||
|
ResetSequenceInfo();
|
||
|
SetCycle( fCycle );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
GetBodyInterface()->StartSequence( "movement" );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// left/right track speed
|
||
|
const float trackMaxSpeed = 80.0f;
|
||
|
const float trackOffset = 56.221f;
|
||
|
|
||
|
Vector forward, right, up;
|
||
|
GetVectors( &forward, &right, &up );
|
||
|
|
||
|
if ( m_leftTracks )
|
||
|
{
|
||
|
Vector trackCenter = GetAbsOrigin() - trackOffset * right;
|
||
|
|
||
|
float speed = ( trackCenter - m_lastLeftTrackPos ).Length() / gpGlobals->frametime;
|
||
|
|
||
|
if ( speed >= trackMaxSpeed )
|
||
|
{
|
||
|
m_leftTracks->SetPlaybackRate( 1.0f );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_leftTracks->SetPlaybackRate( speed / trackMaxSpeed );
|
||
|
}
|
||
|
|
||
|
m_lastLeftTrackPos = trackCenter;
|
||
|
}
|
||
|
|
||
|
if ( m_rightTracks )
|
||
|
{
|
||
|
Vector trackCenter = GetAbsOrigin() + trackOffset * right;
|
||
|
|
||
|
float speed = ( trackCenter - m_lastRightTrackPos ).Length() / gpGlobals->frametime;
|
||
|
|
||
|
if ( speed >= trackMaxSpeed )
|
||
|
{
|
||
|
m_rightTracks->SetPlaybackRate( 1.0f );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_rightTracks->SetPlaybackRate( speed / trackMaxSpeed );
|
||
|
}
|
||
|
|
||
|
m_lastRightTrackPos = trackCenter;
|
||
|
}
|
||
|
|
||
|
|
||
|
if ( m_goalNode != NULL )
|
||
|
{
|
||
|
Vector toGoal = m_goalNode->WorldSpaceCenter() - GetAbsOrigin();
|
||
|
toGoal.z = 0.0f;
|
||
|
float range = toGoal.NormalizeInPlace();
|
||
|
|
||
|
if ( GetParent() )
|
||
|
{
|
||
|
// Track train might be closer
|
||
|
toGoal = m_goalNode->WorldSpaceCenter() - GetParent()->GetAbsOrigin();
|
||
|
toGoal.z = 0.0f;
|
||
|
float flTempRange = toGoal.NormalizeInPlace();
|
||
|
range = MIN( range, flTempRange );
|
||
|
}
|
||
|
|
||
|
if ( TFGameRules() )
|
||
|
{
|
||
|
if ( m_nNodeNumber <= 0 )
|
||
|
{
|
||
|
//TFGameRules()->SetBossNormalizedTravelDistance( 0.0f );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Assert( m_CumulativeDistances.IsValidIndex( m_nNodeNumber ) );
|
||
|
float fBaseDistance = m_CumulativeDistances[ m_nNodeNumber - 1 ];
|
||
|
float fDistanceFromPreviousToNext = m_CumulativeDistances[ m_nNodeNumber ] - fBaseDistance;
|
||
|
float fDistanceTraveled = fBaseDistance + ( fDistanceFromPreviousToNext - range );
|
||
|
float fDistancePercent = fDistanceTraveled / m_fTotalDistance;
|
||
|
//TFGameRules()->SetBossNormalizedTravelDistance( fDistancePercent );
|
||
|
|
||
|
if ( m_flLastTankAlert + 5.0f < gpGlobals->curtime )
|
||
|
{
|
||
|
if ( !m_bPlayedNearAlert && fDistancePercent > 0.75f)
|
||
|
{
|
||
|
TFGameRules()->PlayThrottledAlert( 255, "Announcer.MVM_Tank_Alert_Near_Hatch", 5.0f );
|
||
|
m_flLastTankAlert = gpGlobals->curtime;
|
||
|
m_bPlayedNearAlert = true;
|
||
|
}
|
||
|
else if ( !m_bPlayedHalfwayAlert && fDistancePercent > 0.5f)
|
||
|
{
|
||
|
int nTankCount = 0;
|
||
|
|
||
|
CBaseEntity *tank = NULL;
|
||
|
while( ( tank = gEntList.FindEntityByClassname( tank, "tank_boss" ) ) != NULL )
|
||
|
{
|
||
|
nTankCount++;
|
||
|
}
|
||
|
|
||
|
if ( nTankCount > 1 )
|
||
|
{
|
||
|
TFGameRules()->PlayThrottledAlert( 255, "Announcer.MVM_Tank_Alert_Halfway_Multiple", 5.0f );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
TFGameRules()->PlayThrottledAlert( 255, "Announcer.MVM_Tank_Alert_Halfway", 5.0f );
|
||
|
}
|
||
|
|
||
|
m_flLastTankAlert = gpGlobals->curtime;
|
||
|
m_bPlayedHalfwayAlert = true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( range < 20.0f )
|
||
|
{
|
||
|
// reached node
|
||
|
inputdata_t dummyData;
|
||
|
dummyData.pActivator = this;
|
||
|
dummyData.pCaller = this;
|
||
|
dummyData.nOutputID = 0;
|
||
|
|
||
|
m_goalNode->InputPass( dummyData );
|
||
|
|
||
|
m_goalNode = m_goalNode->GetNext();
|
||
|
m_nNodeNumber++;
|
||
|
|
||
|
if ( m_goalNode == NULL && m_bomb )
|
||
|
{
|
||
|
//DevMsg( "Tank's final position: %.2f %.2f %.2f", GetAbsOrigin().x, GetAbsOrigin().y, GetAbsOrigin().z );
|
||
|
|
||
|
/*if ( TFGameRules() )
|
||
|
{
|
||
|
TFGameRules()->SetBossNormalizedTravelDistance( 1.0f );
|
||
|
}*/
|
||
|
|
||
|
// reached end of track - deploy the bomb
|
||
|
int animSequence = m_bomb->LookupSequence( "deploy" );
|
||
|
if ( animSequence )
|
||
|
{
|
||
|
m_bomb->SetSequence( animSequence );
|
||
|
m_bomb->SetPlaybackRate( 1.0f );
|
||
|
m_bomb->SetCycle( 0 );
|
||
|
m_bomb->ResetSequenceInfo();
|
||
|
}
|
||
|
|
||
|
animSequence = LookupSequence( "deploy" );
|
||
|
if ( animSequence )
|
||
|
{
|
||
|
SetSequence( animSequence );
|
||
|
SetPlaybackRate( 1.0f );
|
||
|
SetCycle( 0 );
|
||
|
ResetSequenceInfo();
|
||
|
}
|
||
|
|
||
|
if ( m_flLastTankAlert + 5.0f < gpGlobals->curtime )
|
||
|
{
|
||
|
TFGameRules()->PlayThrottledAlert( 255, "Announcer.MVM_Tank_Alert_Deploying", 5.0f );
|
||
|
m_flLastTankAlert = gpGlobals->curtime;
|
||
|
m_bPlayedNearAlert = true;
|
||
|
}
|
||
|
|
||
|
m_isDroppingBomb = true;
|
||
|
m_flDroppingStart = gpGlobals->curtime;
|
||
|
|
||
|
StopSound( "MVM.TankEngineLoop" );
|
||
|
|
||
|
EmitSound( "MVM.TankDeploy" );
|
||
|
|
||
|
TFGameRules()->HaveAllPlayersSpeakConceptIfAllowed( MP_CONCEPT_MVM_TANK_DEPLOYING, TF_TEAM_PVE_DEFENDERS );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( m_goalNode )
|
||
|
{
|
||
|
Vector goal = m_goalNode->WorldSpaceCenter();
|
||
|
|
||
|
GetLocomotionInterface()->SetDesiredSpeed( GetMaxSpeed() );
|
||
|
GetLocomotionInterface()->Approach( goal );
|
||
|
GetLocomotionInterface()->FaceTowards( goal );
|
||
|
|
||
|
if ( m_rumbleTimer.IsElapsed() )
|
||
|
{
|
||
|
m_rumbleTimer.Start( 0.25f );
|
||
|
|
||
|
// shake nearby players' screens.
|
||
|
UTIL_ScreenShake( GetAbsOrigin(), 2.0f, 5.0f, 1.0f, 500.0f, SHAKE_START );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( m_isDroppingBomb && IsSequenceFinished() )
|
||
|
{
|
||
|
FirePopFileEvent( &m_onBombDroppedEventInfo );
|
||
|
m_isDroppingBomb = false;
|
||
|
|
||
|
TFGameRules()->BroadcastSound( 255, "Announcer.MVM_Tank_Planted" );
|
||
|
}
|
||
|
|
||
|
// if the Tank is driving under something, shut off its smokestack
|
||
|
if ( m_exhaustAttachment > 0 )
|
||
|
{
|
||
|
Vector smokePos;
|
||
|
GetAttachment( m_exhaustAttachment, smokePos );
|
||
|
|
||
|
trace_t result;
|
||
|
UTIL_TraceLine( smokePos, smokePos + Vector( 0, 0, 300.0f ), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &result );
|
||
|
|
||
|
if ( result.DidHit() )
|
||
|
{
|
||
|
if ( m_isSmoking )
|
||
|
{
|
||
|
StopParticleEffects( this );
|
||
|
m_isSmoking = false;
|
||
|
}
|
||
|
}
|
||
|
else if ( !m_isSmoking )
|
||
|
{
|
||
|
DispatchParticleEffect( "smoke_train", PATTACH_POINT_FOLLOW, this, m_exhaustAttachment );
|
||
|
m_isSmoking = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// destroy things we drive into/over
|
||
|
if ( m_crushTimer.IsElapsed() )
|
||
|
{
|
||
|
m_crushTimer.Start( 0.5f );
|
||
|
|
||
|
const int maxCollectedEntities = 64;
|
||
|
CBaseEntity *intersectingEntities[ maxCollectedEntities ];
|
||
|
int count = UTIL_EntitiesInBox( intersectingEntities, maxCollectedEntities,
|
||
|
GetAbsOrigin() + WorldAlignMins() * 0.75f, // a little fudge room for players on the top or sides
|
||
|
GetAbsOrigin() + WorldAlignMaxs() * 0.75f,
|
||
|
FL_CLIENT | FL_OBJECT );
|
||
|
|
||
|
for( int i = 0; i < count; ++i )
|
||
|
{
|
||
|
CBaseEntity *victim = intersectingEntities[i];
|
||
|
|
||
|
if ( victim == NULL )
|
||
|
continue;
|
||
|
|
||
|
int damage = MAX( victim->GetMaxHealth(), victim->GetHealth() );
|
||
|
|
||
|
CTakeDamageInfo info( this, this, 4 * damage, DMG_CRUSH, TF_DMG_CUSTOM_NONE );
|
||
|
victim->TakeDamage( info );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
UpdatePingSound();
|
||
|
|
||
|
BaseClass::BossThink();
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------------------------------
|
||
|
void CTFTankBoss::ModifyDamage( CTakeDamageInfo *info ) const
|
||
|
{
|
||
|
CTFWeaponBase *pWeapon = dynamic_cast< CTFWeaponBase * >( info->GetWeapon() );
|
||
|
|
||
|
if ( pWeapon && pWeapon->GetWeaponID() == TF_WEAPON_MINIGUN )
|
||
|
{
|
||
|
// miniguns are crazy powerful when all bullets always hit
|
||
|
const float minigunFactor = 0.25f;
|
||
|
info->SetDamage( info->GetDamage() * minigunFactor );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void CTFTankBoss::UpdateCollisionBounds( void )
|
||
|
{
|
||
|
// Remember the initial bounds
|
||
|
if ( m_vCollisionMins.IsZero() || m_vCollisionMaxs.IsZero() )
|
||
|
{
|
||
|
m_vCollisionMins = WorldAlignMins();
|
||
|
m_vCollisionMaxs = WorldAlignMaxs();
|
||
|
}
|
||
|
|
||
|
// When the tank is at a diagonal angle we don't want the bounds to bloat too far
|
||
|
float flDiagonalShrinkMultiplier = 1.0f - fabsf( sinf( DEG2RAD( GetAbsAngles().y ) * 2.0f ) ) * 0.4f;
|
||
|
|
||
|
Vector vMins = m_vCollisionMins;
|
||
|
vMins.x *= flDiagonalShrinkMultiplier;
|
||
|
vMins.y *= flDiagonalShrinkMultiplier;
|
||
|
|
||
|
Vector vMaxs = m_vCollisionMaxs;
|
||
|
vMaxs.x *= flDiagonalShrinkMultiplier;
|
||
|
vMaxs.y *= flDiagonalShrinkMultiplier;
|
||
|
|
||
|
// Build new world aligned bounds based on how it's rotated
|
||
|
VMatrix rot;
|
||
|
MatrixFromAngles( GetAbsAngles(), rot );
|
||
|
|
||
|
Vector vMinsOut, vMaxsOut;
|
||
|
TransformAABB( rot.As3x4(), vMins, vMaxs, vMinsOut, vMaxsOut );
|
||
|
CollisionProp()->SetCollisionBounds( vMinsOut, vMaxsOut );
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------------------------------
|
||
|
void CTFTankBoss::FirePopFileEvent( EventInfo *eventInfo )
|
||
|
{
|
||
|
if ( eventInfo && eventInfo->m_action.Length() > 0 )
|
||
|
{
|
||
|
CBaseEntity *targetEntity = gEntList.FindEntityByName( NULL, eventInfo->m_target );
|
||
|
if ( !targetEntity )
|
||
|
{
|
||
|
Warning( "CTFTankBoss: Can't find target entity '%s' for event\n", eventInfo->m_target.Access() );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
g_EventQueue.AddEvent( targetEntity, eventInfo->m_action, 0.0f, NULL, NULL );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void CTFTankBoss::Explode( void )
|
||
|
{
|
||
|
StopSound( "MVM.TankEngineLoop" );
|
||
|
|
||
|
FirePopFileEvent( &m_onKilledEventInfo );
|
||
|
|
||
|
CTFTankDestruction *pDestruction = dynamic_cast< CTFTankDestruction* >( CreateEntityByName( "tank_destruction" ) );
|
||
|
if ( pDestruction )
|
||
|
{
|
||
|
// Only do special capture point death if it was force killed by bomb drop
|
||
|
pDestruction->m_bIsAtCapturePoint = ( m_goalNode == NULL && !m_bIsPlayerKilled );
|
||
|
pDestruction->m_nDeathAnimPick = m_nDeathAnimPick;
|
||
|
V_strncpy( pDestruction->m_szDeathPostfix, m_szDeathPostfix, ARRAYSIZE( pDestruction->m_szDeathPostfix ) );
|
||
|
|
||
|
pDestruction->SetAbsOrigin( GetAbsOrigin() );
|
||
|
pDestruction->SetAbsAngles( GetAbsAngles() );
|
||
|
DispatchSpawn( pDestruction );
|
||
|
}
|
||
|
|
||
|
if ( m_bIsPlayerKilled )
|
||
|
{
|
||
|
TFGameRules()->BroadcastSound( 255, "Announcer.MVM_General_Destruction" );
|
||
|
TFGameRules()->HaveAllPlayersSpeakConceptIfAllowed( MP_CONCEPT_MVM_TANK_DEAD, TF_TEAM_PVE_DEFENDERS );
|
||
|
|
||
|
IGameEvent *event = gameeventmanager->CreateEvent( "mvm_tank_destroyed_by_players" );
|
||
|
if ( event )
|
||
|
{
|
||
|
gameeventmanager->FireEvent( event );
|
||
|
}
|
||
|
|
||
|
if ( TFGameRules()->IsMannVsMachineMode() )
|
||
|
{
|
||
|
// ACHIEVEMENT_TF_MVM_DESTROY_TANK_WHILE_DEPLOYING
|
||
|
if ( m_isDroppingBomb )
|
||
|
{
|
||
|
// short delay so you only get the achievement if the bomb doors have opened/closed and it's ready to deploy
|
||
|
if ( gpGlobals->curtime - m_flDroppingStart > 5.8f )
|
||
|
{
|
||
|
// anyone who has damaged the tank since the deploy anim began will get the achievement
|
||
|
float flWindow = gpGlobals->curtime - m_flDroppingStart;
|
||
|
|
||
|
for ( int i = 0; i < m_vecDamagers.Count(); i++ )
|
||
|
{
|
||
|
// get the achievement if you have damaged the tank since the deploy anim began
|
||
|
if ( ( gpGlobals->curtime - m_vecDamagers[i].flTimeDamage ) < flWindow )
|
||
|
{
|
||
|
CTFPlayer *pTFPlayer = dynamic_cast< CTFPlayer* >( m_vecDamagers[i].hEntity.Get() );
|
||
|
if ( pTFPlayer )
|
||
|
{
|
||
|
pTFPlayer->AwardAchievement( ACHIEVEMENT_TF_MVM_DESTROY_TANK_WHILE_DEPLOYING );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// ACHIEVEMENT_TF_MVM_DESTROY_TANK_QUICKLY
|
||
|
if ( ( gpGlobals->curtime - m_flSpawnTime ) < MVM_DESTROY_TANK_QUICKLY_TIME )
|
||
|
{
|
||
|
for ( int i = 0; i < m_vecDamagers.Count(); i++ )
|
||
|
{
|
||
|
// get the achievement if you have damaged the tank since the deploy anim began
|
||
|
if ( ( gpGlobals->curtime - m_vecDamagers[i].flTimeDamage ) < MVM_DESTROY_TANK_QUICKLY_TIME )
|
||
|
{
|
||
|
CTFPlayer *pTFPlayer = dynamic_cast< CTFPlayer* >( m_vecDamagers[i].hEntity.Get() );
|
||
|
if ( pTFPlayer )
|
||
|
{
|
||
|
pTFPlayer->AwardAchievement( ACHIEVEMENT_TF_MVM_DESTROY_TANK_QUICKLY );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// strange part credit? (this logic isn't so correct -- it'll try to grant the credit to the active
|
||
|
// weapon of anyone who damaged the tank, *not* the weapon that actually did the damage, as we don't
|
||
|
// track that)
|
||
|
FOR_EACH_VEC( m_vecDamagers, i )
|
||
|
{
|
||
|
CTFPlayer *pTFPlayer = dynamic_cast< CTFPlayer* >( m_vecDamagers[i].hEntity.Get() );
|
||
|
if ( !pTFPlayer )
|
||
|
continue;
|
||
|
|
||
|
EconEntity_OnOwnerKillEaterEventNoPartner( pTFPlayer->GetActiveTFWeapon(), pTFPlayer, kKillEaterEvent_TanksDestroyed );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
#define TANK_PING_TIME 5.0
|
||
|
void CTFTankBoss::UpdatePingSound( void )
|
||
|
{
|
||
|
if( gpGlobals->curtime - m_flLastPingTime >= TANK_PING_TIME )
|
||
|
{
|
||
|
m_flLastPingTime = gpGlobals->curtime;
|
||
|
EmitSound( "MVM.TankPing");
|
||
|
}
|
||
|
}
|
||
|
|