diff options
Diffstat (limited to 'game/server/tf/halloween/zombie/zombie.cpp')
| -rw-r--r-- | game/server/tf/halloween/zombie/zombie.cpp | 599 |
1 files changed, 599 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; +} |