//========= Copyright Valve Corporation, All rights reserved. ============// // headless_hatman.cpp // An NPC that spawns in the Halloween map and wreaks havok // Michael Booth, October 2010 #include "cbase.h" #include "tf_player.h" #include "tf_gamerules.h" #include "tf_team.h" #include "nav_mesh/tf_nav_area.h" #include "headless_hatman.h" #include "NextBot/Path/NextBotChasePath.h" #include "econ_wearable.h" #include "team_control_point_master.h" #include "particle_parse.h" #include "ghost/ghost.h" #include "halloween_behavior/headless_hatman_emerge.h" #include "halloween_behavior/headless_hatman_dying.h" ConVar tf_halloween_bot_health_base( "tf_halloween_bot_health_base", "3000", FCVAR_CHEAT ); ConVar tf_halloween_bot_health_per_player( "tf_halloween_bot_health_per_player", "200", FCVAR_CHEAT ); ConVar tf_halloween_bot_min_player_count( "tf_halloween_bot_min_player_count", "10", FCVAR_CHEAT ); ConVar tf_halloween_bot_speed( "tf_halloween_bot_speed", "400", FCVAR_CHEAT ); ConVar tf_halloween_bot_attack_range( "tf_halloween_bot_attack_range", "200", FCVAR_CHEAT ); ConVar tf_halloween_bot_speed_recovery_rate( "tf_halloween_bot_speed_recovery_rate", "100", FCVAR_CHEAT, "Movement units/second" ); ConVar tf_halloween_bot_chase_duration( "tf_halloween_bot_chase_duration", "30", FCVAR_CHEAT ); ConVar tf_halloween_bot_terrify_radius( "tf_halloween_bot_terrify_radius", "500", FCVAR_CHEAT ); ConVar tf_halloween_bot_chase_range( "tf_halloween_bot_chase_range", "1500", FCVAR_CHEAT ); ConVar tf_halloween_bot_quit_range( "tf_halloween_bot_quit_range", "2000", FCVAR_CHEAT ); //----------------------------------------------------------------------------------------------------- // The Horseless Headless Horseman //----------------------------------------------------------------------------------------------------- LINK_ENTITY_TO_CLASS( headless_hatman, CHeadlessHatman ); IMPLEMENT_SERVERCLASS_ST( CHeadlessHatman, DT_HeadlessHatman ) END_SEND_TABLE() //----------------------------------------------------------------------------------------------------- CHeadlessHatman::CHeadlessHatman() { m_intention = new CHeadlessHatmanIntention( this ); m_locomotor = new CHeadlessHatmanLocomotion( this ); m_body = new CHeadlessHatmanBody( this ); } //----------------------------------------------------------------------------------------------------- CHeadlessHatman::~CHeadlessHatman() { if ( m_intention ) delete m_intention; if ( m_locomotor ) delete m_locomotor; if ( m_body ) delete m_body; } void CHeadlessHatman::PrecacheHeadlessHatman() { int model = PrecacheModel( "models/bots/headless_hatman.mdl" ); PrecacheGibsForModel( model ); if ( TFGameRules() && TFGameRules()->IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_DOOMSDAY ) ) { PrecacheModel( "models/weapons/c_models/c_big_mallet/c_big_mallet.mdl" ); PrecacheParticleSystem( "hammer_impact_button" ); PrecacheScriptSound( "Halloween.HammerImpact" ); } else { PrecacheModel( "models/weapons/c_models/c_bigaxe/c_bigaxe.mdl" ); } PrecacheScriptSound( "Halloween.HeadlessBossSpawn" ); PrecacheScriptSound( "Halloween.HeadlessBossSpawnRumble" ); PrecacheScriptSound( "Halloween.HeadlessBossAttack" ); PrecacheScriptSound( "Halloween.HeadlessBossAlert" ); PrecacheScriptSound( "Halloween.HeadlessBossBoo" ); PrecacheScriptSound( "Halloween.HeadlessBossPain" ); PrecacheScriptSound( "Halloween.HeadlessBossLaugh" ); PrecacheScriptSound( "Halloween.HeadlessBossDying" ); PrecacheScriptSound( "Halloween.HeadlessBossDeath" ); PrecacheScriptSound( "Halloween.HeadlessBossAxeHitFlesh" ); PrecacheScriptSound( "Halloween.HeadlessBossAxeHitWorld" ); PrecacheScriptSound( "Halloween.HeadlessBossFootfalls" ); PrecacheScriptSound( "Player.IsNowIt" ); PrecacheScriptSound( "Player.YouAreIt" ); PrecacheScriptSound( "Player.TaggedOtherIt" ); PrecacheParticleSystem( "halloween_boss_summon" ); PrecacheParticleSystem( "halloween_boss_axe_hit_world" ); PrecacheParticleSystem( "halloween_boss_injured" ); PrecacheParticleSystem( "halloween_boss_death" ); PrecacheParticleSystem( "halloween_boss_foot_impact" ); PrecacheParticleSystem( "halloween_boss_eye_glow" ); } //----------------------------------------------------------------------------------------------------- void CHeadlessHatman::Precache() { BaseClass::Precache(); // always allow late precaching, so we don't pay the cost of the // Halloween Boss for the entire year bool bAllowPrecache = CBaseEntity::IsPrecacheAllowed(); CBaseEntity::SetAllowPrecache( true ); PrecacheHeadlessHatman(); CBaseEntity::SetAllowPrecache( bAllowPrecache ); } //----------------------------------------------------------------------------------------------------- void CHeadlessHatman::Spawn( void ) { Precache(); BaseClass::Spawn(); SetModel( "models/bots/headless_hatman.mdl" ); m_axe = (CBaseAnimating *)CreateEntityByName( "prop_dynamic" ); if ( m_axe ) { m_axe->SetModel( GetWeaponModel() ); // bonemerge the axe into our model m_axe->FollowEntity( this, true ); } // scale the boss' health with the player count int totalPlayers = GetGlobalTFTeam( TF_TEAM_BLUE )->GetNumPlayers() + GetGlobalTFTeam( TF_TEAM_RED )->GetNumPlayers(); int health = tf_halloween_bot_health_base.GetInt(); if ( totalPlayers > tf_halloween_bot_min_player_count.GetInt() ) { health += ( totalPlayers - tf_halloween_bot_min_player_count.GetInt() ) * tf_halloween_bot_health_per_player.GetInt(); } SetHealth( health ); SetMaxHealth( health ); m_homePos = GetAbsOrigin(); m_damagePoseParameter = -1; SetBloodColor( DONT_BLEED ); } //----------------------------------------------------------------------------------------------------- int CHeadlessHatman::OnTakeDamage_Alive( const CTakeDamageInfo &info ) { DispatchParticleEffect( "halloween_boss_injured", info.GetDamagePosition(), GetAbsAngles() ); return BaseClass::OnTakeDamage_Alive( info ); } //--------------------------------------------------------------------------------------------- void CHeadlessHatman::Update( void ) { BaseClass::Update(); if ( m_damagePoseParameter < 0 ) { m_damagePoseParameter = LookupPoseParameter( "damage" ); } if ( m_damagePoseParameter >= 0 ) { SetPoseParameter( m_damagePoseParameter, 1.0f - ( (float)GetHealth() / (float)GetMaxHealth() ) ); } } //--------------------------------------------------------------------------------------------- const char *CHeadlessHatman::GetWeaponModel() const { if ( TFGameRules() && TFGameRules()->IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_DOOMSDAY ) ) { return "models/weapons/c_models/c_big_mallet/c_big_mallet.mdl"; } else { return "models/weapons/c_models/c_bigaxe/c_bigaxe.mdl"; } } //--------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------- class CHeadlessHatmanBehavior : public Action< CHeadlessHatman > { public: virtual Action< CHeadlessHatman > *InitialContainedAction( CHeadlessHatman *me ) { return new CHeadlessHatmanEmerge; } virtual ActionResult< CHeadlessHatman > Update( CHeadlessHatman *me, float interval ) { if ( !me->IsAlive() ) { if ( !me->WasSpawnedByCheats() ) { // award achievement to everyone who injured me within the last few seconds const float deathTime = 5.0f; const CUtlVector< CHeadlessHatman::AttackerInfo > &attackerVector = me->GetAttackerVector(); for( int i=0; icurtime - attackerVector[i].m_timestamp < deathTime ) { CReliableBroadcastRecipientFilter filter; UTIL_SayText2Filter( filter, attackerVector[i].m_attacker, false, "#TF_Halloween_Boss_Killers", attackerVector[i].m_attacker->GetPlayerName() ); if ( TFGameRules() && TFGameRules()->IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_MANN_MANOR ) ) { // killing the boss with a melee weapon is a separate achievement if ( attackerVector[i].m_wasLastHitFromMeleeWeapon ) { attackerVector[i].m_attacker->AwardAchievement( ACHIEVEMENT_TF_HALLOWEEN_BOSS_KILL_MELEE ); } attackerVector[i].m_attacker->AwardAchievement( ACHIEVEMENT_TF_HALLOWEEN_BOSS_KILL ); } } } } // nobody is IT any longer TFGameRules()->SetIT( NULL ); return ChangeTo( new CHeadlessHatmanDying, "I am dead!" ); } return Continue(); } virtual const char *GetName( void ) const { return "Attack"; } // return name of this action }; //--------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------- CHeadlessHatmanIntention::CHeadlessHatmanIntention( CHeadlessHatman *me ) : IIntention( me ) { m_behavior = new Behavior< CHeadlessHatman >( new CHeadlessHatmanBehavior ); } CHeadlessHatmanIntention::~CHeadlessHatmanIntention() { delete m_behavior; } void CHeadlessHatmanIntention::Reset( void ) { delete m_behavior; m_behavior = new Behavior< CHeadlessHatman >( new CHeadlessHatmanBehavior ); } void CHeadlessHatmanIntention::Update( void ) { m_behavior->Update( static_cast< CHeadlessHatman * >( GetBot() ), GetUpdateInterval() ); } // is this a place we can be? QueryResultType CHeadlessHatmanIntention::IsPositionAllowed( const INextBot *meBot, const Vector &pos ) const { return ANSWER_YES; } //--------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------- float CHeadlessHatmanLocomotion::GetRunSpeed( void ) const { return tf_halloween_bot_speed.GetFloat(); } //--------------------------------------------------------------------------------------------- // if delta Z is greater than this, we have to jump to get up float CHeadlessHatmanLocomotion::GetStepHeight( void ) const { return 18.0f; } //--------------------------------------------------------------------------------------------- // return maximum height of a jump float CHeadlessHatmanLocomotion::GetMaxJumpHeight( void ) const { return 18.0f; } //--------------------------------------------------------------------------------------------- // Return max rate of yaw rotation float CHeadlessHatmanLocomotion::GetMaxYawRate( void ) const { return 200.0f; } //--------------------------------------------------------------------------------------------- // Should we collide with this entity? bool CHeadlessHatmanLocomotion::ShouldCollideWith( const CBaseEntity *object ) const { // don't collide with player in doomsday event if ( TFGameRules()->IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_DOOMSDAY ) && object->IsPlayer() ) { return false; } return BaseClass::ShouldCollideWith( object ); }