Modified source engine (2017) developed by valve and leaked in 2020. Not for commercial purporses
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.
 
 
 
 
 
 

1077 lines
30 KiB

//========= 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");
}
}