//========= 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; iStartActivity( 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 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; }