summaryrefslogtreecommitdiff
path: root/game/server/tf/player_vs_environment/boss_alpha
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /game/server/tf/player_vs_environment/boss_alpha
downloadarchived-source-engine-2018-hl2-src-master.tar.xz
archived-source-engine-2018-hl2-src-master.zip
Diffstat (limited to 'game/server/tf/player_vs_environment/boss_alpha')
-rw-r--r--game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_behavior.cpp109
-rw-r--r--game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_behavior.h30
-rw-r--r--game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_chase_victim.cpp170
-rw-r--r--game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_chase_victim.h41
-rw-r--r--game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_get_off_me.cpp93
-rw-r--r--game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_get_off_me.h27
-rw-r--r--game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_guard_spot.cpp129
-rw-r--r--game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_guard_spot.h28
-rw-r--r--game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_launch_grenades.cpp204
-rw-r--r--game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_launch_grenades.h36
-rw-r--r--game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_launch_rockets.cpp119
-rw-r--r--game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_launch_rockets.h38
-rw-r--r--game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_lost_victim.cpp64
-rw-r--r--game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_lost_victim.h28
-rw-r--r--game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_nuke_attack.cpp208
-rw-r--r--game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_nuke_attack.h31
-rw-r--r--game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_stunned.cpp171
-rw-r--r--game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_stunned.h39
-rw-r--r--game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_tactical_monitor.cpp81
-rw-r--r--game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_tactical_monitor.h32
-rw-r--r--game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_wait_for_players.cpp79
-rw-r--r--game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_wait_for_players.h25
-rw-r--r--game/server/tf/player_vs_environment/boss_alpha/boss_alpha.cpp1385
-rw-r--r--game/server/tf/player_vs_environment/boss_alpha/boss_alpha.h508
24 files changed, 3675 insertions, 0 deletions
diff --git a/game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_behavior.cpp b/game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_behavior.cpp
new file mode 100644
index 0000000..6cfe287
--- /dev/null
+++ b/game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_behavior.cpp
@@ -0,0 +1,109 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// boss_alpha_behavior.cpp
+// Michael Booth, November 2010
+
+#include "cbase.h"
+
+#ifdef TF_RAID_MODE
+
+#include "CRagdollMagnet.h"
+#include "tf_gamerules.h"
+#include "tf_shareddefs.h"
+#include "player_vs_environment/monster_resource.h"
+#include "player_vs_environment/boss_alpha/boss_alpha.h"
+#include "player_vs_environment/boss_alpha/behavior/boss_alpha_behavior.h"
+#include "player_vs_environment/boss_alpha/behavior/boss_alpha_tactical_monitor.h"
+
+
+//---------------------------------------------------------------------------------------------
+Action< CBossAlpha > *CBossAlphaBehavior::InitialContainedAction( CBossAlpha *me )
+{
+ return new CBossAlphaTacticalMonitor;
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CBossAlpha > CBossAlphaBehavior::Update( CBossAlpha *me, float interval )
+{
+ if ( m_vocalTimer.IsElapsed() )
+ {
+ m_vocalTimer.Start( RandomFloat( 3.0f, 5.0f ) );
+
+ if ( !me->IsBusy() )
+ {
+ me->EmitSound( "RobotBoss.Vocalize" );
+ }
+ }
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+EventDesiredResult< CBossAlpha > CBossAlphaBehavior::OnKilled( CBossAlpha *me, const CTakeDamageInfo &info )
+{
+ // relay the event to the map logic
+ me->m_outputOnKilled.FireOutput( me, me );
+
+ // Calculate death force
+ Vector forceVector = me->CalcDamageForceVector( info );
+
+ // See if there's a ragdoll magnet that should influence our force.
+ CRagdollMagnet *magnet = CRagdollMagnet::FindBestMagnet( me );
+ if ( magnet )
+ {
+ forceVector += magnet->GetForceVector( me );
+ }
+
+ if ( me->IsMiniBoss() )
+ {
+ me->EmitSound( "Cart.Explode" );
+ me->BecomeRagdoll( info, forceVector );
+
+ if ( g_pMonsterResource )
+ {
+ g_pMonsterResource->HideBossHealthMeter();
+ }
+ }
+ else
+ {
+ // full end-of-game boss
+ UTIL_Remove( me );
+
+ if ( TFGameRules()->IsBossBattleMode() )
+ {
+ // check that ALL bosses are dead
+ bool isBossBattleWon = true;
+
+ CBossAlpha *boss = NULL;
+ while( ( boss = (CBossAlpha *)gEntList.FindEntityByClassname( boss, "boss_alpha" ) ) != NULL )
+ {
+ if ( !me->IsSelf( boss ) && boss->IsAlive() && !boss->IsMiniBoss() )
+ {
+ isBossBattleWon = false;
+ }
+ }
+
+ if ( isBossBattleWon )
+ {
+ TFGameRules()->SetWinningTeam( TF_TEAM_BLUE, WINREASON_OPPONENTS_DEAD );
+
+ if ( g_pMonsterResource )
+ {
+ g_pMonsterResource->HideBossHealthMeter();
+ }
+ }
+ }
+ }
+
+ return TryDone();
+}
+
+
+//---------------------------------------------------------------------------------------------
+EventDesiredResult< CBossAlpha > CBossAlphaBehavior::OnContact( CBossAlpha *me, CBaseEntity *other, CGameTrace *result )
+{
+ return TryContinue();
+}
+
+#endif // TF_RAID_MODE
diff --git a/game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_behavior.h b/game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_behavior.h
new file mode 100644
index 0000000..c637bf5
--- /dev/null
+++ b/game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_behavior.h
@@ -0,0 +1,30 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// boss_alpha_behavior.h
+// Michael Booth, November 2010
+
+#ifndef BOSS_ALPHA_BEHAVIOR_H
+#define BOSS_ALPHA_BEHAVIOR_H
+
+#ifdef TF_RAID_MODE
+
+//---------------------------------------------------------------------------------------------
+class CBossAlphaBehavior : public Action< CBossAlpha >
+{
+public:
+ virtual Action< CBossAlpha > *InitialContainedAction( CBossAlpha *me );
+
+ virtual ActionResult< CBossAlpha > Update( CBossAlpha *me, float interval );
+
+ virtual EventDesiredResult< CBossAlpha > OnKilled( CBossAlpha *me, const CTakeDamageInfo &info );
+ virtual EventDesiredResult< CBossAlpha > OnContact( CBossAlpha *me, CBaseEntity *other, CGameTrace *result = NULL );
+
+ virtual const char *GetName( void ) const { return "Behavior"; } // return name of this action
+
+private:
+ CountdownTimer m_vocalTimer;
+};
+
+
+#endif // TF_RAID_MODE
+
+#endif // BOSS_ALPHA_BEHAVIOR_H
diff --git a/game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_chase_victim.cpp b/game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_chase_victim.cpp
new file mode 100644
index 0000000..77204b7
--- /dev/null
+++ b/game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_chase_victim.cpp
@@ -0,0 +1,170 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// boss_alpha_chase_victim.cpp
+// Michael Booth, November 2010
+
+#include "cbase.h"
+
+#ifdef TF_RAID_MODE
+
+#include "player_vs_environment/boss_alpha/boss_alpha.h"
+#include "player_vs_environment/boss_alpha/behavior/boss_alpha_chase_victim.h"
+#include "player_vs_environment/boss_alpha/behavior/boss_alpha_lost_victim.h"
+#include "player_vs_environment/boss_alpha/behavior/boss_alpha_nuke_attack.h"
+#include "player_vs_environment/boss_alpha/behavior/boss_alpha_launch_grenades.h"
+#include "player_vs_environment/boss_alpha/behavior/boss_alpha_launch_rockets.h"
+
+extern ConVar tf_boss_alpha_grenade_launch_range;
+extern ConVar tf_boss_alpha_chase_range;
+
+
+//---------------------------------------------------------------------------------------------
+CBossAlphaChaseVictim::CBossAlphaChaseVictim( CBaseCombatCharacter *chaseTarget )
+{
+ m_chaseTarget = chaseTarget;
+ m_lastKnownTargetSpot = chaseTarget->GetAbsOrigin();
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CBossAlpha > CBossAlphaChaseVictim::OnStart( CBossAlpha *me, Action< CBossAlpha > *priorAction )
+{
+ if ( m_chaseTarget == NULL )
+ {
+ return Done( "Target is NULL" );
+ }
+
+ m_lastKnownTargetSpot = m_chaseTarget->GetAbsOrigin();
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CBossAlpha > CBossAlphaChaseVictim::Update( CBossAlpha *me, float interval )
+{
+ if ( m_chaseTarget == NULL || !m_chaseTarget->IsAlive() )
+ {
+ return ChangeTo( new CBossAlphaLostVictim, "No victim" );
+ }
+
+ if ( m_chaseTarget != me->GetAttackTarget() )
+ {
+ return Done( "Changing targets" );
+ }
+
+ Vector moveGoal = m_chaseTarget->GetAbsOrigin();
+
+ if ( me->IsLineOfSightClear( m_chaseTarget ) )
+ {
+ if ( !m_visibleTimer.HasStarted() )
+ {
+ m_visibleTimer.Start();
+ }
+
+ if ( me->HasAbility( CBossAlpha::CAN_NUKE ) && me->GetNukeTimer()->IsElapsed() )
+ {
+ return SuspendFor( new CBossAlphaNukeAttack, "Nuking!" );
+ }
+
+ m_lastKnownTargetSpot = m_chaseTarget->GetAbsOrigin();
+
+ if ( me->HasAbility( CBossAlpha::CAN_LAUNCH_STICKIES ) )
+ {
+ if ( ( me->GetGrenadeTimer()->IsElapsed() && me->IsRangeLessThan( m_chaseTarget, tf_boss_alpha_grenade_launch_range.GetFloat() ) ) ||
+ me->IsInCondition( CBossAlpha::ENRAGED ) )
+ {
+ return SuspendFor( new CBossAlphaLaunchGrenades, "Target is close (or I am enraged) - grenades!" );
+ }
+ }
+
+ // chase into line of sight a bit so they can't immediately get behind cover again
+ if ( me->HasAbility( CBossAlpha::CAN_FIRE_ROCKETS ) )
+ {
+ if ( m_visibleTimer.IsGreaterThen( 1.0f ) ||
+ me->IsRangeLessThan( m_chaseTarget, tf_boss_alpha_chase_range.GetFloat() ) )
+ {
+ return SuspendFor( new CBossAlphaLaunchRockets, "Fire!" );
+ }
+ }
+
+ if ( me->IsRangeLessThan( m_chaseTarget, 150.0f ) )
+ {
+ // too close - stand still
+ if ( !me->GetBodyInterface()->IsActivity( ACT_MP_STAND_MELEE ) )
+ {
+ me->GetBodyInterface()->StartActivity( ACT_MP_STAND_MELEE );
+ }
+
+ return Continue();
+ }
+ }
+ else
+ {
+ m_visibleTimer.Invalidate();
+
+ // move to where we last saw our target
+ moveGoal = m_lastKnownTargetSpot;
+
+ if ( me->IsRangeLessThan( m_lastKnownTargetSpot, 20.0f ) )
+ {
+ // reached spot where we last saw our victim - give up
+ me->SetAttackTarget( NULL );
+
+ return ChangeTo( new CBossAlphaLostVictim, "I lost my chase victim" );
+ }
+ }
+
+
+ // move into sight of target
+ if ( m_path.GetAge() > 1.0f )
+ {
+ CBossAlphaPathCost cost( me );
+ m_path.Compute( me, moveGoal, cost );
+ }
+
+ me->GetLocomotionInterface()->Run();
+ m_path.Update( me );
+
+ // play running animation
+ if ( !me->GetBodyInterface()->IsActivity( ACT_MP_RUN_MELEE ) )
+ {
+ me->GetBodyInterface()->StartActivity( ACT_MP_RUN_MELEE );
+ }
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+EventDesiredResult< CBossAlpha > CBossAlphaChaseVictim::OnMoveToSuccess( CBossAlpha *me, const Path *path )
+{
+ return TryDone( RESULT_CRITICAL, "Reached move goal" );
+}
+
+
+//---------------------------------------------------------------------------------------------
+EventDesiredResult< CBossAlpha > CBossAlphaChaseVictim::OnMoveToFailure( CBossAlpha *me, const Path *path, MoveToFailureType reason )
+{
+ return TryDone( RESULT_CRITICAL, "Path follow failed" );
+}
+
+
+//---------------------------------------------------------------------------------------------
+void CBossAlphaChaseVictim::OnEnd( CBossAlpha *me, Action< CBossAlpha > *nextAction )
+{
+}
+
+
+//---------------------------------------------------------------------------------------------
+EventDesiredResult< CBossAlpha > CBossAlphaChaseVictim::OnStuck( CBossAlpha *me )
+{
+ // we're stuck - just warp to the our next path goal
+ if ( m_path.GetCurrentGoal() )
+ {
+ me->SetAbsOrigin( m_path.GetCurrentGoal()->pos + Vector( 0, 0, 10.0f ) );
+ }
+
+ return TryContinue();
+}
+
+#endif // TF_RAID_MODE
diff --git a/game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_chase_victim.h b/game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_chase_victim.h
new file mode 100644
index 0000000..4cdf5ff
--- /dev/null
+++ b/game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_chase_victim.h
@@ -0,0 +1,41 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// boss_alpha_chase_victim.h
+// Michael Booth, November 2010
+
+#ifndef BOSS_ALPHA_CHASE_VICTIM_H
+#define BOSS_ALPHA_CHASE_VICTIM_H
+
+#ifdef TF_RAID_MODE
+
+#include "nav_mesh/tf_path_follower.h"
+
+//---------------------------------------------------------------------------------------------
+//---------------------------------------------------------------------------------------------
+class CBossAlphaChaseVictim : public Action< CBossAlpha >
+{
+public:
+ CBossAlphaChaseVictim( CBaseCombatCharacter *chaseTarget );
+
+ virtual ActionResult< CBossAlpha > OnStart( CBossAlpha *me, Action< CBossAlpha > *priorAction );
+ virtual ActionResult< CBossAlpha > Update( CBossAlpha *me, float interval );
+ virtual void OnEnd( CBossAlpha *me, Action< CBossAlpha > *nextAction );
+
+ virtual EventDesiredResult< CBossAlpha > OnStuck( CBossAlpha *me );
+ virtual EventDesiredResult< CBossAlpha > OnMoveToSuccess( CBossAlpha *me, const Path *path );
+ virtual EventDesiredResult< CBossAlpha > OnMoveToFailure( CBossAlpha *me, const Path *path, MoveToFailureType reason );
+
+ virtual const char *GetName( void ) const { return "ChaseVictim"; } // return name of this action
+
+private:
+ CTFPathFollower m_path;
+ IntervalTimer m_visibleTimer;
+ CHandle< CBaseCombatCharacter > m_lastTarget;
+
+ CHandle< CBaseCombatCharacter > m_chaseTarget;
+ Vector m_lastKnownTargetSpot;
+};
+
+
+#endif // TF_RAID_MODE
+
+#endif // BOSS_ALPHA_CHASE_VICTIM_H
diff --git a/game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_get_off_me.cpp b/game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_get_off_me.cpp
new file mode 100644
index 0000000..b68ca8e
--- /dev/null
+++ b/game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_get_off_me.cpp
@@ -0,0 +1,93 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// boss_alpha_get_off_me.cpp
+// Michael Booth, November 2010
+
+#include "cbase.h"
+
+#ifdef TF_RAID_MODE
+
+#include "tf_player.h"
+#include "player_vs_environment/boss_alpha/boss_alpha.h"
+#include "player_vs_environment/boss_alpha/behavior/boss_alpha_get_off_me.h"
+
+ConVar tf_boss_alpha_charge_pushaway_force( "tf_boss_alpha_charge_pushaway_force", "500"/*, FCVAR_CHEAT*/ );
+
+
+//---------------------------------------------------------------------------------------------
+void PushawayPlayer( CTFPlayer *victim, const Vector &pushOrigin, float pushForce )
+{
+ if ( !victim )
+ return;
+
+ if ( victim->GetFlags() & FL_ONGROUND )
+ {
+ // launching into the air
+ victim->SetAbsVelocity( vec3_origin );
+
+ const float stunTime = 0.5f;
+ victim->m_Shared.StunPlayer( stunTime, 1.0, TF_STUN_MOVEMENT );
+
+ victim->ApplyPunchImpulseX( RandomInt( 10, 15 ) );
+ victim->SpeakConceptIfAllowed( MP_CONCEPT_DEFLECTED, "projectile:0,victim:1" );
+ }
+
+ victim->RemoveFlag( FL_ONGROUND );
+
+ Vector toVictim = victim->WorldSpaceCenter() - pushOrigin;
+ toVictim.z = 0.0f;
+ toVictim.NormalizeInPlace();
+ toVictim.z = 1.0f;
+
+ victim->ApplyAbsVelocityImpulse( pushForce * toVictim );
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CBossAlpha > CBossAlphaGetOffMe::OnStart( CBossAlpha *me, Action< CBossAlpha > *priorAction )
+{
+ me->AddGestureSequence( me->LookupSequence( "gesture_melee_help" ) );
+ m_timer.Start( 0.5f );
+
+ me->AddCondition( CBossAlpha::BUSY );
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CBossAlpha > CBossAlphaGetOffMe::Update( CBossAlpha *me, float interval )
+{
+ if ( m_timer.IsElapsed() )
+ {
+ // blast players off of my head
+ CUtlVector< CTFPlayer * > onMeVector;
+ me->CollectPlayersStandingOnMe( &onMeVector );
+
+ Vector headPos;
+ QAngle headAngles;
+ if ( me->GetAttachment( "head", headPos, headAngles ) )
+ {
+ for( int i=0; i<onMeVector.Count(); ++i )
+ {
+ // push 'em off
+ PushawayPlayer( onMeVector[i], headPos, tf_boss_alpha_charge_pushaway_force.GetFloat() );
+ }
+ }
+
+ me->EmitSound( "Weapon_FlameThrower.AirBurstAttack" );
+
+ return Done();
+ }
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+void CBossAlphaGetOffMe::OnEnd( CBossAlpha *me, Action< CBossAlpha > *nextAction )
+{
+ me->RemoveCondition( CBossAlpha::BUSY );
+}
+
+
+#endif // TF_RAID_MODE
diff --git a/game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_get_off_me.h b/game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_get_off_me.h
new file mode 100644
index 0000000..5322103
--- /dev/null
+++ b/game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_get_off_me.h
@@ -0,0 +1,27 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// boss_alpha_get_off_me.h
+// Michael Booth, November 2010
+
+#ifndef BOSS_ALPHA_GET_OFF_ME_H
+#define BOSS_ALPHA_GET_OFF_ME_H
+
+#ifdef TF_RAID_MODE
+
+
+//----------------------------------------------------------------------------
+class CBossAlphaGetOffMe : public Action< CBossAlpha >
+{
+public:
+ virtual ActionResult< CBossAlpha > OnStart( CBossAlpha *me, Action< CBossAlpha > *priorAction );
+ virtual ActionResult< CBossAlpha > Update( CBossAlpha *me, float interval );
+ virtual void OnEnd( CBossAlpha *me, Action< CBossAlpha > *nextAction );
+
+ virtual const char *GetName( void ) const { return "GetOffMe"; } // return name of this action
+
+private:
+ CountdownTimer m_timer;
+};
+
+#endif // TF_RAID_MODE
+
+#endif // BOSS_ALPHA_GET_OFF_ME_H
diff --git a/game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_guard_spot.cpp b/game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_guard_spot.cpp
new file mode 100644
index 0000000..7f80e68
--- /dev/null
+++ b/game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_guard_spot.cpp
@@ -0,0 +1,129 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// boss_alpha_guard_spot.cpp
+// Michael Booth, November 2010
+
+#include "cbase.h"
+
+#ifdef TF_RAID_MODE
+
+#include "tf_player.h"
+#include "nav_mesh/tf_nav_area.h"
+#include "tf_projectile_rocket.h"
+#include "player_vs_environment/boss_alpha/boss_alpha.h"
+#include "player_vs_environment/boss_alpha/behavior/boss_alpha_guard_spot.h"
+#include "player_vs_environment/boss_alpha/behavior/boss_alpha_chase_victim.h"
+
+
+//-----------------------------------------------------------------------------------------------------
+ActionResult< CBossAlpha > CBossAlphaGuardSpot::OnStart( CBossAlpha *me, Action< CBossAlpha > *priorAction )
+{
+ m_path.SetMinLookAheadDistance( 300.0f );
+
+ me->GetBodyInterface()->StartActivity( ACT_MP_STAND_ITEM1 );
+ me->SetHomePosition( me->GetAbsOrigin() );
+
+ m_lookAtSpot = vec3_origin;
+
+ return Continue();
+}
+
+
+//-----------------------------------------------------------------------------------------------------
+ActionResult< CBossAlpha > CBossAlphaGuardSpot::Update( CBossAlpha *me, float interval )
+{
+ CBaseCombatCharacter *target = me->GetAttackTarget();
+ if ( target )
+ {
+ if ( me->IsLineOfSightClear( target ) || me->IsPrisonerOfMinion( target ) )
+ {
+ return SuspendFor( new CBossAlphaChaseVictim( me->GetAttackTarget() ), "Get 'em!" );
+ }
+ }
+
+ CBaseCombatCharacter *visible = me->GetNearestVisibleEnemy();
+ if ( visible )
+ {
+ // look at visible victim out of range
+ me->GetLocomotionInterface()->FaceTowards( visible->WorldSpaceCenter() );
+ }
+
+ const float atHomeRange = 50.0f;
+ if ( me->IsRangeGreaterThan( me->GetHomePosition(), atHomeRange ) )
+ {
+ if ( m_path.GetAge() > 3.0f )
+ {
+ CBossAlphaPathCost cost( me );
+ if ( m_path.Compute( me, me->GetHomePosition(), cost ) == false )
+ {
+ // can't reach guard post - just jump there for now
+ me->Teleport( &me->GetHomePosition(), NULL, NULL );
+ }
+ }
+
+ m_path.Update( me );
+ }
+ else
+ {
+ // on guard spot - look around
+ if ( m_lookTimer.IsElapsed() )
+ {
+ m_lookTimer.Start( RandomFloat( 1.0f, 2.0f ) );
+
+ CTFNavArea *myArea = (CTFNavArea *)me->GetLastKnownArea();
+ if ( myArea )
+ {
+ const CUtlVector< CTFNavArea * > &invasionAreaVector = myArea->GetEnemyInvasionAreaVector( TF_TEAM_RED );
+
+ if ( invasionAreaVector.Count() > 0 )
+ {
+ // try to not look directly at walls
+ const float minGazeRange = 300.0f;
+ const int retryCount = 20.0f;
+ for( int r=0; r<retryCount; ++r )
+ {
+ int which = RandomInt( 0, invasionAreaVector.Count()-1 );
+ Vector gazeSpot = invasionAreaVector[ which ]->GetRandomPoint() + Vector( 0, 0, 0.75f * HumanHeight );
+
+ if ( me->IsRangeGreaterThan( gazeSpot, minGazeRange ) && me->GetVisionInterface()->IsLineOfSightClear( gazeSpot ) )
+ {
+ // use maxLookInterval so these looks override body aiming from path following
+ m_lookAtSpot = gazeSpot;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ me->GetLocomotionInterface()->FaceTowards( m_lookAtSpot );
+ }
+
+ if ( me->GetLocomotionInterface()->IsAttemptingToMove() )
+ {
+ // play running animation
+ if ( !me->GetBodyInterface()->IsActivity( ACT_MP_RUN_MELEE ) )
+ {
+ me->GetBodyInterface()->StartActivity( ACT_MP_RUN_MELEE );
+ }
+ }
+ else
+ {
+ // standing still
+ if ( !me->GetBodyInterface()->IsActivity( ACT_MP_STAND_ITEM1 ) )
+ {
+ me->GetBodyInterface()->StartActivity( ACT_MP_STAND_ITEM1 );
+ }
+ }
+
+ return Continue();
+}
+
+
+//-----------------------------------------------------------------------------------------------------
+EventDesiredResult< CBossAlpha > CBossAlphaGuardSpot::OnInjured( CBossAlpha *me, const CTakeDamageInfo &info )
+{
+ return TryContinue();
+}
+
+
+#endif // TF_RAID_MODE
diff --git a/game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_guard_spot.h b/game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_guard_spot.h
new file mode 100644
index 0000000..8fc8685
--- /dev/null
+++ b/game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_guard_spot.h
@@ -0,0 +1,28 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// boss_alpha_guard_spot.h
+// Michael Booth, November 2010
+
+#include "cbase.h"
+
+#ifdef TF_RAID_MODE
+
+#include "nav_mesh/tf_path_follower.h"
+
+//---------------------------------------------------------------------------------------------
+class CBossAlphaGuardSpot : public Action< CBossAlpha >
+{
+public:
+ virtual ActionResult< CBossAlpha > OnStart( CBossAlpha *me, Action< CBossAlpha > *priorAction );
+ virtual ActionResult< CBossAlpha > Update( CBossAlpha *me, float interval );
+ virtual EventDesiredResult< CBossAlpha > OnInjured( CBossAlpha *me, const CTakeDamageInfo &info );
+
+ virtual const char *GetName( void ) const { return "GuardSpot"; } // return name of this action
+
+private:
+ CTFPathFollower m_path;
+ CountdownTimer m_lookTimer;
+ Vector m_lookAtSpot;
+};
+
+
+#endif // TF_RAID_MODE
diff --git a/game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_launch_grenades.cpp b/game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_launch_grenades.cpp
new file mode 100644
index 0000000..34008d3
--- /dev/null
+++ b/game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_launch_grenades.cpp
@@ -0,0 +1,204 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// boss_alpha_launch_grenades.cpp
+// Michael Booth, November 2010
+
+#include "cbase.h"
+
+#ifdef TF_RAID_MODE
+
+#include "player_vs_environment/boss_alpha/boss_alpha.h"
+#include "player_vs_environment/boss_alpha/behavior/boss_alpha_launch_grenades.h"
+
+ConVar tf_boss_alpha_grenade_ring_min_horiz_vel( "tf_boss_alpha_grenade_ring_min_horiz_vel", "100"/*, FCVAR_CHEAT*/ );
+ConVar tf_boss_alpha_grenade_ring_max_horiz_vel( "tf_boss_alpha_grenade_ring_max_horiz_vel", "350"/*, FCVAR_CHEAT*/ );
+ConVar tf_boss_alpha_grenade_vert_vel( "tf_boss_alpha_grenade_vert_vel", "750"/*, FCVAR_CHEAT*/ );
+ConVar tf_boss_alpha_grenade_det_time( "tf_boss_alpha_grenade_det_time", "3"/*, FCVAR_CHEAT*/ );
+ConVar tf_boss_alpha_grenade_damage( "tf_boss_alpha_grenade_damage", "25"/*, FCVAR_CHEAT*/ );
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CBossAlpha > CBossAlphaLaunchGrenades::OnStart( CBossAlpha *me, Action< CBossAlpha > *priorAction )
+{
+ me->GetBodyInterface()->StartActivity( ACT_MP_STAND_SECONDARY );
+ m_animLayer = me->AddLayeredSequence( me->LookupSequence( "gesture_melee_cheer" ), 0 );
+
+ m_timer.Start( 1.0f );
+ m_detonateTimer.Invalidate();
+ me->AddCondition( CBossAlpha::BUSY );
+ me->GetGrenadeTimer()->Start( me->GetGrenadeInterval() );
+
+ me->EmitSound( "RobotBoss.LaunchGrenades" );
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+void CBossAlphaLaunchGrenades::LaunchGrenade( CBossAlpha *me, const Vector &launchVel, CTFWeaponInfo *weaponInfo )
+{
+ CTFGrenadePipebombProjectile *pProjectile = CTFGrenadePipebombProjectile::Create( me->WorldSpaceCenter() + Vector( 0, 0, 100 ), vec3_angle, launchVel,
+ AngularImpulse( 600, random->RandomInt( -1200, 1200 ), 0 ),
+ me, *weaponInfo, TF_PROJECTILE_PIPEBOMB_REMOTE, 1 );
+ if ( pProjectile )
+ {
+ pProjectile->SetLauncher( me );
+ pProjectile->SetDamage( tf_boss_alpha_grenade_damage.GetFloat() );
+
+ if ( me->IsInCondition( CBossAlpha::ENRAGED ) )
+ {
+ pProjectile->SetCritical( true );
+ }
+
+ m_grenadeVector.AddToTail( pProjectile );
+ }
+}
+
+
+//---------------------------------------------------------------------------------------------
+void CBossAlphaLaunchGrenades::LaunchGrenadeRings( CBossAlpha *me )
+{
+ const char *weaponAlias = WeaponIdToAlias( TF_WEAPON_GRENADELAUNCHER );
+ if ( !weaponAlias )
+ return;
+
+ WEAPON_FILE_INFO_HANDLE weaponInfoHandle = LookupWeaponInfoSlot( weaponAlias );
+ if ( weaponInfoHandle == GetInvalidWeaponInfoHandle() )
+ return;
+
+ CTFWeaponInfo *weaponInfo = static_cast< CTFWeaponInfo * >( GetFileWeaponInfoFromHandle( weaponInfoHandle ) );
+
+ QAngle myAngles = me->EyeAngles();
+
+ // create rings of stickies
+ float deltaVel = tf_boss_alpha_grenade_ring_max_horiz_vel.GetFloat() - tf_boss_alpha_grenade_ring_min_horiz_vel.GetFloat();
+ const int ringCount = 2;
+ for( int r=0; r<ringCount; ++r )
+ {
+ float u = (float)r/(float)(ringCount-1);
+
+ float horizVel = tf_boss_alpha_grenade_ring_min_horiz_vel.GetFloat() + u * deltaVel;
+
+ float angleDelta = 10.0f + 20.0f * ( 1.0f - u );
+
+ for( float angle=0.0f; angle<360.0f; angle += angleDelta )
+ {
+ Vector forward;
+ AngleVectors( myAngles, &forward );
+
+ Vector vecVelocity( horizVel * forward.x, horizVel * forward.y, tf_boss_alpha_grenade_vert_vel.GetFloat() );
+
+ LaunchGrenade( me, vecVelocity, weaponInfo );
+
+ myAngles.y += angleDelta;
+ }
+ }
+}
+
+
+ConVar tf_boss_alpha_grenade_spoke_angle( "tf_boss_alpha_grenade_spoke_angle", "45"/*, FCVAR_CHEAT*/ );
+ConVar tf_boss_alpha_grenade_spoke_count( "tf_boss_alpha_grenade_spoke_count", "15"/*, FCVAR_CHEAT*/ );
+ConVar tf_boss_alpha_grenade_spoke_min_horiz_vel( "tf_boss_alpha_grenade_spoke_min_horiz_vel", "100"/*, FCVAR_CHEAT*/ );
+ConVar tf_boss_alpha_grenade_spoke_max_horiz_vel( "tf_boss_alpha_grenade_spoke_max_horiz_vel", "750"/*, FCVAR_CHEAT*/ );
+
+
+//---------------------------------------------------------------------------------------------
+void CBossAlphaLaunchGrenades::LaunchGrenadeSpokes( CBossAlpha *me )
+{
+ const char *weaponAlias = WeaponIdToAlias( TF_WEAPON_GRENADELAUNCHER );
+ if ( !weaponAlias )
+ return;
+
+ WEAPON_FILE_INFO_HANDLE weaponInfoHandle = LookupWeaponInfoSlot( weaponAlias );
+ if ( weaponInfoHandle == GetInvalidWeaponInfoHandle() )
+ return;
+
+ CTFWeaponInfo *weaponInfo = static_cast< CTFWeaponInfo * >( GetFileWeaponInfoFromHandle( weaponInfoHandle ) );
+
+ // create spokes of stickies
+ float deltaVel = tf_boss_alpha_grenade_spoke_max_horiz_vel.GetFloat() - tf_boss_alpha_grenade_spoke_min_horiz_vel.GetFloat();
+ float angleDelta = tf_boss_alpha_grenade_spoke_angle.GetFloat();
+ QAngle myAngles = me->EyeAngles();
+
+ for( float angle=0.0f; angle<360.0f; angle += angleDelta )
+ {
+ Vector forward;
+ AngleVectors( myAngles, &forward );
+
+ int spokeCount = tf_boss_alpha_grenade_spoke_count.GetInt();
+
+ for( int i=0; i<spokeCount; ++i )
+ {
+ float u = (float)i/(float)(spokeCount-1);
+
+ float horizVel = tf_boss_alpha_grenade_spoke_min_horiz_vel.GetFloat() + u * deltaVel;
+
+ Vector vecVelocity( horizVel * forward.x, horizVel * forward.y, tf_boss_alpha_grenade_vert_vel.GetFloat() );
+
+ LaunchGrenade( me, vecVelocity, weaponInfo );
+ }
+
+ myAngles.y += angleDelta;
+ }
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CBossAlpha > CBossAlphaLaunchGrenades::Update( CBossAlpha *me, float interval )
+{
+ QAngle myAngles = me->EyeAngles();
+
+ if ( m_timer.HasStarted() && m_timer.IsElapsed() )
+ {
+ m_timer.Invalidate();
+
+ if ( RandomInt( 0, 100 ) < 50 )
+ {
+ LaunchGrenadeRings( me );
+ }
+ else
+ {
+ LaunchGrenadeSpokes( me );
+ }
+
+ me->EmitSound( "Weapon_Grenade_Normal.Single" );
+
+ m_detonateTimer.Start( tf_boss_alpha_grenade_det_time.GetFloat() );
+ }
+
+ if ( m_detonateTimer.HasStarted() && m_detonateTimer.IsElapsed() )
+ {
+ // detonate the stickies
+ for( int i=0; i<m_grenadeVector.Count(); ++i )
+ {
+ if ( m_grenadeVector[i] )
+ {
+ m_grenadeVector[i]->Detonate();
+ }
+ }
+
+ return Done();
+ }
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+void CBossAlphaLaunchGrenades::OnEnd( CBossAlpha *me, Action< CBossAlpha > *nextAction )
+{
+ // fizzle any outstanding stickies
+ for( int i=0; i<m_grenadeVector.Count(); ++i )
+ {
+ if ( m_grenadeVector[i] )
+ {
+ m_grenadeVector[i]->Fizzle();
+ m_grenadeVector[i]->Detonate();
+ }
+ }
+
+ me->RemoveCondition( CBossAlpha::ENRAGED );
+ me->RemoveCondition( CBossAlpha::BUSY );
+ me->FastRemoveLayer( m_animLayer );
+}
+
+#endif // TF_RAID_MODE
diff --git a/game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_launch_grenades.h b/game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_launch_grenades.h
new file mode 100644
index 0000000..592bfd5
--- /dev/null
+++ b/game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_launch_grenades.h
@@ -0,0 +1,36 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// boss_alpha_launch_grenades.h
+// Michael Booth, November 2010
+
+#ifndef BOSS_ALPHA_LAUNCH_GRENADES_H
+#define BOSS_ALPHA_LAUNCH_GRENADES_H
+
+#ifdef TF_RAID_MODE
+
+#include "tf_weapon_grenade_pipebomb.h"
+
+class CBossAlphaLaunchGrenades : public Action< CBossAlpha >
+{
+public:
+ virtual ActionResult< CBossAlpha > OnStart( CBossAlpha *me, Action< CBossAlpha > *priorAction );
+ virtual ActionResult< CBossAlpha > Update( CBossAlpha *me, float interval );
+ virtual void OnEnd( CBossAlpha *me, Action< CBossAlpha > *nextAction );
+
+ // if anything interrupts this action, abort it
+ virtual ActionResult< CBossAlpha > OnSuspend( CBossAlpha *me, Action< CBossAlpha > *interruptingAction ) { return Done(); }
+
+ virtual const char *GetName( void ) const { return "LaunchGrenades"; } // return name of this action
+
+private:
+ CountdownTimer m_timer;
+ CountdownTimer m_detonateTimer;
+ CUtlVector< CHandle< CTFGrenadePipebombProjectile > > m_grenadeVector;
+ void LaunchGrenade( CBossAlpha *me, const Vector &launchVel, CTFWeaponInfo *weaponInfo );
+ void LaunchGrenadeRings( CBossAlpha *me );
+ void LaunchGrenadeSpokes( CBossAlpha *me );
+ int m_animLayer;
+};
+
+#endif // TF_RAID_MODE
+
+#endif // BOSS_ALPHA_LAUNCH_GRENADES_H \ No newline at end of file
diff --git a/game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_launch_rockets.cpp b/game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_launch_rockets.cpp
new file mode 100644
index 0000000..a1558d8
--- /dev/null
+++ b/game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_launch_rockets.cpp
@@ -0,0 +1,119 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// boss_alpha_launch_rockets.cpp
+// Michael Booth, November 2010
+
+#include "cbase.h"
+
+#ifdef TF_RAID_MODE
+
+#include "tf_projectile_rocket.h"
+#include "player_vs_environment/boss_alpha/boss_alpha.h"
+#include "player_vs_environment/boss_alpha/behavior/boss_alpha_launch_rockets.h"
+
+ConVar tf_boss_alpha_dont_shoot( "tf_boss_alpha_dont_shoot", "0"/*, FCVAR_CHEAT*/ );
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CBossAlpha > CBossAlphaLaunchRockets::OnStart( CBossAlpha *me, Action< CBossAlpha > *priorAction )
+{
+ // start animation
+ me->GetBodyInterface()->StartActivity( ACT_MP_STAND_SECONDARY );
+
+ m_animLayer = me->AddLayeredSequence( me->LookupSequence( "taunt02" ), 0 );
+
+ m_timer.Start( 1.0f );
+
+ m_rocketsLeft = me->GetRocketLaunchCount();
+
+ me->AddCondition( CBossAlpha::BUSY );
+ me->LockAttackTarget();
+
+ me->EmitSound( "RobotBoss.LaunchRockets" );
+
+ if ( me->GetAttackTarget() == NULL )
+ {
+ return Done( "No target" );
+ }
+
+ m_target = me->GetAttackTarget();
+ m_lastTargetPosition = m_target->WorldSpaceCenter();
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CBossAlpha > CBossAlphaLaunchRockets::Update( CBossAlpha *me, float interval )
+{
+ if ( m_target != NULL )
+ {
+ m_lastTargetPosition = m_target->WorldSpaceCenter();
+ }
+
+ me->GetLocomotionInterface()->FaceTowards( m_lastTargetPosition );
+
+ if ( m_timer.IsElapsed() && m_launchTimer.IsElapsed() )
+ {
+ if ( !m_rocketsLeft )
+ {
+ return Done();
+ }
+
+ --m_rocketsLeft;
+ m_launchTimer.Start( me->GetRocketInterval() );
+
+ QAngle launchAngles = me->GetAbsAngles();
+
+ if ( m_target == NULL )
+ {
+ Vector to = m_lastTargetPosition - me->WorldSpaceCenter();
+ VectorAngles( to, launchAngles );
+ }
+ else
+ {
+ float range = me->GetRangeTo( m_target->EyePosition() );
+
+ const float rocketSpeed = me->GetRocketAimError() * 1100.0f; // 2000.0f; // 1100.0f; nerfing accuracy
+ float flightTime = range / rocketSpeed;
+
+ Vector aimSpot = m_target->EyePosition() + m_target->GetAbsVelocity() * flightTime;
+
+ Vector to = aimSpot - me->WorldSpaceCenter();
+ VectorAngles( to, launchAngles );
+ }
+
+ if ( !tf_boss_alpha_dont_shoot.GetBool() )
+ {
+ CTFProjectile_Rocket *pRocket = CTFProjectile_Rocket::Create( me, me->WorldSpaceCenter(), launchAngles, me, me );
+ if ( pRocket )
+ {
+ if ( me->IsInCondition( CBossAlpha::ENRAGED ) )
+ {
+ pRocket->SetCritical( true );
+ pRocket->EmitSound( "Weapon_RPG.SingleCrit" );
+ }
+ else
+ {
+ me->EmitSound( me->GetRocketSoundEffect() );
+ }
+
+ pRocket->SetDamage( me->GetRocketDamage() );
+ }
+ }
+ }
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+void CBossAlphaLaunchRockets::OnEnd( CBossAlpha *me, Action< CBossAlpha > *nextAction )
+{
+ me->RemoveCondition( CBossAlpha::ENRAGED );
+ me->RemoveCondition( CBossAlpha::BUSY );
+ me->FastRemoveLayer( m_animLayer );
+ me->UnlockAttackTarget();
+}
+
+
+#endif // TF_RAID_MODE
diff --git a/game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_launch_rockets.h b/game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_launch_rockets.h
new file mode 100644
index 0000000..2f216b9
--- /dev/null
+++ b/game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_launch_rockets.h
@@ -0,0 +1,38 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// boss_alpha_launch_rockets.h
+// Michael Booth, November 2010
+
+#ifndef BOSS_ALPHA_LAUNCH_ROCKETS_H
+#define BOSS_ALPHA_LAUNCH_ROCKETS_H
+
+#ifdef TF_RAID_MODE
+
+//---------------------------------------------------------------------------------------------
+class CBossAlphaLaunchRockets : public Action< CBossAlpha >
+{
+public:
+ virtual ActionResult< CBossAlpha > OnStart( CBossAlpha *me, Action< CBossAlpha > *priorAction );
+ virtual ActionResult< CBossAlpha > Update( CBossAlpha *me, float interval );
+ virtual void OnEnd( CBossAlpha *me, Action< CBossAlpha > *nextAction );
+
+ // if anything interrupts this action, abort it
+ virtual ActionResult< CBossAlpha > OnSuspend( CBossAlpha *me, Action< CBossAlpha > *interruptingAction ) { return Done(); }
+
+ virtual const char *GetName( void ) const { return "LaunchRockets"; } // return name of this action
+
+private:
+ CountdownTimer m_timer;
+
+ CountdownTimer m_launchTimer;
+ int m_rocketsLeft;
+
+ int m_animLayer;
+
+ CHandle< CBaseCombatCharacter > m_target;
+ Vector m_lastTargetPosition;
+};
+
+
+#endif // TF_RAID_MODE
+
+#endif // BOSS_ALPHA_LAUNCH_ROCKETS_H
diff --git a/game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_lost_victim.cpp b/game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_lost_victim.cpp
new file mode 100644
index 0000000..f935cfa
--- /dev/null
+++ b/game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_lost_victim.cpp
@@ -0,0 +1,64 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// boss_alpha_lost_victim.cpp
+// Michael Booth, November 2010
+
+#include "cbase.h"
+
+#ifdef TF_RAID_MODE
+
+#include "player_vs_environment/boss_alpha/boss_alpha.h"
+#include "player_vs_environment/boss_alpha/behavior/boss_alpha_lost_victim.h"
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CBossAlpha > CBossAlphaLostVictim::OnStart( CBossAlpha *me, Action< CBossAlpha > *priorAction )
+{
+ m_headTurn = 0.0f;
+ m_headYawPoseParameter = me->LookupPoseParameter( "body_yaw" );
+
+ m_timer.Start( RandomFloat( 3.0f, 5.0f ) );
+
+ me->EmitSound( "RobotBoss.Scanning" );
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CBossAlpha > CBossAlphaLostVictim::Update( CBossAlpha *me, float interval )
+{
+ if ( m_timer.IsElapsed() )
+ {
+ return Done( "Giving up" );
+ }
+
+ CBaseCombatCharacter *target = me->GetAttackTarget();
+ if ( target )
+ {
+ if ( me->IsLineOfSightClear( target ) || me->IsPrisonerOfMinion( target ) )
+ {
+ me->EmitSound( "RobotBoss.Acquire" );
+ me->AddGesture( ACT_MP_GESTURE_FLINCH_CHEST );
+ return Done( "Ah hah!" );
+ }
+ }
+
+ const float rate = M_PI / 3.0f;
+ m_headTurn += rate * interval;
+
+ float s, c;
+ SinCos( m_headTurn, &s, &c );
+
+ me->SetPoseParameter( m_headYawPoseParameter, 40.0f * s );
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+void CBossAlphaLostVictim::OnEnd( CBossAlpha *me, Action< CBossAlpha > *nextAction )
+{
+ me->SetPoseParameter( m_headYawPoseParameter, 0 );
+}
+
+
+#endif // TF_RAID_MODE
diff --git a/game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_lost_victim.h b/game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_lost_victim.h
new file mode 100644
index 0000000..b5be9c1
--- /dev/null
+++ b/game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_lost_victim.h
@@ -0,0 +1,28 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// boss_alpha_lost_victim.h
+// Michael Booth, November 2010
+
+#ifndef BOSS_ALPHA_LOST_VICTIM_H
+#define BOSS_ALPHA_LOST_VICTIM_H
+
+#ifdef TF_RAID_MODE
+
+//---------------------------------------------------------------------------------------------
+class CBossAlphaLostVictim : public Action< CBossAlpha >
+{
+public:
+ virtual ActionResult< CBossAlpha > OnStart( CBossAlpha *me, Action< CBossAlpha > *priorAction );
+ virtual ActionResult< CBossAlpha > Update( CBossAlpha *me, float interval );
+ virtual void OnEnd( CBossAlpha *me, Action< CBossAlpha > *nextAction );
+
+ virtual const char *GetName( void ) const { return "LostVictim"; } // return name of this action
+
+private:
+ CountdownTimer m_timer;
+ float m_headTurn;
+ int m_headYawPoseParameter;
+};
+
+#endif // TF_RAID_MODE
+
+#endif // BOSS_ALPHA_LOST_VICTIM_H
diff --git a/game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_nuke_attack.cpp b/game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_nuke_attack.cpp
new file mode 100644
index 0000000..f1a3289
--- /dev/null
+++ b/game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_nuke_attack.cpp
@@ -0,0 +1,208 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// boss_alpha_nuke_attack.cpp
+// Michael Booth, November 2010
+
+#include "cbase.h"
+
+#ifdef TF_RAID_MODE
+
+#include "tf_player.h"
+#include "tf_team.h"
+#include "player_vs_environment/monster_resource.h"
+#include "player_vs_environment/boss_alpha/boss_alpha.h"
+#include "player_vs_environment/boss_alpha/behavior/boss_alpha_nuke_attack.h"
+#include "player_vs_environment/boss_alpha/behavior/boss_alpha_stunned.h"
+
+
+ConVar tf_boss_alpha_nuke_charge_time( "tf_boss_alpha_nuke_charge_time", "5" );
+ConVar tf_boss_alpha_nuke_interval( "tf_boss_alpha_nuke_interval", "20" );
+ConVar tf_boss_alpha_nuke_lethal_time( "tf_boss_alpha_nuke_lethal_time", "999999999" ); // 300
+ConVar tf_boss_alpha_nuke_damage( "tf_boss_alpha_nuke_damage", "75"/*, FCVAR_CHEAT*/ );
+ConVar tf_boss_alpha_nuke_max_remaining_health( "tf_boss_alpha_nuke_max_remaining_health", "60"/*, FCVAR_CHEAT*/ );
+ConVar tf_boss_alpha_nuke_afterburn_time( "tf_boss_alpha_nuke_afterburn_time", "5"/*, FCVAR_CHEAT*/ );
+
+extern ConVar tf_boss_alpha_stunned_duration;
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CBossAlpha > CBossAlphaNukeAttack::OnStart( CBossAlpha *me, Action< CBossAlpha > *priorAction )
+{
+ me->GetBodyInterface()->StartActivity( ACT_MP_JUMP_FLOAT_LOSERSTATE );
+ me->StartNukeEffect();
+
+ me->EmitSound( "RobotBoss.ChargeUpNukeAttack" );
+ // me->AddCondition( CBossAlpha::VULNERABLE_TO_STUN );
+
+ m_chargeUpTimer.Start( tf_boss_alpha_nuke_charge_time.GetFloat() );
+ m_shakeTimer.Start( 0.25f );
+
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CBossAlpha > CBossAlphaNukeAttack::Update( CBossAlpha *me, float interval )
+{
+ float stunRatio = me->GetStunDamage() / me->GetBecomeStunnedDamage();
+
+ if ( me->HasAbility( CBossAlpha::CAN_BE_STUNNED ) && stunRatio >= 1.0f )
+ {
+ return ChangeTo( new CBossAlphaStunned( tf_boss_alpha_stunned_duration.GetFloat() ), "They got me" );
+ }
+
+ // update the client's HUD
+ if ( g_pMonsterResource )
+ {
+ g_pMonsterResource->SetBossStunPercentage( 1.0f - stunRatio );
+ }
+
+ if ( m_shakeTimer.IsElapsed() )
+ {
+ m_shakeTimer.Reset();
+ UTIL_ScreenShake( me->GetAbsOrigin(), 15.0f, 5.0f, 1.0f, 3000.0f, SHAKE_START );
+ }
+
+ if ( m_chargeUpTimer.IsElapsed() )
+ {
+ // BLAST!
+ CUtlVector< CTFPlayer * > playerVector;
+ CollectPlayers( &playerVector, TF_TEAM_RED, COLLECT_ONLY_LIVING_PLAYERS );
+ CollectPlayers( &playerVector, TF_TEAM_BLUE, COLLECT_ONLY_LIVING_PLAYERS, APPEND_PLAYERS );
+
+ me->EmitSound( "RobotBoss.NukeAttack" );
+
+ CUtlVector< CBaseCombatCharacter * > victimVector;
+
+ int i;
+
+ // players
+ for ( i=0; i<playerVector.Count(); ++i )
+ {
+ CBasePlayer *player = playerVector[i];
+
+ if ( player && player->IsAlive() && player->GetTeamNumber() == TF_TEAM_BLUE )
+ {
+ victimVector.AddToTail( player );
+ }
+ }
+
+ // objects
+ CTFTeam *team = GetGlobalTFTeam( TF_TEAM_BLUE );
+ if ( team )
+ {
+ for ( i=0; i<team->GetNumObjects(); ++i )
+ {
+ CBaseObject *object = team->GetObject( i );
+ if ( object )
+ {
+ victimVector.AddToTail( object );
+ }
+ }
+ }
+
+#ifdef SKIPME
+ team = GetGlobalTFTeam( TF_TEAM_RED );
+ if ( team )
+ {
+ for ( i=0; i<team->GetNumObjects(); ++i )
+ {
+ CBaseObject *object = team->GetObject( i );
+ if ( object )
+ {
+ victimVector.AddToTail( object );
+ }
+ }
+ }
+
+ // non-player bots
+ CUtlVector< INextBot * > botVector;
+ TheNextBots().CollectAllBots( &botVector );
+ for( i=0; i<botVector.Count(); ++i )
+ {
+ CBaseCombatCharacter *bot = botVector[i]->GetEntity();
+
+ if ( !bot->IsPlayer() && bot->IsAlive() )
+ {
+ victimVector.AddToTail( bot );
+ }
+ }
+#endif // SKIPME
+
+ for( int i=0; i<victimVector.Count(); ++i )
+ {
+ CBaseCombatCharacter *victim = victimVector[i];
+
+ if ( me->IsSelf( victim ) )
+ continue;
+
+ if ( me->IsLineOfSightClear( victim ) )
+ {
+ Vector toVictim = victim->WorldSpaceCenter() - me->WorldSpaceCenter();
+ toVictim.NormalizeInPlace();
+
+ float damage = tf_boss_alpha_nuke_damage.GetFloat();
+
+ if ( me->GetAge() > tf_boss_alpha_nuke_lethal_time.GetFloat() )
+ {
+ // nuke is now lethal
+ damage = 999.9f;
+ }
+ else if ( tf_boss_alpha_nuke_max_remaining_health.GetFloat() >= 0.0f )
+ {
+ // nuke slams everyone's health to this
+ if ( victim->GetHealth() > tf_boss_alpha_nuke_max_remaining_health.GetFloat() )
+ {
+ damage = victim->GetHealth() - tf_boss_alpha_nuke_max_remaining_health.GetFloat();
+ }
+ }
+
+ CTakeDamageInfo info( me, me, damage, DMG_ENERGYBEAM, TF_DMG_CUSTOM_NONE );
+ CalculateMeleeDamageForce( &info, toVictim, me->WorldSpaceCenter(), 1.0f );
+ victim->TakeDamage( info );
+
+ if ( victim->IsPlayer() )
+ {
+ CTFPlayer *playerVictim = ToTFPlayer( victim );
+
+ // catch them on fire (unless they are a Pyro)
+ if ( !playerVictim->IsPlayerClass( TF_CLASS_PYRO ) )
+ {
+ playerVictim->m_Shared.Burn( me, tf_boss_alpha_nuke_afterburn_time.GetFloat() );
+ }
+
+ color32 colorHit = { 255, 255, 255, 255 };
+ UTIL_ScreenFade( victim, colorHit, 1.0f, 0.1f, FFADE_IN );
+ }
+ }
+ }
+
+ return Done();
+ }
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+void CBossAlphaNukeAttack::OnEnd( CBossAlpha *me, Action< CBossAlpha > *nextAction )
+{
+ me->RemoveCondition( CBossAlpha::VULNERABLE_TO_STUN );
+ me->StopNukeEffect();
+ me->ClearStunDamage();
+ me->GetNukeTimer()->Start( tf_boss_alpha_nuke_interval.GetFloat() );
+
+ if ( g_pMonsterResource )
+ {
+ g_pMonsterResource->HideBossStunMeter();
+ }
+}
+
+
+//---------------------------------------------------------------------------------------------
+EventDesiredResult< CBossAlpha > CBossAlphaNukeAttack::OnInjured( CBossAlpha *me, const CTakeDamageInfo &info )
+{
+ return TryToSustain( RESULT_CRITICAL );
+}
+
+#endif // TF_RAID_MODE
diff --git a/game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_nuke_attack.h b/game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_nuke_attack.h
new file mode 100644
index 0000000..3566a59
--- /dev/null
+++ b/game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_nuke_attack.h
@@ -0,0 +1,31 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// boss_alpha_nuke_attack.h
+// Michael Booth, November 2010
+
+#ifndef BOSS_ALPHA_NUKE_ATTACK_H
+#define BOSS_ALPHA_NUKE_ATTACK_H
+
+#ifdef TF_RAID_MODE
+
+class CBossAlphaNukeAttack : public Action< CBossAlpha >
+{
+public:
+ virtual ActionResult< CBossAlpha > OnStart( CBossAlpha *me, Action< CBossAlpha > *priorAction );
+ virtual ActionResult< CBossAlpha > Update( CBossAlpha *me, float interval );
+ virtual void OnEnd( CBossAlpha *me, Action< CBossAlpha > *nextAction );
+
+ // if anything interrupts this action, abort it
+ virtual ActionResult< CBossAlpha > OnSuspend( CBossAlpha *me, Action< CBossAlpha > *interruptingAction ) { return Done(); }
+
+ virtual EventDesiredResult< CBossAlpha > OnInjured( CBossAlpha *me, const CTakeDamageInfo &info );
+
+ virtual const char *GetName( void ) const { return "NukeAttack"; } // return name of this action
+
+private:
+ CountdownTimer m_shakeTimer;
+ CountdownTimer m_chargeUpTimer;
+};
+
+#endif // TF_RAID_MODE
+
+#endif // BOSS_ALPHA_NUKE_ATTACK_H
diff --git a/game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_stunned.cpp b/game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_stunned.cpp
new file mode 100644
index 0000000..19f340f
--- /dev/null
+++ b/game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_stunned.cpp
@@ -0,0 +1,171 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// boss_alpha_stunned.cpp
+// Michael Booth, November 2010
+
+#include "cbase.h"
+
+#ifdef TF_RAID_MODE
+
+#include "tf_shareddefs.h"
+#include "tf_ammo_pack.h"
+#include "player_vs_environment/boss_alpha/boss_alpha.h"
+#include "player_vs_environment/boss_alpha/behavior/boss_alpha_stunned.h"
+
+
+extern ConVar tf_boss_alpha_min_nuke_after_stun_time;
+
+
+//---------------------------------------------------------------------------------------------
+CBossAlphaStunned::CBossAlphaStunned( float duration, Action< CBossAlpha > *nextAction )
+{
+ m_timer.Start( duration );
+ m_nextAction = nextAction;
+}
+
+
+//---------------------------------------------------------------------------------------------
+ConVar tf_boss_alpha_stun_ammo_count( "tf_boss_alpha_stun_ammo_count", "3"/*, FCVAR_CHEAT*/ );
+ConVar tf_boss_alpha_stun_ammo_amount( "tf_boss_alpha_stun_ammo_amount", "100"/*, FCVAR_CHEAT*/ );
+ConVar tf_boss_alpha_stun_ammo_velocity( "tf_boss_alpha_stun_ammo_velocity", "100"/*, FCVAR_CHEAT*/ );
+
+void TossAmmoPack( CBossAlpha *me )
+{
+ int iPrimary = tf_boss_alpha_stun_ammo_amount.GetInt();
+ int iSecondary = tf_boss_alpha_stun_ammo_amount.GetInt();
+ int iMetal = tf_boss_alpha_stun_ammo_amount.GetInt();
+
+ // Create the ammo pack.
+ CTFAmmoPack *pAmmoPack = CTFAmmoPack::Create( me->GetAbsOrigin(), me->GetAbsAngles(), NULL, "models/items/ammopack_medium.mdl" );
+ if ( pAmmoPack )
+ {
+/*
+ Vector vel;
+
+ vel.x = RandomFloat( -1.0f, 1.0f ) * tf_boss_alpha_stun_ammo_velocity.GetFloat();
+ vel.y = RandomFloat( -1.0f, 1.0f ) * tf_boss_alpha_stun_ammo_velocity.GetFloat();
+ vel.z = tf_boss_alpha_stun_ammo_velocity.GetFloat();
+
+ pAmmoPack->SetInitialVelocity( vel );
+*/
+ pAmmoPack->m_nSkin = 0;
+
+ // Give the ammo pack some health, so that trains can destroy it.
+ pAmmoPack->SetCollisionGroup( COLLISION_GROUP_DEBRIS );
+ pAmmoPack->m_takedamage = DAMAGE_YES;
+ pAmmoPack->SetHealth( 900 );
+
+ pAmmoPack->SetBodygroup( 1, 1 );
+
+ pAmmoPack->ApplyLocalAngularVelocityImpulse( AngularImpulse( 600, random->RandomInt( -1200, 1200 ), 0 ) );
+
+ DispatchSpawn( pAmmoPack );
+
+ // Fill up the ammo pack.
+ pAmmoPack->GiveAmmo( iPrimary, TF_AMMO_PRIMARY );
+ pAmmoPack->GiveAmmo( iSecondary, TF_AMMO_SECONDARY );
+ pAmmoPack->GiveAmmo( iMetal, TF_AMMO_METAL );
+ }
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CBossAlpha > CBossAlphaStunned::OnStart( CBossAlpha *me, Action< CBossAlpha > *priorAction )
+{
+ // start animation
+ me->GetBodyInterface()->StartActivity( ACT_MP_STAND_MELEE );
+ m_layerUsed = me->AddLayeredSequence( me->LookupSequence( "PRIMARY_Stun_begin" ), 0 );
+ m_state = BECOMING_STUNNED;
+
+ m_timer.Reset();
+
+ me->AddCondition( CBossAlpha::STUNNED );
+ me->EmitSound( "RobotBoss.StunStart" );
+
+ // throw out some ammo
+ for( int i=0; i<tf_boss_alpha_stun_ammo_count.GetInt(); ++i )
+ {
+ TossAmmoPack( me );
+ }
+
+ // relay the event to the map logic
+ me->m_outputOnStunned.FireOutput( me, me );
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CBossAlpha > CBossAlphaStunned::Update( CBossAlpha *me, float interval )
+{
+ switch( m_state )
+ {
+ case BECOMING_STUNNED:
+ if ( me->IsSequenceFinished() )
+ {
+ me->FastRemoveLayer( m_layerUsed );
+
+ m_state = STUNNED;
+ m_layerUsed = me->AddLayeredSequence( me->LookupSequence( "PRIMARY_stun_middle" ), 0 );
+ me->SetLayerLooping( m_layerUsed, true );
+ me->EmitSound( "RobotBoss.Stunned" );
+ }
+ break;
+
+ case STUNNED:
+ if ( m_timer.IsElapsed() )
+ {
+ me->FastRemoveLayer( m_layerUsed );
+
+ m_state = RECOVERING;
+ m_layerUsed = me->AddLayeredSequence( me->LookupSequence( "PRIMARY_stun_end" ), 0 );
+ me->StopSound( "RobotBoss.Stunned" );
+ me->EmitSound( "RobotBoss.StunRecover" );
+ }
+ break;
+
+ case RECOVERING:
+ if ( me->IsSequenceFinished() )
+ {
+ me->FastRemoveLayer( m_layerUsed );
+
+ if ( m_nextAction )
+ {
+ return ChangeTo( m_nextAction, "Stun finished" );
+ }
+
+ return Done( "Stun finished" );
+ }
+ break;
+ }
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+EventDesiredResult< CBossAlpha > CBossAlphaStunned::OnInjured( CBossAlpha *me, const CTakeDamageInfo &info )
+{
+ return TryToSustain( RESULT_CRITICAL );
+}
+
+
+//---------------------------------------------------------------------------------------------
+void CBossAlphaStunned::OnEnd( CBossAlpha *me, Action< CBossAlpha > *nextAction )
+{
+ me->RemoveCondition( CBossAlpha::STUNNED );
+
+ if ( me->HasAbility( CBossAlpha::CAN_ENRAGE ) )
+ {
+ // being stunned makes the boss ANGRY!
+ me->AddCondition( CBossAlpha::ENRAGED );
+ }
+
+ // make sure the boss attacks at least once before he starts a nuke
+ if ( me->GetNukeTimer()->GetRemainingTime() < tf_boss_alpha_min_nuke_after_stun_time.GetFloat() )
+ {
+ me->GetNukeTimer()->Start( tf_boss_alpha_min_nuke_after_stun_time.GetFloat() );
+ }
+}
+
+
+#endif // TF_RAID_MODE
diff --git a/game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_stunned.h b/game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_stunned.h
new file mode 100644
index 0000000..d73ba17
--- /dev/null
+++ b/game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_stunned.h
@@ -0,0 +1,39 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// boss_alpha_stunned.h
+// Michael Booth, November 2010
+
+#ifndef BOSS_ALPHA_STUNNED_H
+#define BOSS_ALPHA_STUNNED_H
+
+#ifdef TF_RAID_MODE
+
+class CBossAlphaStunned : public Action< CBossAlpha >
+{
+public:
+ CBossAlphaStunned( float duration, Action< CBossAlpha > *nextAction = NULL );
+
+ virtual ActionResult< CBossAlpha > OnStart( CBossAlpha *me, Action< CBossAlpha > *priorAction );
+ virtual ActionResult< CBossAlpha > Update( CBossAlpha *me, float interval );
+ virtual void OnEnd( CBossAlpha *me, Action< CBossAlpha > *nextAction );
+
+ virtual EventDesiredResult< CBossAlpha > OnInjured( CBossAlpha *me, const CTakeDamageInfo &info );
+
+ virtual const char *GetName( void ) const { return "Stunned"; } // return name of this action
+
+private:
+ CountdownTimer m_timer;
+ enum StunStateType
+ {
+ BECOMING_STUNNED,
+ STUNNED,
+ RECOVERING
+ }
+ m_state;
+ int m_layerUsed;
+
+ Action< CBossAlpha > *m_nextAction;
+};
+
+#endif // TF_RAID_MODE
+
+#endif // BOSS_ALPHA_STUNNED_H
diff --git a/game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_tactical_monitor.cpp b/game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_tactical_monitor.cpp
new file mode 100644
index 0000000..eca4107
--- /dev/null
+++ b/game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_tactical_monitor.cpp
@@ -0,0 +1,81 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// boss_alpha_tactical_monitor.cpp
+// Michael Booth, November 2010
+
+#include "cbase.h"
+
+#ifdef TF_RAID_MODE
+
+#include "tf_gamerules.h"
+#include "player_vs_environment/boss_alpha/boss_alpha.h"
+#include "player_vs_environment/boss_alpha/behavior/boss_alpha_tactical_monitor.h"
+#include "player_vs_environment/boss_alpha/behavior/boss_alpha_get_off_me.h"
+#include "player_vs_environment/boss_alpha/behavior/boss_alpha_wait_for_players.h"
+#include "player_vs_environment/boss_alpha/behavior/boss_alpha_stunned.h"
+
+
+ConVar tf_boss_alpha_get_off_me_duration( "tf_boss_alpha_get_off_me_duration", "3"/*, FCVAR_CHEAT */ );
+ConVar tf_boss_alpha_stunned_duration( "tf_boss_alpha_stunned_duration", "10" );
+
+
+//---------------------------------------------------------------------------------------------
+Action< CBossAlpha > *CBossAlphaTacticalMonitor::InitialContainedAction( CBossAlpha *me )
+{
+ if ( TFGameRules()->IsBossBattleMode() )
+ {
+ return new CBossAlphaWaitForPlayers;
+ }
+
+ return NULL;
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CBossAlpha > CBossAlphaTacticalMonitor::OnStart( CBossAlpha *me, Action< CBossAlpha > *priorAction )
+{
+ m_getOffMeTimer.Invalidate();
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CBossAlpha > CBossAlphaTacticalMonitor::Update( CBossAlpha *me, float interval )
+{
+ if ( me->IsInCondition( CBossAlpha::STUNNED ) )
+ {
+ return SuspendFor( new CBossAlphaStunned( tf_boss_alpha_stunned_duration.GetFloat() ), "Ouch!" );
+ }
+
+ if ( !m_getOffMeTimer.HasStarted() )
+ {
+ CUtlVector< CTFPlayer * > onMeVector;
+ me->CollectPlayersStandingOnMe( &onMeVector );
+
+ if ( onMeVector.Count() )
+ {
+ // someone is standing on me - push them off soon
+ m_getOffMeTimer.Start( tf_boss_alpha_get_off_me_duration.GetFloat() );
+ }
+ }
+ else if ( m_getOffMeTimer.IsElapsed() )
+ {
+ if ( !me->IsBusy() )
+ {
+ m_getOffMeTimer.Invalidate();
+
+ // if someone is still on me, push them off
+ CUtlVector< CTFPlayer * > onMeVector;
+ me->CollectPlayersStandingOnMe( &onMeVector );
+ if ( onMeVector.Count() )
+ {
+ return SuspendFor( new CBossAlphaGetOffMe, "Get offa me!" );
+ }
+ }
+ }
+
+ return Continue();
+}
+
+
+#endif // TF_RAID_MODE
diff --git a/game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_tactical_monitor.h b/game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_tactical_monitor.h
new file mode 100644
index 0000000..e612b7c
--- /dev/null
+++ b/game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_tactical_monitor.h
@@ -0,0 +1,32 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// boss_alpha_tactical_monitor.h
+// Michael Booth, November 2010
+
+#ifndef BOSS_ALPHA_TACTICAL_MONITOR_H
+#define BOSS_ALPHA_TACTICAL_MONITOR_H
+
+#ifdef TF_RAID_MODE
+
+class CBossAlpha;
+
+
+//---------------------------------------------------------------------------------------------
+class CBossAlphaTacticalMonitor : public Action< CBossAlpha >
+{
+public:
+ virtual Action< CBossAlpha > *InitialContainedAction( CBossAlpha *me );
+
+ virtual ActionResult< CBossAlpha > OnStart( CBossAlpha *me, Action< CBossAlpha > *priorAction );
+ virtual ActionResult< CBossAlpha > Update( CBossAlpha *me, float interval );
+
+ virtual const char *GetName( void ) const { return "TacticalMonitor"; } // return name of this action
+
+private:
+ CountdownTimer m_backOffCooldownTimer;
+ CountdownTimer m_getOffMeTimer;
+};
+
+
+#endif // TF_RAID_MODE
+
+#endif // BOSS_ALPHA_TACTICAL_MONITOR_H
diff --git a/game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_wait_for_players.cpp b/game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_wait_for_players.cpp
new file mode 100644
index 0000000..cec965f
--- /dev/null
+++ b/game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_wait_for_players.cpp
@@ -0,0 +1,79 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// boss_alpha_wait_for_players.cpp
+// Michael Booth, November 2010
+
+#include "cbase.h"
+
+#ifdef TF_RAID_MODE
+
+#include "player_vs_environment/boss_alpha/boss_alpha.h"
+#include "player_vs_environment/boss_alpha/behavior/boss_alpha_wait_for_players.h"
+#include "player_vs_environment/boss_alpha/behavior/boss_alpha_guard_spot.h"
+
+
+extern ConVar tf_boss_alpha_nuke_interval;
+
+ConVar tf_boss_alpha_sleep( "tf_boss_alpha_sleep", "0"/*, FCVAR_CHEAT */ );
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CBossAlpha > CBossAlphaWaitForPlayers::OnStart( CBossAlpha *me, Action< CBossAlpha > *priorAction )
+{
+ me->AddCondition( CBossAlpha::BUSY );
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CBossAlpha > CBossAlphaWaitForPlayers::Update( CBossAlpha *me, float interval )
+{
+ if ( tf_boss_alpha_sleep.GetBool() )
+ {
+ return Continue();
+ }
+
+ CBaseCombatCharacter *target = me->GetAttackTarget();
+ if ( target )
+ {
+ return ChangeTo( new CBossAlphaGuardSpot, "I see you..." );
+ }
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+void CBossAlphaWaitForPlayers::OnEnd( CBossAlpha *me, Action< CBossAlpha > *nextAction )
+{
+ me->RemoveCondition( CBossAlpha::BUSY );
+
+ me->GetNukeTimer()->Start( tf_boss_alpha_nuke_interval.GetFloat() );
+ me->GetGrenadeTimer()->Reset();
+}
+
+
+//---------------------------------------------------------------------------------------------
+EventDesiredResult< CBossAlpha > CBossAlphaWaitForPlayers::OnInjured( CBossAlpha *me, const CTakeDamageInfo &info )
+{
+ if ( tf_boss_alpha_sleep.GetBool() )
+ {
+ return TryContinue();
+ }
+
+ return TryChangeTo( new CBossAlphaGuardSpot, RESULT_CRITICAL, "Ouch!" );
+}
+
+
+//---------------------------------------------------------------------------------------------
+EventDesiredResult< CBossAlpha > CBossAlphaWaitForPlayers::OnContact( CBossAlpha *me, CBaseEntity *other, CGameTrace *result )
+{
+ if ( other && other->IsPlayer() && !tf_boss_alpha_sleep.GetBool() )
+ {
+ return TryChangeTo( new CBossAlphaGuardSpot, RESULT_CRITICAL, "Don't touch me" );
+ }
+
+ return TryContinue();
+}
+
+#endif // TF_RAID_MODE
diff --git a/game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_wait_for_players.h b/game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_wait_for_players.h
new file mode 100644
index 0000000..a24011d
--- /dev/null
+++ b/game/server/tf/player_vs_environment/boss_alpha/behavior/boss_alpha_wait_for_players.h
@@ -0,0 +1,25 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// boss_alpha_wait_for_players.h
+// Michael Booth, November 2010
+
+#ifndef BOSS_ALPHA_WAIT_FOR_PLAYER_H
+#define BOSS_ALPHA_WAIT_FOR_PLAYER_H
+
+#ifdef TF_RAID_MODE
+
+class CBossAlphaWaitForPlayers : public Action< CBossAlpha >
+{
+public:
+ virtual ActionResult< CBossAlpha > OnStart( CBossAlpha *me, Action< CBossAlpha > *priorAction );
+ virtual ActionResult< CBossAlpha > Update( CBossAlpha *me, float interval );
+ virtual void OnEnd( CBossAlpha *me, Action< CBossAlpha > *nextAction );
+
+ virtual EventDesiredResult< CBossAlpha > OnInjured( CBossAlpha *me, const CTakeDamageInfo &info );
+ virtual EventDesiredResult< CBossAlpha > OnContact( CBossAlpha *me, CBaseEntity *other, CGameTrace *result = NULL );
+
+ virtual const char *GetName( void ) const { return "WaitForPlayers"; } // return name of this action
+};
+
+#endif // TF_RAID_MODE
+
+#endif // BOSS_ALPHA_WAIT_FOR_PLAYER_H
diff --git a/game/server/tf/player_vs_environment/boss_alpha/boss_alpha.cpp b/game/server/tf/player_vs_environment/boss_alpha/boss_alpha.cpp
new file mode 100644
index 0000000..043455f
--- /dev/null
+++ b/game/server/tf/player_vs_environment/boss_alpha/boss_alpha.cpp
@@ -0,0 +1,1385 @@
+//========= 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
diff --git a/game/server/tf/player_vs_environment/boss_alpha/boss_alpha.h b/game/server/tf/player_vs_environment/boss_alpha/boss_alpha.h
new file mode 100644
index 0000000..e4e7859
--- /dev/null
+++ b/game/server/tf/player_vs_environment/boss_alpha/boss_alpha.h
@@ -0,0 +1,508 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// boss_alpha.h
+// Our first "real" TF Boss
+// Michael Booth, November 2010
+
+#ifndef BOSS_ALPHA_H
+#define BOSS_ALPHA_H
+
+#ifdef TF_RAID_MODE
+
+#include "NextBot.h"
+#include "NextBotBehavior.h"
+#include "NextBotGroundLocomotion.h"
+#include "Path/NextBotPathFollow.h"
+#include "bot_npc/bot_npc_body.h"
+#include "bot/map_entities/tf_spawner_boss.h"
+
+class CTFPlayer;
+class CBossAlpha;
+
+
+//----------------------------------------------------------------------------
+class CBossAlphaLocomotion : public NextBotGroundLocomotion
+{
+public:
+ CBossAlphaLocomotion( INextBot *bot );
+ virtual ~CBossAlphaLocomotion() { }
+
+ virtual float GetRunSpeed( void ) const; // get maximum running speed
+ virtual float GetStepHeight( void ) const; // if delta Z is greater than this, we have to jump to get up
+ virtual float GetMaxJumpHeight( void ) const; // return maximum height of a jump
+
+ virtual float GetMaxAcceleration( void ) const
+ {
+ return 2500.0f;
+ }
+
+ virtual float GetMaxYawRate( void ) const // return max rate of yaw rotation
+ {
+ return 50.0f;
+ }
+
+private:
+ float m_runSpeed;
+};
+
+
+//----------------------------------------------------------------------------
+class CBossAlphaIntention : public IIntention
+{
+public:
+ CBossAlphaIntention( CBossAlpha *me );
+ virtual ~CBossAlphaIntention();
+
+ virtual void Reset( void );
+ virtual void Update( void );
+
+ virtual QueryResultType IsPositionAllowed( const INextBot *me, const Vector &pos ) const; // is the a place we can be?
+
+ virtual INextBotEventResponder *FirstContainedResponder( void ) const { return m_behavior; }
+ virtual INextBotEventResponder *NextContainedResponder( INextBotEventResponder *current ) const { return NULL; }
+
+private:
+ Behavior< CBossAlpha > *m_behavior;
+};
+
+
+//----------------------------------------------------------------------------
+class CBossAlphaVision : public IVision
+{
+public:
+ CBossAlphaVision( INextBot *bot ) : IVision( bot )
+ {
+ }
+
+ virtual ~CBossAlphaVision() { }
+
+ virtual bool IsIgnored( CBaseEntity *subject ) const; // return true to completely ignore this entity (may not be in sight when this is called)
+};
+
+
+//----------------------------------------------------------------------------
+//----------------------------------------------------------------------------
+class CBossAlpha : public NextBotCombatCharacter
+{
+public:
+ DECLARE_CLASS( CBossAlpha, NextBotCombatCharacter );
+ DECLARE_SERVERCLASS();
+ DECLARE_DATADESC();
+
+ CBossAlpha();
+ virtual ~CBossAlpha();
+
+ virtual void Precache();
+ virtual void Spawn( void );
+
+ virtual int OnTakeDamage_Alive( const CTakeDamageInfo &info );
+
+ // INextBot
+ virtual CBossAlphaIntention *GetIntentionInterface( void ) const { return m_intention; }
+ virtual CBossAlphaLocomotion *GetLocomotionInterface( void ) const { return m_locomotor; }
+ virtual CBotNPCBody *GetBodyInterface( void ) const { return m_body; }
+ virtual CBossAlphaVision *GetVisionInterface( void ) const { return m_vision; }
+
+ virtual bool IsRemovedOnReset( void ) const { return false; } // remove this bot when the NextBot manager calls Reset
+
+ virtual void Update( void );
+
+// virtual bool IsPotentiallyChaseable( CTFPlayer *victim );
+
+ void Break( void ); // bust into gibs
+
+ struct AttackerInfo
+ {
+ CHandle< CBaseCombatCharacter > m_attacker;
+ float m_timestamp;
+ float m_damage;
+ bool m_wasCritical;
+ };
+ const CUtlVector< AttackerInfo > &GetAttackerVector( void ) const;
+ void RememberAttacker( CBaseCombatCharacter *attacker, float damage, bool wasCritical );
+
+ struct ThreatInfo
+ {
+ CHandle< CBaseCombatCharacter > m_who;
+ float m_threat;
+ };
+
+ const ThreatInfo *GetMaxThreat( void ) const;
+ const ThreatInfo *GetThreat( CBaseCombatCharacter *who ) const;
+
+ void SwingAxe( void );
+ void UpdateAxeSwing( void );
+ bool IsSwingingAxe( void ) const;
+
+
+ //----------------------------------
+ enum Ability
+ {
+ CAN_BE_STUNNED = 0x01,
+ CAN_NUKE = 0x02,
+ CAN_ENRAGE = 0x04,
+ CAN_FIRE_ROCKETS = 0x08,
+ CAN_LAUNCH_STICKIES = 0x10,
+ CAN_LAUNCH_MINIONS = 0x20,
+ };
+ virtual bool HasAbility( Ability ability ) const;
+
+ virtual bool IsMiniBoss( void ) const { return false; }
+
+ virtual float GetMoveSpeed( void ) const { return 300.0f; }
+
+ virtual int GetRocketLaunchCount( void ) const { return 5; }
+ virtual float GetRocketDamage( void ) const { return 25.0f; }
+ virtual float GetRocketAimError( void ) const { return 1.81f; }
+ virtual float GetRocketInterval( void ) const { return 0.3f; }
+ virtual const char *GetRocketSoundEffect( void ) const { return "Weapon_RPG.Single"; }
+
+ virtual float GetGrenadeInterval( void ) const { return 10.0f; }
+
+ virtual float GetBecomeStunnedDamage( void ) const { return 500.0f; }
+
+
+ //----------------------------------
+ enum Condition
+ {
+ SHIELDED = 0x01,
+ CHARGING = 0x02,
+ STUNNED = 0x04,
+ INVULNERABLE = 0x08,
+ VULNERABLE_TO_STUN = 0x10,
+ BUSY = 0x20,
+ ENRAGED = 0x40,
+ };
+
+ bool IsBusy( void ) const; // returns true if we're in a condition that means we can't start another action
+
+ void AddCondition( Condition c );
+ void RemoveCondition( Condition c );
+ bool IsInCondition( Condition c ) const;
+
+ bool IsAttackTarget( CBaseCombatCharacter *target ) const;
+ bool HasAttackTarget( void ) const;
+ void SetAttackTarget( CBaseCombatCharacter *target, float duration = 0.0f );
+ CBaseCombatCharacter *GetAttackTarget( void ) const;
+ void LockAttackTarget( void ); // don't allow target to change until it is unlocked or the target is destroyed
+ void UnlockAttackTarget( void );
+
+ CBaseCombatCharacter *GetNearestVisibleEnemy( void ) const;
+
+ void SetHomePosition( const Vector &pos );
+ const Vector &GetHomePosition( void ) const;
+
+ CBaseAnimating *GetWeapon( void ) const;
+ CBaseAnimating *GetShield( void ) const;
+
+ CountdownTimer *GetNukeTimer( void );
+ CountdownTimer *GetGrenadeTimer( void );
+
+ float GetReceivedDamagePerSecond( void ) const;
+ float GetReceivedDamagePerSecondDelta( void ) const;
+
+ void ClearStunDamage( void );
+ void AccumulateStunDamage( float damage );
+ float GetStunDamage( void ) const;
+
+ CTFPlayer *GetClosestMinionPrisoner( void );
+ bool IsPrisonerOfMinion( CBaseCombatCharacter *victim );
+
+ void StartNukeEffect( void );
+ void StopNukeEffect( void );
+
+ float GetAge( void ) const; // how long have we been alive
+
+ void CollectPlayersStandingOnMe( CUtlVector< CTFPlayer * > *playerVector );
+
+ // Entity I/O
+ COutputEvent m_outputOnStunned;
+
+ COutputEvent m_outputOnHealthBelow90Percent;
+ COutputEvent m_outputOnHealthBelow80Percent;
+ COutputEvent m_outputOnHealthBelow70Percent;
+ COutputEvent m_outputOnHealthBelow60Percent;
+ COutputEvent m_outputOnHealthBelow50Percent;
+ COutputEvent m_outputOnHealthBelow40Percent;
+ COutputEvent m_outputOnHealthBelow30Percent;
+ COutputEvent m_outputOnHealthBelow20Percent;
+ COutputEvent m_outputOnHealthBelow10Percent;
+
+ COutputEvent m_outputOnKilled;
+
+protected:
+ virtual void TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator );
+
+private:
+ CBossAlphaIntention *m_intention;
+ CBossAlphaLocomotion *m_locomotor;
+ CBotNPCBody *m_body;
+ CBossAlphaVision *m_vision;
+
+ CBaseAnimating *m_axe;
+ CBaseAnimating *m_shield;
+
+ CountdownTimer m_axeSwingTimer;
+ CountdownTimer m_attackTimer;
+ CountdownTimer m_nukeTimer;
+ CountdownTimer m_grenadeTimer;
+ CountdownTimer m_ouchTimer;
+ CountdownTimer m_hateTauntTimer;
+
+ CNetworkVar( bool, m_isNuking );
+
+ CHandle< CBaseCombatCharacter > m_nearestVisibleEnemy;
+ void UpdateNearestVisibleEnemy( void );
+ CountdownTimer m_nearestVisibleEnemyTimer;
+
+ CUtlVector< AttackerInfo > m_attackerVector; // list of everyone who injured me, and when
+ CUtlVector< ThreatInfo > m_threatVector; // list of attackers and their current damage/second on me
+
+ float m_currentDamagePerSecond;
+ float m_lastDamagePerSecond;
+ void UpdateDamagePerSecond( void );
+
+ CHandle< CBaseCombatCharacter > m_attackTarget;
+ CountdownTimer m_attackTargetTimer;
+ bool m_isAttackTargetLocked;
+ void UpdateAttackTarget( void );
+
+ int m_damagePoseParameter;
+
+ bool m_isShielded;
+ Vector m_homePos;
+
+ bool IsIgnored( CTFPlayer *player ) const;
+
+ unsigned int m_conditionFlags;
+
+ float m_stunDamage;
+ float m_lastHealthPercentage;
+
+ IntervalTimer m_ageTimer;
+
+ void UpdateSkillShots( void );
+
+ bool CheckSkillShots( const CTakeDamageInfo &info );
+ CountdownTimer m_headStunTimer;
+ CountdownTimer m_consecutiveRocketTimer;
+ int m_consecutiveRockets;
+
+ CountdownTimer m_ricochetSoundTimer;
+
+ void ResetSkillShots( void );
+ void OnSkillShotComboStarted( void );
+ void OnSkillShot( void );
+
+ CountdownTimer m_skillShotComboTimer;
+ int m_skillShotCount;
+
+ bool m_isPrecisionShotDone;
+ CountdownTimer m_precisionSkillShotTimer;
+ bool m_isPrecisionShotHit[3];
+
+ bool m_isDamageSpongeSkillShotDone;
+ float m_damageSpongeSkillShotAmount;
+
+ bool m_isHardHitSkillShotDone;
+
+ trace_t m_lastTraceAttackTrace;
+ Vector m_lastTraceAttackDir;
+};
+
+
+inline bool CBossAlpha::HasAbility( Ability ability ) const
+{
+ const int myAbilities = CAN_BE_STUNNED | CAN_NUKE | CAN_ENRAGE | CAN_FIRE_ROCKETS | CAN_LAUNCH_STICKIES | CAN_LAUNCH_MINIONS;
+
+ return myAbilities & ability ? true : false;
+}
+
+inline bool CBossAlpha::IsAttackTarget( CBaseCombatCharacter *target ) const
+{
+ if ( HasAttackTarget() )
+ {
+ return ( m_attackTarget == target ) ? true : false;
+ }
+ return false;
+}
+
+inline bool CBossAlpha::HasAttackTarget( void ) const
+{
+ return ( m_attackTarget == NULL || !m_attackTarget->IsAlive() ) ? false : true;
+}
+
+inline void CBossAlpha::LockAttackTarget( void )
+{
+ m_isAttackTargetLocked = HasAttackTarget();
+}
+
+inline void CBossAlpha::UnlockAttackTarget( void )
+{
+ m_isAttackTargetLocked = false;
+}
+
+inline float CBossAlpha::GetAge( void ) const
+{
+ return m_ageTimer.GetElapsedTime();
+}
+
+inline void CBossAlpha::StartNukeEffect( void )
+{
+ m_isNuking = true;
+}
+
+inline void CBossAlpha::StopNukeEffect( void )
+{
+ m_isNuking = false;
+}
+
+inline void CBossAlpha::ClearStunDamage( void )
+{
+ m_stunDamage = 0.0f;
+}
+
+inline void CBossAlpha::AccumulateStunDamage( float damage )
+{
+ m_stunDamage += damage;
+}
+
+inline float CBossAlpha::GetStunDamage( void ) const
+{
+ return m_stunDamage;
+}
+
+inline float CBossAlpha::GetReceivedDamagePerSecond( void ) const
+{
+ return m_currentDamagePerSecond;
+}
+
+inline float CBossAlpha::GetReceivedDamagePerSecondDelta( void ) const
+{
+ return m_currentDamagePerSecond - m_lastDamagePerSecond;
+}
+
+inline CountdownTimer *CBossAlpha::GetNukeTimer( void )
+{
+ return &m_nukeTimer;
+}
+
+inline CountdownTimer *CBossAlpha::GetGrenadeTimer( void )
+{
+ return &m_grenadeTimer;
+}
+
+inline CBaseAnimating *CBossAlpha::GetWeapon( void ) const
+{
+ return m_axe;
+}
+
+inline CBaseAnimating *CBossAlpha::GetShield( void ) const
+{
+ return m_shield;
+}
+
+inline void CBossAlpha::SetHomePosition( const Vector &pos )
+{
+ m_homePos = pos;
+}
+
+inline const Vector &CBossAlpha::GetHomePosition( void ) const
+{
+ return m_homePos;
+}
+
+inline CBaseCombatCharacter *CBossAlpha::GetNearestVisibleEnemy( void ) const
+{
+ return m_nearestVisibleEnemy;
+}
+
+inline void CBossAlpha::AddCondition( Condition c )
+{
+ m_conditionFlags |= c;
+}
+
+inline bool CBossAlpha::IsInCondition( Condition c ) const
+{
+ return ( m_conditionFlags & c ) ? true : false;
+}
+
+inline const CUtlVector< CBossAlpha::AttackerInfo > &CBossAlpha::GetAttackerVector( void ) const
+{
+ return m_attackerVector;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+class CBossAlphaPathCost : public IPathCost
+{
+public:
+ CBossAlphaPathCost( CBossAlpha *me )
+ {
+ m_me = me;
+ }
+
+ // return the cost (weighted distance between) of moving from "fromArea" to "area", or -1 if the move is not allowed
+ virtual float operator()( CNavArea *area, CNavArea *fromArea, const CNavLadder *ladder, const CFuncElevator *elevator, float length ) const
+ {
+ if ( fromArea == NULL )
+ {
+ // first area in path, no cost
+ return 0.0f;
+ }
+ else
+ {
+ if ( !m_me->GetLocomotionInterface()->IsAreaTraversable( area ) )
+ {
+ // our locomotor says we can't move here
+ return -1.0f;
+ }
+
+ // compute distance traveled along path so far
+ float dist;
+
+ if ( ladder )
+ {
+ dist = ladder->m_length;
+ }
+ else if ( length > 0.0 )
+ {
+ // optimization to avoid recomputing length
+ dist = length;
+ }
+ else
+ {
+ dist = ( area->GetCenter() - fromArea->GetCenter() ).Length();
+ }
+
+ float cost = dist + fromArea->GetCostSoFar();
+
+ // check height change
+ float deltaZ = fromArea->ComputeAdjacentConnectionHeightChange( area );
+ if ( deltaZ >= m_me->GetLocomotionInterface()->GetStepHeight() )
+ {
+ if ( deltaZ >= m_me->GetLocomotionInterface()->GetMaxJumpHeight() )
+ {
+ // too high to reach
+ return -1.0f;
+ }
+
+ // jumping is slower than flat ground
+ const float jumpPenalty = 5.0f;
+ cost += jumpPenalty * dist;
+ }
+ else if ( deltaZ < -m_me->GetLocomotionInterface()->GetDeathDropHeight() )
+ {
+ // too far to drop
+ return -1.0f;
+ }
+
+ return cost;
+ }
+ }
+
+ CBossAlpha *m_me;
+};
+
+#endif // TF_RAID_MODE
+
+#endif // BOSS_ALPHA_H