You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
330 lines
11 KiB
330 lines
11 KiB
//========= 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 ); |
|
}
|
|
|