summaryrefslogtreecommitdiff
path: root/game/server/tf/halloween/halloween_behavior/crybaby_boss_attack.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'game/server/tf/halloween/halloween_behavior/crybaby_boss_attack.cpp')
-rw-r--r--game/server/tf/halloween/halloween_behavior/crybaby_boss_attack.cpp269
1 files changed, 269 insertions, 0 deletions
diff --git a/game/server/tf/halloween/halloween_behavior/crybaby_boss_attack.cpp b/game/server/tf/halloween/halloween_behavior/crybaby_boss_attack.cpp
new file mode 100644
index 0000000..e2decd4
--- /dev/null
+++ b/game/server/tf/halloween/halloween_behavior/crybaby_boss_attack.cpp
@@ -0,0 +1,269 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// crybaby_boss_attack.cpp
+// Halloween Boss 2011 chase and attack behavior
+// Michael Booth, October 2011
+
+#include "cbase.h"
+
+#include "tf_player.h"
+#include "tf_gamerules.h"
+#include "tf_team.h"
+#include "nav_mesh/tf_nav_area.h"
+#include "particle_parse.h"
+#include "team_control_point_master.h"
+
+#include "../headless_hatman.h"
+#include "crybaby_boss_attack.h"
+
+
+//----------------------------------------------------------------------------------
+CCryBabyBossAttack::CCryBabyBossAttack( CTFPlayer *victim )
+{
+ m_victim = victim;
+}
+
+
+//----------------------------------------------------------------------------------
+ActionResult< CHeadlessHatman > CCryBabyBossAttack::OnStart( CHeadlessHatman *me, Action< CHeadlessHatman > *priorAction )
+{
+ // start animation
+ me->GetBodyInterface()->StartActivity( ACT_MP_RUN_ITEM1 );
+
+ m_axeSwingTimer.Invalidate();
+ m_attackTimer.Invalidate();
+
+ m_attackTarget = NULL;
+ m_attackTargetFocusTimer.Invalidate();
+
+ return Continue();
+}
+
+
+//----------------------------------------------------------------------------------
+bool CCryBabyBossAttack::IsSwingingAxe( void ) const
+{
+ return !m_axeSwingTimer.IsElapsed();
+}
+
+
+//----------------------------------------------------------------------------------
+void CCryBabyBossAttack::UpdateAxeSwing( CHeadlessHatman *me )
+{
+ if ( m_axeSwingTimer.HasStarted() )
+ {
+ // continue axe swing
+ if ( m_axeSwingTimer.IsElapsed() )
+ {
+ // moment of impact - did axe swing hit?
+ m_axeSwingTimer.Invalidate();
+
+ if ( m_attackTarget != NULL )
+ {
+ Vector forward;
+ me->GetVectors( &forward, NULL, NULL );
+
+ Vector toVictim = m_attackTarget->WorldSpaceCenter() - me->WorldSpaceCenter();
+ toVictim.NormalizeInPlace();
+
+ // looser tolerance as victim gets closer
+ const float closeRange = 100.0f;
+ float range = me->GetRangeTo( m_attackTarget );
+ float closeness = ( range < closeRange ) ? 0.0f : ( range - closeRange ) / ( tf_halloween_bot_attack_range.GetFloat() - closeRange );
+ float hitAngle = 0.0f + closeness * 0.27f;
+
+ if ( DotProduct( forward, toVictim ) > hitAngle )
+ {
+ if ( me->IsRangeLessThan( m_attackTarget, 0.9f * tf_halloween_bot_attack_range.GetFloat() ) )
+ {
+ if ( me->IsLineOfSightClear( m_attackTarget ) )
+ {
+ // CHOP!
+ CTakeDamageInfo info( me, me, 2.0f * m_attackTarget->GetHealth(), DMG_CLUB, TF_DMG_CUSTOM_DECAPITATION_BOSS );
+ CalculateMeleeDamageForce( &info, toVictim, me->WorldSpaceCenter(), 5.0f );
+ m_attackTarget->TakeDamage( info );
+ me->EmitSound( "Halloween.HeadlessBossAxeHitFlesh" );
+ }
+ }
+ }
+ }
+
+ // always playe the axe-hit-world impact sound, since it carries through the world better
+ me->EmitSound( "Halloween.HeadlessBossAxeHitWorld" );
+ UTIL_ScreenShake( me->GetAbsOrigin(), 15.0f, 5.0f, 1.0f, 1000.0f, SHAKE_START );
+
+ Vector bladePos;
+ QAngle bladeAngle;
+ if ( me->GetAxe() && me->GetAxe()->GetAttachment( "axe_blade", bladePos, bladeAngle ) )
+ {
+ DispatchParticleEffect( "halloween_boss_axe_hit_world", bladePos, bladeAngle );
+ }
+ }
+ }
+}
+
+
+//----------------------------------------------------------------------------------
+// Validate that our victim is still alive and someplace we can actually get to
+bool CCryBabyBossAttack::IsVictimChaseable( CHeadlessHatman *me )
+{
+ if ( m_victim == NULL )
+ {
+ return false;
+ }
+
+ if ( !m_victim->IsAlive() )
+ {
+ return false;
+ }
+
+ CTFNavArea *victimArea = (CTFNavArea *)m_victim->GetLastKnownArea();
+ if ( !victimArea || victimArea->HasAttributeTF( TF_NAV_SPAWN_ROOM_BLUE | TF_NAV_SPAWN_ROOM_RED ) )
+ {
+ // unreachable
+ return false;
+ }
+
+ if ( m_victim->GetGroundEntity() != NULL )
+ {
+ Vector victimAreaPos;
+ victimArea->GetClosestPointOnArea( m_victim->GetAbsOrigin(), &victimAreaPos );
+ if ( ( m_victim->GetAbsOrigin() - victimAreaPos ).AsVector2D().IsLengthGreaterThan( 50.0f ) )
+ {
+ // off the mesh and unreachable
+ return false;
+ }
+ }
+
+ return true;
+}
+
+
+//----------------------------------------------------------------------------------
+ActionResult< CHeadlessHatman > CCryBabyBossAttack::Update( CHeadlessHatman *me, float interval )
+{
+ if ( !me->IsAlive() )
+ {
+ return Done();
+ }
+
+ if ( !IsVictimChaseable( me ) )
+ {
+ return Done( "Victim is dead or unreachable" );
+ }
+
+ if ( !IsSwingingAxe() && m_laughTimer.IsElapsed() )
+ {
+ me->EmitSound( "Halloween.HeadlessBossLaugh" );
+ m_laughTimer.Start( RandomFloat( 3.0f, 5.0f ) );
+ }
+
+ // chase after our chase victim
+ const float standAndSwingRange = 100.0f;
+
+ if ( me->IsRangeGreaterThan( m_victim, standAndSwingRange ) || !me->IsLineOfSightClear( m_victim ) )
+ {
+ CHeadlessHatmanPathCost cost( me );
+ m_chasePath.Update( me, m_victim, cost );
+ }
+
+ if ( m_attackTargetFocusTimer.IsElapsed() || m_attackTarget == NULL )
+ {
+ m_attackTarget = m_victim.Get();
+ }
+
+ // swing our axe at our attack target if they are in range
+ if ( m_attackTarget != NULL && m_attackTarget->IsAlive() )
+ {
+ if ( me->IsRangeLessThan( m_attackTarget, tf_halloween_bot_attack_range.GetFloat() ) )
+ {
+ if ( m_attackTimer.IsElapsed() )
+ {
+ if ( !IsSwingingAxe() )
+ {
+ me->AddGesture( ACT_MP_ATTACK_STAND_ITEM1 );
+ m_axeSwingTimer.Start( 0.58f );
+ me->EmitSound( "Halloween.HeadlessBossAttack" );
+
+ m_attackTimer.Start( 1.0f );
+ }
+ }
+
+ me->GetLocomotionInterface()->FaceTowards( m_attackTarget->WorldSpaceCenter() );
+ }
+ }
+
+ // finish axe swing once it has begun
+ UpdateAxeSwing( me );
+
+ if ( me->GetLocomotionInterface()->IsAttemptingToMove() )
+ {
+ // play running animation
+ int healthRatio = 100 * me->GetHealth() / me->GetMaxHealth();
+
+ if ( healthRatio > 50 )
+ {
+ if ( !me->GetBodyInterface()->IsActivity( ACT_MP_RUN_ITEM1 ) )
+ {
+ me->GetBodyInterface()->StartActivity( ACT_MP_RUN_ITEM1 );
+ }
+ }
+ else
+ {
+ if ( !me->GetBodyInterface()->IsActivity( ACT_MP_RUN_MELEE ) )
+ {
+ me->GetBodyInterface()->StartActivity( ACT_MP_RUN_MELEE );
+ }
+ }
+
+ if ( m_footfallTimer.IsElapsed() )
+ {
+ m_footfallTimer.Start( 0.25f );
+
+ // periodically shake nearby players' screens when we're moving
+ UTIL_ScreenShake( me->GetAbsOrigin(), 15.0f, 5.0f, 1.0f, 1000.0f, SHAKE_START );
+ }
+ }
+ else
+ {
+ // standing still
+ if ( !me->GetBodyInterface()->IsActivity( ACT_MP_STAND_ITEM1 ) )
+ {
+ me->GetBodyInterface()->StartActivity( ACT_MP_STAND_ITEM1 );
+ }
+ }
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+EventDesiredResult< CHeadlessHatman > CCryBabyBossAttack::OnStuck( CHeadlessHatman *me )
+{
+ // we're stuck - just warp to the our next path goal
+ if ( m_chasePath.GetCurrentGoal() )
+ {
+ me->SetAbsOrigin( m_chasePath.GetCurrentGoal()->pos + Vector( 0, 0, 10.0f ) );
+ }
+
+ return TryContinue( RESULT_TRY );
+}
+
+
+//---------------------------------------------------------------------------------------------
+EventDesiredResult< CHeadlessHatman > CCryBabyBossAttack::OnContact( CHeadlessHatman *me, CBaseEntity *other, CGameTrace *result )
+{
+ if ( other )
+ {
+ CBaseCombatCharacter *combatChar = dynamic_cast< CBaseCombatCharacter * >( other );
+ if ( combatChar && combatChar->IsAlive() )
+ {
+ // force attack the thing we bumped into
+ // this prevents us from being stuck on dispensers, for example
+ m_attackTarget = combatChar;
+ m_attackTargetFocusTimer.Start( 3.0f );
+ }
+ }
+
+ return TryContinue( RESULT_TRY );
+}
+