Team Fortress 2 Source Code as on 22/4/2020
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

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. // headless_hatman.cpp
  3. // An NPC that spawns in the Halloween map and wreaks havok
  4. // Michael Booth, October 2010
  5. #include "cbase.h"
  6. #include "tf_player.h"
  7. #include "tf_gamerules.h"
  8. #include "tf_team.h"
  9. #include "nav_mesh/tf_nav_area.h"
  10. #include "headless_hatman.h"
  11. #include "NextBot/Path/NextBotChasePath.h"
  12. #include "econ_wearable.h"
  13. #include "team_control_point_master.h"
  14. #include "particle_parse.h"
  15. #include "ghost/ghost.h"
  16. #include "halloween_behavior/headless_hatman_emerge.h"
  17. #include "halloween_behavior/headless_hatman_dying.h"
  18. ConVar tf_halloween_bot_health_base( "tf_halloween_bot_health_base", "3000", FCVAR_CHEAT );
  19. ConVar tf_halloween_bot_health_per_player( "tf_halloween_bot_health_per_player", "200", FCVAR_CHEAT );
  20. ConVar tf_halloween_bot_min_player_count( "tf_halloween_bot_min_player_count", "10", FCVAR_CHEAT );
  21. ConVar tf_halloween_bot_speed( "tf_halloween_bot_speed", "400", FCVAR_CHEAT );
  22. ConVar tf_halloween_bot_attack_range( "tf_halloween_bot_attack_range", "200", FCVAR_CHEAT );
  23. ConVar tf_halloween_bot_speed_recovery_rate( "tf_halloween_bot_speed_recovery_rate", "100", FCVAR_CHEAT, "Movement units/second" );
  24. ConVar tf_halloween_bot_chase_duration( "tf_halloween_bot_chase_duration", "30", FCVAR_CHEAT );
  25. ConVar tf_halloween_bot_terrify_radius( "tf_halloween_bot_terrify_radius", "500", FCVAR_CHEAT );
  26. ConVar tf_halloween_bot_chase_range( "tf_halloween_bot_chase_range", "1500", FCVAR_CHEAT );
  27. ConVar tf_halloween_bot_quit_range( "tf_halloween_bot_quit_range", "2000", FCVAR_CHEAT );
  28. //-----------------------------------------------------------------------------------------------------
  29. // The Horseless Headless Horseman
  30. //-----------------------------------------------------------------------------------------------------
  31. LINK_ENTITY_TO_CLASS( headless_hatman, CHeadlessHatman );
  32. IMPLEMENT_SERVERCLASS_ST( CHeadlessHatman, DT_HeadlessHatman )
  33. END_SEND_TABLE()
  34. //-----------------------------------------------------------------------------------------------------
  35. CHeadlessHatman::CHeadlessHatman()
  36. {
  37. m_intention = new CHeadlessHatmanIntention( this );
  38. m_locomotor = new CHeadlessHatmanLocomotion( this );
  39. m_body = new CHeadlessHatmanBody( this );
  40. }
  41. //-----------------------------------------------------------------------------------------------------
  42. CHeadlessHatman::~CHeadlessHatman()
  43. {
  44. if ( m_intention )
  45. delete m_intention;
  46. if ( m_locomotor )
  47. delete m_locomotor;
  48. if ( m_body )
  49. delete m_body;
  50. }
  51. void CHeadlessHatman::PrecacheHeadlessHatman()
  52. {
  53. int model = PrecacheModel( "models/bots/headless_hatman.mdl" );
  54. PrecacheGibsForModel( model );
  55. if ( TFGameRules() && TFGameRules()->IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_DOOMSDAY ) )
  56. {
  57. PrecacheModel( "models/weapons/c_models/c_big_mallet/c_big_mallet.mdl" );
  58. PrecacheParticleSystem( "hammer_impact_button" );
  59. PrecacheScriptSound( "Halloween.HammerImpact" );
  60. }
  61. else
  62. {
  63. PrecacheModel( "models/weapons/c_models/c_bigaxe/c_bigaxe.mdl" );
  64. }
  65. PrecacheScriptSound( "Halloween.HeadlessBossSpawn" );
  66. PrecacheScriptSound( "Halloween.HeadlessBossSpawnRumble" );
  67. PrecacheScriptSound( "Halloween.HeadlessBossAttack" );
  68. PrecacheScriptSound( "Halloween.HeadlessBossAlert" );
  69. PrecacheScriptSound( "Halloween.HeadlessBossBoo" );
  70. PrecacheScriptSound( "Halloween.HeadlessBossPain" );
  71. PrecacheScriptSound( "Halloween.HeadlessBossLaugh" );
  72. PrecacheScriptSound( "Halloween.HeadlessBossDying" );
  73. PrecacheScriptSound( "Halloween.HeadlessBossDeath" );
  74. PrecacheScriptSound( "Halloween.HeadlessBossAxeHitFlesh" );
  75. PrecacheScriptSound( "Halloween.HeadlessBossAxeHitWorld" );
  76. PrecacheScriptSound( "Halloween.HeadlessBossFootfalls" );
  77. PrecacheScriptSound( "Player.IsNowIt" );
  78. PrecacheScriptSound( "Player.YouAreIt" );
  79. PrecacheScriptSound( "Player.TaggedOtherIt" );
  80. PrecacheParticleSystem( "halloween_boss_summon" );
  81. PrecacheParticleSystem( "halloween_boss_axe_hit_world" );
  82. PrecacheParticleSystem( "halloween_boss_injured" );
  83. PrecacheParticleSystem( "halloween_boss_death" );
  84. PrecacheParticleSystem( "halloween_boss_foot_impact" );
  85. PrecacheParticleSystem( "halloween_boss_eye_glow" );
  86. }
  87. //-----------------------------------------------------------------------------------------------------
  88. void CHeadlessHatman::Precache()
  89. {
  90. BaseClass::Precache();
  91. // always allow late precaching, so we don't pay the cost of the
  92. // Halloween Boss for the entire year
  93. bool bAllowPrecache = CBaseEntity::IsPrecacheAllowed();
  94. CBaseEntity::SetAllowPrecache( true );
  95. PrecacheHeadlessHatman();
  96. CBaseEntity::SetAllowPrecache( bAllowPrecache );
  97. }
  98. //-----------------------------------------------------------------------------------------------------
  99. void CHeadlessHatman::Spawn( void )
  100. {
  101. Precache();
  102. BaseClass::Spawn();
  103. SetModel( "models/bots/headless_hatman.mdl" );
  104. m_axe = (CBaseAnimating *)CreateEntityByName( "prop_dynamic" );
  105. if ( m_axe )
  106. {
  107. m_axe->SetModel( GetWeaponModel() );
  108. // bonemerge the axe into our model
  109. m_axe->FollowEntity( this, true );
  110. }
  111. // scale the boss' health with the player count
  112. int totalPlayers = GetGlobalTFTeam( TF_TEAM_BLUE )->GetNumPlayers() + GetGlobalTFTeam( TF_TEAM_RED )->GetNumPlayers();
  113. int health = tf_halloween_bot_health_base.GetInt();
  114. if ( totalPlayers > tf_halloween_bot_min_player_count.GetInt() )
  115. {
  116. health += ( totalPlayers - tf_halloween_bot_min_player_count.GetInt() ) * tf_halloween_bot_health_per_player.GetInt();
  117. }
  118. SetHealth( health );
  119. SetMaxHealth( health );
  120. m_homePos = GetAbsOrigin();
  121. m_damagePoseParameter = -1;
  122. SetBloodColor( DONT_BLEED );
  123. }
  124. //-----------------------------------------------------------------------------------------------------
  125. int CHeadlessHatman::OnTakeDamage_Alive( const CTakeDamageInfo &info )
  126. {
  127. DispatchParticleEffect( "halloween_boss_injured", info.GetDamagePosition(), GetAbsAngles() );
  128. return BaseClass::OnTakeDamage_Alive( info );
  129. }
  130. //---------------------------------------------------------------------------------------------
  131. void CHeadlessHatman::Update( void )
  132. {
  133. BaseClass::Update();
  134. if ( m_damagePoseParameter < 0 )
  135. {
  136. m_damagePoseParameter = LookupPoseParameter( "damage" );
  137. }
  138. if ( m_damagePoseParameter >= 0 )
  139. {
  140. SetPoseParameter( m_damagePoseParameter, 1.0f - ( (float)GetHealth() / (float)GetMaxHealth() ) );
  141. }
  142. }
  143. //---------------------------------------------------------------------------------------------
  144. const char *CHeadlessHatman::GetWeaponModel() const
  145. {
  146. if ( TFGameRules() && TFGameRules()->IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_DOOMSDAY ) )
  147. {
  148. return "models/weapons/c_models/c_big_mallet/c_big_mallet.mdl";
  149. }
  150. else
  151. {
  152. return "models/weapons/c_models/c_bigaxe/c_bigaxe.mdl";
  153. }
  154. }
  155. //---------------------------------------------------------------------------------------------
  156. //---------------------------------------------------------------------------------------------
  157. class CHeadlessHatmanBehavior : public Action< CHeadlessHatman >
  158. {
  159. public:
  160. virtual Action< CHeadlessHatman > *InitialContainedAction( CHeadlessHatman *me )
  161. {
  162. return new CHeadlessHatmanEmerge;
  163. }
  164. virtual ActionResult< CHeadlessHatman > Update( CHeadlessHatman *me, float interval )
  165. {
  166. if ( !me->IsAlive() )
  167. {
  168. if ( !me->WasSpawnedByCheats() )
  169. {
  170. // award achievement to everyone who injured me within the last few seconds
  171. const float deathTime = 5.0f;
  172. const CUtlVector< CHeadlessHatman::AttackerInfo > &attackerVector = me->GetAttackerVector();
  173. for( int i=0; i<attackerVector.Count(); ++i )
  174. {
  175. if ( attackerVector[i].m_attacker != NULL &&
  176. gpGlobals->curtime - attackerVector[i].m_timestamp < deathTime )
  177. {
  178. CReliableBroadcastRecipientFilter filter;
  179. UTIL_SayText2Filter( filter, attackerVector[i].m_attacker, false, "#TF_Halloween_Boss_Killers", attackerVector[i].m_attacker->GetPlayerName() );
  180. if ( TFGameRules() && TFGameRules()->IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_MANN_MANOR ) )
  181. {
  182. // killing the boss with a melee weapon is a separate achievement
  183. if ( attackerVector[i].m_wasLastHitFromMeleeWeapon )
  184. {
  185. attackerVector[i].m_attacker->AwardAchievement( ACHIEVEMENT_TF_HALLOWEEN_BOSS_KILL_MELEE );
  186. }
  187. attackerVector[i].m_attacker->AwardAchievement( ACHIEVEMENT_TF_HALLOWEEN_BOSS_KILL );
  188. }
  189. }
  190. }
  191. }
  192. // nobody is IT any longer
  193. TFGameRules()->SetIT( NULL );
  194. return ChangeTo( new CHeadlessHatmanDying, "I am dead!" );
  195. }
  196. return Continue();
  197. }
  198. virtual const char *GetName( void ) const { return "Attack"; } // return name of this action
  199. };
  200. //---------------------------------------------------------------------------------------------
  201. //---------------------------------------------------------------------------------------------
  202. CHeadlessHatmanIntention::CHeadlessHatmanIntention( CHeadlessHatman *me ) : IIntention( me )
  203. {
  204. m_behavior = new Behavior< CHeadlessHatman >( new CHeadlessHatmanBehavior );
  205. }
  206. CHeadlessHatmanIntention::~CHeadlessHatmanIntention()
  207. {
  208. delete m_behavior;
  209. }
  210. void CHeadlessHatmanIntention::Reset( void )
  211. {
  212. delete m_behavior;
  213. m_behavior = new Behavior< CHeadlessHatman >( new CHeadlessHatmanBehavior );
  214. }
  215. void CHeadlessHatmanIntention::Update( void )
  216. {
  217. m_behavior->Update( static_cast< CHeadlessHatman * >( GetBot() ), GetUpdateInterval() );
  218. }
  219. // is this a place we can be?
  220. QueryResultType CHeadlessHatmanIntention::IsPositionAllowed( const INextBot *meBot, const Vector &pos ) const
  221. {
  222. return ANSWER_YES;
  223. }
  224. //---------------------------------------------------------------------------------------------
  225. //---------------------------------------------------------------------------------------------
  226. float CHeadlessHatmanLocomotion::GetRunSpeed( void ) const
  227. {
  228. return tf_halloween_bot_speed.GetFloat();
  229. }
  230. //---------------------------------------------------------------------------------------------
  231. // if delta Z is greater than this, we have to jump to get up
  232. float CHeadlessHatmanLocomotion::GetStepHeight( void ) const
  233. {
  234. return 18.0f;
  235. }
  236. //---------------------------------------------------------------------------------------------
  237. // return maximum height of a jump
  238. float CHeadlessHatmanLocomotion::GetMaxJumpHeight( void ) const
  239. {
  240. return 18.0f;
  241. }
  242. //---------------------------------------------------------------------------------------------
  243. // Return max rate of yaw rotation
  244. float CHeadlessHatmanLocomotion::GetMaxYawRate( void ) const
  245. {
  246. return 200.0f;
  247. }
  248. //---------------------------------------------------------------------------------------------
  249. // Should we collide with this entity?
  250. bool CHeadlessHatmanLocomotion::ShouldCollideWith( const CBaseEntity *object ) const
  251. {
  252. // don't collide with player in doomsday event
  253. if ( TFGameRules()->IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_DOOMSDAY ) && object->IsPlayer() )
  254. {
  255. return false;
  256. }
  257. return BaseClass::ShouldCollideWith( object );
  258. }