mirror of
https://github.com/nillerusr/source-engine.git
synced 2026-08-17 22:11:58 +00:00
1
This commit is contained in:
+81
@@ -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
|
||||
+182
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 );
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user