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.

1609 lines
48 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. //
  4. //
  5. //=============================================================================
  6. #include "cbase.h"
  7. #include "tf_player.h"
  8. #include "tf_gamerules.h"
  9. #include "tf_team.h"
  10. #include "nav_mesh/tf_nav_area.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 "tf_weaponbase_merasmus_grenade.h"
  16. #include "merasmus_dancer.h"
  17. #include "tf_wheel_of_doom.h"
  18. #include "soundenvelope.h"
  19. #include "util.h"
  20. #include "tf_obj_sentrygun.h"
  21. #include "logicrelay.h"
  22. #include "steamworks_gamestats.h"
  23. #include "tf/halloween/ghost/ghost.h"
  24. #include "player_vs_environment/monster_resource.h"
  25. #include "merasmus_trick_or_treat_prop.h"
  26. #include "merasmus.h"
  27. #include "merasmus_behavior/merasmus_disguise.h"
  28. #include "merasmus_behavior/merasmus_dying.h"
  29. #include "merasmus_behavior/merasmus_reveal.h"
  30. #include "merasmus_behavior/merasmus_teleport.h"
  31. #include "rtime.h"
  32. #include "gc_clientsystem.h"
  33. #include "tf_gcmessages.h"
  34. #include "tf_fx.h"
  35. ConVar tf_merasmus_health_base( "tf_merasmus_health_base", "33750", FCVAR_CHEAT );
  36. ConVar tf_merasmus_health_per_player( "tf_merasmus_health_per_player", "2500", FCVAR_CHEAT );
  37. ConVar tf_merasmus_min_player_count( "tf_merasmus_min_player_count", "10", FCVAR_CHEAT );
  38. ConVar tf_merasmus_lifetime( "tf_merasmus_lifetime", "120", FCVAR_CHEAT );
  39. ConVar tf_merasmus_speed( "tf_merasmus_speed", "600", FCVAR_CHEAT );
  40. ConVar tf_merasmus_speed_recovery_rate( "tf_merasmus_speed_recovery_rate", "100", FCVAR_CHEAT, "Movement units/second" );
  41. ConVar tf_merasmus_chase_duration( "tf_merasmus_chase_duration", "7", FCVAR_CHEAT );
  42. ConVar tf_merasmus_chase_range( "tf_merasmus_chase_range", "2000", FCVAR_CHEAT );
  43. ConVar tf_merasmus_should_disguise_threshold( "tf_merasmus_should_disguise_threshold", "0.45f", FCVAR_CHEAT );
  44. ConVar tf_merasmus_min_props_to_reveal( "tf_merasmus_min_props_to_reveal", "0.7f", FCVAR_CHEAT, "Percentage of total fake props players have to destroy before Merasmus reveals himself");
  45. ConVar tf_merasmus_attack_range( "tf_merasmus_attack_range", "200", FCVAR_CHEAT );
  46. ConVar tf_merasmus_health_regen_rate( "tf_merasmus_health_regen_rate", "0.001f", FCVAR_CHEAT, "Percentage of Max HP per sec that Merasmus will regenerate while in disguise" );
  47. ConVar tf_merasmus_bomb_head_duration( "tf_merasmus_bomb_head_duration", "15.f", FCVAR_CHEAT );
  48. ConVar tf_merasmus_bomb_head_per_team( "tf_merasmus_bomb_head_per_team", "1", FCVAR_CHEAT );
  49. ConVar tf_merasmus_stun_duration( "tf_merasmus_stun_duration", "2.f", FCVAR_CHEAT );
  50. extern ConVar tf_merasmus_spawn_interval;
  51. extern ConVar tf_merasmus_spawn_interval_variation;
  52. #define MERASMUS_MODEL_NAME "models/bots/merasmus/merasmus.mdl"
  53. #define MERASMUS_BOMB_MODEL "models/props_lakeside_event/bomb_temp.mdl"
  54. static const char* s_pszDisguiseProps[] =
  55. {
  56. // "models/props_hydro/dumptruck.mdl", // 265
  57. "models/props_halloween/pumpkin_02.mdl",
  58. "models/props_halloween/pumpkin_03.mdl",
  59. "models/egypt/palm_tree/palm_tree.mdl",
  60. "models/props_spytech/control_room_console01.mdl",
  61. "models/props_spytech/work_table001.mdl",
  62. // "models/egypt/tent/tent.mdl", // 248
  63. "models/props_coalmines/boulder1.mdl",
  64. "models/props_coalmines/boulder2.mdl",
  65. "models/props_farm/concrete_block001.mdl", // 152
  66. // "models/props_farm/tractor_tire001.mdl", // requires offset
  67. "models/props_farm/welding_machine01.mdl",
  68. "models/props_medieval/medieval_resupply.mdl",
  69. "models/props_medieval/target/target.mdl",
  70. "models/props_swamp/picnic_table.mdl",
  71. "models/props_manor/baby_grand_01.mdl", // 154
  72. "models/props_manor/bookcase_132_02.mdl",
  73. "models/props_manor/chair_01.mdl",
  74. "models/props_manor/couch_01.mdl",
  75. "models/props_manor/grandfather_clock_01.mdl",
  76. // "models/props_manor/tractor_01.mdl", // 227
  77. // "models/props_gameplay/haybale.mdl", // requires offset
  78. "models/props_viaduct_event/coffin_simple_closed.mdl",
  79. // "models/props_farm/wooden_barrel.mdl", // requires offset
  80. "models/props_2fort/miningcrate001.mdl",
  81. "models/props_gameplay/resupply_locker.mdl",
  82. "models/props_2fort/oildrum.mdl",
  83. // "models/props_farm/wood_pile.mdl", // requires offset
  84. "models/props_lakeside/wood_crate_01.mdl",
  85. // "models/props_farm/pallet001.mdl", // requires offset
  86. "models/props_well/hand_truck01.mdl",
  87. "models/props_vehicles/mining_car_metal.mdl",
  88. "models/props_2fort/tire002.mdl",
  89. "models/props_well/computer_cart01.mdl",
  90. "models/egypt/palm_tree/palm_tree.mdl"
  91. };
  92. //-----------------------------------------------------------------------------------------------------
  93. // The Horseless Headless Horseman
  94. //-----------------------------------------------------------------------------------------------------
  95. LINK_ENTITY_TO_CLASS( merasmus, CMerasmus );
  96. IMPLEMENT_SERVERCLASS_ST( CMerasmus, DT_Merasmus )
  97. SendPropBool( SENDINFO( m_bRevealed ) ),
  98. SendPropBool( SENDINFO( m_bIsDoingAOEAttack ) ),
  99. SendPropBool( SENDINFO( m_bStunned ) ),
  100. END_SEND_TABLE()
  101. int CMerasmus::m_level = 1;
  102. IMPLEMENT_AUTO_LIST( IMerasmusAutoList );
  103. //-----------------------------------------------------------------------------------------------------
  104. CMerasmus::CMerasmus()
  105. {
  106. m_intention = new CMerasmusIntention( this );
  107. m_locomotor = new CMerasmusLocomotion( this );
  108. m_flyingLocomotor = new CMerasmusFlyingLocomotion( this );
  109. m_body = new CMerasmusBody( this );
  110. m_bRevealed = false;
  111. m_bIsDoingAOEAttack = false;
  112. m_wheel = NULL;
  113. m_stunTimer.Invalidate();
  114. m_bStunned = false;
  115. m_nBombHitCount = 0;
  116. m_hMerasmusRevealer = NULL;
  117. m_isFlying = false;
  118. m_isHiding=false;
  119. m_hHealthBar = g_pMonsterResource;
  120. ListenForGameEvent( "player_death" );
  121. }
  122. //-----------------------------------------------------------------------------------------------------
  123. CMerasmus::~CMerasmus()
  124. {
  125. if ( m_intention )
  126. delete m_intention;
  127. if ( m_locomotor )
  128. delete m_locomotor;
  129. if ( m_flyingLocomotor )
  130. delete m_flyingLocomotor;
  131. if ( m_body )
  132. delete m_body;
  133. // Make sure the health meter goes away
  134. if( m_hHealthBar.Get() )
  135. {
  136. m_hHealthBar->HideBossHealthMeter();
  137. }
  138. IGameEvent *event = gameeventmanager->CreateEvent( "recalculate_truce" );
  139. if ( event )
  140. {
  141. gameeventmanager->FireEvent( event, true );
  142. }
  143. }
  144. //-----------------------------------------------------------------------------------------------------
  145. void CMerasmus::Precache()
  146. {
  147. BaseClass::Precache();
  148. // always allow late precaching, so we don't pay the cost of the
  149. // Halloween Boss for the entire year
  150. bool bAllowPrecache = CBaseEntity::IsPrecacheAllowed();
  151. CBaseEntity::SetAllowPrecache( true );
  152. PrecacheMerasmus();
  153. CBaseEntity::SetAllowPrecache( bAllowPrecache );
  154. }
  155. void CMerasmus::PrecacheMerasmus()
  156. {
  157. int model = PrecacheModel( MERASMUS_MODEL_NAME );
  158. PrecacheGibsForModel( model );
  159. // precache disguise prop list
  160. for ( int i=0; i<ARRAYSIZE( s_pszDisguiseProps ); ++i )
  161. {
  162. PrecacheModel( s_pszDisguiseProps[i] );
  163. }
  164. PrecacheModel( MERASMUS_BOMB_MODEL );
  165. PrecacheModel( "models/props_lakeside_event/bomb_temp_hat.mdl" ); // bomb head on player
  166. PrecacheModel( "models/props_halloween/bombonomicon.mdl" ); // bombonomicon hint to player
  167. // Boss VOs
  168. PrecacheScriptSound( "Halloween.MerasmusAppears" );
  169. PrecacheScriptSound( "Halloween.MerasmusBanish" );
  170. //PrecacheScriptSound( "Halloween.MerasmusCastBleedingSpell" );
  171. PrecacheScriptSound( "Halloween.MerasmusCastFireSpell" );
  172. PrecacheScriptSound( "Halloween.MerasmusCastJarateSpell" );
  173. PrecacheScriptSound( "Halloween.MerasmusCastJarateSpellRare" );
  174. PrecacheScriptSound( "Halloween.MerasmusLaunchSpell" );
  175. PrecacheScriptSound( "Halloween.MerasmusControlPoint" );
  176. PrecacheScriptSound( "Halloween.MerasmusDepart" );
  177. PrecacheScriptSound( "Halloween.MerasmusDepartRare" );
  178. PrecacheScriptSound( "Halloween.MerasmusDiscovered" );
  179. PrecacheScriptSound( "Halloween.MerasmusGrenadeThrow" );
  180. PrecacheScriptSound( "Halloween.MerasmusHidden" );
  181. PrecacheScriptSound( "Halloween.MerasmusHiddenRare" );
  182. PrecacheScriptSound( "Halloween.MerasmusHitByBomb" );
  183. PrecacheScriptSound( "Halloween.MerasmusHitByBombRare" );
  184. PrecacheScriptSound( "Halloween.MerasmusInitiateHiding" );
  185. PrecacheScriptSound( "Halloween.MerasmusStaffAttack" );
  186. PrecacheScriptSound( "Halloween.MerasmusStaffAttackRare" );
  187. PrecacheScriptSound( "Halloween.MerasmusTauntFakeProp" );
  188. //PrecacheScriptSound( "Halloween.MerasmusTeleport" );
  189. // Boss event sound effects
  190. PrecacheScriptSound( "Halloween.MerasmusBossSpawn" );
  191. PrecacheScriptSound( "Halloween.Merasmus_Death" );
  192. PrecacheScriptSound( "Halloween.EyeballBossEscapeSoon" );
  193. PrecacheScriptSound( "Halloween.EyeballBossEscapeImminent" );
  194. PrecacheScriptSound( "Halloween.EyeballBossEscaped" );
  195. PrecacheScriptSound( "Halloween.Merasmus_Float" );
  196. PrecacheScriptSound( "Halloween.Merasmus_Stun" );
  197. PrecacheScriptSound( "Halloween.Merasmus_Spell" );
  198. PrecacheScriptSound( "Halloween.Merasmus_Hiding_Explode" );
  199. PrecacheParticleSystem( "merasmus_spawn" ); // spawn
  200. PrecacheParticleSystem( "merasmus_tp" ); // puff effect
  201. PrecacheParticleSystem( "merasmus_blood" ); // when he takes damage
  202. PrecacheParticleSystem( "merasmus_blood_bits" ); // when he takes damage while stunned
  203. PrecacheParticleSystem( "merasmus_ambient_body" ); // glow around the body
  204. PrecacheParticleSystem( "merasmus_shoot" ); // when he casts spell
  205. PrecacheParticleSystem( "merasmus_book_attack" ); // big attack
  206. PrecacheParticleSystem( "merasmus_object_spawn" ); // object spawn
  207. PrecacheParticleSystem( "merasmus_zap" ); // zap!
  208. PrecacheParticleSystem( "merasmus_dazed" ); // stunned
  209. PrecacheParticleSystem( "merasmus_dazed_explosion" ); // bomb head explode
  210. // TEMP
  211. PrecacheScriptSound( "Halloween.HeadlessBossAxeHitFlesh" );
  212. }
  213. //-----------------------------------------------------------------------------------------------------
  214. void CMerasmus::Spawn( void )
  215. {
  216. Precache();
  217. SetModel( MERASMUS_MODEL_NAME );
  218. BaseClass::Spawn();
  219. PlayHighPrioritySound("Halloween.MerasmusAppears");
  220. m_isHiding=false;
  221. // scale the boss' health with the player count
  222. int totalPlayers = GetGlobalTFTeam( TF_TEAM_BLUE )->GetNumPlayers() + GetGlobalTFTeam( TF_TEAM_RED )->GetNumPlayers();
  223. int health = tf_merasmus_health_base.GetInt();
  224. if ( totalPlayers > tf_merasmus_min_player_count.GetInt() )
  225. {
  226. health += ( totalPlayers - tf_merasmus_min_player_count.GetInt() ) * tf_merasmus_health_per_player.GetInt();
  227. }
  228. CBaseEntity *pWheel = gEntList.FindEntityByName( NULL, "wheel_of_fortress" );
  229. if ( pWheel )
  230. {
  231. m_wheel = assert_cast< CWheelOfDoom* >( pWheel );
  232. }
  233. SetHealth( health );
  234. SetMaxHealth( health );
  235. m_homePos = GetAbsOrigin();
  236. m_damagePoseParameter = -1;
  237. SetBloodColor( DONT_BLEED );
  238. if ( m_pIdleSound )
  239. {
  240. CSoundEnvelopeController::GetController().SoundDestroy( m_pIdleSound );
  241. m_pIdleSound = NULL;
  242. }
  243. // Collect all of the players that are alive at this moment. These are the players who will get
  244. // their hat leveled up when Merasmus dies. We only want to give credit to these players to discourage
  245. // the strategy of spawning Merasmus with 10 people so his health is scaled for 10 people,
  246. // then have 22 other people connect and destroy him.
  247. CUtlVector< CTFPlayer * > playerVector;
  248. CollectPlayers( &playerVector );
  249. FOR_EACH_VEC( playerVector, i )
  250. {
  251. m_startingAttackersVector.AddToTail( playerVector[i] );
  252. }
  253. CPVSFilter filter( GetAbsOrigin() );
  254. CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
  255. m_pIdleSound = controller.SoundCreate( filter, entindex(), "Halloween.Merasmus_Float" );
  256. controller.Play( m_pIdleSound, 1.0, 100 );
  257. m_solidType = GetSolid();
  258. m_solidFlags = GetSolidFlags();
  259. const float flLifeTime = tf_merasmus_lifetime.GetFloat();
  260. m_lifeTimer.Start( flLifeTime );
  261. m_flLastWarnTime = 0;
  262. IGameEvent *event = gameeventmanager->CreateEvent( "merasmus_summoned" );
  263. if ( event )
  264. {
  265. event->SetInt( "level", GetLevel() );
  266. gameeventmanager->FireEvent( event );
  267. }
  268. TriggerLogicRelay( "boss_enter_relay", true );
  269. DispatchParticleEffect( "merasmus_spawn", GetAbsOrigin(), GetAbsAngles() );
  270. m_bossStats.ResetStats();
  271. event = gameeventmanager->CreateEvent( "recalculate_truce" );
  272. if ( event )
  273. {
  274. gameeventmanager->FireEvent( event, true );
  275. }
  276. }
  277. //---------------------------------------------------------------------------------------------
  278. void RemoveAllBombHeadFromPlayers()
  279. {
  280. CUtlVector< CTFPlayer * > playerVector;
  281. CollectPlayers( &playerVector, TF_TEAM_RED, COLLECT_ONLY_LIVING_PLAYERS );
  282. CollectPlayers( &playerVector, TF_TEAM_BLUE, COLLECT_ONLY_LIVING_PLAYERS, APPEND_PLAYERS );
  283. for ( int i=0; i<playerVector.Count(); ++i )
  284. {
  285. if ( playerVector[i]->m_Shared.InCond( TF_COND_HALLOWEEN_BOMB_HEAD ) )
  286. {
  287. playerVector[i]->m_Shared.RemoveCond( TF_COND_HALLOWEEN_BOMB_HEAD );
  288. }
  289. }
  290. }
  291. void CMerasmus::UpdateOnRemove()
  292. {
  293. Assert( TFGameRules() );
  294. if ( m_hHealthBar )
  295. {
  296. m_hHealthBar->HideBossHealthMeter();
  297. }
  298. if ( m_pIdleSound )
  299. {
  300. CSoundEnvelopeController::GetController().SoundDestroy( m_pIdleSound );
  301. m_pIdleSound = NULL;
  302. }
  303. // the boss is NULL, remove bomb head condition won't give players power play
  304. RemoveAllBombHeadFromPlayers();
  305. RemoveAllFakeProps();
  306. BaseClass::UpdateOnRemove();
  307. // Report Stats
  308. SW_ReportMerasmusStats();
  309. }
  310. //---------------------------------------------------------------------------------------------
  311. float MerasmusModifyDamage( const CTakeDamageInfo &info )
  312. {
  313. CTFWeaponBase *pWeapon = dynamic_cast< CTFWeaponBase * >( info.GetWeapon() );
  314. CObjectSentrygun *sentry = dynamic_cast< CObjectSentrygun * >( info.GetInflictor() );
  315. CTFProjectile_SentryRocket *sentryRocket = dynamic_cast< CTFProjectile_SentryRocket * >( info.GetInflictor() );
  316. if ( sentry || sentryRocket )
  317. {
  318. return info.GetDamage() * 0.5f;
  319. }
  320. else if ( pWeapon )
  321. {
  322. switch( pWeapon->GetWeaponID() )
  323. {
  324. case TF_WEAPON_MINIGUN:
  325. return info.GetDamage() * 0.5f;
  326. case TF_WEAPON_SODA_POPPER:
  327. return info.GetDamage() * 1.5f;
  328. case TF_WEAPON_HANDGUN_SCOUT_PRIMARY:
  329. return info.GetDamage() * 1.75;
  330. case TF_WEAPON_SCATTERGUN:
  331. case TF_WEAPON_REVOLVER:
  332. return info.GetDamage() * 2.f;
  333. case TF_WEAPON_SNIPERRIFLE:
  334. case TF_WEAPON_SNIPERRIFLE_DECAP:
  335. case TF_WEAPON_SNIPERRIFLE_CLASSIC:
  336. case TF_WEAPON_COMPOUND_BOW:
  337. case TF_WEAPON_KNIFE:
  338. case TF_WEAPON_PEP_BRAWLER_BLASTER:
  339. return info.GetDamage() * 3.f;
  340. }
  341. }
  342. // unmodified
  343. return info.GetDamage();
  344. }
  345. //-----------------------------------------------------------------------------------------------------
  346. int CMerasmus::OnTakeDamage_Alive( const CTakeDamageInfo &info )
  347. {
  348. if ( !IsRevealed() )
  349. {
  350. return 0;
  351. }
  352. if ( IsSelf( info.GetAttacker() ) )
  353. {
  354. // don't injure myself
  355. return 0;
  356. }
  357. CTakeDamageInfo modifiedInfo = info;
  358. if ( RandomInt( 0, 30 ) == 0 )
  359. {
  360. //EmitSound( "Halloween.MerasmusHurt" ); << add new sound entry
  361. }
  362. if ( IsStunned() )
  363. {
  364. DispatchParticleEffect( "merasmus_blood", modifiedInfo.GetDamagePosition(), GetAbsAngles() );
  365. }
  366. else
  367. {
  368. DispatchParticleEffect( "merasmus_blood_bits", modifiedInfo.GetDamagePosition(), GetAbsAngles() );
  369. }
  370. modifiedInfo.SetDamage( MerasmusModifyDamage( modifiedInfo ) );
  371. if ( m_bIsDoingAOEAttack || m_bStunned )
  372. {
  373. modifiedInfo.AddDamageType( DMG_CRITICAL );
  374. }
  375. int result = BaseClass::OnTakeDamage_Alive( modifiedInfo );
  376. // update boss health meter
  377. float healthPercentage = (float)GetHealth() / (float)GetMaxHealth();
  378. if ( m_hHealthBar )
  379. {
  380. if ( healthPercentage <= 0.0f )
  381. {
  382. m_hHealthBar->HideBossHealthMeter();
  383. }
  384. else
  385. {
  386. m_hHealthBar->SetBossHealthPercentage( healthPercentage );
  387. }
  388. }
  389. // Stats Tracking
  390. CTFPlayer *pAttacker = ToTFPlayer( info.GetAttacker() );
  391. if ( pAttacker )
  392. {
  393. int iClass = pAttacker->GetPlayerClass()->GetClassIndex();
  394. if ( iClass > TF_CLASS_UNDEFINED && iClass < TF_LAST_NORMAL_CLASS )
  395. {
  396. m_bossStats.m_arrClassDamage[ iClass ] += info.GetDamage();
  397. }
  398. }
  399. return result;
  400. }
  401. //---------------------------------------------------------------------------------------------
  402. void CMerasmus::FireGameEvent( IGameEvent *event)
  403. {
  404. if ( !V_strcmp( event->GetName(), "player_death" ) )
  405. {
  406. // Collect Data
  407. int nDmgType = event->GetInt( "customkill", -1 );
  408. if ( nDmgType == TF_DMG_CUSTOM_MERASMUS_DECAPITATION || nDmgType == TF_DMG_CUSTOM_MERASMUS_ZAP )
  409. {
  410. m_bossStats.m_nStaffKills++;
  411. }
  412. else if ( nDmgType == TF_DMG_CUSTOM_MERASMUS_GRENADE || nDmgType == TF_DMG_CUSTOM_MERASMUS_PLAYER_BOMB )
  413. {
  414. m_bossStats.m_nBombKills++;
  415. }
  416. else
  417. {
  418. // Treat as a PvPKill
  419. m_bossStats.m_nPvpKills++;
  420. }
  421. }
  422. }
  423. //---------------------------------------------------------------------------------------------
  424. void CMerasmus::Update( void )
  425. {
  426. BaseClass::Update();
  427. if ( m_damagePoseParameter < 0 )
  428. {
  429. m_damagePoseParameter = LookupPoseParameter( "damage" );
  430. }
  431. if ( m_damagePoseParameter >= 0 )
  432. {
  433. SetPoseParameter( m_damagePoseParameter, 1.0f - ( (float)GetHealth() / (float)GetMaxHealth() ) );
  434. }
  435. }
  436. Vector CMerasmus::GetCastPosition() const
  437. {
  438. Vector vForward, vRight, vUp;
  439. AngleVectors( GetAbsAngles(), &vForward, &vRight, &vUp );
  440. return WorldSpaceCenter() + vForward * 60.f + 30.f * vRight + 60.f * vUp;
  441. }
  442. void RemoveAllGrenades( CMerasmus *me )
  443. {
  444. const int maxCollectedEntities = 1024;
  445. CBaseEntity *pObjects[ maxCollectedEntities ];
  446. int count = UTIL_EntitiesInSphere( pObjects, maxCollectedEntities, me->GetAbsOrigin(), 400, FL_GRENADE );
  447. for( int i = 0; i < count; ++i )
  448. {
  449. if ( pObjects[i] == NULL )
  450. continue;
  451. if ( pObjects[i]->IsPlayer() )
  452. continue;
  453. // Remove the enemy pipe
  454. pObjects[i]->SetThink( &CBaseEntity::SUB_Remove );
  455. pObjects[i]->SetNextThink( gpGlobals->curtime );
  456. pObjects[i]->SetTouch( NULL );
  457. pObjects[i]->AddEffects( EF_NODRAW );
  458. }
  459. }
  460. void CMerasmus::OnRevealed(bool bPlaySound)
  461. {
  462. RecordDisguiseTime();
  463. m_bRevealed = true;
  464. m_isHiding = false;
  465. m_nRevealedHealth = GetHealth();
  466. if (bPlaySound)
  467. {
  468. PlayHighPrioritySound( "Halloween.MerasmusDiscovered" );
  469. }
  470. RemoveEffects( EF_NOINTERP | EF_NODRAW );
  471. DispatchParticleEffect( "merasmus_spawn", WorldSpaceCenter(), GetAbsAngles() );
  472. // don't collide with anything, we do our own collision detection in our think method
  473. SetSolid( m_solidType );
  474. SetSolidFlags( m_solidFlags );
  475. RemoveAllFakeProps();
  476. TFGameRules()->PushAllPlayersAway( GetAbsOrigin(), 400.f, 500.f, TF_TEAM_RED );
  477. TFGameRules()->PushAllPlayersAway( GetAbsOrigin(), 400.f, 500.f, TF_TEAM_BLUE );
  478. RemoveAllGrenades( this );
  479. // give player who found merasmus buff
  480. if ( m_hMerasmusRevealer )
  481. {
  482. // condition was removed by blowing up merasmus, you get buff award
  483. const float buffDuration = 10.0f;
  484. m_hMerasmusRevealer->m_Shared.AddCond( TF_COND_CRITBOOSTED_PUMPKIN, buffDuration );
  485. m_hMerasmusRevealer->m_Shared.AddCond( TF_COND_SPEED_BOOST, buffDuration );
  486. m_hMerasmusRevealer->m_Shared.AddCond( TF_COND_INVULNERABLE, buffDuration );
  487. }
  488. m_hMerasmusRevealer = NULL;
  489. // show Boss' health meter on HUD
  490. if ( m_hHealthBar )
  491. {
  492. float healthPercentage = (float)GetHealth() / (float)GetMaxHealth();
  493. m_hHealthBar->SetBossHealthPercentage( healthPercentage );
  494. }
  495. // face towards a nearby player
  496. CUtlVector< CTFPlayer * > playerVector;
  497. CollectPlayers( &playerVector, TF_TEAM_RED, COLLECT_ONLY_LIVING_PLAYERS );
  498. CollectPlayers( &playerVector, TF_TEAM_BLUE, COLLECT_ONLY_LIVING_PLAYERS, APPEND_PLAYERS );
  499. float closeRangeSq = FLT_MAX;
  500. CTFPlayer *close = NULL;
  501. for( int i=0; i<playerVector.Count(); ++i )
  502. {
  503. CTFPlayer *player = playerVector[i];
  504. float rangeSq = GetRangeSquaredTo( player );
  505. if ( rangeSq < closeRangeSq )
  506. {
  507. closeRangeSq = rangeSq;
  508. close = player;
  509. }
  510. }
  511. QAngle facingAngle;
  512. if ( close )
  513. {
  514. Vector toPlayer = close->GetAbsOrigin() - GetAbsOrigin();
  515. toPlayer.z = 0.0f;
  516. toPlayer.NormalizeInPlace();
  517. VectorAngles( toPlayer, Vector(0,0,1), facingAngle );
  518. }
  519. else
  520. {
  521. facingAngle.x = 0.0f;
  522. facingAngle.y = RandomFloat( 0.0f, 360.0f );
  523. facingAngle.z = 0.0f;
  524. }
  525. SetAbsAngles( facingAngle );
  526. }
  527. bool CMerasmus::ShouldReveal() const
  528. {
  529. int nDestroyedProps = 0;
  530. for ( int i=0; i<m_fakePropVector.Count(); ++i )
  531. {
  532. if ( m_fakePropVector[i] == NULL )
  533. {
  534. nDestroyedProps++;
  535. }
  536. }
  537. return nDestroyedProps >= m_nDestroyedPropsToReveal;
  538. }
  539. bool CMerasmus::IsNextKilledPropMerasmus() const
  540. {
  541. int nDestroyedProps = 0;
  542. for ( int i=0; i<m_fakePropVector.Count(); ++i )
  543. {
  544. if ( m_fakePropVector[i] == NULL )
  545. {
  546. nDestroyedProps++;
  547. }
  548. }
  549. return nDestroyedProps + 1 == m_nDestroyedPropsToReveal;
  550. }
  551. void CMerasmus::OnDisguise()
  552. {
  553. m_flStartDisguiseTime = gpGlobals->curtime;
  554. m_bRevealed = false;
  555. m_isHiding = true;
  556. StopAOEAttack();
  557. AddEffects( EF_NOINTERP | EF_NODRAW );
  558. DispatchParticleEffect( "merasmus_tp", WorldSpaceCenter(), GetAbsAngles() );
  559. // don't collide with anything, we do our own collision detection in our think method
  560. SetSolid( SOLID_NONE );
  561. SetSolidFlags( FSOLID_NOT_SOLID );
  562. // fake randomness of when Merasmus should reveal
  563. m_nDestroyedPropsToReveal = RandomInt( MAX( 1, tf_merasmus_min_props_to_reveal.GetFloat() * m_fakePropVector.Count() ), m_fakePropVector.Count() );
  564. }
  565. bool CMerasmus::ShouldDisguise() const
  566. {
  567. if ( GetHealth() <= 0 )
  568. {
  569. return false;
  570. }
  571. float flLostHealthPercentage = (float)( m_nRevealedHealth - GetHealth() ) / (float)GetMaxHealth();
  572. return flLostHealthPercentage > tf_merasmus_should_disguise_threshold.GetFloat();
  573. }
  574. CTFWeaponBaseGrenadeProj* CMerasmus::CreateMerasmusGrenade( const Vector& vPosition, const Vector& vVelocity, CBaseCombatCharacter* pOwner, float fScale )
  575. {
  576. QAngle qAngles = RandomAngle( 0, 360 );
  577. CTFWeaponBaseMerasmusGrenade *pGrenade = static_cast<CTFWeaponBaseMerasmusGrenade*>( CBaseEntity::Create( "tf_weaponbase_merasmus_grenade", vPosition, qAngles, pOwner ) );
  578. if ( pGrenade )
  579. {
  580. pGrenade->SetModel( MERASMUS_BOMB_MODEL );
  581. DispatchSpawn( pGrenade );
  582. pGrenade->InitGrenade( vVelocity, AngularImpulse( 600, random->RandomInt( -1200, 1200 ), 0 ), pOwner, 50 * fScale, 300.f * fScale );
  583. pGrenade->SetDetonateTimerLength( 2.f );
  584. pGrenade->SetModelScale( fScale );
  585. pGrenade->SetCollisionGroup( TFCOLLISION_GROUP_ROCKETS ); // we want to use collision_group_rockets so we don't ever collide with players
  586. }
  587. return pGrenade;
  588. }
  589. const char* CMerasmus::GetRandomPropModelName()
  590. {
  591. int which = RandomInt( 0, ARRAYSIZE( s_pszDisguiseProps ) - 1 );
  592. return s_pszDisguiseProps[ which ];
  593. }
  594. void CMerasmus::PushPlayer( CTFPlayer* pPlayer, float flPushForce ) const
  595. {
  596. // send the player flying
  597. // make sure we push players up and away
  598. Vector toPlayer = pPlayer->EyePosition() - GetAbsOrigin();
  599. toPlayer.z = 0.0f;
  600. toPlayer.NormalizeInPlace();
  601. toPlayer.z = 1.0f;
  602. Vector push = flPushForce * toPlayer;
  603. pPlayer->ApplyAbsVelocityImpulse( push );
  604. }
  605. void CMerasmus::AddStun( CTFPlayer* pPlayer )
  606. {
  607. if ( !IsRevealed() )
  608. {
  609. // don't let bomb head player explode on merasmus while disguise
  610. return;
  611. }
  612. // first stun
  613. if ( !IsStunned() )
  614. {
  615. CPVSFilter filter( WorldSpaceCenter() );
  616. if (RandomInt( 1, 10) == 9 )
  617. {
  618. PlayLowPrioritySound( filter, "Halloween.MerasmusHitByBombRare" );
  619. }
  620. else
  621. {
  622. PlayLowPrioritySound( filter, "Halloween.MerasmusHitByBomb" );
  623. }
  624. }
  625. // buff the player that stunned me
  626. const float buffDuration = 10.0f;
  627. pPlayer->m_Shared.AddCond( TF_COND_CRITBOOSTED_PUMPKIN, buffDuration );
  628. pPlayer->m_Shared.AddCond( TF_COND_SPEED_BOOST, buffDuration );
  629. pPlayer->m_Shared.AddCond( TF_COND_INVULNERABLE, buffDuration );
  630. pPlayer->m_Shared.RemoveCond( TF_COND_STUNNED );
  631. pPlayer->m_Shared.RemoveCond( TF_COND_HALLOWEEN_BOMB_HEAD );
  632. pPlayer->MerasmusPlayerBombExplode( false );
  633. PushPlayer( pPlayer, 300.f );
  634. DispatchParticleEffect( "merasmus_dazed_explosion", WorldSpaceCenter(), GetAbsAngles() );
  635. IGameEvent *pEvent = gameeventmanager->CreateEvent( "merasmus_stunned" );
  636. if ( pEvent )
  637. {
  638. pEvent->SetInt( "player", pPlayer->GetUserID() );
  639. gameeventmanager->FireEvent( pEvent, true );
  640. }
  641. // don't stun while doing AOE
  642. if ( !m_bIsDoingAOEAttack )
  643. {
  644. m_nBombHitCount++;
  645. m_stunTimer.Start( tf_merasmus_stun_duration.GetFloat() );
  646. }
  647. }
  648. void CMerasmus::OnBeginStun()
  649. {
  650. EmitSound( "Halloween.Merasmus_Stun" );
  651. m_bStunned = true;
  652. }
  653. void CMerasmus::OnEndStun()
  654. {
  655. m_stunTimer.Invalidate();
  656. m_bStunned = false;
  657. }
  658. void CMerasmus::AddFakeProp( CTFMerasmusTrickOrTreatProp* pFakeProp )
  659. {
  660. m_fakePropVector.AddToTail( pFakeProp );
  661. }
  662. void CMerasmus::RemoveAllFakeProps()
  663. {
  664. for ( int i=0; i<m_fakePropVector.Count(); ++i )
  665. {
  666. if ( m_fakePropVector[i] != NULL )
  667. {
  668. UTIL_Remove( m_fakePropVector[i] );
  669. }
  670. }
  671. m_fakePropVector.RemoveAll();
  672. }
  673. void BombHeadForTeam( int nTeam, int nBombHeadPlayers )
  674. {
  675. // decrease nBombHeadPlayers by the number of existing bomb heads
  676. CUtlVector< CTFPlayer * > playerVector;
  677. CollectPlayers( &playerVector, nTeam, COLLECT_ONLY_LIVING_PLAYERS );
  678. if ( playerVector.Count() <= 0 )
  679. {
  680. // everyone on this team is dead
  681. return;
  682. }
  683. for( int n=0; n<nBombHeadPlayers; ++n )
  684. {
  685. // find the living player who was a bombhead the longest time ago and give them the bomb
  686. CTFPlayer *pVictim = NULL;
  687. float oldestTimeStamp = -1.0f;
  688. for( int i=0; i<playerVector.Count(); ++i )
  689. {
  690. CTFPlayer *pPlayer = playerVector[i];
  691. if ( pPlayer->GetTimeSinceWasBombHead() > oldestTimeStamp &&
  692. !pPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_BOMB_HEAD ) &&
  693. pPlayer->GetLastKnownArea() &&
  694. pPlayer->GetLastKnownArea()->HasFuncNavPrefer() )
  695. {
  696. pVictim = pPlayer;
  697. oldestTimeStamp = pPlayer->GetTimeSinceWasBombHead();
  698. }
  699. }
  700. if ( !pVictim )
  701. {
  702. // no victims available - try again next time
  703. return;
  704. }
  705. // give this victim the bomb
  706. float flBuffDuration = tf_merasmus_bomb_head_duration.GetFloat();
  707. pVictim->m_Shared.StunPlayer( tf_merasmus_bomb_head_duration.GetFloat(), 0.f, TF_STUN_LOSER_STATE );
  708. pVictim->m_Shared.AddCond( TF_COND_HALLOWEEN_BOMB_HEAD, flBuffDuration );
  709. pVictim->m_Shared.AddCond( TF_COND_SPEED_BOOST, flBuffDuration );
  710. pVictim->SetBombHeadTimestamp();
  711. // notify player they are a bomb
  712. ClientPrint( pVictim, HUD_PRINTCENTER, "#TF_HALLOWEEN_MERASMUS_YOU_ARE_BOMB", pVictim->GetPlayerName() );
  713. }
  714. }
  715. void CMerasmus::BombHeadMode()
  716. {
  717. int nBombHeadPlayers = tf_merasmus_bomb_head_per_team.GetInt();
  718. BombHeadForTeam( TF_TEAM_RED, nBombHeadPlayers );
  719. BombHeadForTeam( TF_TEAM_BLUE, nBombHeadPlayers );
  720. }
  721. bool CMerasmus::ShouldLeave() const
  722. {
  723. return m_lifeTimer.IsElapsed();
  724. }
  725. void CMerasmus::LeaveWarning()
  726. {
  727. if ( m_lifeTimer.GetRemainingTime() < 10.0f && m_flLastWarnTime > 10.0f )
  728. {
  729. IGameEvent *event = gameeventmanager->CreateEvent( "merasmus_escape_warning" );
  730. if ( event )
  731. {
  732. event->SetInt( "level", GetLevel() );
  733. event->SetInt( "time_remaining", 10 );
  734. gameeventmanager->FireEvent( event );
  735. }
  736. }
  737. else if ( m_lifeTimer.GetRemainingTime() < 30.0f && m_flLastWarnTime > 30.0f )
  738. {
  739. IGameEvent *event = gameeventmanager->CreateEvent( "merasmus_escape_warning" );
  740. if ( event )
  741. {
  742. event->SetInt( "level", GetLevel() );
  743. event->SetInt( "time_remaining", 30 );
  744. gameeventmanager->FireEvent( event );
  745. }
  746. }
  747. else if ( m_lifeTimer.GetRemainingTime() < 60.0f && m_flLastWarnTime > 60.0f )
  748. {
  749. IGameEvent *event = gameeventmanager->CreateEvent( "merasmus_escape_warning" );
  750. if ( event )
  751. {
  752. event->SetInt( "level", GetLevel() );
  753. event->SetInt( "time_remaining", 60 );
  754. gameeventmanager->FireEvent( event );
  755. }
  756. }
  757. m_flLastWarnTime = m_lifeTimer.GetRemainingTime();
  758. }
  759. void CMerasmus::OnLeaveWhileInPropForm()
  760. {
  761. CUtlVector< CBaseEntity* > validProps;
  762. for ( int i=0; i<m_fakePropVector.Count(); ++i )
  763. {
  764. if ( m_fakePropVector[i] != NULL )
  765. {
  766. validProps.AddToTail( m_fakePropVector[i] );
  767. }
  768. }
  769. if ( validProps.Count() )
  770. {
  771. int which = RandomInt( 0, validProps.Count() -1 );
  772. SetAbsOrigin( validProps[ which ]->GetAbsOrigin() );
  773. }
  774. }
  775. void CMerasmus::TriggerLogicRelay( const char* pszLogicRelayName, bool bSpawn /*= false*/ )
  776. {
  777. CLogicRelay* pLogicRelay = assert_cast< CLogicRelay* >( gEntList.FindEntityByName( NULL, pszLogicRelayName ) );
  778. if ( pLogicRelay )
  779. {
  780. inputdata_t data;
  781. data.pCaller = this;
  782. data.pActivator = this;
  783. pLogicRelay->InputTrigger( data );
  784. if ( bSpawn )
  785. {
  786. SetAbsOrigin( pLogicRelay->GetAbsOrigin() );
  787. }
  788. }
  789. }
  790. void CMerasmus::PlayLowPrioritySound( IRecipientFilter &filter, const char* pszSoundEntryName )
  791. {
  792. CSoundParameters params;
  793. if ( CBaseEntity::GetParametersForSound( pszSoundEntryName, params, NULL ) )
  794. {
  795. EmitSound_t es( params );
  796. es.m_nFlags |= SND_DO_NOT_OVERWRITE_EXISTING_ON_CHANNEL;
  797. EmitSound( filter, entindex(), es );
  798. }
  799. }
  800. void CMerasmus::PlayHighPrioritySound( const char* pszSoundEntryName )
  801. {
  802. CBroadcastRecipientFilter filter;
  803. CSoundParameters params;
  804. if ( CBaseEntity::GetParametersForSound( pszSoundEntryName, params, NULL ) )
  805. {
  806. EmitSound_t es( params );
  807. EmitSound( filter, entindex(), es );
  808. }
  809. }
  810. //---------------------------------------------------------------------------------------------
  811. void CMerasmus::RecordDisguiseTime( )
  812. {
  813. if ( m_flStartDisguiseTime == 0 )
  814. return;
  815. float flTime = ( gpGlobals->curtime - m_flStartDisguiseTime );
  816. if ( m_bossStats.m_flPropHuntTime1 == 0 )
  817. {
  818. m_bossStats.m_flPropHuntTime1 = flTime;
  819. }
  820. else
  821. {
  822. m_bossStats.m_flPropHuntTime2 = flTime;
  823. }
  824. m_flStartDisguiseTime = 0;
  825. }
  826. void CMerasmus::StartRespawnTimer() const
  827. {
  828. if( TFGameRules() )
  829. {
  830. if( GetLevel() <= 3 )
  831. {
  832. TFGameRules()->StartHalloweenBossTimer( tf_merasmus_spawn_interval.GetFloat() ,tf_merasmus_spawn_interval_variation.GetFloat() );
  833. }
  834. else
  835. {
  836. TFGameRules()->StartHalloweenBossTimer( 60.f );
  837. }
  838. }
  839. }
  840. //---------------------------------------------------------------------------------------------
  841. void CMerasmus::SW_ReportMerasmusStats( void )
  842. {
  843. if ( !GCClientSystem() )
  844. return;
  845. static uint8 unEventCounter = 0;
  846. GCSDK::CProtoBufMsg<CMsgHalloween_Merasmus2012> msg( k_EMsgGC_Halloween_Merasmus2012 );
  847. msg.Body().set_time_submitted( CRTime::RTime32TimeCur() );
  848. msg.Body().set_is_valve_server( false );
  849. msg.Body().set_boss_level( GetLevel() );
  850. msg.Body().set_spawned_health( GetMaxHealth() );
  851. msg.Body().set_remaining_health( GetHealth() );
  852. msg.Body().set_life_time( (int)m_lifeTimer.GetElapsedTime() ); // Amount of time in seconds, boss was alive for
  853. msg.Body().set_bomb_kills( m_bossStats.m_nBombKills ); // Kills from Bombs
  854. msg.Body().set_staff_kills( m_bossStats.m_nStaffKills ); // kills from staff attack
  855. msg.Body().set_pvp_kills( m_bossStats.m_nPvpKills ); // Number of kills from players while Boss is out (Jerk factor)
  856. msg.Body().set_prophunt_time1( m_bossStats.m_flPropHuntTime1 );
  857. msg.Body().set_prophunt_time2( m_bossStats.m_flPropHuntTime2 );
  858. msg.Body().set_dmg_scout( m_bossStats.m_arrClassDamage[ TF_CLASS_SCOUT ] ); // Amount of damage done by each class
  859. msg.Body().set_dmg_sniper( m_bossStats.m_arrClassDamage[ TF_CLASS_SNIPER] );
  860. msg.Body().set_dmg_soldier( m_bossStats.m_arrClassDamage[ TF_CLASS_SOLDIER] );
  861. msg.Body().set_dmg_demo( m_bossStats.m_arrClassDamage[ TF_CLASS_DEMOMAN] );
  862. msg.Body().set_dmg_medic( m_bossStats.m_arrClassDamage[ TF_CLASS_MEDIC ] );
  863. msg.Body().set_dmg_heavy( m_bossStats.m_arrClassDamage[ TF_CLASS_HEAVYWEAPONS ] );
  864. msg.Body().set_dmg_pyro( m_bossStats.m_arrClassDamage[ TF_CLASS_PYRO ] );
  865. msg.Body().set_dmg_spy( m_bossStats.m_arrClassDamage[ TF_CLASS_SPY ] );
  866. msg.Body().set_dmg_engineer( m_bossStats.m_arrClassDamage[ TF_CLASS_ENGINEER ] );
  867. // Class counts
  868. CUtlVector< CTFPlayer * > playerVector;
  869. CollectPlayers( &playerVector );
  870. int nClassCounts[ TF_LAST_NORMAL_CLASS ];
  871. V_memset( nClassCounts, 0, sizeof( nClassCounts ) );
  872. FOR_EACH_VEC( playerVector, index )
  873. {
  874. int iClass = playerVector[index]->GetPlayerClass()->GetClassIndex();
  875. if ( iClass > TF_CLASS_UNDEFINED && iClass < TF_LAST_NORMAL_CLASS )
  876. {
  877. nClassCounts[iClass]++;
  878. }
  879. }
  880. msg.Body().set_scout_count( nClassCounts[TF_CLASS_SCOUT] ); // Class and player break down at the point of boss despawn
  881. msg.Body().set_sniper_count( nClassCounts[TF_CLASS_SNIPER] );
  882. msg.Body().set_solider_count( nClassCounts[TF_CLASS_SOLDIER] );
  883. msg.Body().set_demo_count( nClassCounts[TF_CLASS_DEMOMAN] );
  884. msg.Body().set_medic_count( nClassCounts[TF_CLASS_MEDIC] );
  885. msg.Body().set_heavy_count( nClassCounts[TF_CLASS_HEAVYWEAPONS] );
  886. msg.Body().set_pyro_count( nClassCounts[TF_CLASS_PYRO] );
  887. msg.Body().set_spy_count( nClassCounts[TF_CLASS_SPY] );
  888. msg.Body().set_engineer_count( nClassCounts[TF_CLASS_ENGINEER] );
  889. GCClientSystem()->BSendMessage( msg );
  890. // OGS Version
  891. //
  892. //#if !defined(NO_STEAM)
  893. // KeyValues* pKVData = new KeyValues( "TF2Halloween2012MerasmusBossStats" );
  894. //
  895. // // Auto Values
  896. // // ID
  897. // // SessionID
  898. // // TimeSubmitted
  899. // //pKVData->SetBool( "IsValveServer", false );
  900. //
  901. // pKVData->SetInt( "BossLevel", GetLevel() );
  902. // pKVData->SetInt( "SpawnedHealth", GetMaxHealth() );
  903. // pKVData->SetInt( "RemainingHealth", GetHealth() ); // 0 == Boss was killed
  904. //
  905. // pKVData->SetInt( "LifeTime", (int)m_lifeTimer.GetElapsedTime() ); // Amount of time in seconds, boss was alive for
  906. // pKVData->SetInt( "BombKills", m_bossStats.m_nBombKills ); // Kills from Bombs
  907. // pKVData->SetInt( "StaffKills", m_bossStats.m_nStaffKills ); // kills from staff account
  908. // pKVData->SetInt( "PvPKills", m_bossStats.m_nPvpKills ); // Number of kills from players while Boss is out (Jerk factor)
  909. // pKVData->SetInt( "PropHuntTime1", m_bossStats.m_flPropHuntTime1 );
  910. // pKVData->SetInt( "PropHuntTime2", m_bossStats.m_flPropHuntTime2 );
  911. //
  912. // // Class Damage
  913. // pKVData->SetInt( "DmgFromScout", m_bossStats.m_arrClassDamage[ TF_CLASS_SCOUT ] ); // Amount of damage done by each class
  914. // pKVData->SetInt( "DmgFromSniper", m_bossStats.m_arrClassDamage[ TF_CLASS_SNIPER ] );
  915. // pKVData->SetInt( "DmgFromSoldier", m_bossStats.m_arrClassDamage[ TF_CLASS_SOLDIER ] );
  916. // pKVData->SetInt( "DmgFromDemo", m_bossStats.m_arrClassDamage[ TF_CLASS_DEMOMAN ] );
  917. // pKVData->SetInt( "DmgFromMedic", m_bossStats.m_arrClassDamage[ TF_CLASS_MEDIC ] );
  918. // pKVData->SetInt( "DmgFromHeavy", m_bossStats.m_arrClassDamage[ TF_CLASS_HEAVYWEAPONS ] );
  919. // pKVData->SetInt( "DmgFromPyro", m_bossStats.m_arrClassDamage[ TF_CLASS_PYRO ] );
  920. // pKVData->SetInt( "DmgFromSpy", m_bossStats.m_arrClassDamage[ TF_CLASS_SPY ] );
  921. // pKVData->SetInt( "DmgFromEngineer", m_bossStats.m_arrClassDamage[ TF_CLASS_ENGINEER ] );
  922. //
  923. // // Class counts
  924. // CUtlVector< CTFPlayer * > playerVector;
  925. // CollectPlayers( &playerVector );
  926. //
  927. // int nClassCounts[ TF_LAST_NORMAL_CLASS ];
  928. // V_memset( nClassCounts, 0, sizeof( nClassCounts ) );
  929. // FOR_EACH_VEC( playerVector, index )
  930. // {
  931. // int iClass = playerVector[index]->GetPlayerClass()->GetClassIndex();
  932. // if ( iClass > TF_CLASS_UNDEFINED && iClass < TF_LAST_NORMAL_CLASS )
  933. // {
  934. // nClassCounts[iClass]++;
  935. // }
  936. // }
  937. //
  938. // pKVData->SetInt( "ScoutCount", nClassCounts[TF_CLASS_SCOUT] ); // Class and player break down at the point of boss despawn
  939. // pKVData->SetInt( "SniperCount", nClassCounts[TF_CLASS_SNIPER] );
  940. // pKVData->SetInt( "SoldierCount", nClassCounts[TF_CLASS_SOLDIER] );
  941. // pKVData->SetInt( "DemoCount", nClassCounts[TF_CLASS_DEMOMAN] );
  942. // pKVData->SetInt( "MedicCount", nClassCounts[TF_CLASS_MEDIC] );
  943. // pKVData->SetInt( "HeavyCount", nClassCounts[TF_CLASS_HEAVYWEAPONS] );
  944. // pKVData->SetInt( "PyroCount", nClassCounts[TF_CLASS_PYRO] );
  945. // pKVData->SetInt( "SpyCount", nClassCounts[TF_CLASS_SPY] );
  946. // pKVData->SetInt( "EngineerCount", nClassCounts[TF_CLASS_ENGINEER] );
  947. //
  948. // //GetSteamWorksSGameStatsUploader().AddStatsForUpload( pKVData );
  949. //#endif
  950. m_bossStats.ResetStats();
  951. }
  952. void CollectTargets( CBaseCombatCharacter *pCaster, float flSpellRange, int nTargetTeam, int nMaxTarget, CUtlVector< CHandle< CBaseEntity > > &vecTargets )
  953. {
  954. vecTargets.RemoveAll();
  955. // collect everyone
  956. CUtlVector< CTFPlayer * > playerVector;
  957. CollectPlayers( &playerVector, nTargetTeam, COLLECT_ONLY_LIVING_PLAYERS );
  958. CUtlVector< CTFPlayer * > candidateTargets;
  959. for ( int i=0; i<playerVector.Count(); ++i )
  960. {
  961. CTFPlayer *pPlayer = playerVector[i];
  962. Vector toPlayer = pCaster->EyePosition() - pPlayer->WorldSpaceCenter();
  963. if ( toPlayer.IsLengthLessThan( flSpellRange ) && pCaster->IsLineOfSightClear( pPlayer ) )
  964. {
  965. candidateTargets.AddToTail( pPlayer );
  966. }
  967. }
  968. while ( candidateTargets.Count() != 0 && vecTargets.Count() != nMaxTarget )
  969. {
  970. int which = RandomInt( 0, candidateTargets.Count() - 1 );
  971. vecTargets.AddToTail( candidateTargets[ which ] );
  972. candidateTargets.FastRemove( which );
  973. }
  974. // find sentry in range
  975. for ( int i=0; i<IBaseObjectAutoList::AutoList().Count(); ++i )
  976. {
  977. CBaseObject *pObj = static_cast< CBaseObject* >( IBaseObjectAutoList::AutoList()[i] );
  978. if ( pObj->ObjectType() == OBJ_SENTRYGUN )
  979. {
  980. Vector toSentry = pCaster->EyePosition() - pObj->WorldSpaceCenter();
  981. if ( toSentry.IsLengthLessThan( flSpellRange ) && pCaster->IsLineOfSightClear( pObj ) )
  982. {
  983. vecTargets.AddToTail( pObj );
  984. }
  985. }
  986. }
  987. }
  988. void CastSpell( CBaseCombatCharacter* pCaster, const char* pszCastingAttachmentName, float flSpellRange, float flMinDamage, float flMaxDamage, CBaseEntity* pTarget )
  989. {
  990. float flSpellTime = 5.f;
  991. if ( pTarget->IsPlayer() )
  992. {
  993. CTFPlayer *pPlayer = ToTFPlayer( pTarget );
  994. pPlayer->m_Shared.SelfBurn( flSpellTime );
  995. pPlayer->ApplyAbsVelocityImpulse( 1000.f * Vector( 0, 0, 1 ) );
  996. Vector toPlayer = pCaster->EyePosition() - pPlayer->WorldSpaceCenter();
  997. float flDistSqr = toPlayer.LengthSqr();
  998. float flDmg = RemapValClamped( flDistSqr, 100.f, Square( 0.5f * flSpellRange ), flMaxDamage, flMinDamage );
  999. CTakeDamageInfo info( pCaster, pCaster, flDmg, DMG_BURN, TF_DMG_CUSTOM_MERASMUS_ZAP );
  1000. pPlayer->TakeDamage( info );
  1001. }
  1002. else if ( pTarget->IsBaseObject() )
  1003. {
  1004. CBaseObject *pObj = static_cast< CBaseObject* >( pTarget );
  1005. pObj->DetonateObject();
  1006. }
  1007. // Shoot a beam at them
  1008. CReliableBroadcastRecipientFilter filter;
  1009. Vector vStartPos;
  1010. QAngle qStartAngles;
  1011. pCaster->GetAttachment( pszCastingAttachmentName, vStartPos, qStartAngles );
  1012. Vector vEnd = pTarget->EyePosition();
  1013. te_tf_particle_effects_control_point_t controlPoint = { PATTACH_ABSORIGIN, vEnd };
  1014. TE_TFParticleEffectComplex( filter, 0.0f, "merasmus_zap", vStartPos, qStartAngles, NULL, &controlPoint, pCaster, PATTACH_CUSTOMORIGIN );
  1015. }
  1016. /*static*/ bool CMerasmus::Zap( CBaseCombatCharacter *pCaster, const char* pszCastingAttachmentName, float flSpellRange, float flMinDamage, float flMaxDamage, int nMaxTarget, int nTargetTeam /*= TEAM_ANY*/ )
  1017. {
  1018. CUtlVector< CHandle< CBaseEntity > > vecTargets;
  1019. CollectTargets( pCaster, flSpellRange, nTargetTeam, nMaxTarget, vecTargets );
  1020. if ( vecTargets.Count() == 0 )
  1021. return false;
  1022. for ( int i=0; i<vecTargets.Count(); ++i )
  1023. {
  1024. CBaseEntity *pTarget = vecTargets[i];
  1025. if ( pTarget )
  1026. {
  1027. CastSpell( pCaster, pszCastingAttachmentName, flSpellRange, flMinDamage, flMaxDamage, pTarget );
  1028. }
  1029. }
  1030. return true;
  1031. }
  1032. //---------------------------------------------------------------------------------------------
  1033. //---------------------------------------------------------------------------------------------
  1034. class CMerasmusBehavior : public Action< CMerasmus >
  1035. {
  1036. public:
  1037. virtual Action< CMerasmus > *InitialContainedAction( CMerasmus *me )
  1038. {
  1039. return new CMerasmusReveal;
  1040. }
  1041. virtual ActionResult< CMerasmus > OnStart( CMerasmus *me, Action< CMerasmus > *priorAction )
  1042. {
  1043. return Continue();
  1044. }
  1045. virtual ActionResult< CMerasmus > Update( CMerasmus *me, float interval )
  1046. {
  1047. if ( !me->IsAlive() )
  1048. {
  1049. if ( !me->WasSpawnedByCheats() )
  1050. {
  1051. // award achievement to everyone who injured me within the last few seconds
  1052. const float deathTime = 5.0f;
  1053. const CUtlVector< CMerasmus::AttackerInfo > &attackerVector = me->GetAttackerVector();
  1054. for( int i=0; i<attackerVector.Count(); ++i )
  1055. {
  1056. if ( attackerVector[i].m_attacker != NULL &&
  1057. gpGlobals->curtime - attackerVector[i].m_timestamp < deathTime )
  1058. {
  1059. CReliableBroadcastRecipientFilter filter;
  1060. UTIL_SayText2Filter( filter, attackerVector[i].m_attacker, false, "#TF_Halloween_Merasmus_Killers", attackerVector[i].m_attacker->GetPlayerName() );
  1061. if ( TFGameRules() && TFGameRules()->IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_LAKESIDE ) )
  1062. {
  1063. attackerVector[i].m_attacker->AwardAchievement( ACHIEVEMENT_TF_HALLOWEEN_MERASMUS_KILL );
  1064. }
  1065. }
  1066. }
  1067. // Award hat levels based on Merasmus' level when he dies, but only to people who were
  1068. // around when Merasmus spawned.
  1069. const CUtlVector< CHandle<CTFPlayer> >& vecStartingAttackers = me->GetStartingAttackers();
  1070. if ( GCClientSystem() )
  1071. {
  1072. // GC message
  1073. // Notify the GC that this occurred to possibly level up your hat if you have one
  1074. GCSDK::CProtoBufMsg<CMsgUpdateHalloweenMerasmusLootLevel> msg( k_EMsgGC_Halloween_UpdateMerasmusLootLevel );
  1075. msg.Body().set_merasmus_level( me->GetLevel() );
  1076. FOR_EACH_VEC( vecStartingAttackers, i )
  1077. {
  1078. CTFPlayer* pPlayer = vecStartingAttackers[i];
  1079. if ( pPlayer )
  1080. {
  1081. CSteamID steamID;
  1082. if ( pPlayer->GetSteamID( &steamID ) && steamID.IsValid() && steamID.BIndividualAccount() )
  1083. {
  1084. CMsgUpdateHalloweenMerasmusLootLevel_Player *pMsgPlayer = msg.Body().add_players();
  1085. pMsgPlayer->set_steam_id( steamID.ConvertToUint64() );
  1086. }
  1087. }
  1088. }
  1089. GCClientSystem()->BSendMessage( msg );
  1090. }
  1091. }
  1092. // nobody is IT any longer
  1093. TFGameRules()->SetIT( NULL );
  1094. return ChangeTo( new CMerasmusDying, "I am dead!" );
  1095. }
  1096. else
  1097. {
  1098. me->LeaveWarning();
  1099. if ( me->ShouldLeave() && !me->IsStunned() && !me->IsFlying() )
  1100. {
  1101. return ChangeTo( new CMerasmusEscape, "Escaping..." );
  1102. }
  1103. }
  1104. return Continue();
  1105. }
  1106. virtual EventDesiredResult< CMerasmus > OnInjured( CMerasmus *me, const CTakeDamageInfo &info )
  1107. {
  1108. if ( me->ShouldDisguise() && me->IsRevealed() && !me->IsStunned() && !me->IsFlying() )
  1109. {
  1110. return TrySuspendFor( new CMerasmusDisguise, RESULT_IMPORTANT, "Disguise" );
  1111. }
  1112. return TryContinue();
  1113. }
  1114. virtual const char *GetName( void ) const { return "Merasmus Behavior"; } // return name of this action
  1115. private:
  1116. };
  1117. //---------------------------------------------------------------------------------------------
  1118. //---------------------------------------------------------------------------------------------
  1119. CMerasmusIntention::CMerasmusIntention( CMerasmus *me ) : IIntention( me )
  1120. {
  1121. m_behavior = new Behavior< CMerasmus >( new CMerasmusBehavior );
  1122. }
  1123. CMerasmusIntention::~CMerasmusIntention()
  1124. {
  1125. delete m_behavior;
  1126. }
  1127. void CMerasmusIntention::Reset( void )
  1128. {
  1129. delete m_behavior;
  1130. m_behavior = new Behavior< CMerasmus >( new CMerasmusBehavior );
  1131. }
  1132. void CMerasmusIntention::Update( void )
  1133. {
  1134. m_behavior->Update( static_cast< CMerasmus * >( GetBot() ), GetUpdateInterval() );
  1135. }
  1136. // is this a place we can be?
  1137. QueryResultType CMerasmusIntention::IsPositionAllowed( const INextBot *meBot, const Vector &pos ) const
  1138. {
  1139. return ANSWER_YES;
  1140. }
  1141. //---------------------------------------------------------------------------------------------
  1142. //---------------------------------------------------------------------------------------------
  1143. void CMerasmusLocomotion::Update( void )
  1144. {
  1145. CMerasmus *me = (CMerasmus *)GetBot()->GetEntity();
  1146. if ( me->IsFlying() )
  1147. {
  1148. // don't update this locomotor since the flying locomotor is active
  1149. return;
  1150. }
  1151. NextBotGroundLocomotion::Update();
  1152. }
  1153. float CMerasmusLocomotion::GetRunSpeed( void ) const
  1154. {
  1155. return tf_merasmus_speed.GetFloat();
  1156. }
  1157. //---------------------------------------------------------------------------------------------
  1158. // if delta Z is greater than this, we have to jump to get up
  1159. float CMerasmusLocomotion::GetStepHeight( void ) const
  1160. {
  1161. return 18.0f;
  1162. }
  1163. //---------------------------------------------------------------------------------------------
  1164. // return maximum height of a jump
  1165. float CMerasmusLocomotion::GetMaxJumpHeight( void ) const
  1166. {
  1167. return 18.0f;
  1168. }
  1169. //---------------------------------------------------------------------------------------------
  1170. // Return max rate of yaw rotation
  1171. float CMerasmusLocomotion::GetMaxYawRate( void ) const
  1172. {
  1173. return 200.0f;
  1174. }
  1175. //---------------------------------------------------------------------------------------------
  1176. bool CMerasmusLocomotion::ShouldCollideWith( const CBaseEntity *object ) const
  1177. {
  1178. if ( !object )
  1179. return false;
  1180. // Don't collide with players
  1181. return object->IsPlayer() ? false : true;
  1182. }
  1183. //---------------------------------------------------------------------------------------------
  1184. //---------------------------------------------------------------------------------------------
  1185. #define MERASMUS_ACCELERATION 250.0f //500.0f
  1186. CMerasmusFlyingLocomotion::CMerasmusFlyingLocomotion( INextBot *bot ) : ILocomotion( bot )
  1187. {
  1188. Reset();
  1189. }
  1190. //---------------------------------------------------------------------------------------------
  1191. CMerasmusFlyingLocomotion::~CMerasmusFlyingLocomotion()
  1192. {
  1193. }
  1194. //---------------------------------------------------------------------------------------------
  1195. // (EXTEND) reset to initial state
  1196. void CMerasmusFlyingLocomotion::Reset( void )
  1197. {
  1198. m_velocity = vec3_origin;
  1199. m_acceleration = vec3_origin;
  1200. m_currentSpeed = 0.0f;
  1201. m_forward = vec3_origin;
  1202. m_desiredAltitude = 50.0f;
  1203. }
  1204. //---------------------------------------------------------------------------------------------
  1205. void CMerasmusFlyingLocomotion::MaintainAltitude( void )
  1206. {
  1207. CBaseCombatCharacter *me = GetBot()->GetEntity();
  1208. float groundZ;
  1209. TheNavMesh->GetSimpleGroundHeight( me->GetAbsOrigin(), &groundZ );
  1210. float currentAltitude = me->GetAbsOrigin().z - groundZ;
  1211. float error = m_desiredAltitude - currentAltitude;
  1212. float accelZ = clamp( error, -MERASMUS_ACCELERATION, MERASMUS_ACCELERATION );
  1213. m_acceleration.z += accelZ;
  1214. }
  1215. //---------------------------------------------------------------------------------------------
  1216. // (EXTEND) update internal state
  1217. void CMerasmusFlyingLocomotion::Update( void )
  1218. {
  1219. CMerasmus *me = (CMerasmus *)GetBot()->GetEntity();
  1220. const float deltaT = GetUpdateInterval();
  1221. if ( !me->IsFlying() )
  1222. {
  1223. // not flying - let the other locomotor run
  1224. return;
  1225. }
  1226. Vector pos = me->GetAbsOrigin();
  1227. // always maintain altitude, even if not trying to move (ie: no Approach call)
  1228. MaintainAltitude();
  1229. m_forward = m_velocity;
  1230. m_currentSpeed = m_forward.NormalizeInPlace();
  1231. Vector damping( 1.0f, 1.0f, 1.0f );
  1232. Vector totalAccel = m_acceleration - m_velocity * damping;
  1233. m_velocity += totalAccel * deltaT;
  1234. me->SetAbsVelocity( m_velocity );
  1235. pos += m_velocity * deltaT;
  1236. // Merasmus doesn't collide with players and floats between valid nav areas
  1237. // so skip the collision checking
  1238. GetBot()->GetEntity()->SetAbsOrigin( pos );
  1239. m_acceleration = vec3_origin;
  1240. }
  1241. //---------------------------------------------------------------------------------------------
  1242. // (EXTEND) move directly towards the given position
  1243. void CMerasmusFlyingLocomotion::Approach( const Vector &goalPos, float goalWeight )
  1244. {
  1245. Vector flyGoal = goalPos;
  1246. flyGoal.z += m_desiredAltitude;
  1247. Vector toGoal = flyGoal - GetBot()->GetEntity()->GetAbsOrigin();
  1248. // altitude is handled in Update()
  1249. toGoal.z = 0.0f;
  1250. toGoal.NormalizeInPlace();
  1251. m_acceleration += MERASMUS_ACCELERATION * toGoal;
  1252. }
  1253. //---------------------------------------------------------------------------------------------
  1254. float CMerasmusFlyingLocomotion::GetDesiredSpeed( void ) const
  1255. {
  1256. return tf_merasmus_speed.GetFloat();
  1257. }
  1258. //---------------------------------------------------------------------------------------------
  1259. void CMerasmusFlyingLocomotion::SetDesiredAltitude( float height )
  1260. {
  1261. m_desiredAltitude = height;
  1262. }
  1263. //---------------------------------------------------------------------------------------------
  1264. float CMerasmusFlyingLocomotion::GetDesiredAltitude( void ) const
  1265. {
  1266. return m_desiredAltitude;
  1267. }
  1268. //---------------------------------------------------------------------------------------------
  1269. bool CMerasmusFlyingLocomotion::ShouldCollideWith( const CBaseEntity *object ) const
  1270. {
  1271. if ( !object )
  1272. return false;
  1273. // Don't collide with players
  1274. return object->IsPlayer() ? false : true;
  1275. }
  1276. //---------------------------------------------------------------------------------------------
  1277. void CMerasmusFlyingLocomotion::FaceTowards( const Vector &target )
  1278. {
  1279. CMerasmus *me = (CMerasmus *)GetBot()->GetEntity();
  1280. const float deltaT = GetUpdateInterval();
  1281. QAngle angles = me->GetLocalAngles();
  1282. float desiredYaw = UTIL_VecToYaw( target - GetFeet() );
  1283. float angleDiff = UTIL_AngleDiff( desiredYaw, angles.y );
  1284. const float maxYawRate = 100.0f;
  1285. float deltaYaw = maxYawRate * deltaT;
  1286. if ( angleDiff < -deltaYaw )
  1287. {
  1288. angles.y -= deltaYaw;
  1289. }
  1290. else if ( angleDiff > deltaYaw )
  1291. {
  1292. angles.y += deltaYaw;
  1293. }
  1294. else
  1295. {
  1296. angles.y += angleDiff;
  1297. }
  1298. me->SetLocalAngles( angles );
  1299. }