summaryrefslogtreecommitdiff
path: root/game/server/tf/halloween/zombie
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /game/server/tf/halloween/zombie
downloadarchived-source-engine-2018-hl2-src-master.tar.xz
archived-source-engine-2018-hl2-src-master.zip
Diffstat (limited to 'game/server/tf/halloween/zombie')
-rw-r--r--game/server/tf/halloween/zombie/zombie.cpp599
-rw-r--r--game/server/tf/halloween/zombie/zombie.h196
-rw-r--r--game/server/tf/halloween/zombie/zombie_behavior/zombie_attack.cpp303
-rw-r--r--game/server/tf/halloween/zombie/zombie_behavior/zombie_attack.h36
-rw-r--r--game/server/tf/halloween/zombie/zombie_behavior/zombie_spawn.cpp29
-rw-r--r--game/server/tf/halloween/zombie/zombie_behavior/zombie_spawn.h22
-rw-r--r--game/server/tf/halloween/zombie/zombie_behavior/zombie_special_attack.cpp68
-rw-r--r--game/server/tf/halloween/zombie/zombie_behavior/zombie_special_attack.h25
-rw-r--r--game/server/tf/halloween/zombie/zombie_body.cpp118
-rw-r--r--game/server/tf/halloween/zombie/zombie_body.h51
-rw-r--r--game/server/tf/halloween/zombie/zombie_spawner.cpp86
-rw-r--r--game/server/tf/halloween/zombie/zombie_spawner.h35
12 files changed, 1568 insertions, 0 deletions
diff --git a/game/server/tf/halloween/zombie/zombie.cpp b/game/server/tf/halloween/zombie/zombie.cpp
new file mode 100644
index 0000000..601b739
--- /dev/null
+++ b/game/server/tf/halloween/zombie/zombie.cpp
@@ -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;
+}
diff --git a/game/server/tf/halloween/zombie/zombie.h b/game/server/tf/halloween/zombie/zombie.h
new file mode 100644
index 0000000..eb6e475
--- /dev/null
+++ b/game/server/tf/halloween/zombie/zombie.h
@@ -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
diff --git a/game/server/tf/halloween/zombie/zombie_behavior/zombie_attack.cpp b/game/server/tf/halloween/zombie/zombie_behavior/zombie_attack.cpp
new file mode 100644
index 0000000..0438e05
--- /dev/null
+++ b/game/server/tf/halloween/zombie/zombie_behavior/zombie_attack.cpp
@@ -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 );
+}
diff --git a/game/server/tf/halloween/zombie/zombie_behavior/zombie_attack.h b/game/server/tf/halloween/zombie/zombie_behavior/zombie_attack.h
new file mode 100644
index 0000000..476ffc0
--- /dev/null
+++ b/game/server/tf/halloween/zombie/zombie_behavior/zombie_attack.h
@@ -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
diff --git a/game/server/tf/halloween/zombie/zombie_behavior/zombie_spawn.cpp b/game/server/tf/halloween/zombie/zombie_behavior/zombie_spawn.cpp
new file mode 100644
index 0000000..87d8e43
--- /dev/null
+++ b/game/server/tf/halloween/zombie/zombie_behavior/zombie_spawn.cpp
@@ -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();
+} \ No newline at end of file
diff --git a/game/server/tf/halloween/zombie/zombie_behavior/zombie_spawn.h b/game/server/tf/halloween/zombie/zombie_behavior/zombie_spawn.h
new file mode 100644
index 0000000..863fee6
--- /dev/null
+++ b/game/server/tf/halloween/zombie/zombie_behavior/zombie_spawn.h
@@ -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
diff --git a/game/server/tf/halloween/zombie/zombie_behavior/zombie_special_attack.cpp b/game/server/tf/halloween/zombie/zombie_behavior/zombie_special_attack.cpp
new file mode 100644
index 0000000..ee2ca3c
--- /dev/null
+++ b/game/server/tf/halloween/zombie/zombie_behavior/zombie_special_attack.cpp
@@ -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 );
+ }
+}
diff --git a/game/server/tf/halloween/zombie/zombie_behavior/zombie_special_attack.h b/game/server/tf/halloween/zombie/zombie_behavior/zombie_special_attack.h
new file mode 100644
index 0000000..b8689fb
--- /dev/null
+++ b/game/server/tf/halloween/zombie/zombie_behavior/zombie_special_attack.h
@@ -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
diff --git a/game/server/tf/halloween/zombie/zombie_body.cpp b/game/server/tf/halloween/zombie/zombie_body.cpp
new file mode 100644
index 0000000..67b13c4
--- /dev/null
+++ b/game/server/tf/halloween/zombie/zombie_body.cpp
@@ -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;
+}
diff --git a/game/server/tf/halloween/zombie/zombie_body.h b/game/server/tf/halloween/zombie/zombie_body.h
new file mode 100644
index 0000000..ba7ffcf
--- /dev/null
+++ b/game/server/tf/halloween/zombie/zombie_body.h
@@ -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
diff --git a/game/server/tf/halloween/zombie/zombie_spawner.cpp b/game/server/tf/halloween/zombie/zombie_spawner.cpp
new file mode 100644
index 0000000..cf0e3cb
--- /dev/null
+++ b/game/server/tf/halloween/zombie/zombie_spawner.cpp
@@ -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();
+}
diff --git a/game/server/tf/halloween/zombie/zombie_spawner.h b/game/server/tf/halloween/zombie/zombie_spawner.h
new file mode 100644
index 0000000..ebbac9f
--- /dev/null
+++ b/game/server/tf/halloween/zombie/zombie_spawner.h
@@ -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