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