diff options
Diffstat (limited to 'game/server/tf/halloween/zombie')
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 |