diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /game/server/tf/halloween/merasmus/merasmus_behavior | |
| download | archived-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')
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 |