This commit is contained in:
FluorescentCIAAfricanAmerican
2020-04-22 12:56:21 -04:00
commit 3bf9df6b27
15370 changed files with 5489726 additions and 0 deletions
@@ -0,0 +1,81 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// eyeball_boss_approach_target.cpp
// The 2011 Halloween Boss
// Michael Booth, October 2011
#include "cbase.h"
#include "../eyeball_boss.h"
#include "eyeball_boss_approach_target.h"
#include "eyeball_boss_launch_rockets.h"
//---------------------------------------------------------------------------------------------
ActionResult< CEyeballBoss > CEyeballBossApproachTarget::OnStart( CEyeballBoss *me, Action< CEyeballBoss > *priorAction )
{
m_giveUpTimer.Start( 5.0f );
m_minChaseTimer.Start( 0.5f );
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CEyeballBoss > CEyeballBossApproachTarget::Update( CEyeballBoss *me, float interval )
{
CBaseCombatCharacter *victim = me->GetVictim();
CBaseCombatCharacter *closestVictim = me->FindClosestVisibleVictim();
if ( victim != closestVictim && m_minChaseTimer.IsElapsed() )
{
return Done( "Noticed better victim" );
}
if ( !victim || !victim->IsAlive() )
{
return Done( "Victim gone" );
}
if ( m_giveUpTimer.IsElapsed() )
{
return Done( "Giving up" );
}
bool isVictimVisible = me->IsLineOfSightClear( victim, CBaseCombatCharacter::IGNORE_ACTORS );
if ( !isVictimVisible )
{
if ( m_lingerTimer.IsElapsed() )
{
return Done( "Lost victim" );
}
// wait a bit to see if we catch a glimpse of our victim again
return Continue();
}
m_lingerTimer.Start( 1.0f );
float attackRange = tf_eyeball_boss_attack_range.GetFloat();
if ( me->IsEnraged() )
{
attackRange *= 2.0f;
}
if ( me->IsRangeLessThan( victim, attackRange ) )
{
return ChangeTo( new CEyeballBossLaunchRockets, "Rocket attack!" );
}
// approach victim
me->GetLocomotionInterface()->SetDesiredSpeed( tf_eyeball_boss_speed.GetFloat() );
me->GetLocomotionInterface()->Approach( victim->WorldSpaceCenter() );
return Continue();
}
//---------------------------------------------------------------------------------------------
void CEyeballBossApproachTarget::OnEnd( CEyeballBoss *me, Action< CEyeballBoss > *nextAction )
{
}
@@ -0,0 +1,26 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// eyeball_boss_approach_target.h
// The 2011 Halloween Boss
// Michael Booth, October 2011
#ifndef EYEBALL_BOSS_APPROACH_TARGET_H
#define EYEBALL_BOSS_APPROACH_TARGET_H
//---------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------
class CEyeballBossApproachTarget : public Action< CEyeballBoss >
{
public:
virtual ActionResult< CEyeballBoss > OnStart( CEyeballBoss *me, Action< CEyeballBoss > *priorAction );
virtual ActionResult< CEyeballBoss > Update( CEyeballBoss *me, float interval );
virtual void OnEnd( CEyeballBoss *me, Action< CEyeballBoss > *nextAction );
virtual const char *GetName( void ) const { return "ApproachTarget"; } // return name of this action
private:
CountdownTimer m_lingerTimer;
CountdownTimer m_giveUpTimer;
CountdownTimer m_minChaseTimer;
};
#endif // EYEBALL_BOSS_APPROACH_TARGET_H
@@ -0,0 +1,201 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// eyeball_boss_behavior.cpp
// The 2011 Halloween Boss' top level behavior, containing all other actions as children
// Michael Booth, October 2011
#include "cbase.h"
#include "tf_player.h"
#include "tf_gamerules.h"
#include "particle_parse.h"
#include "tf/halloween/eyeball_boss/teleport_vortex.h"
#include "../eyeball_boss.h"
#include "eyeball_boss_behavior.h"
#include "eyeball_boss_emerge.h"
#include "eyeball_boss_stunned.h"
//---------------------------------------------------------------------------------------------
Action< CEyeballBoss > *CEyeballBossBehavior::InitialContainedAction( CEyeballBoss *me )
{
return new CEyeballBossEmerge;
}
//---------------------------------------------------------------------------------------------
ActionResult< CEyeballBoss > CEyeballBossBehavior::OnStart( CEyeballBoss *me, Action< CEyeballBoss > *priorAction )
{
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CEyeballBoss > CEyeballBossBehavior::Update( CEyeballBoss *me, float interval )
{
if ( tf_eyeball_boss_debug.GetBool() )
{
DevMsg( "%3.2f: DPS = %3.2f\n", gpGlobals->curtime, me->GetInjuryRate() );
}
return Continue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CEyeballBoss > CEyeballBossBehavior::OnInjured( CEyeballBoss *me, const CTakeDamageInfo &info )
{
CTFPlayer *attacker = ToTFPlayer( info.GetAttacker() );
if ( attacker )
{
if ( attacker->HasPurgatoryBuff() && m_stunCooldownTimer.IsElapsed() )
{
IGameEvent *event = gameeventmanager->CreateEvent( "eyeball_boss_stunned" );
if ( event )
{
event->SetInt( "level", me->GetLevel() );
event->SetInt( "player_entindex", attacker->entindex() );
gameeventmanager->FireEvent( event );
}
me->LogPlayerInteraction( "eyeball_stunned", attacker );
m_stunCooldownTimer.Start( 10.0f );
return TrySuspendFor( new CEyeballBossStunned, RESULT_IMPORTANT, "Hurt by Purgatory Buff!" );
}
// critz piss me off
if ( info.GetDamageType() & DMG_CRITICAL )
{
me->BecomeEnraged( 5.0f );
}
// heavy DPS pisses me off
if ( me->GetInjuryRate() > 300.0f )
{
me->BecomeEnraged( 5.0f );
}
}
return TryContinue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CEyeballBoss > CEyeballBossBehavior::OnKilled( CEyeballBoss *me, const CTakeDamageInfo &info )
{
// award achievement to everyone who injured me within the last few seconds
const float deathTime = 5.0f;
const CUtlVector< CEyeballBoss::AttackerInfo > &attackerVector = me->GetAttackerVector();
for( int i=0; i<attackerVector.Count(); ++i )
{
if ( attackerVector[i].m_attacker != NULL &&
gpGlobals->curtime - attackerVector[i].m_timestamp < deathTime )
{
if ( !me->IsSpell() )
{
IGameEvent *event = gameeventmanager->CreateEvent( "eyeball_boss_killer" );
if ( event )
{
event->SetInt( "level", me->GetLevel() );
event->SetInt( "player_entindex", attackerVector[i].m_attacker->entindex() );
gameeventmanager->FireEvent( event );
}
}
if ( TFGameRules() && TFGameRules()->IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_VIADUCT ) )
{
if ( !me->WasSpawnedByCheats() )
{
attackerVector[i].m_attacker->AwardAchievement( ACHIEVEMENT_TF_HALLOWEEN_EYEBOSS_KILL );
}
}
me->LogPlayerInteraction( "eyeball_killer", attackerVector[i].m_attacker );
}
}
CUtlVector< CTFPlayer * > playerVector;
CollectPlayers( &playerVector, TF_TEAM_RED );
CollectPlayers( &playerVector, TF_TEAM_BLUE, false, APPEND_PLAYERS );
UTIL_LogPrintf( "HALLOWEEN: eyeball_death (max_dps %3.2f) (max_health %d) (player_count %d) (level %d)\n", me->GetMaxInjuryRate(), me->GetMaxHealth(), playerVector.Count(), me->GetLevel() );
return TryChangeTo( new CEyeballBossDead, RESULT_CRITICAL, "I died!" );
}
//---------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------
ActionResult< CEyeballBoss > CEyeballBossDead::OnStart( CEyeballBoss *me, Action< CEyeballBoss > *priorAction )
{
int animSequence = me->LookupSequence( "death" );
if ( animSequence )
{
me->SetSequence( animSequence );
me->SetPlaybackRate( 1.0f );
me->SetCycle( 0 );
me->ResetSequenceInfo();
}
me->EmitSound( "Halloween.EyeballBossStunned" );
m_giveUpTimer.Start( 10.0f );
if ( tf_eyeball_boss_debug.GetBool() )
{
DevMsg( "Max Eyeball DPS taken = %3.2f\n", me->GetMaxInjuryRate() );
}
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CEyeballBoss > CEyeballBossDead::Update( CEyeballBoss *me, float interval )
{
float ground;
TheNavMesh->GetSimpleGroundHeight( me->WorldSpaceCenter(), &ground );
if ( m_giveUpTimer.IsElapsed() || ( me->WorldSpaceCenter().z - ground ) < 100.0f )
{
// we're on the ground - pop
DispatchParticleEffect( "eyeboss_death", me->GetAbsOrigin(), me->GetAbsAngles() );
me->EmitSound( "Cart.Explode" );
me->EmitSound( "Halloween.EyeballBossDie" );
UTIL_ScreenShake( me->GetAbsOrigin(), 25.0f, 5.0f, 5.0f, 1000.0f, SHAKE_START );
UTIL_Remove( me );
me->SetVictim( NULL );
if ( !me->IsSpell() )
{
IGameEvent *event = gameeventmanager->CreateEvent( "eyeball_boss_killed" );
if ( event )
{
event->SetInt( "level", me->GetLevel() );
gameeventmanager->FireEvent( event );
}
// next time, I'll be tougher!
me->GainLevel();
}
// coat nearby players with goo
const float gooRange = 750.0f;
me->JarateNearbyPlayers( gooRange );
// create vortex to loot
CTeleportVortex *vortex = (CTeleportVortex *)CBaseEntity::Create( "teleport_vortex", me->GetAbsOrigin(), vec3_angle );
if ( vortex )
{
vortex->SetupVortex( true );
}
}
return Continue();
}
@@ -0,0 +1,46 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// eyeball_boss_behavior.h
// The 2011 Halloween Boss
// Michael Booth, October 2011
#ifndef EYEBALL_BOSS_BEHAVIOR_H
#define EYEBALL_BOSS_BEHAVIOR_H
//---------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------
class CEyeballBossBehavior : public Action< CEyeballBoss >
{
public:
virtual Action< CEyeballBoss > *InitialContainedAction( CEyeballBoss *me );
virtual ActionResult< CEyeballBoss > OnStart( CEyeballBoss *me, Action< CEyeballBoss > *priorAction );
virtual ActionResult< CEyeballBoss > Update( CEyeballBoss *me, float interval );
virtual EventDesiredResult< CEyeballBoss > OnInjured( CEyeballBoss *me, const CTakeDamageInfo &info );
virtual EventDesiredResult< CEyeballBoss > OnKilled( CEyeballBoss *me, const CTakeDamageInfo &info );
virtual const char *GetName( void ) const { return "Behavior"; } // return name of this action
private:
CountdownTimer m_stunCooldownTimer;
};
//---------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------
class CEyeballBossDead : public Action< CEyeballBoss >
{
public:
virtual ActionResult< CEyeballBoss > OnStart( CEyeballBoss *me, Action< CEyeballBoss > *priorAction );
virtual ActionResult< CEyeballBoss > Update( CEyeballBoss *me, float interval );
virtual const char *GetName( void ) const { return "Dead"; } // return name of this action
private:
CountdownTimer m_giveUpTimer;
};
#endif // EYEBALL_BOSS_BEHAVIOR_H
@@ -0,0 +1,172 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// eyeball_boss_emerge.cpp
// The Halloween Boss emerging from the ground
// Michael Booth, October 2011
#include "cbase.h"
#include "tf_player.h"
#include "tf_gamerules.h"
#include "tf_team.h"
#include "nav_mesh/tf_nav_area.h"
#include "particle_parse.h"
#include "../eyeball_boss.h"
#include "eyeball_boss_emerge.h"
#include "eyeball_boss_idle.h"
//---------------------------------------------------------------------------------------------
ActionResult< CEyeballBoss > CEyeballBossEmerge::OnStart( CEyeballBoss *me, Action< CEyeballBoss > *priorAction )
{
if ( me->IsSpell() )
{
// just do teleport in code
me->RemoveEffects( EF_NOINTERP | EF_NODRAW );
DispatchParticleEffect( "eyeboss_tp_normal", me->GetAbsOrigin(), me->GetAbsAngles() );
int animSequence = me->LookupSequence( "teleport_in" );
if ( animSequence )
{
me->SetSequence( animSequence );
me->SetPlaybackRate( 1.0f );
me->SetCycle( 0 );
me->ResetSequenceInfo();
}
}
else
{
int animSequence = me->LookupSequence( "arrives" );
if ( animSequence )
{
me->SetSequence( animSequence );
me->SetPlaybackRate( 1.0f );
me->SetCycle( 0 );
me->ResetSequenceInfo();
}
DispatchParticleEffect( "halloween_boss_summon", me->GetAbsOrigin(), me->GetAbsAngles() );
m_groundPos = me->GetAbsOrigin();
m_riseTimer.Start( 3.0f );
m_emergePos = me->GetAbsOrigin() + Vector( 0, 0, 100.0f );
m_height = 150.0f;
me->SetAbsOrigin( m_emergePos + Vector( 0, 0, -m_height ) );
me->EmitSound( "Halloween.HeadlessBossSpawnRumble" );
IGameEvent *event = gameeventmanager->CreateEvent( "eyeball_boss_summoned" );
if ( event )
{
event->SetInt( "level", me->GetLevel() );
gameeventmanager->FireEvent( event );
}
m_killTimer.Start( 2.0f );
CUtlVector< CTFPlayer * > playerVector;
CollectPlayers( &playerVector, TF_TEAM_RED );
CollectPlayers( &playerVector, TF_TEAM_BLUE, false, APPEND_PLAYERS );
UTIL_LogPrintf( "HALLOWEEN: eyeball_spawn (max_health %d) (player_count %d) (level %d)\n", me->GetMaxHealth(), playerVector.Count(), me->GetLevel() );
}
return Continue();
}
//----------------------------------------------------------------------------------
ActionResult< CEyeballBoss > CEyeballBossEmerge::Update( CEyeballBoss *me, float interval )
{
if ( !m_riseTimer.IsElapsed() )
{
me->SetAbsOrigin( m_emergePos + Vector( 0, 0, -m_height * m_riseTimer.GetRemainingTime() / m_riseTimer.GetCountdownDuration() ) );
if ( m_rumbleTimer.IsElapsed() )
{
m_rumbleTimer.Start( 0.25f );
// shake nearby players' screens.
UTIL_ScreenShake( me->GetAbsOrigin(), 15.0f, 5.0f, 1.0f, 1000.0f, SHAKE_START );
}
}
if ( me->IsActivityFinished() )
{
return ChangeTo( new CEyeballBossIdle, "Here I am!" );
}
// don't do any kill players or remove pipe bomb code below if I'm a spell
if ( me->IsSpell() )
{
return Continue();
}
// push players away to avoid penetration issues
CUtlVector< CTFPlayer * > playerVector;
CollectPlayers( &playerVector, TF_TEAM_RED, COLLECT_ONLY_LIVING_PLAYERS );
CollectPlayers( &playerVector, TF_TEAM_BLUE, COLLECT_ONLY_LIVING_PLAYERS, APPEND_PLAYERS );
const float pushRange = 250.0f;
const float pushForce = 200.0f;
const float deathRange = me->GetLevel() > 1 ? 200.0f : 100.0f;
for( int i=0; i<playerVector.Count(); ++i )
{
CTFPlayer *player = playerVector[i];
Vector toPlayer = player->EyePosition() - m_groundPos;
float range = toPlayer.NormalizeInPlace();
if ( range < pushRange )
{
// make sure we push players up and away
toPlayer.z = 0.0f;
toPlayer.NormalizeInPlace();
toPlayer.z = 1.0f;
Vector push = pushForce * toPlayer;
player->RemoveFlag( FL_ONGROUND );
player->ApplyAbsVelocityImpulse( push );
}
// kill anyone touching the summon portal
if ( !m_killTimer.IsElapsed() )
{
if ( range < deathRange )
{
CTakeDamageInfo info( me, me, 2.0f * player->GetMaxHealth(), DMG_BLAST, TF_DMG_CUSTOM_PLASMA );
player->TakeDamage( info );
}
}
}
// leveled-up boss fizzles any grenades/etc near his summon portal
if ( !m_killTimer.IsElapsed() && me->GetLevel() > 1 )
{
Vector vecSize = Vector( 256, 256, 256 );
const int maxCollectedEntities = 1024;
CBaseEntity *pObjects[ maxCollectedEntities ];
int count = UTIL_EntitiesInBox( pObjects, maxCollectedEntities, m_groundPos - vecSize, m_groundPos + vecSize, FL_GRENADE );
for( int i = 0; i < count; ++i )
{
if ( pObjects[i] == NULL )
continue;
if ( pObjects[i]->IsPlayer() )
continue;
// Remove the enemy pipe
pObjects[i]->SetThink( &CBaseEntity::SUB_Remove );
pObjects[i]->SetNextThink( gpGlobals->curtime );
pObjects[i]->SetTouch( NULL );
pObjects[i]->AddEffects( EF_NODRAW );
}
}
return Continue();
}
@@ -0,0 +1,28 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// eyeball_boss_emerge.h
// The Halloween Boss emerging from the ground
// Michael Booth, October 2011
#ifndef EYEBALL_BOSS_EMERGE_H
#define EYEBALL_BOSS_EMERGE_H
//---------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------
class CEyeballBossEmerge : public Action< CEyeballBoss >
{
public:
virtual ActionResult< CEyeballBoss > OnStart( CEyeballBoss *me, Action< CEyeballBoss > *priorAction );
virtual ActionResult< CEyeballBoss > Update( CEyeballBoss *me, float interval );
virtual const char *GetName( void ) const { return "Emerge"; } // return name of this action
private:
CountdownTimer m_riseTimer;
CountdownTimer m_rumbleTimer;
CountdownTimer m_killTimer;
Vector m_emergePos;
Vector m_groundPos;
float m_height;
};
#endif // EYEBALL_BOSS_EMERGE_H
@@ -0,0 +1,56 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// eyeball_boss_emote.cpp
// The 2011 Halloween Boss - play an animation
// Michael Booth, October 2011
#include "cbase.h"
#include "../eyeball_boss.h"
#include "eyeball_boss_emote.h"
//---------------------------------------------------------------------------------------------
CEyeballBossEmote::CEyeballBossEmote( int animationSequence, const char *soundName, Action< CEyeballBoss > *nextAction )
{
m_animationSequence = animationSequence;
m_soundName = soundName;
m_nextAction = nextAction;
}
//---------------------------------------------------------------------------------------------
ActionResult< CEyeballBoss > CEyeballBossEmote::OnStart( CEyeballBoss *me, Action< CEyeballBoss > *priorAction )
{
if ( m_animationSequence )
{
me->SetSequence( m_animationSequence );
me->SetPlaybackRate( 1.0f );
me->SetCycle( 0 );
me->ResetSequenceInfo();
}
if ( m_soundName )
{
me->EmitSound( m_soundName );
}
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CEyeballBoss > CEyeballBossEmote::Update( CEyeballBoss *me, float interval )
{
if ( me->IsSequenceFinished() )
{
if ( m_nextAction )
{
return ChangeTo( m_nextAction );
}
return Done();
}
return Continue();
}
@@ -0,0 +1,28 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// eyeball_boss_emote.h
// The 2011 Halloween Boss - play an animation
// Michael Booth, October 2011
#ifndef EYEBALL_BOSS_EMOTE_H
#define EYEBALL_BOSS_EMOTE_H
//---------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------
class CEyeballBossEmote : public Action< CEyeballBoss >
{
public:
CEyeballBossEmote( int animationSequence, const char *soundName, Action< CEyeballBoss > *nextAction = NULL );
virtual ~CEyeballBossEmote() { }
virtual ActionResult< CEyeballBoss > OnStart( CEyeballBoss *me, Action< CEyeballBoss > *priorAction );
virtual ActionResult< CEyeballBoss > Update( CEyeballBoss *me, float interval );
virtual const char *GetName( void ) const { return "Emote"; } // return name of this action
private:
int m_animationSequence;
const char *m_soundName;
Action< CEyeballBoss > *m_nextAction;
};
#endif // EYEBALL_BOSS_EMOTE_H
@@ -0,0 +1,242 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// eyeball_boss_idle.cpp
// The 2011 Halloween Boss
// Michael Booth, October 2011
#include "cbase.h"
#include "particle_parse.h"
#include "tf_gamerules.h"
#include "../eyeball_boss.h"
#include "eyeball_boss_idle.h"
#include "eyeball_boss_teleport.h"
#include "eyeball_boss_notice.h"
#include "eyeball_boss_emote.h"
//---------------------------------------------------------------------------------------------
ActionResult< CEyeballBoss > CEyeballBossIdle::OnStart( CEyeballBoss *me, Action< CEyeballBoss > *priorAction )
{
m_attacker = NULL;
m_talkTimer.Start( RandomFloat( 3.0f, 5.0f ) );
m_lifeTimer.Start( me->IsSpell() ? tf_eyeball_boss_lifetime_spell.GetFloat() : tf_eyeball_boss_lifetime.GetFloat() );
m_moveTimer.Start( RandomFloat( 10.0f, 15.0f ) );
m_lastWarnTime = 0.0f;
m_isLaughReady = false;
m_lastHealth = me->GetHealth();
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CEyeballBoss > CEyeballBossIdle::Update( CEyeballBoss *me, float interval )
{
if ( tf_eyeball_boss_debug_orientation.GetBool() )
{
CBaseCombatCharacter *target = me->FindClosestVisibleVictim();
if ( target )
{
me->GetBodyInterface()->AimHeadTowards( target );
}
if ( me->GetInjuryRate() > 0.0001f )
{
DevMsg( "%3.2f: DPS = %3.2f, Max DPS = %3.2f\n", gpGlobals->curtime, me->GetInjuryRate(), me->GetMaxInjuryRate() );
}
return Continue();
}
if ( !me->IsSpell() )
{
if ( m_lifeTimer.GetRemainingTime() < 10.0f && m_lastWarnTime > 10.0f )
{
IGameEvent *event = gameeventmanager->CreateEvent( "eyeball_boss_escape_imminent" );
if ( event )
{
event->SetInt( "level", me->GetLevel() );
event->SetInt( "time_remaining", 10 );
gameeventmanager->FireEvent( event );
}
}
else if ( m_lifeTimer.GetRemainingTime() < 30.0f && m_lastWarnTime > 30.0f )
{
IGameEvent *event = gameeventmanager->CreateEvent( "eyeball_boss_escape_imminent" );
if ( event )
{
event->SetInt( "level", me->GetLevel() );
event->SetInt( "time_remaining", 30 );
gameeventmanager->FireEvent( event );
}
}
else if ( m_lifeTimer.GetRemainingTime() < 60.0f && m_lastWarnTime > 60.0f )
{
IGameEvent *event = gameeventmanager->CreateEvent( "eyeball_boss_escape_imminent" );
if ( event )
{
event->SetInt( "level", me->GetLevel() );
event->SetInt( "time_remaining", 60 );
gameeventmanager->FireEvent( event );
}
}
m_lastWarnTime = m_lifeTimer.GetRemainingTime();
}
// just leave
if ( me->IsSpell() && m_lifeTimer.IsElapsed() )
{
return ChangeTo( new CEyeballBossEscape, "Escaping..." );
}
// teleport to a new place
if ( m_moveTimer.IsElapsed() && !me->IsSpell() )
{
// get low
me->GetLocomotionInterface()->SetDesiredAltitude( 0.0f );
// only teleport if we're low enough for players to reach the vortex we leave behind
Vector ground = me->WorldSpaceCenter();
if ( TheNavMesh->GetSimpleGroundHeight( me->WorldSpaceCenter(), &ground.z ) )
{
const float maxTeleportHeight = 300.0f;
// make sure the ground is actually reachable by the players
CNavArea *area = TheNavMesh->GetNearestNavArea( ground, true, maxTeleportHeight * 1.5f );
if ( area )
{
if ( me->WorldSpaceCenter().z - ground.z < maxTeleportHeight )
{
if ( m_lifeTimer.IsElapsed() )
{
return ChangeTo( new CEyeballBossEscape, "Escaping..." );
}
m_moveTimer.Start( RandomFloat( 10.0f, 15.0f ) );
return SuspendFor( new CEyeballBossTeleport, "Moving..." );
}
}
}
}
else
{
// resume hovering
me->GetLocomotionInterface()->SetDesiredAltitude( tf_eyeball_boss_hover_height.GetFloat() );
}
CBaseCombatCharacter *victim = me->FindClosestVisibleVictim();
if ( victim )
{
me->SetVictim( victim );
return SuspendFor( new CEyeballBossNotice, "Target found..." );
}
if ( m_attacker != NULL )
{
// look at attacker that just injured us
me->GetBodyInterface()->AimHeadTowards( m_attacker );
m_attacker = NULL;
m_lookAroundTimer.Start( RandomFloat( 0.5f, 2.0f ) );
}
else
{
if ( m_lookAroundTimer.IsElapsed() )
{
// look around
m_lookAroundTimer.Start( RandomFloat( 2.0f, 4.0f ) );
Vector target;
SinCos( RandomFloat( -3.141592f, 3.141592f ), &target.y, &target.x );
target.z = 0.0f;
me->GetBodyInterface()->AimHeadTowards( me->GetAbsOrigin() + 100.0f * target );
}
// we have no target - if someone died recently, laugh
if ( m_isLaughReady )
{
m_isLaughReady = false;
return SuspendFor( new CEyeballBossEmote( me->LookupSequence( "laugh" ), "Halloween.EyeballBossLaugh" ), "Taunt our victim" );
}
}
int animSequence = 0;
if ( me->IsEnraged() )
{
animSequence = me->LookupSequence( "lookaround3" );
}
else if ( me->IsGrumpy() )
{
animSequence = me->LookupSequence( "lookaround2" );
}
else
{
animSequence = me->LookupSequence( "lookaround1" );
}
if ( animSequence )
{
if ( me->GetSequence() != animSequence || me->IsSequenceFinished() )
{
me->SetSequence( animSequence );
me->SetPlaybackRate( 1.0f );
me->SetCycle( 0 );
me->ResetSequenceInfo();
}
}
if ( m_talkTimer.IsElapsed() )
{
if ( me->IsEnraged() )
{
me->EmitSound( "Halloween.EyeballBossRage" );
m_talkTimer.Start( RandomFloat( 1.0f, 2.0f ) );
}
else
{
me->EmitSound( "Halloween.EyeballBossIdle" );
m_talkTimer.Start( RandomFloat( 3.0f, 5.0f ) );
}
}
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CEyeballBoss > CEyeballBossIdle::OnResume( CEyeballBoss *me, Action< CEyeballBoss > *interruptingAction )
{
m_talkTimer.Start( RandomFloat( 3.0f, 5.0f ) );
return Continue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CEyeballBoss > CEyeballBossIdle::OnInjured( CEyeballBoss *me, const CTakeDamageInfo &info )
{
m_attacker = info.GetAttacker();
return TryContinue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CEyeballBoss > CEyeballBossIdle::OnOtherKilled( CEyeballBoss *me, CBaseCombatCharacter *victim, const CTakeDamageInfo &info )
{
m_isLaughReady = true;
return TryContinue();
}
@@ -0,0 +1,37 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// eyeball_boss_idle.h
// The 2011 Halloween Boss
// Michael Booth, October 2011
#ifndef EYEBALL_BOSS_IDLE_H
#define EYEBALL_BOSS_IDLE_H
//---------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------
class CEyeballBossIdle : public Action< CEyeballBoss >
{
public:
virtual ActionResult< CEyeballBoss > OnStart( CEyeballBoss *me, Action< CEyeballBoss > *priorAction );
virtual ActionResult< CEyeballBoss > Update( CEyeballBoss *me, float interval );
virtual ActionResult< CEyeballBoss > OnResume( CEyeballBoss *me, Action< CEyeballBoss > *interruptingAction );
virtual EventDesiredResult< CEyeballBoss > OnInjured( CEyeballBoss *me, const CTakeDamageInfo &info );
virtual EventDesiredResult< CEyeballBoss > OnOtherKilled( CEyeballBoss *me, CBaseCombatCharacter *victim, const CTakeDamageInfo &info );
virtual const char *GetName( void ) const { return "Idle"; } // return name of this action
private:
CountdownTimer m_lookAroundTimer;
PathFollower m_path;
CountdownTimer m_repathTimer;
CHandle< CBaseEntity > m_attacker;
CountdownTimer m_talkTimer;
CountdownTimer m_lifeTimer;
CountdownTimer m_moveTimer;
float m_lastWarnTime;
int m_lastHealth;
bool m_isLaughReady;
};
#endif // EYEBALL_BOSS_IDLE_H
@@ -0,0 +1,182 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// eyeball_boss_launch_rockets.cpp
// The 2011 Halloween Boss
// Michael Booth, October 2011
#include "cbase.h"
#include "tf_projectile_rocket.h"
#include "tf_obj_sentrygun.h"
#include "../eyeball_boss.h"
#include "eyeball_boss_launch_rockets.h"
//---------------------------------------------------------------------------------------------
ActionResult< CEyeballBoss > CEyeballBossLaunchRockets::OnStart( CEyeballBoss *me, Action< CEyeballBoss > *priorAction )
{
if ( me->GetVictim() == NULL )
{
return Done( "No target" );
}
m_lastTargetPosition = me->GetVictim()->GetAbsOrigin();
const char *firingAnimation = NULL;
if ( me->IsEnraged() )
{
m_rocketsLeft = 3;
m_initialDelayTimer.Start( 0.25f );
firingAnimation = "firing3";
me->EmitSound( "Halloween.EyeballBossRage" );
}
else if ( me->IsGrumpy() )
{
m_rocketsLeft = 3;
m_initialDelayTimer.Start( 0.25f );
firingAnimation = "firing2";
}
else
{
m_rocketsLeft = 1;
m_initialDelayTimer.Start( 0.5f );
firingAnimation = "firing1";
}
int animSequence = me->LookupSequence( firingAnimation );
if ( animSequence )
{
me->SetSequence( animSequence );
me->SetPlaybackRate( 1.0f );
me->SetCycle( 0 );
me->ResetSequenceInfo();
}
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CEyeballBoss > CEyeballBossLaunchRockets::Update( CEyeballBoss *me, float interval )
{
if ( me->GetVictim() && me->GetVictim()->IsAlive() )
{
m_lastTargetPosition = me->GetVictim()->GetAbsOrigin();
// target upper part of sentry guns
if ( me->GetVictim()->IsBaseObject() )
{
CObjectSentrygun *sentry = dynamic_cast< CObjectSentrygun * >( const_cast< CBaseCombatCharacter * >( me->GetVictim() ) );
if ( sentry )
{
m_lastTargetPosition = sentry->EyePosition();
}
}
// circle around them
Vector forward, right;
me->GetVectors( &forward, &right, NULL );
me->GetLocomotionInterface()->SetDesiredSpeed( tf_eyeball_boss_speed.GetFloat() );
if ( !me->IsSpell() )
{
if ( me->GetVictim()->entindex() & 0x1 )
{
me->GetLocomotionInterface()->Approach( 100.0f * right + me->WorldSpaceCenter() );
}
else
{
me->GetLocomotionInterface()->Approach( -100.0f * right + me->WorldSpaceCenter() );
}
}
me->GetBodyInterface()->AimHeadTowards( me->GetVictim() );
}
else
{
me->GetBodyInterface()->AimHeadTowards( m_lastTargetPosition );
}
// fire rockets
if ( m_initialDelayTimer.IsElapsed() && m_launchTimer.IsElapsed() )
{
--m_rocketsLeft;
m_launchTimer.Start( 0.3f );
const float rocketDamage = 50.0f;
// if I'm enraged, shoot full speed rockets, otherwise shoot slow ones
float speedFactor = me->IsEnraged() ? 1.0f : 0.3f;
float rocketSpeed = 1100.0f * speedFactor;
Vector targetSpot = m_lastTargetPosition;
if ( me->IsEnraged() && me->GetVictim() )
{
// lead our target
float rangeBetween = me->GetRangeTo( targetSpot );
const float veryCloseRange = 150.0f;
if ( rangeBetween > veryCloseRange )
{
float timeToTravel = rangeBetween / rocketSpeed;
Vector leadOffset = timeToTravel * me->GetVictim()->GetAbsVelocity();
CTraceFilterNoNPCsOrPlayer filter( me, COLLISION_GROUP_NONE );
trace_t result;
UTIL_TraceLine( me->WorldSpaceCenter(), me->GetVictim()->GetAbsOrigin() + leadOffset, MASK_SOLID_BRUSHONLY, &filter, &result );
if ( result.DidHit() )
{
const float errorTolerance = 300.0f;
if ( ( result.endpos - targetSpot ).IsLengthGreaterThan( errorTolerance ) )
{
// rocket is going to hit an obstruction not near our victim - just aim right for them and hope for splash
leadOffset = vec3_origin;
}
}
targetSpot = me->GetVictim()->GetAbsOrigin() + leadOffset;
}
}
QAngle launchAngles;
Vector toTarget = targetSpot - me->WorldSpaceCenter();
VectorAngles( toTarget, launchAngles );
CBaseEntity *pScorer = me->GetOwnerEntity() ? me->GetOwnerEntity() : me;
CTFProjectile_Rocket *pRocket = CTFProjectile_Rocket::Create( me, me->WorldSpaceCenter(), launchAngles, me, pScorer );
if ( pRocket )
{
pRocket->SetModel( "models/props_halloween/eyeball_projectile.mdl" );
Vector forward;
AngleVectors( launchAngles, &forward, NULL, NULL );
Vector vecVelocity = forward * rocketSpeed;
pRocket->SetAbsVelocity( vecVelocity );
pRocket->SetupInitialTransmittedGrenadeVelocity( vecVelocity );
pRocket->EmitSound( "Weapon_RPG.SingleCrit" );
pRocket->SetDamage( rocketDamage );
pRocket->SetCritical( !me->IsSpell() );
pRocket->SetEyeBallRocket( !me->IsSpell() );
pRocket->SetSpell( me->IsSpell() );
pRocket->ChangeTeam( me->GetTeamNumber() );
}
if ( !m_rocketsLeft )
{
return Done();
}
}
return Continue();
}
@@ -0,0 +1,30 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// eyeball_boss_launch_rockets.h
// The 2011 Halloween Boss
// Michael Booth, October 2011
#ifndef EYEBALL_BOSS_LAUNCH_ROCKETS_H
#define EYEBALL_BOSS_LAUNCH_ROCKETS_H
//---------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------
class CEyeballBossLaunchRockets : public Action< CEyeballBoss >
{
public:
virtual ActionResult< CEyeballBoss > OnStart( CEyeballBoss *me, Action< CEyeballBoss > *priorAction );
virtual ActionResult< CEyeballBoss > Update( CEyeballBoss *me, float interval );
virtual const char *GetName( void ) const { return "LaunchRockets"; } // return name of this action
private:
CountdownTimer m_initialDelayTimer;
CountdownTimer m_launchTimer;
int m_rocketsLeft;
Vector m_lastTargetPosition;
};
#endif // EYEBALL_BOSS_LAUNCH_ROCKETS_H
@@ -0,0 +1,43 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// eyeball_boss_notice.cpp
// The 2011 Halloween Boss
// Michael Booth, October 2011
#include "cbase.h"
#include "../eyeball_boss.h"
#include "eyeball_boss_notice.h"
#include "eyeball_boss_approach_target.h"
//---------------------------------------------------------------------------------------------
ActionResult< CEyeballBoss > CEyeballBossNotice::OnStart( CEyeballBoss *me, Action< CEyeballBoss > *priorAction )
{
m_timer.Start( 0.25f );
me->EmitSound( "Halloween.EyeballBossBecomeAlert" );
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CEyeballBoss > CEyeballBossNotice::Update( CEyeballBoss *me, float interval )
{
CBaseEntity *victim = me->GetVictim();
if ( !victim )
{
return Done( "Victim gone" );
}
me->GetBodyInterface()->AimHeadTowards( victim );
if ( m_timer.IsElapsed() )
{
return ChangeTo( new CEyeballBossApproachTarget, "Chasing victim" );
}
return Continue();
}
@@ -0,0 +1,23 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// eyeball_boss_notice.h
// The 2011 Halloween Boss
// Michael Booth, October 2011
#ifndef EYEBALL_BOSS_NOTICE_H
#define EYEBALL_BOSS_NOTICE_H
//---------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------
class CEyeballBossNotice : public Action< CEyeballBoss >
{
public:
virtual ActionResult< CEyeballBoss > OnStart( CEyeballBoss *me, Action< CEyeballBoss > *priorAction );
virtual ActionResult< CEyeballBoss > Update( CEyeballBoss *me, float interval );
virtual const char *GetName( void ) const { return "Notice"; } // return name of this action
private:
CountdownTimer m_timer;
};
#endif // EYEBALL_BOSS_NOTICE_H
@@ -0,0 +1,60 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// eyeball_boss_stunned.cpp
// The 2011 Halloween Boss
// Michael Booth, October 2011
#include "cbase.h"
#include "../eyeball_boss.h"
#include "eyeball_boss_stunned.h"
//---------------------------------------------------------------------------------------------
ActionResult< CEyeballBoss > CEyeballBossStunned::OnStart( CEyeballBoss *me, Action< CEyeballBoss > *priorAction )
{
m_stunTimer.Start( 5.0f );
int animSequence = me->LookupSequence( "stunned" );
if ( animSequence )
{
me->SetSequence( animSequence );
me->SetPlaybackRate( 1.0f );
me->SetCycle( 0 );
me->ResetSequenceInfo();
}
me->EmitSound( "Halloween.EyeballBossStunned" );
// limit the total amount of damage we can take while stunned
me->SetDamageLimit( me->GetMaxHealth() / 3 );
// sink
me->GetLocomotionInterface()->SetDesiredAltitude( 0.0f );
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CEyeballBoss > CEyeballBossStunned::Update( CEyeballBoss *me, float interval )
{
if ( m_stunTimer.IsElapsed() )
{
// get mad and retaliate
me->BecomeEnraged( 20.0f );
return Done();
}
return Continue();
}
//---------------------------------------------------------------------------------------------
void CEyeballBossStunned::OnEnd( CEyeballBoss *me, Action< CEyeballBoss > *nextAction )
{
me->RemoveDamageLimit();
// resume hovering
me->GetLocomotionInterface()->SetDesiredAltitude( tf_eyeball_boss_hover_height.GetFloat() );
}
@@ -0,0 +1,31 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// eyeball_boss_stunned.h
// The 2011 Halloween Boss
// Michael Booth, October 2011
#ifndef EYEBALL_BOSS_STUNNED_H
#define EYEBALL_BOSS_STUNNED_H
//---------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------
class CEyeballBossStunned : public Action< CEyeballBoss >
{
public:
virtual ActionResult< CEyeballBoss > OnStart( CEyeballBoss *me, Action< CEyeballBoss > *priorAction );
virtual ActionResult< CEyeballBoss > Update( CEyeballBoss *me, float interval );
virtual void OnEnd( CEyeballBoss *me, Action< CEyeballBoss > *nextAction );
virtual EventDesiredResult< CEyeballBoss > OnInjured( CEyeballBoss *me, const CTakeDamageInfo &info )
{
// don't get stunned while stunned
return TryToSustain( RESULT_CRITICAL );
}
virtual const char *GetName( void ) const { return "Stunned"; } // return name of this action
private:
CountdownTimer m_stunTimer;
float m_spinRate;
};
#endif // EYEBALL_BOSS_STUNNED_H
@@ -0,0 +1,148 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// eyeball_boss_teleport.cpp
// The 2011 Halloween Boss
// Michael Booth, October 2011
#include "cbase.h"
#include "particle_parse.h"
#include "tf/halloween/eyeball_boss/teleport_vortex.h"
#include "../eyeball_boss.h"
#include "eyeball_boss_teleport.h"
#include "player_vs_environment/monster_resource.h"
//---------------------------------------------------------------------------------------------
ActionResult< CEyeballBoss > CEyeballBossTeleport::OnStart( CEyeballBoss *me, Action< CEyeballBoss > *priorAction )
{
m_state = TELEPORTING_OUT;
int animSequence = me->LookupSequence( "teleport_out" );
if ( animSequence )
{
me->SetSequence( animSequence );
me->SetPlaybackRate( 1.0f );
me->SetCycle( 0 );
me->ResetSequenceInfo();
}
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CEyeballBoss > CEyeballBossTeleport::Update( CEyeballBoss *me, float interval )
{
if ( me->IsSequenceFinished() )
{
switch( m_state )
{
case TELEPORTING_OUT:
{
CTeleportVortex *vortex = (CTeleportVortex *)CBaseEntity::Create( "teleport_vortex", me->GetAbsOrigin(), vec3_angle );
if ( vortex )
{
vortex->SetupVortex( false );
}
DispatchParticleEffect( "eyeboss_tp_normal", me->GetAbsOrigin(), me->GetAbsAngles() );
me->EmitSound( "Halloween.EyeballBossTeleport" );
me->AddEffects( EF_NOINTERP | EF_NODRAW );
me->SetAbsOrigin( me->PickNewSpawnSpot() + Vector( 0, 0, 75.0f ) );
// wait on the other side for a moment
m_state = TELEPORTING_IN;
}
break;
case TELEPORTING_IN:
{
me->RemoveEffects( EF_NOINTERP | EF_NODRAW );
DispatchParticleEffect( "eyeboss_tp_normal", me->GetAbsOrigin(), me->GetAbsAngles() );
int animSequence = me->LookupSequence( "teleport_in" );
if ( animSequence )
{
me->SetSequence( animSequence );
me->SetPlaybackRate( 1.0f );
me->SetCycle( 0 );
me->ResetSequenceInfo();
}
m_state = DONE;
}
break;
case DONE:
return Done();
}
}
return Continue();
}
//---------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------
ActionResult< CEyeballBoss > CEyeballBossEscape::OnStart( CEyeballBoss *me, Action< CEyeballBoss > *priorAction )
{
int animSequence = me->LookupSequence( "escape" );
if ( animSequence )
{
me->SetSequence( animSequence );
me->SetPlaybackRate( 1.0f );
me->SetCycle( 0 );
me->ResetSequenceInfo();
}
me->EmitSound( "Halloween.EyeballBossLaugh" );
UTIL_LogPrintf( "HALLOWEEN: eyeball_escaped (max_dps %3.2f) (health %d) (level %d)\n", me->GetMaxInjuryRate(), me->GetHealth(), me->GetLevel() );
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CEyeballBoss > CEyeballBossEscape::Update( CEyeballBoss *me, float interval )
{
if ( me->IsSequenceFinished() )
{
if ( me->IsSpell() )
{
me->EmitSound( "Halloween.spell_spawn_boss_disappear" );
}
DispatchParticleEffect( "eyeboss_tp_escape", me->GetAbsOrigin(), me->GetAbsAngles() );
if ( g_pMonsterResource )
{
g_pMonsterResource->HideBossHealthMeter();
}
UTIL_Remove( me );
me->SetVictim( NULL );
if ( !me->IsSpell() )
{
IGameEvent *event = gameeventmanager->CreateEvent( "eyeball_boss_escaped" );
if ( event )
{
event->SetInt( "level", me->GetLevel() );
gameeventmanager->FireEvent( event );
}
}
// reset back to normal level
me->ResetLevel();
return Done();
}
return Continue();
}
@@ -0,0 +1,45 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// eyeball_boss_teleport.h
// The 2011 Halloween Boss
// Michael Booth, October 2011
#ifndef EYEBALL_BOSS_TELEPORT_H
#define EYEBALL_BOSS_TELEPORT_H
//---------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------
class CEyeballBossTeleport : public Action< CEyeballBoss >
{
public:
virtual ActionResult< CEyeballBoss > OnStart( CEyeballBoss *me, Action< CEyeballBoss > *priorAction );
virtual ActionResult< CEyeballBoss > Update( CEyeballBoss *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;
};
//---------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------
class CEyeballBossEscape : public Action< CEyeballBoss >
{
public:
virtual ActionResult< CEyeballBoss > OnStart( CEyeballBoss *me, Action< CEyeballBoss > *priorAction );
virtual ActionResult< CEyeballBoss > Update( CEyeballBoss *me, float interval );
virtual const char *GetName( void ) const { return "Escape"; } // return name of this action
private:
CountdownTimer m_timer;
};
#endif // EYEBALL_BOSS_TELEPORT_H
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,338 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// eyeball_boss.h
// The 2011 Halloween Boss
// Michael Booth, October 2011
#ifndef EYEBALL_BOSS_H
#define EYEBALL_BOSS_H
#include "NextBot.h"
#include "NextBotBehavior.h"
#include "Path/NextBotPathFollow.h"
#include "bot_npc/bot_npc_body.h"
#include "../halloween_base_boss.h"
#define EYEBALL_RADIUS 100.0f
#define EYEBALL_NORMAL_SKIN 0
#define EYEBALL_RED_SKIN 1
#define EYEBALL_TEAM_RED 2
#define EYEBALL_TEAM_BLUE 3
#define PURGATORY_Z -1152
#define EYEBALL_ANGRY 2
#define EYEBALL_GRUMPY 1
#define EYEBALL_CALM 0
extern ConVar tf_eyeball_boss_debug;
extern ConVar tf_eyeball_boss_debug_orientation;
extern ConVar tf_eyeball_boss_lifetime;
extern ConVar tf_eyeball_boss_lifetime_spell;
extern ConVar tf_eyeball_boss_speed;
extern ConVar tf_eyeball_boss_hover_height;
extern ConVar tf_eyeball_boss_acceleration;
extern ConVar tf_eyeball_boss_horiz_damping;
extern ConVar tf_eyeball_boss_vert_damping;
extern ConVar tf_eyeball_boss_attack_range;
extern ConVar tf_eyeball_boss_health_base;
extern ConVar tf_eyeball_boss_health_per_player;
extern ConVar tf_halloween_bot_min_player_count;
//----------------------------------------------------------------------------
class CEyeballBossBody : public CBotNPCBody
{
public:
CEyeballBossBody( INextBot *bot );
virtual ~CEyeballBossBody() { }
virtual void Update( void );
virtual void AimHeadTowards( const Vector &lookAtPos,
LookAtPriorityType priority = BORING,
float duration = 0.0f,
INextBotReply *replyWhenAimed = NULL,
const char *reason = NULL ); // aim the bot's head towards the given goal
virtual void AimHeadTowards( CBaseEntity *subject,
LookAtPriorityType priority = BORING,
float duration = 0.0f,
INextBotReply *replyWhenAimed = NULL,
const char *reason = NULL ); // continually aim the bot's head towards the given subject
virtual float GetMaxHeadAngularVelocity( void ) const // return max turn rate of head in degrees/second
{
return 3000.0f;
}
private:
int m_leftRightPoseParameter;
int m_upDownPoseParameter;
Vector m_lookAtSpot;
};
//----------------------------------------------------------------------------
// Bypass vision system
class CDisableVision : public IVision
{
public:
CDisableVision( INextBot *bot ) : IVision( bot ) { }
virtual ~CDisableVision() { }
virtual void Reset( void ) { }
virtual void Update( void ) { }
};
//----------------------------------------------------------------------------
class CEyeballBossLocomotion : public ILocomotion
{
public:
CEyeballBossLocomotion( INextBot *bot );
virtual ~CEyeballBossLocomotion();
virtual void Reset( void ); // (EXTEND) reset to initial state
virtual void Update( void ); // (EXTEND) update internal state
virtual void Approach( const Vector &goalPos, float goalWeight = 1.0f ); // (EXTEND) move directly towards the given position
virtual void SetDesiredSpeed( float speed ); // set desired speed for locomotor movement
virtual float GetDesiredSpeed( void ) const; // returns the current desired speed
virtual float GetStepHeight( void ) const; // if delta Z is greater than this, we have to jump to get up
virtual float GetMaxJumpHeight( void ) const; // return maximum height of a jump
virtual float GetDeathDropHeight( void ) const; // distance at which we will die if we fall
virtual void SetDesiredAltitude( float height ); // how high above our Approach goal do we float?
virtual float GetDesiredAltitude( void ) const;
virtual const Vector &GetGroundNormal( void ) const; // surface normal of the ground we are in contact with
virtual const Vector &GetVelocity( void ) const; // return current world space velocity
void SetVelocity( const Vector &velocity );
virtual void FaceTowards( const Vector &target ); // rotate body to face towards "target"
// return position of "feet" - the driving point where the bot contacts the ground
// for this floating boss, "feet" refers to the ground directly underneath him
virtual const Vector &GetFeet( void ) const;
protected:
float m_desiredSpeed;
float m_currentSpeed;
Vector m_forward;
float m_desiredAltitude;
void MaintainAltitude( void );
Vector m_velocity;
Vector m_acceleration;
};
inline float CEyeballBossLocomotion::GetStepHeight( void ) const
{
return 50.0f;
}
inline float CEyeballBossLocomotion::GetMaxJumpHeight( void ) const
{
return 100.0f;
}
inline float CEyeballBossLocomotion::GetDeathDropHeight( void ) const
{
return 999.9f;
}
inline const Vector &CEyeballBossLocomotion::GetGroundNormal( void ) const
{
static Vector up( 0, 0, 1.0f );
return up;
}
inline const Vector &CEyeballBossLocomotion::GetVelocity( void ) const
{
return m_velocity;
}
inline void CEyeballBossLocomotion::SetVelocity( const Vector &velocity )
{
m_velocity = velocity;
}
DECLARE_AUTO_LIST( IEyeballBossAutoList );
//----------------------------------------------------------------------------
class CEyeballBoss : public CHalloweenBaseBoss, public IEyeballBossAutoList
{
public:
DECLARE_CLASS( CEyeballBoss, CHalloweenBaseBoss );
DECLARE_SERVERCLASS();
CEyeballBoss();
virtual ~CEyeballBoss();
static void PrecacheEyeballBoss();
virtual void Precache();
virtual void Spawn( void );
virtual void UpdateOnRemove( void );
virtual int OnTakeDamage_Alive( const CTakeDamageInfo &info );
virtual void UpdateLastKnownArea( void ); // invoke this to update our last known nav area (since there is no think method chained to CBaseCombatCharacter)
// INextBot
DECLARE_INTENTION_INTERFACE( CEyeballBoss );
virtual CEyeballBossLocomotion *GetLocomotionInterface( void ) const { return m_locomotor; }
virtual CEyeballBossBody *GetBodyInterface( void ) const { return m_body; }
virtual CDisableVision *GetVisionInterface( void ) const { return m_vision; }
virtual void Update( void );
virtual Vector EyePosition( void );
virtual bool ShouldCollide( int collisionGroup, int contentsMask ) const;
virtual float GetCritInjuryMultiplier( void ) const; // when we are hit by a crit, damage is mutiplied by this
const Vector &GetHomePosition( void ) const;
void BecomeEnraged( float duration );
bool IsEnraged( void ) const;
bool IsGrumpy( void ) const;
void SetLookAtTarget( const Vector &spot );
void SetVictim( CBaseCombatCharacter *victim );
CBaseCombatCharacter *GetVictim( void ) const;
bool IsInPurgatory( CBaseEntity *entity ) const;
CBaseCombatCharacter *FindClosestVisibleVictim( void );
const Vector &PickNewSpawnSpot( void ) const;
void JarateNearbyPlayers( float range );
void SetDamageLimit( int limit );
void RemoveDamageLimit( void );
void LogPlayerInteraction( const char *verb, CTFPlayer *player );
void GainLevel( void );
void ResetLevel( void );
virtual int GetLevel( void ) const OVERRIDE;
virtual HalloweenBossType GetBossType() const { return HALLOWEEN_BOSS_MONOCULUS; }
private:
CEyeballBossLocomotion *m_locomotor;
CEyeballBossBody *m_body;
CDisableVision *m_vision;
Vector m_eyeOffset;
Vector m_homePos;
CTFPlayer *m_target;
CountdownTimer m_invulnTimer;
CNetworkVector( m_lookAtSpot );
CNetworkVar( int, m_attitude );
CountdownTimer m_rageTimer;
CHandle< CBaseCombatCharacter > m_victim;
CUtlVector< CHandle< CBaseEntity > > m_spawnSpotVector;
int m_damageLimit;
static int m_level;
};
inline int CEyeballBoss::GetLevel( void ) const
{
return m_level;
}
inline void CEyeballBoss::GainLevel( void )
{
++m_level;
}
inline void CEyeballBoss::ResetLevel( void )
{
m_level = 1;
}
inline void CEyeballBoss::SetDamageLimit( int limit )
{
m_damageLimit = limit;
}
inline void CEyeballBoss::RemoveDamageLimit( void )
{
m_damageLimit = -1;
}
inline float CEyeballBoss::GetCritInjuryMultiplier( void ) const
{
return 2.0f;
}
inline bool CEyeballBoss::IsInPurgatory( CBaseEntity *entity ) const
{
if ( IsSpell() )
return false;
return ( entity->GetAbsOrigin().z < PURGATORY_Z );
}
inline void CEyeballBoss::SetVictim( CBaseCombatCharacter *victim )
{
m_victim = victim;
}
inline bool CEyeballBoss::IsEnraged( void ) const
{
// always enrage if I'm a spell
if ( IsSpell() )
return true;
// being near death always makes me mad
if ( GetHealth() < GetMaxHealth()/3 )
return true;
return m_rageTimer.HasStarted() && !m_rageTimer.IsElapsed();
}
inline bool CEyeballBoss::IsGrumpy( void ) const
{
if ( IsEnraged() )
return false;
return ( GetHealth() < 2*GetMaxHealth()/3 );
}
inline const Vector &CEyeballBoss::GetHomePosition( void ) const
{
return m_homePos;
}
inline Vector CEyeballBoss::EyePosition( void )
{
return GetAbsOrigin() + m_eyeOffset;
}
inline void CEyeballBoss::SetLookAtTarget( const Vector &spot )
{
m_lookAtSpot = spot;
}
#endif // EYEBALL_BOSS_H
+331
View File
@@ -0,0 +1,331 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// ghost.cpp
// A spooky halloween ghost bot
// Michael Booth, October 2011
#include "cbase.h"
#include "tf_player.h"
#include "tf_gamerules.h"
#include "tf_team.h"
#include "tf_projectile_arrow.h"
#include "tf_weapon_grenade_pipebomb.h"
#include "nav_mesh/tf_nav_area.h"
#include "ghost.h"
#include "NextBot/Path/NextBotChasePath.h"
#include "econ_wearable.h"
#include "team_control_point_master.h"
#include "particle_parse.h"
#include "CRagdollMagnet.h"
#include "NextBot/Behavior/BehaviorMoveTo.h"
void CC_GhostSpawn( const CCommand& args )
{
MDLCACHE_CRITICAL_SECTION();
CBaseEntity *entity = CreateEntityByName( "ghost" );
if ( entity )
{
entity->Precache();
DispatchSpawn( entity );
// Now attempt to drop into the world
CBasePlayer* pPlayer = UTIL_GetCommandClient();
trace_t tr;
Vector forward;
pPlayer->EyeVectors( &forward );
UTIL_TraceLine(pPlayer->EyePosition(),
pPlayer->EyePosition() + forward * MAX_TRACE_LENGTH,MASK_SOLID,
pPlayer, COLLISION_GROUP_NONE, &tr );
if ( tr.fraction != 1.0 )
{
// Raise the end position a little up off the floor, place the npc and drop him down
tr.endpos.z += 12;
entity->Teleport( &tr.endpos, NULL, NULL );
}
}
}
static ConCommand ghost_spawn( "ghost_spawn", CC_GhostSpawn, "Spawns a Ghost where the player is looking.", FCVAR_GAMEDLL | FCVAR_CHEAT );
//-----------------------------------------------------------------------------------------------------
CGhost *SpawnGhost( const Vector &spot, const QAngle &angles, float lifetime )
{
CGhost *ghost = (CGhost *)CreateEntityByName( "ghost" );
if ( ghost )
{
DispatchSpawn( ghost );
ghost->SetAbsOrigin( spot );
ghost->SetLocalAngles( angles );
ghost->SetLifetime( lifetime );
return ghost;
}
return NULL;
}
//-----------------------------------------------------------------------------------------------------
// The Ghost
//-----------------------------------------------------------------------------------------------------
LINK_ENTITY_TO_CLASS( ghost, CGhost );
//-----------------------------------------------------------------------------------------------------
CGhost::CGhost()
{
ALLOCATE_INTENTION_INTERFACE( CGhost );
m_locomotor = new CGhostLocomotion( this );
m_eyeOffset = vec3_origin;
m_lifetime = 10.0f;
}
//-----------------------------------------------------------------------------------------------------
CGhost::~CGhost()
{
DEALLOCATE_INTENTION_INTERFACE;
if ( m_locomotor )
delete m_locomotor;
}
//-----------------------------------------------------------------------------------------------------
void CGhost::PrecacheGhost()
{
PrecacheModel( "models/props_halloween/ghost_no_hat.mdl" );
PrecacheParticleSystem( "ghost_appearation" );
PrecacheScriptSound( "Halloween.GhostMoan" );
PrecacheScriptSound( "Halloween.GhostBoo" );
PrecacheScriptSound( "Halloween.Haunted" );
}
//-----------------------------------------------------------------------------------------------------
void CGhost::Precache()
{
BaseClass::Precache();
// always allow late precaching, so we don't pay the cost of the
// Halloween Ghost for the entire year
bool bAllowPrecache = CBaseEntity::IsPrecacheAllowed();
CBaseEntity::SetAllowPrecache( true );
PrecacheGhost();
CBaseEntity::SetAllowPrecache( bAllowPrecache );
}
//-----------------------------------------------------------------------------------------------------
void CGhost::Spawn( void )
{
Precache();
BaseClass::Spawn();
SetCollisionGroup( COLLISION_GROUP_NONE );
SetSolid( SOLID_NONE );
AddSolidFlags( FSOLID_NOT_SOLID );
SetModel( "models/props_halloween/ghost_no_hat.mdl" );
}
//---------------------------------------------------------------------------------------------
bool CGhost::ShouldCollide( int collisionGroup, int contentsMask ) const
{
return false;
//return BaseClass::ShouldCollide( collisionGroup, contentsMask );
}
//---------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------
class CGhostBehavior : public Action< CGhost >
{
public:
//---------------------------------------------------------------------------------------------
virtual ActionResult< CGhost > OnStart( CGhost *me, Action< CGhost > *priorAction )
{
m_lifeTimer.Start();
m_stuckAnchor = me->GetAbsOrigin();
m_stuckTimer.Start( 1.0f );
me->GetVectors( &m_forward, NULL, NULL );
DispatchParticleEffect( "ghost_appearation", me->WorldSpaceCenter(), me->GetAbsAngles() );
return Continue();
}
//---------------------------------------------------------------------------------------------
virtual ActionResult< CGhost > Update( CGhost *me, float interval )
{
if ( m_lifeTimer.IsGreaterThen( me->GetLifetime() ) || m_stuckTimer.IsElapsed() )
{
DispatchParticleEffect( "ghost_appearation", me->WorldSpaceCenter(), me->GetAbsAngles() );
me->EmitSound( "Halloween.Haunted" );
UTIL_Remove( me );
return Done();
}
if ( m_moanTimer.IsElapsed() )
{
me->EmitSound( "Halloween.GhostMoan" );
m_moanTimer.Start( RandomFloat( 5.0f, 7.0f ) );
}
DriftAroundAndAvoidObstacles( me );
ScareNearbyPlayers( me );
return Continue();
}
//---------------------------------------------------------------------------------------------
void DriftAroundAndAvoidObstacles( CGhost *me )
{
const float feelerRange = 150.0f;
Vector left( -m_forward.y, m_forward.x, 0.0f );
Vector right( m_forward.y, -m_forward.x, 0.0f );
CTraceFilterNoNPCsOrPlayer traceFilter( me, COLLISION_GROUP_NONE );
trace_t resultLeft;
UTIL_TraceLine( me->WorldSpaceCenter(), me->WorldSpaceCenter() + feelerRange * ( m_forward + left ), MASK_PLAYERSOLID, &traceFilter, &resultLeft );
//NDebugOverlay::Line( me->WorldSpaceCenter(), me->WorldSpaceCenter() + feelerRange * ( m_forward + left ), 0, 0, 255, true, interval );
trace_t resultRight;
UTIL_TraceLine( me->WorldSpaceCenter(), me->WorldSpaceCenter() + feelerRange * ( m_forward + right ), MASK_PLAYERSOLID, &traceFilter, &resultRight );
//NDebugOverlay::Line( me->WorldSpaceCenter(), me->WorldSpaceCenter() + feelerRange * ( m_forward + right ), 255, 0, 0, true, interval );
const float turnRate = 0.2f;
if ( resultLeft.DidHit() )
{
if ( resultRight.DidHit() )
{
// both sides hit
if ( resultLeft.fraction < resultRight.fraction )
{
// left hit closer - turn right
m_forward += turnRate * right;
}
else
{
// right hit closer - turn left
m_forward += turnRate * left;
}
}
else
{
// left hit - turn right
m_forward += turnRate * right;
}
}
else if ( resultRight.DidHit() )
{
// right hit - turn left
m_forward += turnRate * left;
}
m_forward.NormalizeInPlace();
Vector goal = 100.0f * m_forward + me->GetAbsOrigin();
me->GetLocomotionInterface()->Approach( goal );
me->GetLocomotionInterface()->FaceTowards( goal );
me->GetLocomotionInterface()->Run();
if ( me->IsRangeGreaterThan( m_stuckAnchor, 50.0f ) )
{
m_stuckAnchor = me->GetAbsOrigin();
m_stuckTimer.Reset();
}
}
//---------------------------------------------------------------------------------------------
void ScareNearbyPlayers( CGhost *me )
{
if ( m_scareTimer.IsElapsed() )
{
m_scareTimer.Start( 1.0f );
CUtlVector< CTFPlayer * > playerVector;
CollectPlayers( &playerVector, TF_TEAM_RED, COLLECT_ONLY_LIVING_PLAYERS );
CollectPlayers( &playerVector, TF_TEAM_BLUE, COLLECT_ONLY_LIVING_PLAYERS, APPEND_PLAYERS );
for( int i=0; i<playerVector.Count(); ++i )
{
CTFPlayer *victim = playerVector[i];
if ( victim && !victim->HasPurgatoryBuff() )
{
if ( me->IsRangeLessThan( victim, GHOST_SCARE_RADIUS ) )
{
if ( me->IsLineOfSightClear( victim ) )
{
// scare them!
const float scareTime = 2.0f;
const float speedReduction = 0.0f;
// "stun by trigger" results in the Halloween "yikes" effects
int stunFlags = TF_STUN_LOSER_STATE | TF_STUN_BY_TRIGGER;
victim->m_Shared.StunPlayer( scareTime, speedReduction, stunFlags, NULL );
}
}
}
}
}
}
virtual const char *GetName( void ) const { return "Behavior"; } // return name of this action
private:
IntervalTimer m_lifeTimer;
CountdownTimer m_moanTimer;
CountdownTimer m_scareTimer;
Vector m_forward;
Vector m_stuckAnchor;
CountdownTimer m_stuckTimer;
};
IMPLEMENT_INTENTION_INTERFACE( CGhost, CGhostBehavior );
//---------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------
// Get maximum running speed
float CGhostLocomotion::GetRunSpeed( void ) const
{
return 90.0f;
}
//---------------------------------------------------------------------------------------------
// Return maximum acceleration of locomotor
float CGhostLocomotion::GetMaxAcceleration( void ) const
{
return 500.0f;
}
//---------------------------------------------------------------------------------------------
// Return maximum deceleration of locomotor
float CGhostLocomotion::GetMaxDeceleration( void ) const
{
return 500.0f;
}
+86
View File
@@ -0,0 +1,86 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// ghost.h
// A spooky halloween ghost bot
// Michael Booth, October 2011
#ifndef GHOST_H
#define GHOST_H
#include "NextBot.h"
#include "NextBotBehavior.h"
#include "NextBotGroundLocomotion.h"
#include "Path/NextBotPathFollow.h"
#define GHOST_SPEED 90
#define GHOST_SCARE_RADIUS 192
class CTFPlayer;
//----------------------------------------------------------------------------
class CGhostLocomotion : public NextBotGroundLocomotion
{
public:
DECLARE_CLASS( CGhostLocomotion, NextBotGroundLocomotion );
CGhostLocomotion( INextBot *bot ) : NextBotGroundLocomotion( bot ) { }
virtual ~CGhostLocomotion() { }
virtual float GetRunSpeed( void ) const; // get maximum running speed
virtual float GetMaxAcceleration( void ) const; // return maximum acceleration of locomotor
virtual float GetMaxDeceleration( void ) const; // return maximum deceleration of locomotor
};
//----------------------------------------------------------------------------
class CGhost : public NextBotCombatCharacter
{
public:
DECLARE_CLASS( CGhost, NextBotCombatCharacter );
CGhost();
virtual ~CGhost();
static void PrecacheGhost();
virtual void Precache();
virtual void Spawn( void );
void SetLifetime( float duration );
float GetLifetime( void ) const;
// INextBot
DECLARE_INTENTION_INTERFACE( CGhost );
virtual CGhostLocomotion *GetLocomotionInterface( void ) const { return m_locomotor; }
virtual Vector EyePosition( void );
virtual bool ShouldCollide( int collisionGroup, int contentsMask ) const;
private:
CGhostLocomotion *m_locomotor;
Vector m_eyeOffset;
Vector m_homePos;
float m_lifetime;
};
inline void CGhost::SetLifetime( float duration )
{
m_lifetime = duration;
}
inline float CGhost::GetLifetime( void ) const
{
return m_lifetime;
}
inline Vector CGhost::EyePosition( void )
{
return GetAbsOrigin() + m_eyeOffset;
}
extern CGhost *SpawnGhost( const Vector &spot, const QAngle &angles, float lifetime = 10.0f );
#endif // GHOST_H
@@ -0,0 +1,313 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// halloween_boss_base.cpp
// Shared code for the Halloween Bosses
// Michael Booth, October 2011
#include "cbase.h"
#include "tf_player.h"
#include "tf_gamerules.h"
#include "halloween_base_boss.h"
#include "tf_gamestats.h"
//-----------------------------------------------------------------------------------------------------
CHalloweenBaseBoss::CHalloweenBaseBoss()
{
m_wasSpawnedByCheats = false;
}
//-----------------------------------------------------------------------------------------------------
CHalloweenBaseBoss::~CHalloweenBaseBoss()
{
}
//-----------------------------------------------------------------------------------------------------
void CHalloweenBaseBoss::Spawn( void )
{
BaseClass::Spawn();
ConVarRef sv_cheats( "sv_cheats" );
if ( sv_cheats.IsValid() && sv_cheats.GetBool() )
{
// remember we spawned with a cheat command
m_wasSpawnedByCheats = true;
}
m_damagePerSecond = 0.0f;
m_maxDamagePerSecond = 0.0f;
if ( TFGameRules() )
{
TFGameRules()->AddActiveBoss( this );
}
// track how many players were playing when boss spawned
CUtlVector< CTFPlayer * > playerVector;
CollectPlayers( &playerVector, TF_TEAM_RED );
CollectPlayers( &playerVector, TF_TEAM_BLUE, false, APPEND_PLAYERS );
// status
CTF_GameStats.Event_HalloweenBossEvent( GetBossType(), GetLevel(), HALLOWEEN_EVENT_BOSS_SPAWN, playerVector.Count(), 0.0f );
}
//-----------------------------------------------------------------------------------------------------
void CHalloweenBaseBoss::Update( void )
{
BaseClass::Update();
UpdateDamagePerSecond();
}
//-----------------------------------------------------------------------------------------------------
void CHalloweenBaseBoss::UpdateOnRemove()
{
if ( TFGameRules() )
{
TFGameRules()->RemoveActiveBoss( this );
}
BaseClass::UpdateOnRemove();
}
//-----------------------------------------------------------------------------------------------------
int CHalloweenBaseBoss::OnTakeDamage( const CTakeDamageInfo &info )
{
if ( info.GetDamage() && ( info.GetAttacker() != this ) )
{
CTFPlayer *pAttacker = ToTFPlayer( info.GetAttacker() );
if ( pAttacker && info.GetWeapon() )
{
CTFWeaponBase *pWeapon = dynamic_cast<CTFWeaponBase *>( info.GetWeapon() );
if ( pWeapon )
{
pWeapon->ApplyOnHitAttributes( this, pAttacker, info );
}
}
}
return BaseClass::OnTakeDamage( info );
}
//-----------------------------------------------------------------------------------------------------
int CHalloweenBaseBoss::OnTakeDamage_Alive( const CTakeDamageInfo &rawInfo )
{
CTakeDamageInfo info = rawInfo;
if ( info.GetAttacker() && info.GetAttacker()->GetTeamNumber() == GetTeamNumber() )
{
return 0;
}
if ( TFGameRules()->RoundHasBeenWon() )
{
info.SetDamage( 0.0f );
}
if ( info.GetDamageType() & DMG_CRITICAL )
{
// do the critical damage increase
info.SetDamage( info.GetDamage() * GetCritInjuryMultiplier() );
}
// keep a list of everyone who hurt me, and when
CTFPlayer *playerAttacker = ToTFPlayer( info.GetAttacker() );
if ( playerAttacker )
{
CTFWeaponBase *attackerWeapon = assert_cast< CTFWeaponBase * >( info.GetWeapon() );
bool isMeleeAttack = attackerWeapon && attackerWeapon->IsMeleeWeapon();
RememberAttacker( playerAttacker, isMeleeAttack, info.GetDamage() );
for( int i=0; i<playerAttacker->m_Shared.GetNumHealers(); ++i )
{
CTFPlayer *medic = ToTFPlayer( playerAttacker->m_Shared.GetHealerByIndex( i ) );
if ( medic )
{
// medics healing my attacker are also considered attackers
// they do zero damage to keep DPS calculations sane
RememberAttacker( medic, isMeleeAttack, 0.0f );
}
}
}
// fire event for client combat text, beep, etc.
IGameEvent *event = gameeventmanager->CreateEvent( "npc_hurt" );
if ( event )
{
event->SetInt( "entindex", entindex() );
event->SetInt( "health", MAX( 0, GetHealth() ) );
event->SetInt( "damageamount", info.GetDamage() );
event->SetBool( "crit", ( info.GetDamageType() & DMG_CRITICAL ) ? true : false );
event->SetInt( "boss", GetBossType() );
CTFPlayer *attackerPlayer = ToTFPlayer( info.GetAttacker() );
if ( attackerPlayer )
{
event->SetInt( "attacker_player", attackerPlayer->GetUserID() );
if ( attackerPlayer->GetActiveTFWeapon() )
{
event->SetInt( "weaponid", attackerPlayer->GetActiveTFWeapon()->GetWeaponID() );
}
else
{
event->SetInt( "weaponid", 0 );
}
}
else
{
// hurt by world
event->SetInt( "attacker_player", 0 );
event->SetInt( "weaponid", 0 );
}
gameeventmanager->FireEvent( event );
}
return BaseClass::OnTakeDamage_Alive( info );
}
void CHalloweenBaseBoss::Event_Killed( const CTakeDamageInfo &info )
{
const CUtlVector< AttackerInfo > &attackerVector = GetAttackerVector();
for( int i=0; i<attackerVector.Count(); ++i )
{
if ( attackerVector[i].m_attacker != NULL )
{
IGameEvent *pEvent = gameeventmanager->CreateEvent( "halloween_boss_killed" );
if ( pEvent )
{
pEvent->SetInt( "boss", GetBossType() );
pEvent->SetInt( "killer", attackerVector[i].m_attacker->GetUserID() );
gameeventmanager->FireEvent( pEvent, true );
}
// Shoot a huge soul at players that have damaged the boss
TFGameRules()->DropHalloweenSoulPack( 25, EyePosition(), attackerVector[i].m_attacker, TEAM_SPECTATOR );
}
}
BaseClass::Event_Killed( info );
}
//---------------------------------------------------------------------------------------------
void CHalloweenBaseBoss::RememberAttacker( CTFPlayer *playerAttacker, bool wasMeleeHit, float damage )
{
// record the damage for dps calculations
DamageRateInfo info;
info.m_damage = damage;
info.m_timestamp = gpGlobals->curtime;
m_damageVector.AddToTail( info );
int i;
// has this player hurt me before
for( i=0; i<m_attackerVector.Count(); ++i )
{
if ( m_attackerVector[i].m_attacker && m_attackerVector[i].m_attacker->entindex() == playerAttacker->entindex() )
{
// this player is hurting me again
m_attackerVector[i].m_timestamp = gpGlobals->curtime;
m_attackerVector[i].m_wasLastHitFromMeleeWeapon = wasMeleeHit;
return;
}
}
// new attacker
AttackerInfo attackerInfo;
attackerInfo.m_attacker = playerAttacker;
attackerInfo.m_timestamp = gpGlobals->curtime;
attackerInfo.m_wasLastHitFromMeleeWeapon = wasMeleeHit;
m_attackerVector.AddToTail( attackerInfo );
}
//---------------------------------------------------------------------------------------------
void CHalloweenBaseBoss::UpdateDamagePerSecond( void )
{
const float windowDuration = 5.0f;
int i;
m_damagePerSecond = 0.0f;
for( i=0; i<m_damageVector.Count(); ++i )
{
float age = gpGlobals->curtime - m_damageVector[i].m_timestamp;
if ( age > windowDuration )
{
// too old
continue;
}
m_damagePerSecond += m_damageVector[i].m_damage;
}
m_damagePerSecond /= windowDuration;
if ( m_damagePerSecond > m_maxDamagePerSecond )
{
m_maxDamagePerSecond = m_damagePerSecond;
}
// if ( m_damagePerSecond > 0.0001f )
// {
// DevMsg( "%3.2f: dps = %3.2f\n", gpGlobals->curtime, m_damagePerSecond );
// }
}
//---------------------------------------------------------------------------------------------
void CHalloweenBaseBoss::Break( void )
{
CPVSFilter filter( GetAbsOrigin() );
UserMessageBegin( filter, "BreakModel" );
WRITE_SHORT( GetModelIndex() );
WRITE_VEC3COORD( GetAbsOrigin() );
WRITE_ANGLES( GetAbsAngles() );
WRITE_SHORT( m_nSkin );
MessageEnd();
}
//---------------------------------------------------------------------------------------------
/*static*/ CHalloweenBaseBoss* CHalloweenBaseBoss::SpawnBossAtPos( HalloweenBossType bossType, const Vector& vSpawnPos, int nTeam /*= TF_TEAM_HALLOWEEN*/, CBaseEntity* pOwner /*= NULL*/ )
{
const char* pszBossType = NULL;
switch ( bossType )
{
case HALLOWEEN_BOSS_HHH:
pszBossType = "headless_hatman";
break;
case HALLOWEEN_BOSS_MONOCULUS:
pszBossType = "eyeball_boss";
break;
case HALLOWEEN_BOSS_MERASMUS:
pszBossType = "merasmus";
break;
default:
AssertMsg( 0, "Invalid Halloween Boss Type" );
}
CHalloweenBaseBoss *pBoss = NULL;
if ( pszBossType )
{
pBoss = dynamic_cast< CHalloweenBaseBoss * >( CreateEntityByName( pszBossType ) );
if ( pBoss )
{
pBoss->SetAbsOrigin( vSpawnPos + Vector( 0, 0, 10.0f ) );
pBoss->ChangeTeam( nTeam );
pBoss->SetOwnerEntity( pOwner );
DispatchSpawn( pBoss );
}
}
return pBoss;
}
@@ -0,0 +1,109 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// halloween_boss_base.h
// Shared code for the Halloween Bosses
// Michael Booth, October 2011
#ifndef HALLOWEEN_BOSS_BASE_H
#define HALLOWEEN_BOSS_BASE_H
#include "tf_shareddefs.h"
#include "NextBot.h"
#include "NextBotBehavior.h"
#include "NextBotGroundLocomotion.h"
#include "headless_hatman_body.h"
#include "Path/NextBotPathFollow.h"
class CTFPlayer;
//----------------------------------------------------------------------------
class CHalloweenBaseBoss : public NextBotCombatCharacter
{
public:
DECLARE_CLASS( CHalloweenBaseBoss, NextBotCombatCharacter );
CHalloweenBaseBoss();
virtual ~CHalloweenBaseBoss();
virtual void Spawn( void );
virtual int OnTakeDamage( const CTakeDamageInfo &rawInfo ) OVERRIDE;
virtual int OnTakeDamage_Alive( const CTakeDamageInfo &info );
virtual void Event_Killed( const CTakeDamageInfo &info ) OVERRIDE;
virtual void UpdateOnRemove();
virtual void Update( void );
void Break( void ); // bust into gibs
struct AttackerInfo
{
CHandle< CTFPlayer > m_attacker;
float m_timestamp;
bool m_wasLastHitFromMeleeWeapon;
};
const CUtlVector< AttackerInfo > &GetAttackerVector( void ) const;
void RememberAttacker( CTFPlayer *player, bool wasMeleeHit, float damage );
bool WasSpawnedByCheats( void ) const;
virtual float GetCritInjuryMultiplier( void ) const; // when we are hit by a crit, damage is mutiplied by this
float GetInjuryRate( void ) const; // return average recent damage taken per second
float GetMaxInjuryRate( void ) const; // return maximum damage taken per second
virtual int GetLevel() const { return 0; }
virtual HalloweenBossType GetBossType() const { return HALLOWEEN_BOSS_INVALID; }
static CHalloweenBaseBoss* SpawnBossAtPos( HalloweenBossType bossType, const Vector& vSpawnPos, int nTeam = TF_TEAM_HALLOWEEN, CBaseEntity* pOwner = NULL );
bool IsSpell() const { return GetTeamNumber() != TF_TEAM_HALLOWEEN; }
enum HalloweenStatsEventType
{
HALLOWEEN_EVENT_BOSS_SPAWN = 0,
};
private:
CUtlVector< AttackerInfo > m_attackerVector; // list of everyone who injured me, and when
void UpdateDamagePerSecond( void );
struct DamageRateInfo
{
float m_timestamp;
float m_damage;
};
CUtlVector< DamageRateInfo > m_damageVector;
float m_damagePerSecond;
float m_maxDamagePerSecond;
bool m_wasSpawnedByCheats;
};
inline float CHalloweenBaseBoss::GetInjuryRate( void ) const
{
return m_damagePerSecond;
}
inline float CHalloweenBaseBoss::GetMaxInjuryRate( void ) const
{
return m_maxDamagePerSecond;
}
inline float CHalloweenBaseBoss::GetCritInjuryMultiplier( void ) const
{
return TF_DAMAGE_CRIT_MULTIPLIER;
}
inline bool CHalloweenBaseBoss::WasSpawnedByCheats( void ) const
{
return m_wasSpawnedByCheats;
}
inline const CUtlVector< CHalloweenBaseBoss::AttackerInfo > &CHalloweenBaseBoss::GetAttackerVector( void ) const
{
return m_attackerVector;
}
#endif // HALLOWEEN_BOSS_BASE_H
@@ -0,0 +1,269 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// crybaby_boss_attack.cpp
// Halloween Boss 2011 chase and attack behavior
// Michael Booth, October 2011
#include "cbase.h"
#include "tf_player.h"
#include "tf_gamerules.h"
#include "tf_team.h"
#include "nav_mesh/tf_nav_area.h"
#include "particle_parse.h"
#include "team_control_point_master.h"
#include "../headless_hatman.h"
#include "crybaby_boss_attack.h"
//----------------------------------------------------------------------------------
CCryBabyBossAttack::CCryBabyBossAttack( CTFPlayer *victim )
{
m_victim = victim;
}
//----------------------------------------------------------------------------------
ActionResult< CHeadlessHatman > CCryBabyBossAttack::OnStart( CHeadlessHatman *me, Action< CHeadlessHatman > *priorAction )
{
// start animation
me->GetBodyInterface()->StartActivity( ACT_MP_RUN_ITEM1 );
m_axeSwingTimer.Invalidate();
m_attackTimer.Invalidate();
m_attackTarget = NULL;
m_attackTargetFocusTimer.Invalidate();
return Continue();
}
//----------------------------------------------------------------------------------
bool CCryBabyBossAttack::IsSwingingAxe( void ) const
{
return !m_axeSwingTimer.IsElapsed();
}
//----------------------------------------------------------------------------------
void CCryBabyBossAttack::UpdateAxeSwing( CHeadlessHatman *me )
{
if ( m_axeSwingTimer.HasStarted() )
{
// continue axe swing
if ( m_axeSwingTimer.IsElapsed() )
{
// moment of impact - did axe swing hit?
m_axeSwingTimer.Invalidate();
if ( m_attackTarget != NULL )
{
Vector forward;
me->GetVectors( &forward, NULL, NULL );
Vector toVictim = m_attackTarget->WorldSpaceCenter() - me->WorldSpaceCenter();
toVictim.NormalizeInPlace();
// looser tolerance as victim gets closer
const float closeRange = 100.0f;
float range = me->GetRangeTo( m_attackTarget );
float closeness = ( range < closeRange ) ? 0.0f : ( range - closeRange ) / ( tf_halloween_bot_attack_range.GetFloat() - closeRange );
float hitAngle = 0.0f + closeness * 0.27f;
if ( DotProduct( forward, toVictim ) > hitAngle )
{
if ( me->IsRangeLessThan( m_attackTarget, 0.9f * tf_halloween_bot_attack_range.GetFloat() ) )
{
if ( me->IsLineOfSightClear( m_attackTarget ) )
{
// CHOP!
CTakeDamageInfo info( me, me, 2.0f * m_attackTarget->GetHealth(), DMG_CLUB, TF_DMG_CUSTOM_DECAPITATION_BOSS );
CalculateMeleeDamageForce( &info, toVictim, me->WorldSpaceCenter(), 5.0f );
m_attackTarget->TakeDamage( info );
me->EmitSound( "Halloween.HeadlessBossAxeHitFlesh" );
}
}
}
}
// always playe the axe-hit-world impact sound, since it carries through the world better
me->EmitSound( "Halloween.HeadlessBossAxeHitWorld" );
UTIL_ScreenShake( me->GetAbsOrigin(), 15.0f, 5.0f, 1.0f, 1000.0f, SHAKE_START );
Vector bladePos;
QAngle bladeAngle;
if ( me->GetAxe() && me->GetAxe()->GetAttachment( "axe_blade", bladePos, bladeAngle ) )
{
DispatchParticleEffect( "halloween_boss_axe_hit_world", bladePos, bladeAngle );
}
}
}
}
//----------------------------------------------------------------------------------
// Validate that our victim is still alive and someplace we can actually get to
bool CCryBabyBossAttack::IsVictimChaseable( CHeadlessHatman *me )
{
if ( m_victim == NULL )
{
return false;
}
if ( !m_victim->IsAlive() )
{
return false;
}
CTFNavArea *victimArea = (CTFNavArea *)m_victim->GetLastKnownArea();
if ( !victimArea || victimArea->HasAttributeTF( TF_NAV_SPAWN_ROOM_BLUE | TF_NAV_SPAWN_ROOM_RED ) )
{
// unreachable
return false;
}
if ( m_victim->GetGroundEntity() != NULL )
{
Vector victimAreaPos;
victimArea->GetClosestPointOnArea( m_victim->GetAbsOrigin(), &victimAreaPos );
if ( ( m_victim->GetAbsOrigin() - victimAreaPos ).AsVector2D().IsLengthGreaterThan( 50.0f ) )
{
// off the mesh and unreachable
return false;
}
}
return true;
}
//----------------------------------------------------------------------------------
ActionResult< CHeadlessHatman > CCryBabyBossAttack::Update( CHeadlessHatman *me, float interval )
{
if ( !me->IsAlive() )
{
return Done();
}
if ( !IsVictimChaseable( me ) )
{
return Done( "Victim is dead or unreachable" );
}
if ( !IsSwingingAxe() && m_laughTimer.IsElapsed() )
{
me->EmitSound( "Halloween.HeadlessBossLaugh" );
m_laughTimer.Start( RandomFloat( 3.0f, 5.0f ) );
}
// chase after our chase victim
const float standAndSwingRange = 100.0f;
if ( me->IsRangeGreaterThan( m_victim, standAndSwingRange ) || !me->IsLineOfSightClear( m_victim ) )
{
CHeadlessHatmanPathCost cost( me );
m_chasePath.Update( me, m_victim, cost );
}
if ( m_attackTargetFocusTimer.IsElapsed() || m_attackTarget == NULL )
{
m_attackTarget = m_victim.Get();
}
// swing our axe at our attack target if they are in range
if ( m_attackTarget != NULL && m_attackTarget->IsAlive() )
{
if ( me->IsRangeLessThan( m_attackTarget, tf_halloween_bot_attack_range.GetFloat() ) )
{
if ( m_attackTimer.IsElapsed() )
{
if ( !IsSwingingAxe() )
{
me->AddGesture( ACT_MP_ATTACK_STAND_ITEM1 );
m_axeSwingTimer.Start( 0.58f );
me->EmitSound( "Halloween.HeadlessBossAttack" );
m_attackTimer.Start( 1.0f );
}
}
me->GetLocomotionInterface()->FaceTowards( m_attackTarget->WorldSpaceCenter() );
}
}
// finish axe swing once it has begun
UpdateAxeSwing( me );
if ( me->GetLocomotionInterface()->IsAttemptingToMove() )
{
// play running animation
int healthRatio = 100 * me->GetHealth() / me->GetMaxHealth();
if ( healthRatio > 50 )
{
if ( !me->GetBodyInterface()->IsActivity( ACT_MP_RUN_ITEM1 ) )
{
me->GetBodyInterface()->StartActivity( ACT_MP_RUN_ITEM1 );
}
}
else
{
if ( !me->GetBodyInterface()->IsActivity( ACT_MP_RUN_MELEE ) )
{
me->GetBodyInterface()->StartActivity( ACT_MP_RUN_MELEE );
}
}
if ( m_footfallTimer.IsElapsed() )
{
m_footfallTimer.Start( 0.25f );
// periodically shake nearby players' screens when we're moving
UTIL_ScreenShake( me->GetAbsOrigin(), 15.0f, 5.0f, 1.0f, 1000.0f, SHAKE_START );
}
}
else
{
// standing still
if ( !me->GetBodyInterface()->IsActivity( ACT_MP_STAND_ITEM1 ) )
{
me->GetBodyInterface()->StartActivity( ACT_MP_STAND_ITEM1 );
}
}
return Continue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CHeadlessHatman > CCryBabyBossAttack::OnStuck( CHeadlessHatman *me )
{
// we're stuck - just warp to the our next path goal
if ( m_chasePath.GetCurrentGoal() )
{
me->SetAbsOrigin( m_chasePath.GetCurrentGoal()->pos + Vector( 0, 0, 10.0f ) );
}
return TryContinue( RESULT_TRY );
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CHeadlessHatman > CCryBabyBossAttack::OnContact( CHeadlessHatman *me, CBaseEntity *other, CGameTrace *result )
{
if ( other )
{
CBaseCombatCharacter *combatChar = dynamic_cast< CBaseCombatCharacter * >( other );
if ( combatChar && combatChar->IsAlive() )
{
// force attack the thing we bumped into
// this prevents us from being stuck on dispensers, for example
m_attackTarget = combatChar;
m_attackTargetFocusTimer.Start( 3.0f );
}
}
return TryContinue( RESULT_TRY );
}
@@ -0,0 +1,47 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// crybaby_boss_attack.h
// Halloween Boss 2011 chase and attack behavior
// Michael Booth, October 2011
#ifndef CRYBABY_BOSS_ATTACK_H
#define CRYBABY_BOSS_ATTACK_H
#include "NextBot/Path/NextBotChasePath.h"
//---------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------
class CCryBabyBossAttack : public Action< CHeadlessHatman >
{
public:
CCryBabyBossAttack( CTFPlayer *victim );
virtual ~CCryBabyBossAttack() { }
virtual ActionResult< CHeadlessHatman > OnStart( CHeadlessHatman *me, Action< CHeadlessHatman > *priorAction );
virtual ActionResult< CHeadlessHatman > Update( CHeadlessHatman *me, float interval );
virtual EventDesiredResult< CHeadlessHatman > OnStuck( CHeadlessHatman *me );
virtual EventDesiredResult< CHeadlessHatman > OnContact( CHeadlessHatman *me, CBaseEntity *other, CGameTrace *result = NULL );
virtual const char *GetName( void ) const { return "CryBabyBossAttack"; } // return name of this action
private:
ChasePath m_chasePath;
CHandle< CTFPlayer > m_victim;
CountdownTimer m_axeSwingTimer;
CountdownTimer m_attackTimer;
CountdownTimer m_laughTimer;
CountdownTimer m_footfallTimer;
void UpdateAxeSwing( CHeadlessHatman *me );
bool IsSwingingAxe( void ) const;
bool IsVictimChaseable( CHeadlessHatman *me );
CHandle< CBaseCombatCharacter > m_attackTarget; // the victim I'm momentarily attacking
CountdownTimer m_attackTargetFocusTimer;
};
#endif // CRYBABY_BOSS_ATTACK_H
@@ -0,0 +1,132 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// crybaby_boss_escape.cpp
// Crybaby Boss runs to the pit in Mann Manor to escape
// Michael Booth, October 2011
#include "cbase.h"
#include "nav_mesh.h"
#include "tf_player.h"
#include "../headless_hatman.h"
#include "crybaby_boss_escape.h"
#include "crybaby_boss_attack.h"
extern ConVar tf_bot_path_lookahead_range;
//---------------------------------------------------------------------------------------------
ActionResult< CHeadlessHatman > CCryBabyBossEscape::OnStart( CHeadlessHatman *me, Action< CHeadlessHatman > *priorAction )
{
m_path.SetMinLookAheadDistance( tf_bot_path_lookahead_range.GetFloat() );
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CHeadlessHatman > CCryBabyBossEscape::Update( CHeadlessHatman *me, float interval )
{
// any nearby player that taunts me, enrages me and provokes me to attack them!
CUtlVector< CTFPlayer * > playerVector;
CollectPlayers( &playerVector, TEAM_ANY, COLLECT_ONLY_LIVING_PLAYERS );
for( int i=0; i<playerVector.Count(); ++i )
{
if ( playerVector[i]->m_Shared.InCond( TF_COND_TAUNTING ) )
{
const float noticeTauntRange = 750.0f;
if ( me->IsRangeLessThan( playerVector[i], noticeTauntRange ) )
{
if ( playerVector[i]->IsLookingTowards( me ) )
{
if ( me->IsLineOfSightClear( playerVector[i] ) )
{
return SuspendFor( new CCryBabyBossAttack( playerVector[i] ), "DON'T LAUGH AT ME!!!" );
}
}
}
}
}
// push players away to avoid penetration issues
const float pushRange = 250.0f;
const float pushForce = 200.0f;
for( int i=0; i<playerVector.Count(); ++i )
{
CTFPlayer *player = playerVector[i];
Vector toPlayer = player->EyePosition() - me->GetAbsOrigin();
float range = toPlayer.NormalizeInPlace();
if ( range < pushRange )
{
// make sure we push players up and away
toPlayer.z = 0.0f;
toPlayer.NormalizeInPlace();
toPlayer.z = 1.0f;
Vector push = pushForce * toPlayer;
player->ApplyAbsVelocityImpulse( push );
}
}
// run to the pit and escape
if ( m_repathTimer.IsElapsed() )
{
m_repathTimer.Start( RandomFloat( 1.0f, 2.0f ) );
CHeadlessHatmanPathCost cost( me );
// hack for now
CNavArea *goalArea = TheNavMesh->GetNavAreaByID( 27 );
if ( !goalArea )
{
return Done( "No goal area!" );
}
m_path.Compute( me, goalArea->GetCenter(), cost );
}
m_path.Update( me );
// animation state
if ( me->GetLocomotionInterface()->IsAttemptingToMove() )
{
// play running animation
if ( !me->GetBodyInterface()->IsActivity( ACT_MP_RUN_MELEE ) )
{
me->GetBodyInterface()->StartActivity( ACT_MP_RUN_MELEE );
}
if ( m_footfallTimer.IsElapsed() )
{
m_footfallTimer.Start( 0.25f );
// periodically shake nearby players' screens when we're moving
UTIL_ScreenShake( me->GetAbsOrigin(), 15.0f, 5.0f, 1.0f, 1000.0f, SHAKE_START );
}
}
else
{
// standing still
if ( !me->GetBodyInterface()->IsActivity( ACT_MP_STAND_ITEM1 ) )
{
me->GetBodyInterface()->StartActivity( ACT_MP_STAND_ITEM1 );
}
}
return Continue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CHeadlessHatman > CCryBabyBossEscape::OnMoveToSuccess( CHeadlessHatman *me, const Path *path )
{
UTIL_Remove( me );
return TryDone( RESULT_CRITICAL, "Reached escape pit" );
}
@@ -0,0 +1,28 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// crybaby_boss_escape.h
// Crybaby Boss runs to the pit in Mann Manor to escape
// Michael Booth, October 2011
#ifndef CRYBABY_ESCAPE_H
#define CRYBABY_ESCAPE_H
#include "Path/NextBotPathFollow.h"
class CCryBabyBossEscape : public Action< CHeadlessHatman >
{
public:
virtual ActionResult< CHeadlessHatman > OnStart( CHeadlessHatman *me, Action< CHeadlessHatman > *priorAction );
virtual ActionResult< CHeadlessHatman > Update( CHeadlessHatman *me, float interval );
virtual EventDesiredResult< CHeadlessHatman > OnMoveToSuccess( CHeadlessHatman *me, const Path *path );
virtual const char *GetName( void ) const { return "CryBabyBossEscape"; };
private:
PathFollower m_path;
CountdownTimer m_repathTimer;
CountdownTimer m_footfallTimer;
};
#endif // CRYBABY_ESCAPE_H
@@ -0,0 +1,543 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// headless_hatman_attack.cpp
// Halloween Boss chase and attack behavior
// Michael Booth, October 2010
#include "cbase.h"
#include "tf_player.h"
#include "tf_gamerules.h"
#include "tf_team.h"
#include "nav_mesh/tf_nav_area.h"
#include "particle_parse.h"
#include "team_control_point_master.h"
#include "../headless_hatman.h"
#include "headless_hatman_attack.h"
#include "headless_hatman_terrify.h"
ConVar tf_halloween_hhh_attack_kart_radius( "tf_halloween_hhh_attack_kart_radius", "300", FCVAR_CHEAT );
//----------------------------------------------------------------------------------
ActionResult< CHeadlessHatman > CHeadlessHatmanAttack::OnStart( CHeadlessHatman *me, Action< CHeadlessHatman > *priorAction )
{
// smooth out the bot's path following by moving toward a point farther down the path
m_path.SetMinLookAheadDistance( 100.0f );
// start animation
me->GetBodyInterface()->StartActivity( ACT_MP_RUN_ITEM1 );
m_axeSwingTimer.Invalidate();
m_attackTimer.Invalidate();
m_closestVisible = NULL;
TFGameRules()->SetIT( NULL );
m_lastIT = NULL;
m_chaseVictimTimer.Invalidate();
m_attackTarget = NULL;
m_attackTargetFocusTimer.Invalidate();
m_scareTimer.Start( 20.0f );
m_homePos = me->GetAbsOrigin();
m_homePosRecalcTimer.Start( 3.0f );
return Continue();
}
//----------------------------------------------------------------------------------
bool CHeadlessHatmanAttack::IsSwingingAxe( void ) const
{
return !m_axeSwingTimer.IsElapsed();
}
//----------------------------------------------------------------------------------
bool CHeadlessHatmanAttack::IsPotentiallyChaseable( CHeadlessHatman *me, CTFPlayer *victim )
{
if ( !victim )
{
return false;
}
if ( !victim->IsAlive() )
{
// victim is dead - pick a new one
return false;
}
if ( victim->m_Shared.InCond( TF_COND_HALLOWEEN_GHOST_MODE ) )
{
// don't attack ghost
return false;
}
CTFNavArea *victimArea = (CTFNavArea *)victim->GetLastKnownArea();
if ( !victimArea || victimArea->HasAttributeTF( TF_NAV_SPAWN_ROOM_BLUE | TF_NAV_SPAWN_ROOM_RED ) )
{
// unreachable - pick a new victim
return false;
}
if ( victim->GetGroundEntity() != NULL )
{
Vector victimAreaPos;
victimArea->GetClosestPointOnArea( victim->GetAbsOrigin(), &victimAreaPos );
if ( ( victim->GetAbsOrigin() - victimAreaPos ).AsVector2D().IsLengthGreaterThan( 50.0f ) )
{
// off the mesh and unreachable - pick a new victim
return false;
}
}
if ( victim->m_Shared.IsInvulnerable() )
{
// invulnerable - pick a new victim
return false;
}
Vector toHome = m_homePos - victim->GetAbsOrigin();
if ( !victim->m_Shared.InCond( TF_COND_HALLOWEEN_KART ) )
{
if ( toHome.IsLengthGreaterThan( tf_halloween_bot_quit_range.GetFloat() ) )
{
// too far from home - pick a new victim
return false;
}
}
return true;
}
//----------------------------------------------------------------------------------
void CHeadlessHatmanAttack::ValidateChaseVictim( CHeadlessHatman *me )
{
CTFPlayer *victim = ToTFPlayer( TFGameRules()->GetIT() );
if ( victim && m_lastIT != victim )
{
// something has changed our victim
m_chaseVictimTimer.Start( tf_halloween_bot_chase_duration.GetFloat() );
m_lastIT = victim;
}
if ( !IsPotentiallyChaseable( me, victim ) )
{
// we can no longer reach this victim
TFGameRules()->SetIT( NULL );
}
}
//----------------------------------------------------------------------------------
void CHeadlessHatmanAttack::SelectVictim( CHeadlessHatman *me )
{
ValidateChaseVictim( me );
m_closestVisible = ToTFPlayer( TFGameRules()->GetIT() );
if ( TFGameRules()->GetIT() == NULL )
{
// pick a new victim to chase
CTFPlayer *newVictim = NULL;
CUtlVector< CTFPlayer * > playerVector;
// collect everyone
CollectPlayers( &playerVector, TF_TEAM_RED, COLLECT_ONLY_LIVING_PLAYERS );
CollectPlayers( &playerVector, TF_TEAM_BLUE, COLLECT_ONLY_LIVING_PLAYERS, APPEND_PLAYERS );
float victimRangeSq = FLT_MAX;
float visibleVictimRangeSq = FLT_MAX;
for( int i=0; i<playerVector.Count(); ++i )
{
if ( !IsPotentiallyChaseable( me, playerVector[i] ) )
{
continue;
}
float rangeSq = me->GetRangeSquaredTo( playerVector[i] );
if ( rangeSq < visibleVictimRangeSq )
{
if ( me->IsLineOfSightClear( playerVector[i] ) )
{
// track closest visible victim so we can look at players out of attack range
m_closestVisible = playerVector[i];
visibleVictimRangeSq = rangeSq;
}
}
// check distance to home - don't pick victims too far away from home
if ( !playerVector[i]->m_Shared.InCond( TF_COND_HALLOWEEN_KART ) )
{
Vector toHome = m_homePos - playerVector[i]->GetAbsOrigin();
if ( toHome.IsLengthGreaterThan( tf_halloween_bot_chase_range.GetFloat() ) )
{
continue;
}
}
if ( rangeSq < victimRangeSq )
{
newVictim = playerVector[i];
victimRangeSq = rangeSq;
}
}
if ( newVictim )
{
// we have a new victim
m_chaseVictimTimer.Start( tf_halloween_bot_chase_duration.GetFloat() );
TFGameRules()->SetIT( newVictim );
}
}
// attack the "IT" player unless we're focusing on something else
if ( m_attackTarget == NULL || m_attackTargetFocusTimer.IsElapsed() || !m_attackTarget->IsAlive() )
{
m_attackTarget = ToTFPlayer( TFGameRules()->GetIT() );
}
}
//----------------------------------------------------------------------------------
void CHeadlessHatmanAttack::AttackTarget( CHeadlessHatman *me, CBaseCombatCharacter *pTarget, float flAttackRange )
{
// out of range? don't do anything
if ( !me->IsRangeLessThan( pTarget, flAttackRange ) )
return;
Vector forward;
me->GetVectors( &forward, NULL, NULL );
Vector toVictim = pTarget->WorldSpaceCenter() - me->WorldSpaceCenter();
toVictim.NormalizeInPlace();
CTFPlayer *pPlayer = pTarget->IsPlayer() ? ToTFPlayer( pTarget ) : NULL;
// push kart player or ghost
if ( pPlayer && ( pPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_KART ) || pPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_GHOST_MODE ) ) )
{
// Apply Huge Force
Vector vecDir = toVictim;
vecDir.NormalizeInPlace();
vecDir.z += 0.7f;
vecDir *= 1300.0f;
if ( pPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_KART ) )
{
pPlayer->AddHalloweenKartPushEvent( NULL, NULL, NULL, vecDir, 100.0f, TF_DMG_CUSTOM_DECAPITATION_BOSS );
}
else
{
pPlayer->ApplyAbsVelocityImpulse( vecDir );
}
}
else
{
// looser tolerance as victim gets closer
const float closeRange = 0.5f * flAttackRange;
float range = me->GetRangeTo( pTarget );
float closeness = ( range < closeRange ) ? 0.0f : ( range - closeRange ) / ( flAttackRange - closeRange );
float hitAngle = 0.0f + closeness * 0.27f;
// is target in front?
if ( DotProduct( forward, toVictim ) > hitAngle )
{
if ( me->IsLineOfSightClear( pTarget ) )
{
// CHOP!
CTakeDamageInfo info( me, me, m_attackTarget->GetMaxHealth() * 0.8f, DMG_CLUB, TF_DMG_CUSTOM_DECAPITATION_BOSS );
CalculateMeleeDamageForce( &info, toVictim, me->WorldSpaceCenter(), 5.0f );
m_attackTarget->TakeDamage( info );
me->EmitSound( "Halloween.HeadlessBossAxeHitFlesh" );
}
}
}
}
//----------------------------------------------------------------------------------
void CHeadlessHatmanAttack::UpdateAxeSwing( CHeadlessHatman *me )
{
if ( m_axeSwingTimer.HasStarted() )
{
// continue axe swing
if ( m_axeSwingTimer.IsElapsed() )
{
// moment of impact - did axe swing hit?
m_axeSwingTimer.Invalidate();
if ( TFGameRules()->IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_DOOMSDAY ) )
{
CUtlVector< CTFPlayer* > playerVector;
CollectPlayers( &playerVector, TEAM_ANY );
for( CTFPlayer* pPlayer : playerVector )
{
AttackTarget( me, pPlayer, tf_halloween_hhh_attack_kart_radius.GetFloat() );
}
DispatchParticleEffect( "hammer_impact_button", me->GetAbsOrigin(), me->GetAbsAngles() );
me->EmitSound( "Halloween.HammerImpact" );
// after HHH punt off a kart target, pick a new target right away
TFGameRules()->SetIT( NULL );
}
else if ( m_attackTarget != NULL )
{
AttackTarget( me, m_attackTarget, tf_halloween_bot_attack_range.GetFloat() );
}
// always playe the axe-hit-world impact sound, since it carries through the world better
me->EmitSound( "Halloween.HeadlessBossAxeHitWorld" );
if ( !TFGameRules()->IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_DOOMSDAY ) )
{
UTIL_ScreenShake( me->GetAbsOrigin(), 15.0f, 5.0f, 1.0f, 1000.0f, SHAKE_START );
}
Vector bladePos;
QAngle bladeAngle;
if ( me->GetAxe() && me->GetAxe()->GetAttachment( "axe_blade", bladePos, bladeAngle ) )
{
DispatchParticleEffect( "halloween_boss_axe_hit_world", bladePos, bladeAngle );
}
}
}
}
//----------------------------------------------------------------------------------
ActionResult< CHeadlessHatman > CHeadlessHatmanAttack::Update( CHeadlessHatman *me, float interval )
{
if ( !me->IsAlive() )
{
return Done();
}
SelectVictim( me );
RecomputeHomePosition();
if ( !IsSwingingAxe() && m_laughTimer.IsElapsed() )
{
me->EmitSound( "Halloween.HeadlessBossLaugh" );
m_laughTimer.Start( RandomFloat( 3.0f, 5.0f ) );
if ( TFGameRules()->GetIT() == NULL )
{
// if we have no victim, show our frustration
int r = RandomInt( 0, 100 );
if ( r < 25 )
{
me->AddGesture( ACT_MP_GESTURE_VC_FISTPUMP_MELEE );
}
else if ( r < 50 )
{
me->AddGesture( ACT_MP_GESTURE_VC_FINGERPOINT_MELEE );
}
}
}
if ( TFGameRules()->GetIT() == NULL )
{
// go home
const float atHomeRange = 50.0f;
if ( me->IsRangeGreaterThan( m_homePos, atHomeRange ) )
{
if ( m_path.GetAge() > 3.0f )
{
CHeadlessHatmanPathCost cost( me );
m_path.Compute( me, m_homePos, cost );
}
m_path.Update( me );
}
else // at home
{
if ( m_closestVisible != NULL )
{
// look at visible victim out of range
me->GetLocomotionInterface()->FaceTowards( m_closestVisible->WorldSpaceCenter() );
}
}
}
else
{
// chase after our chase victim
const float standAndSwingRange = 100.0f;
CTFPlayer *chaseVictim = ToTFPlayer( TFGameRules()->GetIT() );
if ( chaseVictim && m_warnITTimer.IsElapsed() && !TFGameRules()->IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_DOOMSDAY ) )
{
m_warnITTimer.Start( 7.0f );
ClientPrint( chaseVictim, HUD_PRINTCENTER, "#TF_HALLOWEEN_BOSS_WARN_VICTIM", chaseVictim->GetPlayerName() );
}
if ( me->IsRangeGreaterThan( chaseVictim, standAndSwingRange ) || !me->IsLineOfSightClear( chaseVictim ) )
{
if ( m_path.GetAge() > 1.0f )
{
CHeadlessHatmanPathCost cost( me );
m_path.Compute( me, chaseVictim, cost );
}
m_path.Update( me );
}
}
// swing our axe at our attack target if they are in range
if ( m_attackTarget != NULL && m_attackTarget->IsAlive() )
{
if ( me->IsRangeLessThan( m_attackTarget, tf_halloween_bot_attack_range.GetFloat() ) )
{
if ( m_scareTimer.IsElapsed() && m_attackTarget->IsPlayer() )
{
m_scareTimer.Reset();
return SuspendFor( new CHeadlessHatmanTerrify, "Boo!" );
}
if ( m_attackTimer.IsElapsed() )
{
if ( !IsSwingingAxe() )
{
if ( TFGameRules()->IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_DOOMSDAY ) )
{
me->AddGesture( ACT_MP_ATTACK_STAND_ITEM2 );
}
else
{
me->AddGesture( ACT_MP_ATTACK_STAND_ITEM1 );
}
m_axeSwingTimer.Start( 0.58f );
me->EmitSound( "Halloween.HeadlessBossAttack" );
m_attackTimer.Start( 1.0f );
}
}
me->GetLocomotionInterface()->FaceTowards( m_attackTarget->WorldSpaceCenter() );
}
}
// finish axe swing once it has begun
UpdateAxeSwing( me );
if ( me->GetLocomotionInterface()->IsAttemptingToMove() )
{
// play running animation
int healthRatio = 100 * me->GetHealth() / me->GetMaxHealth();
if ( healthRatio > 50 )
{
if ( !me->GetBodyInterface()->IsActivity( ACT_MP_RUN_ITEM1 ) )
{
me->GetBodyInterface()->StartActivity( ACT_MP_RUN_ITEM1 );
}
}
else
{
if ( !me->GetBodyInterface()->IsActivity( ACT_MP_RUN_MELEE ) )
{
me->GetBodyInterface()->StartActivity( ACT_MP_RUN_MELEE );
}
}
if ( !TFGameRules()->IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_DOOMSDAY ) )
{
if ( m_footfallTimer.IsElapsed() )
{
m_footfallTimer.Start( 0.25f );
// periodically shake nearby players' screens when we're moving
UTIL_ScreenShake( me->GetAbsOrigin(), 15.0f, 5.0f, 1.0f, 1000.0f, SHAKE_START );
}
}
}
else
{
// standing still
if ( !me->GetBodyInterface()->IsActivity( ACT_MP_STAND_ITEM1 ) )
{
me->GetBodyInterface()->StartActivity( ACT_MP_STAND_ITEM1 );
}
}
return Continue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CHeadlessHatman > CHeadlessHatmanAttack::OnStuck( CHeadlessHatman *me )
{
// we're stuck - just warp to the our next path goal
if ( m_path.GetCurrentGoal() )
{
me->SetAbsOrigin( m_path.GetCurrentGoal()->pos + Vector( 0, 0, 10.0f ) );
}
return TryContinue( RESULT_TRY );
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CHeadlessHatman > CHeadlessHatmanAttack::OnContact( CHeadlessHatman *me, CBaseEntity *other, CGameTrace *result )
{
if ( other )
{
CBaseCombatCharacter *combatChar = dynamic_cast< CBaseCombatCharacter * >( other );
if ( combatChar && combatChar->IsAlive() )
{
// force attack the thing we bumped into
// this prevents us from being stuck on dispensers, for example
m_attackTarget = combatChar;
m_attackTargetFocusTimer.Start( 3.0f );
}
}
return TryContinue( RESULT_TRY );
}
//---------------------------------------------------------------------------------------------
void CHeadlessHatmanAttack::RecomputeHomePosition( void )
{
if ( !m_homePosRecalcTimer.IsElapsed() )
{
return;
}
m_homePosRecalcTimer.Reset();
CTeamControlPoint *contestedPoint = NULL;
CTeamControlPointMaster *pMaster = g_hControlPointMasters.Count() ? g_hControlPointMasters[0] : NULL;
if ( pMaster )
{
for( int i=0; i<pMaster->GetNumPoints(); ++i )
{
contestedPoint = pMaster->GetControlPoint( i );
if ( contestedPoint && pMaster->IsInRound( contestedPoint ) )
{
if ( ObjectiveResource()->GetOwningTeam( contestedPoint->GetPointIndex() ) == TF_TEAM_BLUE )
continue;
// blue are the invaders
if ( !TeamplayGameRules()->TeamMayCapturePoint( TF_TEAM_BLUE, contestedPoint->GetPointIndex() ) )
continue;
break;
}
}
}
if ( contestedPoint )
{
m_homePos = contestedPoint->GetAbsOrigin();
}
}
@@ -0,0 +1,54 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// headless_hatman_attack.h
// Halloween Boss chase and attack behavior
// Michael Booth, October 2010
#ifndef HEADLESS_HATMAN_ATTACK_H
#define HEADLESS_HATMAN_ATTACK_H
//---------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------
class CHeadlessHatmanAttack : public Action< CHeadlessHatman >
{
public:
virtual ActionResult< CHeadlessHatman > OnStart( CHeadlessHatman *me, Action< CHeadlessHatman > *priorAction );
virtual ActionResult< CHeadlessHatman > Update( CHeadlessHatman *me, float interval );
virtual EventDesiredResult< CHeadlessHatman > OnStuck( CHeadlessHatman *me );
virtual EventDesiredResult< CHeadlessHatman > OnContact( CHeadlessHatman *me, CBaseEntity *other, CGameTrace *result = NULL );
virtual const char *GetName( void ) const { return "Attack"; } // return name of this action
private:
PathFollower m_path;
Vector m_homePos;
CountdownTimer m_homePosRecalcTimer;
void RecomputeHomePosition( void );
CountdownTimer m_axeSwingTimer;
CountdownTimer m_attackTimer;
CountdownTimer m_laughTimer;
CountdownTimer m_scareTimer;
CountdownTimer m_warnITTimer;
CountdownTimer m_footfallTimer;
void AttackTarget( CHeadlessHatman *me, CBaseCombatCharacter *pTarget, float flAttackRange );
void UpdateAxeSwing( CHeadlessHatman *me );
bool IsSwingingAxe( void ) const;
CHandle< CTFPlayer > m_closestVisible;
CHandle< CTFPlayer > m_lastIT;
CountdownTimer m_chaseVictimTimer;
void ValidateChaseVictim( CHeadlessHatman *me );
bool IsPotentiallyChaseable( CHeadlessHatman *me, CTFPlayer *victim );
CHandle< CBaseCombatCharacter > m_attackTarget; // the victim I'm momentarily attacking
CountdownTimer m_attackTargetFocusTimer;
void SelectVictim( CHeadlessHatman *me );
};
#endif // HEADLESS_HATMAN_ATTACK_H
@@ -0,0 +1,44 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// headless_hatman_dying.cpp
// The HHH in the process of dying
// Michael Booth, October 2010
#include "cbase.h"
#include "particle_parse.h"
#include "../headless_hatman.h"
#include "headless_hatman_dying.h"
//---------------------------------------------------------------------------------------------
ActionResult< CHeadlessHatman > CHeadlessHatmanDying::OnStart( CHeadlessHatman *me, Action< CHeadlessHatman > *priorAction )
{
me->GetBodyInterface()->StartActivity( ACT_DIESIMPLE );
me->EmitSound( "Halloween.HeadlessBossDying" );
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CHeadlessHatman > CHeadlessHatmanDying::Update( CHeadlessHatman *me, float interval )
{
if ( me->IsActivityFinished() )
{
me->Break();
DispatchParticleEffect( "halloween_boss_death", me->GetAbsOrigin(), me->GetAbsAngles() );
UTIL_Remove( me );
IGameEvent *event = gameeventmanager->CreateEvent( "pumpkin_lord_killed" );
if ( event )
{
gameeventmanager->FireEvent( event );
}
return Done();
}
return Continue();
}
@@ -0,0 +1,20 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// headless_hatman_dying.h
// The HHH in the process of dying
// Michael Booth, October 2010
#ifndef HEADLESS_HATMAN_DYING_H
#define HEADLESS_HATMAN_DYING_H
//---------------------------------------------------------------------------------------------
class CHeadlessHatmanDying : public Action< CHeadlessHatman >
{
public:
virtual ActionResult< CHeadlessHatman > OnStart( CHeadlessHatman *me, Action< CHeadlessHatman > *priorAction );
virtual ActionResult< CHeadlessHatman > Update( CHeadlessHatman *me, float interval );
virtual const char *GetName( void ) const { return "Dying"; } // return name of this action
};
#endif // HEADLESS_HATMAN_DYING_H
@@ -0,0 +1,133 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// headless_hatman_emerge.cpp
// The Halloween Boss emerging from the ground
// Michael Booth, October 2010
#include "cbase.h"
#include "tf_player.h"
#include "tf_gamerules.h"
#include "tf_team.h"
#include "nav_mesh/tf_nav_area.h"
#include "particle_parse.h"
#include "../headless_hatman.h"
#include "headless_hatman_emerge.h"
#include "headless_hatman_attack.h"
#include "crybaby_boss_escape.h"
//---------------------------------------------------------------------------------------------
ActionResult< CHeadlessHatman > CHeadlessHatmanEmerge::OnStart( CHeadlessHatman *me, Action< CHeadlessHatman > *priorAction )
{
me->GetBodyInterface()->StartActivity( ACT_TRANSITION );
DispatchParticleEffect( "halloween_boss_summon", me->GetAbsOrigin(), me->GetAbsAngles() );
m_riseTimer.Start( 3.0f );
m_emergePos = me->GetAbsOrigin() + Vector( 0, 0, 10.0f );
m_height = 200.0f;
me->SetAbsOrigin( m_emergePos + Vector( 0, 0, -m_height ) );
me->EmitSound( "Halloween.HeadlessBossSpawnRumble" );
// face towards a nearby player
CUtlVector< CTFPlayer * > playerVector;
CollectPlayers( &playerVector, TF_TEAM_RED, COLLECT_ONLY_LIVING_PLAYERS );
CollectPlayers( &playerVector, TF_TEAM_BLUE, COLLECT_ONLY_LIVING_PLAYERS, APPEND_PLAYERS );
float closeRangeSq = FLT_MAX;
CTFPlayer *close = NULL;
for( int i=0; i<playerVector.Count(); ++i )
{
CTFPlayer *player = playerVector[i];
float rangeSq = me->GetRangeSquaredTo( player );
if ( rangeSq < closeRangeSq )
{
closeRangeSq = rangeSq;
close = player;
}
}
QAngle facingAngle;
if ( close )
{
Vector toPlayer = close->GetAbsOrigin() - me->GetAbsOrigin();
toPlayer.z = 0.0f;
toPlayer.NormalizeInPlace();
VectorAngles( toPlayer, Vector(0,0,1), facingAngle );
}
else
{
facingAngle.x = 0.0f;
facingAngle.y = RandomFloat( 0.0f, 360.0f );
facingAngle.z = 0.0f;
}
me->SetAbsAngles( facingAngle );
IGameEvent *event = gameeventmanager->CreateEvent( "pumpkin_lord_summoned" );
if ( event )
{
gameeventmanager->FireEvent( event );
}
return Continue();
}
//----------------------------------------------------------------------------------
ActionResult< CHeadlessHatman > CHeadlessHatmanEmerge::Update( CHeadlessHatman *me, float interval )
{
if ( !m_riseTimer.IsElapsed() )
{
me->SetAbsOrigin( m_emergePos + Vector( 0, 0, -m_height * m_riseTimer.GetRemainingTime() / m_riseTimer.GetCountdownDuration() ) );
if ( m_rumbleTimer.IsElapsed() )
{
m_rumbleTimer.Start( 0.25f );
// shake nearby players' screens.
UTIL_ScreenShake( me->GetAbsOrigin(), 15.0f, 5.0f, 1.0f, 1000.0f, SHAKE_START );
}
}
if ( me->IsActivityFinished() )
{
return ChangeTo( new CHeadlessHatmanAttack, "Here I come!" );
}
// push players away to avoid penetration issues
CUtlVector< CTFPlayer * > playerVector;
CollectPlayers( &playerVector, TF_TEAM_RED, COLLECT_ONLY_LIVING_PLAYERS );
CollectPlayers( &playerVector, TF_TEAM_BLUE, COLLECT_ONLY_LIVING_PLAYERS, APPEND_PLAYERS );
const float pushRange = 250.0f;
const float pushForce = 200.0f;
for( int i=0; i<playerVector.Count(); ++i )
{
CTFPlayer *player = playerVector[i];
Vector toPlayer = player->EyePosition() - m_emergePos;
float range = toPlayer.NormalizeInPlace();
if ( range < pushRange )
{
// make sure we push players up and away
toPlayer.z = 0.0f;
toPlayer.NormalizeInPlace();
toPlayer.z = 1.0f;
Vector push = pushForce * toPlayer;
player->ApplyAbsVelocityImpulse( push );
}
}
return Continue();
}
@@ -0,0 +1,26 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// headless_hatman_emerge.h
// The Halloween Boss emerging from the ground
// Michael Booth, October 2010
#ifndef HEADLESS_HATMAN_EMERGE_H
#define HEADLESS_HATMAN_EMERGE_H
//---------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------
class CHeadlessHatmanEmerge : public Action< CHeadlessHatman >
{
public:
virtual ActionResult< CHeadlessHatman > OnStart( CHeadlessHatman *me, Action< CHeadlessHatman > *priorAction );
virtual ActionResult< CHeadlessHatman > Update( CHeadlessHatman *me, float interval );
virtual const char *GetName( void ) const { return "Emerge"; } // return name of this action
private:
CountdownTimer m_riseTimer;
CountdownTimer m_rumbleTimer;
Vector m_emergePos;
float m_height;
};
#endif // HEADLESS_HATMAN_EMERGE_H
@@ -0,0 +1,95 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// headless_hatman_terrify.cpp
// The Halloween Boss leans over and yells "Boo!", terrifying nearby victims
// Michael Booth, October 2010
#include "cbase.h"
#include "tf_player.h"
#include "tf_team.h"
#include "../headless_hatman.h"
#include "headless_hatman_terrify.h"
//---------------------------------------------------------------------------------------------
ActionResult< CHeadlessHatman > CHeadlessHatmanTerrify::OnStart( CHeadlessHatman *me, Action< CHeadlessHatman > *priorAction )
{
me->AddGesture( ACT_MP_GESTURE_VC_HANDMOUTH_ITEM1 );
m_booTimer.Start( 0.25f );
m_scareTimer.Start( 0.75f );
m_timer.Start( 1.25f );
return Continue();
}
//---------------------------------------------------------------------------------------------
ActionResult< CHeadlessHatman > CHeadlessHatmanTerrify::Update( CHeadlessHatman *me, float interval )
{
if ( m_timer.IsElapsed() )
{
return Done();
}
if ( m_booTimer.HasStarted() && m_booTimer.IsElapsed() )
{
m_booTimer.Invalidate();
me->EmitSound( "Halloween.HeadlessBossBoo" );
}
if ( m_scareTimer.IsElapsed() )
{
CUtlVector< CTFPlayer * > playerVector;
CollectPlayers( &playerVector, TF_TEAM_RED, COLLECT_ONLY_LIVING_PLAYERS );
CollectPlayers( &playerVector, TF_TEAM_BLUE, COLLECT_ONLY_LIVING_PLAYERS, APPEND_PLAYERS );
for( int i=0; i<playerVector.Count(); ++i )
{
CTFPlayer *victim = playerVector[i];
if ( me->IsRangeLessThan( victim, tf_halloween_bot_terrify_radius.GetFloat() ) )
{
if ( !IsWearingPumpkinHeadOrSaxtonMask( victim ) && me->IsLineOfSightClear( victim ) )
{
// scare them!
const float scareTime = 2.0f;
const float speedReduction = 0.0f;
// "stun by trigger" results in the Halloween "yikes" effects
int stunFlags = TF_STUN_LOSER_STATE | TF_STUN_BY_TRIGGER;
victim->m_Shared.StunPlayer( scareTime, speedReduction, stunFlags, NULL );
}
}
}
}
return Continue();
}
//---------------------------------------------------------------------------------------------
bool CHeadlessHatmanTerrify::IsWearingPumpkinHeadOrSaxtonMask( CTFPlayer *player )
{
const int pumpkinHeadHat = 278;
const int saxtonMask = 277;
for( int i=0; i<player->GetNumWearables(); ++i )
{
CEconWearable *wearable = player->GetWearable( i );
if ( wearable && wearable->GetAttributeContainer() )
{
CEconItemView *item = wearable->GetAttributeContainer()->GetItem();
if ( item && item->IsValid() )
{
if ( item->GetItemDefIndex() == pumpkinHeadHat || item->GetItemDefIndex() == saxtonMask )
{
return true;
}
}
}
}
return false;
}
@@ -0,0 +1,26 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// headless_hatman_terrify.h
// The Halloween Boss leans over and yells "Boo!", terrifying nearby victims
// Michael Booth, October 2010
#ifndef HEADLESS_HATMAN_TERRIFY_H
#define HEADLESS_HATMAN_TERRIFY_H
//---------------------------------------------------------------------------------------------
class CHeadlessHatmanTerrify : public Action< CHeadlessHatman >
{
public:
virtual ActionResult< CHeadlessHatman > OnStart( CHeadlessHatman *me, Action< CHeadlessHatman > *priorAction );
virtual ActionResult< CHeadlessHatman > Update( CHeadlessHatman *me, float interval );
virtual const char *GetName( void ) const { return "Terrify"; } // return name of this action
private:
CountdownTimer m_booTimer;
CountdownTimer m_scareTimer;
CountdownTimer m_timer;
bool IsWearingPumpkinHeadOrSaxtonMask( CTFPlayer *player );
};
#endif // HEADLESS_HATMAN_TERRIFY_H
@@ -0,0 +1,292 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// memdbgon must be the last include file in a .cpp file!!!
#include "cbase.h"
#include "utlvector.h"
#include "util.h"
#include "halloween/halloween_gift_spawn_locations.h"
#include "tier0/memdbgon.h"
// -------------------------------------------------------------------------
struct valid_item_pos
{
float x, y, z;
};
static valid_item_pos kValidPositions_MannManor[] = {
{ -725.000000, 1362.500000, -639.968750 },
{ -3137.500000, 1500.000000, -1023.968750 },
{ -1212.500000, 1187.500000, -959.968750 },
{ -3150.000000, 1375.000000, -791.968750 },
{ -2237.500000, 2150.000000, -1015.968750 },
{ -1837.500000, 2250.000000, -895.968750 },
{ -1162.500000, 1837.500000, -959.968750 },
{ -3662.500000, 1212.500000, -1024.431152 },
{ -912.500000, 3937.500000, -1039.968750 },
{ -175.000000, 1837.500000, -1007.968750 },
{ -1575.000000, 1762.500000, -959.968750 },
{ -1625.000000, 1387.500000, -959.968750 },
{ -850.000000, 1025.000000, -639.468750 },
{ -2625.000000, 3750.000000, -1086.180908 },
{ -450.000000, 1850.000000, -1006.910278 },
{ -2775.000000, 1425.000000, -1023.968750 },
{ -2037.500000, 1625.000000, -895.968750 },
{ -637.500000, 2475.000000, -767.968750 },
{ -3637.500000, 2862.500000, -903.968750 },
{ -2812.500000, 1325.000000, -791.968750 },
{ -1212.500000, 2162.500000, -959.968750 },
{ -1525.000000, 2662.500000, -895.968750 },
{ -2837.500000, 2537.500000, -1085.712402 },
{ -1312.500000, 3787.500000, -1079.968750 },
{ -2450.000000, 1637.500000, -895.968750 },
{ -625.000000, 1037.500000, -639.968750 },
{ -3625.000000, 2587.500000, -903.968750 },
{ -2800.000000, 3362.500000, -1087.954102 },
{ -2250.000000, 1625.000000, -895.968750 },
{ -1775.000000, 2825.000000, -783.968750 },
{ -3712.500000, 2100.000000, -671.968750 },
{ -3500.000000, 2100.000000, -863.968750 },
{ -1400.000000, 3325.000000, -1088.116577 },
{ -800.000000, 2825.000000, -767.968750 },
{ -1787.500000, 2575.000000, -767.968750 },
{ -2962.500000, 3187.500000, -668.908264 },
{ -1962.500000, 1900.000000, -895.968750 },
{ -712.500000, 2262.500000, -767.968750 },
{ -587.500000, 3887.500000, -1099.458862 },
{ -3737.500000, 2112.500000, -863.968750 },
{ -3212.500000, 1237.500000, -1023.968750 },
{ -1325.000000, 3987.500000, -1079.968750 },
{ -737.500000, 1887.500000, -1007.968750 },
{ -2837.500000, 2137.500000, -1083.385742 },
{ -3112.500000, 1137.500000, -791.968750 },
{ -2275.000000, 3187.500000, -767.968750 },
{ -1475.000000, 2962.500000, -847.968750 },
{ -3425.000000, 2137.500000, -671.968750 },
{ -3450.000000, 2912.500000, -903.968750 },
{ -1725.000000, 3737.500000, -1050.131104 },
{ -850.000000, 3237.500000, -1031.956299 },
{ -2650.000000, 2512.500000, -1085.018555 },
{ -2487.500000, 2037.500000, -1050.676147 },
{ -1600.000000, 1175.000000, -959.968750 },
{ -650.000000, 1937.500000, -743.482178 },
{ -650.000000, 1775.000000, -692.995728 },
{ -1012.500000, 1500.000000, -639.968750 },
{ -3700.000000, 1275.000000, -811.867065 },
{ -3112.500000, 1600.000000, -791.968750 },
{ -2950.000000, 1850.000000, -1053.853516 },
{ -1225.000000, 3475.000000, -1086.870605 },
{ -1150.000000, 3300.000000, -1070.466187 },
{ -925.000000, 3425.000000, -1050.419189 },
{ -650.000000, 3300.000000, -1043.805176 },
{ -625.000000, 2800.000000, -767.968750 },
{ -2775.000000, 1650.000000, -1023.968750 },
{ -3575.000000, 1100.000000, -808.125854 },
{ -1212.500000, 1425.000000, -959.968750 },
{ -1737.500000, 3000.000000, -783.968750 },
{ -1462.500000, 2437.500000, -895.968750 },
{ -862.500000, 2200.000000, -767.968750 },
{ -2962.500000, 3550.000000, -671.968750 },
{ -3462.500000, 2487.500000, -903.968750 },
{ -1012.500000, 3150.000000, -1048.620605 },
{ -562.500000, 3725.000000, -1087.434692 },
{ -2062.500000, 3250.000000, -984.070923 },
{ -2675.000000, 2112.500000, -1082.104248 },
{ -2487.500000, 2400.000000, -1051.597046 },
{ -2037.500000, 3137.500000, -838.662476 },
{ -3062.500000, 1262.500000, -1023.968750 },
{ -2887.500000, 2812.500000, -694.111816 },
{ -2987.500000, 3250.000000, -1086.727539 },
{ -2812.500000, 3762.500000, -1086.203857 },
{ -2350.000000, 3537.500000, -1082.763184 },
{ -2162.500000, 3837.500000, -967.968750 },
{ -1562.500000, 3312.500000, -1079.254028 },
{ -1375.000000, 3500.000000, -1085.550903 },
{ -887.500000, 3700.000000, -1059.968750 },
{ -2812.500000, 2287.500000, -1081.588135 },
{ -2687.500000, 2362.500000, -1081.903198 },
{ -2337.500000, 2412.500000, -1023.968750 },
{ -1950.000000, 1437.500000, -895.968750 },
{ -2850.000000, 1112.500000, -872.148926 },
{ -1350.000000, 1862.500000, -959.968750 },
{ -1600.000000, 2425.000000, -830.815613 },
{ -3487.500000, 1262.500000, -809.627197 },
{ -3475.000000, 1237.500000, -1023.968750 },
{ -2875.000000, 2787.500000, -1085.673584 },
{ -3012.500000, 2062.500000, -1073.808105 },
{ -2550.000000, 3962.500000, -1087.083984 },
{ -1512.500000, 3812.500000, -1079.644287 },
{ -1100.000000, 3437.500000, -1073.367554 },
{ -750.000000, 3562.500000, -1059.989502 },
{ -850.000000, 2987.500000, -767.968750 },
{ -575.000000, 2287.500000, -767.968750 },
{ -1175.000000, 2337.500000, -959.968750 },
{ -1850.000000, 3175.000000, -916.482300 },
{ -2675.000000, 1125.000000, -954.482300 },
{ -1562.500000, 1575.000000, -959.968750 },
{ -600.000000, 525.000000, -759.968750 },
{ -2962.500000, 2975.000000, -671.584595 },
{ -3687.500000, 2275.000000, -751.524414 },
{ -3437.500000, 2250.000000, -863.968750 },
{ -2750.000000, 1500.000000, -791.968750 },
{ -2862.500000, 3025.000000, -1088.080444 },
{ -2650.000000, 3400.000000, -1086.712158 },
{ -2037.500000, 3850.000000, -967.968750 },
{ -2050.000000, 3537.500000, -1039.457031 },
{ -1262.500000, 3625.000000, -1079.968750 },
{ -1100.000000, 3725.000000, -1079.968750 },
{ -1137.500000, 2975.000000, -1064.579102 },
{ -1100.000000, 2800.000000, -1035.468262 },
{ -1000.000000, 3300.000000, -1043.383545 },
{ -900.000000, 3550.000000, -1069.937378 },
{ -675.000000, 3437.500000, -1038.987061 },
{ -625.000000, 3162.500000, -1048.860962 },
{ -2825.000000, 1975.000000, -1071.794189 },
{ -2875.000000, 1250.000000, -1023.968750 },
{ -2625.000000, 1950.000000, -1080.477051 },
{ -2512.500000, 2600.000000, -1060.309570 },
{ -2675.000000, 1250.000000, -990.329102 },
{ -1025.000000, 1262.500000, -618.168762 },
{ -2850.000000, 3175.000000, -1091.269287 },
{ -2275.000000, 3862.500000, -993.140991 },
{ -1987.500000, 3650.000000, -1038.143188 },
{ -1937.500000, 3475.000000, -1009.303223 },
{ -1625.000000, 3925.000000, -1057.784912 },
{ -575.000000, 3562.500000, -1064.084229 },
{ -2450.000000, 3400.000000, -1092.235474 },
{ -1675.000000, 3537.500000, -1058.036133 },
{ -1187.500000, 2562.500000, -990.317749 },
{ -1612.500000, 2975.000000, -815.968750 },
{ -1175.000000, 4000.000000, -1059.968750 },
{ -2200.000000, 2425.000000, -1023.968750 },
{ -2162.500000, 3425.000000, -1036.134521 },
{ -1450.000000, 2187.500000, -767.968750 },
{ -1100.000000, 2187.500000, -767.968750 },
};
const float g_flPlayerEyeHeight = 70.0f;
static valid_item_pos kValidPositions_Viaduct[] =
{
{ -1056.508423, 120.524803, 307.972595f - g_flPlayerEyeHeight },
{ -1245.718750, -371.585114, 316.206665f - g_flPlayerEyeHeight },
{ -1755.105469, -602.121094, 211.415222f - g_flPlayerEyeHeight },
{ -1866.556030, -11.241785, 311.038757f - g_flPlayerEyeHeight },
{ -1801.210083, 567.631775, 221.211823f - g_flPlayerEyeHeight },
{ -2254.645996, -501.424133, 323.972595f - g_flPlayerEyeHeight },
{ -2210.340576, 376.084320, 323.972595f - g_flPlayerEyeHeight },
{ -1888.754272, 639.861633, 204.341263f - g_flPlayerEyeHeight },
{ -1903.123413, -641.635986, 203.944336f - g_flPlayerEyeHeight },
{ -1212.720703, 990.511353, 227.972595f - g_flPlayerEyeHeight },
{ -1225.367554, -966.919922, 227.972595f - g_flPlayerEyeHeight },
{ -2486.856934, -1201.215576, 194.879059f - g_flPlayerEyeHeight },
{ -2568.440674, 1470.992432, 195.961288f - g_flPlayerEyeHeight },
{ -1681.012573, -1190.597534, 131.972595f - g_flPlayerEyeHeight },
{ -1668.416504, 1226.122070, 131.972595f - g_flPlayerEyeHeight },
{ -2459.953125, 394.793518, 323.972595f - g_flPlayerEyeHeight },
{ -2495.843994, -505.581940, 323.972595f - g_flPlayerEyeHeight },
{ -1507.193359, -632.548096, 207.798416f - g_flPlayerEyeHeight },
{ -1515.489746, 637.124756, 207.442322f - g_flPlayerEyeHeight },
{ -1533.132202, 6.273134, 299.123474f - g_flPlayerEyeHeight },
{ -1533.132202, 6.273134, 299.123474f - g_flPlayerEyeHeight },
{ -1533.132202, 6.273134, 299.123474f - g_flPlayerEyeHeight },
{ -1533.132202, 6.273134, 299.123474f - g_flPlayerEyeHeight },
{ -1533.132202, 6.273134, 299.123474f - g_flPlayerEyeHeight },
{ -1533.132202, 6.273134, 299.123474f - g_flPlayerEyeHeight },
{ -1533.132202, 6.273134, 299.123474f - g_flPlayerEyeHeight },
{ -1767.763306, -31.788916, 304.332611f - g_flPlayerEyeHeight },
{ -1314.115601, 1.664654, 304.332611f - g_flPlayerEyeHeight },
};
static valid_item_pos kValidPositions_Lakeside[] =
{
{ 440.514740, 19.020391, 52.031319f - g_flPlayerEyeHeight },
{ -439.044678, -0.653890, 52.031319f - g_flPlayerEyeHeight },
{ 14.226428, -860.007385, 116.031319f - g_flPlayerEyeHeight },
{ 536.307800, -35.320126, -110.660393f - g_flPlayerEyeHeight },
{ -522.902588, -15.580125, -107.159370f - g_flPlayerEyeHeight },
{ -13.246658, 556.848022, 84.031319f - g_flPlayerEyeHeight },
{ 199.859665, -560.373779, 52.031319f - g_flPlayerEyeHeight },
{ -206.504196, -524.416443, 52.031319f - g_flPlayerEyeHeight },
{ -572.280029, -854.871521, -102.842537f - g_flPlayerEyeHeight },
{ 570.159973, -833.295898, -104.718376f - g_flPlayerEyeHeight },
{ -10.444493, -1814.084106, 91.031319f - g_flPlayerEyeHeight },
{ -1117.456177, -1821.980591, 81.080040f - g_flPlayerEyeHeight },
{ 1122.193481, -1760.623291, 84.117996f - g_flPlayerEyeHeight },
{ 934.447083, -14.523724, -153.873962f - g_flPlayerEyeHeight },
{ -935.006165, 0.899750, -154.107819f - g_flPlayerEyeHeight },
{ -831.039368, 1028.812988, -132.968689f - g_flPlayerEyeHeight },
{ 838.370667, 1039.730347, -132.968689f - g_flPlayerEyeHeight },
{ 330.726898, -1061.698975, 91.031319f - g_flPlayerEyeHeight },
{ -347.198090, -1074.667480, 91.031319f - g_flPlayerEyeHeight },
{ -970.985229, -1151.937866, -129.290192f - g_flPlayerEyeHeight },
{ 964.682556, -1112.505127, -132.963379f - g_flPlayerEyeHeight },
{ -310.233612, -548.355957, -68.615242f - g_flPlayerEyeHeight },
{ 310.885254, -545.554932, -68.772285f - g_flPlayerEyeHeight },
};
static valid_item_pos kValidPositions_Hightower[] =
{
{ 6126.312500, 7590.515137, 435.031311f - g_flPlayerEyeHeight },
{ 7961.887207, 7579.561523, -35.332085f - g_flPlayerEyeHeight },
{ 6192.0f, 7592.0f, 373.0f, },
{ 7634.951660, 7592.539063, 130.810715f - g_flPlayerEyeHeight },
{ 6472.569336, 7593.163086, -149.912628f - g_flPlayerEyeHeight }, // Cayle says to use this one three times
{ 6472.569336, 7593.163086, -149.912628f - g_flPlayerEyeHeight },
{ 6472.569336, 7593.163086, -149.912628f - g_flPlayerEyeHeight },
{ 6333.593750, 7398.845703, 425.031311f - g_flPlayerEyeHeight },
{ 6338.365723, 7780.267090, 425.031311f - g_flPlayerEyeHeight },
{ 6111.854004, 7582.590820, 425.031311f - g_flPlayerEyeHeight },
{ 8269.034180, 8336.364258, -193.885345f - g_flPlayerEyeHeight },
{ 6152.998047, 6708.033203, -190.968689f - g_flPlayerEyeHeight },
{ 7947.068359, 7592.274902, -45.030327f - g_flPlayerEyeHeight },
{ 8690.697266, 7519.280273, -134.968689f - g_flPlayerEyeHeight },
{ 5184.807617, 7460.308594, 65.031311f - g_flPlayerEyeHeight },
{ 5176.026367, 7762.865234, 65.031311f - g_flPlayerEyeHeight },
{ 5665.124512, 7912.830078, 186.027237f - g_flPlayerEyeHeight },
{ 5629.083984, 7272.486816, 186.031311f - g_flPlayerEyeHeight },
{ 7569.272461, 7356.835938, 30.543331f - g_flPlayerEyeHeight },
{ 7620.356445, 7806.569824, 43.632362f - g_flPlayerEyeHeight },
{ 10225.334961, 7462.086914, -366.968689f - g_flPlayerEyeHeight },
{ 10208.725586, 7768.193848, -366.968689f - g_flPlayerEyeHeight },
};
struct halloween_map_info
{
const char *m_pszMapName;
valid_item_pos *m_pPositions;
int m_pPositionCount;
};
static halloween_map_info g_HalloweenMapInfo[] =
{
{ "cp_manor_event", kValidPositions_MannManor, ARRAYSIZE( kValidPositions_MannManor ) },
{ "koth_viaduct_event", kValidPositions_Viaduct, ARRAYSIZE( kValidPositions_Viaduct ) },
{ "koth_lakeside_event", kValidPositions_Lakeside, ARRAYSIZE( kValidPositions_Lakeside ) },
{ "plr_hightower_event", kValidPositions_Hightower, ARRAYSIZE( kValidPositions_Hightower ) },
};
//
void AddHalloweenGiftPositionsForMap( const char *pszMapName, CUtlVector<Vector> &vLocations )
{
for ( int i = 0; i < ARRAYSIZE( g_HalloweenMapInfo ); i++ )
{
if ( FStrEq( pszMapName, g_HalloweenMapInfo[i].m_pszMapName ) )
{
for ( int iPoint = 0; iPoint < g_HalloweenMapInfo[i].m_pPositionCount; iPoint++ )
{
vLocations.AddToTail( Vector(
g_HalloweenMapInfo[ i ].m_pPositions[ iPoint ].x,
g_HalloweenMapInfo[ i ].m_pPositions[ iPoint ].y,
g_HalloweenMapInfo[ i ].m_pPositions[ iPoint ].z
) );
}
return;
}
}
}
@@ -0,0 +1,16 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// memdbgon must be the last include file in a .cpp file!!!
#include "cbase.h"
#include "utlvector.h"
#include "util.h"
#include "tier0/memdbgon.h"
//
void AddHalloweenGiftPositionsForMap( const char *pszMapName, CUtlVector<Vector> &vLocations );
//
@@ -0,0 +1,330 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// headless_hatman.cpp
// An NPC that spawns in the Halloween map and wreaks havok
// Michael Booth, October 2010
#include "cbase.h"
#include "tf_player.h"
#include "tf_gamerules.h"
#include "tf_team.h"
#include "nav_mesh/tf_nav_area.h"
#include "headless_hatman.h"
#include "NextBot/Path/NextBotChasePath.h"
#include "econ_wearable.h"
#include "team_control_point_master.h"
#include "particle_parse.h"
#include "ghost/ghost.h"
#include "halloween_behavior/headless_hatman_emerge.h"
#include "halloween_behavior/headless_hatman_dying.h"
ConVar tf_halloween_bot_health_base( "tf_halloween_bot_health_base", "3000", FCVAR_CHEAT );
ConVar tf_halloween_bot_health_per_player( "tf_halloween_bot_health_per_player", "200", FCVAR_CHEAT );
ConVar tf_halloween_bot_min_player_count( "tf_halloween_bot_min_player_count", "10", FCVAR_CHEAT );
ConVar tf_halloween_bot_speed( "tf_halloween_bot_speed", "400", FCVAR_CHEAT );
ConVar tf_halloween_bot_attack_range( "tf_halloween_bot_attack_range", "200", FCVAR_CHEAT );
ConVar tf_halloween_bot_speed_recovery_rate( "tf_halloween_bot_speed_recovery_rate", "100", FCVAR_CHEAT, "Movement units/second" );
ConVar tf_halloween_bot_chase_duration( "tf_halloween_bot_chase_duration", "30", FCVAR_CHEAT );
ConVar tf_halloween_bot_terrify_radius( "tf_halloween_bot_terrify_radius", "500", FCVAR_CHEAT );
ConVar tf_halloween_bot_chase_range( "tf_halloween_bot_chase_range", "1500", FCVAR_CHEAT );
ConVar tf_halloween_bot_quit_range( "tf_halloween_bot_quit_range", "2000", FCVAR_CHEAT );
//-----------------------------------------------------------------------------------------------------
// The Horseless Headless Horseman
//-----------------------------------------------------------------------------------------------------
LINK_ENTITY_TO_CLASS( headless_hatman, CHeadlessHatman );
IMPLEMENT_SERVERCLASS_ST( CHeadlessHatman, DT_HeadlessHatman )
END_SEND_TABLE()
//-----------------------------------------------------------------------------------------------------
CHeadlessHatman::CHeadlessHatman()
{
m_intention = new CHeadlessHatmanIntention( this );
m_locomotor = new CHeadlessHatmanLocomotion( this );
m_body = new CHeadlessHatmanBody( this );
}
//-----------------------------------------------------------------------------------------------------
CHeadlessHatman::~CHeadlessHatman()
{
if ( m_intention )
delete m_intention;
if ( m_locomotor )
delete m_locomotor;
if ( m_body )
delete m_body;
}
void CHeadlessHatman::PrecacheHeadlessHatman()
{
int model = PrecacheModel( "models/bots/headless_hatman.mdl" );
PrecacheGibsForModel( model );
if ( TFGameRules() && TFGameRules()->IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_DOOMSDAY ) )
{
PrecacheModel( "models/weapons/c_models/c_big_mallet/c_big_mallet.mdl" );
PrecacheParticleSystem( "hammer_impact_button" );
PrecacheScriptSound( "Halloween.HammerImpact" );
}
else
{
PrecacheModel( "models/weapons/c_models/c_bigaxe/c_bigaxe.mdl" );
}
PrecacheScriptSound( "Halloween.HeadlessBossSpawn" );
PrecacheScriptSound( "Halloween.HeadlessBossSpawnRumble" );
PrecacheScriptSound( "Halloween.HeadlessBossAttack" );
PrecacheScriptSound( "Halloween.HeadlessBossAlert" );
PrecacheScriptSound( "Halloween.HeadlessBossBoo" );
PrecacheScriptSound( "Halloween.HeadlessBossPain" );
PrecacheScriptSound( "Halloween.HeadlessBossLaugh" );
PrecacheScriptSound( "Halloween.HeadlessBossDying" );
PrecacheScriptSound( "Halloween.HeadlessBossDeath" );
PrecacheScriptSound( "Halloween.HeadlessBossAxeHitFlesh" );
PrecacheScriptSound( "Halloween.HeadlessBossAxeHitWorld" );
PrecacheScriptSound( "Halloween.HeadlessBossFootfalls" );
PrecacheScriptSound( "Player.IsNowIt" );
PrecacheScriptSound( "Player.YouAreIt" );
PrecacheScriptSound( "Player.TaggedOtherIt" );
PrecacheParticleSystem( "halloween_boss_summon" );
PrecacheParticleSystem( "halloween_boss_axe_hit_world" );
PrecacheParticleSystem( "halloween_boss_injured" );
PrecacheParticleSystem( "halloween_boss_death" );
PrecacheParticleSystem( "halloween_boss_foot_impact" );
PrecacheParticleSystem( "halloween_boss_eye_glow" );
}
//-----------------------------------------------------------------------------------------------------
void CHeadlessHatman::Precache()
{
BaseClass::Precache();
// always allow late precaching, so we don't pay the cost of the
// Halloween Boss for the entire year
bool bAllowPrecache = CBaseEntity::IsPrecacheAllowed();
CBaseEntity::SetAllowPrecache( true );
PrecacheHeadlessHatman();
CBaseEntity::SetAllowPrecache( bAllowPrecache );
}
//-----------------------------------------------------------------------------------------------------
void CHeadlessHatman::Spawn( void )
{
Precache();
BaseClass::Spawn();
SetModel( "models/bots/headless_hatman.mdl" );
m_axe = (CBaseAnimating *)CreateEntityByName( "prop_dynamic" );
if ( m_axe )
{
m_axe->SetModel( GetWeaponModel() );
// bonemerge the axe into our model
m_axe->FollowEntity( this, true );
}
// scale the boss' health with the player count
int totalPlayers = GetGlobalTFTeam( TF_TEAM_BLUE )->GetNumPlayers() + GetGlobalTFTeam( TF_TEAM_RED )->GetNumPlayers();
int health = tf_halloween_bot_health_base.GetInt();
if ( totalPlayers > tf_halloween_bot_min_player_count.GetInt() )
{
health += ( totalPlayers - tf_halloween_bot_min_player_count.GetInt() ) * tf_halloween_bot_health_per_player.GetInt();
}
SetHealth( health );
SetMaxHealth( health );
m_homePos = GetAbsOrigin();
m_damagePoseParameter = -1;
SetBloodColor( DONT_BLEED );
}
//-----------------------------------------------------------------------------------------------------
int CHeadlessHatman::OnTakeDamage_Alive( const CTakeDamageInfo &info )
{
DispatchParticleEffect( "halloween_boss_injured", info.GetDamagePosition(), GetAbsAngles() );
return BaseClass::OnTakeDamage_Alive( info );
}
//---------------------------------------------------------------------------------------------
void CHeadlessHatman::Update( void )
{
BaseClass::Update();
if ( m_damagePoseParameter < 0 )
{
m_damagePoseParameter = LookupPoseParameter( "damage" );
}
if ( m_damagePoseParameter >= 0 )
{
SetPoseParameter( m_damagePoseParameter, 1.0f - ( (float)GetHealth() / (float)GetMaxHealth() ) );
}
}
//---------------------------------------------------------------------------------------------
const char *CHeadlessHatman::GetWeaponModel() const
{
if ( TFGameRules() && TFGameRules()->IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_DOOMSDAY ) )
{
return "models/weapons/c_models/c_big_mallet/c_big_mallet.mdl";
}
else
{
return "models/weapons/c_models/c_bigaxe/c_bigaxe.mdl";
}
}
//---------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------
class CHeadlessHatmanBehavior : public Action< CHeadlessHatman >
{
public:
virtual Action< CHeadlessHatman > *InitialContainedAction( CHeadlessHatman *me )
{
return new CHeadlessHatmanEmerge;
}
virtual ActionResult< CHeadlessHatman > Update( CHeadlessHatman *me, float interval )
{
if ( !me->IsAlive() )
{
if ( !me->WasSpawnedByCheats() )
{
// award achievement to everyone who injured me within the last few seconds
const float deathTime = 5.0f;
const CUtlVector< CHeadlessHatman::AttackerInfo > &attackerVector = me->GetAttackerVector();
for( int i=0; i<attackerVector.Count(); ++i )
{
if ( attackerVector[i].m_attacker != NULL &&
gpGlobals->curtime - attackerVector[i].m_timestamp < deathTime )
{
CReliableBroadcastRecipientFilter filter;
UTIL_SayText2Filter( filter, attackerVector[i].m_attacker, false, "#TF_Halloween_Boss_Killers", attackerVector[i].m_attacker->GetPlayerName() );
if ( TFGameRules() && TFGameRules()->IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_MANN_MANOR ) )
{
// killing the boss with a melee weapon is a separate achievement
if ( attackerVector[i].m_wasLastHitFromMeleeWeapon )
{
attackerVector[i].m_attacker->AwardAchievement( ACHIEVEMENT_TF_HALLOWEEN_BOSS_KILL_MELEE );
}
attackerVector[i].m_attacker->AwardAchievement( ACHIEVEMENT_TF_HALLOWEEN_BOSS_KILL );
}
}
}
}
// nobody is IT any longer
TFGameRules()->SetIT( NULL );
return ChangeTo( new CHeadlessHatmanDying, "I am dead!" );
}
return Continue();
}
virtual const char *GetName( void ) const { return "Attack"; } // return name of this action
};
//---------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------
CHeadlessHatmanIntention::CHeadlessHatmanIntention( CHeadlessHatman *me ) : IIntention( me )
{
m_behavior = new Behavior< CHeadlessHatman >( new CHeadlessHatmanBehavior );
}
CHeadlessHatmanIntention::~CHeadlessHatmanIntention()
{
delete m_behavior;
}
void CHeadlessHatmanIntention::Reset( void )
{
delete m_behavior;
m_behavior = new Behavior< CHeadlessHatman >( new CHeadlessHatmanBehavior );
}
void CHeadlessHatmanIntention::Update( void )
{
m_behavior->Update( static_cast< CHeadlessHatman * >( GetBot() ), GetUpdateInterval() );
}
// is this a place we can be?
QueryResultType CHeadlessHatmanIntention::IsPositionAllowed( const INextBot *meBot, const Vector &pos ) const
{
return ANSWER_YES;
}
//---------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------
float CHeadlessHatmanLocomotion::GetRunSpeed( void ) const
{
return tf_halloween_bot_speed.GetFloat();
}
//---------------------------------------------------------------------------------------------
// if delta Z is greater than this, we have to jump to get up
float CHeadlessHatmanLocomotion::GetStepHeight( void ) const
{
return 18.0f;
}
//---------------------------------------------------------------------------------------------
// return maximum height of a jump
float CHeadlessHatmanLocomotion::GetMaxJumpHeight( void ) const
{
return 18.0f;
}
//---------------------------------------------------------------------------------------------
// Return max rate of yaw rotation
float CHeadlessHatmanLocomotion::GetMaxYawRate( void ) const
{
return 200.0f;
}
//---------------------------------------------------------------------------------------------
// Should we collide with this entity?
bool CHeadlessHatmanLocomotion::ShouldCollideWith( const CBaseEntity *object ) const
{
// don't collide with player in doomsday event
if ( TFGameRules()->IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_DOOMSDAY ) && object->IsPlayer() )
{
return false;
}
return BaseClass::ShouldCollideWith( object );
}
+205
View File
@@ -0,0 +1,205 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
// headless_hatman.h
// An NPC that spawns in the Halloween map and wreaks havok
// Michael Booth, October 2010
#ifndef HEADLESS_HATMAN_H
#define HEADLESS_HATMAN_H
#include "NextBot.h"
#include "NextBotBehavior.h"
#include "NextBotGroundLocomotion.h"
#include "headless_hatman_body.h"
#include "Path/NextBotPathFollow.h"
#include "halloween_base_boss.h"
extern ConVar tf_halloween_bot_health_base;
extern ConVar tf_halloween_bot_health_per_player;
extern ConVar tf_halloween_bot_min_player_count;
extern ConVar tf_halloween_bot_speed;
extern ConVar tf_halloween_bot_attack_range;
extern ConVar tf_halloween_bot_speed_recovery_rate;
extern ConVar tf_halloween_bot_speed_penalty;
extern ConVar tf_halloween_bot_chase_duration;
extern ConVar tf_halloween_bot_terrify_radius;
extern ConVar tf_halloween_bot_chase_range;
extern ConVar tf_halloween_bot_quit_range;
class CTFPlayer;
class CHeadlessHatman;
//----------------------------------------------------------------------------
class CHeadlessHatmanLocomotion : public NextBotGroundLocomotion
{
public:
CHeadlessHatmanLocomotion( INextBot *bot ) : NextBotGroundLocomotion( bot ) { }
virtual ~CHeadlessHatmanLocomotion() { }
virtual float GetRunSpeed( void ) const; // get maximum running speed
virtual float GetStepHeight( void ) const; // if delta Z is greater than this, we have to jump to get up
virtual float GetMaxJumpHeight( void ) const; // return maximum height of a jump
/**
* Should we collide with this entity?
*/
virtual bool ShouldCollideWith( const CBaseEntity *object ) const;
private:
virtual float GetMaxYawRate( void ) const; // return max rate of yaw rotation
};
//----------------------------------------------------------------------------
class CHeadlessHatmanIntention : public IIntention
{
public:
CHeadlessHatmanIntention( CHeadlessHatman *me );
virtual ~CHeadlessHatmanIntention();
virtual void Reset( void );
virtual void Update( void );
virtual QueryResultType IsPositionAllowed( const INextBot *me, const Vector &pos ) const; // is the a place we can be?
virtual INextBotEventResponder *FirstContainedResponder( void ) const { return m_behavior; }
virtual INextBotEventResponder *NextContainedResponder( INextBotEventResponder *current ) const { return NULL; }
private:
Behavior< CHeadlessHatman > *m_behavior;
};
//----------------------------------------------------------------------------
class CHeadlessHatman : public CHalloweenBaseBoss
{
public:
DECLARE_CLASS( CHeadlessHatman, CHalloweenBaseBoss );
DECLARE_SERVERCLASS();
CHeadlessHatman();
virtual ~CHeadlessHatman();
static void PrecacheHeadlessHatman();
virtual void Precache();
virtual void Spawn( void );
virtual int OnTakeDamage_Alive( const CTakeDamageInfo &info );
// INextBot
virtual CHeadlessHatmanIntention *GetIntentionInterface( void ) const { return m_intention; }
virtual CHeadlessHatmanLocomotion *GetLocomotionInterface( void ) const { return m_locomotor; }
virtual CHeadlessHatmanBody *GetBodyInterface( void ) const { return m_body; }
virtual void Update( void );
const Vector &GetHomePosition( void ) const;
CBaseAnimating *GetAxe( void ) const;
virtual HalloweenBossType GetBossType() const { return HALLOWEEN_BOSS_HHH; }
private:
const char *GetWeaponModel() const;
CHeadlessHatmanIntention *m_intention;
CHeadlessHatmanLocomotion *m_locomotor;
CHeadlessHatmanBody *m_body;
CBaseAnimating *m_axe;
CUtlVector< AttackerInfo > m_attackerVector; // list of everyone who injured me, and when
CountdownTimer m_painTimer;
Vector m_homePos;
int m_damagePoseParameter;
};
inline CBaseAnimating *CHeadlessHatman::GetAxe( void ) const
{
return m_axe;
}
inline const Vector &CHeadlessHatman::GetHomePosition( void ) const
{
return m_homePos;
}
//--------------------------------------------------------------------------------------------------------------
class CHeadlessHatmanPathCost : public IPathCost
{
public:
CHeadlessHatmanPathCost( CHeadlessHatman *me )
{
m_me = me;
}
// return the cost (weighted distance between) of moving from "fromArea" to "area", or -1 if the move is not allowed
virtual float operator()( CNavArea *area, CNavArea *fromArea, const CNavLadder *ladder, const CFuncElevator *elevator, float length ) const
{
if ( fromArea == NULL )
{
// first area in path, no cost
return 0.0f;
}
else
{
if ( !m_me->GetLocomotionInterface()->IsAreaTraversable( area ) )
{
// our locomotor says we can't move here
return -1.0f;
}
// compute distance traveled along path so far
float dist;
if ( ladder )
{
dist = ladder->m_length;
}
else if ( length > 0.0 )
{
// optimization to avoid recomputing length
dist = length;
}
else
{
dist = ( area->GetCenter() - fromArea->GetCenter() ).Length();
}
float cost = dist + fromArea->GetCostSoFar();
// check height change
float deltaZ = fromArea->ComputeAdjacentConnectionHeightChange( area );
if ( deltaZ >= m_me->GetLocomotionInterface()->GetStepHeight() )
{
if ( deltaZ >= m_me->GetLocomotionInterface()->GetMaxJumpHeight() )
{
// too high to reach
return -1.0f;
}
// jumping is slower than flat ground
const float jumpPenalty = 5.0f;
cost += jumpPenalty * dist;
}
else if ( deltaZ < -m_me->GetLocomotionInterface()->GetDeathDropHeight() )
{
// too far to drop
return -1.0f;
}
return cost;
}
}
CHeadlessHatman *m_me;
};
#endif // HEADLESS_HATMAN_H
@@ -0,0 +1,114 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
#include "cbase.h"
#include "NextBot.h"
#include "headless_hatman.h"
#include "headless_hatman_body.h"
//-------------------------------------------------------------------------------------------
CHeadlessHatmanBody::CHeadlessHatmanBody( INextBot *bot ) : IBody( bot )
{
m_moveXPoseParameter = -1;
m_moveYPoseParameter = -1;
m_currentActivity = -1;
}
//-------------------------------------------------------------------------------------------
bool CHeadlessHatmanBody::StartActivity( Activity act, unsigned int flags )
{
CBaseCombatCharacter *me = (CBaseCombatCharacter *)GetBot()->GetEntity();
int animSequence = ::SelectWeightedSequence( me->GetModelPtr(), act, me->GetSequence() );
if ( animSequence )
{
m_currentActivity = act;
me->SetSequence( animSequence );
me->SetPlaybackRate( 1.0f );
me->SetCycle( 0 );
me->ResetSequenceInfo();
return true;
}
return false;
}
//-------------------------------------------------------------------------------------------
void CHeadlessHatmanBody::Update( void )
{
CBaseCombatCharacter *me = (CBaseCombatCharacter *)GetBot()->GetEntity();
if ( m_moveXPoseParameter < 0 )
{
m_moveXPoseParameter = me->LookupPoseParameter( "move_x" );
}
if ( m_moveYPoseParameter < 0 )
{
m_moveYPoseParameter = me->LookupPoseParameter( "move_y" );
}
// Update the pose parameters
float speed = GetBot()->GetLocomotionInterface()->GetGroundSpeed(); // me->GetAbsVelocity().Length();
if ( speed < 0.01f )
{
// stopped
if ( m_moveXPoseParameter >= 0 )
{
me->SetPoseParameter( m_moveXPoseParameter, 0.0f );
}
if ( m_moveYPoseParameter >= 0 )
{
me->SetPoseParameter( m_moveYPoseParameter, 0.0f );
}
}
else
{
Vector forward, right, up;
me->GetVectors( &forward, &right, &up );
const Vector &motionVector = GetBot()->GetLocomotionInterface()->GetGroundMotionVector();
// move_x == 1.0 at full forward motion and -1.0 in full reverse
if ( m_moveXPoseParameter >= 0 )
{
float forwardVel = DotProduct( motionVector, forward );
me->SetPoseParameter( m_moveXPoseParameter, forwardVel );
}
if ( m_moveYPoseParameter >= 0 )
{
float sideVel = DotProduct( motionVector, right );
me->SetPoseParameter( m_moveYPoseParameter, sideVel );
}
}
// adjust animation speed to actual movement speed
if ( me->m_flGroundSpeed > 0.0f )
{
// Clamp playback rate to avoid datatable warnings. Anything faster would look silly, anyway.
float playbackRate = clamp( speed / me->m_flGroundSpeed, -4.f, 12.f );
me->SetPlaybackRate( playbackRate );
}
// move the animation ahead in time
me->StudioFrameAdvance();
me->DispatchAnimEvents( me );
}
//---------------------------------------------------------------------------------------------
// return the bot's collision mask (hack until we get a general hull trace abstraction here or in the locomotion interface)
unsigned int CHeadlessHatmanBody::GetSolidMask( void ) const
{
return MASK_NPCSOLID | CONTENTS_PLAYERCLIP;
}
@@ -0,0 +1,47 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
#ifndef HEADLESS_HATMAN_BODY_H
#define HEADLESS_HATMAN_BODY_H
#include "animation.h"
#include "NextBotBodyInterface.h"
class INextBot;
//----------------------------------------------------------------------------------------------------------------
/**
* The interface for control and information about the bot's body state (posture, animation state, etc)
*/
class CHeadlessHatmanBody : public IBody
{
public:
CHeadlessHatmanBody( INextBot *bot );
virtual ~CHeadlessHatmanBody() { }
virtual void Update( void );
virtual bool StartActivity( Activity act, unsigned int flags = 0 );
virtual Activity GetActivity( void ) const; // return currently animating activity
virtual bool IsActivity( Activity act ) const; // return true if currently animating activity matches the given one
virtual unsigned int GetSolidMask( void ) const; // return the bot's collision mask (hack until we get a general hull trace abstraction here or in the locomotion interface)
private:
int m_currentActivity;
int m_moveXPoseParameter;
int m_moveYPoseParameter;
};
inline Activity CHeadlessHatmanBody::GetActivity( void ) const
{
return (Activity)m_currentActivity;
}
inline bool CHeadlessHatmanBody::IsActivity( Activity act ) const
{
return act == m_currentActivity ? true : false;
}
#endif // HEADLESS_HATMAN_BODY_H
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,412 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
//
//
//=============================================================================
#ifndef MERASMUS_H
#define MERASMUS_H
#include "NextBot.h"
#include "NextBotBehavior.h"
#include "NextBotGroundLocomotion.h"
#include "merasmus_body.h"
#include "Path/NextBotPathFollow.h"
#include "../halloween_base_boss.h"
extern ConVar tf_merasmus_health_base;
extern ConVar tf_merasmus_health_per_player;
extern ConVar tf_merasmus_min_player_count;
extern ConVar tf_merasmus_speed;
extern ConVar tf_merasmus_attack_range;
extern ConVar tf_merasmus_speed_recovery_rate;
extern ConVar tf_merasmus_speed_penalty;
extern ConVar tf_merasmus_chase_duration;
extern ConVar tf_merasmus_chase_range;
extern ConVar tf_merasmus_health_regen_rate;
extern ConVar tf_merasmus_bomb_head_duration;
extern ConVar tf_merasmus_bomb_head_per_team;
class CTFPlayer;
class CWheelOfDoom;
class CMerasmus;
class CTFWeaponBaseGrenadeProj;
class CTFMerasmusTrickOrTreatProp;
class CMonsterResource;
//----------------------------------------------------------------------------
class CMerasmusSWStats
{
public:
void ResetStats ()
{
V_memset( m_arrClassDamage, 0, sizeof( m_arrClassDamage ) );
m_flPropHuntTime1 = 0;
m_flPropHuntTime2 = 0;
m_flLifeTime = 0;
m_nBombKills = 0;
m_nStaffKills = 0;
m_nPvpKills = 0;
}
int m_arrClassDamage[ TF_LAST_NORMAL_CLASS ];
float m_flPropHuntTime1;
float m_flPropHuntTime2;
float m_flLifeTime;
int m_nBombKills;
int m_nStaffKills;
int m_nPvpKills;
};
//----------------------------------------------------------------------------
class CMerasmusLocomotion : public NextBotGroundLocomotion
{
public:
CMerasmusLocomotion( INextBot *bot ) : NextBotGroundLocomotion( bot ) { }
virtual ~CMerasmusLocomotion() { }
virtual void Update( void ); // (EXTEND) update internal state
virtual float GetRunSpeed( void ) const; // get maximum running speed
virtual float GetStepHeight( void ) const; // if delta Z is greater than this, we have to jump to get up
virtual float GetMaxJumpHeight( void ) const; // return maximum height of a jump
/**
* Should we collide with this entity?
*/
virtual bool ShouldCollideWith( const CBaseEntity *object ) const;
private:
virtual float GetMaxYawRate( void ) const; // return max rate of yaw rotation
};
//----------------------------------------------------------------------------
class CMerasmusFlyingLocomotion : public ILocomotion
{
public:
CMerasmusFlyingLocomotion( INextBot *bot );
virtual ~CMerasmusFlyingLocomotion();
virtual void Reset( void ); // (EXTEND) reset to initial state
virtual void Update( void ); // (EXTEND) update internal state
virtual void Approach( const Vector &goalPos, float goalWeight = 1.0f ); // (EXTEND) move directly towards the given position
virtual float GetDesiredSpeed( void ) const; // returns the current desired speed
virtual void SetDesiredAltitude( float height ); // how high above our Approach goal do we float?
virtual float GetDesiredAltitude( void ) const;
virtual const Vector &GetGroundNormal( void ) const; // surface normal of the ground we are in contact with
virtual const Vector &GetVelocity( void ) const; // return current world space velocity
void SetVelocity( const Vector &velocity );
virtual bool ShouldCollideWith( const CBaseEntity *object ) const;
virtual void FaceTowards( const Vector &target ); // rotate body to face towards "target"
protected:
float m_currentSpeed;
Vector m_forward;
float m_desiredAltitude;
void MaintainAltitude( void );
Vector m_velocity;
Vector m_acceleration;
};
inline const Vector &CMerasmusFlyingLocomotion::GetGroundNormal( void ) const
{
static Vector up( 0, 0, 1.0f );
return up;
}
inline const Vector &CMerasmusFlyingLocomotion::GetVelocity( void ) const
{
return m_velocity;
}
inline void CMerasmusFlyingLocomotion::SetVelocity( const Vector &velocity )
{
m_velocity = velocity;
}
//----------------------------------------------------------------------------
class CMerasmusIntention : public IIntention
{
public:
CMerasmusIntention( CMerasmus *me );
virtual ~CMerasmusIntention();
virtual void Reset( void );
virtual void Update( void );
virtual QueryResultType IsPositionAllowed( const INextBot *me, const Vector &pos ) const; // is the a place we can be?
virtual INextBotEventResponder *FirstContainedResponder( void ) const { return m_behavior; }
virtual INextBotEventResponder *NextContainedResponder( INextBotEventResponder *current ) const { return NULL; }
private:
Behavior< CMerasmus > *m_behavior;
};
DECLARE_AUTO_LIST( IMerasmusAutoList );
//----------------------------------------------------------------------------
class CMerasmus : public CHalloweenBaseBoss, public CGameEventListener, public IMerasmusAutoList
{
public:
DECLARE_CLASS( CMerasmus, CHalloweenBaseBoss );
DECLARE_SERVERCLASS();
CMerasmus();
virtual ~CMerasmus();
static void PrecacheMerasmus();
virtual void Precache();
virtual void Spawn( void );
virtual void UpdateOnRemove();
virtual int OnTakeDamage_Alive( const CTakeDamageInfo &info );
// CGameEventListener
virtual void FireGameEvent( IGameEvent *event );
// INextBot
virtual CMerasmusIntention *GetIntentionInterface( void ) const { return m_intention; }
virtual ILocomotion *GetLocomotionInterface( void ) const { if ( m_isFlying ) return m_flyingLocomotor; return m_locomotor; }
virtual CMerasmusBody *GetBodyInterface( void ) const { return m_body; }
virtual void Update( void );
const Vector &GetHomePosition( void ) const;
Vector GetCastPosition() const;
bool IsRevealed() const { return m_bRevealed; }
void OnRevealed(bool bPlaySound = true);
bool ShouldReveal() const;
bool IsNextKilledPropMerasmus() const;
void SetRevealer( CTFPlayer* pPlayer ) { m_hMerasmusRevealer = pPlayer; }
void OnDisguise();
bool ShouldDisguise() const;
void StartAOEAttack() { m_bIsDoingAOEAttack = true; }
void StopAOEAttack() { m_bIsDoingAOEAttack = false; }
bool IsDoingAOEAttack() const { return m_bIsDoingAOEAttack; }
static CTFWeaponBaseGrenadeProj* CreateMerasmusGrenade( const Vector& vPosition, const Vector& vVelocity, CBaseCombatCharacter* pOwner, float fScale = 1.0f );
static const char* GetRandomPropModelName();
void PushPlayer( CTFPlayer* pPlayer, float flPushForce ) const;
int GetBombHitCount() const { return m_nBombHitCount; }
void ResetBombHitCount() { m_nBombHitCount = 0; }
void AddStun( CTFPlayer* pPlayer );
void OnBeginStun();
void OnEndStun();
bool HasStunTimer() const { return !m_stunTimer.IsElapsed(); }
bool IsStunned() const { return m_bStunned; }
void AddFakeProp( CTFMerasmusTrickOrTreatProp* pFakeProp );
void RemoveAllFakeProps();
void BombHeadMode();
bool ShouldLeave() const;
void LeaveWarning();
void OnLeaveWhileInPropForm();
void TriggerLogicRelay( const char* pszLogicRelayName, bool bSpawn = false );
void StartFlying( void ) { m_isFlying = true; }
void StopFlying( void ) { m_isFlying = false; }
bool IsFlying( void ) const { return m_isFlying; }
bool IsHiding( void ) const { return m_isHiding; }
void PlayLowPrioritySound( IRecipientFilter &filter, const char* pszSoundEntryName );
void PlayHighPrioritySound( const char* pszSoundEntryName );
void GainLevel( void );
void ResetLevel( void );
static int GetMerasmusLevel() { return m_level; }
virtual int GetLevel( void ) const OVERRIDE;
static void DBG_SetLevel( int nLevel );
virtual HalloweenBossType GetBossType() const { return HALLOWEEN_BOSS_MERASMUS; }
void StartRespawnTimer() const;
const CUtlVector< CHandle<CTFPlayer> >& GetStartingAttackers() const;
static bool Zap( CBaseCombatCharacter *pCaster, const char* pszCastingAttachmentName, float flSpellRange, float flMinDamage, float flMaxDamage, int nMaxTarget, int nTargetTeam = TEAM_ANY );
// Stats
void RecordDisguiseTime( );
void SW_ReportMerasmusStats( void );
private:
CMerasmusIntention *m_intention;
CMerasmusLocomotion *m_locomotor;
CMerasmusFlyingLocomotion *m_flyingLocomotor;
CMerasmusBody *m_body;
bool m_isFlying;
bool m_isHiding;
CUtlVector< AttackerInfo > m_attackerVector; // list of everyone who injured me, and when
CUtlVector< CHandle<CTFPlayer> > m_startingAttackersVector;
CountdownTimer m_stunTimer;
int m_nBombHitCount;
Vector m_homePos;
int m_damagePoseParameter;
int m_nRevealedHealth;
CHandle< CWheelOfDoom > m_wheel;
CHandle< CMonsterResource > m_hHealthBar;
CNetworkVar( bool, m_bRevealed );
CNetworkVar( bool, m_bIsDoingAOEAttack );
CNetworkVar( bool, m_bStunned );
CSoundPatch *m_pIdleSound;
CUtlVector< CHandle< CTFMerasmusTrickOrTreatProp > > m_fakePropVector;
int m_nDestroyedPropsToReveal;
SolidType_t m_solidType;
int m_solidFlags;
CHandle< CTFPlayer > m_hMerasmusRevealer;
CountdownTimer m_lifeTimer;
float m_flLastWarnTime;
// For Stats
float m_flStartDisguiseTime;
CMerasmusSWStats m_bossStats;
static int m_level;
};
inline int CMerasmus::GetLevel( void ) const
{
return m_level;
}
inline void CMerasmus::GainLevel( void )
{
++m_level;
}
inline void CMerasmus::ResetLevel( void )
{
m_level = 1;
}
inline void CMerasmus::DBG_SetLevel( int nLevel )
{
m_level = nLevel;
}
inline const Vector &CMerasmus::GetHomePosition( void ) const
{
return m_homePos;
}
inline const CUtlVector< CHandle<CTFPlayer> >& CMerasmus::GetStartingAttackers() const
{
return m_startingAttackersVector;
}
//--------------------------------------------------------------------------------------------------------------
class CMerasmusPathCost : public IPathCost
{
public:
CMerasmusPathCost( CMerasmus *me )
{
m_me = me;
}
// return the cost (weighted distance between) of moving from "fromArea" to "area", or -1 if the move is not allowed
virtual float operator()( CNavArea *area, CNavArea *fromArea, const CNavLadder *ladder, const CFuncElevator *elevator, float length ) const
{
if ( fromArea == NULL )
{
// first area in path, no cost
return 0.0f;
}
else
{
if ( !m_me->GetLocomotionInterface()->IsAreaTraversable( area ) )
{
// our locomotor says we can't move here
return -1.0f;
}
// compute distance traveled along path so far
float dist;
if ( ladder )
{
dist = ladder->m_length;
}
else if ( length > 0.0 )
{
// optimization to avoid recomputing length
dist = length;
}
else
{
dist = ( area->GetCenter() - fromArea->GetCenter() ).Length();
}
float cost = dist + fromArea->GetCostSoFar();
// check height change
float deltaZ = fromArea->ComputeAdjacentConnectionHeightChange( area );
if ( deltaZ >= m_me->GetLocomotionInterface()->GetStepHeight() )
{
if ( deltaZ >= m_me->GetLocomotionInterface()->GetMaxJumpHeight() )
{
// too high to reach
return -1.0f;
}
// jumping is slower than flat ground
const float jumpPenalty = 5.0f;
cost += jumpPenalty * dist;
}
else if ( deltaZ < -m_me->GetLocomotionInterface()->GetDeathDropHeight() )
{
// too far to drop
return -1.0f;
}
return cost;
}
}
CMerasmus *m_me;
};
#endif // MERASMUS_H
@@ -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();
}
@@ -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
@@ -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 );
}
@@ -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
@@ -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 ) );
}
@@ -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
@@ -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();
}
@@ -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
@@ -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();
}
@@ -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
@@ -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();
}
@@ -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
@@ -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();
}
@@ -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
@@ -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();
}
@@ -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
@@ -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 );
}
@@ -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
@@ -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;
}
}
@@ -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
@@ -0,0 +1,118 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
//
//
//=============================================================================
#include "cbase.h"
#include "NextBot.h"
#include "merasmus.h"
#include "merasmus_body.h"
//-------------------------------------------------------------------------------------------
CMerasmusBody::CMerasmusBody( INextBot *bot ) : IBody( bot )
{
m_moveXPoseParameter = -1;
m_moveYPoseParameter = -1;
m_currentActivity = -1;
}
//-------------------------------------------------------------------------------------------
bool CMerasmusBody::StartActivity( Activity act, unsigned int flags )
{
CMerasmus *me = (CMerasmus *)GetBot()->GetEntity();
int animSequence = ::SelectWeightedSequence( me->GetModelPtr(), act, me->GetSequence() );
if ( animSequence )
{
m_currentActivity = act;
me->SetSequence( animSequence );
me->SetPlaybackRate( 1.0f );
me->SetCycle( 0 );
me->ResetSequenceInfo();
return true;
}
return false;
}
//-------------------------------------------------------------------------------------------
void CMerasmusBody::Update( void )
{
CMerasmus *me = (CMerasmus *)GetBot()->GetEntity();
if ( m_moveXPoseParameter < 0 )
{
m_moveXPoseParameter = me->LookupPoseParameter( "move_x" );
}
if ( m_moveYPoseParameter < 0 )
{
m_moveYPoseParameter = me->LookupPoseParameter( "move_y" );
}
// Update the pose parameters
float speed = me->GetLocomotionInterface()->GetGroundSpeed(); // me->GetAbsVelocity().Length();
if ( speed < 0.01f )
{
// stopped
if ( m_moveXPoseParameter >= 0 )
{
me->SetPoseParameter( m_moveXPoseParameter, 0.0f );
}
if ( m_moveYPoseParameter >= 0 )
{
me->SetPoseParameter( m_moveYPoseParameter, 0.0f );
}
}
else
{
Vector forward, right, up;
me->GetVectors( &forward, &right, &up );
const Vector &motionVector = me->GetLocomotionInterface()->GetGroundMotionVector();
// move_x == 1.0 at full forward motion and -1.0 in full reverse
if ( m_moveXPoseParameter >= 0 )
{
float forwardVel = DotProduct( motionVector, forward );
me->SetPoseParameter( m_moveXPoseParameter, forwardVel );
}
if ( m_moveYPoseParameter >= 0 )
{
float sideVel = DotProduct( motionVector, right );
me->SetPoseParameter( m_moveYPoseParameter, sideVel );
}
}
// adjust animation speed to actual movement speed
if ( me->m_flGroundSpeed > 0.0f )
{
// Clamp playback rate to avoid datatable warnings. Anything faster would look silly, anyway.
float playbackRate = clamp( speed / me->m_flGroundSpeed, -4.f, 12.f );
me->SetPlaybackRate( playbackRate );
}
// move the animation ahead in time
me->StudioFrameAdvance();
me->DispatchAnimEvents( me );
}
//---------------------------------------------------------------------------------------------
// return the bot's collision mask (hack until we get a general hull trace abstraction here or in the locomotion interface)
unsigned int CMerasmusBody::GetSolidMask( void ) const
{
return MASK_NPCSOLID | CONTENTS_PLAYERCLIP;
}
@@ -0,0 +1,51 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
//
//
//=============================================================================
#ifndef MERASMUS_BODY_H
#define MERASMUS_BODY_H
#include "animation.h"
#include "NextBotBodyInterface.h"
class INextBot;
//----------------------------------------------------------------------------------------------------------------
/**
* The interface for control and information about the bot's body state (posture, animation state, etc)
*/
class CMerasmusBody : public IBody
{
public:
CMerasmusBody( INextBot *bot );
virtual ~CMerasmusBody() { }
virtual void Update( void );
virtual bool StartActivity( Activity act, unsigned int flags = 0 );
virtual Activity GetActivity( void ) const; // return currently animating activity
virtual bool IsActivity( Activity act ) const; // return true if currently animating activity matches the given one
virtual unsigned int GetSolidMask( void ) const; // return the bot's collision mask (hack until we get a general hull trace abstraction here or in the locomotion interface)
private:
int m_currentActivity;
int m_moveXPoseParameter;
int m_moveYPoseParameter;
};
inline Activity CMerasmusBody::GetActivity( void ) const
{
return (Activity)m_currentActivity;
}
inline bool CMerasmusBody::IsActivity( Activity act ) const
{
return act == m_currentActivity ? true : false;
}
#endif // MERASMUS_BODY_H
@@ -0,0 +1,172 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================//
#include "cbase.h"
#include "tf_gamerules.h"
#include "merasmus_dancer.h"
#include "animation.h"
//-----------------------------------------------------------------------------
#include "tf_fx.h"
//-----------------------------------------------------------------------------
#define POOF_SOUND "Halloween.Merasmus_Hiding_Explode"
//-----------------------------------------------------------------------------
LINK_ENTITY_TO_CLASS( merasmus_dancer, CMerasmusDancer );
IMPLEMENT_SERVERCLASS_ST( CMerasmusDancer, DT_MerasmusDancer )
END_SEND_TABLE()
//-----------------------------------------------------------------------------
#define MERASMUS_MODEL_NAME "models/bots/merasmus/merasmus.mdl"
//-----------------------------------------------------------------------------
CMerasmusDancer::CMerasmusDancer()
: m_bEmitParticleEffect( false )
{
}
//-----------------------------------------------------------------------------
CMerasmusDancer::~CMerasmusDancer()
{
}
//-----------------------------------------------------------------------------
void CMerasmusDancer::Spawn()
{
Precache();
m_DieCountdownTimer.Invalidate();
BaseClass::Spawn();
SetModel( MERASMUS_MODEL_NAME );
UseClientSideAnimation();
SetThink( &CMerasmusDancer::DanceThink );
SetNextThink( gpGlobals->curtime );
m_bEmitParticleEffect = true;
}
//-----------------------------------------------------------------------------
void CMerasmusDancer::PlaySequence( const char *pSeqName )
{
int iAnimSequence = LookupSequence( pSeqName ); // dance animation
if ( iAnimSequence )
{
SetSequence( iAnimSequence );
SetPlaybackRate( 1.0f );
SetCycle( 0 );
ResetSequenceInfo();
HideStaff();
}
}
//-----------------------------------------------------------------------------
void CMerasmusDancer::PlayActivity( int iActivity )
{
int iAnimSequence = ::SelectWeightedSequence( GetModelPtr(), iActivity, GetSequence() );
if ( iAnimSequence )
{
SetSequence( iAnimSequence );
SetPlaybackRate( 1.0f );
SetCycle( 0 );
ResetSequenceInfo();
HideStaff();
}
}
//-----------------------------------------------------------------------------
void CMerasmusDancer::HideStaff()
{
int nStaffBodyGroup = FindBodygroupByName( "staff" );
SetBodygroup( nStaffBodyGroup, 2 );
}
//-----------------------------------------------------------------------------
void CMerasmusDancer::Dance()
{
PlaySequence( "taunt06" );
}
//-----------------------------------------------------------------------------
void CMerasmusDancer::Vanish()
{
m_bEmitParticleEffect = true;
m_DieCountdownTimer.Start( 0.0f );
}
//-----------------------------------------------------------------------------
void CMerasmusDancer::BlastOff()
{
m_bEmitParticleEffect = true;
m_DieCountdownTimer.Start( 0.3f );
PlayActivity( ACT_FLY );
}
//-----------------------------------------------------------------------------
void CMerasmusDancer::Precache()
{
BaseClass::Precache();
int model = PrecacheModel( MERASMUS_MODEL_NAME );
PrecacheGibsForModel( model );
PrecacheParticleSystem( "merasmus_tp" ); // puff effect
PrecacheScriptSound( POOF_SOUND );
// We deliberately allow late precaches here.
bool bAllowPrecache = CBaseAnimating::IsPrecacheAllowed();
CBaseAnimating::SetAllowPrecache( bAllowPrecache );
}
//-----------------------------------------------------------------------------
bool CMerasmusDancer::ShouldDelete() const
{
return m_DieCountdownTimer.HasStarted() && m_DieCountdownTimer.IsElapsed();
}
//-----------------------------------------------------------------------------
void CMerasmusDancer::DanceThink()
{
// Emit the initial effect here, rather than in Spawn(), since GetAbsOrigin() and GetAbsAngles() don't return useful values then.
if ( m_bEmitParticleEffect )
{
DispatchParticleEffect( "merasmus_tp", GetAbsOrigin(), GetAbsAngles() );
m_bEmitParticleEffect = false;
EmitSound( POOF_SOUND );
}
if ( ShouldDelete() )
{
EmitSound( POOF_SOUND );
UTIL_Remove( this );
return;
}
SetNextThink( gpGlobals->curtime );
}
@@ -0,0 +1,49 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Teleport vortex for the Eyeball Boss
//
//=============================================================================//
#ifndef MERASMUS_DANCE_H
#define MERASMUS_DANCE_H
#ifdef _WIN32
#pragma once
#endif
#include "baseanimating.h"
//=============================================================================
//
// Non-AI version of Merasmus that can be spawned during the dance spell.
//
class CMerasmusDancer : public CBaseAnimating
{
DECLARE_CLASS( CMerasmusDancer, CBaseAnimating );
DECLARE_SERVERCLASS();
public:
CMerasmusDancer();
virtual ~CMerasmusDancer();
void Dance();
void Vanish();
void BlastOff();
private:
virtual void Spawn();
virtual void Precache();
void HideStaff();
void PlaySequence( const char *pSeqName );
void PlayActivity( int iActivity );
void DanceThink();
bool ShouldDelete() const;
bool m_bEmitParticleEffect;
CountdownTimer m_DieCountdownTimer;
};
#endif // MERASMUS_DANCE_H
@@ -0,0 +1,178 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
//
//
//=============================================================================
#include "cbase.h"
#include "tf_ammo_pack.h"
#include "particle_parse.h"
#include "tf/halloween/ghost/ghost.h"
#include "tf_player.h"
#include "tf_gamerules.h"
#include "merasmus.h"
#include "merasmus_trick_or_treat_prop.h"
LINK_ENTITY_TO_CLASS( tf_merasmus_trick_or_treat_prop, CTFMerasmusTrickOrTreatProp );
IMPLEMENT_AUTO_LIST( ITFMerasmusTrickOrTreatProp );
ConVar tf_merasmus_prop_health( "tf_merasmus_prop_health", "150", FCVAR_CHEAT | FCVAR_GAMEDLL );
CTFMerasmusTrickOrTreatProp::CTFMerasmusTrickOrTreatProp()
{
}
void CTFMerasmusTrickOrTreatProp::Spawn()
{
Precache();
SetModel( CMerasmus::GetRandomPropModelName() );
BaseClass::Spawn();
SetMoveType( MOVETYPE_NONE );
SetSolid( SOLID_VPHYSICS );
m_takedamage = DAMAGE_YES;
SetHealth( tf_merasmus_prop_health.GetInt() );
DispatchParticleEffect( "merasmus_object_spawn", WorldSpaceCenter(), GetAbsAngles() );
}
void CTFMerasmusTrickOrTreatProp::Event_Killed( const CTakeDamageInfo &info )
{
SpawnTrickOrTreatItem();
DispatchParticleEffect( "merasmus_object_spawn", WorldSpaceCenter(), GetAbsAngles() );
EmitSound( "Halloween.Merasmus_Hiding_Explode" );
if ( TFGameRules()->GetActiveBoss() )
{
CMerasmus* pMerasmus = assert_cast< CMerasmus* >( TFGameRules()->GetActiveBoss() );
if ( pMerasmus )
{
if ( pMerasmus->IsNextKilledPropMerasmus() )
{
// move merasmus to the destroyed prop before we reveal him
pMerasmus->SetAbsOrigin( GetAbsOrigin() );
if ( info.GetAttacker() && info.GetAttacker()->IsPlayer() )
{
CTFPlayer* pTFPlayer = assert_cast< CTFPlayer* >( info.GetAttacker() );
if ( pTFPlayer )
{
pMerasmus->SetRevealer( pTFPlayer );
IGameEvent *pEvent = gameeventmanager->CreateEvent( "merasmus_prop_found" );
if ( pEvent )
{
pEvent->SetInt( "player", pTFPlayer->GetUserID() );
gameeventmanager->FireEvent( pEvent, true );
}
}
}
}
else
{
CPVSFilter filter( pMerasmus->WorldSpaceCenter() );
if (RandomInt(1,3) == 1)
{
pMerasmus->PlayLowPrioritySound( filter, "Halloween.MerasmusTauntFakeProp" );
}
}
}
}
BaseClass::Event_Killed( info );
}
int CTFMerasmusTrickOrTreatProp::OnTakeDamage( const CTakeDamageInfo &info )
{
DispatchParticleEffect( "merasmus_blood", info.GetDamagePosition(), GetAbsAngles() );
CTakeDamageInfo newinfo = info;
CTFPlayer *pTFPlayer = ToTFPlayer( newinfo.GetAttacker() );
if ( pTFPlayer && ( pTFPlayer->IsPlayerClass( TF_CLASS_SOLDIER ) || pTFPlayer->IsPlayerClass( TF_CLASS_DEMOMAN ) ) && ( newinfo.GetDamageType() & DMG_BLAST ) )
{
newinfo.SetDamage( GetHealth() * 2.f );
}
return BaseClass::OnTakeDamage( newinfo );
}
void CTFMerasmusTrickOrTreatProp::Touch( CBaseEntity *pOther )
{
BaseClass::Touch( pOther );
if ( pOther && pOther->IsPlayer() )
{
CTFPlayer *pPlayer = ToTFPlayer( pOther );
if ( pPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_BOMB_HEAD ) )
{
pPlayer->m_Shared.RemoveCond( TF_COND_HALLOWEEN_BOMB_HEAD );
pPlayer->m_Shared.RemoveCond( TF_COND_STUNNED );
pPlayer->MerasmusPlayerBombExplode( false );
// force kill
CTakeDamageInfo info( pPlayer, pPlayer, 99999, DMG_BLAST, TF_DMG_CUSTOM_NONE );
Event_Killed( info );
}
}
}
CTFMerasmusTrickOrTreatProp* CTFMerasmusTrickOrTreatProp::Create( const Vector& vPosition, const QAngle& qAngles )
{
CTFMerasmusTrickOrTreatProp *pTrickOrTreatProp = static_cast<CTFMerasmusTrickOrTreatProp*>( CBaseEntity::Create( "tf_merasmus_trick_or_treat_prop", vPosition, qAngles, NULL ) );
// must be on a team different from player(s) in order for some
// weapons to hit (ie: pipe bombs)
if ( pTrickOrTreatProp )
{
pTrickOrTreatProp->ChangeTeam( TF_TEAM_HALLOWEEN );
}
return pTrickOrTreatProp;
}
void CTFMerasmusTrickOrTreatProp::SpawnTrickOrTreatItem()
{
int nNumAmmo = 1/*RandomInt( 1, 3 )*/;
for ( int i=0; i<nNumAmmo; ++i )
{
// Create the ammo pack.
CTFAmmoPack *pAmmoPack = CTFAmmoPack::Create( WorldSpaceCenter(), vec3_angle, this, "models/items/ammopack_medium.mdl" );
Assert( pAmmoPack );
if ( pAmmoPack )
{
pAmmoPack->MakeHolidayPack();
pAmmoPack->SetBonusScale( 2.f );
pAmmoPack->SetModelScale( 1.4f );
Vector vecRight, vecUp;
AngleVectors( EyeAngles(), NULL, &vecRight, &vecUp );
// Calculate the initial impulse on the weapon.
Vector vecImpulse = RandomVector( 40.f, 80.f );
vecImpulse.z *= Sign( vecImpulse.z ); // always go up
pAmmoPack->SetInitialVelocity( vecImpulse );
pAmmoPack->ApplyAbsVelocityImpulse( vecImpulse );
// Give the ammo pack some health, so that trains can destroy it.
pAmmoPack->SetCollisionGroup( COLLISION_GROUP_DEBRIS );
pAmmoPack->m_takedamage = DAMAGE_YES;
pAmmoPack->SetHealth( 900 );
pAmmoPack->SetBodygroup( 1, 1 );
}
}
}
@@ -0,0 +1,30 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
//
//
//=============================================================================
#ifndef MERASMUS_TRICK_OR_TREAT_PROP_H
#define MERASMUS_TRICK_OR_TREAT_PROP_H
DECLARE_AUTO_LIST( ITFMerasmusTrickOrTreatProp );
class CTFMerasmusTrickOrTreatProp : public CBaseAnimating, public ITFMerasmusTrickOrTreatProp
{
DECLARE_CLASS( CTFMerasmusTrickOrTreatProp, CBaseAnimating );
public:
CTFMerasmusTrickOrTreatProp();
~CTFMerasmusTrickOrTreatProp() {}
virtual void Spawn( void );
virtual void Event_Killed( const CTakeDamageInfo &info );
virtual int OnTakeDamage( const CTakeDamageInfo &info );
virtual void Touch( CBaseEntity *pOther );
static CTFMerasmusTrickOrTreatProp* Create( const Vector& vPosition, const QAngle& qAngles );
private:
void SpawnTrickOrTreatItem();
};
#endif // MERASMUS_TRICK_OR_TREAT_PROP_H
@@ -0,0 +1,111 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Spell
//
//=============================================================================//
#include "cbase.h"
#include "tf_spell_pickup.h"
#include "tf_player.h"
#include "halloween/tf_weapon_spellbook.h"
#include "tf_gamerules.h"
LINK_ENTITY_TO_CLASS( tf_spell_pickup, CSpellPickup );
BEGIN_DATADESC( CSpellPickup )
// Keyfields.
DEFINE_KEYFIELD( m_nTier, FIELD_INTEGER, "tier" ),
END_DATADESC();
//-----------------------------------------------------------------------------
CSpellPickup::CSpellPickup()
{
m_nTier = 0;
}
//-----------------------------------------------------------------------------
void CSpellPickup::Spawn( void )
{
BaseClass::Spawn();
m_nSkin = m_nTier;
}
//-----------------------------------------------------------------------------
void CSpellPickup::Precache( void )
{
BaseClass::Precache();
PrecacheScriptSound( "Halloween.spell_pickup" );
PrecacheScriptSound( "Halloween.spell_pickup_rare" );
}
//-----------------------------------------------------------------------------
bool CSpellPickup::MyTouch( CBasePlayer *pPlayer )
{
CTFPlayer *pTFPlayer = ToTFPlayer( pPlayer );
if ( pTFPlayer )
{
CTFSpellBook *pSpellBook = dynamic_cast< CTFSpellBook* >( pTFPlayer->GetEntityForLoadoutSlot( LOADOUT_POSITION_ACTION ) );
if ( pSpellBook )
{
pSpellBook->RollNewSpell( m_nTier );
CSingleUserRecipientFilter filter( pPlayer );
const char *pszSoundName = ( m_nTier > 0 ) ? "Halloween.spell_pickup_rare" : "Halloween.spell_pickup";
EmitSound( filter, entindex(), pszSoundName );
}
}
return true;
}
//-----------------------------------------------------------------------------
bool CSpellPickup::ItemCanBeTouchedByPlayer( CBasePlayer *pPlayer )
{
if ( IsDisabled() )
return false;
// Dont let them pick up new spells if they already have a spell unless its a tier 1 spell
CTFPlayer *pTFPlayer = ToTFPlayer( pPlayer );
if ( pTFPlayer && m_nTier == 0 )
{
CTFSpellBook *pSpellBook = dynamic_cast< CTFSpellBook* >( pTFPlayer->GetEntityForLoadoutSlot( LOADOUT_POSITION_ACTION ) );
if ( !pSpellBook )
{
// TEMP
ClientPrint( pPlayer, HUD_PRINTCENTER, "Equip a SpellBook in your ActionSlot to pick this up.", pPlayer->GetPlayerName() );
return false;
}
if ( pSpellBook->HasASpellWithCharges() )
{
return false;
}
}
return true;
}
//-----------------------------------------------------------------------------
const char *CSpellPickup::GetPowerupModel( void )
{
if ( TFGameRules() && TFGameRules()->IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_DOOMSDAY ) )
{
if ( m_nTier == 1 )
{
return "models/items/crystal_ball_pickup_major.mdl";
}
return "models/items/crystal_ball_pickup.mdl";
}
if ( m_nTier == 1 )
{
return "models/props_halloween/hwn_spellbook_upright_major.mdl";
}
return BaseClass::GetPowerupModel();
}
@@ -0,0 +1,37 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Spell.
//
//=============================================================================//
#ifndef TF_SPELL_PICKUP_H
#define TF_SPELL_PICKUP_H
#ifdef _WIN32
#pragma once
#endif
#include "tf_powerup.h"
class CSpellPickup : public CTFPowerup
{
DECLARE_CLASS( CSpellPickup, CTFPowerup )
DECLARE_DATADESC();
public:
CSpellPickup();
virtual void Spawn( void ) OVERRIDE;
virtual void Precache() OVERRIDE;
virtual bool MyTouch( CBasePlayer *pPlayer ) OVERRIDE;
virtual const char *GetPowerupModel( void ) OVERRIDE;
virtual const char *GetDefaultPowerupModel( void ) OVERRIDE { return "models/props_halloween/hwn_spellbook_upright.mdl"; }
virtual bool ItemCanBeTouchedByPlayer( CBasePlayer *pPlayer ) OVERRIDE;
void SetTier( int nTier ) { m_nTier = nTier; }
private:
int m_nTier;
};
#endif // TF_SPELL_PICKUP_H
+599
View File
@@ -0,0 +1,599 @@
//========= 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 "NextBot/Path/NextBotChasePath.h"
#include "particle_parse.h"
#include "zombie.h"
#include "zombie_behavior/zombie_spawn.h"
#include "halloween/tf_weapon_spellbook.h"
#define SKELETON_MODEL "models/bots/skeleton_sniper/skeleton_sniper.mdl"
#define SKELETON_KING_MODEL "models/bots/skeleton_sniper_boss/skeleton_sniper_boss.mdl"
#define SKELETON_KING_CROWN_MODEL "models/player/items/demo/crown.mdl"
ConVar tf_max_active_zombie( "tf_max_active_zombie", "30", FCVAR_CHEAT );
#ifdef STAGING_ONLY
ConVar tf_halloween_skeleton_test_hat( "tf_halloween_skeleton_test_hat", "-1", FCVAR_CHEAT );
#endif // STAGING_ONLY
//-----------------------------------------------------------------------------------------------------
// NPC Zombie versions of the players
//-----------------------------------------------------------------------------------------------------
LINK_ENTITY_TO_CLASS( tf_zombie, CZombie );
IMPLEMENT_SERVERCLASS_ST( CZombie, DT_Zombie )
SendPropFloat( SENDINFO( m_flHeadScale ) ),
END_SEND_TABLE()
IMPLEMENT_AUTO_LIST( IZombieAutoList );
static const char *s_skeletonHatModels[] =
{
"models/player/items/all_class/skull_scout.mdl",
"models/workshop/player/items/scout/hw2013_boston_bandy_mask/hw2013_boston_bandy_mask.mdl",
"models/workshop/player/items/demo/hw2013_blackguards_bicorn/hw2013_blackguards_bicorn.mdl",
"models/player/items/heavy/heavy_big_chief.mdl",
};
//-----------------------------------------------------------------------------------------------------
CZombie::CZombie()
{
m_intention = new CZombieIntention( this );
m_locomotor = new CZombieLocomotion( this );
m_body = new CHeadlessHatmanBody( this );
m_nType = SKELETON_NORMAL;
m_flHeadScale = 1.f;
m_flAttackRange = 50.f;
m_flAttackDamage = 30.f;
m_bSpy = false;
m_bForceSuicide = false;
}
//-----------------------------------------------------------------------------------------------------
CZombie::~CZombie()
{
if ( m_intention )
delete m_intention;
if ( m_locomotor )
delete m_locomotor;
if ( m_body )
delete m_body;
}
void CZombie::PrecacheZombie()
{
/*PrecacheModel( "models/player/items/scout/scout_zombie.mdl" );
PrecacheModel( "models/player/items/sniper/sniper_zombie.mdl" );
PrecacheModel( "models/player/items/soldier/soldier_zombie.mdl" );
PrecacheModel( "models/player/items/demo/demo_zombie.mdl" );
PrecacheModel( "models/player/items/medic/medic_zombie.mdl" );
PrecacheModel( "models/player/items/heavy/heavy_zombie.mdl" );
PrecacheModel( "models/player/items/pyro/pyro_zombie.mdl" );
PrecacheModel( "models/player/items/spy/spy_zombie.mdl" );
PrecacheModel( "models/player/items/engineer/engineer_zombie.mdl" );*/
int nSkeletonModel = PrecacheModel( SKELETON_MODEL );
PrecacheGibsForModel( nSkeletonModel );
int nSkeletonKingModel = PrecacheModel( SKELETON_KING_MODEL );
PrecacheGibsForModel( nSkeletonKingModel );
PrecacheModel( SKELETON_KING_CROWN_MODEL );
if( TFGameRules()->GetHalloweenScenario() == CTFGameRules::HALLOWEEN_SCENARIO_DOOMSDAY )
{
for ( int i=0; i<ARRAYSIZE( s_skeletonHatModels ) ; ++i )
{
PrecacheModel( s_skeletonHatModels[i] );
}
}
PrecacheParticleSystem( "bomibomicon_ring" );
PrecacheParticleSystem( "spell_pumpkin_mirv_goop_red" );
PrecacheParticleSystem( "spell_pumpkin_mirv_goop_blue" );
PrecacheParticleSystem( "spell_skeleton_goop_green" );
PrecacheScriptSound( "Halloween.skeleton_break" );
PrecacheScriptSound( "Halloween.skeleton_laugh_small" );
PrecacheScriptSound( "Halloween.skeleton_laugh_medium" );
PrecacheScriptSound( "Halloween.skeleton_laugh_giant" );
}
//-----------------------------------------------------------------------------------------------------
void CZombie::Precache()
{
BaseClass::Precache();
// These are player models which are already precached...
bool bAllowPrecache = CBaseEntity::IsPrecacheAllowed();
CBaseEntity::SetAllowPrecache( true );
PrecacheZombie();
CBaseEntity::SetAllowPrecache( bAllowPrecache );
}
//-----------------------------------------------------------------------------------------------------
void CZombie::Spawn( void )
{
Precache();
/*int which = RandomInt( TF_CLASS_SCOUT, TF_CLASS_ENGINEER );
const char *name = g_aRawPlayerClassNamesShort[ which ];
if ( FStrEq( name, "spy" ) )
{
m_bSpy = true;
}*/
//SetModel( CFmtStr( "models/player/%s.mdl", name ) );
SetModel( SKELETON_MODEL );
BaseClass::Spawn();
const int health = 50;
SetHealth( health );
SetMaxHealth( health );
AddFlag( FL_NPC );
QAngle qAngle = vec3_angle;
qAngle[YAW] = RandomFloat( 0, 360 );
SetAbsAngles( qAngle );
// Spawn Pos
GetBodyInterface()->StartActivity( ACT_TRANSITION );
//int iSkinIndex = GetTeamNumber() == TF_TEAM_RED ? 0 : 1;
//m_zombieParts = (CBaseAnimating *)CreateEntityByName( "prop_dynamic" );
//if ( m_zombieParts )
//{
// m_zombieParts->SetModel( CFmtStr( "models/player/items/%s/%s_zombie.mdl", name, name ) );
// m_zombieParts->m_nSkin = iSkinIndex;
// // bonemerge into our model
// m_zombieParts->FollowEntity( this, true );
//}
//if ( m_bSpy )
//{
// // Spy has a bunch of extra skins used to adjust the mask
// iSkinIndex += 22;
//}
//else
//{
// // 4: red zombie
// // 5: blue zombie
// // 6: red zombie invuln
// // 7: blue zombie invuln
// iSkinIndex += 4;
//}
switch ( GetTeamNumber() )
{
case TF_TEAM_RED:
m_nSkin = 0;
break;
case TF_TEAM_BLUE:
m_nSkin = 1;
break;
default:
{
m_nSkin = 2;
// make sure I'm on TF_TEAM_HALLOWEEN
ChangeTeam( TF_TEAM_HALLOWEEN );
}
}
// force kill oldest skeletons in the level (except skeleton king) to keep the number of skeletons under the max active
int nForceKill = IZombieAutoList::AutoList().Count() - tf_max_active_zombie.GetInt();
for ( int i=0; i<IZombieAutoList::AutoList().Count() && nForceKill > 0; ++i )
{
CZombie *pZombie = static_cast< CZombie* >( IZombieAutoList::AutoList()[i] );
if ( pZombie->GetSkeletonType() != SKELETON_KING )
{
pZombie->ForceSuicide();
nForceKill--;
}
}
Assert( nForceKill <= 0 );
}
//-----------------------------------------------------------------------------------------------------
int CZombie::OnTakeDamage_Alive( const CTakeDamageInfo &info )
{
if ( info.GetAttacker() && info.GetAttacker()->GetTeamNumber() == GetTeamNumber() )
return 0;
if ( !IsPlayingGesture( ACT_MP_GESTURE_FLINCH_CHEST ) )
{
AddGesture( ACT_MP_GESTURE_FLINCH_CHEST );
}
const char* pszEffectName;
if ( GetTeamNumber() == TF_TEAM_HALLOWEEN )
{
pszEffectName = "spell_skeleton_goop_green";
}
else
{
pszEffectName = GetTeamNumber() == TF_TEAM_RED ? "spell_pumpkin_mirv_goop_red" : "spell_pumpkin_mirv_goop_blue";
}
DispatchParticleEffect( pszEffectName, info.GetDamagePosition(), GetAbsAngles() );
return BaseClass::OnTakeDamage_Alive( info );
}
//-----------------------------------------------------------------------------------------------------
void CZombie::Event_Killed( const CTakeDamageInfo &info )
{
EmitSound( "Halloween.skeleton_break" );
if ( TFGameRules() && TFGameRules()->IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_HIGHTOWER ) )
{
CTFPlayer *pPlayerAttacker = NULL;
if ( info.GetAttacker() && info.GetAttacker()->IsPlayer() )
{
pPlayerAttacker = ToTFPlayer( info.GetAttacker() );
if ( pPlayerAttacker )
{
pPlayerAttacker->AwardAchievement( ACHIEVEMENT_TF_HALLOWEEN_HELLTOWER_SKELETON_GRIND );
IGameEvent *pEvent = gameeventmanager->CreateEvent( "halloween_skeleton_killed" );
if ( pEvent )
{
pEvent->SetInt( "player", pPlayerAttacker->GetUserID() );
gameeventmanager->FireEvent( pEvent, true );
}
}
}
}
BaseClass::Event_Killed( info );
}
//-----------------------------------------------------------------------------------------------------
void CZombie::UpdateOnRemove()
{
CPVSFilter filter( GetAbsOrigin() );
UserMessageBegin( filter, "BreakModel" );
WRITE_SHORT( GetModelIndex() );
WRITE_VEC3COORD( GetAbsOrigin() );
WRITE_ANGLES( GetAbsAngles() );
WRITE_SHORT( m_nSkin );
MessageEnd();
UTIL_Remove( m_hHat );
BaseClass::UpdateOnRemove();
}
//---------------------------------------------------------------------------------------------
/*static*/ CZombie* CZombie::SpawnAtPos( const Vector& vSpawnPos, float flLifeTime /*= 0.f*/, int nTeam /*= TF_TEAM_HALLOWEEN*/, CBaseEntity *pOwner /*= NULL*/, SkeletonType_t nSkeletonType /*= SKELETON_NORMAL*/ )
{
CZombie *pZombie = (CZombie *)CreateEntityByName( "tf_zombie" );
if ( pZombie )
{
pZombie->ChangeTeam( nTeam );
DispatchSpawn( pZombie );
pZombie->SetAbsOrigin( vSpawnPos );
pZombie->SetOwnerEntity( pOwner );
if ( flLifeTime > 0.f )
{
pZombie->StartLifeTimer( flLifeTime );
}
pZombie->SetSkeletonType( nSkeletonType );
}
return pZombie;
}
bool CZombie::ShouldSuicide() const
{
// out of life time
if ( m_lifeTimer.HasStarted() && m_lifeTimer.IsElapsed() )
return true;
// owner changed team
if ( GetOwnerEntity() && GetOwnerEntity()->GetTeamNumber() != GetTeamNumber() )
return true;
return m_bForceSuicide;
}
//-----------------------------------------------------------------------------------------------------
void CZombie::SetSkeletonType( SkeletonType_t nType )
{
m_nType = nType;
// Skeleton King?
if ( nType == SKELETON_KING )
{
SetModel( SKELETON_KING_MODEL );
SetModelScale( 2.f );
const int health = 1000;
SetHealth( health );
SetMaxHealth( health );
m_flAttackRange = 100.f;
m_flAttackDamage = 100.f;
AddHat( SKELETON_KING_CROWN_MODEL );
}
else if ( nType == SKELETON_MINI )
{
SetModel( SKELETON_MODEL );
SetModelScale( 0.5f );
m_flHeadScale = 3.f;
if( TFGameRules()->GetHalloweenScenario() == CTFGameRules::HALLOWEEN_SCENARIO_DOOMSDAY )
{
int iModelIndex = RandomInt( 0, ARRAYSIZE( s_skeletonHatModels ) - 1 );
#ifdef STAGING_ONLY
iModelIndex = tf_halloween_skeleton_test_hat.GetInt() > 0 ? tf_halloween_skeleton_test_hat.GetInt() : iModelIndex;
#endif // STAGING_ONLY
const char *pszHat = s_skeletonHatModels[ iModelIndex ];
AddHat( pszHat );
}
m_flAttackRange = 40.f;
m_flAttackDamage = 20.f;
}
}
//-----------------------------------------------------------------------------------------------------
void CZombie::AddHat( const char *pszModel )
{
if ( !m_hHat )
{
int iHead = LookupBone( "bip_head" );
Assert( iHead != -1 );
if ( iHead != -1 )
{
m_hHat = (CBaseAnimating *)CreateEntityByName( "prop_dynamic" );
if ( m_hHat )
{
m_hHat->SetModel( pszModel );
Vector pos;
QAngle angles;
GetBonePosition( iHead, pos, angles );
m_hHat->SetAbsOrigin( pos );
m_hHat->SetAbsAngles( angles );
m_hHat->FollowEntity( this, true );
}
}
}
}
//---------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------
class CZombieBehavior : public Action< CZombie >
{
public:
virtual Action< CZombie > *InitialContainedAction( CZombie *me )
{
return new CZombieSpawn;
}
virtual ActionResult< CZombie > OnStart( CZombie *me, Action< CZombie > *priorAction )
{
return Continue();
}
virtual ActionResult< CZombie > Update( CZombie *me, float interval )
{
if ( !me->IsAlive() || me->ShouldSuicide() )
{
UTIL_Remove( me );
return Done();
}
if ( ShouldLaugh( me ) )
{
Laugh( me );
}
return Continue();
}
virtual EventDesiredResult< CZombie > OnKilled( CZombie *me, const CTakeDamageInfo &info )
{
// bonemerged models don't ragdoll
//UTIL_Remove( me->m_zombieParts );
if ( info.GetAttacker() && dynamic_cast< CBaseCombatCharacter* >( info.GetAttacker() ) )
{
if ( me->GetSkeletonType() == CZombie::SKELETON_NORMAL )
{
// normal skeleton spawns 3 mini skeletons
CBaseCombatCharacter* pOwner = dynamic_cast< CBaseCombatCharacter* >( me->GetOwnerEntity() );
pOwner = pOwner ? pOwner : me;
for ( int i=0; i<3; ++i )
{
CreateSpellSpawnZombie( pOwner, me->GetAbsOrigin(), 2 );
}
}
else if ( me->GetSkeletonType() == CZombie::SKELETON_KING )
{
// skeleton king drops rare spell
TFGameRules()->DropSpellPickup( me->GetAbsOrigin(), 1 );
}
}
UTIL_Remove( me );
return TryDone();
}
virtual const char *GetName( void ) const { return "ZombieBehavior"; } // return name of this action
private:
bool ShouldLaugh( CZombie *me )
{
if ( !m_laughTimer.HasStarted() )
{
switch ( me->GetSkeletonType() )
{
case CZombie::SKELETON_KING:
{
m_laughTimer.Start( RandomFloat( 6.f, 7.f ) );
break;
}
case CZombie::SKELETON_MINI:
{
m_laughTimer.Start( RandomFloat( 2.f, 3.f ) );
break;
}
default:
{
m_laughTimer.Start( RandomFloat( 4.f, 5.f ) );
}
}
return false;
}
if ( m_laughTimer.HasStarted() && m_laughTimer.IsElapsed() )
{
m_laughTimer.Invalidate();
return true;
}
return false;
}
void Laugh( CZombie *me )
{
const char *pszSoundName;
switch ( me->GetSkeletonType() )
{
case CZombie::SKELETON_KING:
{
pszSoundName = "Halloween.skeleton_laugh_giant";
break;
}
case CZombie::SKELETON_MINI:
{
pszSoundName = "Halloween.skeleton_laugh_small";
break;
}
default:
{
pszSoundName = "Halloween.skeleton_laugh_medium";
}
}
me->EmitSound( pszSoundName );
}
CountdownTimer m_laughTimer;
};
//---------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------
CZombieIntention::CZombieIntention( CZombie *me ) : IIntention( me )
{
m_behavior = new Behavior< CZombie >( new CZombieBehavior );
}
CZombieIntention::~CZombieIntention()
{
delete m_behavior;
}
void CZombieIntention::Reset( void )
{
delete m_behavior;
m_behavior = new Behavior< CZombie >( new CZombieBehavior );
}
void CZombieIntention::Update( void )
{
m_behavior->Update( static_cast< CZombie * >( GetBot() ), GetUpdateInterval() );
}
// is this a place we can be?
QueryResultType CZombieIntention::IsPositionAllowed( const INextBot *meBot, const Vector &pos ) const
{
return ANSWER_YES;
}
//---------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------
float CZombieLocomotion::GetRunSpeed( void ) const
{
return 300.f;
}
//---------------------------------------------------------------------------------------------
// if delta Z is greater than this, we have to jump to get up
float CZombieLocomotion::GetStepHeight( void ) const
{
return 18.0f;
}
//---------------------------------------------------------------------------------------------
// return maximum height of a jump
float CZombieLocomotion::GetMaxJumpHeight( void ) const
{
return 18.0f;
}
//---------------------------------------------------------------------------------------------
// Return max rate of yaw rotation
float CZombieLocomotion::GetMaxYawRate( void ) const
{
return 200.0f;
}
//---------------------------------------------------------------------------------------------
bool CZombieLocomotion::ShouldCollideWith( const CBaseEntity *object ) const
{
return false;
}
+196
View File
@@ -0,0 +1,196 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
//
//
//=============================================================================
#ifndef ZOMBIE_H
#define ZOMBIE_H
#include "NextBot.h"
#include "NextBotBehavior.h"
#include "NextBotGroundLocomotion.h"
#include "../headless_hatman_body.h"
#include "Path/NextBotPathFollow.h"
class CZombie;
//----------------------------------------------------------------------------
class CZombieLocomotion : public NextBotGroundLocomotion
{
public:
CZombieLocomotion( INextBot *bot ) : NextBotGroundLocomotion( bot ) { }
virtual ~CZombieLocomotion() { }
virtual float GetRunSpeed( void ) const; // get maximum running speed
virtual float GetStepHeight( void ) const; // if delta Z is greater than this, we have to jump to get up
virtual float GetMaxJumpHeight( void ) const; // return maximum height of a jump
virtual bool ShouldCollideWith( const CBaseEntity *object ) const;
private:
virtual float GetMaxYawRate( void ) const; // return max rate of yaw rotation
};
//----------------------------------------------------------------------------
class CZombieIntention : public IIntention
{
public:
CZombieIntention( CZombie *me );
virtual ~CZombieIntention();
virtual void Reset( void );
virtual void Update( void );
virtual QueryResultType IsPositionAllowed( const INextBot *me, const Vector &pos ) const; // is the a place we can be?
virtual INextBotEventResponder *FirstContainedResponder( void ) const { return m_behavior; }
virtual INextBotEventResponder *NextContainedResponder( INextBotEventResponder *current ) const { return NULL; }
private:
Behavior< CZombie > *m_behavior;
};
//----------------------------------------------------------------------------
DECLARE_AUTO_LIST( IZombieAutoList );
class CZombie : public NextBotCombatCharacter, public IZombieAutoList
{
public:
DECLARE_CLASS( CZombie, NextBotCombatCharacter );
DECLARE_SERVERCLASS();
CZombie();
virtual ~CZombie();
static void PrecacheZombie();
virtual void Precache();
virtual void Spawn( void );
virtual int OnTakeDamage_Alive( const CTakeDamageInfo &info );
virtual void Event_Killed( const CTakeDamageInfo &info );
virtual void UpdateOnRemove();
// INextBot
virtual CZombieIntention *GetIntentionInterface( void ) const { return m_intention; }
virtual CZombieLocomotion *GetLocomotionInterface( void ) const { return m_locomotor; }
virtual CHeadlessHatmanBody *GetBodyInterface( void ) const { return m_body; }
CBaseAnimating *m_zombieParts;
void StartLifeTimer( float flLifeTime ) { m_lifeTimer.Start( flLifeTime ); }
bool ShouldSuicide() const;
void ForceSuicide() { m_bForceSuicide = true; }
enum SkeletonType_t
{
SKELETON_NORMAL = 0,
SKELETON_KING,
SKELETON_MINI
};
SkeletonType_t GetSkeletonType() const { return m_nType; }
void SetSkeletonType( SkeletonType_t nType );
void AddHat( const char *pszModel );
static CZombie* SpawnAtPos( const Vector& vSpawnPos, float flLifeTime = 0.f, int nTeam = TF_TEAM_HALLOWEEN, CBaseEntity *pOwner = NULL, SkeletonType_t nSkeletonType = SKELETON_NORMAL );
float GetAttackRange() const { return m_flAttackRange; }
float GetAttackDamage() const { return m_flAttackDamage; }
private:
CZombieIntention *m_intention;
CZombieLocomotion *m_locomotor;
CHeadlessHatmanBody *m_body;
SkeletonType_t m_nType;
CNetworkVar( float, m_flHeadScale );
CHandle< CBaseAnimating > m_hHat;
float m_flAttackRange;
float m_flAttackDamage;
bool m_bSpy;
bool m_bForceSuicide;
CountdownTimer m_lifeTimer;
};
//--------------------------------------------------------------------------------------------------------------
class CZombiePathCost : public IPathCost
{
public:
CZombiePathCost( CZombie *me )
{
m_me = me;
}
// return the cost (weighted distance between) of moving from "fromArea" to "area", or -1 if the move is not allowed
virtual float operator()( CNavArea *area, CNavArea *fromArea, const CNavLadder *ladder, const CFuncElevator *elevator, float length ) const
{
if ( fromArea == NULL )
{
// first area in path, no cost
return 0.0f;
}
else
{
if ( !m_me->GetLocomotionInterface()->IsAreaTraversable( area ) )
{
// our locomotor says we can't move here
return -1.0f;
}
// compute distance traveled along path so far
float dist;
if ( ladder )
{
dist = ladder->m_length;
}
else if ( length > 0.0 )
{
// optimization to avoid recomputing length
dist = length;
}
else
{
dist = ( area->GetCenter() - fromArea->GetCenter() ).Length();
}
// check height change
float deltaZ = fromArea->ComputeAdjacentConnectionHeightChange( area );
if ( deltaZ >= m_me->GetLocomotionInterface()->GetStepHeight() )
{
if ( deltaZ >= m_me->GetLocomotionInterface()->GetMaxJumpHeight() )
{
// too high to reach
return -1.0f;
}
// jumping is slower than flat ground
const float jumpPenalty = 5.0f;
dist += jumpPenalty * dist;
}
else if ( deltaZ < -m_me->GetLocomotionInterface()->GetDeathDropHeight() )
{
// too far to drop
return -1.0f;
}
// this term causes the same bot to choose different routes over time,
// but keep the same route for a period in case of repaths
int timeMod = (int)( gpGlobals->curtime / 10.0f ) + 1;
float preference = 1.0f + 50.0f * ( 1.0f + FastCos( (float)( m_me->GetEntity()->entindex() * area->GetID() * timeMod ) ) );
float cost = dist * preference;
return cost + fromArea->GetCostSoFar();;
}
}
CZombie *m_me;
};
#endif // ZOMBIE_H
@@ -0,0 +1,303 @@
//========= 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 "../zombie.h"
#include "zombie_attack.h"
#include "zombie_special_attack.h"
#define ZOMBIE_CHASE_MIN_DURATION 3.0f
ConVar tf_halloween_zombie_damage( "tf_halloween_zombie_damage", "10", FCVAR_CHEAT, "How much damage a zombie melee hit does." );
//----------------------------------------------------------------------------------
ActionResult< CZombie > CZombieAttack::OnStart( CZombie *me, Action< CZombie > *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_MELEE );
m_specialAttackTimer.Start( RandomFloat( 5.f, 10.f ) );
return Continue();
}
//----------------------------------------------------------------------------------
bool CZombieAttack::IsPotentiallyChaseable( CZombie *me, CBaseCombatCharacter *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;
}
}
return true;
}
//----------------------------------------------------------------------------------
void CZombieAttack::SelectVictim( CZombie *me )
{
if ( IsPotentiallyChaseable( me, m_attackTarget ) && !m_attackTargetFocusTimer.IsElapsed() )
{
// Continue chasing current target
return;
}
// pick a new victim to chase
CBaseCombatCharacter *newVictim = NULL;
CUtlVector< CTFPlayer * > playerVector;
// collect everyone
if ( me->GetTeamNumber() == TF_TEAM_RED )
{
CollectPlayers( &playerVector, TF_TEAM_BLUE, COLLECT_ONLY_LIVING_PLAYERS );
}
else if ( me->GetTeamNumber() == TF_TEAM_BLUE )
{
CollectPlayers( &playerVector, TF_TEAM_RED, COLLECT_ONLY_LIVING_PLAYERS );
}
else
{
CollectPlayers( &playerVector, TF_TEAM_RED, COLLECT_ONLY_LIVING_PLAYERS );
CollectPlayers( &playerVector, TF_TEAM_BLUE, COLLECT_ONLY_LIVING_PLAYERS, APPEND_PLAYERS );
}
float victimRangeSq = FLT_MAX;
// find closest player
for( int i=0; i<playerVector.Count(); ++i )
{
CTFPlayer *pPlayer = playerVector[i];
if ( !IsPotentiallyChaseable( me, pPlayer ) )
{
continue;
}
// ignore stealth player
if ( pPlayer->m_Shared.IsStealthed() )
{
if ( !pPlayer->m_Shared.InCond( TF_COND_BURNING ) &&
!pPlayer->m_Shared.InCond( TF_COND_URINE ) &&
!pPlayer->m_Shared.InCond( TF_COND_STEALTHED_BLINK ) &&
!pPlayer->m_Shared.InCond( TF_COND_BLEEDING ) )
{
// cloaked spies are invisible to us
continue;
}
}
// ignore player who disguises as my team
if ( pPlayer->m_Shared.InCond( TF_COND_DISGUISED ) && pPlayer->m_Shared.GetDisguiseTeam() == me->GetTeamNumber() )
{
continue;
}
// ignore ghost players
if ( pPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_GHOST_MODE ) )
{
continue;
}
float rangeSq = me->GetRangeSquaredTo( pPlayer );
if ( rangeSq < victimRangeSq )
{
newVictim = pPlayer;
victimRangeSq = rangeSq;
}
}
// find closest zombie
for ( int i=0; i<IZombieAutoList::AutoList().Count(); ++i )
{
CZombie* pZombie = static_cast< CZombie* >( IZombieAutoList::AutoList()[i] );
if ( pZombie->GetTeamNumber() == me->GetTeamNumber() )
{
continue;
}
if ( !IsPotentiallyChaseable( me, pZombie ) )
{
continue;
}
float rangeSq = me->GetRangeSquaredTo( pZombie );
if ( rangeSq < victimRangeSq )
{
newVictim = pZombie;
victimRangeSq = rangeSq;
}
}
if ( newVictim )
{
// we have a new victim
m_attackTargetFocusTimer.Start( ZOMBIE_CHASE_MIN_DURATION );
}
m_attackTarget = newVictim;
}
//----------------------------------------------------------------------------------
ActionResult< CZombie > CZombieAttack::Update( CZombie *me, float interval )
{
if ( !me->IsAlive() )
{
return Done();
}
if ( !m_tauntTimer.IsElapsed() )
{
// wait for taunt to finish
return Continue();
}
SelectVictim( me );
if ( m_attackTarget == NULL || !m_attackTarget->IsAlive() )
{
return Continue();
}
// chase after our chase victim
const float standAndSwingRange = 50.0f;
bool isLineOfSightClear = me->IsLineOfSightClear( m_attackTarget );
if ( me->IsRangeGreaterThan( m_attackTarget, standAndSwingRange ) || !isLineOfSightClear )
{
if ( m_path.GetAge() > 0.5f )
{
CZombiePathCost cost( me );
m_path.Compute( me, m_attackTarget, cost );
}
m_path.Update( me );
}
// claw at attack target if they are in range
const float zombieSwingRange = 150.0f;
if ( me->IsRangeLessThan( m_attackTarget, zombieSwingRange ) )
{
me->GetLocomotionInterface()->FaceTowards( m_attackTarget->WorldSpaceCenter() );
// swing!
if ( !me->IsPlayingGesture( ACT_MP_ATTACK_STAND_MELEE ) )
{
me->AddGesture( ACT_MP_ATTACK_STAND_MELEE );
}
const float zombieAttackRange = me->GetAttackRange();
if ( me->IsRangeLessThan( m_attackTarget, zombieAttackRange ) )
{
if ( me->GetSkeletonType() == 1 && m_specialAttackTimer.IsElapsed() )
{
m_specialAttackTimer.Start( RandomFloat( 5.f, 10.f ) );
return SuspendFor( new CZombieSpecialAttack, "Do Special Attack!" );
}
if ( m_attackTimer.IsElapsed() )
{
m_attackTimer.Start( RandomFloat( 0.8f, 1.2f ) );
Vector toVictim = m_attackTarget->WorldSpaceCenter() - me->WorldSpaceCenter();
toVictim.NormalizeInPlace();
// hit!
CBaseEntity *pAttacker = me->GetOwnerEntity() ? me->GetOwnerEntity() : me;
CTakeDamageInfo info( pAttacker, pAttacker, me->GetAttackDamage(), DMG_SLASH );
info.SetDamageCustom( TF_DMG_CUSTOM_SPELL_SKELETON );
CalculateMeleeDamageForce( &info, toVictim, me->WorldSpaceCenter(), 5.0f );
m_attackTarget->TakeDamage( info );
}
}
}
if ( !me->GetBodyInterface()->IsActivity( ACT_MP_RUN_MELEE ) )
{
me->GetBodyInterface()->StartActivity( ACT_MP_RUN_MELEE );
}
return Continue();
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CZombie > CZombieAttack::OnStuck( CZombie *me )
{
// if we're stuck just die
CTakeDamageInfo info( me, me, 99999.9f, DMG_SLASH );
me->TakeDamage( info );
return TryContinue( RESULT_TRY );
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CZombie > CZombieAttack::OnContact( CZombie *me, CBaseEntity *other, CGameTrace *result )
{
if ( other->IsPlayer() )
{
CTFPlayer *pTFPlayer = ToTFPlayer( other );
if ( pTFPlayer )
{
if ( pTFPlayer->IsAlive() && me->GetTeamNumber() != TF_TEAM_HALLOWEEN && me->GetTeamNumber() != pTFPlayer->GetTeamNumber() )
{
// force attack the thing we bumped into
// this prevents us from being stuck on dispensers, for example
m_attackTarget = pTFPlayer;
m_attackTargetFocusTimer.Start( ZOMBIE_CHASE_MIN_DURATION );
}
}
}
return TryContinue( RESULT_TRY );
}
//---------------------------------------------------------------------------------------------
EventDesiredResult< CZombie > CZombieAttack::OnOtherKilled( CZombie *me, CBaseCombatCharacter *victim, const CTakeDamageInfo &info )
{
/*if ( victim && victim->IsPlayer() && me->GetLocomotionInterface()->IsOnGround() )
{
me->AddGestureSequence( me->LookupSequence( "taunt06" ) );
m_tauntTimer.Start( 3.0f );
}*/
return TryContinue( RESULT_TRY );
}
@@ -0,0 +1,36 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
//
//
//=============================================================================
#ifndef ZOMBIE_ATTACK_H
#define ZOMBIE_ATTACK_H
//---------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------
class CZombieAttack : public Action< CZombie >
{
public:
virtual ActionResult< CZombie > OnStart( CZombie *me, Action< CZombie > *priorAction );
virtual ActionResult< CZombie > Update( CZombie *me, float interval );
virtual EventDesiredResult< CZombie > OnStuck( CZombie *me );
virtual EventDesiredResult< CZombie > OnContact( CZombie *me, CBaseEntity *other, CGameTrace *result = NULL );
virtual EventDesiredResult< CZombie > OnOtherKilled( CZombie *me, CBaseCombatCharacter *victim, const CTakeDamageInfo &info );
virtual const char *GetName( void ) const { return "Attack"; } // return name of this action
private:
PathFollower m_path;
CHandle< CBaseCombatCharacter > m_attackTarget;
CountdownTimer m_attackTimer;
CountdownTimer m_specialAttackTimer;
CountdownTimer m_attackTargetFocusTimer;
CountdownTimer m_tauntTimer;
bool IsPotentiallyChaseable( CZombie *me, CBaseCombatCharacter *victim );
void SelectVictim( CZombie *me );
};
#endif // ZOMBIE_ATTACK_H
@@ -0,0 +1,29 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
//
//
//=============================================================================
#include "cbase.h"
#include "tf_shareddefs.h"
#include "../zombie.h"
#include "zombie_attack.h"
#include "zombie_spawn.h"
ActionResult< CZombie > CZombieSpawn::OnStart( CZombie *me, Action< CZombie > *priorAction )
{
me->GetBodyInterface()->StartActivity( ACT_TRANSITION );
return Continue();
}
ActionResult< CZombie > CZombieSpawn::Update( CZombie *me, float interval )
{
if ( me->IsActivityFinished() )
{
return ChangeTo( new CZombieAttack, "Start Attack!" );
}
return Continue();
}
@@ -0,0 +1,22 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
//
//
//=============================================================================
#ifndef ZOMBIE_SPAWN_H
#define ZOMBIE_SPAWN_H
//---------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------
class CZombieSpawn : public Action< CZombie >
{
public:
virtual ActionResult< CZombie > OnStart( CZombie *me, Action< CZombie > *priorAction );
virtual ActionResult< CZombie > Update( CZombie *me, float interval );
virtual const char *GetName( void ) const { return "Spawn"; } // return name of this action
private:
};
#endif // ZOMBIE_SPAWN_H
@@ -0,0 +1,68 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
//
//
//=============================================================================
#include "cbase.h"
#include "tf_player.h"
#include "tf_gamerules.h"
#include "tf_fx.h"
#include "../zombie.h"
#include "zombie_special_attack.h"
ActionResult< CZombie > CZombieSpecialAttack::OnStart( CZombie *me, Action< CZombie > *priorAction )
{
me->GetBodyInterface()->StartActivity( ACT_SPECIAL_ATTACK1 );
m_stompTimer.Start( 1 );
return Continue();
}
ActionResult< CZombie > CZombieSpecialAttack::Update( CZombie *me, float interval )
{
if ( m_stompTimer.HasStarted() && m_stompTimer.IsElapsed() )
{
DoSpecialAttack( me );
m_stompTimer.Invalidate();
}
if ( me->IsActivityFinished() )
{
return Done();
}
return Continue();
}
void CZombieSpecialAttack::DoSpecialAttack( CZombie *me )
{
CPVSFilter filter( me->GetAbsOrigin() );
TE_TFParticleEffect( filter, 0.0, "bomibomicon_ring", me->GetAbsOrigin(), vec3_angle );
int nTargetTeam = TEAM_ANY;
if ( me->GetTeamNumber() != TF_TEAM_HALLOWEEN )
{
nTargetTeam = me->GetTeamNumber() == TF_TEAM_RED ? TF_TEAM_BLUE : TF_TEAM_RED;
}
CUtlVector< CTFPlayer* > pushedPlayers;
TFGameRules()->PushAllPlayersAway( me->GetAbsOrigin(), 200.f, 500.f, nTargetTeam, &pushedPlayers );
CBaseEntity *pAttacker = me->GetOwnerEntity() ? me->GetOwnerEntity() : me;
for ( int i=0; i<pushedPlayers.Count(); ++i )
{
Vector toVictim = pushedPlayers[i]->WorldSpaceCenter() - me->WorldSpaceCenter();
toVictim.NormalizeInPlace();
// hit!
CTakeDamageInfo info( pAttacker, pAttacker, me->GetAttackDamage(), DMG_SLASH );
info.SetDamageCustom( TF_DMG_CUSTOM_SPELL_SKELETON );
CalculateMeleeDamageForce( &info, toVictim, me->WorldSpaceCenter(), 5.0f );
pushedPlayers[i]->TakeDamage( info );
}
}
@@ -0,0 +1,25 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
//
//
//=============================================================================
#ifndef ZOMBIE_SPECIAL_ATTACK_H
#define ZOMBIE_SPECIAL_ATTACK_H
//---------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------
class CZombieSpecialAttack : public Action< CZombie >
{
public:
virtual ActionResult< CZombie > OnStart( CZombie *me, Action< CZombie > *priorAction );
virtual ActionResult< CZombie > Update( CZombie *me, float interval );
virtual const char *GetName( void ) const { return "Special Attack"; } // return name of this action
private:
void DoSpecialAttack( CZombie *me );
CountdownTimer m_stompTimer;
};
#endif // ZOMBIE_SPECIAL_ATTACK_H
@@ -0,0 +1,118 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
//
//
//=============================================================================
#include "cbase.h"
#include "NextBot.h"
#include "zombie.h"
#include "zombie_body.h"
//-------------------------------------------------------------------------------------------
CZombieBody::CZombieBody( INextBot *bot ) : IBody( bot )
{
m_moveXPoseParameter = -1;
m_moveYPoseParameter = -1;
m_currentActivity = -1;
}
//-------------------------------------------------------------------------------------------
bool CZombieBody::StartActivity( Activity act, unsigned int flags )
{
CZombie *me = (CZombie *)GetBot()->GetEntity();
int animSequence = ::SelectWeightedSequence( me->GetModelPtr(), act, me->GetSequence() );
if ( animSequence )
{
m_currentActivity = act;
me->SetSequence( animSequence );
me->SetPlaybackRate( 1.0f );
me->SetCycle( 0 );
me->ResetSequenceInfo();
return true;
}
return false;
}
//-------------------------------------------------------------------------------------------
void CZombieBody::Update( void )
{
CZombie *me = (CZombie *)GetBot()->GetEntity();
if ( m_moveXPoseParameter < 0 )
{
m_moveXPoseParameter = me->LookupPoseParameter( "move_x" );
}
if ( m_moveYPoseParameter < 0 )
{
m_moveYPoseParameter = me->LookupPoseParameter( "move_y" );
}
// Update the pose parameters
float speed = me->GetLocomotionInterface()->GetGroundSpeed(); // me->GetAbsVelocity().Length();
if ( speed < 0.01f )
{
// stopped
if ( m_moveXPoseParameter >= 0 )
{
me->SetPoseParameter( m_moveXPoseParameter, 0.0f );
}
if ( m_moveYPoseParameter >= 0 )
{
me->SetPoseParameter( m_moveYPoseParameter, 0.0f );
}
}
else
{
Vector forward, right, up;
me->GetVectors( &forward, &right, &up );
const Vector &motionVector = me->GetLocomotionInterface()->GetGroundMotionVector();
// move_x == 1.0 at full forward motion and -1.0 in full reverse
if ( m_moveXPoseParameter >= 0 )
{
float forwardVel = DotProduct( motionVector, forward );
me->SetPoseParameter( m_moveXPoseParameter, forwardVel );
}
if ( m_moveYPoseParameter >= 0 )
{
float sideVel = DotProduct( motionVector, right );
me->SetPoseParameter( m_moveYPoseParameter, sideVel );
}
}
// adjust animation speed to actual movement speed
if ( me->m_flGroundSpeed > 0.0f )
{
// Clamp playback rate to avoid datatable warnings. Anything faster would look silly, anyway.
float playbackRate = clamp( speed / me->m_flGroundSpeed, -4.f, 12.f );
me->SetPlaybackRate( playbackRate );
}
// move the animation ahead in time
me->StudioFrameAdvance();
me->DispatchAnimEvents( me );
}
//---------------------------------------------------------------------------------------------
// return the bot's collision mask (hack until we get a general hull trace abstraction here or in the locomotion interface)
unsigned int CZombieBody::GetSolidMask( void ) const
{
return MASK_NPCSOLID | CONTENTS_PLAYERCLIP;
}
@@ -0,0 +1,51 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
//
//
//=============================================================================
#ifndef ZOMBIE_BODY_H
#define ZOMBIE_BODY_H
#include "animation.h"
#include "NextBotBodyInterface.h"
class INextBot;
//----------------------------------------------------------------------------------------------------------------
/**
* The interface for control and information about the bot's body state (posture, animation state, etc)
*/
class CZombieBody : public IBody
{
public:
CZombieBody( INextBot *bot );
virtual ~CZombieBody() { }
virtual void Update( void );
virtual bool StartActivity( Activity act, unsigned int flags = 0 );
virtual Activity GetActivity( void ) const; // return currently animating activity
virtual bool IsActivity( Activity act ) const; // return true if currently animating activity matches the given one
virtual unsigned int GetSolidMask( void ) const; // return the bot's collision mask (hack until we get a general hull trace abstraction here or in the locomotion interface)
private:
int m_currentActivity;
int m_moveXPoseParameter;
int m_moveYPoseParameter;
};
inline Activity CZombieBody::GetActivity( void ) const
{
return (Activity)m_currentActivity;
}
inline bool CZombieBody::IsActivity( Activity act ) const
{
return act == m_currentActivity ? true : false;
}
#endif // ZOMBIE_BODY_H
@@ -0,0 +1,86 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
//
//
//=============================================================================
#include "cbase.h"
#include "tf_shareddefs.h"
#include "zombie.h"
#include "zombie_spawner.h"
LINK_ENTITY_TO_CLASS( tf_zombie_spawner, CZombieSpawner );
BEGIN_DATADESC( CZombieSpawner )
DEFINE_KEYFIELD( m_flZombieLifeTime, FIELD_FLOAT, "zombie_lifetime" ),
DEFINE_KEYFIELD( m_nMaxActiveZombies, FIELD_INTEGER, "max_zombies" ),
DEFINE_KEYFIELD( m_bInfiniteZombies, FIELD_BOOLEAN, "infinite_zombies" ),
DEFINE_KEYFIELD( m_nSkeletonType, FIELD_INTEGER, "zombie_type" ),
DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ),
DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ),
DEFINE_INPUTFUNC( FIELD_INTEGER, "SetMaxActiveZombies", InputSetMaxActiveZombies ),
END_DATADESC()
CZombieSpawner::CZombieSpawner()
{
m_bEnabled = false;
m_bInfiniteZombies = false;
m_nMaxActiveZombies = 1;
m_flZombieLifeTime = 0;
m_nSkeletonType = 0;
m_nSpawned = 0;
}
void CZombieSpawner::Spawn()
{
BaseClass::Spawn();
SetNextThink( gpGlobals->curtime );
}
void CZombieSpawner::Think()
{
m_activeZombies.FindAndFastRemove( NULL );
if ( m_bEnabled && ( ( m_bInfiniteZombies && m_activeZombies.Count() < m_nMaxActiveZombies ) || ( !m_bInfiniteZombies && m_nSpawned < m_nMaxActiveZombies ) ) )
{
CZombie *pZombie = CZombie::SpawnAtPos( GetAbsOrigin(), m_flZombieLifeTime, TF_TEAM_HALLOWEEN, NULL, (CZombie::SkeletonType_t)m_nSkeletonType );
if ( pZombie )
{
m_nSpawned++;
m_activeZombies.AddToTail( pZombie );
}
SetNextThink( gpGlobals->curtime + RandomFloat( 1.5f, 3.f ) );
return;
}
SetNextThink( gpGlobals->curtime + 0.2f );
}
void CZombieSpawner::InputEnable( inputdata_t &inputdata )
{
m_bEnabled = true;
SetNextThink( gpGlobals->curtime );
}
void CZombieSpawner::InputDisable( inputdata_t &inputdata )
{
m_bEnabled = false;
m_nSpawned = 0;
m_activeZombies.Purge();
SetNextThink( gpGlobals->curtime );
}
void CZombieSpawner::InputSetMaxActiveZombies( inputdata_t &inputdata )
{
m_nMaxActiveZombies = inputdata.value.Int();
}
@@ -0,0 +1,35 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
//
//
//=============================================================================
#ifndef ZOMBIE_SPAWN_H
#define ZOMBIE_SPAWN_H
class CZombieSpawner : public CPointEntity
{
DECLARE_CLASS( CZombieSpawner, CPointEntity );
DECLARE_DATADESC();
public:
CZombieSpawner();
virtual void Spawn();
virtual void Think();
void InputEnable( inputdata_t &inputdata );
void InputDisable( inputdata_t &inputdata );
void InputSetMaxActiveZombies( inputdata_t &inputdata );
private:
bool m_bEnabled;
bool m_bInfiniteZombies;
int m_nMaxActiveZombies;
float m_flZombieLifeTime;
int m_nSkeletonType;
int m_nSpawned;
CUtlVector< CHandle< CZombie > > m_activeZombies;
};
#endif // ZOMBIE_SPAWN_H