summaryrefslogtreecommitdiff
path: root/game/server/tf/halloween/merasmus/merasmus_behavior
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /game/server/tf/halloween/merasmus/merasmus_behavior
downloadarchived-source-engine-2018-hl2-src-master.tar.xz
archived-source-engine-2018-hl2-src-master.zip
Diffstat (limited to 'game/server/tf/halloween/merasmus/merasmus_behavior')
-rw-r--r--game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_aoe_attack.cpp248
-rw-r--r--game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_aoe_attack.h51
-rw-r--r--game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_attack.cpp386
-rw-r--r--game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_attack.h63
-rw-r--r--game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_disguise.cpp226
-rw-r--r--game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_disguise.h33
-rw-r--r--game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_dying.cpp60
-rw-r--r--game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_dying.h20
-rw-r--r--game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_reveal.cpp38
-rw-r--r--game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_reveal.h20
-rw-r--r--game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_staff_attack.cpp117
-rw-r--r--game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_staff_attack.h28
-rw-r--r--game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_stunned.cpp91
-rw-r--r--game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_stunned.h29
-rw-r--r--game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_teleport.cpp189
-rw-r--r--game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_teleport.h49
-rw-r--r--game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_throwing_grenade.cpp159
-rw-r--r--game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_throwing_grenade.h27
-rw-r--r--game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_zap.cpp73
-rw-r--r--game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_zap.h32
20 files changed, 1939 insertions, 0 deletions
diff --git a/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_aoe_attack.cpp b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_aoe_attack.cpp
new file mode 100644
index 0000000..6698be0
--- /dev/null
+++ b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_aoe_attack.cpp
@@ -0,0 +1,248 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//
+//
+//=============================================================================
+#include "cbase.h"
+
+#include "tf_gamerules.h"
+#include "particle_parse.h"
+#include "nav_mesh/tf_nav_area.h"
+#include "nav_mesh/tf_nav_mesh.h"
+#include "tf/halloween/eyeball_boss/teleport_vortex.h"
+#include "tf_weaponbase_grenadeproj.h"
+#include "sceneentity.h"
+
+#include "../merasmus.h"
+#include "merasmus_aoe_attack.h"
+
+//---------------------------------------------------------------------------------------------
+#define MAX_BOMBS_PER_TICK 4
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CMerasmus > CMerasmusAOEAttack::OnStart( CMerasmus *me, Action< CMerasmus > *priorAction )
+{
+ m_aoeStartTimer.Start( 4.f );
+ m_state = AOE_BEGIN;
+
+ me->StartFlying();
+
+ CTFNavArea *pointArea = TheTFNavMesh()->GetControlPointCenterArea( 0 );
+
+ if ( !pointArea )
+ {
+ return Done( "No control point!" );
+ }
+
+ const float surroundRange = 400.f;
+ CollectSurroundingAreas( &m_wanderAreaVector, pointArea, surroundRange, StepHeight, StepHeight );
+
+ if ( m_wanderAreaVector.Count() == 0 )
+ {
+ return Done( "No nav areas near control point!" );
+ }
+
+ me->GetBodyInterface()->StartActivity( ACT_RANGE_ATTACK1 );
+
+ // This is only to play sound since animation doesn't work with the vcd
+ CFmtStr vcdName( "scenes/bot/merasmus/low/bomb_attack_00%d.vcd", RandomInt( 1, 9 ) );
+ InstancedScriptedScene( me, vcdName.Get(), NULL, 0.0f, false, NULL, true );
+
+ m_wanderArea = NULL;
+
+ return Continue();
+}
+
+
+#define BOMB_SCALE 1.0f // 0.85f
+#define BOMB_VERT_VEL 750.0f
+#define BOMB_START_OFFSET Vector( 0.0f, 0.0f, 150.0f )
+
+//---------------------------------------------------------------------------------------------
+void CMerasmusAOEAttack::QueueSingleGrenadeForLaunch( const Vector &vecVelocity )
+{
+ m_vecGrenadesToCreate.AddToTail( MerasmusGrenadeCreateSpec_t( vecVelocity ) );
+}
+
+//---------------------------------------------------------------------------------------------
+void CMerasmusAOEAttack::ClearPendingGrenades()
+{
+ m_vecGrenadesToCreate.RemoveAll();
+}
+
+//---------------------------------------------------------------------------------------------
+void CMerasmusAOEAttack::LaunchPendingGrenades( CMerasmus *me )
+{
+ int nNumGrenadesLeftToCreate = MIN( m_vecGrenadesToCreate.Count(), MAX_BOMBS_PER_TICK );
+ while ( nNumGrenadesLeftToCreate > 0 )
+ {
+ // Create the first one in the list
+ MerasmusGrenadeCreateSpec_t &info = m_vecGrenadesToCreate[0];
+ CMerasmus::CreateMerasmusGrenade( me->WorldSpaceCenter() + BOMB_START_OFFSET, info.m_vecVelocity, me, BOMB_SCALE );
+
+ // Remove the first one in the list
+ m_vecGrenadesToCreate.Remove( 0 );
+
+ --nNumGrenadesLeftToCreate;
+ }
+}
+
+//---------------------------------------------------------------------------------------------
+void CMerasmusAOEAttack::QueueBombRingsForLaunch( CMerasmus *me )
+{
+ ClearPendingGrenades();
+
+ const float bombRingMinHorizVel = 100.0f;
+ const float bombRingMaxHorizVel = 2000.0f;
+
+ QAngle myAngles = me->EyeAngles();
+
+ float deltaVel = bombRingMaxHorizVel - bombRingMinHorizVel;
+ const int ringCount = 2;
+
+ for( int r=0; r<ringCount; ++r )
+ {
+ float u = (float)(r+1)/(float)ringCount;
+
+ float horizVel = bombRingMinHorizVel + u * deltaVel;
+
+// float angleDelta = 10.0f + 20.0f * ( 1.0f - u );
+ float angleDelta = 20.0f + 30.0f * ( 1.0f - u );
+
+ for( float angle=0.0f; angle<360.0f; angle += angleDelta )
+ {
+ Vector forward;
+ AngleVectors( myAngles, &forward );
+
+ Vector vecVelocity( horizVel * forward.x, horizVel * forward.y, BOMB_VERT_VEL );
+
+ QueueSingleGrenadeForLaunch( vecVelocity );
+
+ myAngles.y += angleDelta;
+ }
+ }
+}
+
+
+//---------------------------------------------------------------------------------------------
+void CMerasmusAOEAttack::QueueBombSpokesForLaunch( CMerasmus *me )
+{
+ ClearPendingGrenades();
+
+ const float bombSpokeAngle = 45.0f;
+ const int bombSpokeCount = 4; // 10;
+ const float bombSpokeMinHorizVel = 100.0f;
+ const float bombSpokeMaxHorizVel = 2000.0f;
+
+ float deltaVel = bombSpokeMaxHorizVel - bombSpokeMinHorizVel;
+ float angleDelta = bombSpokeAngle;
+
+ QAngle myAngles = me->EyeAngles();
+
+ for( float angle=0.0f; angle<360.0f; angle += angleDelta )
+ {
+ Vector forward;
+ AngleVectors( myAngles, &forward );
+
+ int spokeCount = bombSpokeCount;
+
+ for( int i=0; i<spokeCount; ++i )
+ {
+ float u = (float)(i+1)/(float)spokeCount;
+
+ float horizVel = bombSpokeMinHorizVel + u * deltaVel;
+
+ Vector vecVelocity( horizVel * forward.x, horizVel * forward.y, BOMB_VERT_VEL );
+
+ QueueSingleGrenadeForLaunch( vecVelocity );
+ }
+
+ myAngles.y += angleDelta;
+ }
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CMerasmus > CMerasmusAOEAttack::Update( CMerasmus *me, float interval )
+{
+ if ( me->IsActivityFinished() )
+ {
+ me->StopAOEAttack();
+
+ return Done();
+ }
+
+ switch ( m_state )
+ {
+ case AOE_BEGIN:
+ {
+ if ( m_aoeStartTimer.IsElapsed() )
+ {
+ m_launchTimer.Start( 0.5f );
+
+ m_state = AOE_FIRING;
+ }
+ }
+ break;
+ case AOE_FIRING:
+ {
+ // Start the AOE particles
+ me->StartAOEAttack();
+
+ CMerasmusFlyingLocomotion *fly = (CMerasmusFlyingLocomotion *)me->GetLocomotionInterface();
+
+ // float up and down a bit
+ fly->SetDesiredAltitude( 175.0f + 25.0f * FastCos( gpGlobals->curtime ) );
+
+ if ( m_launchTimer.IsElapsed() )
+ {
+ if ( RandomInt( 1, 100 ) < 50 )
+ {
+ QueueBombSpokesForLaunch( me );
+ }
+ else
+ {
+ QueueBombRingsForLaunch( me );
+ }
+
+ m_launchTimer.Start( 2.0f );
+ }
+
+ // Go through and launch any pending grenades
+ LaunchPendingGrenades( me );
+
+ // wander among nav areas near the cap point
+ if ( m_wanderTimer.IsElapsed() || m_wanderArea == NULL )
+ {
+ m_wanderTimer.Start( RandomFloat( 1.0f, 3.0f ) );
+
+ m_wanderArea = (CTFNavArea *)m_wanderAreaVector[ RandomInt( 0, m_wanderAreaVector.Count()-1 ) ];
+ }
+
+ if ( m_wanderArea )
+ {
+ Vector flySpot = m_wanderArea->GetCenter();
+ flySpot.z = me->GetAbsOrigin().z;
+
+ me->GetLocomotionInterface()->Approach( flySpot );
+ me->GetLocomotionInterface()->FaceTowards( flySpot );
+ }
+ }
+ break;
+ }
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+void CMerasmusAOEAttack::OnEnd( CMerasmus *me, Action< CMerasmus > *nextAction )
+{
+ me->StopFlying();
+
+ // The animation sometime doesn't turn off the bodygroup correctly. Slam it in code.
+ int staffBodyGroup = me->FindBodygroupByName( "staff" );
+ me->SetBodygroup( staffBodyGroup, 0 );
+
+ me->ResetBombHitCount();
+}
diff --git a/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_aoe_attack.h b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_aoe_attack.h
new file mode 100644
index 0000000..76c04cf
--- /dev/null
+++ b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_aoe_attack.h
@@ -0,0 +1,51 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//
+//
+//=============================================================================
+#ifndef MERASMUS_AOE_ATTACK_H
+#define MERASMUS_AOE_ATTACK_H
+
+class CMerasmusAOEAttack : public Action< CMerasmus >
+{
+public:
+ virtual ActionResult< CMerasmus > OnStart( CMerasmus *me, Action< CMerasmus > *priorAction );
+ virtual ActionResult< CMerasmus > Update( CMerasmus *me, float interval );
+ virtual void OnEnd( CMerasmus *me, Action< CMerasmus > *nextAction );
+
+ virtual const char *GetName( void ) const { return "AOE Attack!"; } // return name of this action
+private:
+ enum AOEState_t
+ {
+ AOE_BEGIN,
+ AOE_FIRING,
+ };
+ AOEState_t m_state;
+
+ CountdownTimer m_aoeStartTimer;
+ CountdownTimer m_launchTimer;
+ CountdownTimer m_flyTimer;
+ CUtlVector< CNavArea * > m_wanderAreaVector;
+ CountdownTimer m_wanderTimer;
+ CTFNavArea *m_wanderArea;
+
+ // To save network perf, we don't create all bombs in a single tick. Rather, we fill up a queue of bombs and distribute the creation over time.
+ // I originally had another property (start position) as part of MerasmusGrenadeCreateSpec_t, but it didn't make sense, since we want to use
+ // his current position when we actually create -- not the position he was at whenever we actually filled the queue with grenades. I'm leaving
+ // the struct here, rather than making m_vecGrenadesToCreate a CUtlVector< Vector >, in case we want to add anything else.
+ struct MerasmusGrenadeCreateSpec_t
+ {
+ MerasmusGrenadeCreateSpec_t( const Vector &v ) : m_vecVelocity( v ) {}
+ Vector m_vecVelocity;
+ };
+ CUtlVector< MerasmusGrenadeCreateSpec_t > m_vecGrenadesToCreate;
+
+ void QueueSingleGrenadeForLaunch( const Vector &vecVelocity ); // Don't call directly - call QueueBombRingsForLaunch() or QueueBombSpokesForLaunch()
+ void ClearPendingGrenades();
+ void LaunchPendingGrenades( CMerasmus *me );
+
+ void QueueBombRingsForLaunch( CMerasmus *me );
+ void QueueBombSpokesForLaunch( CMerasmus *me );
+};
+
+#endif // MERASMUS_TELEPORT_AOE_ATTACK_H
diff --git a/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_attack.cpp b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_attack.cpp
new file mode 100644
index 0000000..9a40377
--- /dev/null
+++ b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_attack.cpp
@@ -0,0 +1,386 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//
+//
+//=============================================================================
+#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 "../merasmus.h"
+#include "../merasmus_trick_or_treat_prop.h"
+#include "merasmus_attack.h"
+#include "merasmus_staff_attack.h"
+#include "merasmus_stunned.h"
+#include "merasmus_throwing_grenade.h"
+#include "merasmus_zap.h"
+
+//----------------------------------------------------------------------------------
+ActionResult< CMerasmus > CMerasmusAttack::OnStart( CMerasmus *me, Action< CMerasmus > *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_attackTimer.Invalidate();
+
+ m_attackTarget = NULL;
+ m_attackTargetFocusTimer.Start( 0.f );
+
+ RandomGrenadeTimer();
+ RandomZapTimer();
+
+ m_homePos = me->GetAbsOrigin();
+ m_homePosRecalcTimer.Start( 3.0f );
+
+ m_bombHeadTimer.Start( 10.f );
+
+ return Continue();
+}
+
+
+//----------------------------------------------------------------------------------
+bool CMerasmusAttack::IsPotentiallyChaseable( CMerasmus *me, CTFPlayer *victim )
+{
+ if ( !victim )
+ {
+ return false;
+ }
+
+ if ( !victim->IsAlive() )
+ {
+ // victim is dead - pick a new one
+ 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 ( toHome.IsLengthGreaterThan( tf_merasmus_chase_range.GetFloat() ) )
+ {
+ // too far from home - pick a new victim
+ return false;
+ }
+
+ return true;
+}
+
+
+//----------------------------------------------------------------------------------
+void CMerasmusAttack::SelectVictim( CMerasmus *me )
+{
+ if ( IsPotentiallyChaseable( me, m_attackTarget ) && !m_attackTargetFocusTimer.IsElapsed() )
+ {
+ return;
+ }
+
+ // 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;
+ for( int i=0; i<playerVector.Count(); ++i )
+ {
+ if ( !IsPotentiallyChaseable( me, playerVector[i] ) )
+ {
+ continue;
+ }
+
+ float rangeSq = me->GetRangeSquaredTo( playerVector[i] );
+ if ( rangeSq < victimRangeSq )
+ {
+ newVictim = playerVector[i];
+ victimRangeSq = rangeSq;
+ }
+ }
+
+ if ( newVictim )
+ {
+ // we have a new victim
+ m_attackTargetFocusTimer.Start( tf_merasmus_chase_duration.GetFloat() );
+ }
+
+ m_attackTarget = newVictim;
+}
+
+
+//----------------------------------------------------------------------------------
+ActionResult< CMerasmus > CMerasmusAttack::Update( CMerasmus *me, float interval )
+{
+ if ( !me->IsAlive() )
+ {
+ return Done();
+ }
+
+ if ( me->HasStunTimer() )
+ {
+ return SuspendFor( new CMerasmusStunned, "Stunned!" );
+ }
+
+ SelectVictim( me );
+ RecomputeHomePosition();
+
+ if ( m_attackTarget == NULL )
+ {
+ // go home
+ const float atHomeRange = 50.0f;
+ if ( me->IsRangeGreaterThan( m_homePos, atHomeRange ) )
+ {
+ if ( m_path.GetAge() > 3.0f )
+ {
+ CMerasmusPathCost cost( me );
+ m_path.Compute( me, m_homePos, cost );
+ }
+
+ m_path.Update( me );
+ }
+// else
+// {
+// // at home with nothing to do - taunt!
+// if ( !me->IsMoving() && m_tauntTimer.IsElapsed() )
+// {
+// m_tauntTimer.Start( RandomFloat( 3.0f, 5.0f ) );
+//
+// return SuspendFor( new CMerasmusTaunt, "Taunting because I have nothing to do." );
+// }
+// }
+ }
+ else
+ {
+ // chase after our chase victim
+ const float standAndSwingRange = 100.0f;
+ CTFPlayer *chaseVictim = m_attackTarget;
+
+ if ( me->IsRangeGreaterThan( chaseVictim, standAndSwingRange ) || !me->IsLineOfSightClear( chaseVictim ) )
+ {
+ if ( m_path.GetAge() > 1.0f )
+ {
+ CMerasmusPathCost cost( me );
+ m_path.Compute( me, chaseVictim, cost );
+ }
+
+ m_path.Update( me );
+ }
+ }
+
+ if ( m_bombHeadTimer.IsElapsed() )
+ {
+ // bomb heads last 15 seconds - make sure we don't add more while existing ones are out
+ m_bombHeadTimer.Start( 16.0f );
+ me->BombHeadMode();
+ }
+
+ // swing our axe at our attack target if they are in range
+ if ( m_attackTarget != NULL && m_attackTarget->IsAlive() )
+ {
+ if ( m_zapTimer.IsElapsed() )
+ {
+ RandomZapTimer();
+ return SuspendFor( new CMerasmusZap, "Zap!" );
+ }
+
+ if ( me->IsRangeLessThan( m_attackTarget, tf_merasmus_attack_range.GetFloat() ) )
+ {
+ if ( m_attackTimer.IsElapsed() )
+ {
+ m_attackTimer.Start( 1.f );
+ return SuspendFor( new CMerasmusStaffAttack( m_attackTarget ), "Whack!" );
+ }
+
+ me->GetLocomotionInterface()->FaceTowards( m_attackTarget->WorldSpaceCenter() );
+ }
+
+ if ( m_grenadeTimer.IsElapsed() )
+ {
+ RandomGrenadeTimer();
+ return SuspendFor( new CMerasmusThrowingGrenade( m_attackTarget ), "Fire in the hole!" );
+ }
+ }
+
+ if ( !me->GetBodyInterface()->IsActivity( ACT_MP_RUN_MELEE ) )
+ {
+ me->GetBodyInterface()->StartActivity( ACT_MP_RUN_MELEE );
+ }
+
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+EventDesiredResult< CMerasmus > CMerasmusAttack::OnStuck( CMerasmus *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< CMerasmus > CMerasmusAttack::OnContact( CMerasmus *me, CBaseEntity *other, CGameTrace *result )
+{
+ if ( other->IsPlayer() )
+ {
+ CTFPlayer *pTFPlayer = ToTFPlayer( other );
+ if ( pTFPlayer )
+ {
+ if ( pTFPlayer->IsAlive() )
+ {
+ // force attack the thing we bumped into
+ // this prevents us from being stuck on dispensers, for example
+ m_attackTarget = pTFPlayer;
+ m_attackTargetFocusTimer.Start( tf_merasmus_chase_duration.GetFloat() );
+ }
+ }
+ }
+
+ return TryContinue( RESULT_TRY );
+}
+
+
+//---------------------------------------------------------------------------------------------
+void CMerasmusAttack::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();
+ }
+}
+
+
+void CMerasmusAttack::RandomGrenadeTimer()
+{
+ m_grenadeTimer.Start( RandomFloat( 2.f, 3.f ) );
+}
+
+
+void CMerasmusAttack::RandomZapTimer()
+{
+ m_zapTimer.Start( RandomFloat( 3.f, 4.f ) );
+}
+
+
+//---------------------------------------------------------------------------------------------
+//---------------------------------------------------------------------------------------------
+ActionResult< CMerasmus > CMerasmusTaunt::OnStart( CMerasmus *me, Action< CMerasmus > *priorAction )
+{
+ m_timer.Start( 3.0f );
+
+ const char *taunts[] =
+ {
+ "gesture_melee_cheer",
+ "gesture_melee_go",
+ "taunt01", // wave
+ "taunt06", // thriller
+ "taunt_laugh",
+ NULL
+ };
+
+ // count the available taunts
+ int count = 0;
+ while( true )
+ {
+ if ( taunts[ count ] == NULL )
+ break;
+
+ ++count;
+ }
+
+ // pick one and play it
+ int which = RandomInt( 0, count-1 );
+ me->AddGestureSequence( me->LookupSequence( taunts[ which ] ) );
+
+ int staffBodyGroup = me->FindBodygroupByName( "staff" );
+ me->SetBodygroup( staffBodyGroup, 1 );
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CMerasmus > CMerasmusTaunt::Update( CMerasmus *me, float interval )
+{
+ if ( m_timer.IsElapsed() )
+ {
+ return Done();
+ }
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+void CMerasmusTaunt::OnEnd( CMerasmus *me, Action< CMerasmus > *nextAction )
+{
+ // turn the staff back on
+ int staffBodyGroup = me->FindBodygroupByName( "staff" );
+ me->SetBodygroup( staffBodyGroup, 0 );
+}
+
+
+
diff --git a/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_attack.h b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_attack.h
new file mode 100644
index 0000000..dc19cdf
--- /dev/null
+++ b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_attack.h
@@ -0,0 +1,63 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//
+//
+//=============================================================================
+#ifndef MERASMUS_ATTACK_H
+#define MERASMUS_ATTACK_H
+
+//---------------------------------------------------------------------------------------------
+//---------------------------------------------------------------------------------------------
+class CMerasmusAttack : public Action< CMerasmus >
+{
+public:
+ virtual ActionResult< CMerasmus > OnStart( CMerasmus *me, Action< CMerasmus > *priorAction );
+ virtual ActionResult< CMerasmus > Update( CMerasmus *me, float interval );
+
+ virtual EventDesiredResult< CMerasmus > OnStuck( CMerasmus *me );
+ virtual EventDesiredResult< CMerasmus > OnContact( CMerasmus *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_attackTimer;
+
+ CountdownTimer m_grenadeTimer;
+ void RandomGrenadeTimer();
+
+ CountdownTimer m_zapTimer;
+ void RandomZapTimer();
+
+ CountdownTimer m_bombHeadTimer;
+ CountdownTimer m_tauntTimer;
+
+ CHandle< CTFPlayer > m_attackTarget; // the victim I'm momentarily attacking
+ CountdownTimer m_attackTargetFocusTimer;
+ bool IsPotentiallyChaseable( CMerasmus *me, CTFPlayer *victim );
+ void SelectVictim( CMerasmus *me );
+};
+
+
+
+//---------------------------------------------------------------------------------------------
+class CMerasmusTaunt : public Action< CMerasmus >
+{
+public:
+ virtual ActionResult< CMerasmus > OnStart( CMerasmus *me, Action< CMerasmus > *priorAction );
+ virtual ActionResult< CMerasmus > Update( CMerasmus *me, float interval );
+ virtual void OnEnd( CMerasmus *me, Action< CMerasmus > *nextAction );
+
+ virtual const char *GetName( void ) const { return "Taunt"; } // return name of this action
+
+private:
+ CountdownTimer m_timer;
+};
+
+
+#endif // MERASMUS_ATTACK_H
diff --git a/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_disguise.cpp b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_disguise.cpp
new file mode 100644
index 0000000..063f959
--- /dev/null
+++ b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_disguise.cpp
@@ -0,0 +1,226 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//
+//
+//=============================================================================
+#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 "player_vs_environment/monster_resource.h"
+
+#include "../merasmus.h"
+#include "../merasmus_trick_or_treat_prop.h"
+#include "merasmus_disguise.h"
+#include "merasmus_reveal.h"
+
+ConVar tf_merasmus_disguise_debug( "tf_merasmus_disguise_debug", "0", FCVAR_CHEAT );
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CMerasmus > CMerasmusDisguise::OnStart( CMerasmus *me, Action< CMerasmus > *priorAction )
+{
+ m_bSpawnedProps = false;
+
+ TryToDisguiseSpawn( me );
+
+ m_flStartRegenTime = gpGlobals->curtime;
+ m_nStartRegenHealth = me->GetHealth();
+
+ me->PlayHighPrioritySound( "Halloween.MerasmusInitiateHiding" );
+ RandomDisguiseTauntTimer();
+
+ m_findPropsFailTimer.Start( 3 );
+
+ // set boss inactive
+ g_pMonsterResource->SetBossState( 1 );
+
+ return Continue();
+}
+
+
+//----------------------------------------------------------------------------------
+ActionResult< CMerasmus > CMerasmusDisguise::Update( CMerasmus *me, float interval )
+{
+ if ( me->ShouldLeave() )
+ {
+ return Done();
+ }
+ me->LeaveWarning();
+
+ if ( !m_bSpawnedProps )
+ {
+ if ( m_findPropsFailTimer.HasStarted() && m_findPropsFailTimer.IsElapsed() )
+ {
+ // Couldn't find props in time - skip
+ return Done();
+ }
+
+ if ( !m_findSpawnPositionTime.IsElapsed() )
+ {
+ // not ready yet
+ return Continue();
+ }
+
+ TryToDisguiseSpawn( me );
+
+ return Continue();
+ }
+
+ if ( m_disguiseTauntTimer.IsElapsed() )
+ {
+ if (RandomInt(0,10) == 0)
+ {
+ me->PlayHighPrioritySound( "Halloween.MerasmusHiddenRare" );
+ }
+ else
+ {
+ me->PlayHighPrioritySound( "Halloween.MerasmusHidden" );
+ }
+
+ RandomDisguiseTauntTimer();
+ }
+
+ // regen health while disguise
+ if ( me->GetHealth() < me->GetMaxHealth() )
+ {
+ float flHealthRegenPerSec = tf_merasmus_health_regen_rate.GetFloat() * me->GetMaxHealth() * ( me->GetLevel() - 1 );
+ int nNewHealth = MIN( ( gpGlobals->curtime - m_flStartRegenTime ) * flHealthRegenPerSec + m_nStartRegenHealth, me->GetMaxHealth() );
+ me->SetHealth( nNewHealth );
+
+ // show Boss' health meter on HUD
+ if ( g_pMonsterResource )
+ {
+ float healthPercentage = (float)me->GetHealth() / (float)me->GetMaxHealth();
+ g_pMonsterResource->SetBossHealthPercentage( healthPercentage );
+ }
+ }
+
+ // should I come out from disguise?
+ if ( me->ShouldReveal() )
+ {
+ return Done( "Revealed!" );
+ }
+
+ return Continue();
+}
+
+
+void CMerasmusDisguise::OnEnd( CMerasmus *me, Action< CMerasmus > *nextAction )
+{
+ if ( me->ShouldLeave() )
+ {
+ me->OnLeaveWhileInPropForm();
+ }
+
+ // set boss active
+ g_pMonsterResource->SetBossState( 0 );
+
+ me->OnRevealed();
+}
+
+
+QAngle GetRandomPropAngles( CTFNavArea* pArea )
+{
+ Vector vNormal;
+ pArea->ComputeNormal( &vNormal );
+ Vector vForward = pArea->GetRandomPoint() - pArea->GetCenter();
+ QAngle qAngles;
+ VectorAngles( vForward, vNormal, qAngles );
+
+ return qAngles;
+}
+
+
+void CMerasmusDisguise::TryToDisguiseSpawn( CMerasmus *me )
+{
+ m_findSpawnPositionTime.Start( 1 );
+
+ // 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 );
+
+ // pick a random spot
+ CUtlVector< CTFNavArea * > candidateAreaVector;
+ for( int i=0; i<TheNavAreas.Count(); ++i )
+ {
+ CTFNavArea *area = (CTFNavArea *)TheNavAreas[i];
+
+ if ( !area->HasFuncNavPrefer() )
+ {
+ // don't spawn outside nav prefer
+ continue;
+ }
+
+ // don't use small nav areas
+ const float goodSize = 150.f;
+ if ( area->GetSizeX() < goodSize || area->GetSizeY() < goodSize )
+ {
+ continue;
+ }
+
+ // don't use area containing player
+ if ( area->GetPlayerCount( TF_TEAM_BLUE ) || area->GetPlayerCount( TF_TEAM_RED ) )
+ {
+ continue;
+ }
+
+ // don't use slope area
+// Vector vNormal;
+// area->ComputeNormal( &vNormal );
+// if ( vNormal.z < 0.9f )
+// {
+// continue;
+// }
+
+ candidateAreaVector.AddToTail( area );
+ }
+
+ if ( candidateAreaVector.Count() == 0 )
+ {
+ // no place to spawn (!)
+ return;
+ }
+
+ // spread out the area
+ CUtlVector< CTFNavArea * > spawnAreaVector;
+ SelectSeparatedShuffleSet< CTFNavArea >( 10, 500.f, candidateAreaVector, &spawnAreaVector );
+
+ if ( spawnAreaVector.Count() == 0 )
+ {
+ // no place to spawn (!)
+ return;
+ }
+
+ if ( tf_merasmus_disguise_debug.GetBool() )
+ {
+ for ( int i=0; i<spawnAreaVector.Count(); ++i )
+ {
+ // draw all potential areas
+ spawnAreaVector[i]->DrawFilled( 0, 255, 0, 0, 30.f );
+ }
+ }
+
+ // spawn random props
+ int nRandomTrickOrTreatProps = spawnAreaVector.Count();
+ for ( int i=0; i<nRandomTrickOrTreatProps; ++i )
+ {
+ int propSpawnID = RandomInt( 0, spawnAreaVector.Count()-1 );
+
+ CTFMerasmusTrickOrTreatProp* pFakeProp = CTFMerasmusTrickOrTreatProp::Create( spawnAreaVector[ propSpawnID ]->GetCenter(), GetRandomPropAngles( spawnAreaVector[ propSpawnID ] ) );
+ me->AddFakeProp( pFakeProp );
+
+ spawnAreaVector.FastRemove( propSpawnID );
+ }
+
+ me->OnDisguise();
+ m_bSpawnedProps = true;
+}
+
+
+void CMerasmusDisguise::RandomDisguiseTauntTimer()
+{
+ m_disguiseTauntTimer.Start( RandomFloat( 10.f, 25.f ) );
+}
diff --git a/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_disguise.h b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_disguise.h
new file mode 100644
index 0000000..bfed85f
--- /dev/null
+++ b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_disguise.h
@@ -0,0 +1,33 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//
+//
+//=============================================================================
+#ifndef MERASMUS_DISGUISE_H
+#define MERASMUS_DISGUISE_H
+
+//---------------------------------------------------------------------------------------------
+//---------------------------------------------------------------------------------------------
+class CMerasmusDisguise : public Action< CMerasmus >
+{
+public:
+ virtual ActionResult< CMerasmus > OnStart( CMerasmus *me, Action< CMerasmus > *priorAction );
+ virtual ActionResult< CMerasmus > Update( CMerasmus *me, float interval );
+ virtual void OnEnd( CMerasmus *me, Action< CMerasmus > *nextAction );
+ virtual const char *GetName( void ) const { return "Disguise"; } // return name of this action
+
+private:
+ void TryToDisguiseSpawn( CMerasmus *me );
+ CountdownTimer m_findPropsFailTimer;
+ CountdownTimer m_findSpawnPositionTime;
+ bool m_bSpawnedProps;
+
+ void RandomDisguiseTauntTimer();
+ CountdownTimer m_disguiseTauntTimer;
+
+ float m_flStartRegenTime;
+ int m_nStartRegenHealth;
+};
+
+
+#endif // MERASMUS_DISGUISE_H
diff --git a/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_dying.cpp b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_dying.cpp
new file mode 100644
index 0000000..216ca92
--- /dev/null
+++ b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_dying.cpp
@@ -0,0 +1,60 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//
+//
+//=============================================================================
+#include "cbase.h"
+
+#include "particle_parse.h"
+#include "tf_gamerules.h"
+
+#include "../merasmus.h"
+#include "merasmus_dying.h"
+#include "tf/halloween/eyeball_boss/teleport_vortex.h"
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CMerasmus > CMerasmusDying::OnStart( CMerasmus *me, Action< CMerasmus > *priorAction )
+{
+ me->GetBodyInterface()->StartActivity( ACT_DIESIMPLE );
+ me->PlayHighPrioritySound( "Halloween.MerasmusBanish" );
+ TFGameRules()->BroadcastSound( 255, "Halloween.Merasmus_Death" );
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CMerasmus > CMerasmusDying::Update( CMerasmus *me, float interval )
+{
+ if ( me->IsActivityFinished() )
+ {
+ me->Break();
+ DispatchParticleEffect( "merasmus_spawn", me->GetAbsOrigin(), me->GetAbsAngles() );
+
+ IGameEvent *event = gameeventmanager->CreateEvent( "merasmus_killed" );
+ if ( event )
+ {
+ event->SetInt( "level", me->GetLevel() );
+ gameeventmanager->FireEvent( event );
+ }
+ me->TriggerLogicRelay( "boss_dead_relay" );
+
+ // create vortex to loot
+ CTeleportVortex *vortex = (CTeleportVortex *)CBaseEntity::Create( "teleport_vortex", me->WorldSpaceCenter(), vec3_angle );
+ if ( vortex )
+ {
+ vortex->SetupVortex( true, true );
+ }
+
+ me->GainLevel();
+
+ me->StartRespawnTimer();
+
+ UTIL_Remove( me );
+
+ return Done();
+ }
+
+ return Continue();
+}
+
diff --git a/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_dying.h b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_dying.h
new file mode 100644
index 0000000..97e888c
--- /dev/null
+++ b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_dying.h
@@ -0,0 +1,20 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//
+//
+//=============================================================================
+#ifndef MERASMUS_DYING_H
+#define MERASMUS_DYING_H
+
+
+//---------------------------------------------------------------------------------------------
+class CMerasmusDying : public Action< CMerasmus >
+{
+public:
+ virtual ActionResult< CMerasmus > OnStart( CMerasmus *me, Action< CMerasmus > *priorAction );
+ virtual ActionResult< CMerasmus > Update( CMerasmus *me, float interval );
+ virtual const char *GetName( void ) const { return "Dying"; } // return name of this action
+};
+
+
+#endif // MERASMUS_DYING_H
diff --git a/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_reveal.cpp b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_reveal.cpp
new file mode 100644
index 0000000..1dc7a62
--- /dev/null
+++ b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_reveal.cpp
@@ -0,0 +1,38 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//
+//
+//=============================================================================
+#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 "../merasmus.h"
+#include "merasmus_reveal.h"
+#include "merasmus_attack.h"
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CMerasmus > CMerasmusReveal::OnStart( CMerasmus *me, Action< CMerasmus > *priorAction )
+{
+ me->OnRevealed(false);
+
+ me->GetBodyInterface()->StartActivity( ACT_SHIELD_UP );
+
+ return Continue();
+}
+
+
+//----------------------------------------------------------------------------------
+ActionResult< CMerasmus > CMerasmusReveal::Update( CMerasmus *me, float interval )
+{
+ if ( me->IsActivityFinished() )
+ {
+ return ChangeTo( new CMerasmusAttack, "Here I come!" );
+ }
+
+ return Continue();
+}
diff --git a/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_reveal.h b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_reveal.h
new file mode 100644
index 0000000..de56951
--- /dev/null
+++ b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_reveal.h
@@ -0,0 +1,20 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//
+//
+//=============================================================================
+#ifndef MERASMUS_EMERGE_H
+#define MERASMUS_EMERGE_H
+
+//---------------------------------------------------------------------------------------------
+//---------------------------------------------------------------------------------------------
+class CMerasmusReveal : public Action< CMerasmus >
+{
+public:
+ virtual ActionResult< CMerasmus > OnStart( CMerasmus *me, Action< CMerasmus > *priorAction );
+ virtual ActionResult< CMerasmus > Update( CMerasmus *me, float interval );
+ virtual const char *GetName( void ) const { return "Reveal"; } // return name of this action
+};
+
+
+#endif // MERASMUS_EMERGE_H
diff --git a/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_staff_attack.cpp b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_staff_attack.cpp
new file mode 100644
index 0000000..b15e4f7
--- /dev/null
+++ b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_staff_attack.cpp
@@ -0,0 +1,117 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//
+//
+//=============================================================================
+#include "cbase.h"
+
+#include "tf_gamerules.h"
+#include "tf_player.h"
+
+#include "../merasmus.h"
+#include "merasmus_staff_attack.h"
+#include "merasmus_stunned.h"
+
+CMerasmusStaffAttack::CMerasmusStaffAttack( CTFPlayer* pTarget )
+{
+ m_hTarget = pTarget;
+}
+
+
+ActionResult< CMerasmus > CMerasmusStaffAttack::OnStart( CMerasmus *me, Action< CMerasmus > *priorAction )
+{
+ // smooth out the bot's path following by moving toward a point farther down the path
+ m_path.SetMinLookAheadDistance( 100.0f );
+
+ int iLayer = me->AddGesture( ACT_MP_ATTACK_STAND_MELEE );
+ float flDuration = me->GetLayerDuration( iLayer );
+ m_staffSwingTimer.Start( flDuration );
+ m_hitTimer.Start( 0.5f * flDuration );
+
+ if ( RandomInt( 0, 2 ) == 0 )
+ {
+ CPVSFilter filter( me->WorldSpaceCenter() );
+ if ( RandomInt( 1, 5 ) == 1 )
+ {
+ me->PlayLowPrioritySound( filter, "Halloween.MerasmusStaffAttackRare" );
+ }
+ else
+ {
+ me->PlayLowPrioritySound( filter, "Halloween.MerasmusStaffAttack" );
+ }
+ }
+
+ return Continue();
+}
+
+
+ActionResult< CMerasmus > CMerasmusStaffAttack::Update( CMerasmus *me, float interval )
+{
+ // Interupt if stunned
+ if ( me->HasStunTimer() )
+ {
+ return ChangeTo( new CMerasmusStunned, "Stun Interupt!" );
+ }
+
+ if ( m_hitTimer.HasStarted() && m_hitTimer.IsElapsed() )
+ {
+ m_hitTimer.Invalidate();
+
+ if ( m_hTarget != NULL )
+ {
+ Vector forward;
+ me->GetVectors( &forward, NULL, NULL );
+
+ Vector toVictim = m_hTarget->WorldSpaceCenter() - me->WorldSpaceCenter();
+ toVictim.NormalizeInPlace();
+
+ // looser tolerance as victim gets closer
+ const float closeRange = 100.0f;
+ float range = me->GetRangeTo( m_hTarget );
+ float closeness = ( range < closeRange ) ? 0.0f : ( range - closeRange ) / ( tf_merasmus_attack_range.GetFloat() - closeRange );
+ float hitAngle = 0.0f + closeness * 0.27f;
+
+ if ( DotProduct( forward, toVictim ) > hitAngle )
+ {
+ if ( me->IsRangeLessThan( m_hTarget, 0.9f * tf_merasmus_attack_range.GetFloat() ) )
+ {
+ if ( me->IsLineOfSightClear( m_hTarget ) )
+ {
+ // CHOP!
+ CTakeDamageInfo info( me, me, 70, DMG_CLUB, TF_DMG_CUSTOM_MERASMUS_DECAPITATION );
+ CalculateMeleeDamageForce( &info, toVictim, me->WorldSpaceCenter(), 5.0f );
+ m_hTarget->TakeDamage( info );
+
+ CPVSFilter filter( me->WorldSpaceCenter() );
+ me->PlayLowPrioritySound( filter, "Halloween.HeadlessBossAxeHitFlesh" );
+
+ me->PushPlayer( m_hTarget, 500.f );
+ }
+ }
+ }
+ }
+ }
+
+ if ( m_hTarget )
+ {
+ if ( me->IsRangeGreaterThan( m_hTarget, 100.f ) || !me->IsLineOfSightClear( m_hTarget ) )
+ {
+ if ( m_path.GetAge() > 1.0f )
+ {
+ CMerasmusPathCost cost( me );
+ m_path.Compute( me, m_hTarget, cost );
+ }
+
+ m_path.Update( me );
+ }
+
+ me->GetLocomotionInterface()->FaceTowards( m_hTarget->WorldSpaceCenter() );
+ }
+
+ if ( m_staffSwingTimer.IsElapsed() )
+ {
+ return Done();
+ }
+
+ return Continue();
+}
diff --git a/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_staff_attack.h b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_staff_attack.h
new file mode 100644
index 0000000..60629fc
--- /dev/null
+++ b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_staff_attack.h
@@ -0,0 +1,28 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//
+//
+//=============================================================================
+#ifndef MERASMUS_STAFF_ATTACK_H
+#define MERASMUS_STAFF_ATTACK_H
+
+//---------------------------------------------------------------------------------------------
+//---------------------------------------------------------------------------------------------
+class CMerasmusStaffAttack : public Action< CMerasmus >
+{
+public:
+ CMerasmusStaffAttack( CTFPlayer* pTarget );
+ virtual ActionResult< CMerasmus > OnStart( CMerasmus *me, Action< CMerasmus > *priorAction );
+ virtual ActionResult< CMerasmus > Update( CMerasmus *me, float interval );
+
+ virtual const char *GetName( void ) const { return "Staff Attack"; } // return name of this action
+
+private:
+ CountdownTimer m_staffSwingTimer;
+ CountdownTimer m_hitTimer;
+ CHandle< CTFPlayer > m_hTarget;
+
+ PathFollower m_path;
+};
+
+#endif // MERASMUS_STAFF_ATTACK_H \ No newline at end of file
diff --git a/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_stunned.cpp b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_stunned.cpp
new file mode 100644
index 0000000..8efc321
--- /dev/null
+++ b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_stunned.cpp
@@ -0,0 +1,91 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//
+//
+//=============================================================================
+#include "cbase.h"
+
+#include "../merasmus.h"
+#include "merasmus_stunned.h"
+#include "merasmus_teleport.h"
+
+#include "tf_player.h"
+
+ActionResult< CMerasmus > CMerasmusStunned::OnStart( CMerasmus *me, Action< CMerasmus > *priorAction )
+{
+ m_nStunStage = STUN_BEGIN;
+
+ int iLayer = me->AddGesture( ACT_MP_STUN_BEGIN );
+ float flDuration = me->GetLayerDuration( iLayer );
+ m_stunAnimationTimer.Start( flDuration );
+
+ me->OnBeginStun();
+
+ return Continue();
+}
+
+
+ActionResult< CMerasmus > CMerasmusStunned::Update( CMerasmus *me, float interval )
+{
+ // finished?
+ if ( m_nStunStage == STUN_END && m_stunFinishTimer.IsElapsed() )
+ {
+ if ( me->ShouldDisguise() )
+ {
+ return Done();
+ }
+
+ if ( me->GetBombHitCount() >= 3 )
+ {
+ return ChangeTo( new CMerasmusTeleport( true, true ), "Teleport AOE!" );
+ }
+ else
+ {
+ return ChangeTo( new CMerasmusTeleport( false, false ), "Teleport to new area!" );
+ }
+ }
+
+ if ( m_stunAnimationTimer.IsElapsed() )
+ {
+ bool bStunned = me->HasStunTimer();
+
+ // reset animation if stunned
+ if ( bStunned )
+ {
+ m_nStunStage = STUN_BEGIN;
+ }
+
+ switch ( m_nStunStage )
+ {
+ case STUN_BEGIN:
+ {
+ int iLayer = me->AddGesture( ACT_MP_STUN_MIDDLE );
+ float flDuration = me->GetLayerDuration( iLayer );
+ m_stunAnimationTimer.Start( flDuration );
+ if ( !bStunned )
+ {
+ m_nStunStage = STUN_MID;
+ }
+ }
+ break;
+ case STUN_MID:
+ {
+ int iLayer = me->AddGesture( ACT_MP_STUN_END );
+ float flDuration = me->GetLayerDuration( iLayer );
+ m_stunAnimationTimer.Start( flDuration );
+
+ m_nStunStage = STUN_END;
+ m_stunFinishTimer.Start( flDuration + 0.5f );
+ }
+ break;
+ }
+ }
+
+ return Continue();
+}
+
+
+void CMerasmusStunned::OnEnd( CMerasmus *me, Action< CMerasmus > *nextAction )
+{
+ me->OnEndStun();
+}
diff --git a/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_stunned.h b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_stunned.h
new file mode 100644
index 0000000..4f02145
--- /dev/null
+++ b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_stunned.h
@@ -0,0 +1,29 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//
+//
+//=============================================================================
+#ifndef TF_MERASMUS_STUNNED_H
+#define TF_MERASMUS_STUNNED_H
+
+class CMerasmusStunned : public Action< CMerasmus >
+{
+public:
+ virtual ActionResult< CMerasmus > OnStart( CMerasmus *me, Action< CMerasmus > *priorAction );
+ virtual ActionResult< CMerasmus > Update( CMerasmus *me, float interval );
+ virtual void OnEnd( CMerasmus *me, Action< CMerasmus > *nextAction );
+
+ virtual const char *GetName( void ) const { return "Stunned!"; } // return name of this action
+private:
+ enum StunStage_t
+ {
+ STUN_BEGIN,
+ STUN_MID,
+ STUN_END
+ };
+ StunStage_t m_nStunStage;
+ CountdownTimer m_stunAnimationTimer;
+ CountdownTimer m_stunFinishTimer;
+};
+
+#endif //TF_MERASMUS_STUNNED_H
diff --git a/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_teleport.cpp b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_teleport.cpp
new file mode 100644
index 0000000..538b77f
--- /dev/null
+++ b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_teleport.cpp
@@ -0,0 +1,189 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//
+//
+//=============================================================================
+#include "cbase.h"
+
+#include "particle_parse.h"
+#include "tf/halloween/eyeball_boss/teleport_vortex.h"
+#include "player_vs_environment/monster_resource.h"
+#include "tf_gamerules.h"
+#include "nav_mesh/tf_nav_area.h"
+
+#include "../merasmus.h"
+#include "merasmus_teleport.h"
+#include "merasmus_aoe_attack.h"
+
+
+CMerasmusTeleport::CMerasmusTeleport( bool bShouldAOE, bool bGoToCap )
+ : m_bShouldAOE( bShouldAOE ), m_bShouldGoToCap( bGoToCap )
+{
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CMerasmus > CMerasmusTeleport::OnStart( CMerasmus *me, Action< CMerasmus > *priorAction )
+{
+ // teleport out
+ m_state = TELEPORTING_OUT;
+ me->GetBodyInterface()->StartActivity( ACT_SHIELD_DOWN );
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CMerasmus > CMerasmusTeleport::Update( CMerasmus *me, float interval )
+{
+ if ( me->IsActivityFinished() )
+ {
+ switch( m_state )
+ {
+ case TELEPORTING_OUT:
+ {
+ DispatchParticleEffect( "merasmus_tp", me->WorldSpaceCenter(), me->GetAbsAngles() );
+
+ me->AddEffects( EF_NOINTERP | EF_NODRAW );
+
+ me->SetAbsOrigin( GetTeleportPosition( me ) );
+
+ // wait on the other side for a moment
+ m_state = TELEPORTING_IN;
+ }
+ break;
+
+ case TELEPORTING_IN:
+ {
+ me->RemoveEffects( EF_NOINTERP | EF_NODRAW );
+
+ DispatchParticleEffect( "merasmus_tp", me->WorldSpaceCenter(), me->GetAbsAngles() );
+
+ me->GetBodyInterface()->StartActivity( ACT_SHIELD_UP );
+
+ m_state = DONE;
+ }
+ break;
+
+ case DONE:
+ {
+ if ( m_bShouldAOE )
+ {
+ m_bShouldAOE = false;
+ return SuspendFor( new CMerasmusAOEAttack, "AOE Attack!" );
+ }
+ }
+ return Done();
+ }
+ }
+
+ return Continue();
+}
+
+
+Vector CMerasmusTeleport::GetTeleportPosition( CMerasmus *me ) const
+{
+ Vector vGroundOffset( 0, 0, 75.0f );
+ if ( m_bShouldGoToCap )
+ {
+ return me->GetHomePosition() + vGroundOffset;
+ }
+ else
+ {
+ // pick a random spot
+ const float goodSize = 100.f;
+ CUtlVector< CTFNavArea * > spawnAreaVector;
+ for( int i=0; i<TheNavAreas.Count(); ++i )
+ {
+ CTFNavArea *area = (CTFNavArea *)TheNavAreas[i];
+
+ if ( !area->HasFuncNavPrefer() )
+ {
+ // don't spawn outside nav prefer
+ continue;
+ }
+
+ // don't use small nav areas
+ if ( area->GetSizeX() < goodSize || area->GetSizeY() < goodSize )
+ {
+ continue;
+ }
+
+ // don't use area containing player
+ if ( area->GetPlayerCount( TF_TEAM_BLUE ) || area->GetPlayerCount( TF_TEAM_RED ) )
+ {
+ continue;
+ }
+
+ spawnAreaVector.AddToTail( area );
+ }
+
+ if ( spawnAreaVector.Count() )
+ {
+ int which = RandomInt( 0, spawnAreaVector.Count() - 1 );
+ return spawnAreaVector[ which ]->GetCenter();
+ }
+ else
+ {
+ return me->GetHomePosition() + vGroundOffset;
+ }
+ }
+}
+
+
+//---------------------------------------------------------------------------------------------
+//---------------------------------------------------------------------------------------------
+ActionResult< CMerasmus > CMerasmusEscape::OnStart( CMerasmus *me, Action< CMerasmus > *priorAction )
+{
+ me->GetBodyInterface()->StartActivity( ACT_FLY );
+
+ if (RandomInt(0,10) == 0)
+ {
+ me->PlayHighPrioritySound( "Halloween.MerasmusDepartRare" );
+ }
+ else
+ {
+ me->PlayHighPrioritySound( "Halloween.MerasmusDepart" );
+ }
+
+ UTIL_LogPrintf( "HALLOWEEN: merasmus_escaped (max_dps %3.2f) (health %d) (level %d)\n", me->GetMaxInjuryRate(), me->GetHealth(), me->GetLevel() );
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CMerasmus > CMerasmusEscape::Update( CMerasmus *me, float interval )
+{
+ if ( me->IsActivityFinished() )
+ {
+ Vector vPos;
+ QAngle qAngles;
+ me->GetAttachment( "effect_robe", vPos, qAngles );
+ DispatchParticleEffect( "merasmus_tp", vPos, qAngles );
+
+ if ( g_pMonsterResource )
+ {
+ g_pMonsterResource->HideBossHealthMeter();
+ }
+
+ IGameEvent *event = gameeventmanager->CreateEvent( "merasmus_escaped" );
+ if ( event )
+ {
+ event->SetInt( "level", me->GetLevel() );
+ gameeventmanager->FireEvent( event );
+ }
+ me->TriggerLogicRelay( "boss_exit_relay" );
+
+ // reset back to normal level
+ me->ResetLevel();
+
+ me->StartRespawnTimer();
+
+ UTIL_Remove( me );
+
+ return Done();
+ }
+
+ return Continue();
+}
diff --git a/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_teleport.h b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_teleport.h
new file mode 100644
index 0000000..1bd5d67
--- /dev/null
+++ b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_teleport.h
@@ -0,0 +1,49 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//
+//
+//=============================================================================
+#ifndef MERASMUS_TELEPORT_H
+#define MERASMUS_TELEPORT_H
+
+//---------------------------------------------------------------------------------------------
+//---------------------------------------------------------------------------------------------
+class CMerasmusTeleport : public Action< CMerasmus >
+{
+public:
+ CMerasmusTeleport( bool bShouldAOE, bool bGoToCap );
+
+ virtual ActionResult< CMerasmus > OnStart( CMerasmus *me, Action< CMerasmus > *priorAction );
+ virtual ActionResult< CMerasmus > Update( CMerasmus *me, float interval );
+
+ virtual const char *GetName( void ) const { return "Teleport"; } // return name of this action
+
+private:
+ enum TeleportState
+ {
+ TELEPORTING_OUT,
+ TELEPORTING_IN,
+ DONE
+ };
+ TeleportState m_state;
+
+ bool m_bShouldAOE;
+ bool m_bShouldGoToCap;
+
+ Vector GetTeleportPosition( CMerasmus *me ) const;
+};
+
+
+//---------------------------------------------------------------------------------------------
+//---------------------------------------------------------------------------------------------
+class CMerasmusEscape : public Action< CMerasmus >
+{
+public:
+ virtual ActionResult< CMerasmus > OnStart( CMerasmus *me, Action< CMerasmus > *priorAction );
+ virtual ActionResult< CMerasmus > Update( CMerasmus *me, float interval );
+
+ virtual const char *GetName( void ) const { return "Escape"; } // return name of this action
+};
+
+
+#endif // MERASMUS_TELEPORT_H
diff --git a/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_throwing_grenade.cpp b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_throwing_grenade.cpp
new file mode 100644
index 0000000..f82fdc7
--- /dev/null
+++ b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_throwing_grenade.cpp
@@ -0,0 +1,159 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//
+//
+//=============================================================================
+#include "cbase.h"
+
+#include "tf_player.h"
+#include "tf_gamerules.h"
+#include "particle_parse.h"
+
+#include "../merasmus.h"
+#include "merasmus_throwing_grenade.h"
+#include "merasmus_stunned.h"
+
+CMerasmusThrowingGrenade::CMerasmusThrowingGrenade( CTFPlayer* pTarget )
+{
+ m_hTarget = pTarget;
+}
+
+
+ActionResult< CMerasmus > CMerasmusThrowingGrenade::OnStart( CMerasmus *me, Action< CMerasmus > *priorAction )
+{
+ if ( m_hTarget == NULL )
+ {
+ return Done( "No Target" );
+ }
+
+ if ( !me->IsLineOfSightClear( m_hTarget ) )
+ {
+ 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 );
+
+ CUtlVector< CTFPlayer * > newTargetVector;
+ for ( int i=0; i<playerVector.Count(); ++i )
+ {
+ if ( playerVector[i] == m_hTarget )
+ {
+ continue;
+ }
+
+ if ( !me->IsLineOfSightClear( playerVector[i] ) )
+ {
+ continue;
+ }
+
+ newTargetVector.AddToTail( playerVector[i] );
+ }
+
+ if ( newTargetVector.Count() == 0 )
+ {
+ m_hTarget = NULL;
+ }
+ else
+ {
+ int which = RandomInt( 0, newTargetVector.Count() - 1 );
+ m_hTarget = newTargetVector[ which ];
+ }
+ }
+
+ if ( m_hTarget == NULL )
+ {
+ return Done( "No Target" );
+ }
+
+ me->GetLocomotionInterface()->FaceTowards( m_hTarget->WorldSpaceCenter() );
+ int iLayer = me->AddGesture( ACT_MP_ATTACK_STAND_ITEM1 );
+ float flDuration = me->GetLayerDuration( iLayer );
+ m_throwTimer.Start( flDuration );
+
+ // we want to release the grenade mid-animation
+ m_releaseGrenadeTimer.Start( 0.25f );
+
+ // hide his staff
+ int staffBodyGroup = me->FindBodygroupByName( "staff" );
+ me->SetBodygroup( staffBodyGroup, 2 );
+
+ // smooth out the bot's path following by moving toward a point farther down the path
+ m_path.SetMinLookAheadDistance( 100.0f );
+
+ return Continue();
+}
+
+
+ActionResult< CMerasmus > CMerasmusThrowingGrenade::Update( CMerasmus *me, float interval )
+{
+ // Interupt if stunned
+ if ( me->HasStunTimer() )
+ {
+ return ChangeTo( new CMerasmusStunned, "Stun Interupt!" );
+ }
+
+ if ( m_releaseGrenadeTimer.HasStarted() && m_releaseGrenadeTimer.IsElapsed() )
+ {
+ m_releaseGrenadeTimer.Invalidate();
+
+ DispatchParticleEffect( "merasmus_shoot", PATTACH_ABSORIGIN_FOLLOW, me, "effect_hand_R" );
+
+ Vector vPos;
+ QAngle qAngles;
+ me->GetAttachment( "effect_hand_R", vPos, qAngles );
+
+ Vector vForward, vRight, vUp;
+ AngleVectors( me->EyeAngles(), &vForward, &vRight, &vUp );
+ float flLaunchSpeed = RandomFloat( 1500.f, 2000.f );
+ Vector vecVelocity = ( vForward * flLaunchSpeed ) + ( vUp * 200.0f ) + ( RandomFloat( -10.0f, 10.0f ) * vRight ) + ( RandomFloat( -10.0f, 10.0f ) * vUp );
+ CTFWeaponBaseGrenadeProj* pGrenade = CMerasmus::CreateMerasmusGrenade( vPos, vecVelocity, me );
+ if ( pGrenade )
+ {
+ if ( RandomInt( 0, 6 ) == 0 )
+ {
+ CPVSFilter filter( me->WorldSpaceCenter() );
+ if ( RandomInt(1,10) == 1 )
+ {
+ me->PlayLowPrioritySound( filter, "Halloween.MerasmusGrenadeThrowRare" );
+ }
+ else
+ {
+ me->PlayLowPrioritySound( filter, "Halloween.MerasmusGrenadeThrow" );
+ }
+ }
+ }
+ }
+
+ if ( m_hTarget )
+ {
+ if ( me->IsRangeGreaterThan( m_hTarget, 100.f ) || !me->IsLineOfSightClear( m_hTarget ) )
+ {
+ if ( m_path.GetAge() > 1.0f )
+ {
+ CMerasmusPathCost cost( me );
+ m_path.Compute( me, m_hTarget, cost );
+ }
+
+ m_path.Update( me );
+ }
+
+ me->GetLocomotionInterface()->FaceTowards( m_hTarget->WorldSpaceCenter() );
+ }
+
+
+ if ( m_throwTimer.IsElapsed() )
+ {
+ return Done( "Fire in the hole!" );
+ }
+
+ return Continue();
+}
+
+
+void CMerasmusThrowingGrenade::OnEnd( CMerasmus *me, Action< CMerasmus > *nextAction )
+{
+ // turn his staff back on
+ int staffBodyGroup = me->FindBodygroupByName( "staff" );
+ me->SetBodygroup( staffBodyGroup, 0 );
+}
diff --git a/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_throwing_grenade.h b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_throwing_grenade.h
new file mode 100644
index 0000000..7a508cf
--- /dev/null
+++ b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_throwing_grenade.h
@@ -0,0 +1,27 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//
+//
+//=============================================================================
+#ifndef MERASMUS_ROCKET_H
+#define MERASMUS_ROCKET_H
+
+class CMerasmusThrowingGrenade : public Action< CMerasmus >
+{
+public:
+ CMerasmusThrowingGrenade( CTFPlayer* pTarget );
+
+ virtual ActionResult< CMerasmus > OnStart( CMerasmus *me, Action< CMerasmus > *priorAction );
+ virtual ActionResult< CMerasmus > Update( CMerasmus *me, float interval );
+ virtual void OnEnd( CMerasmus *me, Action< CMerasmus > *nextAction );
+
+ virtual const char *GetName( void ) const { return "Rocket"; } // return name of this action
+private:
+ CHandle< CTFPlayer > m_hTarget;
+ CountdownTimer m_throwTimer;
+ CountdownTimer m_releaseGrenadeTimer;
+
+ PathFollower m_path;
+};
+
+#endif // MERASMUS_ROCKET_H
diff --git a/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_zap.cpp b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_zap.cpp
new file mode 100644
index 0000000..1be6822
--- /dev/null
+++ b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_zap.cpp
@@ -0,0 +1,73 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//
+//
+//=============================================================================
+#include "cbase.h"
+
+#include "../merasmus.h"
+#include "merasmus_zap.h"
+#include "merasmus_stunned.h"
+
+ActionResult< CMerasmus > CMerasmusZap::OnStart( CMerasmus *me, Action< CMerasmus > *priorAction )
+{
+ me->GetBodyInterface()->StartActivity( ACT_RANGE_ATTACK2 );
+ m_zapTimer.Start( 1.3f );
+
+ m_spellType = SpellType_t( RandomInt( 0, SPELL_COUNT - 1 ) );
+ PlayCastSound( me );
+
+ return Continue();
+}
+
+
+ActionResult< CMerasmus > CMerasmusZap::Update( CMerasmus *me, float interval )
+{
+ // Interupt if stunned
+ if ( me->HasStunTimer() )
+ {
+ return ChangeTo( new CMerasmusStunned, "Stun Interupt!" );
+ }
+
+ if ( m_zapTimer.HasStarted() && m_zapTimer.IsElapsed() )
+ {
+ m_zapTimer.Invalidate();
+
+ const float flSpellRange = 600.f + 50.f * ( me->GetLevel() - 1 );
+ const int nTargetCount = 6 + ( me->GetLevel() - 1 );
+ const float flMaxDamage = 50.f + ( 5 * (me->GetLevel() - 1) );
+ const float flMinDamage = 20.f + ( 5 * (me->GetLevel() - 1) );
+
+ if ( CMerasmus::Zap( me, "effect_staff", flSpellRange, flMinDamage, flMaxDamage, nTargetCount ) )
+ {
+ me->EmitSound( "Halloween.Merasmus_Spell" );
+ }
+ }
+
+ if ( me->IsActivityFinished() )
+ {
+ return Done( "Zapped!" );
+ }
+
+ return Continue();
+}
+
+
+void CMerasmusZap::PlayCastSound( CMerasmus* me ) const
+{
+ CPVSFilter filter( me->WorldSpaceCenter() );
+ switch ( m_spellType )
+ {
+ case SPELL_FIRE:
+ {
+ me->PlayLowPrioritySound( filter, "Halloween.MerasmusCastFireSpell" );
+ }
+ break;
+ case SPELL_LAUNCH:
+ {
+ me->PlayLowPrioritySound( filter, "Halloween.MerasmusLaunchSpell" );
+ }
+ break;
+ }
+}
+
diff --git a/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_zap.h b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_zap.h
new file mode 100644
index 0000000..559d136
--- /dev/null
+++ b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_zap.h
@@ -0,0 +1,32 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//
+//
+//=============================================================================
+#ifndef TF_MERASMUS_ZAP_H
+#define TF_MERASMUS_ZAP_H
+
+#include "tf_gamerules.h"
+
+class CMerasmusZap : public Action< CMerasmus >
+{
+public:
+ virtual ActionResult< CMerasmus > OnStart( CMerasmus *me, Action< CMerasmus > *priorAction );
+ virtual ActionResult< CMerasmus > Update( CMerasmus *me, float interval );
+
+ virtual const char *GetName( void ) const { return "Zap!"; } // return name of this action
+private:
+ enum SpellType_t
+ {
+ SPELL_FIRE,
+ SPELL_LAUNCH,
+
+ SPELL_COUNT
+ };
+ SpellType_t m_spellType;
+ void PlayCastSound( CMerasmus* me ) const;
+
+ CountdownTimer m_zapTimer;
+};
+
+#endif //TF_MERASMUS_ZAP_H