summaryrefslogtreecommitdiff
path: root/game/server/tf/halloween/headless_hatman.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'game/server/tf/halloween/headless_hatman.cpp')
-rw-r--r--game/server/tf/halloween/headless_hatman.cpp330
1 files changed, 330 insertions, 0 deletions
diff --git a/game/server/tf/halloween/headless_hatman.cpp b/game/server/tf/halloween/headless_hatman.cpp
new file mode 100644
index 0000000..d4aef78
--- /dev/null
+++ b/game/server/tf/halloween/headless_hatman.cpp
@@ -0,0 +1,330 @@
+//========= 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; i<attackerVector.Count(); ++i )
+ {
+ if ( attackerVector[i].m_attacker != NULL &&
+ gpGlobals->curtime - 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 );
+}