summaryrefslogtreecommitdiff
path: root/game/server/tf/halloween/halloween_behavior
diff options
context:
space:
mode:
Diffstat (limited to 'game/server/tf/halloween/halloween_behavior')
-rw-r--r--game/server/tf/halloween/halloween_behavior/crybaby_boss_attack.cpp269
-rw-r--r--game/server/tf/halloween/halloween_behavior/crybaby_boss_attack.h47
-rw-r--r--game/server/tf/halloween/halloween_behavior/crybaby_boss_escape.cpp132
-rw-r--r--game/server/tf/halloween/halloween_behavior/crybaby_boss_escape.h28
-rw-r--r--game/server/tf/halloween/halloween_behavior/headless_hatman_attack.cpp543
-rw-r--r--game/server/tf/halloween/halloween_behavior/headless_hatman_attack.h54
-rw-r--r--game/server/tf/halloween/halloween_behavior/headless_hatman_dying.cpp44
-rw-r--r--game/server/tf/halloween/halloween_behavior/headless_hatman_dying.h20
-rw-r--r--game/server/tf/halloween/halloween_behavior/headless_hatman_emerge.cpp133
-rw-r--r--game/server/tf/halloween/halloween_behavior/headless_hatman_emerge.h26
-rw-r--r--game/server/tf/halloween/halloween_behavior/headless_hatman_terrify.cpp95
-rw-r--r--game/server/tf/halloween/halloween_behavior/headless_hatman_terrify.h26
12 files changed, 1417 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 );
+}
+
diff --git a/game/server/tf/halloween/halloween_behavior/crybaby_boss_attack.h b/game/server/tf/halloween/halloween_behavior/crybaby_boss_attack.h
new file mode 100644
index 0000000..8c2cff4
--- /dev/null
+++ b/game/server/tf/halloween/halloween_behavior/crybaby_boss_attack.h
@@ -0,0 +1,47 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// crybaby_boss_attack.h
+// Halloween Boss 2011 chase and attack behavior
+// Michael Booth, October 2011
+
+#ifndef CRYBABY_BOSS_ATTACK_H
+#define CRYBABY_BOSS_ATTACK_H
+
+#include "NextBot/Path/NextBotChasePath.h"
+
+//---------------------------------------------------------------------------------------------
+//---------------------------------------------------------------------------------------------
+class CCryBabyBossAttack : public Action< CHeadlessHatman >
+{
+public:
+ CCryBabyBossAttack( CTFPlayer *victim );
+ virtual ~CCryBabyBossAttack() { }
+
+ virtual ActionResult< CHeadlessHatman > OnStart( CHeadlessHatman *me, Action< CHeadlessHatman > *priorAction );
+ virtual ActionResult< CHeadlessHatman > Update( CHeadlessHatman *me, float interval );
+
+ virtual EventDesiredResult< CHeadlessHatman > OnStuck( CHeadlessHatman *me );
+ virtual EventDesiredResult< CHeadlessHatman > OnContact( CHeadlessHatman *me, CBaseEntity *other, CGameTrace *result = NULL );
+
+ virtual const char *GetName( void ) const { return "CryBabyBossAttack"; } // return name of this action
+
+private:
+ ChasePath m_chasePath;
+ CHandle< CTFPlayer > m_victim;
+
+ CountdownTimer m_axeSwingTimer;
+ CountdownTimer m_attackTimer;
+ CountdownTimer m_laughTimer;
+ CountdownTimer m_footfallTimer;
+
+ void UpdateAxeSwing( CHeadlessHatman *me );
+ bool IsSwingingAxe( void ) const;
+
+ bool IsVictimChaseable( CHeadlessHatman *me );
+
+ CHandle< CBaseCombatCharacter > m_attackTarget; // the victim I'm momentarily attacking
+ CountdownTimer m_attackTargetFocusTimer;
+};
+
+
+
+#endif // CRYBABY_BOSS_ATTACK_H
diff --git a/game/server/tf/halloween/halloween_behavior/crybaby_boss_escape.cpp b/game/server/tf/halloween/halloween_behavior/crybaby_boss_escape.cpp
new file mode 100644
index 0000000..ba64933
--- /dev/null
+++ b/game/server/tf/halloween/halloween_behavior/crybaby_boss_escape.cpp
@@ -0,0 +1,132 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// crybaby_boss_escape.cpp
+// Crybaby Boss runs to the pit in Mann Manor to escape
+// Michael Booth, October 2011
+
+#include "cbase.h"
+
+#include "nav_mesh.h"
+#include "tf_player.h"
+
+#include "../headless_hatman.h"
+#include "crybaby_boss_escape.h"
+#include "crybaby_boss_attack.h"
+
+extern ConVar tf_bot_path_lookahead_range;
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CHeadlessHatman > CCryBabyBossEscape::OnStart( CHeadlessHatman *me, Action< CHeadlessHatman > *priorAction )
+{
+ m_path.SetMinLookAheadDistance( tf_bot_path_lookahead_range.GetFloat() );
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CHeadlessHatman > CCryBabyBossEscape::Update( CHeadlessHatman *me, float interval )
+{
+ // any nearby player that taunts me, enrages me and provokes me to attack them!
+ CUtlVector< CTFPlayer * > playerVector;
+ CollectPlayers( &playerVector, TEAM_ANY, COLLECT_ONLY_LIVING_PLAYERS );
+
+ for( int i=0; i<playerVector.Count(); ++i )
+ {
+ if ( playerVector[i]->m_Shared.InCond( TF_COND_TAUNTING ) )
+ {
+ const float noticeTauntRange = 750.0f;
+ if ( me->IsRangeLessThan( playerVector[i], noticeTauntRange ) )
+ {
+ if ( playerVector[i]->IsLookingTowards( me ) )
+ {
+ if ( me->IsLineOfSightClear( playerVector[i] ) )
+ {
+ return SuspendFor( new CCryBabyBossAttack( playerVector[i] ), "DON'T LAUGH AT ME!!!" );
+ }
+ }
+ }
+ }
+ }
+
+ // push players away to avoid penetration issues
+ const float pushRange = 250.0f;
+ const float pushForce = 200.0f;
+
+ for( int i=0; i<playerVector.Count(); ++i )
+ {
+ CTFPlayer *player = playerVector[i];
+
+ Vector toPlayer = player->EyePosition() - me->GetAbsOrigin();
+ float range = toPlayer.NormalizeInPlace();
+
+ if ( range < pushRange )
+ {
+ // make sure we push players up and away
+ toPlayer.z = 0.0f;
+ toPlayer.NormalizeInPlace();
+ toPlayer.z = 1.0f;
+
+ Vector push = pushForce * toPlayer;
+
+ player->ApplyAbsVelocityImpulse( push );
+ }
+ }
+
+ // run to the pit and escape
+ if ( m_repathTimer.IsElapsed() )
+ {
+ m_repathTimer.Start( RandomFloat( 1.0f, 2.0f ) );
+
+ CHeadlessHatmanPathCost cost( me );
+
+ // hack for now
+ CNavArea *goalArea = TheNavMesh->GetNavAreaByID( 27 );
+ if ( !goalArea )
+ {
+ return Done( "No goal area!" );
+ }
+
+ m_path.Compute( me, goalArea->GetCenter(), cost );
+ }
+
+ m_path.Update( me );
+
+ // animation state
+ if ( me->GetLocomotionInterface()->IsAttemptingToMove() )
+ {
+ // play running animation
+ 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 > CCryBabyBossEscape::OnMoveToSuccess( CHeadlessHatman *me, const Path *path )
+{
+ UTIL_Remove( me );
+
+ return TryDone( RESULT_CRITICAL, "Reached escape pit" );
+}
+
diff --git a/game/server/tf/halloween/halloween_behavior/crybaby_boss_escape.h b/game/server/tf/halloween/halloween_behavior/crybaby_boss_escape.h
new file mode 100644
index 0000000..7f16d39
--- /dev/null
+++ b/game/server/tf/halloween/halloween_behavior/crybaby_boss_escape.h
@@ -0,0 +1,28 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// crybaby_boss_escape.h
+// Crybaby Boss runs to the pit in Mann Manor to escape
+// Michael Booth, October 2011
+
+#ifndef CRYBABY_ESCAPE_H
+#define CRYBABY_ESCAPE_H
+
+#include "Path/NextBotPathFollow.h"
+
+class CCryBabyBossEscape : public Action< CHeadlessHatman >
+{
+public:
+ virtual ActionResult< CHeadlessHatman > OnStart( CHeadlessHatman *me, Action< CHeadlessHatman > *priorAction );
+ virtual ActionResult< CHeadlessHatman > Update( CHeadlessHatman *me, float interval );
+
+ virtual EventDesiredResult< CHeadlessHatman > OnMoveToSuccess( CHeadlessHatman *me, const Path *path );
+
+ virtual const char *GetName( void ) const { return "CryBabyBossEscape"; };
+
+private:
+ PathFollower m_path;
+ CountdownTimer m_repathTimer;
+ CountdownTimer m_footfallTimer;
+};
+
+
+#endif // CRYBABY_ESCAPE_H
diff --git a/game/server/tf/halloween/halloween_behavior/headless_hatman_attack.cpp b/game/server/tf/halloween/halloween_behavior/headless_hatman_attack.cpp
new file mode 100644
index 0000000..014991c
--- /dev/null
+++ b/game/server/tf/halloween/halloween_behavior/headless_hatman_attack.cpp
@@ -0,0 +1,543 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// headless_hatman_attack.cpp
+// Halloween Boss chase and attack behavior
+// Michael Booth, October 2010
+
+#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 "headless_hatman_attack.h"
+#include "headless_hatman_terrify.h"
+
+
+ConVar tf_halloween_hhh_attack_kart_radius( "tf_halloween_hhh_attack_kart_radius", "300", FCVAR_CHEAT );
+
+//----------------------------------------------------------------------------------
+ActionResult< CHeadlessHatman > CHeadlessHatmanAttack::OnStart( CHeadlessHatman *me, Action< CHeadlessHatman > *priorAction )
+{
+ // smooth out the bot's path following by moving toward a point farther down the path
+ m_path.SetMinLookAheadDistance( 100.0f );
+
+ // start animation
+ me->GetBodyInterface()->StartActivity( ACT_MP_RUN_ITEM1 );
+
+ m_axeSwingTimer.Invalidate();
+ m_attackTimer.Invalidate();
+
+ m_closestVisible = NULL;
+ TFGameRules()->SetIT( NULL );
+ m_lastIT = NULL;
+ m_chaseVictimTimer.Invalidate();
+
+ m_attackTarget = NULL;
+ m_attackTargetFocusTimer.Invalidate();
+
+ m_scareTimer.Start( 20.0f );
+
+ m_homePos = me->GetAbsOrigin();
+ m_homePosRecalcTimer.Start( 3.0f );
+
+ return Continue();
+}
+
+
+//----------------------------------------------------------------------------------
+bool CHeadlessHatmanAttack::IsSwingingAxe( void ) const
+{
+ return !m_axeSwingTimer.IsElapsed();
+}
+
+
+//----------------------------------------------------------------------------------
+bool CHeadlessHatmanAttack::IsPotentiallyChaseable( CHeadlessHatman *me, CTFPlayer *victim )
+{
+ if ( !victim )
+ {
+ return false;
+ }
+
+ if ( !victim->IsAlive() )
+ {
+ // victim is dead - pick a new one
+ return false;
+ }
+
+ if ( victim->m_Shared.InCond( TF_COND_HALLOWEEN_GHOST_MODE ) )
+ {
+ // don't attack ghost
+ return false;
+ }
+
+ CTFNavArea *victimArea = (CTFNavArea *)victim->GetLastKnownArea();
+ if ( !victimArea || victimArea->HasAttributeTF( TF_NAV_SPAWN_ROOM_BLUE | TF_NAV_SPAWN_ROOM_RED ) )
+ {
+ // unreachable - pick a new victim
+ return false;
+ }
+
+ if ( victim->GetGroundEntity() != NULL )
+ {
+ Vector victimAreaPos;
+ victimArea->GetClosestPointOnArea( victim->GetAbsOrigin(), &victimAreaPos );
+ if ( ( victim->GetAbsOrigin() - victimAreaPos ).AsVector2D().IsLengthGreaterThan( 50.0f ) )
+ {
+ // off the mesh and unreachable - pick a new victim
+ return false;
+ }
+ }
+
+ if ( victim->m_Shared.IsInvulnerable() )
+ {
+ // invulnerable - pick a new victim
+ return false;
+ }
+
+ Vector toHome = m_homePos - victim->GetAbsOrigin();
+ if ( !victim->m_Shared.InCond( TF_COND_HALLOWEEN_KART ) )
+ {
+ if ( toHome.IsLengthGreaterThan( tf_halloween_bot_quit_range.GetFloat() ) )
+ {
+ // too far from home - pick a new victim
+ return false;
+ }
+ }
+
+ return true;
+}
+
+
+//----------------------------------------------------------------------------------
+void CHeadlessHatmanAttack::ValidateChaseVictim( CHeadlessHatman *me )
+{
+ CTFPlayer *victim = ToTFPlayer( TFGameRules()->GetIT() );
+
+ if ( victim && m_lastIT != victim )
+ {
+ // something has changed our victim
+ m_chaseVictimTimer.Start( tf_halloween_bot_chase_duration.GetFloat() );
+ m_lastIT = victim;
+ }
+
+ if ( !IsPotentiallyChaseable( me, victim ) )
+ {
+ // we can no longer reach this victim
+ TFGameRules()->SetIT( NULL );
+ }
+}
+
+
+//----------------------------------------------------------------------------------
+void CHeadlessHatmanAttack::SelectVictim( CHeadlessHatman *me )
+{
+ ValidateChaseVictim( me );
+
+ m_closestVisible = ToTFPlayer( TFGameRules()->GetIT() );
+
+ if ( TFGameRules()->GetIT() == NULL )
+ {
+ // pick a new victim to chase
+ CTFPlayer *newVictim = NULL;
+ CUtlVector< CTFPlayer * > playerVector;
+
+ // collect everyone
+ CollectPlayers( &playerVector, TF_TEAM_RED, COLLECT_ONLY_LIVING_PLAYERS );
+ CollectPlayers( &playerVector, TF_TEAM_BLUE, COLLECT_ONLY_LIVING_PLAYERS, APPEND_PLAYERS );
+
+ float victimRangeSq = FLT_MAX;
+ float visibleVictimRangeSq = FLT_MAX;
+
+ for( int i=0; i<playerVector.Count(); ++i )
+ {
+ if ( !IsPotentiallyChaseable( me, playerVector[i] ) )
+ {
+ continue;
+ }
+
+ float rangeSq = me->GetRangeSquaredTo( playerVector[i] );
+ if ( rangeSq < visibleVictimRangeSq )
+ {
+ if ( me->IsLineOfSightClear( playerVector[i] ) )
+ {
+ // track closest visible victim so we can look at players out of attack range
+ m_closestVisible = playerVector[i];
+ visibleVictimRangeSq = rangeSq;
+ }
+ }
+
+ // check distance to home - don't pick victims too far away from home
+ if ( !playerVector[i]->m_Shared.InCond( TF_COND_HALLOWEEN_KART ) )
+ {
+ Vector toHome = m_homePos - playerVector[i]->GetAbsOrigin();
+ if ( toHome.IsLengthGreaterThan( tf_halloween_bot_chase_range.GetFloat() ) )
+ {
+ continue;
+ }
+ }
+
+ if ( rangeSq < victimRangeSq )
+ {
+ newVictim = playerVector[i];
+ victimRangeSq = rangeSq;
+ }
+ }
+
+ if ( newVictim )
+ {
+ // we have a new victim
+ m_chaseVictimTimer.Start( tf_halloween_bot_chase_duration.GetFloat() );
+
+ TFGameRules()->SetIT( newVictim );
+ }
+ }
+
+ // attack the "IT" player unless we're focusing on something else
+ if ( m_attackTarget == NULL || m_attackTargetFocusTimer.IsElapsed() || !m_attackTarget->IsAlive() )
+ {
+ m_attackTarget = ToTFPlayer( TFGameRules()->GetIT() );
+ }
+}
+
+//----------------------------------------------------------------------------------
+void CHeadlessHatmanAttack::AttackTarget( CHeadlessHatman *me, CBaseCombatCharacter *pTarget, float flAttackRange )
+{
+ // out of range? don't do anything
+ if ( !me->IsRangeLessThan( pTarget, flAttackRange ) )
+ return;
+
+ Vector forward;
+ me->GetVectors( &forward, NULL, NULL );
+
+ Vector toVictim = pTarget->WorldSpaceCenter() - me->WorldSpaceCenter();
+ toVictim.NormalizeInPlace();
+
+ CTFPlayer *pPlayer = pTarget->IsPlayer() ? ToTFPlayer( pTarget ) : NULL;
+ // push kart player or ghost
+ if ( pPlayer && ( pPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_KART ) || pPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_GHOST_MODE ) ) )
+ {
+ // Apply Huge Force
+ Vector vecDir = toVictim;
+ vecDir.NormalizeInPlace();
+ vecDir.z += 0.7f;
+
+ vecDir *= 1300.0f;
+
+ if ( pPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_KART ) )
+ {
+ pPlayer->AddHalloweenKartPushEvent( NULL, NULL, NULL, vecDir, 100.0f, TF_DMG_CUSTOM_DECAPITATION_BOSS );
+ }
+ else
+ {
+ pPlayer->ApplyAbsVelocityImpulse( vecDir );
+ }
+ }
+ else
+ {
+ // looser tolerance as victim gets closer
+ const float closeRange = 0.5f * flAttackRange;
+ float range = me->GetRangeTo( pTarget );
+ float closeness = ( range < closeRange ) ? 0.0f : ( range - closeRange ) / ( flAttackRange - closeRange );
+ float hitAngle = 0.0f + closeness * 0.27f;
+
+ // is target in front?
+ if ( DotProduct( forward, toVictim ) > hitAngle )
+ {
+ if ( me->IsLineOfSightClear( pTarget ) )
+ {
+ // CHOP!
+ CTakeDamageInfo info( me, me, m_attackTarget->GetMaxHealth() * 0.8f, DMG_CLUB, TF_DMG_CUSTOM_DECAPITATION_BOSS );
+ CalculateMeleeDamageForce( &info, toVictim, me->WorldSpaceCenter(), 5.0f );
+ m_attackTarget->TakeDamage( info );
+ me->EmitSound( "Halloween.HeadlessBossAxeHitFlesh" );
+ }
+ }
+ }
+}
+
+
+//----------------------------------------------------------------------------------
+void CHeadlessHatmanAttack::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 ( TFGameRules()->IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_DOOMSDAY ) )
+ {
+ CUtlVector< CTFPlayer* > playerVector;
+ CollectPlayers( &playerVector, TEAM_ANY );
+ for( CTFPlayer* pPlayer : playerVector )
+ {
+ AttackTarget( me, pPlayer, tf_halloween_hhh_attack_kart_radius.GetFloat() );
+ }
+
+ DispatchParticleEffect( "hammer_impact_button", me->GetAbsOrigin(), me->GetAbsAngles() );
+
+ me->EmitSound( "Halloween.HammerImpact" );
+
+ // after HHH punt off a kart target, pick a new target right away
+ TFGameRules()->SetIT( NULL );
+ }
+ else if ( m_attackTarget != NULL )
+ {
+ AttackTarget( me, m_attackTarget, tf_halloween_bot_attack_range.GetFloat() );
+ }
+
+ // always playe the axe-hit-world impact sound, since it carries through the world better
+ me->EmitSound( "Halloween.HeadlessBossAxeHitWorld" );
+ if ( !TFGameRules()->IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_DOOMSDAY ) )
+ {
+ 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 );
+ }
+ }
+ }
+}
+
+
+//----------------------------------------------------------------------------------
+ActionResult< CHeadlessHatman > CHeadlessHatmanAttack::Update( CHeadlessHatman *me, float interval )
+{
+ if ( !me->IsAlive() )
+ {
+ return Done();
+ }
+
+ SelectVictim( me );
+ RecomputeHomePosition();
+
+ if ( !IsSwingingAxe() && m_laughTimer.IsElapsed() )
+ {
+ me->EmitSound( "Halloween.HeadlessBossLaugh" );
+ m_laughTimer.Start( RandomFloat( 3.0f, 5.0f ) );
+
+ if ( TFGameRules()->GetIT() == NULL )
+ {
+ // if we have no victim, show our frustration
+ int r = RandomInt( 0, 100 );
+ if ( r < 25 )
+ {
+ me->AddGesture( ACT_MP_GESTURE_VC_FISTPUMP_MELEE );
+ }
+ else if ( r < 50 )
+ {
+ me->AddGesture( ACT_MP_GESTURE_VC_FINGERPOINT_MELEE );
+ }
+ }
+ }
+
+ if ( TFGameRules()->GetIT() == NULL )
+ {
+ // go home
+ const float atHomeRange = 50.0f;
+ if ( me->IsRangeGreaterThan( m_homePos, atHomeRange ) )
+ {
+ if ( m_path.GetAge() > 3.0f )
+ {
+ CHeadlessHatmanPathCost cost( me );
+ m_path.Compute( me, m_homePos, cost );
+ }
+
+ m_path.Update( me );
+ }
+ else // at home
+ {
+ if ( m_closestVisible != NULL )
+ {
+ // look at visible victim out of range
+ me->GetLocomotionInterface()->FaceTowards( m_closestVisible->WorldSpaceCenter() );
+ }
+ }
+ }
+ else
+ {
+ // chase after our chase victim
+ const float standAndSwingRange = 100.0f;
+ CTFPlayer *chaseVictim = ToTFPlayer( TFGameRules()->GetIT() );
+
+ if ( chaseVictim && m_warnITTimer.IsElapsed() && !TFGameRules()->IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_DOOMSDAY ) )
+ {
+ m_warnITTimer.Start( 7.0f );
+ ClientPrint( chaseVictim, HUD_PRINTCENTER, "#TF_HALLOWEEN_BOSS_WARN_VICTIM", chaseVictim->GetPlayerName() );
+ }
+
+ if ( me->IsRangeGreaterThan( chaseVictim, standAndSwingRange ) || !me->IsLineOfSightClear( chaseVictim ) )
+ {
+ if ( m_path.GetAge() > 1.0f )
+ {
+ CHeadlessHatmanPathCost cost( me );
+ m_path.Compute( me, chaseVictim, cost );
+ }
+
+ m_path.Update( me );
+ }
+ }
+
+ // 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_scareTimer.IsElapsed() && m_attackTarget->IsPlayer() )
+ {
+ m_scareTimer.Reset();
+ return SuspendFor( new CHeadlessHatmanTerrify, "Boo!" );
+ }
+
+ if ( m_attackTimer.IsElapsed() )
+ {
+ if ( !IsSwingingAxe() )
+ {
+ if ( TFGameRules()->IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_DOOMSDAY ) )
+ {
+ me->AddGesture( ACT_MP_ATTACK_STAND_ITEM2 );
+ }
+ else
+ {
+ 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 ( !TFGameRules()->IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_DOOMSDAY ) )
+ {
+ 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 > CHeadlessHatmanAttack::OnStuck( CHeadlessHatman *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( RESULT_TRY );
+}
+
+
+//---------------------------------------------------------------------------------------------
+EventDesiredResult< CHeadlessHatman > CHeadlessHatmanAttack::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 );
+}
+
+
+//---------------------------------------------------------------------------------------------
+void CHeadlessHatmanAttack::RecomputeHomePosition( void )
+{
+ if ( !m_homePosRecalcTimer.IsElapsed() )
+ {
+ return;
+ }
+
+ m_homePosRecalcTimer.Reset();
+
+ CTeamControlPoint *contestedPoint = NULL;
+ CTeamControlPointMaster *pMaster = g_hControlPointMasters.Count() ? g_hControlPointMasters[0] : NULL;
+ if ( pMaster )
+ {
+ for( int i=0; i<pMaster->GetNumPoints(); ++i )
+ {
+ contestedPoint = pMaster->GetControlPoint( i );
+ if ( contestedPoint && pMaster->IsInRound( contestedPoint ) )
+ {
+ if ( ObjectiveResource()->GetOwningTeam( contestedPoint->GetPointIndex() ) == TF_TEAM_BLUE )
+ continue;
+
+ // blue are the invaders
+ if ( !TeamplayGameRules()->TeamMayCapturePoint( TF_TEAM_BLUE, contestedPoint->GetPointIndex() ) )
+ continue;
+
+ break;
+ }
+ }
+ }
+
+ if ( contestedPoint )
+ {
+ m_homePos = contestedPoint->GetAbsOrigin();
+ }
+}
+
+
diff --git a/game/server/tf/halloween/halloween_behavior/headless_hatman_attack.h b/game/server/tf/halloween/halloween_behavior/headless_hatman_attack.h
new file mode 100644
index 0000000..d54ae80
--- /dev/null
+++ b/game/server/tf/halloween/halloween_behavior/headless_hatman_attack.h
@@ -0,0 +1,54 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// headless_hatman_attack.h
+// Halloween Boss chase and attack behavior
+// Michael Booth, October 2010
+
+#ifndef HEADLESS_HATMAN_ATTACK_H
+#define HEADLESS_HATMAN_ATTACK_H
+
+//---------------------------------------------------------------------------------------------
+//---------------------------------------------------------------------------------------------
+class CHeadlessHatmanAttack : public Action< CHeadlessHatman >
+{
+public:
+ virtual ActionResult< CHeadlessHatman > OnStart( CHeadlessHatman *me, Action< CHeadlessHatman > *priorAction );
+ virtual ActionResult< CHeadlessHatman > Update( CHeadlessHatman *me, float interval );
+
+ virtual EventDesiredResult< CHeadlessHatman > OnStuck( CHeadlessHatman *me );
+ virtual EventDesiredResult< CHeadlessHatman > OnContact( CHeadlessHatman *me, CBaseEntity *other, CGameTrace *result = NULL );
+
+ virtual const char *GetName( void ) const { return "Attack"; } // return name of this action
+
+private:
+ PathFollower m_path;
+
+ Vector m_homePos;
+ CountdownTimer m_homePosRecalcTimer;
+ void RecomputeHomePosition( void );
+
+ CountdownTimer m_axeSwingTimer;
+ CountdownTimer m_attackTimer;
+ CountdownTimer m_laughTimer;
+ CountdownTimer m_scareTimer;
+ CountdownTimer m_warnITTimer;
+ CountdownTimer m_footfallTimer;
+
+ void AttackTarget( CHeadlessHatman *me, CBaseCombatCharacter *pTarget, float flAttackRange );
+ void UpdateAxeSwing( CHeadlessHatman *me );
+ bool IsSwingingAxe( void ) const;
+
+ CHandle< CTFPlayer > m_closestVisible;
+
+ CHandle< CTFPlayer > m_lastIT;
+ CountdownTimer m_chaseVictimTimer;
+ void ValidateChaseVictim( CHeadlessHatman *me );
+ bool IsPotentiallyChaseable( CHeadlessHatman *me, CTFPlayer *victim );
+
+ CHandle< CBaseCombatCharacter > m_attackTarget; // the victim I'm momentarily attacking
+ CountdownTimer m_attackTargetFocusTimer;
+ void SelectVictim( CHeadlessHatman *me );
+};
+
+
+
+#endif // HEADLESS_HATMAN_ATTACK_H
diff --git a/game/server/tf/halloween/halloween_behavior/headless_hatman_dying.cpp b/game/server/tf/halloween/halloween_behavior/headless_hatman_dying.cpp
new file mode 100644
index 0000000..5a4a0ea
--- /dev/null
+++ b/game/server/tf/halloween/halloween_behavior/headless_hatman_dying.cpp
@@ -0,0 +1,44 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// headless_hatman_dying.cpp
+// The HHH in the process of dying
+// Michael Booth, October 2010
+
+#include "cbase.h"
+
+#include "particle_parse.h"
+
+#include "../headless_hatman.h"
+#include "headless_hatman_dying.h"
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CHeadlessHatman > CHeadlessHatmanDying::OnStart( CHeadlessHatman *me, Action< CHeadlessHatman > *priorAction )
+{
+ me->GetBodyInterface()->StartActivity( ACT_DIESIMPLE );
+ me->EmitSound( "Halloween.HeadlessBossDying" );
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CHeadlessHatman > CHeadlessHatmanDying::Update( CHeadlessHatman *me, float interval )
+{
+ if ( me->IsActivityFinished() )
+ {
+ me->Break();
+ DispatchParticleEffect( "halloween_boss_death", me->GetAbsOrigin(), me->GetAbsAngles() );
+
+ UTIL_Remove( me );
+
+ IGameEvent *event = gameeventmanager->CreateEvent( "pumpkin_lord_killed" );
+ if ( event )
+ {
+ gameeventmanager->FireEvent( event );
+ }
+
+ return Done();
+ }
+
+ return Continue();
+}
+
diff --git a/game/server/tf/halloween/halloween_behavior/headless_hatman_dying.h b/game/server/tf/halloween/halloween_behavior/headless_hatman_dying.h
new file mode 100644
index 0000000..ea4b32e
--- /dev/null
+++ b/game/server/tf/halloween/halloween_behavior/headless_hatman_dying.h
@@ -0,0 +1,20 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// headless_hatman_dying.h
+// The HHH in the process of dying
+// Michael Booth, October 2010
+
+#ifndef HEADLESS_HATMAN_DYING_H
+#define HEADLESS_HATMAN_DYING_H
+
+
+//---------------------------------------------------------------------------------------------
+class CHeadlessHatmanDying : public Action< CHeadlessHatman >
+{
+public:
+ virtual ActionResult< CHeadlessHatman > OnStart( CHeadlessHatman *me, Action< CHeadlessHatman > *priorAction );
+ virtual ActionResult< CHeadlessHatman > Update( CHeadlessHatman *me, float interval );
+ virtual const char *GetName( void ) const { return "Dying"; } // return name of this action
+};
+
+
+#endif // HEADLESS_HATMAN_DYING_H
diff --git a/game/server/tf/halloween/halloween_behavior/headless_hatman_emerge.cpp b/game/server/tf/halloween/halloween_behavior/headless_hatman_emerge.cpp
new file mode 100644
index 0000000..a45b679
--- /dev/null
+++ b/game/server/tf/halloween/halloween_behavior/headless_hatman_emerge.cpp
@@ -0,0 +1,133 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// headless_hatman_emerge.cpp
+// The Halloween Boss emerging from the ground
+// Michael Booth, October 2010
+
+#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 "../headless_hatman.h"
+#include "headless_hatman_emerge.h"
+#include "headless_hatman_attack.h"
+#include "crybaby_boss_escape.h"
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CHeadlessHatman > CHeadlessHatmanEmerge::OnStart( CHeadlessHatman *me, Action< CHeadlessHatman > *priorAction )
+{
+ me->GetBodyInterface()->StartActivity( ACT_TRANSITION );
+
+ DispatchParticleEffect( "halloween_boss_summon", me->GetAbsOrigin(), me->GetAbsAngles() );
+
+ m_riseTimer.Start( 3.0f );
+ m_emergePos = me->GetAbsOrigin() + Vector( 0, 0, 10.0f );
+
+ m_height = 200.0f;
+ me->SetAbsOrigin( m_emergePos + Vector( 0, 0, -m_height ) );
+ me->EmitSound( "Halloween.HeadlessBossSpawnRumble" );
+
+ // face towards a nearby player
+ CUtlVector< CTFPlayer * > playerVector;
+ CollectPlayers( &playerVector, TF_TEAM_RED, COLLECT_ONLY_LIVING_PLAYERS );
+ CollectPlayers( &playerVector, TF_TEAM_BLUE, COLLECT_ONLY_LIVING_PLAYERS, APPEND_PLAYERS );
+
+ float closeRangeSq = FLT_MAX;
+ CTFPlayer *close = NULL;
+
+ for( int i=0; i<playerVector.Count(); ++i )
+ {
+ CTFPlayer *player = playerVector[i];
+
+ float rangeSq = me->GetRangeSquaredTo( player );
+ if ( rangeSq < closeRangeSq )
+ {
+ closeRangeSq = rangeSq;
+ close = player;
+ }
+ }
+
+ QAngle facingAngle;
+
+ if ( close )
+ {
+ Vector toPlayer = close->GetAbsOrigin() - me->GetAbsOrigin();
+ toPlayer.z = 0.0f;
+ toPlayer.NormalizeInPlace();
+
+ VectorAngles( toPlayer, Vector(0,0,1), facingAngle );
+ }
+ else
+ {
+ facingAngle.x = 0.0f;
+ facingAngle.y = RandomFloat( 0.0f, 360.0f );
+ facingAngle.z = 0.0f;
+ }
+
+ me->SetAbsAngles( facingAngle );
+
+
+ IGameEvent *event = gameeventmanager->CreateEvent( "pumpkin_lord_summoned" );
+ if ( event )
+ {
+ gameeventmanager->FireEvent( event );
+ }
+
+ return Continue();
+}
+
+
+//----------------------------------------------------------------------------------
+ActionResult< CHeadlessHatman > CHeadlessHatmanEmerge::Update( CHeadlessHatman *me, float interval )
+{
+ if ( !m_riseTimer.IsElapsed() )
+ {
+ me->SetAbsOrigin( m_emergePos + Vector( 0, 0, -m_height * m_riseTimer.GetRemainingTime() / m_riseTimer.GetCountdownDuration() ) );
+
+ if ( m_rumbleTimer.IsElapsed() )
+ {
+ m_rumbleTimer.Start( 0.25f );
+
+ // shake nearby players' screens.
+ UTIL_ScreenShake( me->GetAbsOrigin(), 15.0f, 5.0f, 1.0f, 1000.0f, SHAKE_START );
+ }
+ }
+
+ if ( me->IsActivityFinished() )
+ {
+ return ChangeTo( new CHeadlessHatmanAttack, "Here I come!" );
+ }
+
+ // push players away to avoid penetration issues
+ CUtlVector< CTFPlayer * > playerVector;
+ CollectPlayers( &playerVector, TF_TEAM_RED, COLLECT_ONLY_LIVING_PLAYERS );
+ CollectPlayers( &playerVector, TF_TEAM_BLUE, COLLECT_ONLY_LIVING_PLAYERS, APPEND_PLAYERS );
+
+ const float pushRange = 250.0f;
+ const float pushForce = 200.0f;
+
+ for( int i=0; i<playerVector.Count(); ++i )
+ {
+ CTFPlayer *player = playerVector[i];
+
+ Vector toPlayer = player->EyePosition() - m_emergePos;
+ float range = toPlayer.NormalizeInPlace();
+
+ if ( range < pushRange )
+ {
+ // make sure we push players up and away
+ toPlayer.z = 0.0f;
+ toPlayer.NormalizeInPlace();
+ toPlayer.z = 1.0f;
+
+ Vector push = pushForce * toPlayer;
+
+ player->ApplyAbsVelocityImpulse( push );
+ }
+ }
+
+ return Continue();
+}
diff --git a/game/server/tf/halloween/halloween_behavior/headless_hatman_emerge.h b/game/server/tf/halloween/halloween_behavior/headless_hatman_emerge.h
new file mode 100644
index 0000000..e4bda2a
--- /dev/null
+++ b/game/server/tf/halloween/halloween_behavior/headless_hatman_emerge.h
@@ -0,0 +1,26 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// headless_hatman_emerge.h
+// The Halloween Boss emerging from the ground
+// Michael Booth, October 2010
+
+#ifndef HEADLESS_HATMAN_EMERGE_H
+#define HEADLESS_HATMAN_EMERGE_H
+
+//---------------------------------------------------------------------------------------------
+//---------------------------------------------------------------------------------------------
+class CHeadlessHatmanEmerge : public Action< CHeadlessHatman >
+{
+public:
+ virtual ActionResult< CHeadlessHatman > OnStart( CHeadlessHatman *me, Action< CHeadlessHatman > *priorAction );
+ virtual ActionResult< CHeadlessHatman > Update( CHeadlessHatman *me, float interval );
+ virtual const char *GetName( void ) const { return "Emerge"; } // return name of this action
+
+private:
+ CountdownTimer m_riseTimer;
+ CountdownTimer m_rumbleTimer;
+ Vector m_emergePos;
+ float m_height;
+};
+
+
+#endif // HEADLESS_HATMAN_EMERGE_H
diff --git a/game/server/tf/halloween/halloween_behavior/headless_hatman_terrify.cpp b/game/server/tf/halloween/halloween_behavior/headless_hatman_terrify.cpp
new file mode 100644
index 0000000..eb855ad
--- /dev/null
+++ b/game/server/tf/halloween/halloween_behavior/headless_hatman_terrify.cpp
@@ -0,0 +1,95 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// headless_hatman_terrify.cpp
+// The Halloween Boss leans over and yells "Boo!", terrifying nearby victims
+// Michael Booth, October 2010
+
+#include "cbase.h"
+
+#include "tf_player.h"
+#include "tf_team.h"
+
+#include "../headless_hatman.h"
+#include "headless_hatman_terrify.h"
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CHeadlessHatman > CHeadlessHatmanTerrify::OnStart( CHeadlessHatman *me, Action< CHeadlessHatman > *priorAction )
+{
+ me->AddGesture( ACT_MP_GESTURE_VC_HANDMOUTH_ITEM1 );
+
+ m_booTimer.Start( 0.25f );
+ m_scareTimer.Start( 0.75f );
+ m_timer.Start( 1.25f );
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CHeadlessHatman > CHeadlessHatmanTerrify::Update( CHeadlessHatman *me, float interval )
+{
+ if ( m_timer.IsElapsed() )
+ {
+ return Done();
+ }
+
+ if ( m_booTimer.HasStarted() && m_booTimer.IsElapsed() )
+ {
+ m_booTimer.Invalidate();
+ me->EmitSound( "Halloween.HeadlessBossBoo" );
+ }
+
+ if ( m_scareTimer.IsElapsed() )
+ {
+ CUtlVector< CTFPlayer * > playerVector;
+ CollectPlayers( &playerVector, TF_TEAM_RED, COLLECT_ONLY_LIVING_PLAYERS );
+ CollectPlayers( &playerVector, TF_TEAM_BLUE, COLLECT_ONLY_LIVING_PLAYERS, APPEND_PLAYERS );
+
+ for( int i=0; i<playerVector.Count(); ++i )
+ {
+ CTFPlayer *victim = playerVector[i];
+
+ if ( me->IsRangeLessThan( victim, tf_halloween_bot_terrify_radius.GetFloat() ) )
+ {
+ if ( !IsWearingPumpkinHeadOrSaxtonMask( victim ) && me->IsLineOfSightClear( victim ) )
+ {
+ // scare them!
+ const float scareTime = 2.0f;
+ const float speedReduction = 0.0f;
+
+ // "stun by trigger" results in the Halloween "yikes" effects
+ int stunFlags = TF_STUN_LOSER_STATE | TF_STUN_BY_TRIGGER;
+ victim->m_Shared.StunPlayer( scareTime, speedReduction, stunFlags, NULL );
+ }
+ }
+ }
+ }
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+bool CHeadlessHatmanTerrify::IsWearingPumpkinHeadOrSaxtonMask( CTFPlayer *player )
+{
+ const int pumpkinHeadHat = 278;
+ const int saxtonMask = 277;
+
+ for( int i=0; i<player->GetNumWearables(); ++i )
+ {
+ CEconWearable *wearable = player->GetWearable( i );
+ if ( wearable && wearable->GetAttributeContainer() )
+ {
+ CEconItemView *item = wearable->GetAttributeContainer()->GetItem();
+ if ( item && item->IsValid() )
+ {
+ if ( item->GetItemDefIndex() == pumpkinHeadHat || item->GetItemDefIndex() == saxtonMask )
+ {
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+}
diff --git a/game/server/tf/halloween/halloween_behavior/headless_hatman_terrify.h b/game/server/tf/halloween/halloween_behavior/headless_hatman_terrify.h
new file mode 100644
index 0000000..a105fb5
--- /dev/null
+++ b/game/server/tf/halloween/halloween_behavior/headless_hatman_terrify.h
@@ -0,0 +1,26 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// headless_hatman_terrify.h
+// The Halloween Boss leans over and yells "Boo!", terrifying nearby victims
+// Michael Booth, October 2010
+
+#ifndef HEADLESS_HATMAN_TERRIFY_H
+#define HEADLESS_HATMAN_TERRIFY_H
+
+//---------------------------------------------------------------------------------------------
+class CHeadlessHatmanTerrify : public Action< CHeadlessHatman >
+{
+public:
+ virtual ActionResult< CHeadlessHatman > OnStart( CHeadlessHatman *me, Action< CHeadlessHatman > *priorAction );
+ virtual ActionResult< CHeadlessHatman > Update( CHeadlessHatman *me, float interval );
+ virtual const char *GetName( void ) const { return "Terrify"; } // return name of this action
+
+private:
+ CountdownTimer m_booTimer;
+ CountdownTimer m_scareTimer;
+ CountdownTimer m_timer;
+
+ bool IsWearingPumpkinHeadOrSaxtonMask( CTFPlayer *player );
+};
+
+
+#endif // HEADLESS_HATMAN_TERRIFY_H