diff options
Diffstat (limited to 'game/server/tf/player_vs_environment/boss_alpha')
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 |