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.
1386 lines
38 KiB
1386 lines
38 KiB
4 years ago
|
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||
|
// boss_alpha.cpp
|
||
|
// Our first "real" TF Boss
|
||
|
// Michael Booth, November 2010
|
||
|
|
||
|
#include "cbase.h"
|
||
|
|
||
|
#ifdef TF_RAID_MODE
|
||
|
|
||
|
#include "tf_player.h"
|
||
|
#include "tf_gamerules.h"
|
||
|
#include "tf_team.h"
|
||
|
#include "tf_projectile_arrow.h"
|
||
|
#include "tf_projectile_rocket.h"
|
||
|
#include "tf_weapon_grenade_pipebomb.h"
|
||
|
#include "tf_ammo_pack.h"
|
||
|
#include "tf_obj_sentrygun.h"
|
||
|
#include "nav_mesh/tf_nav_area.h"
|
||
|
#include "NextBot/Path/NextBotChasePath.h"
|
||
|
#include "econ_wearable.h"
|
||
|
#include "team_control_point_master.h"
|
||
|
#include "particle_parse.h"
|
||
|
#include "CRagdollMagnet.h"
|
||
|
#include "nav_mesh/tf_path_follower.h"
|
||
|
#include "bot_npc/bot_npc_minion.h"
|
||
|
#include "player_vs_environment/monster_resource.h"
|
||
|
#include "bot/map_entities/tf_bot_generator.h"
|
||
|
|
||
|
#include "player_vs_environment/boss_alpha/boss_alpha.h"
|
||
|
#include "player_vs_environment/boss_alpha/behavior/boss_alpha_behavior.h"
|
||
|
|
||
|
|
||
|
//#define USE_BOSS_SENTRY
|
||
|
|
||
|
|
||
|
ConVar tf_boss_alpha_health( "tf_boss_alpha_health", "30000"/*, FCVAR_CHEAT*/ );
|
||
|
ConVar tf_boss_alpha_attack_range( "tf_boss_alpha_attack_range", "300"/*, FCVAR_CHEAT*/ );
|
||
|
ConVar tf_boss_alpha_threat_tolerance( "tf_boss_alpha_threat_tolerance", "100"/*, FCVAR_CHEAT*/ );
|
||
|
ConVar tf_boss_alpha_chase_range( "tf_boss_alpha_chase_range", "300"/*, FCVAR_CHEAT*/ );
|
||
|
ConVar tf_boss_alpha_grenade_launch_range( "tf_boss_alpha_grenade_launch_range", "300"/*, FCVAR_CHEAT*/ );
|
||
|
ConVar tf_boss_alpha_quit_range( "tf_boss_alpha_quit_range", "2500"/*, FCVAR_CHEAT*/ );
|
||
|
ConVar tf_boss_alpha_reaction_time( "tf_boss_alpha_reaction_time", "0.5"/*, FCVAR_CHEAT*/ );
|
||
|
ConVar tf_boss_alpha_stunned_injury_multiplier( "tf_boss_alpha_stunned_injury_multiplier", "10" );
|
||
|
ConVar tf_boss_alpha_head_radius( "tf_boss_alpha_head_radius", "75" ); // 50
|
||
|
ConVar tf_boss_alpha_hate_taunt_cooldown( "tf_boss_alpha_hate_taunt_cooldown", "10"/*, FCVAR_CHEAT*/ );
|
||
|
ConVar tf_boss_alpha_debug_damage( "tf_boss_alpha_debug_damage", "0"/*, FCVAR_CHEAT*/ );
|
||
|
ConVar tf_boss_alpha_min_nuke_after_stun_time( "tf_boss_alpha_min_nuke_after_stun_time", "5" /*, FCVAR_CHEAT */ );
|
||
|
|
||
|
ConVar tf_boss_alpha_always_stun( "tf_boss_alpha_always_stun", "0"/*, FCVAR_CHEAT*/ );
|
||
|
ConVar tf_boss_alpha_stun_rocket_reflect_count( "tf_boss_alpha_stun_rocket_reflect_count", "2"/*, FCVAR_CHEAT */ );
|
||
|
ConVar tf_boss_alpha_stun_rocket_reflect_duration( "tf_boss_alpha_stun_rocket_reflect_duration", "1"/*, FCVAR_CHEAT */ );
|
||
|
|
||
|
ConVar tf_boss_alpha_debug_skill_shots( "tf_boss_alpha_debug_skill_shots", "0"/*, FCVAR_CHEAT */ );
|
||
|
|
||
|
extern ConVar tf_boss_alpha_nuke_interval;
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------------------------------
|
||
|
// The Alpha Boss: A rocket and stickybomb firing giant robot that periodically charges up a big
|
||
|
// "nuke" attack, and is invulnerable unless stunned.
|
||
|
//-----------------------------------------------------------------------------------------------------
|
||
|
LINK_ENTITY_TO_CLASS( boss_alpha, CBossAlpha );
|
||
|
|
||
|
PRECACHE_REGISTER( boss_alpha );
|
||
|
|
||
|
IMPLEMENT_SERVERCLASS_ST( CBossAlpha, DT_BossAlpha )
|
||
|
|
||
|
SendPropBool( SENDINFO( m_isNuking ) ),
|
||
|
|
||
|
END_SEND_TABLE()
|
||
|
|
||
|
|
||
|
BEGIN_DATADESC( CBossAlpha )
|
||
|
DEFINE_OUTPUT( m_outputOnStunned, "OnStunned" ),
|
||
|
DEFINE_OUTPUT( m_outputOnHealthBelow90Percent, "OnHealthBelow90Percent" ),
|
||
|
DEFINE_OUTPUT( m_outputOnHealthBelow80Percent, "OnHealthBelow80Percent" ),
|
||
|
DEFINE_OUTPUT( m_outputOnHealthBelow70Percent, "OnHealthBelow70Percent" ),
|
||
|
DEFINE_OUTPUT( m_outputOnHealthBelow60Percent, "OnHealthBelow60Percent" ),
|
||
|
DEFINE_OUTPUT( m_outputOnHealthBelow50Percent, "OnHealthBelow50Percent" ),
|
||
|
DEFINE_OUTPUT( m_outputOnHealthBelow40Percent, "OnHealthBelow40Percent" ),
|
||
|
DEFINE_OUTPUT( m_outputOnHealthBelow30Percent, "OnHealthBelow30Percent" ),
|
||
|
DEFINE_OUTPUT( m_outputOnHealthBelow20Percent, "OnHealthBelow20Percent" ),
|
||
|
DEFINE_OUTPUT( m_outputOnHealthBelow10Percent, "OnHealthBelow10Percent" ),
|
||
|
DEFINE_OUTPUT( m_outputOnKilled, "OnKilled" ),
|
||
|
END_DATADESC()
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------------------------------
|
||
|
CBossAlpha::CBossAlpha()
|
||
|
{
|
||
|
m_intention = new CBossAlphaIntention( this );
|
||
|
m_locomotor = new CBossAlphaLocomotion( this );
|
||
|
m_body = new CBotNPCBody( this );
|
||
|
m_vision = new CBossAlphaVision( this );
|
||
|
|
||
|
m_conditionFlags = 0;
|
||
|
m_isNuking = false;
|
||
|
m_ageTimer.Invalidate();
|
||
|
|
||
|
m_lastHealthPercentage = 1.0f;
|
||
|
|
||
|
ClearStunDamage();
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------------------------------
|
||
|
CBossAlpha::~CBossAlpha()
|
||
|
{
|
||
|
if ( m_intention )
|
||
|
delete m_intention;
|
||
|
|
||
|
if ( m_locomotor )
|
||
|
delete m_locomotor;
|
||
|
|
||
|
if ( m_body )
|
||
|
delete m_body;
|
||
|
|
||
|
if ( m_vision )
|
||
|
delete m_vision;
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------------------------------
|
||
|
void CBossAlpha::Precache()
|
||
|
{
|
||
|
BaseClass::Precache();
|
||
|
|
||
|
#ifdef USE_BOSS_SENTRY
|
||
|
int model = PrecacheModel( "models/bots/boss_sentry/boss_sentry.mdl" );
|
||
|
#else
|
||
|
int model = PrecacheModel( "models/bots/knight/knight.mdl" );
|
||
|
#endif
|
||
|
|
||
|
PrecacheGibsForModel( model );
|
||
|
|
||
|
PrecacheScriptSound( "Weapon_Sword.Swing" );
|
||
|
PrecacheScriptSound( "Weapon_Sword.HitFlesh" );
|
||
|
PrecacheScriptSound( "Weapon_Sword.HitWorld" );
|
||
|
PrecacheScriptSound( "DemoCharge.HitWorld" );
|
||
|
PrecacheScriptSound( "TFPlayer.Pain" );
|
||
|
PrecacheScriptSound( "Halloween.HeadlessBossAttack" );
|
||
|
PrecacheScriptSound( "RobotBoss.StunStart" );
|
||
|
PrecacheScriptSound( "RobotBoss.Stunned" );
|
||
|
PrecacheScriptSound( "RobotBoss.StunRecover" );
|
||
|
PrecacheScriptSound( "RobotBoss.Acquire" );
|
||
|
PrecacheScriptSound( "RobotBoss.Vocalize" );
|
||
|
PrecacheScriptSound( "RobotBoss.Footstep" );
|
||
|
PrecacheScriptSound( "RobotBoss.LaunchGrenades" );
|
||
|
PrecacheScriptSound( "RobotBoss.LaunchRockets" );
|
||
|
PrecacheScriptSound( "RobotBoss.Hurt" );
|
||
|
PrecacheScriptSound( "RobotBoss.Vulnerable" );
|
||
|
PrecacheScriptSound( "RobotBoss.ChargeUpNukeAttack" );
|
||
|
PrecacheScriptSound( "RobotBoss.NukeAttack" );
|
||
|
PrecacheScriptSound( "RobotBoss.Scanning" );
|
||
|
PrecacheScriptSound( "RobotBoss.ReinforcementsArrived" );
|
||
|
PrecacheScriptSound( "RobotBoss.HardHitSkillShot" );
|
||
|
PrecacheScriptSound( "RobotBoss.DamageSpongeSkillShot" );
|
||
|
PrecacheScriptSound( "RobotBoss.PreciseHit1SkillShot" );
|
||
|
PrecacheScriptSound( "RobotBoss.PreciseHit2SkillShot" );
|
||
|
PrecacheScriptSound( "RobotBoss.PreciseHit3SkillShot" );
|
||
|
PrecacheScriptSound( "Cart.Explode" );
|
||
|
PrecacheScriptSound( "Weapon_Crowbar.Melee_HitWorld" );
|
||
|
|
||
|
PrecacheParticleSystem( "asplode_hoodoo_embers" );
|
||
|
PrecacheParticleSystem( "charge_up" );
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------------------------------
|
||
|
void CBossAlpha::Spawn( void )
|
||
|
{
|
||
|
BaseClass::Spawn();
|
||
|
|
||
|
#ifdef USE_BOSS_SENTRY
|
||
|
SetModel( "models/bots/boss_sentry/boss_sentry.mdl" );
|
||
|
#else
|
||
|
SetModel( "models/bots/knight/knight.mdl" );
|
||
|
#endif
|
||
|
|
||
|
m_conditionFlags = 0;
|
||
|
|
||
|
ClearStunDamage();
|
||
|
ResetSkillShots();
|
||
|
|
||
|
int health = tf_boss_alpha_health.GetInt();
|
||
|
SetHealth( health );
|
||
|
SetMaxHealth( health );
|
||
|
|
||
|
// show Boss' health meter on HUD
|
||
|
if ( g_pMonsterResource )
|
||
|
{
|
||
|
g_pMonsterResource->SetBossHealthPercentage( 1.0f );
|
||
|
}
|
||
|
|
||
|
m_damagePoseParameter = -1;
|
||
|
|
||
|
// randomize initial check
|
||
|
m_nearestVisibleEnemy = NULL;
|
||
|
m_nearestVisibleEnemyTimer.Start( RandomFloat( 0.0f, tf_boss_alpha_reaction_time.GetFloat() ) );
|
||
|
|
||
|
m_homePos = GetAbsOrigin();
|
||
|
|
||
|
m_currentDamagePerSecond = 0.0f;
|
||
|
m_lastDamagePerSecond = 0.0f;
|
||
|
|
||
|
m_attackTarget = NULL;
|
||
|
m_attackTargetTimer.Invalidate();
|
||
|
m_isAttackTargetLocked = false;
|
||
|
|
||
|
m_nukeTimer.Start( tf_boss_alpha_nuke_interval.GetFloat() );
|
||
|
m_isNuking = false;
|
||
|
|
||
|
m_grenadeTimer.Start( GetGrenadeInterval() );
|
||
|
m_ageTimer.Start();
|
||
|
|
||
|
m_lastHealthPercentage = 1.0f;
|
||
|
|
||
|
ChangeTeam( TF_TEAM_RED );
|
||
|
|
||
|
TFGameRules()->SetActiveBoss( this );
|
||
|
|
||
|
// CollisionProp()->SetSurroundingBoundsType( USE_HITBOXES );
|
||
|
|
||
|
Vector mins( -50, -50, 0 );
|
||
|
Vector maxs( 100, 100, 275 );
|
||
|
CollisionProp()->SetSurroundingBoundsType( USE_SPECIFIED_BOUNDS, &mins, &maxs );
|
||
|
|
||
|
Vector collideMins( -50, -50, 125 );
|
||
|
Vector collideMaxs( 50, 50, 260 );
|
||
|
CollisionProp()->SetCollisionBounds( collideMins, collideMaxs );
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------------------------------
|
||
|
ConVar tf_boss_alpha_dmg_mult_sniper( "tf_boss_alpha_dmg_mult_sniper", "1"/*, FCVAR_CHEAT*/ );
|
||
|
ConVar tf_boss_alpha_dmg_mult_minigun( "tf_boss_alpha_dmg_mult_minigun", "0.3"/*, FCVAR_CHEAT*/ );
|
||
|
ConVar tf_boss_alpha_dmg_mult_flamethrower( "tf_boss_alpha_dmg_mult_flamethrower", "1"/*, FCVAR_CHEAT*/ );
|
||
|
ConVar tf_boss_alpha_dmg_mult_sentrygun( "tf_boss_alpha_dmg_mult_sentrygun", "0.3"/*, FCVAR_CHEAT*/ );
|
||
|
ConVar tf_boss_alpha_dmg_mult_grenade( "tf_boss_alpha_dmg_mult_grenade", "0.3"/*, FCVAR_CHEAT*/ );
|
||
|
ConVar tf_boss_alpha_dmg_mult_rocket( "tf_boss_alpha_dmg_mult_rocket", "0.5"/*, FCVAR_CHEAT*/ );
|
||
|
|
||
|
|
||
|
float ModifyBossDamage( const CTakeDamageInfo &info )
|
||
|
{
|
||
|
CTFWeaponBase *pWeapon = dynamic_cast< CTFWeaponBase * >( info.GetWeapon() );
|
||
|
|
||
|
if ( pWeapon )
|
||
|
{
|
||
|
switch( pWeapon->GetWeaponID() )
|
||
|
{
|
||
|
case TF_WEAPON_SNIPERRIFLE:
|
||
|
case TF_WEAPON_SNIPERRIFLE_DECAP:
|
||
|
case TF_WEAPON_SNIPERRIFLE_CLASSIC:
|
||
|
case TF_WEAPON_COMPOUND_BOW:
|
||
|
return info.GetDamage() * tf_boss_alpha_dmg_mult_sniper.GetFloat();
|
||
|
|
||
|
case TF_WEAPON_MINIGUN:
|
||
|
return info.GetDamage() * tf_boss_alpha_dmg_mult_minigun.GetFloat();
|
||
|
|
||
|
case TF_WEAPON_FLAMETHROWER:
|
||
|
return info.GetDamage() * tf_boss_alpha_dmg_mult_flamethrower.GetFloat();
|
||
|
|
||
|
case TF_WEAPON_SENTRY_BULLET:
|
||
|
return info.GetDamage() * tf_boss_alpha_dmg_mult_sentrygun.GetFloat();
|
||
|
|
||
|
case TF_WEAPON_GRENADELAUNCHER:
|
||
|
case TF_WEAPON_PIPEBOMBLAUNCHER:
|
||
|
case TF_WEAPON_GRENADE_DEMOMAN:
|
||
|
return info.GetDamage() * tf_boss_alpha_dmg_mult_grenade.GetFloat();
|
||
|
|
||
|
case TF_WEAPON_ROCKETLAUNCHER:
|
||
|
case TF_WEAPON_ROCKETLAUNCHER_DIRECTHIT:
|
||
|
return info.GetDamage() * tf_boss_alpha_dmg_mult_rocket.GetFloat();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// unmodified
|
||
|
return info.GetDamage();
|
||
|
}
|
||
|
|
||
|
#define HITBOX_SKILL_STICKYBOMB_1 23
|
||
|
#define HITBOX_SKILL_STICKYBOMB_2 24
|
||
|
|
||
|
#define HITBOX_SKILL_PRECISION_1 20
|
||
|
#define HITBOX_SKILL_PRECISION_2 21
|
||
|
#define HITBOX_SKILL_PRECISION_3 22
|
||
|
#define PRECISION_SHOT_COUNT 3
|
||
|
|
||
|
#define HITBOX_SKILL_DAMAGE_SPONGE 19
|
||
|
|
||
|
#define HITBOX_SKILL_HARD_HIT 18
|
||
|
|
||
|
ConVar tf_boss_alpha_skill_shot_combo_time( "tf_boss_alpha_skill_shot_combo_time", "10"/*, FCVAR_CHEAT */ );
|
||
|
ConVar tf_boss_alpha_skill_shot_count( "tf_boss_alpha_skill_shot_count", "3"/*, FCVAR_CHEAT */ );
|
||
|
ConVar tf_boss_alpha_skill_shot_precision_time( "tf_boss_alpha_skill_shot_precision_time", "6"/*, FCVAR_CHEAT */ );
|
||
|
ConVar tf_boss_alpha_skill_shot_hard_hit_damage( "tf_boss_alpha_skill_shot_hard_hit_damage", "40"/*, FCVAR_CHEAT */ );
|
||
|
ConVar tf_boss_alpha_skill_shot_hard_hit_z( "tf_boss_alpha_skill_shot_hard_hit_z", "0"/*, FCVAR_CHEAT */ );
|
||
|
ConVar tf_boss_alpha_skill_shot_damage_sponge_total( "tf_boss_alpha_skill_shot_damage_sponge_total", "500"/*, FCVAR_CHEAT */ );
|
||
|
ConVar tf_boss_alpha_skill_shot_damage_sponge_decay( "tf_boss_alpha_skill_shot_damage_sponge_decay", "100"/*, FCVAR_CHEAT */ );
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------------------------------
|
||
|
void CBossAlpha::ResetSkillShots( void )
|
||
|
{
|
||
|
m_skillShotComboTimer.Invalidate();
|
||
|
m_skillShotCount = 0;
|
||
|
|
||
|
m_isPrecisionShotDone = false;
|
||
|
m_precisionSkillShotTimer.Invalidate();
|
||
|
|
||
|
for( int i=0; i<PRECISION_SHOT_COUNT; ++i )
|
||
|
{
|
||
|
m_isPrecisionShotHit[i] = false;
|
||
|
}
|
||
|
|
||
|
m_isDamageSpongeSkillShotDone = false;
|
||
|
m_damageSpongeSkillShotAmount = 0.0f;
|
||
|
|
||
|
m_isHardHitSkillShotDone = false;
|
||
|
|
||
|
if ( g_pMonsterResource )
|
||
|
{
|
||
|
g_pMonsterResource->HideSkillShotComboMeter();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------------------------------
|
||
|
void CBossAlpha::OnSkillShotComboStarted( void )
|
||
|
{
|
||
|
m_skillShotComboTimer.Start( tf_boss_alpha_skill_shot_combo_time.GetFloat() );
|
||
|
|
||
|
if ( g_pMonsterResource )
|
||
|
{
|
||
|
g_pMonsterResource->StartSkillShotComboMeter( tf_boss_alpha_skill_shot_combo_time.GetFloat() );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------------------------------
|
||
|
void CBossAlpha::OnSkillShot( void )
|
||
|
{
|
||
|
if ( !m_skillShotComboTimer.HasStarted() || m_skillShotComboTimer.IsElapsed() )
|
||
|
{
|
||
|
// start a new combo
|
||
|
OnSkillShotComboStarted();
|
||
|
m_skillShotCount = 1;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// combo in progress
|
||
|
++m_skillShotCount;
|
||
|
|
||
|
if ( g_pMonsterResource )
|
||
|
{
|
||
|
g_pMonsterResource->IncrementSkillShotComboMeter();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( m_skillShotCount >= tf_boss_alpha_skill_shot_count.GetInt() )
|
||
|
{
|
||
|
AddCondition( STUNNED );
|
||
|
EmitSound( "RobotBoss.Vulnerable" );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------------------------------
|
||
|
//
|
||
|
// Invoked when we are struck. Check if a vulnerability was hit, and update the skill shot combo
|
||
|
//
|
||
|
bool CBossAlpha::CheckSkillShots( const CTakeDamageInfo &info )
|
||
|
{
|
||
|
if ( !HasAbility( CBossAlpha::CAN_BE_STUNNED ) || !info.GetAttacker() )
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// skill shots are not available until the boss recovers
|
||
|
if ( IsInCondition( STUNNED ) )
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( tf_boss_alpha_always_stun.GetBool() )
|
||
|
{
|
||
|
m_skillShotComboTimer.Start( 1.0f );
|
||
|
m_skillShotCount = 999;
|
||
|
OnSkillShot();
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
// const Vector &hitSpot = info.GetDamagePosition();
|
||
|
|
||
|
CBaseEntity *inflictor = info.GetInflictor();
|
||
|
if ( !inflictor )
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
Vector hitDir = m_lastTraceAttackDir;
|
||
|
|
||
|
/*
|
||
|
Vector hitDir = inflictor->GetAbsVelocity();
|
||
|
|
||
|
if ( inflictor->IsPlayer() )
|
||
|
{
|
||
|
hitDir = hitSpot - inflictor->EyePosition();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
CObjectSentrygun *sentry = dynamic_cast< CObjectSentrygun * >( inflictor );
|
||
|
if ( sentry )
|
||
|
{
|
||
|
hitDir = hitSpot - sentry->EyePosition();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
hitDir.NormalizeInPlace();
|
||
|
*/
|
||
|
|
||
|
Vector traceFrom = m_lastTraceAttackTrace.startpos - m_lastTraceAttackDir * 10.0f;
|
||
|
Vector traceTo = m_lastTraceAttackTrace.endpos + m_lastTraceAttackDir * 100.0f;
|
||
|
|
||
|
trace_t result;
|
||
|
//UTIL_TraceLine( hitSpot - 50.0f * hitDir, hitSpot + 50.0f * hitDir, MASK_SOLID | CONTENTS_HITBOX, inflictor, COLLISION_GROUP_NONE, &result );
|
||
|
UTIL_TraceLine( traceFrom, traceTo, MASK_SOLID | CONTENTS_HITBOX, inflictor, COLLISION_GROUP_NONE, &result );
|
||
|
|
||
|
if ( tf_boss_alpha_debug_skill_shots.GetBool() )
|
||
|
{
|
||
|
if ( result.hitbox != 0 )
|
||
|
{
|
||
|
NDebugOverlay::HorzArrow( traceFrom, traceTo, 3.0f, 0, 255, 0, 255, true, 9999.9f );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
NDebugOverlay::HorzArrow( traceFrom, traceTo, 3.0f, 255, 0, 0, 255, true, 9999.9f );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( !result.DidHit() )
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
switch( result.hitbox )
|
||
|
{
|
||
|
case HITBOX_SKILL_PRECISION_1:
|
||
|
case HITBOX_SKILL_PRECISION_2:
|
||
|
case HITBOX_SKILL_PRECISION_3:
|
||
|
{
|
||
|
int which = result.hitbox - HITBOX_SKILL_PRECISION_1;
|
||
|
|
||
|
if ( !m_isPrecisionShotDone && !m_isPrecisionShotHit[ which ] )
|
||
|
{
|
||
|
if ( !m_precisionSkillShotTimer.HasStarted() )
|
||
|
{
|
||
|
m_precisionSkillShotTimer.Start( tf_boss_alpha_skill_shot_precision_time.GetFloat() );
|
||
|
}
|
||
|
|
||
|
m_isPrecisionShotHit[ which ] = true;
|
||
|
UTIL_ClientPrintAll( HUD_PRINTTALK, CFmtStr( "PRECISION SHOT %d...", which+1 ) );
|
||
|
|
||
|
int i;
|
||
|
for( i=0; i<PRECISION_SHOT_COUNT; ++i )
|
||
|
{
|
||
|
if ( !m_isPrecisionShotHit[i] )
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if ( i == PRECISION_SHOT_COUNT )
|
||
|
{
|
||
|
// successfully completed the precision shot
|
||
|
m_isPrecisionShotDone = true;
|
||
|
UTIL_ClientPrintAll( HUD_PRINTTALK, "PRECISION SKILL SHOT!" );
|
||
|
EmitSound( CFmtStr( "RobotBoss.PreciseHit%dSkillShot", which+1 ) );
|
||
|
OnSkillShot();
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case HITBOX_SKILL_DAMAGE_SPONGE:
|
||
|
if ( !m_isDamageSpongeSkillShotDone )
|
||
|
{
|
||
|
m_damageSpongeSkillShotAmount += info.GetDamage();
|
||
|
|
||
|
if ( m_damageSpongeSkillShotAmount > tf_boss_alpha_skill_shot_damage_sponge_total.GetFloat() )
|
||
|
{
|
||
|
// successfully completed the damage sponge shot
|
||
|
m_isDamageSpongeSkillShotDone = true;
|
||
|
m_damageSpongeSkillShotAmount = 0.0f;
|
||
|
UTIL_ClientPrintAll( HUD_PRINTTALK, "DAMAGE SPONGE SKILL SHOT!" );
|
||
|
EmitSound( "RobotBoss.DamageSpongeSkillShot" );
|
||
|
OnSkillShot();
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
UTIL_ClientPrintAll( HUD_PRINTTALK, CFmtStr( "DAMAGE SPONGE = %3.2f", m_damageSpongeSkillShotAmount ) );
|
||
|
return true;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case HITBOX_SKILL_HARD_HIT:
|
||
|
if ( !m_isHardHitSkillShotDone )
|
||
|
{
|
||
|
if ( info.GetDamage() > tf_boss_alpha_skill_shot_hard_hit_damage.GetFloat() )
|
||
|
{
|
||
|
// make sure player hit from above
|
||
|
if ( info.GetAttacker() )
|
||
|
{
|
||
|
Vector toAttacker = info.GetAttacker()->EyePosition() - m_lastTraceAttackTrace.endpos;
|
||
|
toAttacker.NormalizeInPlace();
|
||
|
|
||
|
if ( toAttacker.z > tf_boss_alpha_skill_shot_hard_hit_z.GetFloat() )
|
||
|
{
|
||
|
// successfully completed the hard hit shot
|
||
|
m_isHardHitSkillShotDone = true;
|
||
|
UTIL_ClientPrintAll( HUD_PRINTTALK, "HARD HIT SKILL SHOT!" );
|
||
|
EmitSound( "RobotBoss.HardHitSkillShot" );
|
||
|
OnSkillShot();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------------------------------
|
||
|
void CBossAlpha::UpdateSkillShots( void )
|
||
|
{
|
||
|
m_damageSpongeSkillShotAmount -= tf_boss_alpha_skill_shot_damage_sponge_decay.GetFloat() * gpGlobals->frametime;
|
||
|
if ( m_damageSpongeSkillShotAmount < 0.0f )
|
||
|
{
|
||
|
m_damageSpongeSkillShotAmount = 0.0f;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
UTIL_ClientPrintAll( HUD_PRINTTALK, CFmtStr( "DAMAGE SPONGE = %3.2f/%3.2f", m_damageSpongeSkillShotAmount, tf_boss_alpha_skill_shot_damage_sponge_total.GetFloat() ) );
|
||
|
}
|
||
|
|
||
|
if ( m_skillShotComboTimer.HasStarted() && m_skillShotComboTimer.IsElapsed() )
|
||
|
{
|
||
|
// took too long to perform skill shots - reset combo
|
||
|
ResetSkillShots();
|
||
|
UTIL_ClientPrintAll( HUD_PRINTTALK, "SKILL SHOT CHAIN FAILED - TOO SLOW!" );
|
||
|
}
|
||
|
|
||
|
if ( !m_isPrecisionShotDone && m_precisionSkillShotTimer.HasStarted() && m_precisionSkillShotTimer.IsElapsed() )
|
||
|
{
|
||
|
// took too long to hit all the precision targets - reset
|
||
|
m_precisionSkillShotTimer.Invalidate();
|
||
|
for( int i=0; i<PRECISION_SHOT_COUNT; ++i )
|
||
|
{
|
||
|
m_isPrecisionShotHit[i] = false;
|
||
|
}
|
||
|
UTIL_ClientPrintAll( HUD_PRINTTALK, "PRECISION SHOTS RESET - TOO SLOW!" );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------------------------------
|
||
|
int CBossAlpha::OnTakeDamage_Alive( const CTakeDamageInfo &rawInfo )
|
||
|
{
|
||
|
CTakeDamageInfo info = rawInfo;
|
||
|
|
||
|
// don't take damage from myself
|
||
|
if ( info.GetAttacker() == this )
|
||
|
{
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
// weapon-specific damage modification
|
||
|
info.SetDamage( ModifyBossDamage( info ) );
|
||
|
|
||
|
// do the critical damage increase
|
||
|
if ( info.GetDamageType() & DMG_CRITICAL )
|
||
|
{
|
||
|
info.SetDamage( info.GetDamage() * TF_DAMAGE_CRIT_MULTIPLIER );
|
||
|
}
|
||
|
|
||
|
bool isSkillShot = false;
|
||
|
if ( CheckSkillShots( info ) )
|
||
|
{
|
||
|
isSkillShot = true;
|
||
|
|
||
|
// skill shots don't deal damage
|
||
|
info.SetDamage( 0 );
|
||
|
}
|
||
|
|
||
|
bool isHeadHit = false;
|
||
|
if ( IsInCondition( VULNERABLE_TO_STUN ) )
|
||
|
{
|
||
|
// track head damage when vulnerable
|
||
|
Vector headPos;
|
||
|
QAngle headAngles;
|
||
|
if ( GetAttachment( "head", headPos, headAngles ) )
|
||
|
{
|
||
|
Vector damagePos = info.GetDamagePosition();
|
||
|
|
||
|
if ( tf_boss_alpha_debug_damage.GetBool() )
|
||
|
{
|
||
|
NDebugOverlay::Cross3D( headPos, 5.0f, 255, 0, 0, true, 5.0f );
|
||
|
NDebugOverlay::Cross3D( damagePos, 5.0f, 0, 255, 0, true, 5.0f );
|
||
|
NDebugOverlay::Line( damagePos, headPos, 255, 255, 0, true, 5.0f );
|
||
|
}
|
||
|
|
||
|
isHeadHit = ( damagePos - headPos ).IsLengthLessThan( tf_boss_alpha_head_radius.GetFloat() );
|
||
|
|
||
|
if ( isHeadHit )
|
||
|
{
|
||
|
// hit the head
|
||
|
AccumulateStunDamage( info.GetDamage() );
|
||
|
DispatchParticleEffect( "asplode_hoodoo_embers", info.GetDamagePosition(), GetAbsAngles() );
|
||
|
|
||
|
if ( tf_boss_alpha_debug_damage.GetBool() )
|
||
|
{
|
||
|
DevMsg( "Stun dmg = %f\n", GetStunDamage() );
|
||
|
NDebugOverlay::Circle( headPos, tf_boss_alpha_head_radius.GetFloat(), 255, 0, 0, 255, true, 5.0f );
|
||
|
}
|
||
|
}
|
||
|
else if ( tf_boss_alpha_debug_damage.GetBool() )
|
||
|
{
|
||
|
NDebugOverlay::Circle( headPos, tf_boss_alpha_head_radius.GetFloat(), 255, 255, 0, 255, true, 5.0f );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// take extra damage when stunned
|
||
|
if ( IsInCondition( STUNNED ) )
|
||
|
{
|
||
|
info.SetDamage( info.GetDamage() * tf_boss_alpha_stunned_injury_multiplier.GetFloat() );
|
||
|
|
||
|
if ( m_ouchTimer.IsElapsed() )
|
||
|
{
|
||
|
m_ouchTimer.Start( 1.0f );
|
||
|
EmitSound( "RobotBoss.Hurt" );
|
||
|
}
|
||
|
}
|
||
|
else if ( !isHeadHit && !isSkillShot )
|
||
|
{
|
||
|
// invulnerable until stunned
|
||
|
if ( m_ricochetSoundTimer.IsElapsed() )
|
||
|
{
|
||
|
TFGameRules()->BroadcastSound( 255, "Weapon_Crowbar.Melee_HitWorld" );
|
||
|
m_ricochetSoundTimer.Start( 0.15f );
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
// keep a list of everyone who hurt me, and when
|
||
|
if ( info.GetAttacker() && info.GetAttacker()->MyCombatCharacterPointer() && !InSameTeam( info.GetAttacker() ) )
|
||
|
{
|
||
|
CBaseCombatCharacter *attacker = info.GetAttacker()->MyCombatCharacterPointer();
|
||
|
|
||
|
// sentry guns are first class attackers
|
||
|
if ( info.GetInflictor() )
|
||
|
{
|
||
|
CObjectSentrygun *sentry = dynamic_cast< CObjectSentrygun * >( info.GetInflictor() );
|
||
|
if ( sentry )
|
||
|
{
|
||
|
attacker = sentry;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
RememberAttacker( attacker, info.GetDamage(), ( info.GetDamageType() & DMG_CRITICAL ) ? true : false );
|
||
|
|
||
|
CTFPlayer *playerAttacker = ToTFPlayer( attacker );
|
||
|
if ( playerAttacker )
|
||
|
{
|
||
|
for( int i=0; i<playerAttacker->m_Shared.GetNumHealers(); ++i )
|
||
|
{
|
||
|
CTFPlayer *medic = ToTFPlayer( playerAttacker->m_Shared.GetHealerByIndex( i ) );
|
||
|
if ( medic )
|
||
|
{
|
||
|
// medics healing my attacker are also considered attackers
|
||
|
RememberAttacker( medic, 0, 0 );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// if we don't have an attack target yet, we do now
|
||
|
if ( !HasAttackTarget() )
|
||
|
{
|
||
|
SetAttackTarget( attacker );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
// fire event for client combat text, beep, etc.
|
||
|
IGameEvent *event = gameeventmanager->CreateEvent( "npc_hurt" );
|
||
|
if ( event )
|
||
|
{
|
||
|
event->SetInt( "entindex", entindex() );
|
||
|
event->SetInt( "health", MAX( 0, GetHealth() ) );
|
||
|
event->SetInt( "damageamount", info.GetDamage() );
|
||
|
event->SetBool( "crit", ( info.GetDamageType() & DMG_CRITICAL ) ? true : false );
|
||
|
|
||
|
CTFPlayer *attackerPlayer = ToTFPlayer( info.GetAttacker() );
|
||
|
if ( attackerPlayer )
|
||
|
{
|
||
|
event->SetInt( "attacker_player", attackerPlayer->GetUserID() );
|
||
|
|
||
|
if ( attackerPlayer->GetActiveTFWeapon() )
|
||
|
{
|
||
|
event->SetInt( "weaponid", attackerPlayer->GetActiveTFWeapon()->GetWeaponID() );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
event->SetInt( "weaponid", 0 );
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// hurt by world
|
||
|
event->SetInt( "attacker_player", 0 );
|
||
|
event->SetInt( "weaponid", 0 );
|
||
|
}
|
||
|
|
||
|
gameeventmanager->FireEvent( event );
|
||
|
}
|
||
|
|
||
|
int result = BaseClass::OnTakeDamage_Alive( info );
|
||
|
|
||
|
// emit injury outputs
|
||
|
float healthPercentage = (float)GetHealth() / (float)GetMaxHealth();
|
||
|
|
||
|
if ( m_lastHealthPercentage > 0.9f && healthPercentage < 0.9f )
|
||
|
{
|
||
|
m_outputOnHealthBelow90Percent.FireOutput( this, this );
|
||
|
}
|
||
|
else if ( m_lastHealthPercentage > 0.8f && healthPercentage < 0.8f )
|
||
|
{
|
||
|
m_outputOnHealthBelow80Percent.FireOutput( this, this );
|
||
|
}
|
||
|
else if ( m_lastHealthPercentage > 0.7f && healthPercentage < 0.7f )
|
||
|
{
|
||
|
m_outputOnHealthBelow70Percent.FireOutput( this, this );
|
||
|
}
|
||
|
else if ( m_lastHealthPercentage > 0.6f && healthPercentage < 0.6f )
|
||
|
{
|
||
|
m_outputOnHealthBelow60Percent.FireOutput( this, this );
|
||
|
}
|
||
|
else if ( m_lastHealthPercentage > 0.5f && healthPercentage < 0.5f )
|
||
|
{
|
||
|
m_outputOnHealthBelow50Percent.FireOutput( this, this );
|
||
|
}
|
||
|
else if ( m_lastHealthPercentage > 0.4f && healthPercentage < 0.4f )
|
||
|
{
|
||
|
m_outputOnHealthBelow40Percent.FireOutput( this, this );
|
||
|
}
|
||
|
else if ( m_lastHealthPercentage > 0.3f && healthPercentage < 0.3f )
|
||
|
{
|
||
|
m_outputOnHealthBelow30Percent.FireOutput( this, this );
|
||
|
}
|
||
|
else if ( m_lastHealthPercentage > 0.2f && healthPercentage < 0.2f )
|
||
|
{
|
||
|
m_outputOnHealthBelow20Percent.FireOutput( this, this );
|
||
|
}
|
||
|
else if ( m_lastHealthPercentage > 0.1f && healthPercentage < 0.1f )
|
||
|
{
|
||
|
m_outputOnHealthBelow10Percent.FireOutput( this, this );
|
||
|
}
|
||
|
|
||
|
m_lastHealthPercentage = healthPercentage;
|
||
|
|
||
|
if ( g_pMonsterResource )
|
||
|
{
|
||
|
g_pMonsterResource->SetBossHealthPercentage( healthPercentage );
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
|
||
|
//---------------------------------------------------------------------------------------------
|
||
|
// Returns true if we're in a condition that means we can't start another action
|
||
|
bool CBossAlpha::IsBusy( void ) const
|
||
|
{
|
||
|
return IsInCondition( (Condition)( CHARGING | STUNNED | VULNERABLE_TO_STUN | BUSY ) );
|
||
|
}
|
||
|
|
||
|
|
||
|
//---------------------------------------------------------------------------------------------
|
||
|
void CBossAlpha::RememberAttacker( CBaseCombatCharacter *attacker, float damage, bool wasCritical )
|
||
|
{
|
||
|
AttackerInfo attackerInfo;
|
||
|
|
||
|
attackerInfo.m_attacker = attacker;
|
||
|
attackerInfo.m_timestamp = gpGlobals->curtime;
|
||
|
attackerInfo.m_damage = damage;
|
||
|
attackerInfo.m_wasCritical = wasCritical;
|
||
|
|
||
|
m_attackerVector.AddToHead( attackerInfo );
|
||
|
}
|
||
|
|
||
|
|
||
|
//----------------------------------------------------------------------------------
|
||
|
CTFPlayer *CBossAlpha::GetClosestMinionPrisoner( void )
|
||
|
{
|
||
|
CUtlVector< CBotNPCMinion * > minionVector;
|
||
|
CBotNPCMinion *minion = NULL;
|
||
|
while( ( minion = (CBotNPCMinion *)gEntList.FindEntityByClassname( minion, "bot_npc_minion" ) ) != NULL )
|
||
|
{
|
||
|
minionVector.AddToTail( minion );
|
||
|
}
|
||
|
|
||
|
CTFPlayer *closeCapture = NULL;
|
||
|
float captureRangeSq = FLT_MAX;
|
||
|
|
||
|
for( int m=0; m<minionVector.Count(); ++m )
|
||
|
{
|
||
|
minion = minionVector[m];
|
||
|
|
||
|
if ( minion->HasTarget() )
|
||
|
{
|
||
|
CTFPlayer *victim = minion->GetTarget();
|
||
|
if ( victim->m_Shared.InCond( TF_COND_STUNNED ) )
|
||
|
{
|
||
|
// they've got one!
|
||
|
float rangeSq = GetRangeSquaredTo( victim );
|
||
|
if ( rangeSq < captureRangeSq )
|
||
|
{
|
||
|
closeCapture = victim;
|
||
|
captureRangeSq = rangeSq;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return closeCapture;
|
||
|
}
|
||
|
|
||
|
|
||
|
//----------------------------------------------------------------------------------
|
||
|
bool CBossAlpha::IsPrisonerOfMinion( CBaseCombatCharacter *victim )
|
||
|
{
|
||
|
if ( !victim->IsPlayer() )
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
CUtlVector< CBotNPCMinion * > minionVector;
|
||
|
CBotNPCMinion *minion = NULL;
|
||
|
while( ( minion = (CBotNPCMinion *)gEntList.FindEntityByClassname( minion, "bot_npc_minion" ) ) != NULL )
|
||
|
{
|
||
|
minionVector.AddToTail( minion );
|
||
|
}
|
||
|
|
||
|
for( int m=0; m<minionVector.Count(); ++m )
|
||
|
{
|
||
|
minion = minionVector[m];
|
||
|
|
||
|
if ( minion->HasTarget() && minion->GetTarget() == victim )
|
||
|
{
|
||
|
if ( minion->GetTarget()->m_Shared.InCond( TF_COND_STUNNED ) )
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
|
||
|
//----------------------------------------------------------------------------------
|
||
|
void CBossAlpha::UpdateDamagePerSecond( void )
|
||
|
{
|
||
|
m_lastDamagePerSecond = m_currentDamagePerSecond;
|
||
|
|
||
|
m_currentDamagePerSecond = 0.0f;
|
||
|
|
||
|
const float windowDuration = 10.0f; // 5.0f;
|
||
|
int i;
|
||
|
|
||
|
m_threatVector.RemoveAll();
|
||
|
|
||
|
for( i=0; i<m_attackerVector.Count(); ++i )
|
||
|
{
|
||
|
float age = gpGlobals->curtime - m_attackerVector[i].m_timestamp;
|
||
|
|
||
|
if ( age > windowDuration )
|
||
|
{
|
||
|
// too old
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
float decayedDamage = ( ( windowDuration - age ) / windowDuration ) * m_attackerVector[i].m_damage;
|
||
|
|
||
|
m_currentDamagePerSecond += decayedDamage;
|
||
|
|
||
|
CBaseCombatCharacter *attacker = m_attackerVector[i].m_attacker;
|
||
|
|
||
|
if ( attacker && attacker->IsAlive() )
|
||
|
{
|
||
|
int j;
|
||
|
for( j=0; j<m_threatVector.Count(); ++j )
|
||
|
{
|
||
|
if ( m_threatVector[j].m_who == attacker )
|
||
|
{
|
||
|
m_threatVector[j].m_threat += decayedDamage;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( j >= m_threatVector.Count() )
|
||
|
{
|
||
|
// new threat
|
||
|
ThreatInfo threat;
|
||
|
threat.m_who = attacker;
|
||
|
threat.m_threat = decayedDamage;
|
||
|
m_threatVector.AddToTail( threat );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// if ( m_currentDamagePerSecond > 0.0001f )
|
||
|
// {
|
||
|
// DevMsg( "%3.2f: dps = %3.2f\n", gpGlobals->curtime, m_currentDamagePerSecond );
|
||
|
// }
|
||
|
}
|
||
|
|
||
|
|
||
|
//----------------------------------------------------------------------------------
|
||
|
const CBossAlpha::ThreatInfo *CBossAlpha::GetMaxThreat( void ) const
|
||
|
{
|
||
|
int maxThreatIndex = -1;
|
||
|
|
||
|
for( int i=0; i<m_threatVector.Count(); ++i )
|
||
|
{
|
||
|
if ( maxThreatIndex < 0 || m_threatVector[i].m_threat > m_threatVector[ maxThreatIndex ].m_threat )
|
||
|
{
|
||
|
maxThreatIndex = i;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( maxThreatIndex < 0 )
|
||
|
{
|
||
|
// no threat yet
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
return &m_threatVector[ maxThreatIndex ];
|
||
|
}
|
||
|
|
||
|
|
||
|
//----------------------------------------------------------------------------------
|
||
|
const CBossAlpha::ThreatInfo *CBossAlpha::GetThreat( CBaseCombatCharacter *who ) const
|
||
|
{
|
||
|
for( int i=0; i<m_threatVector.Count(); ++i )
|
||
|
{
|
||
|
if ( m_threatVector[i].m_who == who )
|
||
|
{
|
||
|
return &m_threatVector[i];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
|
||
|
//----------------------------------------------------------------------------------
|
||
|
void CBossAlpha::UpdateAttackTarget( void )
|
||
|
{
|
||
|
if ( m_isAttackTargetLocked && HasAttackTarget() )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// who is most dangerous to me at the moment
|
||
|
const ThreatInfo *maxThreat = GetMaxThreat();
|
||
|
|
||
|
if ( !maxThreat )
|
||
|
{
|
||
|
// nobody is hurting me at the moment
|
||
|
|
||
|
if ( HasAttackTarget() )
|
||
|
{
|
||
|
// stay focused on current target
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// we have no current target, either
|
||
|
|
||
|
// if my minions have captured someone, go get them
|
||
|
CTFPlayer *closeCapture = GetClosestMinionPrisoner();
|
||
|
if ( closeCapture )
|
||
|
{
|
||
|
SetAttackTarget( closeCapture );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// if we see an enemy, attack them
|
||
|
CBaseCombatCharacter *visible = GetNearestVisibleEnemy();
|
||
|
if ( visible )
|
||
|
{
|
||
|
SetAttackTarget( visible );
|
||
|
}
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// we are under attack, if we don't have a target, attack the highest threat
|
||
|
if ( !HasAttackTarget() )
|
||
|
{
|
||
|
SetAttackTarget( maxThreat->m_who );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if ( IsAttackTarget( maxThreat->m_who ) )
|
||
|
{
|
||
|
// our current target is still dealing the most damage to us
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// switch to new threat if is is more dangerous
|
||
|
const ThreatInfo *attackTargetThreat = GetThreat( GetAttackTarget() );
|
||
|
|
||
|
if ( !attackTargetThreat || maxThreat->m_threat > attackTargetThreat->m_threat + tf_boss_alpha_threat_tolerance.GetFloat() )
|
||
|
{
|
||
|
// change threats
|
||
|
SetAttackTarget( maxThreat->m_who );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//----------------------------------------------------------------------------------
|
||
|
void CBossAlpha::RemoveCondition( Condition c )
|
||
|
{
|
||
|
if ( c == STUNNED )
|
||
|
{
|
||
|
// reset the accumulator
|
||
|
ClearStunDamage();
|
||
|
|
||
|
ResetSkillShots();
|
||
|
}
|
||
|
|
||
|
m_conditionFlags &= ~c;
|
||
|
}
|
||
|
|
||
|
|
||
|
//---------------------------------------------------------------------------------------------
|
||
|
void CBossAlpha::Update( void )
|
||
|
{
|
||
|
BaseClass::Update();
|
||
|
|
||
|
UpdateNearestVisibleEnemy();
|
||
|
UpdateDamagePerSecond();
|
||
|
UpdateAttackTarget();
|
||
|
UpdateSkillShots();
|
||
|
|
||
|
if ( m_damagePoseParameter < 0 )
|
||
|
{
|
||
|
m_damagePoseParameter = LookupPoseParameter( "damage" );
|
||
|
}
|
||
|
|
||
|
if ( m_damagePoseParameter >= 0 )
|
||
|
{
|
||
|
SetPoseParameter( m_damagePoseParameter, 1.0f - ( (float)GetHealth() / (float)GetMaxHealth() ) );
|
||
|
}
|
||
|
|
||
|
// chase down players who taunt me
|
||
|
if ( m_hateTauntTimer.IsElapsed() )
|
||
|
{
|
||
|
CUtlVector< CTFPlayer * > playerVector;
|
||
|
CollectPlayers( &playerVector, TF_TEAM_BLUE, COLLECT_ONLY_LIVING_PLAYERS );
|
||
|
|
||
|
for( int i=0; i<playerVector.Count(); ++i )
|
||
|
{
|
||
|
if ( playerVector[i]->IsTaunting() )
|
||
|
{
|
||
|
m_hateTauntTimer.Start( tf_boss_alpha_hate_taunt_cooldown.GetFloat() );
|
||
|
|
||
|
if ( IsLineOfSightClear( playerVector[i], IGNORE_ACTORS ) )
|
||
|
{
|
||
|
// the taunter becomes our new attack target
|
||
|
SetAttackTarget( playerVector[i], tf_boss_alpha_hate_taunt_cooldown.GetFloat() );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//---------------------------------------------------------------------------------------------
|
||
|
bool CBossAlpha::IsIgnored( CTFPlayer *player ) const
|
||
|
{
|
||
|
if ( player->m_Shared.IsStealthed() )
|
||
|
{
|
||
|
if ( player->m_Shared.GetPercentInvisible() < 0.75f )
|
||
|
{
|
||
|
// spy is partially cloaked, and therefore attracts our attention
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( player->m_Shared.InCond( TF_COND_BURNING ) ||
|
||
|
player->m_Shared.InCond( TF_COND_URINE ) ||
|
||
|
player->m_Shared.InCond( TF_COND_STEALTHED_BLINK ) ||
|
||
|
player->m_Shared.InCond( TF_COND_BLEEDING ) )
|
||
|
{
|
||
|
// always notice players with these conditions
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// invisible!
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
|
||
|
//---------------------------------------------------------------------------------------------
|
||
|
void CBossAlpha::UpdateNearestVisibleEnemy( void )
|
||
|
{
|
||
|
if ( !m_nearestVisibleEnemyTimer.IsElapsed() )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
m_nearestVisibleEnemyTimer.Start( tf_boss_alpha_reaction_time.GetFloat() );
|
||
|
|
||
|
// collect everyone
|
||
|
CUtlVector< CTFPlayer * > playerVector;
|
||
|
//CollectPlayers( &playerVector, TF_TEAM_RED, COLLECT_ONLY_LIVING_PLAYERS );
|
||
|
CollectPlayers( &playerVector, TF_TEAM_BLUE, COLLECT_ONLY_LIVING_PLAYERS, APPEND_PLAYERS );
|
||
|
|
||
|
Vector myForward;
|
||
|
GetVectors( &myForward, NULL, NULL );
|
||
|
|
||
|
m_nearestVisibleEnemy = NULL;
|
||
|
float victimRangeSq = FLT_MAX;
|
||
|
|
||
|
for( int i=0; i<playerVector.Count(); ++i )
|
||
|
{
|
||
|
CTFPlayer *victim = playerVector[i];
|
||
|
|
||
|
if ( IsIgnored( victim ) )
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
float rangeSq = GetRangeSquaredTo( playerVector[i] );
|
||
|
if ( rangeSq < victimRangeSq )
|
||
|
{
|
||
|
// FOV check
|
||
|
Vector to = playerVector[i]->WorldSpaceCenter() - WorldSpaceCenter();
|
||
|
to.NormalizeInPlace();
|
||
|
|
||
|
if ( DotProduct( to, myForward ) > -0.7071f )
|
||
|
{
|
||
|
if ( IsLineOfSightClear( playerVector[i] ) )
|
||
|
{
|
||
|
m_nearestVisibleEnemy = playerVector[i];
|
||
|
victimRangeSq = rangeSq;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//---------------------------------------------------------------------------------------------
|
||
|
void CBossAlpha::SetAttackTarget( CBaseCombatCharacter *target, float duration )
|
||
|
{
|
||
|
if ( target && m_attackTarget != NULL && m_attackTarget->IsAlive() && m_attackTargetTimer.HasStarted() && !m_attackTargetTimer.IsElapsed() )
|
||
|
{
|
||
|
// can't switch away from our still valid target yet
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if ( m_attackTarget != target )
|
||
|
{
|
||
|
if ( target )
|
||
|
{
|
||
|
EmitSound( "RobotBoss.Acquire" );
|
||
|
AddGesture( ACT_MP_GESTURE_FLINCH_CHEST );
|
||
|
}
|
||
|
|
||
|
TFGameRules()->SetIT( m_attackTarget );
|
||
|
|
||
|
m_attackTarget = target;
|
||
|
}
|
||
|
|
||
|
if ( duration > 0.0f )
|
||
|
{
|
||
|
m_attackTargetTimer.Start( duration );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_attackTargetTimer.Invalidate();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//---------------------------------------------------------------------------------------------
|
||
|
CBaseCombatCharacter *CBossAlpha::GetAttackTarget( void ) const
|
||
|
{
|
||
|
if ( m_attackTarget != NULL && m_attackTarget->IsAlive() )
|
||
|
{
|
||
|
return m_attackTarget;
|
||
|
}
|
||
|
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
|
||
|
//---------------------------------------------------------------------------------------------
|
||
|
void CBossAlpha::Break( void )
|
||
|
{
|
||
|
CPVSFilter filter( GetAbsOrigin() );
|
||
|
UserMessageBegin( filter, "BreakModel" );
|
||
|
WRITE_SHORT( GetModelIndex() );
|
||
|
WRITE_VEC3COORD( GetAbsOrigin() );
|
||
|
WRITE_ANGLES( GetAbsAngles() );
|
||
|
WRITE_SHORT( GetSkin() );
|
||
|
MessageEnd();
|
||
|
}
|
||
|
|
||
|
|
||
|
//---------------------------------------------------------------------------------------------
|
||
|
void CBossAlpha::CollectPlayersStandingOnMe( CUtlVector< CTFPlayer * > *playerVector )
|
||
|
{
|
||
|
CUtlVector< CTFPlayer * > allPlayerVector;
|
||
|
CollectPlayers( &allPlayerVector, TEAM_ANY, COLLECT_ONLY_LIVING_PLAYERS );
|
||
|
|
||
|
for( int i=0; i<allPlayerVector.Count(); ++i )
|
||
|
{
|
||
|
CTFPlayer *player = allPlayerVector[i];
|
||
|
|
||
|
if ( player->GetGroundEntity() == this )
|
||
|
{
|
||
|
playerVector->AddToTail( player );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//---------------------------------------------------------------------------------------------
|
||
|
void CBossAlpha::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator )
|
||
|
{
|
||
|
// cache the trace info so we can precisely re-trace to find hitbox hits in OnTakeDamage_Alive() later
|
||
|
if ( ptr )
|
||
|
{
|
||
|
m_lastTraceAttackTrace = *ptr;
|
||
|
}
|
||
|
|
||
|
m_lastTraceAttackDir = vecDir;
|
||
|
|
||
|
BaseClass::TraceAttack( info, vecDir, ptr, pAccumulator );
|
||
|
}
|
||
|
|
||
|
|
||
|
//---------------------------------------------------------------------------------------------
|
||
|
// Intention interface
|
||
|
//---------------------------------------------------------------------------------------------
|
||
|
CBossAlphaIntention::CBossAlphaIntention( CBossAlpha *me ) : IIntention( me )
|
||
|
{
|
||
|
m_behavior = new Behavior< CBossAlpha >( new CBossAlphaBehavior );
|
||
|
}
|
||
|
|
||
|
CBossAlphaIntention::~CBossAlphaIntention()
|
||
|
{
|
||
|
delete m_behavior;
|
||
|
}
|
||
|
|
||
|
void CBossAlphaIntention::Reset( void )
|
||
|
{
|
||
|
delete m_behavior;
|
||
|
m_behavior = new Behavior< CBossAlpha >( new CBossAlphaBehavior );
|
||
|
}
|
||
|
|
||
|
void CBossAlphaIntention::Update( void )
|
||
|
{
|
||
|
m_behavior->Update( static_cast< CBossAlpha * >( GetBot() ), GetUpdateInterval() );
|
||
|
}
|
||
|
|
||
|
QueryResultType CBossAlphaIntention::IsPositionAllowed( const INextBot *meBot, const Vector &pos ) const
|
||
|
{
|
||
|
// is this a place we can be?
|
||
|
return ANSWER_YES;
|
||
|
}
|
||
|
|
||
|
|
||
|
//---------------------------------------------------------------------------------------------
|
||
|
// Locomotion interface
|
||
|
//---------------------------------------------------------------------------------------------
|
||
|
CBossAlphaLocomotion::CBossAlphaLocomotion( INextBot *bot ) : NextBotGroundLocomotion( bot )
|
||
|
{
|
||
|
CBossAlpha *me = (CBossAlpha *)GetBot()->GetEntity();
|
||
|
|
||
|
m_runSpeed = me->GetMoveSpeed();
|
||
|
}
|
||
|
|
||
|
|
||
|
//---------------------------------------------------------------------------------------------
|
||
|
float CBossAlphaLocomotion::GetRunSpeed( void ) const
|
||
|
{
|
||
|
CBossAlpha *me = (CBossAlpha *)GetBot()->GetEntity();
|
||
|
|
||
|
return me->IsInCondition( CBossAlpha::CHARGING ) ? 1000.0f : m_runSpeed;
|
||
|
}
|
||
|
|
||
|
|
||
|
//---------------------------------------------------------------------------------------------
|
||
|
// if delta Z is greater than this, we have to jump to get up
|
||
|
float CBossAlphaLocomotion::GetStepHeight( void ) const
|
||
|
{
|
||
|
return 18.0f;
|
||
|
}
|
||
|
|
||
|
|
||
|
//---------------------------------------------------------------------------------------------
|
||
|
// return maximum height of a jump
|
||
|
float CBossAlphaLocomotion::GetMaxJumpHeight( void ) const
|
||
|
{
|
||
|
return 18.0f;
|
||
|
}
|
||
|
|
||
|
|
||
|
//---------------------------------------------------------------------------------------------
|
||
|
// Vision interface
|
||
|
//---------------------------------------------------------------------------------------------
|
||
|
|
||
|
//---------------------------------------------------------------------------------------------
|
||
|
// Return true to completely ignore this entity (may not be in sight when this is called)
|
||
|
bool CBossAlphaVision::IsIgnored( CBaseEntity *subject ) const
|
||
|
{
|
||
|
if ( subject->IsPlayer() )
|
||
|
{
|
||
|
CTFPlayer *enemy = static_cast< CTFPlayer * >( subject );
|
||
|
|
||
|
if ( enemy->m_Shared.InCond( TF_COND_BURNING ) ||
|
||
|
enemy->m_Shared.InCond( TF_COND_URINE ) ||
|
||
|
enemy->m_Shared.InCond( TF_COND_STEALTHED_BLINK ) ||
|
||
|
enemy->m_Shared.InCond( TF_COND_BLEEDING ) )
|
||
|
{
|
||
|
// always notice players with these conditions
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( enemy->m_Shared.IsStealthed() )
|
||
|
{
|
||
|
if ( enemy->m_Shared.GetPercentInvisible() < 0.75f )
|
||
|
{
|
||
|
// spy is partially cloaked, and therefore attracts our attention
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// invisible!
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
if ( enemy->IsPlacingSapper() )
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( enemy->m_Shared.InCond( TF_COND_DISGUISING ) )
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( enemy->m_Shared.InCond( TF_COND_DISGUISED ) && enemy->m_Shared.GetDisguiseTeam() == GetBot()->GetEntity()->GetTeamNumber() )
|
||
|
{
|
||
|
// spy is disguised as a member of my team
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
#endif // TF_RAID_MODE
|