summaryrefslogtreecommitdiff
path: root/game/server/tf/halloween/merasmus
diff options
context:
space:
mode:
Diffstat (limited to 'game/server/tf/halloween/merasmus')
-rw-r--r--game/server/tf/halloween/merasmus/merasmus.cpp1609
-rw-r--r--game/server/tf/halloween/merasmus/merasmus.h412
-rw-r--r--game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_aoe_attack.cpp248
-rw-r--r--game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_aoe_attack.h51
-rw-r--r--game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_attack.cpp386
-rw-r--r--game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_attack.h63
-rw-r--r--game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_disguise.cpp226
-rw-r--r--game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_disguise.h33
-rw-r--r--game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_dying.cpp60
-rw-r--r--game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_dying.h20
-rw-r--r--game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_reveal.cpp38
-rw-r--r--game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_reveal.h20
-rw-r--r--game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_staff_attack.cpp117
-rw-r--r--game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_staff_attack.h28
-rw-r--r--game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_stunned.cpp91
-rw-r--r--game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_stunned.h29
-rw-r--r--game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_teleport.cpp189
-rw-r--r--game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_teleport.h49
-rw-r--r--game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_throwing_grenade.cpp159
-rw-r--r--game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_throwing_grenade.h27
-rw-r--r--game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_zap.cpp73
-rw-r--r--game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_zap.h32
-rw-r--r--game/server/tf/halloween/merasmus/merasmus_body.cpp118
-rw-r--r--game/server/tf/halloween/merasmus/merasmus_body.h51
-rw-r--r--game/server/tf/halloween/merasmus/merasmus_dancer.cpp172
-rw-r--r--game/server/tf/halloween/merasmus/merasmus_dancer.h49
-rw-r--r--game/server/tf/halloween/merasmus/merasmus_trick_or_treat_prop.cpp178
-rw-r--r--game/server/tf/halloween/merasmus/merasmus_trick_or_treat_prop.h30
28 files changed, 4558 insertions, 0 deletions
diff --git a/game/server/tf/halloween/merasmus/merasmus.cpp b/game/server/tf/halloween/merasmus/merasmus.cpp
new file mode 100644
index 0000000..93f42d2
--- /dev/null
+++ b/game/server/tf/halloween/merasmus/merasmus.cpp
@@ -0,0 +1,1609 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//
+//
+//=============================================================================
+#include "cbase.h"
+#include "tf_player.h"
+#include "tf_gamerules.h"
+#include "tf_team.h"
+#include "nav_mesh/tf_nav_area.h"
+#include "NextBot/Path/NextBotChasePath.h"
+#include "econ_wearable.h"
+#include "team_control_point_master.h"
+#include "particle_parse.h"
+#include "tf_weaponbase_merasmus_grenade.h"
+#include "merasmus_dancer.h"
+#include "tf_wheel_of_doom.h"
+#include "soundenvelope.h"
+#include "util.h"
+#include "tf_obj_sentrygun.h"
+#include "logicrelay.h"
+#include "steamworks_gamestats.h"
+
+#include "tf/halloween/ghost/ghost.h"
+
+#include "player_vs_environment/monster_resource.h"
+
+#include "merasmus_trick_or_treat_prop.h"
+#include "merasmus.h"
+#include "merasmus_behavior/merasmus_disguise.h"
+#include "merasmus_behavior/merasmus_dying.h"
+#include "merasmus_behavior/merasmus_reveal.h"
+#include "merasmus_behavior/merasmus_teleport.h"
+
+#include "rtime.h"
+#include "gc_clientsystem.h"
+#include "tf_gcmessages.h"
+
+#include "tf_fx.h"
+
+ConVar tf_merasmus_health_base( "tf_merasmus_health_base", "33750", FCVAR_CHEAT );
+ConVar tf_merasmus_health_per_player( "tf_merasmus_health_per_player", "2500", FCVAR_CHEAT );
+ConVar tf_merasmus_min_player_count( "tf_merasmus_min_player_count", "10", FCVAR_CHEAT );
+
+ConVar tf_merasmus_lifetime( "tf_merasmus_lifetime", "120", FCVAR_CHEAT );
+
+ConVar tf_merasmus_speed( "tf_merasmus_speed", "600", FCVAR_CHEAT );
+ConVar tf_merasmus_speed_recovery_rate( "tf_merasmus_speed_recovery_rate", "100", FCVAR_CHEAT, "Movement units/second" );
+ConVar tf_merasmus_chase_duration( "tf_merasmus_chase_duration", "7", FCVAR_CHEAT );
+ConVar tf_merasmus_chase_range( "tf_merasmus_chase_range", "2000", FCVAR_CHEAT );
+
+ConVar tf_merasmus_should_disguise_threshold( "tf_merasmus_should_disguise_threshold", "0.45f", FCVAR_CHEAT );
+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");
+
+ConVar tf_merasmus_attack_range( "tf_merasmus_attack_range", "200", FCVAR_CHEAT );
+
+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" );
+
+ConVar tf_merasmus_bomb_head_duration( "tf_merasmus_bomb_head_duration", "15.f", FCVAR_CHEAT );
+ConVar tf_merasmus_bomb_head_per_team( "tf_merasmus_bomb_head_per_team", "1", FCVAR_CHEAT );
+
+ConVar tf_merasmus_stun_duration( "tf_merasmus_stun_duration", "2.f", FCVAR_CHEAT );
+
+extern ConVar tf_merasmus_spawn_interval;
+extern ConVar tf_merasmus_spawn_interval_variation;
+
+#define MERASMUS_MODEL_NAME "models/bots/merasmus/merasmus.mdl"
+#define MERASMUS_BOMB_MODEL "models/props_lakeside_event/bomb_temp.mdl"
+
+static const char* s_pszDisguiseProps[] =
+{
+ // "models/props_hydro/dumptruck.mdl", // 265
+ "models/props_halloween/pumpkin_02.mdl",
+ "models/props_halloween/pumpkin_03.mdl",
+ "models/egypt/palm_tree/palm_tree.mdl",
+ "models/props_spytech/control_room_console01.mdl",
+ "models/props_spytech/work_table001.mdl",
+ // "models/egypt/tent/tent.mdl", // 248
+ "models/props_coalmines/boulder1.mdl",
+ "models/props_coalmines/boulder2.mdl",
+ "models/props_farm/concrete_block001.mdl", // 152
+ // "models/props_farm/tractor_tire001.mdl", // requires offset
+ "models/props_farm/welding_machine01.mdl",
+ "models/props_medieval/medieval_resupply.mdl",
+ "models/props_medieval/target/target.mdl",
+ "models/props_swamp/picnic_table.mdl",
+ "models/props_manor/baby_grand_01.mdl", // 154
+ "models/props_manor/bookcase_132_02.mdl",
+ "models/props_manor/chair_01.mdl",
+ "models/props_manor/couch_01.mdl",
+ "models/props_manor/grandfather_clock_01.mdl",
+ // "models/props_manor/tractor_01.mdl", // 227
+ // "models/props_gameplay/haybale.mdl", // requires offset
+ "models/props_viaduct_event/coffin_simple_closed.mdl",
+ // "models/props_farm/wooden_barrel.mdl", // requires offset
+ "models/props_2fort/miningcrate001.mdl",
+ "models/props_gameplay/resupply_locker.mdl",
+ "models/props_2fort/oildrum.mdl",
+ // "models/props_farm/wood_pile.mdl", // requires offset
+ "models/props_lakeside/wood_crate_01.mdl",
+ // "models/props_farm/pallet001.mdl", // requires offset
+ "models/props_well/hand_truck01.mdl",
+ "models/props_vehicles/mining_car_metal.mdl",
+ "models/props_2fort/tire002.mdl",
+ "models/props_well/computer_cart01.mdl",
+ "models/egypt/palm_tree/palm_tree.mdl"
+};
+
+
+//-----------------------------------------------------------------------------------------------------
+// The Horseless Headless Horseman
+//-----------------------------------------------------------------------------------------------------
+LINK_ENTITY_TO_CLASS( merasmus, CMerasmus );
+
+IMPLEMENT_SERVERCLASS_ST( CMerasmus, DT_Merasmus )
+ SendPropBool( SENDINFO( m_bRevealed ) ),
+ SendPropBool( SENDINFO( m_bIsDoingAOEAttack ) ),
+ SendPropBool( SENDINFO( m_bStunned ) ),
+END_SEND_TABLE()
+
+
+int CMerasmus::m_level = 1;
+
+IMPLEMENT_AUTO_LIST( IMerasmusAutoList );
+
+//-----------------------------------------------------------------------------------------------------
+CMerasmus::CMerasmus()
+{
+ m_intention = new CMerasmusIntention( this );
+ m_locomotor = new CMerasmusLocomotion( this );
+ m_flyingLocomotor = new CMerasmusFlyingLocomotion( this );
+ m_body = new CMerasmusBody( this );
+ m_bRevealed = false;
+ m_bIsDoingAOEAttack = false;
+
+ m_wheel = NULL;
+
+ m_stunTimer.Invalidate();
+ m_bStunned = false;
+
+ m_nBombHitCount = 0;
+
+ m_hMerasmusRevealer = NULL;
+
+ m_isFlying = false;
+ m_isHiding=false;
+
+ m_hHealthBar = g_pMonsterResource;
+ ListenForGameEvent( "player_death" );
+}
+
+
+//-----------------------------------------------------------------------------------------------------
+CMerasmus::~CMerasmus()
+{
+ if ( m_intention )
+ delete m_intention;
+
+ if ( m_locomotor )
+ delete m_locomotor;
+
+ if ( m_flyingLocomotor )
+ delete m_flyingLocomotor;
+
+ if ( m_body )
+ delete m_body;
+
+ // Make sure the health meter goes away
+ if( m_hHealthBar.Get() )
+ {
+ m_hHealthBar->HideBossHealthMeter();
+ }
+
+ IGameEvent *event = gameeventmanager->CreateEvent( "recalculate_truce" );
+ if ( event )
+ {
+ gameeventmanager->FireEvent( event, true );
+ }
+}
+
+//-----------------------------------------------------------------------------------------------------
+void CMerasmus::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 );
+
+ PrecacheMerasmus();
+
+ CBaseEntity::SetAllowPrecache( bAllowPrecache );
+}
+
+
+void CMerasmus::PrecacheMerasmus()
+{
+ int model = PrecacheModel( MERASMUS_MODEL_NAME );
+ PrecacheGibsForModel( model );
+
+ // precache disguise prop list
+ for ( int i=0; i<ARRAYSIZE( s_pszDisguiseProps ); ++i )
+ {
+ PrecacheModel( s_pszDisguiseProps[i] );
+ }
+
+ PrecacheModel( MERASMUS_BOMB_MODEL );
+ PrecacheModel( "models/props_lakeside_event/bomb_temp_hat.mdl" ); // bomb head on player
+ PrecacheModel( "models/props_halloween/bombonomicon.mdl" ); // bombonomicon hint to player
+
+ // Boss VOs
+ PrecacheScriptSound( "Halloween.MerasmusAppears" );
+ PrecacheScriptSound( "Halloween.MerasmusBanish" );
+ //PrecacheScriptSound( "Halloween.MerasmusCastBleedingSpell" );
+ PrecacheScriptSound( "Halloween.MerasmusCastFireSpell" );
+ PrecacheScriptSound( "Halloween.MerasmusCastJarateSpell" );
+ PrecacheScriptSound( "Halloween.MerasmusCastJarateSpellRare" );
+ PrecacheScriptSound( "Halloween.MerasmusLaunchSpell" );
+ PrecacheScriptSound( "Halloween.MerasmusControlPoint" );
+ PrecacheScriptSound( "Halloween.MerasmusDepart" );
+ PrecacheScriptSound( "Halloween.MerasmusDepartRare" );
+ PrecacheScriptSound( "Halloween.MerasmusDiscovered" );
+ PrecacheScriptSound( "Halloween.MerasmusGrenadeThrow" );
+ PrecacheScriptSound( "Halloween.MerasmusHidden" );
+ PrecacheScriptSound( "Halloween.MerasmusHiddenRare" );
+ PrecacheScriptSound( "Halloween.MerasmusHitByBomb" );
+ PrecacheScriptSound( "Halloween.MerasmusHitByBombRare" );
+ PrecacheScriptSound( "Halloween.MerasmusInitiateHiding" );
+ PrecacheScriptSound( "Halloween.MerasmusStaffAttack" );
+ PrecacheScriptSound( "Halloween.MerasmusStaffAttackRare" );
+ PrecacheScriptSound( "Halloween.MerasmusTauntFakeProp" );
+
+ //PrecacheScriptSound( "Halloween.MerasmusTeleport" );
+
+ // Boss event sound effects
+ PrecacheScriptSound( "Halloween.MerasmusBossSpawn" );
+ PrecacheScriptSound( "Halloween.Merasmus_Death" );
+ PrecacheScriptSound( "Halloween.EyeballBossEscapeSoon" );
+ PrecacheScriptSound( "Halloween.EyeballBossEscapeImminent" );
+ PrecacheScriptSound( "Halloween.EyeballBossEscaped" );
+ PrecacheScriptSound( "Halloween.Merasmus_Float" );
+ PrecacheScriptSound( "Halloween.Merasmus_Stun" );
+ PrecacheScriptSound( "Halloween.Merasmus_Spell" );
+ PrecacheScriptSound( "Halloween.Merasmus_Hiding_Explode" );
+
+ PrecacheParticleSystem( "merasmus_spawn" ); // spawn
+ PrecacheParticleSystem( "merasmus_tp" ); // puff effect
+ PrecacheParticleSystem( "merasmus_blood" ); // when he takes damage
+ PrecacheParticleSystem( "merasmus_blood_bits" ); // when he takes damage while stunned
+ PrecacheParticleSystem( "merasmus_ambient_body" ); // glow around the body
+ PrecacheParticleSystem( "merasmus_shoot" ); // when he casts spell
+ PrecacheParticleSystem( "merasmus_book_attack" ); // big attack
+ PrecacheParticleSystem( "merasmus_object_spawn" ); // object spawn
+ PrecacheParticleSystem( "merasmus_zap" ); // zap!
+ PrecacheParticleSystem( "merasmus_dazed" ); // stunned
+ PrecacheParticleSystem( "merasmus_dazed_explosion" ); // bomb head explode
+
+ // TEMP
+ PrecacheScriptSound( "Halloween.HeadlessBossAxeHitFlesh" );
+}
+
+
+//-----------------------------------------------------------------------------------------------------
+void CMerasmus::Spawn( void )
+{
+ Precache();
+
+ SetModel( MERASMUS_MODEL_NAME );
+
+ BaseClass::Spawn();
+
+ PlayHighPrioritySound("Halloween.MerasmusAppears");
+
+ m_isHiding=false;
+
+ // scale the boss' health with the player count
+ int totalPlayers = GetGlobalTFTeam( TF_TEAM_BLUE )->GetNumPlayers() + GetGlobalTFTeam( TF_TEAM_RED )->GetNumPlayers();
+
+ int health = tf_merasmus_health_base.GetInt();
+ if ( totalPlayers > tf_merasmus_min_player_count.GetInt() )
+ {
+ health += ( totalPlayers - tf_merasmus_min_player_count.GetInt() ) * tf_merasmus_health_per_player.GetInt();
+ }
+
+ CBaseEntity *pWheel = gEntList.FindEntityByName( NULL, "wheel_of_fortress" );
+ if ( pWheel )
+ {
+ m_wheel = assert_cast< CWheelOfDoom* >( pWheel );
+ }
+
+ SetHealth( health );
+ SetMaxHealth( health );
+
+ m_homePos = GetAbsOrigin();
+
+ m_damagePoseParameter = -1;
+
+ SetBloodColor( DONT_BLEED );
+
+ if ( m_pIdleSound )
+ {
+ CSoundEnvelopeController::GetController().SoundDestroy( m_pIdleSound );
+ m_pIdleSound = NULL;
+ }
+
+ // Collect all of the players that are alive at this moment. These are the players who will get
+ // their hat leveled up when Merasmus dies. We only want to give credit to these players to discourage
+ // the strategy of spawning Merasmus with 10 people so his health is scaled for 10 people,
+ // then have 22 other people connect and destroy him.
+ CUtlVector< CTFPlayer * > playerVector;
+ CollectPlayers( &playerVector );
+ FOR_EACH_VEC( playerVector, i )
+ {
+ m_startingAttackersVector.AddToTail( playerVector[i] );
+ }
+
+ CPVSFilter filter( GetAbsOrigin() );
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+ m_pIdleSound = controller.SoundCreate( filter, entindex(), "Halloween.Merasmus_Float" );
+ controller.Play( m_pIdleSound, 1.0, 100 );
+
+ m_solidType = GetSolid();
+ m_solidFlags = GetSolidFlags();
+
+ const float flLifeTime = tf_merasmus_lifetime.GetFloat();
+ m_lifeTimer.Start( flLifeTime );
+ m_flLastWarnTime = 0;
+
+ IGameEvent *event = gameeventmanager->CreateEvent( "merasmus_summoned" );
+ if ( event )
+ {
+ event->SetInt( "level", GetLevel() );
+ gameeventmanager->FireEvent( event );
+ }
+ TriggerLogicRelay( "boss_enter_relay", true );
+
+ DispatchParticleEffect( "merasmus_spawn", GetAbsOrigin(), GetAbsAngles() );
+
+ m_bossStats.ResetStats();
+
+ event = gameeventmanager->CreateEvent( "recalculate_truce" );
+ if ( event )
+ {
+ gameeventmanager->FireEvent( event, true );
+ }
+}
+
+
+//---------------------------------------------------------------------------------------------
+void RemoveAllBombHeadFromPlayers()
+{
+ CUtlVector< CTFPlayer * > playerVector;
+ CollectPlayers( &playerVector, TF_TEAM_RED, COLLECT_ONLY_LIVING_PLAYERS );
+ CollectPlayers( &playerVector, TF_TEAM_BLUE, COLLECT_ONLY_LIVING_PLAYERS, APPEND_PLAYERS );
+
+ for ( int i=0; i<playerVector.Count(); ++i )
+ {
+ if ( playerVector[i]->m_Shared.InCond( TF_COND_HALLOWEEN_BOMB_HEAD ) )
+ {
+ playerVector[i]->m_Shared.RemoveCond( TF_COND_HALLOWEEN_BOMB_HEAD );
+ }
+ }
+}
+
+
+void CMerasmus::UpdateOnRemove()
+{
+ Assert( TFGameRules() );
+ if ( m_hHealthBar )
+ {
+ m_hHealthBar->HideBossHealthMeter();
+ }
+
+ if ( m_pIdleSound )
+ {
+ CSoundEnvelopeController::GetController().SoundDestroy( m_pIdleSound );
+ m_pIdleSound = NULL;
+ }
+
+ // the boss is NULL, remove bomb head condition won't give players power play
+ RemoveAllBombHeadFromPlayers();
+
+ RemoveAllFakeProps();
+
+ BaseClass::UpdateOnRemove();
+
+ // Report Stats
+ SW_ReportMerasmusStats();
+}
+
+
+//---------------------------------------------------------------------------------------------
+float MerasmusModifyDamage( const CTakeDamageInfo &info )
+{
+ CTFWeaponBase *pWeapon = dynamic_cast< CTFWeaponBase * >( info.GetWeapon() );
+ CObjectSentrygun *sentry = dynamic_cast< CObjectSentrygun * >( info.GetInflictor() );
+ CTFProjectile_SentryRocket *sentryRocket = dynamic_cast< CTFProjectile_SentryRocket * >( info.GetInflictor() );
+
+ if ( sentry || sentryRocket )
+ {
+ return info.GetDamage() * 0.5f;
+ }
+ else if ( pWeapon )
+ {
+ switch( pWeapon->GetWeaponID() )
+ {
+ case TF_WEAPON_MINIGUN:
+ return info.GetDamage() * 0.5f;
+
+ case TF_WEAPON_SODA_POPPER:
+ return info.GetDamage() * 1.5f;
+
+ case TF_WEAPON_HANDGUN_SCOUT_PRIMARY:
+ return info.GetDamage() * 1.75;
+
+ case TF_WEAPON_SCATTERGUN:
+ case TF_WEAPON_REVOLVER:
+ return info.GetDamage() * 2.f;
+
+ case TF_WEAPON_SNIPERRIFLE:
+ case TF_WEAPON_SNIPERRIFLE_DECAP:
+ case TF_WEAPON_SNIPERRIFLE_CLASSIC:
+ case TF_WEAPON_COMPOUND_BOW:
+ case TF_WEAPON_KNIFE:
+ case TF_WEAPON_PEP_BRAWLER_BLASTER:
+ return info.GetDamage() * 3.f;
+ }
+ }
+
+ // unmodified
+ return info.GetDamage();
+}
+
+
+//-----------------------------------------------------------------------------------------------------
+int CMerasmus::OnTakeDamage_Alive( const CTakeDamageInfo &info )
+{
+ if ( !IsRevealed() )
+ {
+ return 0;
+ }
+
+ if ( IsSelf( info.GetAttacker() ) )
+ {
+ // don't injure myself
+ return 0;
+ }
+
+ CTakeDamageInfo modifiedInfo = info;
+
+ if ( RandomInt( 0, 30 ) == 0 )
+ {
+ //EmitSound( "Halloween.MerasmusHurt" ); << add new sound entry
+ }
+
+ if ( IsStunned() )
+ {
+ DispatchParticleEffect( "merasmus_blood", modifiedInfo.GetDamagePosition(), GetAbsAngles() );
+ }
+ else
+ {
+ DispatchParticleEffect( "merasmus_blood_bits", modifiedInfo.GetDamagePosition(), GetAbsAngles() );
+ }
+
+ modifiedInfo.SetDamage( MerasmusModifyDamage( modifiedInfo ) );
+ if ( m_bIsDoingAOEAttack || m_bStunned )
+ {
+ modifiedInfo.AddDamageType( DMG_CRITICAL );
+ }
+
+ int result = BaseClass::OnTakeDamage_Alive( modifiedInfo );
+
+ // update boss health meter
+ float healthPercentage = (float)GetHealth() / (float)GetMaxHealth();
+
+ if ( m_hHealthBar )
+ {
+ if ( healthPercentage <= 0.0f )
+ {
+ m_hHealthBar->HideBossHealthMeter();
+ }
+ else
+ {
+ m_hHealthBar->SetBossHealthPercentage( healthPercentage );
+ }
+ }
+
+ // Stats Tracking
+ CTFPlayer *pAttacker = ToTFPlayer( info.GetAttacker() );
+ if ( pAttacker )
+ {
+ int iClass = pAttacker->GetPlayerClass()->GetClassIndex();
+ if ( iClass > TF_CLASS_UNDEFINED && iClass < TF_LAST_NORMAL_CLASS )
+ {
+ m_bossStats.m_arrClassDamage[ iClass ] += info.GetDamage();
+ }
+ }
+ return result;
+}
+
+//---------------------------------------------------------------------------------------------
+void CMerasmus::FireGameEvent( IGameEvent *event)
+{
+ if ( !V_strcmp( event->GetName(), "player_death" ) )
+ {
+ // Collect Data
+ int nDmgType = event->GetInt( "customkill", -1 );
+ if ( nDmgType == TF_DMG_CUSTOM_MERASMUS_DECAPITATION || nDmgType == TF_DMG_CUSTOM_MERASMUS_ZAP )
+ {
+ m_bossStats.m_nStaffKills++;
+ }
+ else if ( nDmgType == TF_DMG_CUSTOM_MERASMUS_GRENADE || nDmgType == TF_DMG_CUSTOM_MERASMUS_PLAYER_BOMB )
+ {
+ m_bossStats.m_nBombKills++;
+ }
+ else
+ {
+ // Treat as a PvPKill
+ m_bossStats.m_nPvpKills++;
+ }
+ }
+}
+
+//---------------------------------------------------------------------------------------------
+void CMerasmus::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() ) );
+ }
+}
+
+
+Vector CMerasmus::GetCastPosition() const
+{
+ Vector vForward, vRight, vUp;
+ AngleVectors( GetAbsAngles(), &vForward, &vRight, &vUp );
+ return WorldSpaceCenter() + vForward * 60.f + 30.f * vRight + 60.f * vUp;
+}
+
+
+void RemoveAllGrenades( CMerasmus *me )
+{
+ const int maxCollectedEntities = 1024;
+ CBaseEntity *pObjects[ maxCollectedEntities ];
+ int count = UTIL_EntitiesInSphere( pObjects, maxCollectedEntities, me->GetAbsOrigin(), 400, FL_GRENADE );
+
+ for( int i = 0; i < count; ++i )
+ {
+ if ( pObjects[i] == NULL )
+ continue;
+
+ if ( pObjects[i]->IsPlayer() )
+ continue;
+
+ // Remove the enemy pipe
+ pObjects[i]->SetThink( &CBaseEntity::SUB_Remove );
+ pObjects[i]->SetNextThink( gpGlobals->curtime );
+ pObjects[i]->SetTouch( NULL );
+ pObjects[i]->AddEffects( EF_NODRAW );
+ }
+}
+
+void CMerasmus::OnRevealed(bool bPlaySound)
+{
+ RecordDisguiseTime();
+ m_bRevealed = true;
+ m_isHiding = false;
+ m_nRevealedHealth = GetHealth();
+
+ if (bPlaySound)
+ {
+ PlayHighPrioritySound( "Halloween.MerasmusDiscovered" );
+ }
+
+ RemoveEffects( EF_NOINTERP | EF_NODRAW );
+
+ DispatchParticleEffect( "merasmus_spawn", WorldSpaceCenter(), GetAbsAngles() );
+
+ // don't collide with anything, we do our own collision detection in our think method
+ SetSolid( m_solidType );
+ SetSolidFlags( m_solidFlags );
+
+ RemoveAllFakeProps();
+ TFGameRules()->PushAllPlayersAway( GetAbsOrigin(), 400.f, 500.f, TF_TEAM_RED );
+ TFGameRules()->PushAllPlayersAway( GetAbsOrigin(), 400.f, 500.f, TF_TEAM_BLUE );
+ RemoveAllGrenades( this );
+
+ // give player who found merasmus buff
+ if ( m_hMerasmusRevealer )
+ {
+ // condition was removed by blowing up merasmus, you get buff award
+ const float buffDuration = 10.0f;
+ m_hMerasmusRevealer->m_Shared.AddCond( TF_COND_CRITBOOSTED_PUMPKIN, buffDuration );
+ m_hMerasmusRevealer->m_Shared.AddCond( TF_COND_SPEED_BOOST, buffDuration );
+ m_hMerasmusRevealer->m_Shared.AddCond( TF_COND_INVULNERABLE, buffDuration );
+ }
+ m_hMerasmusRevealer = NULL;
+
+ // show Boss' health meter on HUD
+ if ( m_hHealthBar )
+ {
+ float healthPercentage = (float)GetHealth() / (float)GetMaxHealth();
+ m_hHealthBar->SetBossHealthPercentage( healthPercentage );
+ }
+
+ // face towards a nearby player
+ CUtlVector< CTFPlayer * > playerVector;
+ CollectPlayers( &playerVector, TF_TEAM_RED, COLLECT_ONLY_LIVING_PLAYERS );
+ CollectPlayers( &playerVector, TF_TEAM_BLUE, COLLECT_ONLY_LIVING_PLAYERS, APPEND_PLAYERS );
+
+ float closeRangeSq = FLT_MAX;
+ CTFPlayer *close = NULL;
+
+ for( int i=0; i<playerVector.Count(); ++i )
+ {
+ CTFPlayer *player = playerVector[i];
+
+ float rangeSq = GetRangeSquaredTo( player );
+ if ( rangeSq < closeRangeSq )
+ {
+ closeRangeSq = rangeSq;
+ close = player;
+ }
+ }
+
+ QAngle facingAngle;
+
+ if ( close )
+ {
+ Vector toPlayer = close->GetAbsOrigin() - GetAbsOrigin();
+ toPlayer.z = 0.0f;
+ toPlayer.NormalizeInPlace();
+
+ VectorAngles( toPlayer, Vector(0,0,1), facingAngle );
+ }
+ else
+ {
+ facingAngle.x = 0.0f;
+ facingAngle.y = RandomFloat( 0.0f, 360.0f );
+ facingAngle.z = 0.0f;
+ }
+
+ SetAbsAngles( facingAngle );
+}
+
+
+bool CMerasmus::ShouldReveal() const
+{
+ int nDestroyedProps = 0;
+ for ( int i=0; i<m_fakePropVector.Count(); ++i )
+ {
+ if ( m_fakePropVector[i] == NULL )
+ {
+ nDestroyedProps++;
+ }
+ }
+ return nDestroyedProps >= m_nDestroyedPropsToReveal;
+}
+
+
+bool CMerasmus::IsNextKilledPropMerasmus() const
+{
+ int nDestroyedProps = 0;
+ for ( int i=0; i<m_fakePropVector.Count(); ++i )
+ {
+ if ( m_fakePropVector[i] == NULL )
+ {
+ nDestroyedProps++;
+ }
+ }
+ return nDestroyedProps + 1 == m_nDestroyedPropsToReveal;
+}
+
+
+void CMerasmus::OnDisguise()
+{
+ m_flStartDisguiseTime = gpGlobals->curtime;
+ m_bRevealed = false;
+ m_isHiding = true;
+
+ StopAOEAttack();
+
+ AddEffects( EF_NOINTERP | EF_NODRAW );
+
+ DispatchParticleEffect( "merasmus_tp", WorldSpaceCenter(), GetAbsAngles() );
+
+ // don't collide with anything, we do our own collision detection in our think method
+ SetSolid( SOLID_NONE );
+ SetSolidFlags( FSOLID_NOT_SOLID );
+
+ // fake randomness of when Merasmus should reveal
+ m_nDestroyedPropsToReveal = RandomInt( MAX( 1, tf_merasmus_min_props_to_reveal.GetFloat() * m_fakePropVector.Count() ), m_fakePropVector.Count() );
+}
+
+
+bool CMerasmus::ShouldDisguise() const
+{
+ if ( GetHealth() <= 0 )
+ {
+ return false;
+ }
+
+ float flLostHealthPercentage = (float)( m_nRevealedHealth - GetHealth() ) / (float)GetMaxHealth();
+ return flLostHealthPercentage > tf_merasmus_should_disguise_threshold.GetFloat();
+}
+
+
+CTFWeaponBaseGrenadeProj* CMerasmus::CreateMerasmusGrenade( const Vector& vPosition, const Vector& vVelocity, CBaseCombatCharacter* pOwner, float fScale )
+{
+ QAngle qAngles = RandomAngle( 0, 360 );
+ CTFWeaponBaseMerasmusGrenade *pGrenade = static_cast<CTFWeaponBaseMerasmusGrenade*>( CBaseEntity::Create( "tf_weaponbase_merasmus_grenade", vPosition, qAngles, pOwner ) );
+ if ( pGrenade )
+ {
+ pGrenade->SetModel( MERASMUS_BOMB_MODEL );
+ DispatchSpawn( pGrenade );
+ pGrenade->InitGrenade( vVelocity, AngularImpulse( 600, random->RandomInt( -1200, 1200 ), 0 ), pOwner, 50 * fScale, 300.f * fScale );
+ pGrenade->SetDetonateTimerLength( 2.f );
+ pGrenade->SetModelScale( fScale );
+ pGrenade->SetCollisionGroup( TFCOLLISION_GROUP_ROCKETS ); // we want to use collision_group_rockets so we don't ever collide with players
+ }
+
+ return pGrenade;
+}
+
+
+const char* CMerasmus::GetRandomPropModelName()
+{
+ int which = RandomInt( 0, ARRAYSIZE( s_pszDisguiseProps ) - 1 );
+ return s_pszDisguiseProps[ which ];
+}
+
+
+void CMerasmus::PushPlayer( CTFPlayer* pPlayer, float flPushForce ) const
+{
+ // send the player flying
+ // make sure we push players up and away
+ Vector toPlayer = pPlayer->EyePosition() - GetAbsOrigin();
+ toPlayer.z = 0.0f;
+ toPlayer.NormalizeInPlace();
+ toPlayer.z = 1.0f;
+
+ Vector push = flPushForce * toPlayer;
+
+ pPlayer->ApplyAbsVelocityImpulse( push );
+}
+
+
+void CMerasmus::AddStun( CTFPlayer* pPlayer )
+{
+ if ( !IsRevealed() )
+ {
+ // don't let bomb head player explode on merasmus while disguise
+ return;
+ }
+
+ // first stun
+ if ( !IsStunned() )
+ {
+ CPVSFilter filter( WorldSpaceCenter() );
+ if (RandomInt( 1, 10) == 9 )
+ {
+ PlayLowPrioritySound( filter, "Halloween.MerasmusHitByBombRare" );
+ }
+ else
+ {
+ PlayLowPrioritySound( filter, "Halloween.MerasmusHitByBomb" );
+ }
+ }
+
+ // buff the player that stunned me
+ const float buffDuration = 10.0f;
+ pPlayer->m_Shared.AddCond( TF_COND_CRITBOOSTED_PUMPKIN, buffDuration );
+ pPlayer->m_Shared.AddCond( TF_COND_SPEED_BOOST, buffDuration );
+ pPlayer->m_Shared.AddCond( TF_COND_INVULNERABLE, buffDuration );
+
+ pPlayer->m_Shared.RemoveCond( TF_COND_STUNNED );
+ pPlayer->m_Shared.RemoveCond( TF_COND_HALLOWEEN_BOMB_HEAD );
+ pPlayer->MerasmusPlayerBombExplode( false );
+
+ PushPlayer( pPlayer, 300.f );
+ DispatchParticleEffect( "merasmus_dazed_explosion", WorldSpaceCenter(), GetAbsAngles() );
+
+ IGameEvent *pEvent = gameeventmanager->CreateEvent( "merasmus_stunned" );
+ if ( pEvent )
+ {
+ pEvent->SetInt( "player", pPlayer->GetUserID() );
+ gameeventmanager->FireEvent( pEvent, true );
+ }
+
+ // don't stun while doing AOE
+ if ( !m_bIsDoingAOEAttack )
+ {
+ m_nBombHitCount++;
+ m_stunTimer.Start( tf_merasmus_stun_duration.GetFloat() );
+ }
+}
+
+
+void CMerasmus::OnBeginStun()
+{
+ EmitSound( "Halloween.Merasmus_Stun" );
+
+ m_bStunned = true;
+}
+
+
+void CMerasmus::OnEndStun()
+{
+ m_stunTimer.Invalidate();
+ m_bStunned = false;
+}
+
+
+void CMerasmus::AddFakeProp( CTFMerasmusTrickOrTreatProp* pFakeProp )
+{
+ m_fakePropVector.AddToTail( pFakeProp );
+}
+
+
+void CMerasmus::RemoveAllFakeProps()
+{
+ for ( int i=0; i<m_fakePropVector.Count(); ++i )
+ {
+ if ( m_fakePropVector[i] != NULL )
+ {
+ UTIL_Remove( m_fakePropVector[i] );
+ }
+ }
+ m_fakePropVector.RemoveAll();
+}
+
+
+void BombHeadForTeam( int nTeam, int nBombHeadPlayers )
+{
+ // decrease nBombHeadPlayers by the number of existing bomb heads
+ CUtlVector< CTFPlayer * > playerVector;
+ CollectPlayers( &playerVector, nTeam, COLLECT_ONLY_LIVING_PLAYERS );
+
+ if ( playerVector.Count() <= 0 )
+ {
+ // everyone on this team is dead
+ return;
+ }
+
+ for( int n=0; n<nBombHeadPlayers; ++n )
+ {
+ // find the living player who was a bombhead the longest time ago and give them the bomb
+ CTFPlayer *pVictim = NULL;
+ float oldestTimeStamp = -1.0f;
+
+ for( int i=0; i<playerVector.Count(); ++i )
+ {
+ CTFPlayer *pPlayer = playerVector[i];
+
+ if ( pPlayer->GetTimeSinceWasBombHead() > oldestTimeStamp &&
+ !pPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_BOMB_HEAD ) &&
+ pPlayer->GetLastKnownArea() &&
+ pPlayer->GetLastKnownArea()->HasFuncNavPrefer() )
+ {
+ pVictim = pPlayer;
+ oldestTimeStamp = pPlayer->GetTimeSinceWasBombHead();
+ }
+ }
+
+ if ( !pVictim )
+ {
+ // no victims available - try again next time
+ return;
+ }
+
+ // give this victim the bomb
+ float flBuffDuration = tf_merasmus_bomb_head_duration.GetFloat();
+ pVictim->m_Shared.StunPlayer( tf_merasmus_bomb_head_duration.GetFloat(), 0.f, TF_STUN_LOSER_STATE );
+ pVictim->m_Shared.AddCond( TF_COND_HALLOWEEN_BOMB_HEAD, flBuffDuration );
+ pVictim->m_Shared.AddCond( TF_COND_SPEED_BOOST, flBuffDuration );
+
+ pVictim->SetBombHeadTimestamp();
+
+ // notify player they are a bomb
+ ClientPrint( pVictim, HUD_PRINTCENTER, "#TF_HALLOWEEN_MERASMUS_YOU_ARE_BOMB", pVictim->GetPlayerName() );
+ }
+}
+
+
+void CMerasmus::BombHeadMode()
+{
+ int nBombHeadPlayers = tf_merasmus_bomb_head_per_team.GetInt();
+ BombHeadForTeam( TF_TEAM_RED, nBombHeadPlayers );
+ BombHeadForTeam( TF_TEAM_BLUE, nBombHeadPlayers );
+}
+
+
+bool CMerasmus::ShouldLeave() const
+{
+ return m_lifeTimer.IsElapsed();
+}
+
+
+void CMerasmus::LeaveWarning()
+{
+ if ( m_lifeTimer.GetRemainingTime() < 10.0f && m_flLastWarnTime > 10.0f )
+ {
+ IGameEvent *event = gameeventmanager->CreateEvent( "merasmus_escape_warning" );
+ if ( event )
+ {
+ event->SetInt( "level", GetLevel() );
+ event->SetInt( "time_remaining", 10 );
+ gameeventmanager->FireEvent( event );
+ }
+ }
+ else if ( m_lifeTimer.GetRemainingTime() < 30.0f && m_flLastWarnTime > 30.0f )
+ {
+ IGameEvent *event = gameeventmanager->CreateEvent( "merasmus_escape_warning" );
+ if ( event )
+ {
+ event->SetInt( "level", GetLevel() );
+ event->SetInt( "time_remaining", 30 );
+ gameeventmanager->FireEvent( event );
+ }
+ }
+ else if ( m_lifeTimer.GetRemainingTime() < 60.0f && m_flLastWarnTime > 60.0f )
+ {
+ IGameEvent *event = gameeventmanager->CreateEvent( "merasmus_escape_warning" );
+ if ( event )
+ {
+ event->SetInt( "level", GetLevel() );
+ event->SetInt( "time_remaining", 60 );
+ gameeventmanager->FireEvent( event );
+ }
+ }
+ m_flLastWarnTime = m_lifeTimer.GetRemainingTime();
+}
+
+
+void CMerasmus::OnLeaveWhileInPropForm()
+{
+ CUtlVector< CBaseEntity* > validProps;
+ for ( int i=0; i<m_fakePropVector.Count(); ++i )
+ {
+ if ( m_fakePropVector[i] != NULL )
+ {
+ validProps.AddToTail( m_fakePropVector[i] );
+ }
+ }
+
+ if ( validProps.Count() )
+ {
+ int which = RandomInt( 0, validProps.Count() -1 );
+ SetAbsOrigin( validProps[ which ]->GetAbsOrigin() );
+ }
+}
+
+
+void CMerasmus::TriggerLogicRelay( const char* pszLogicRelayName, bool bSpawn /*= false*/ )
+{
+ CLogicRelay* pLogicRelay = assert_cast< CLogicRelay* >( gEntList.FindEntityByName( NULL, pszLogicRelayName ) );
+ if ( pLogicRelay )
+ {
+ inputdata_t data;
+ data.pCaller = this;
+ data.pActivator = this;
+ pLogicRelay->InputTrigger( data );
+
+ if ( bSpawn )
+ {
+ SetAbsOrigin( pLogicRelay->GetAbsOrigin() );
+ }
+ }
+}
+
+
+void CMerasmus::PlayLowPrioritySound( IRecipientFilter &filter, const char* pszSoundEntryName )
+{
+ CSoundParameters params;
+ if ( CBaseEntity::GetParametersForSound( pszSoundEntryName, params, NULL ) )
+ {
+ EmitSound_t es( params );
+ es.m_nFlags |= SND_DO_NOT_OVERWRITE_EXISTING_ON_CHANNEL;
+ EmitSound( filter, entindex(), es );
+ }
+}
+
+void CMerasmus::PlayHighPrioritySound( const char* pszSoundEntryName )
+{
+ CBroadcastRecipientFilter filter;
+ CSoundParameters params;
+ if ( CBaseEntity::GetParametersForSound( pszSoundEntryName, params, NULL ) )
+ {
+ EmitSound_t es( params );
+ EmitSound( filter, entindex(), es );
+ }
+}
+
+//---------------------------------------------------------------------------------------------
+void CMerasmus::RecordDisguiseTime( )
+{
+ if ( m_flStartDisguiseTime == 0 )
+ return;
+
+ float flTime = ( gpGlobals->curtime - m_flStartDisguiseTime );
+
+ if ( m_bossStats.m_flPropHuntTime1 == 0 )
+ {
+ m_bossStats.m_flPropHuntTime1 = flTime;
+ }
+ else
+ {
+ m_bossStats.m_flPropHuntTime2 = flTime;
+ }
+
+ m_flStartDisguiseTime = 0;
+}
+
+void CMerasmus::StartRespawnTimer() const
+{
+ if( TFGameRules() )
+ {
+ if( GetLevel() <= 3 )
+ {
+ TFGameRules()->StartHalloweenBossTimer( tf_merasmus_spawn_interval.GetFloat() ,tf_merasmus_spawn_interval_variation.GetFloat() );
+ }
+ else
+ {
+ TFGameRules()->StartHalloweenBossTimer( 60.f );
+ }
+ }
+}
+
+//---------------------------------------------------------------------------------------------
+void CMerasmus::SW_ReportMerasmusStats( void )
+{
+ if ( !GCClientSystem() )
+ return;
+
+ static uint8 unEventCounter = 0;
+
+ GCSDK::CProtoBufMsg<CMsgHalloween_Merasmus2012> msg( k_EMsgGC_Halloween_Merasmus2012 );
+ msg.Body().set_time_submitted( CRTime::RTime32TimeCur() );
+ msg.Body().set_is_valve_server( false );
+ msg.Body().set_boss_level( GetLevel() );
+ msg.Body().set_spawned_health( GetMaxHealth() );
+ msg.Body().set_remaining_health( GetHealth() );
+ msg.Body().set_life_time( (int)m_lifeTimer.GetElapsedTime() ); // Amount of time in seconds, boss was alive for
+ msg.Body().set_bomb_kills( m_bossStats.m_nBombKills ); // Kills from Bombs
+ msg.Body().set_staff_kills( m_bossStats.m_nStaffKills ); // kills from staff attack
+ msg.Body().set_pvp_kills( m_bossStats.m_nPvpKills ); // Number of kills from players while Boss is out (Jerk factor)
+ msg.Body().set_prophunt_time1( m_bossStats.m_flPropHuntTime1 );
+ msg.Body().set_prophunt_time2( m_bossStats.m_flPropHuntTime2 );
+
+ msg.Body().set_dmg_scout( m_bossStats.m_arrClassDamage[ TF_CLASS_SCOUT ] ); // Amount of damage done by each class
+ msg.Body().set_dmg_sniper( m_bossStats.m_arrClassDamage[ TF_CLASS_SNIPER] );
+ msg.Body().set_dmg_soldier( m_bossStats.m_arrClassDamage[ TF_CLASS_SOLDIER] );
+ msg.Body().set_dmg_demo( m_bossStats.m_arrClassDamage[ TF_CLASS_DEMOMAN] );
+ msg.Body().set_dmg_medic( m_bossStats.m_arrClassDamage[ TF_CLASS_MEDIC ] );
+ msg.Body().set_dmg_heavy( m_bossStats.m_arrClassDamage[ TF_CLASS_HEAVYWEAPONS ] );
+ msg.Body().set_dmg_pyro( m_bossStats.m_arrClassDamage[ TF_CLASS_PYRO ] );
+ msg.Body().set_dmg_spy( m_bossStats.m_arrClassDamage[ TF_CLASS_SPY ] );
+ msg.Body().set_dmg_engineer( m_bossStats.m_arrClassDamage[ TF_CLASS_ENGINEER ] );
+
+ // Class counts
+ CUtlVector< CTFPlayer * > playerVector;
+ CollectPlayers( &playerVector );
+
+ int nClassCounts[ TF_LAST_NORMAL_CLASS ];
+ V_memset( nClassCounts, 0, sizeof( nClassCounts ) );
+ FOR_EACH_VEC( playerVector, index )
+ {
+ int iClass = playerVector[index]->GetPlayerClass()->GetClassIndex();
+ if ( iClass > TF_CLASS_UNDEFINED && iClass < TF_LAST_NORMAL_CLASS )
+ {
+ nClassCounts[iClass]++;
+ }
+ }
+
+ msg.Body().set_scout_count( nClassCounts[TF_CLASS_SCOUT] ); // Class and player break down at the point of boss despawn
+ msg.Body().set_sniper_count( nClassCounts[TF_CLASS_SNIPER] );
+ msg.Body().set_solider_count( nClassCounts[TF_CLASS_SOLDIER] );
+ msg.Body().set_demo_count( nClassCounts[TF_CLASS_DEMOMAN] );
+ msg.Body().set_medic_count( nClassCounts[TF_CLASS_MEDIC] );
+ msg.Body().set_heavy_count( nClassCounts[TF_CLASS_HEAVYWEAPONS] );
+ msg.Body().set_pyro_count( nClassCounts[TF_CLASS_PYRO] );
+ msg.Body().set_spy_count( nClassCounts[TF_CLASS_SPY] );
+ msg.Body().set_engineer_count( nClassCounts[TF_CLASS_ENGINEER] );
+
+ GCClientSystem()->BSendMessage( msg );
+
+
+// OGS Version
+//
+//#if !defined(NO_STEAM)
+// KeyValues* pKVData = new KeyValues( "TF2Halloween2012MerasmusBossStats" );
+//
+// // Auto Values
+// // ID
+// // SessionID
+// // TimeSubmitted
+// //pKVData->SetBool( "IsValveServer", false );
+//
+// pKVData->SetInt( "BossLevel", GetLevel() );
+// pKVData->SetInt( "SpawnedHealth", GetMaxHealth() );
+// pKVData->SetInt( "RemainingHealth", GetHealth() ); // 0 == Boss was killed
+//
+// pKVData->SetInt( "LifeTime", (int)m_lifeTimer.GetElapsedTime() ); // Amount of time in seconds, boss was alive for
+// pKVData->SetInt( "BombKills", m_bossStats.m_nBombKills ); // Kills from Bombs
+// pKVData->SetInt( "StaffKills", m_bossStats.m_nStaffKills ); // kills from staff account
+// pKVData->SetInt( "PvPKills", m_bossStats.m_nPvpKills ); // Number of kills from players while Boss is out (Jerk factor)
+// pKVData->SetInt( "PropHuntTime1", m_bossStats.m_flPropHuntTime1 );
+// pKVData->SetInt( "PropHuntTime2", m_bossStats.m_flPropHuntTime2 );
+//
+// // Class Damage
+// pKVData->SetInt( "DmgFromScout", m_bossStats.m_arrClassDamage[ TF_CLASS_SCOUT ] ); // Amount of damage done by each class
+// pKVData->SetInt( "DmgFromSniper", m_bossStats.m_arrClassDamage[ TF_CLASS_SNIPER ] );
+// pKVData->SetInt( "DmgFromSoldier", m_bossStats.m_arrClassDamage[ TF_CLASS_SOLDIER ] );
+// pKVData->SetInt( "DmgFromDemo", m_bossStats.m_arrClassDamage[ TF_CLASS_DEMOMAN ] );
+// pKVData->SetInt( "DmgFromMedic", m_bossStats.m_arrClassDamage[ TF_CLASS_MEDIC ] );
+// pKVData->SetInt( "DmgFromHeavy", m_bossStats.m_arrClassDamage[ TF_CLASS_HEAVYWEAPONS ] );
+// pKVData->SetInt( "DmgFromPyro", m_bossStats.m_arrClassDamage[ TF_CLASS_PYRO ] );
+// pKVData->SetInt( "DmgFromSpy", m_bossStats.m_arrClassDamage[ TF_CLASS_SPY ] );
+// pKVData->SetInt( "DmgFromEngineer", m_bossStats.m_arrClassDamage[ TF_CLASS_ENGINEER ] );
+//
+// // Class counts
+// CUtlVector< CTFPlayer * > playerVector;
+// CollectPlayers( &playerVector );
+//
+// int nClassCounts[ TF_LAST_NORMAL_CLASS ];
+// V_memset( nClassCounts, 0, sizeof( nClassCounts ) );
+// FOR_EACH_VEC( playerVector, index )
+// {
+// int iClass = playerVector[index]->GetPlayerClass()->GetClassIndex();
+// if ( iClass > TF_CLASS_UNDEFINED && iClass < TF_LAST_NORMAL_CLASS )
+// {
+// nClassCounts[iClass]++;
+// }
+// }
+//
+// pKVData->SetInt( "ScoutCount", nClassCounts[TF_CLASS_SCOUT] ); // Class and player break down at the point of boss despawn
+// pKVData->SetInt( "SniperCount", nClassCounts[TF_CLASS_SNIPER] );
+// pKVData->SetInt( "SoldierCount", nClassCounts[TF_CLASS_SOLDIER] );
+// pKVData->SetInt( "DemoCount", nClassCounts[TF_CLASS_DEMOMAN] );
+// pKVData->SetInt( "MedicCount", nClassCounts[TF_CLASS_MEDIC] );
+// pKVData->SetInt( "HeavyCount", nClassCounts[TF_CLASS_HEAVYWEAPONS] );
+// pKVData->SetInt( "PyroCount", nClassCounts[TF_CLASS_PYRO] );
+// pKVData->SetInt( "SpyCount", nClassCounts[TF_CLASS_SPY] );
+// pKVData->SetInt( "EngineerCount", nClassCounts[TF_CLASS_ENGINEER] );
+//
+// //GetSteamWorksSGameStatsUploader().AddStatsForUpload( pKVData );
+//#endif
+
+ m_bossStats.ResetStats();
+}
+
+
+void CollectTargets( CBaseCombatCharacter *pCaster, float flSpellRange, int nTargetTeam, int nMaxTarget, CUtlVector< CHandle< CBaseEntity > > &vecTargets )
+{
+ vecTargets.RemoveAll();
+
+ // collect everyone
+ CUtlVector< CTFPlayer * > playerVector;
+ CollectPlayers( &playerVector, nTargetTeam, COLLECT_ONLY_LIVING_PLAYERS );
+
+ CUtlVector< CTFPlayer * > candidateTargets;
+ for ( int i=0; i<playerVector.Count(); ++i )
+ {
+ CTFPlayer *pPlayer = playerVector[i];
+ Vector toPlayer = pCaster->EyePosition() - pPlayer->WorldSpaceCenter();
+ if ( toPlayer.IsLengthLessThan( flSpellRange ) && pCaster->IsLineOfSightClear( pPlayer ) )
+ {
+ candidateTargets.AddToTail( pPlayer );
+ }
+ }
+
+ while ( candidateTargets.Count() != 0 && vecTargets.Count() != nMaxTarget )
+ {
+ int which = RandomInt( 0, candidateTargets.Count() - 1 );
+ vecTargets.AddToTail( candidateTargets[ which ] );
+ candidateTargets.FastRemove( which );
+ }
+
+ // find sentry in range
+ for ( int i=0; i<IBaseObjectAutoList::AutoList().Count(); ++i )
+ {
+ CBaseObject *pObj = static_cast< CBaseObject* >( IBaseObjectAutoList::AutoList()[i] );
+ if ( pObj->ObjectType() == OBJ_SENTRYGUN )
+ {
+ Vector toSentry = pCaster->EyePosition() - pObj->WorldSpaceCenter();
+ if ( toSentry.IsLengthLessThan( flSpellRange ) && pCaster->IsLineOfSightClear( pObj ) )
+ {
+ vecTargets.AddToTail( pObj );
+ }
+ }
+ }
+}
+
+
+void CastSpell( CBaseCombatCharacter* pCaster, const char* pszCastingAttachmentName, float flSpellRange, float flMinDamage, float flMaxDamage, CBaseEntity* pTarget )
+{
+ float flSpellTime = 5.f;
+
+ if ( pTarget->IsPlayer() )
+ {
+ CTFPlayer *pPlayer = ToTFPlayer( pTarget );
+ pPlayer->m_Shared.SelfBurn( flSpellTime );
+ pPlayer->ApplyAbsVelocityImpulse( 1000.f * Vector( 0, 0, 1 ) );
+
+ Vector toPlayer = pCaster->EyePosition() - pPlayer->WorldSpaceCenter();
+ float flDistSqr = toPlayer.LengthSqr();
+
+ float flDmg = RemapValClamped( flDistSqr, 100.f, Square( 0.5f * flSpellRange ), flMaxDamage, flMinDamage );
+ CTakeDamageInfo info( pCaster, pCaster, flDmg, DMG_BURN, TF_DMG_CUSTOM_MERASMUS_ZAP );
+ pPlayer->TakeDamage( info );
+ }
+ else if ( pTarget->IsBaseObject() )
+ {
+ CBaseObject *pObj = static_cast< CBaseObject* >( pTarget );
+ pObj->DetonateObject();
+ }
+
+ // Shoot a beam at them
+ CReliableBroadcastRecipientFilter filter;
+ Vector vStartPos;
+ QAngle qStartAngles;
+ pCaster->GetAttachment( pszCastingAttachmentName, vStartPos, qStartAngles );
+ Vector vEnd = pTarget->EyePosition();
+ te_tf_particle_effects_control_point_t controlPoint = { PATTACH_ABSORIGIN, vEnd };
+ TE_TFParticleEffectComplex( filter, 0.0f, "merasmus_zap", vStartPos, qStartAngles, NULL, &controlPoint, pCaster, PATTACH_CUSTOMORIGIN );
+}
+
+
+/*static*/ bool CMerasmus::Zap( CBaseCombatCharacter *pCaster, const char* pszCastingAttachmentName, float flSpellRange, float flMinDamage, float flMaxDamage, int nMaxTarget, int nTargetTeam /*= TEAM_ANY*/ )
+{
+ CUtlVector< CHandle< CBaseEntity > > vecTargets;
+ CollectTargets( pCaster, flSpellRange, nTargetTeam, nMaxTarget, vecTargets );
+
+ if ( vecTargets.Count() == 0 )
+ return false;
+
+ for ( int i=0; i<vecTargets.Count(); ++i )
+ {
+ CBaseEntity *pTarget = vecTargets[i];
+ if ( pTarget )
+ {
+ CastSpell( pCaster, pszCastingAttachmentName, flSpellRange, flMinDamage, flMaxDamage, pTarget );
+ }
+ }
+
+ return true;
+}
+
+
+//---------------------------------------------------------------------------------------------
+//---------------------------------------------------------------------------------------------
+class CMerasmusBehavior : public Action< CMerasmus >
+{
+public:
+ virtual Action< CMerasmus > *InitialContainedAction( CMerasmus *me )
+ {
+ return new CMerasmusReveal;
+ }
+
+ virtual ActionResult< CMerasmus > OnStart( CMerasmus *me, Action< CMerasmus > *priorAction )
+ {
+ return Continue();
+ }
+
+ virtual ActionResult< CMerasmus > Update( CMerasmus *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< CMerasmus::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_Merasmus_Killers", attackerVector[i].m_attacker->GetPlayerName() );
+
+ if ( TFGameRules() && TFGameRules()->IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_LAKESIDE ) )
+ {
+ attackerVector[i].m_attacker->AwardAchievement( ACHIEVEMENT_TF_HALLOWEEN_MERASMUS_KILL );
+ }
+ }
+ }
+
+ // Award hat levels based on Merasmus' level when he dies, but only to people who were
+ // around when Merasmus spawned.
+ const CUtlVector< CHandle<CTFPlayer> >& vecStartingAttackers = me->GetStartingAttackers();
+ if ( GCClientSystem() )
+ {
+ // GC message
+ // Notify the GC that this occurred to possibly level up your hat if you have one
+ GCSDK::CProtoBufMsg<CMsgUpdateHalloweenMerasmusLootLevel> msg( k_EMsgGC_Halloween_UpdateMerasmusLootLevel );
+ msg.Body().set_merasmus_level( me->GetLevel() );
+
+ FOR_EACH_VEC( vecStartingAttackers, i )
+ {
+ CTFPlayer* pPlayer = vecStartingAttackers[i];
+ if ( pPlayer )
+ {
+ CSteamID steamID;
+ if ( pPlayer->GetSteamID( &steamID ) && steamID.IsValid() && steamID.BIndividualAccount() )
+ {
+ CMsgUpdateHalloweenMerasmusLootLevel_Player *pMsgPlayer = msg.Body().add_players();
+ pMsgPlayer->set_steam_id( steamID.ConvertToUint64() );
+ }
+
+ }
+ }
+ GCClientSystem()->BSendMessage( msg );
+ }
+ }
+
+ // nobody is IT any longer
+ TFGameRules()->SetIT( NULL );
+
+ return ChangeTo( new CMerasmusDying, "I am dead!" );
+ }
+ else
+ {
+ me->LeaveWarning();
+
+ if ( me->ShouldLeave() && !me->IsStunned() && !me->IsFlying() )
+ {
+ return ChangeTo( new CMerasmusEscape, "Escaping..." );
+ }
+ }
+
+ return Continue();
+ }
+
+
+ virtual EventDesiredResult< CMerasmus > OnInjured( CMerasmus *me, const CTakeDamageInfo &info )
+ {
+ if ( me->ShouldDisguise() && me->IsRevealed() && !me->IsStunned() && !me->IsFlying() )
+ {
+ return TrySuspendFor( new CMerasmusDisguise, RESULT_IMPORTANT, "Disguise" );
+ }
+
+ return TryContinue();
+ }
+
+ virtual const char *GetName( void ) const { return "Merasmus Behavior"; } // return name of this action
+
+private:
+
+
+};
+
+
+//---------------------------------------------------------------------------------------------
+//---------------------------------------------------------------------------------------------
+CMerasmusIntention::CMerasmusIntention( CMerasmus *me ) : IIntention( me )
+{
+ m_behavior = new Behavior< CMerasmus >( new CMerasmusBehavior );
+}
+
+CMerasmusIntention::~CMerasmusIntention()
+{
+ delete m_behavior;
+}
+
+void CMerasmusIntention::Reset( void )
+{
+ delete m_behavior;
+ m_behavior = new Behavior< CMerasmus >( new CMerasmusBehavior );
+}
+
+void CMerasmusIntention::Update( void )
+{
+ m_behavior->Update( static_cast< CMerasmus * >( GetBot() ), GetUpdateInterval() );
+}
+
+// is this a place we can be?
+QueryResultType CMerasmusIntention::IsPositionAllowed( const INextBot *meBot, const Vector &pos ) const
+{
+ return ANSWER_YES;
+}
+
+
+
+//---------------------------------------------------------------------------------------------
+//---------------------------------------------------------------------------------------------
+void CMerasmusLocomotion::Update( void )
+{
+ CMerasmus *me = (CMerasmus *)GetBot()->GetEntity();
+
+ if ( me->IsFlying() )
+ {
+ // don't update this locomotor since the flying locomotor is active
+ return;
+ }
+
+ NextBotGroundLocomotion::Update();
+}
+
+float CMerasmusLocomotion::GetRunSpeed( void ) const
+{
+ return tf_merasmus_speed.GetFloat();
+}
+
+
+//---------------------------------------------------------------------------------------------
+// if delta Z is greater than this, we have to jump to get up
+float CMerasmusLocomotion::GetStepHeight( void ) const
+{
+ return 18.0f;
+}
+
+
+//---------------------------------------------------------------------------------------------
+// return maximum height of a jump
+float CMerasmusLocomotion::GetMaxJumpHeight( void ) const
+{
+ return 18.0f;
+}
+
+
+//---------------------------------------------------------------------------------------------
+// Return max rate of yaw rotation
+float CMerasmusLocomotion::GetMaxYawRate( void ) const
+{
+ return 200.0f;
+}
+
+
+//---------------------------------------------------------------------------------------------
+bool CMerasmusLocomotion::ShouldCollideWith( const CBaseEntity *object ) const
+{
+ if ( !object )
+ return false;
+
+ // Don't collide with players
+ return object->IsPlayer() ? false : true;
+}
+
+
+//---------------------------------------------------------------------------------------------
+//---------------------------------------------------------------------------------------------
+#define MERASMUS_ACCELERATION 250.0f //500.0f
+
+CMerasmusFlyingLocomotion::CMerasmusFlyingLocomotion( INextBot *bot ) : ILocomotion( bot )
+{
+ Reset();
+}
+
+
+//---------------------------------------------------------------------------------------------
+CMerasmusFlyingLocomotion::~CMerasmusFlyingLocomotion()
+{
+}
+
+
+//---------------------------------------------------------------------------------------------
+// (EXTEND) reset to initial state
+void CMerasmusFlyingLocomotion::Reset( void )
+{
+ m_velocity = vec3_origin;
+ m_acceleration = vec3_origin;
+ m_currentSpeed = 0.0f;
+ m_forward = vec3_origin;
+ m_desiredAltitude = 50.0f;
+}
+
+
+//---------------------------------------------------------------------------------------------
+void CMerasmusFlyingLocomotion::MaintainAltitude( void )
+{
+ CBaseCombatCharacter *me = GetBot()->GetEntity();
+
+ float groundZ;
+ TheNavMesh->GetSimpleGroundHeight( me->GetAbsOrigin(), &groundZ );
+
+ float currentAltitude = me->GetAbsOrigin().z - groundZ;
+ float error = m_desiredAltitude - currentAltitude;
+ float accelZ = clamp( error, -MERASMUS_ACCELERATION, MERASMUS_ACCELERATION );
+
+ m_acceleration.z += accelZ;
+}
+
+
+//---------------------------------------------------------------------------------------------
+// (EXTEND) update internal state
+void CMerasmusFlyingLocomotion::Update( void )
+{
+ CMerasmus *me = (CMerasmus *)GetBot()->GetEntity();
+ const float deltaT = GetUpdateInterval();
+
+ if ( !me->IsFlying() )
+ {
+ // not flying - let the other locomotor run
+ return;
+ }
+
+ Vector pos = me->GetAbsOrigin();
+
+ // always maintain altitude, even if not trying to move (ie: no Approach call)
+ MaintainAltitude();
+
+ m_forward = m_velocity;
+ m_currentSpeed = m_forward.NormalizeInPlace();
+
+ Vector damping( 1.0f, 1.0f, 1.0f );
+ Vector totalAccel = m_acceleration - m_velocity * damping;
+
+ m_velocity += totalAccel * deltaT;
+ me->SetAbsVelocity( m_velocity );
+
+ pos += m_velocity * deltaT;
+
+ // Merasmus doesn't collide with players and floats between valid nav areas
+ // so skip the collision checking
+ GetBot()->GetEntity()->SetAbsOrigin( pos );
+ m_acceleration = vec3_origin;
+}
+
+
+//---------------------------------------------------------------------------------------------
+// (EXTEND) move directly towards the given position
+void CMerasmusFlyingLocomotion::Approach( const Vector &goalPos, float goalWeight )
+{
+ Vector flyGoal = goalPos;
+ flyGoal.z += m_desiredAltitude;
+
+ Vector toGoal = flyGoal - GetBot()->GetEntity()->GetAbsOrigin();
+ // altitude is handled in Update()
+ toGoal.z = 0.0f;
+ toGoal.NormalizeInPlace();
+
+ m_acceleration += MERASMUS_ACCELERATION * toGoal;
+}
+
+
+//---------------------------------------------------------------------------------------------
+float CMerasmusFlyingLocomotion::GetDesiredSpeed( void ) const
+{
+ return tf_merasmus_speed.GetFloat();
+}
+
+
+//---------------------------------------------------------------------------------------------
+void CMerasmusFlyingLocomotion::SetDesiredAltitude( float height )
+{
+ m_desiredAltitude = height;
+}
+
+
+//---------------------------------------------------------------------------------------------
+float CMerasmusFlyingLocomotion::GetDesiredAltitude( void ) const
+{
+ return m_desiredAltitude;
+}
+
+//---------------------------------------------------------------------------------------------
+bool CMerasmusFlyingLocomotion::ShouldCollideWith( const CBaseEntity *object ) const
+{
+ if ( !object )
+ return false;
+
+ // Don't collide with players
+ return object->IsPlayer() ? false : true;
+}
+
+//---------------------------------------------------------------------------------------------
+void CMerasmusFlyingLocomotion::FaceTowards( const Vector &target )
+{
+ CMerasmus *me = (CMerasmus *)GetBot()->GetEntity();
+ const float deltaT = GetUpdateInterval();
+
+ QAngle angles = me->GetLocalAngles();
+
+ float desiredYaw = UTIL_VecToYaw( target - GetFeet() );
+
+ float angleDiff = UTIL_AngleDiff( desiredYaw, angles.y );
+
+ const float maxYawRate = 100.0f;
+ float deltaYaw = maxYawRate * deltaT;
+
+ if ( angleDiff < -deltaYaw )
+ {
+ angles.y -= deltaYaw;
+ }
+ else if ( angleDiff > deltaYaw )
+ {
+ angles.y += deltaYaw;
+ }
+ else
+ {
+ angles.y += angleDiff;
+ }
+
+ me->SetLocalAngles( angles );
+}
+
+
+
diff --git a/game/server/tf/halloween/merasmus/merasmus.h b/game/server/tf/halloween/merasmus/merasmus.h
new file mode 100644
index 0000000..2b3f4ea
--- /dev/null
+++ b/game/server/tf/halloween/merasmus/merasmus.h
@@ -0,0 +1,412 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//
+//
+//=============================================================================
+#ifndef MERASMUS_H
+#define MERASMUS_H
+
+#include "NextBot.h"
+#include "NextBotBehavior.h"
+#include "NextBotGroundLocomotion.h"
+#include "merasmus_body.h"
+#include "Path/NextBotPathFollow.h"
+#include "../halloween_base_boss.h"
+
+
+extern ConVar tf_merasmus_health_base;
+extern ConVar tf_merasmus_health_per_player;
+extern ConVar tf_merasmus_min_player_count;
+
+extern ConVar tf_merasmus_speed;
+extern ConVar tf_merasmus_attack_range;
+extern ConVar tf_merasmus_speed_recovery_rate;
+extern ConVar tf_merasmus_speed_penalty;
+extern ConVar tf_merasmus_chase_duration;
+extern ConVar tf_merasmus_chase_range;
+
+extern ConVar tf_merasmus_health_regen_rate;
+
+extern ConVar tf_merasmus_bomb_head_duration;
+extern ConVar tf_merasmus_bomb_head_per_team;
+
+class CTFPlayer;
+class CWheelOfDoom;
+class CMerasmus;
+class CTFWeaponBaseGrenadeProj;
+class CTFMerasmusTrickOrTreatProp;
+class CMonsterResource;
+
+//----------------------------------------------------------------------------
+class CMerasmusSWStats
+{
+public:
+
+ void ResetStats ()
+ {
+ V_memset( m_arrClassDamage, 0, sizeof( m_arrClassDamage ) );
+ m_flPropHuntTime1 = 0;
+ m_flPropHuntTime2 = 0;
+ m_flLifeTime = 0;
+ m_nBombKills = 0;
+ m_nStaffKills = 0;
+ m_nPvpKills = 0;
+ }
+
+ int m_arrClassDamage[ TF_LAST_NORMAL_CLASS ];
+ float m_flPropHuntTime1;
+ float m_flPropHuntTime2;
+ float m_flLifeTime;
+ int m_nBombKills;
+ int m_nStaffKills;
+ int m_nPvpKills;
+};
+
+//----------------------------------------------------------------------------
+class CMerasmusLocomotion : public NextBotGroundLocomotion
+{
+public:
+ CMerasmusLocomotion( INextBot *bot ) : NextBotGroundLocomotion( bot ) { }
+ virtual ~CMerasmusLocomotion() { }
+
+ virtual void Update( void ); // (EXTEND) update internal state
+
+ virtual float GetRunSpeed( void ) const; // get maximum running speed
+ virtual float GetStepHeight( void ) const; // if delta Z is greater than this, we have to jump to get up
+ virtual float GetMaxJumpHeight( void ) const; // return maximum height of a jump
+
+ /**
+ * Should we collide with this entity?
+ */
+ virtual bool ShouldCollideWith( const CBaseEntity *object ) const;
+
+private:
+ virtual float GetMaxYawRate( void ) const; // return max rate of yaw rotation
+};
+
+
+//----------------------------------------------------------------------------
+class CMerasmusFlyingLocomotion : public ILocomotion
+{
+public:
+ CMerasmusFlyingLocomotion( INextBot *bot );
+ virtual ~CMerasmusFlyingLocomotion();
+
+ virtual void Reset( void ); // (EXTEND) reset to initial state
+ virtual void Update( void ); // (EXTEND) update internal state
+
+ virtual void Approach( const Vector &goalPos, float goalWeight = 1.0f ); // (EXTEND) move directly towards the given position
+
+ virtual float GetDesiredSpeed( void ) const; // returns the current desired speed
+
+ virtual void SetDesiredAltitude( float height ); // how high above our Approach goal do we float?
+ virtual float GetDesiredAltitude( void ) const;
+
+ virtual const Vector &GetGroundNormal( void ) const; // surface normal of the ground we are in contact with
+
+ virtual const Vector &GetVelocity( void ) const; // return current world space velocity
+ void SetVelocity( const Vector &velocity );
+
+ virtual bool ShouldCollideWith( const CBaseEntity *object ) const;
+
+ virtual void FaceTowards( const Vector &target ); // rotate body to face towards "target"
+
+protected:
+ float m_currentSpeed;
+ Vector m_forward;
+
+ float m_desiredAltitude;
+ void MaintainAltitude( void );
+
+ Vector m_velocity;
+ Vector m_acceleration;
+};
+
+inline const Vector &CMerasmusFlyingLocomotion::GetGroundNormal( void ) const
+{
+ static Vector up( 0, 0, 1.0f );
+
+ return up;
+}
+
+inline const Vector &CMerasmusFlyingLocomotion::GetVelocity( void ) const
+{
+ return m_velocity;
+}
+
+inline void CMerasmusFlyingLocomotion::SetVelocity( const Vector &velocity )
+{
+ m_velocity = velocity;
+}
+
+
+//----------------------------------------------------------------------------
+class CMerasmusIntention : public IIntention
+{
+public:
+ CMerasmusIntention( CMerasmus *me );
+ virtual ~CMerasmusIntention();
+
+ virtual void Reset( void );
+ virtual void Update( void );
+
+ virtual QueryResultType IsPositionAllowed( const INextBot *me, const Vector &pos ) const; // is the a place we can be?
+
+ virtual INextBotEventResponder *FirstContainedResponder( void ) const { return m_behavior; }
+ virtual INextBotEventResponder *NextContainedResponder( INextBotEventResponder *current ) const { return NULL; }
+
+private:
+ Behavior< CMerasmus > *m_behavior;
+};
+
+DECLARE_AUTO_LIST( IMerasmusAutoList );
+
+//----------------------------------------------------------------------------
+class CMerasmus : public CHalloweenBaseBoss, public CGameEventListener, public IMerasmusAutoList
+{
+public:
+ DECLARE_CLASS( CMerasmus, CHalloweenBaseBoss );
+ DECLARE_SERVERCLASS();
+
+ CMerasmus();
+ virtual ~CMerasmus();
+
+ static void PrecacheMerasmus();
+ virtual void Precache();
+ virtual void Spawn( void );
+ virtual void UpdateOnRemove();
+
+ virtual int OnTakeDamage_Alive( const CTakeDamageInfo &info );
+
+ // CGameEventListener
+ virtual void FireGameEvent( IGameEvent *event );
+
+ // INextBot
+ virtual CMerasmusIntention *GetIntentionInterface( void ) const { return m_intention; }
+ virtual ILocomotion *GetLocomotionInterface( void ) const { if ( m_isFlying ) return m_flyingLocomotor; return m_locomotor; }
+ virtual CMerasmusBody *GetBodyInterface( void ) const { return m_body; }
+
+ virtual void Update( void );
+
+ const Vector &GetHomePosition( void ) const;
+
+ Vector GetCastPosition() const;
+
+ bool IsRevealed() const { return m_bRevealed; }
+ void OnRevealed(bool bPlaySound = true);
+ bool ShouldReveal() const;
+ bool IsNextKilledPropMerasmus() const;
+ void SetRevealer( CTFPlayer* pPlayer ) { m_hMerasmusRevealer = pPlayer; }
+
+ void OnDisguise();
+ bool ShouldDisguise() const;
+
+ void StartAOEAttack() { m_bIsDoingAOEAttack = true; }
+ void StopAOEAttack() { m_bIsDoingAOEAttack = false; }
+ bool IsDoingAOEAttack() const { return m_bIsDoingAOEAttack; }
+
+ static CTFWeaponBaseGrenadeProj* CreateMerasmusGrenade( const Vector& vPosition, const Vector& vVelocity, CBaseCombatCharacter* pOwner, float fScale = 1.0f );
+
+ static const char* GetRandomPropModelName();
+
+ void PushPlayer( CTFPlayer* pPlayer, float flPushForce ) const;
+ int GetBombHitCount() const { return m_nBombHitCount; }
+ void ResetBombHitCount() { m_nBombHitCount = 0; }
+ void AddStun( CTFPlayer* pPlayer );
+ void OnBeginStun();
+ void OnEndStun();
+ bool HasStunTimer() const { return !m_stunTimer.IsElapsed(); }
+ bool IsStunned() const { return m_bStunned; }
+
+ void AddFakeProp( CTFMerasmusTrickOrTreatProp* pFakeProp );
+ void RemoveAllFakeProps();
+
+ void BombHeadMode();
+
+ bool ShouldLeave() const;
+ void LeaveWarning();
+ void OnLeaveWhileInPropForm();
+
+ void TriggerLogicRelay( const char* pszLogicRelayName, bool bSpawn = false );
+
+ void StartFlying( void ) { m_isFlying = true; }
+ void StopFlying( void ) { m_isFlying = false; }
+ bool IsFlying( void ) const { return m_isFlying; }
+ bool IsHiding( void ) const { return m_isHiding; }
+
+ void PlayLowPrioritySound( IRecipientFilter &filter, const char* pszSoundEntryName );
+ void PlayHighPrioritySound( const char* pszSoundEntryName );
+
+ void GainLevel( void );
+ void ResetLevel( void );
+ static int GetMerasmusLevel() { return m_level; }
+ virtual int GetLevel( void ) const OVERRIDE;
+ static void DBG_SetLevel( int nLevel );
+
+ virtual HalloweenBossType GetBossType() const { return HALLOWEEN_BOSS_MERASMUS; }
+
+ void StartRespawnTimer() const;
+
+ const CUtlVector< CHandle<CTFPlayer> >& GetStartingAttackers() const;
+
+ static bool Zap( CBaseCombatCharacter *pCaster, const char* pszCastingAttachmentName, float flSpellRange, float flMinDamage, float flMaxDamage, int nMaxTarget, int nTargetTeam = TEAM_ANY );
+
+ // Stats
+ void RecordDisguiseTime( );
+ void SW_ReportMerasmusStats( void );
+private:
+
+
+ CMerasmusIntention *m_intention;
+ CMerasmusLocomotion *m_locomotor;
+ CMerasmusFlyingLocomotion *m_flyingLocomotor;
+ CMerasmusBody *m_body;
+
+ bool m_isFlying;
+
+ bool m_isHiding;
+
+ CUtlVector< AttackerInfo > m_attackerVector; // list of everyone who injured me, and when
+ CUtlVector< CHandle<CTFPlayer> > m_startingAttackersVector;
+
+ CountdownTimer m_stunTimer;
+ int m_nBombHitCount;
+
+ Vector m_homePos;
+ int m_damagePoseParameter;
+
+ int m_nRevealedHealth;
+
+ CHandle< CWheelOfDoom > m_wheel;
+
+ CHandle< CMonsterResource > m_hHealthBar;
+
+ CNetworkVar( bool, m_bRevealed );
+ CNetworkVar( bool, m_bIsDoingAOEAttack );
+ CNetworkVar( bool, m_bStunned );
+
+ CSoundPatch *m_pIdleSound;
+
+ CUtlVector< CHandle< CTFMerasmusTrickOrTreatProp > > m_fakePropVector;
+
+ int m_nDestroyedPropsToReveal;
+
+ SolidType_t m_solidType;
+ int m_solidFlags;
+
+ CHandle< CTFPlayer > m_hMerasmusRevealer;
+
+ CountdownTimer m_lifeTimer;
+ float m_flLastWarnTime;
+
+ // For Stats
+ float m_flStartDisguiseTime;
+ CMerasmusSWStats m_bossStats;
+
+ static int m_level;
+};
+
+
+inline int CMerasmus::GetLevel( void ) const
+{
+ return m_level;
+}
+
+inline void CMerasmus::GainLevel( void )
+{
+ ++m_level;
+}
+
+inline void CMerasmus::ResetLevel( void )
+{
+ m_level = 1;
+}
+
+inline void CMerasmus::DBG_SetLevel( int nLevel )
+{
+ m_level = nLevel;
+}
+
+inline const Vector &CMerasmus::GetHomePosition( void ) const
+{
+ return m_homePos;
+}
+
+inline const CUtlVector< CHandle<CTFPlayer> >& CMerasmus::GetStartingAttackers() const
+{
+ return m_startingAttackersVector;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+class CMerasmusPathCost : public IPathCost
+{
+public:
+ CMerasmusPathCost( CMerasmus *me )
+ {
+ m_me = me;
+ }
+
+ // return the cost (weighted distance between) of moving from "fromArea" to "area", or -1 if the move is not allowed
+ virtual float operator()( CNavArea *area, CNavArea *fromArea, const CNavLadder *ladder, const CFuncElevator *elevator, float length ) const
+ {
+ if ( fromArea == NULL )
+ {
+ // first area in path, no cost
+ return 0.0f;
+ }
+ else
+ {
+ if ( !m_me->GetLocomotionInterface()->IsAreaTraversable( area ) )
+ {
+ // our locomotor says we can't move here
+ return -1.0f;
+ }
+
+ // compute distance traveled along path so far
+ float dist;
+
+ if ( ladder )
+ {
+ dist = ladder->m_length;
+ }
+ else if ( length > 0.0 )
+ {
+ // optimization to avoid recomputing length
+ dist = length;
+ }
+ else
+ {
+ dist = ( area->GetCenter() - fromArea->GetCenter() ).Length();
+ }
+
+ float cost = dist + fromArea->GetCostSoFar();
+
+ // check height change
+ float deltaZ = fromArea->ComputeAdjacentConnectionHeightChange( area );
+ if ( deltaZ >= m_me->GetLocomotionInterface()->GetStepHeight() )
+ {
+ if ( deltaZ >= m_me->GetLocomotionInterface()->GetMaxJumpHeight() )
+ {
+ // too high to reach
+ return -1.0f;
+ }
+
+ // jumping is slower than flat ground
+ const float jumpPenalty = 5.0f;
+ cost += jumpPenalty * dist;
+ }
+ else if ( deltaZ < -m_me->GetLocomotionInterface()->GetDeathDropHeight() )
+ {
+ // too far to drop
+ return -1.0f;
+ }
+
+ return cost;
+ }
+ }
+
+ CMerasmus *m_me;
+};
+
+
+#endif // MERASMUS_H
diff --git a/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_aoe_attack.cpp b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_aoe_attack.cpp
new file mode 100644
index 0000000..6698be0
--- /dev/null
+++ b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_aoe_attack.cpp
@@ -0,0 +1,248 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//
+//
+//=============================================================================
+#include "cbase.h"
+
+#include "tf_gamerules.h"
+#include "particle_parse.h"
+#include "nav_mesh/tf_nav_area.h"
+#include "nav_mesh/tf_nav_mesh.h"
+#include "tf/halloween/eyeball_boss/teleport_vortex.h"
+#include "tf_weaponbase_grenadeproj.h"
+#include "sceneentity.h"
+
+#include "../merasmus.h"
+#include "merasmus_aoe_attack.h"
+
+//---------------------------------------------------------------------------------------------
+#define MAX_BOMBS_PER_TICK 4
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CMerasmus > CMerasmusAOEAttack::OnStart( CMerasmus *me, Action< CMerasmus > *priorAction )
+{
+ m_aoeStartTimer.Start( 4.f );
+ m_state = AOE_BEGIN;
+
+ me->StartFlying();
+
+ CTFNavArea *pointArea = TheTFNavMesh()->GetControlPointCenterArea( 0 );
+
+ if ( !pointArea )
+ {
+ return Done( "No control point!" );
+ }
+
+ const float surroundRange = 400.f;
+ CollectSurroundingAreas( &m_wanderAreaVector, pointArea, surroundRange, StepHeight, StepHeight );
+
+ if ( m_wanderAreaVector.Count() == 0 )
+ {
+ return Done( "No nav areas near control point!" );
+ }
+
+ me->GetBodyInterface()->StartActivity( ACT_RANGE_ATTACK1 );
+
+ // This is only to play sound since animation doesn't work with the vcd
+ CFmtStr vcdName( "scenes/bot/merasmus/low/bomb_attack_00%d.vcd", RandomInt( 1, 9 ) );
+ InstancedScriptedScene( me, vcdName.Get(), NULL, 0.0f, false, NULL, true );
+
+ m_wanderArea = NULL;
+
+ return Continue();
+}
+
+
+#define BOMB_SCALE 1.0f // 0.85f
+#define BOMB_VERT_VEL 750.0f
+#define BOMB_START_OFFSET Vector( 0.0f, 0.0f, 150.0f )
+
+//---------------------------------------------------------------------------------------------
+void CMerasmusAOEAttack::QueueSingleGrenadeForLaunch( const Vector &vecVelocity )
+{
+ m_vecGrenadesToCreate.AddToTail( MerasmusGrenadeCreateSpec_t( vecVelocity ) );
+}
+
+//---------------------------------------------------------------------------------------------
+void CMerasmusAOEAttack::ClearPendingGrenades()
+{
+ m_vecGrenadesToCreate.RemoveAll();
+}
+
+//---------------------------------------------------------------------------------------------
+void CMerasmusAOEAttack::LaunchPendingGrenades( CMerasmus *me )
+{
+ int nNumGrenadesLeftToCreate = MIN( m_vecGrenadesToCreate.Count(), MAX_BOMBS_PER_TICK );
+ while ( nNumGrenadesLeftToCreate > 0 )
+ {
+ // Create the first one in the list
+ MerasmusGrenadeCreateSpec_t &info = m_vecGrenadesToCreate[0];
+ CMerasmus::CreateMerasmusGrenade( me->WorldSpaceCenter() + BOMB_START_OFFSET, info.m_vecVelocity, me, BOMB_SCALE );
+
+ // Remove the first one in the list
+ m_vecGrenadesToCreate.Remove( 0 );
+
+ --nNumGrenadesLeftToCreate;
+ }
+}
+
+//---------------------------------------------------------------------------------------------
+void CMerasmusAOEAttack::QueueBombRingsForLaunch( CMerasmus *me )
+{
+ ClearPendingGrenades();
+
+ const float bombRingMinHorizVel = 100.0f;
+ const float bombRingMaxHorizVel = 2000.0f;
+
+ QAngle myAngles = me->EyeAngles();
+
+ float deltaVel = bombRingMaxHorizVel - bombRingMinHorizVel;
+ const int ringCount = 2;
+
+ for( int r=0; r<ringCount; ++r )
+ {
+ float u = (float)(r+1)/(float)ringCount;
+
+ float horizVel = bombRingMinHorizVel + u * deltaVel;
+
+// float angleDelta = 10.0f + 20.0f * ( 1.0f - u );
+ float angleDelta = 20.0f + 30.0f * ( 1.0f - u );
+
+ for( float angle=0.0f; angle<360.0f; angle += angleDelta )
+ {
+ Vector forward;
+ AngleVectors( myAngles, &forward );
+
+ Vector vecVelocity( horizVel * forward.x, horizVel * forward.y, BOMB_VERT_VEL );
+
+ QueueSingleGrenadeForLaunch( vecVelocity );
+
+ myAngles.y += angleDelta;
+ }
+ }
+}
+
+
+//---------------------------------------------------------------------------------------------
+void CMerasmusAOEAttack::QueueBombSpokesForLaunch( CMerasmus *me )
+{
+ ClearPendingGrenades();
+
+ const float bombSpokeAngle = 45.0f;
+ const int bombSpokeCount = 4; // 10;
+ const float bombSpokeMinHorizVel = 100.0f;
+ const float bombSpokeMaxHorizVel = 2000.0f;
+
+ float deltaVel = bombSpokeMaxHorizVel - bombSpokeMinHorizVel;
+ float angleDelta = bombSpokeAngle;
+
+ QAngle myAngles = me->EyeAngles();
+
+ for( float angle=0.0f; angle<360.0f; angle += angleDelta )
+ {
+ Vector forward;
+ AngleVectors( myAngles, &forward );
+
+ int spokeCount = bombSpokeCount;
+
+ for( int i=0; i<spokeCount; ++i )
+ {
+ float u = (float)(i+1)/(float)spokeCount;
+
+ float horizVel = bombSpokeMinHorizVel + u * deltaVel;
+
+ Vector vecVelocity( horizVel * forward.x, horizVel * forward.y, BOMB_VERT_VEL );
+
+ QueueSingleGrenadeForLaunch( vecVelocity );
+ }
+
+ myAngles.y += angleDelta;
+ }
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CMerasmus > CMerasmusAOEAttack::Update( CMerasmus *me, float interval )
+{
+ if ( me->IsActivityFinished() )
+ {
+ me->StopAOEAttack();
+
+ return Done();
+ }
+
+ switch ( m_state )
+ {
+ case AOE_BEGIN:
+ {
+ if ( m_aoeStartTimer.IsElapsed() )
+ {
+ m_launchTimer.Start( 0.5f );
+
+ m_state = AOE_FIRING;
+ }
+ }
+ break;
+ case AOE_FIRING:
+ {
+ // Start the AOE particles
+ me->StartAOEAttack();
+
+ CMerasmusFlyingLocomotion *fly = (CMerasmusFlyingLocomotion *)me->GetLocomotionInterface();
+
+ // float up and down a bit
+ fly->SetDesiredAltitude( 175.0f + 25.0f * FastCos( gpGlobals->curtime ) );
+
+ if ( m_launchTimer.IsElapsed() )
+ {
+ if ( RandomInt( 1, 100 ) < 50 )
+ {
+ QueueBombSpokesForLaunch( me );
+ }
+ else
+ {
+ QueueBombRingsForLaunch( me );
+ }
+
+ m_launchTimer.Start( 2.0f );
+ }
+
+ // Go through and launch any pending grenades
+ LaunchPendingGrenades( me );
+
+ // wander among nav areas near the cap point
+ if ( m_wanderTimer.IsElapsed() || m_wanderArea == NULL )
+ {
+ m_wanderTimer.Start( RandomFloat( 1.0f, 3.0f ) );
+
+ m_wanderArea = (CTFNavArea *)m_wanderAreaVector[ RandomInt( 0, m_wanderAreaVector.Count()-1 ) ];
+ }
+
+ if ( m_wanderArea )
+ {
+ Vector flySpot = m_wanderArea->GetCenter();
+ flySpot.z = me->GetAbsOrigin().z;
+
+ me->GetLocomotionInterface()->Approach( flySpot );
+ me->GetLocomotionInterface()->FaceTowards( flySpot );
+ }
+ }
+ break;
+ }
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+void CMerasmusAOEAttack::OnEnd( CMerasmus *me, Action< CMerasmus > *nextAction )
+{
+ me->StopFlying();
+
+ // The animation sometime doesn't turn off the bodygroup correctly. Slam it in code.
+ int staffBodyGroup = me->FindBodygroupByName( "staff" );
+ me->SetBodygroup( staffBodyGroup, 0 );
+
+ me->ResetBombHitCount();
+}
diff --git a/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_aoe_attack.h b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_aoe_attack.h
new file mode 100644
index 0000000..76c04cf
--- /dev/null
+++ b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_aoe_attack.h
@@ -0,0 +1,51 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//
+//
+//=============================================================================
+#ifndef MERASMUS_AOE_ATTACK_H
+#define MERASMUS_AOE_ATTACK_H
+
+class CMerasmusAOEAttack : public Action< CMerasmus >
+{
+public:
+ virtual ActionResult< CMerasmus > OnStart( CMerasmus *me, Action< CMerasmus > *priorAction );
+ virtual ActionResult< CMerasmus > Update( CMerasmus *me, float interval );
+ virtual void OnEnd( CMerasmus *me, Action< CMerasmus > *nextAction );
+
+ virtual const char *GetName( void ) const { return "AOE Attack!"; } // return name of this action
+private:
+ enum AOEState_t
+ {
+ AOE_BEGIN,
+ AOE_FIRING,
+ };
+ AOEState_t m_state;
+
+ CountdownTimer m_aoeStartTimer;
+ CountdownTimer m_launchTimer;
+ CountdownTimer m_flyTimer;
+ CUtlVector< CNavArea * > m_wanderAreaVector;
+ CountdownTimer m_wanderTimer;
+ CTFNavArea *m_wanderArea;
+
+ // To save network perf, we don't create all bombs in a single tick. Rather, we fill up a queue of bombs and distribute the creation over time.
+ // I originally had another property (start position) as part of MerasmusGrenadeCreateSpec_t, but it didn't make sense, since we want to use
+ // his current position when we actually create -- not the position he was at whenever we actually filled the queue with grenades. I'm leaving
+ // the struct here, rather than making m_vecGrenadesToCreate a CUtlVector< Vector >, in case we want to add anything else.
+ struct MerasmusGrenadeCreateSpec_t
+ {
+ MerasmusGrenadeCreateSpec_t( const Vector &v ) : m_vecVelocity( v ) {}
+ Vector m_vecVelocity;
+ };
+ CUtlVector< MerasmusGrenadeCreateSpec_t > m_vecGrenadesToCreate;
+
+ void QueueSingleGrenadeForLaunch( const Vector &vecVelocity ); // Don't call directly - call QueueBombRingsForLaunch() or QueueBombSpokesForLaunch()
+ void ClearPendingGrenades();
+ void LaunchPendingGrenades( CMerasmus *me );
+
+ void QueueBombRingsForLaunch( CMerasmus *me );
+ void QueueBombSpokesForLaunch( CMerasmus *me );
+};
+
+#endif // MERASMUS_TELEPORT_AOE_ATTACK_H
diff --git a/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_attack.cpp b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_attack.cpp
new file mode 100644
index 0000000..9a40377
--- /dev/null
+++ b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_attack.cpp
@@ -0,0 +1,386 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//
+//
+//=============================================================================
+#include "cbase.h"
+
+#include "tf_player.h"
+#include "tf_gamerules.h"
+#include "tf_team.h"
+#include "nav_mesh/tf_nav_area.h"
+#include "particle_parse.h"
+#include "team_control_point_master.h"
+
+#include "../merasmus.h"
+#include "../merasmus_trick_or_treat_prop.h"
+#include "merasmus_attack.h"
+#include "merasmus_staff_attack.h"
+#include "merasmus_stunned.h"
+#include "merasmus_throwing_grenade.h"
+#include "merasmus_zap.h"
+
+//----------------------------------------------------------------------------------
+ActionResult< CMerasmus > CMerasmusAttack::OnStart( CMerasmus *me, Action< CMerasmus > *priorAction )
+{
+ // smooth out the bot's path following by moving toward a point farther down the path
+ m_path.SetMinLookAheadDistance( 100.0f );
+
+ // start animation
+ me->GetBodyInterface()->StartActivity( ACT_MP_RUN_ITEM1 );
+
+ m_attackTimer.Invalidate();
+
+ m_attackTarget = NULL;
+ m_attackTargetFocusTimer.Start( 0.f );
+
+ RandomGrenadeTimer();
+ RandomZapTimer();
+
+ m_homePos = me->GetAbsOrigin();
+ m_homePosRecalcTimer.Start( 3.0f );
+
+ m_bombHeadTimer.Start( 10.f );
+
+ return Continue();
+}
+
+
+//----------------------------------------------------------------------------------
+bool CMerasmusAttack::IsPotentiallyChaseable( CMerasmus *me, CTFPlayer *victim )
+{
+ if ( !victim )
+ {
+ return false;
+ }
+
+ if ( !victim->IsAlive() )
+ {
+ // victim is dead - pick a new one
+ return false;
+ }
+
+ CTFNavArea *victimArea = (CTFNavArea *)victim->GetLastKnownArea();
+ if ( !victimArea || victimArea->HasAttributeTF( TF_NAV_SPAWN_ROOM_BLUE | TF_NAV_SPAWN_ROOM_RED ) )
+ {
+ // unreachable - pick a new victim
+ return false;
+ }
+
+ if ( victim->GetGroundEntity() != NULL )
+ {
+ Vector victimAreaPos;
+ victimArea->GetClosestPointOnArea( victim->GetAbsOrigin(), &victimAreaPos );
+ if ( ( victim->GetAbsOrigin() - victimAreaPos ).AsVector2D().IsLengthGreaterThan( 50.0f ) )
+ {
+ // off the mesh and unreachable - pick a new victim
+ return false;
+ }
+ }
+
+ if ( victim->m_Shared.IsInvulnerable() )
+ {
+ // invulnerable - pick a new victim
+ return false;
+ }
+
+ Vector toHome = m_homePos - victim->GetAbsOrigin();
+ if ( toHome.IsLengthGreaterThan( tf_merasmus_chase_range.GetFloat() ) )
+ {
+ // too far from home - pick a new victim
+ return false;
+ }
+
+ return true;
+}
+
+
+//----------------------------------------------------------------------------------
+void CMerasmusAttack::SelectVictim( CMerasmus *me )
+{
+ if ( IsPotentiallyChaseable( me, m_attackTarget ) && !m_attackTargetFocusTimer.IsElapsed() )
+ {
+ return;
+ }
+
+ // pick a new victim to chase
+ CTFPlayer *newVictim = NULL;
+ CUtlVector< CTFPlayer * > playerVector;
+
+ // collect everyone
+ CollectPlayers( &playerVector, TF_TEAM_RED, COLLECT_ONLY_LIVING_PLAYERS );
+ CollectPlayers( &playerVector, TF_TEAM_BLUE, COLLECT_ONLY_LIVING_PLAYERS, APPEND_PLAYERS );
+
+ float victimRangeSq = FLT_MAX;
+ for( int i=0; i<playerVector.Count(); ++i )
+ {
+ if ( !IsPotentiallyChaseable( me, playerVector[i] ) )
+ {
+ continue;
+ }
+
+ float rangeSq = me->GetRangeSquaredTo( playerVector[i] );
+ if ( rangeSq < victimRangeSq )
+ {
+ newVictim = playerVector[i];
+ victimRangeSq = rangeSq;
+ }
+ }
+
+ if ( newVictim )
+ {
+ // we have a new victim
+ m_attackTargetFocusTimer.Start( tf_merasmus_chase_duration.GetFloat() );
+ }
+
+ m_attackTarget = newVictim;
+}
+
+
+//----------------------------------------------------------------------------------
+ActionResult< CMerasmus > CMerasmusAttack::Update( CMerasmus *me, float interval )
+{
+ if ( !me->IsAlive() )
+ {
+ return Done();
+ }
+
+ if ( me->HasStunTimer() )
+ {
+ return SuspendFor( new CMerasmusStunned, "Stunned!" );
+ }
+
+ SelectVictim( me );
+ RecomputeHomePosition();
+
+ if ( m_attackTarget == NULL )
+ {
+ // go home
+ const float atHomeRange = 50.0f;
+ if ( me->IsRangeGreaterThan( m_homePos, atHomeRange ) )
+ {
+ if ( m_path.GetAge() > 3.0f )
+ {
+ CMerasmusPathCost cost( me );
+ m_path.Compute( me, m_homePos, cost );
+ }
+
+ m_path.Update( me );
+ }
+// else
+// {
+// // at home with nothing to do - taunt!
+// if ( !me->IsMoving() && m_tauntTimer.IsElapsed() )
+// {
+// m_tauntTimer.Start( RandomFloat( 3.0f, 5.0f ) );
+//
+// return SuspendFor( new CMerasmusTaunt, "Taunting because I have nothing to do." );
+// }
+// }
+ }
+ else
+ {
+ // chase after our chase victim
+ const float standAndSwingRange = 100.0f;
+ CTFPlayer *chaseVictim = m_attackTarget;
+
+ if ( me->IsRangeGreaterThan( chaseVictim, standAndSwingRange ) || !me->IsLineOfSightClear( chaseVictim ) )
+ {
+ if ( m_path.GetAge() > 1.0f )
+ {
+ CMerasmusPathCost cost( me );
+ m_path.Compute( me, chaseVictim, cost );
+ }
+
+ m_path.Update( me );
+ }
+ }
+
+ if ( m_bombHeadTimer.IsElapsed() )
+ {
+ // bomb heads last 15 seconds - make sure we don't add more while existing ones are out
+ m_bombHeadTimer.Start( 16.0f );
+ me->BombHeadMode();
+ }
+
+ // swing our axe at our attack target if they are in range
+ if ( m_attackTarget != NULL && m_attackTarget->IsAlive() )
+ {
+ if ( m_zapTimer.IsElapsed() )
+ {
+ RandomZapTimer();
+ return SuspendFor( new CMerasmusZap, "Zap!" );
+ }
+
+ if ( me->IsRangeLessThan( m_attackTarget, tf_merasmus_attack_range.GetFloat() ) )
+ {
+ if ( m_attackTimer.IsElapsed() )
+ {
+ m_attackTimer.Start( 1.f );
+ return SuspendFor( new CMerasmusStaffAttack( m_attackTarget ), "Whack!" );
+ }
+
+ me->GetLocomotionInterface()->FaceTowards( m_attackTarget->WorldSpaceCenter() );
+ }
+
+ if ( m_grenadeTimer.IsElapsed() )
+ {
+ RandomGrenadeTimer();
+ return SuspendFor( new CMerasmusThrowingGrenade( m_attackTarget ), "Fire in the hole!" );
+ }
+ }
+
+ if ( !me->GetBodyInterface()->IsActivity( ACT_MP_RUN_MELEE ) )
+ {
+ me->GetBodyInterface()->StartActivity( ACT_MP_RUN_MELEE );
+ }
+
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+EventDesiredResult< CMerasmus > CMerasmusAttack::OnStuck( CMerasmus *me )
+{
+ // we're stuck - just warp to the our next path goal
+ if ( m_path.GetCurrentGoal() )
+ {
+ me->SetAbsOrigin( m_path.GetCurrentGoal()->pos + Vector( 0, 0, 10.0f ) );
+ }
+
+ return TryContinue( RESULT_TRY );
+}
+
+
+//---------------------------------------------------------------------------------------------
+EventDesiredResult< CMerasmus > CMerasmusAttack::OnContact( CMerasmus *me, CBaseEntity *other, CGameTrace *result )
+{
+ if ( other->IsPlayer() )
+ {
+ CTFPlayer *pTFPlayer = ToTFPlayer( other );
+ if ( pTFPlayer )
+ {
+ if ( pTFPlayer->IsAlive() )
+ {
+ // force attack the thing we bumped into
+ // this prevents us from being stuck on dispensers, for example
+ m_attackTarget = pTFPlayer;
+ m_attackTargetFocusTimer.Start( tf_merasmus_chase_duration.GetFloat() );
+ }
+ }
+ }
+
+ return TryContinue( RESULT_TRY );
+}
+
+
+//---------------------------------------------------------------------------------------------
+void CMerasmusAttack::RecomputeHomePosition( void )
+{
+ if ( !m_homePosRecalcTimer.IsElapsed() )
+ {
+ return;
+ }
+
+ m_homePosRecalcTimer.Reset();
+
+ CTeamControlPoint *contestedPoint = NULL;
+ CTeamControlPointMaster *pMaster = g_hControlPointMasters.Count() ? g_hControlPointMasters[0] : NULL;
+ if ( pMaster )
+ {
+ for( int i=0; i<pMaster->GetNumPoints(); ++i )
+ {
+ contestedPoint = pMaster->GetControlPoint( i );
+ if ( contestedPoint && pMaster->IsInRound( contestedPoint ) )
+ {
+ if ( ObjectiveResource()->GetOwningTeam( contestedPoint->GetPointIndex() ) == TF_TEAM_BLUE )
+ continue;
+
+ // blue are the invaders
+ if ( !TeamplayGameRules()->TeamMayCapturePoint( TF_TEAM_BLUE, contestedPoint->GetPointIndex() ) )
+ continue;
+
+ break;
+ }
+ }
+ }
+
+ if ( contestedPoint )
+ {
+ m_homePos = contestedPoint->GetAbsOrigin();
+ }
+}
+
+
+void CMerasmusAttack::RandomGrenadeTimer()
+{
+ m_grenadeTimer.Start( RandomFloat( 2.f, 3.f ) );
+}
+
+
+void CMerasmusAttack::RandomZapTimer()
+{
+ m_zapTimer.Start( RandomFloat( 3.f, 4.f ) );
+}
+
+
+//---------------------------------------------------------------------------------------------
+//---------------------------------------------------------------------------------------------
+ActionResult< CMerasmus > CMerasmusTaunt::OnStart( CMerasmus *me, Action< CMerasmus > *priorAction )
+{
+ m_timer.Start( 3.0f );
+
+ const char *taunts[] =
+ {
+ "gesture_melee_cheer",
+ "gesture_melee_go",
+ "taunt01", // wave
+ "taunt06", // thriller
+ "taunt_laugh",
+ NULL
+ };
+
+ // count the available taunts
+ int count = 0;
+ while( true )
+ {
+ if ( taunts[ count ] == NULL )
+ break;
+
+ ++count;
+ }
+
+ // pick one and play it
+ int which = RandomInt( 0, count-1 );
+ me->AddGestureSequence( me->LookupSequence( taunts[ which ] ) );
+
+ int staffBodyGroup = me->FindBodygroupByName( "staff" );
+ me->SetBodygroup( staffBodyGroup, 1 );
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CMerasmus > CMerasmusTaunt::Update( CMerasmus *me, float interval )
+{
+ if ( m_timer.IsElapsed() )
+ {
+ return Done();
+ }
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+void CMerasmusTaunt::OnEnd( CMerasmus *me, Action< CMerasmus > *nextAction )
+{
+ // turn the staff back on
+ int staffBodyGroup = me->FindBodygroupByName( "staff" );
+ me->SetBodygroup( staffBodyGroup, 0 );
+}
+
+
+
diff --git a/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_attack.h b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_attack.h
new file mode 100644
index 0000000..dc19cdf
--- /dev/null
+++ b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_attack.h
@@ -0,0 +1,63 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//
+//
+//=============================================================================
+#ifndef MERASMUS_ATTACK_H
+#define MERASMUS_ATTACK_H
+
+//---------------------------------------------------------------------------------------------
+//---------------------------------------------------------------------------------------------
+class CMerasmusAttack : public Action< CMerasmus >
+{
+public:
+ virtual ActionResult< CMerasmus > OnStart( CMerasmus *me, Action< CMerasmus > *priorAction );
+ virtual ActionResult< CMerasmus > Update( CMerasmus *me, float interval );
+
+ virtual EventDesiredResult< CMerasmus > OnStuck( CMerasmus *me );
+ virtual EventDesiredResult< CMerasmus > OnContact( CMerasmus *me, CBaseEntity *other, CGameTrace *result = NULL );
+
+ virtual const char *GetName( void ) const { return "Attack"; } // return name of this action
+
+private:
+ PathFollower m_path;
+
+ Vector m_homePos;
+ CountdownTimer m_homePosRecalcTimer;
+ void RecomputeHomePosition( void );
+
+ CountdownTimer m_attackTimer;
+
+ CountdownTimer m_grenadeTimer;
+ void RandomGrenadeTimer();
+
+ CountdownTimer m_zapTimer;
+ void RandomZapTimer();
+
+ CountdownTimer m_bombHeadTimer;
+ CountdownTimer m_tauntTimer;
+
+ CHandle< CTFPlayer > m_attackTarget; // the victim I'm momentarily attacking
+ CountdownTimer m_attackTargetFocusTimer;
+ bool IsPotentiallyChaseable( CMerasmus *me, CTFPlayer *victim );
+ void SelectVictim( CMerasmus *me );
+};
+
+
+
+//---------------------------------------------------------------------------------------------
+class CMerasmusTaunt : public Action< CMerasmus >
+{
+public:
+ virtual ActionResult< CMerasmus > OnStart( CMerasmus *me, Action< CMerasmus > *priorAction );
+ virtual ActionResult< CMerasmus > Update( CMerasmus *me, float interval );
+ virtual void OnEnd( CMerasmus *me, Action< CMerasmus > *nextAction );
+
+ virtual const char *GetName( void ) const { return "Taunt"; } // return name of this action
+
+private:
+ CountdownTimer m_timer;
+};
+
+
+#endif // MERASMUS_ATTACK_H
diff --git a/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_disguise.cpp b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_disguise.cpp
new file mode 100644
index 0000000..063f959
--- /dev/null
+++ b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_disguise.cpp
@@ -0,0 +1,226 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//
+//
+//=============================================================================
+#include "cbase.h"
+#include "tf_player.h"
+#include "tf_gamerules.h"
+#include "tf_team.h"
+#include "nav_mesh/tf_nav_area.h"
+#include "particle_parse.h"
+#include "player_vs_environment/monster_resource.h"
+
+#include "../merasmus.h"
+#include "../merasmus_trick_or_treat_prop.h"
+#include "merasmus_disguise.h"
+#include "merasmus_reveal.h"
+
+ConVar tf_merasmus_disguise_debug( "tf_merasmus_disguise_debug", "0", FCVAR_CHEAT );
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CMerasmus > CMerasmusDisguise::OnStart( CMerasmus *me, Action< CMerasmus > *priorAction )
+{
+ m_bSpawnedProps = false;
+
+ TryToDisguiseSpawn( me );
+
+ m_flStartRegenTime = gpGlobals->curtime;
+ m_nStartRegenHealth = me->GetHealth();
+
+ me->PlayHighPrioritySound( "Halloween.MerasmusInitiateHiding" );
+ RandomDisguiseTauntTimer();
+
+ m_findPropsFailTimer.Start( 3 );
+
+ // set boss inactive
+ g_pMonsterResource->SetBossState( 1 );
+
+ return Continue();
+}
+
+
+//----------------------------------------------------------------------------------
+ActionResult< CMerasmus > CMerasmusDisguise::Update( CMerasmus *me, float interval )
+{
+ if ( me->ShouldLeave() )
+ {
+ return Done();
+ }
+ me->LeaveWarning();
+
+ if ( !m_bSpawnedProps )
+ {
+ if ( m_findPropsFailTimer.HasStarted() && m_findPropsFailTimer.IsElapsed() )
+ {
+ // Couldn't find props in time - skip
+ return Done();
+ }
+
+ if ( !m_findSpawnPositionTime.IsElapsed() )
+ {
+ // not ready yet
+ return Continue();
+ }
+
+ TryToDisguiseSpawn( me );
+
+ return Continue();
+ }
+
+ if ( m_disguiseTauntTimer.IsElapsed() )
+ {
+ if (RandomInt(0,10) == 0)
+ {
+ me->PlayHighPrioritySound( "Halloween.MerasmusHiddenRare" );
+ }
+ else
+ {
+ me->PlayHighPrioritySound( "Halloween.MerasmusHidden" );
+ }
+
+ RandomDisguiseTauntTimer();
+ }
+
+ // regen health while disguise
+ if ( me->GetHealth() < me->GetMaxHealth() )
+ {
+ float flHealthRegenPerSec = tf_merasmus_health_regen_rate.GetFloat() * me->GetMaxHealth() * ( me->GetLevel() - 1 );
+ int nNewHealth = MIN( ( gpGlobals->curtime - m_flStartRegenTime ) * flHealthRegenPerSec + m_nStartRegenHealth, me->GetMaxHealth() );
+ me->SetHealth( nNewHealth );
+
+ // show Boss' health meter on HUD
+ if ( g_pMonsterResource )
+ {
+ float healthPercentage = (float)me->GetHealth() / (float)me->GetMaxHealth();
+ g_pMonsterResource->SetBossHealthPercentage( healthPercentage );
+ }
+ }
+
+ // should I come out from disguise?
+ if ( me->ShouldReveal() )
+ {
+ return Done( "Revealed!" );
+ }
+
+ return Continue();
+}
+
+
+void CMerasmusDisguise::OnEnd( CMerasmus *me, Action< CMerasmus > *nextAction )
+{
+ if ( me->ShouldLeave() )
+ {
+ me->OnLeaveWhileInPropForm();
+ }
+
+ // set boss active
+ g_pMonsterResource->SetBossState( 0 );
+
+ me->OnRevealed();
+}
+
+
+QAngle GetRandomPropAngles( CTFNavArea* pArea )
+{
+ Vector vNormal;
+ pArea->ComputeNormal( &vNormal );
+ Vector vForward = pArea->GetRandomPoint() - pArea->GetCenter();
+ QAngle qAngles;
+ VectorAngles( vForward, vNormal, qAngles );
+
+ return qAngles;
+}
+
+
+void CMerasmusDisguise::TryToDisguiseSpawn( CMerasmus *me )
+{
+ m_findSpawnPositionTime.Start( 1 );
+
+ // face towards a nearby player
+ CUtlVector< CTFPlayer * > playerVector;
+ CollectPlayers( &playerVector, TF_TEAM_RED, COLLECT_ONLY_LIVING_PLAYERS );
+ CollectPlayers( &playerVector, TF_TEAM_BLUE, COLLECT_ONLY_LIVING_PLAYERS, APPEND_PLAYERS );
+
+ // pick a random spot
+ CUtlVector< CTFNavArea * > candidateAreaVector;
+ for( int i=0; i<TheNavAreas.Count(); ++i )
+ {
+ CTFNavArea *area = (CTFNavArea *)TheNavAreas[i];
+
+ if ( !area->HasFuncNavPrefer() )
+ {
+ // don't spawn outside nav prefer
+ continue;
+ }
+
+ // don't use small nav areas
+ const float goodSize = 150.f;
+ if ( area->GetSizeX() < goodSize || area->GetSizeY() < goodSize )
+ {
+ continue;
+ }
+
+ // don't use area containing player
+ if ( area->GetPlayerCount( TF_TEAM_BLUE ) || area->GetPlayerCount( TF_TEAM_RED ) )
+ {
+ continue;
+ }
+
+ // don't use slope area
+// Vector vNormal;
+// area->ComputeNormal( &vNormal );
+// if ( vNormal.z < 0.9f )
+// {
+// continue;
+// }
+
+ candidateAreaVector.AddToTail( area );
+ }
+
+ if ( candidateAreaVector.Count() == 0 )
+ {
+ // no place to spawn (!)
+ return;
+ }
+
+ // spread out the area
+ CUtlVector< CTFNavArea * > spawnAreaVector;
+ SelectSeparatedShuffleSet< CTFNavArea >( 10, 500.f, candidateAreaVector, &spawnAreaVector );
+
+ if ( spawnAreaVector.Count() == 0 )
+ {
+ // no place to spawn (!)
+ return;
+ }
+
+ if ( tf_merasmus_disguise_debug.GetBool() )
+ {
+ for ( int i=0; i<spawnAreaVector.Count(); ++i )
+ {
+ // draw all potential areas
+ spawnAreaVector[i]->DrawFilled( 0, 255, 0, 0, 30.f );
+ }
+ }
+
+ // spawn random props
+ int nRandomTrickOrTreatProps = spawnAreaVector.Count();
+ for ( int i=0; i<nRandomTrickOrTreatProps; ++i )
+ {
+ int propSpawnID = RandomInt( 0, spawnAreaVector.Count()-1 );
+
+ CTFMerasmusTrickOrTreatProp* pFakeProp = CTFMerasmusTrickOrTreatProp::Create( spawnAreaVector[ propSpawnID ]->GetCenter(), GetRandomPropAngles( spawnAreaVector[ propSpawnID ] ) );
+ me->AddFakeProp( pFakeProp );
+
+ spawnAreaVector.FastRemove( propSpawnID );
+ }
+
+ me->OnDisguise();
+ m_bSpawnedProps = true;
+}
+
+
+void CMerasmusDisguise::RandomDisguiseTauntTimer()
+{
+ m_disguiseTauntTimer.Start( RandomFloat( 10.f, 25.f ) );
+}
diff --git a/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_disguise.h b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_disguise.h
new file mode 100644
index 0000000..bfed85f
--- /dev/null
+++ b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_disguise.h
@@ -0,0 +1,33 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//
+//
+//=============================================================================
+#ifndef MERASMUS_DISGUISE_H
+#define MERASMUS_DISGUISE_H
+
+//---------------------------------------------------------------------------------------------
+//---------------------------------------------------------------------------------------------
+class CMerasmusDisguise : public Action< CMerasmus >
+{
+public:
+ virtual ActionResult< CMerasmus > OnStart( CMerasmus *me, Action< CMerasmus > *priorAction );
+ virtual ActionResult< CMerasmus > Update( CMerasmus *me, float interval );
+ virtual void OnEnd( CMerasmus *me, Action< CMerasmus > *nextAction );
+ virtual const char *GetName( void ) const { return "Disguise"; } // return name of this action
+
+private:
+ void TryToDisguiseSpawn( CMerasmus *me );
+ CountdownTimer m_findPropsFailTimer;
+ CountdownTimer m_findSpawnPositionTime;
+ bool m_bSpawnedProps;
+
+ void RandomDisguiseTauntTimer();
+ CountdownTimer m_disguiseTauntTimer;
+
+ float m_flStartRegenTime;
+ int m_nStartRegenHealth;
+};
+
+
+#endif // MERASMUS_DISGUISE_H
diff --git a/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_dying.cpp b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_dying.cpp
new file mode 100644
index 0000000..216ca92
--- /dev/null
+++ b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_dying.cpp
@@ -0,0 +1,60 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//
+//
+//=============================================================================
+#include "cbase.h"
+
+#include "particle_parse.h"
+#include "tf_gamerules.h"
+
+#include "../merasmus.h"
+#include "merasmus_dying.h"
+#include "tf/halloween/eyeball_boss/teleport_vortex.h"
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CMerasmus > CMerasmusDying::OnStart( CMerasmus *me, Action< CMerasmus > *priorAction )
+{
+ me->GetBodyInterface()->StartActivity( ACT_DIESIMPLE );
+ me->PlayHighPrioritySound( "Halloween.MerasmusBanish" );
+ TFGameRules()->BroadcastSound( 255, "Halloween.Merasmus_Death" );
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CMerasmus > CMerasmusDying::Update( CMerasmus *me, float interval )
+{
+ if ( me->IsActivityFinished() )
+ {
+ me->Break();
+ DispatchParticleEffect( "merasmus_spawn", me->GetAbsOrigin(), me->GetAbsAngles() );
+
+ IGameEvent *event = gameeventmanager->CreateEvent( "merasmus_killed" );
+ if ( event )
+ {
+ event->SetInt( "level", me->GetLevel() );
+ gameeventmanager->FireEvent( event );
+ }
+ me->TriggerLogicRelay( "boss_dead_relay" );
+
+ // create vortex to loot
+ CTeleportVortex *vortex = (CTeleportVortex *)CBaseEntity::Create( "teleport_vortex", me->WorldSpaceCenter(), vec3_angle );
+ if ( vortex )
+ {
+ vortex->SetupVortex( true, true );
+ }
+
+ me->GainLevel();
+
+ me->StartRespawnTimer();
+
+ UTIL_Remove( me );
+
+ return Done();
+ }
+
+ return Continue();
+}
+
diff --git a/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_dying.h b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_dying.h
new file mode 100644
index 0000000..97e888c
--- /dev/null
+++ b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_dying.h
@@ -0,0 +1,20 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//
+//
+//=============================================================================
+#ifndef MERASMUS_DYING_H
+#define MERASMUS_DYING_H
+
+
+//---------------------------------------------------------------------------------------------
+class CMerasmusDying : public Action< CMerasmus >
+{
+public:
+ virtual ActionResult< CMerasmus > OnStart( CMerasmus *me, Action< CMerasmus > *priorAction );
+ virtual ActionResult< CMerasmus > Update( CMerasmus *me, float interval );
+ virtual const char *GetName( void ) const { return "Dying"; } // return name of this action
+};
+
+
+#endif // MERASMUS_DYING_H
diff --git a/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_reveal.cpp b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_reveal.cpp
new file mode 100644
index 0000000..1dc7a62
--- /dev/null
+++ b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_reveal.cpp
@@ -0,0 +1,38 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//
+//
+//=============================================================================
+#include "cbase.h"
+#include "tf_player.h"
+#include "tf_gamerules.h"
+#include "tf_team.h"
+#include "nav_mesh/tf_nav_area.h"
+#include "particle_parse.h"
+
+#include "../merasmus.h"
+#include "merasmus_reveal.h"
+#include "merasmus_attack.h"
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CMerasmus > CMerasmusReveal::OnStart( CMerasmus *me, Action< CMerasmus > *priorAction )
+{
+ me->OnRevealed(false);
+
+ me->GetBodyInterface()->StartActivity( ACT_SHIELD_UP );
+
+ return Continue();
+}
+
+
+//----------------------------------------------------------------------------------
+ActionResult< CMerasmus > CMerasmusReveal::Update( CMerasmus *me, float interval )
+{
+ if ( me->IsActivityFinished() )
+ {
+ return ChangeTo( new CMerasmusAttack, "Here I come!" );
+ }
+
+ return Continue();
+}
diff --git a/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_reveal.h b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_reveal.h
new file mode 100644
index 0000000..de56951
--- /dev/null
+++ b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_reveal.h
@@ -0,0 +1,20 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//
+//
+//=============================================================================
+#ifndef MERASMUS_EMERGE_H
+#define MERASMUS_EMERGE_H
+
+//---------------------------------------------------------------------------------------------
+//---------------------------------------------------------------------------------------------
+class CMerasmusReveal : public Action< CMerasmus >
+{
+public:
+ virtual ActionResult< CMerasmus > OnStart( CMerasmus *me, Action< CMerasmus > *priorAction );
+ virtual ActionResult< CMerasmus > Update( CMerasmus *me, float interval );
+ virtual const char *GetName( void ) const { return "Reveal"; } // return name of this action
+};
+
+
+#endif // MERASMUS_EMERGE_H
diff --git a/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_staff_attack.cpp b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_staff_attack.cpp
new file mode 100644
index 0000000..b15e4f7
--- /dev/null
+++ b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_staff_attack.cpp
@@ -0,0 +1,117 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//
+//
+//=============================================================================
+#include "cbase.h"
+
+#include "tf_gamerules.h"
+#include "tf_player.h"
+
+#include "../merasmus.h"
+#include "merasmus_staff_attack.h"
+#include "merasmus_stunned.h"
+
+CMerasmusStaffAttack::CMerasmusStaffAttack( CTFPlayer* pTarget )
+{
+ m_hTarget = pTarget;
+}
+
+
+ActionResult< CMerasmus > CMerasmusStaffAttack::OnStart( CMerasmus *me, Action< CMerasmus > *priorAction )
+{
+ // smooth out the bot's path following by moving toward a point farther down the path
+ m_path.SetMinLookAheadDistance( 100.0f );
+
+ int iLayer = me->AddGesture( ACT_MP_ATTACK_STAND_MELEE );
+ float flDuration = me->GetLayerDuration( iLayer );
+ m_staffSwingTimer.Start( flDuration );
+ m_hitTimer.Start( 0.5f * flDuration );
+
+ if ( RandomInt( 0, 2 ) == 0 )
+ {
+ CPVSFilter filter( me->WorldSpaceCenter() );
+ if ( RandomInt( 1, 5 ) == 1 )
+ {
+ me->PlayLowPrioritySound( filter, "Halloween.MerasmusStaffAttackRare" );
+ }
+ else
+ {
+ me->PlayLowPrioritySound( filter, "Halloween.MerasmusStaffAttack" );
+ }
+ }
+
+ return Continue();
+}
+
+
+ActionResult< CMerasmus > CMerasmusStaffAttack::Update( CMerasmus *me, float interval )
+{
+ // Interupt if stunned
+ if ( me->HasStunTimer() )
+ {
+ return ChangeTo( new CMerasmusStunned, "Stun Interupt!" );
+ }
+
+ if ( m_hitTimer.HasStarted() && m_hitTimer.IsElapsed() )
+ {
+ m_hitTimer.Invalidate();
+
+ if ( m_hTarget != NULL )
+ {
+ Vector forward;
+ me->GetVectors( &forward, NULL, NULL );
+
+ Vector toVictim = m_hTarget->WorldSpaceCenter() - me->WorldSpaceCenter();
+ toVictim.NormalizeInPlace();
+
+ // looser tolerance as victim gets closer
+ const float closeRange = 100.0f;
+ float range = me->GetRangeTo( m_hTarget );
+ float closeness = ( range < closeRange ) ? 0.0f : ( range - closeRange ) / ( tf_merasmus_attack_range.GetFloat() - closeRange );
+ float hitAngle = 0.0f + closeness * 0.27f;
+
+ if ( DotProduct( forward, toVictim ) > hitAngle )
+ {
+ if ( me->IsRangeLessThan( m_hTarget, 0.9f * tf_merasmus_attack_range.GetFloat() ) )
+ {
+ if ( me->IsLineOfSightClear( m_hTarget ) )
+ {
+ // CHOP!
+ CTakeDamageInfo info( me, me, 70, DMG_CLUB, TF_DMG_CUSTOM_MERASMUS_DECAPITATION );
+ CalculateMeleeDamageForce( &info, toVictim, me->WorldSpaceCenter(), 5.0f );
+ m_hTarget->TakeDamage( info );
+
+ CPVSFilter filter( me->WorldSpaceCenter() );
+ me->PlayLowPrioritySound( filter, "Halloween.HeadlessBossAxeHitFlesh" );
+
+ me->PushPlayer( m_hTarget, 500.f );
+ }
+ }
+ }
+ }
+ }
+
+ if ( m_hTarget )
+ {
+ if ( me->IsRangeGreaterThan( m_hTarget, 100.f ) || !me->IsLineOfSightClear( m_hTarget ) )
+ {
+ if ( m_path.GetAge() > 1.0f )
+ {
+ CMerasmusPathCost cost( me );
+ m_path.Compute( me, m_hTarget, cost );
+ }
+
+ m_path.Update( me );
+ }
+
+ me->GetLocomotionInterface()->FaceTowards( m_hTarget->WorldSpaceCenter() );
+ }
+
+ if ( m_staffSwingTimer.IsElapsed() )
+ {
+ return Done();
+ }
+
+ return Continue();
+}
diff --git a/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_staff_attack.h b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_staff_attack.h
new file mode 100644
index 0000000..60629fc
--- /dev/null
+++ b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_staff_attack.h
@@ -0,0 +1,28 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//
+//
+//=============================================================================
+#ifndef MERASMUS_STAFF_ATTACK_H
+#define MERASMUS_STAFF_ATTACK_H
+
+//---------------------------------------------------------------------------------------------
+//---------------------------------------------------------------------------------------------
+class CMerasmusStaffAttack : public Action< CMerasmus >
+{
+public:
+ CMerasmusStaffAttack( CTFPlayer* pTarget );
+ virtual ActionResult< CMerasmus > OnStart( CMerasmus *me, Action< CMerasmus > *priorAction );
+ virtual ActionResult< CMerasmus > Update( CMerasmus *me, float interval );
+
+ virtual const char *GetName( void ) const { return "Staff Attack"; } // return name of this action
+
+private:
+ CountdownTimer m_staffSwingTimer;
+ CountdownTimer m_hitTimer;
+ CHandle< CTFPlayer > m_hTarget;
+
+ PathFollower m_path;
+};
+
+#endif // MERASMUS_STAFF_ATTACK_H \ No newline at end of file
diff --git a/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_stunned.cpp b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_stunned.cpp
new file mode 100644
index 0000000..8efc321
--- /dev/null
+++ b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_stunned.cpp
@@ -0,0 +1,91 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//
+//
+//=============================================================================
+#include "cbase.h"
+
+#include "../merasmus.h"
+#include "merasmus_stunned.h"
+#include "merasmus_teleport.h"
+
+#include "tf_player.h"
+
+ActionResult< CMerasmus > CMerasmusStunned::OnStart( CMerasmus *me, Action< CMerasmus > *priorAction )
+{
+ m_nStunStage = STUN_BEGIN;
+
+ int iLayer = me->AddGesture( ACT_MP_STUN_BEGIN );
+ float flDuration = me->GetLayerDuration( iLayer );
+ m_stunAnimationTimer.Start( flDuration );
+
+ me->OnBeginStun();
+
+ return Continue();
+}
+
+
+ActionResult< CMerasmus > CMerasmusStunned::Update( CMerasmus *me, float interval )
+{
+ // finished?
+ if ( m_nStunStage == STUN_END && m_stunFinishTimer.IsElapsed() )
+ {
+ if ( me->ShouldDisguise() )
+ {
+ return Done();
+ }
+
+ if ( me->GetBombHitCount() >= 3 )
+ {
+ return ChangeTo( new CMerasmusTeleport( true, true ), "Teleport AOE!" );
+ }
+ else
+ {
+ return ChangeTo( new CMerasmusTeleport( false, false ), "Teleport to new area!" );
+ }
+ }
+
+ if ( m_stunAnimationTimer.IsElapsed() )
+ {
+ bool bStunned = me->HasStunTimer();
+
+ // reset animation if stunned
+ if ( bStunned )
+ {
+ m_nStunStage = STUN_BEGIN;
+ }
+
+ switch ( m_nStunStage )
+ {
+ case STUN_BEGIN:
+ {
+ int iLayer = me->AddGesture( ACT_MP_STUN_MIDDLE );
+ float flDuration = me->GetLayerDuration( iLayer );
+ m_stunAnimationTimer.Start( flDuration );
+ if ( !bStunned )
+ {
+ m_nStunStage = STUN_MID;
+ }
+ }
+ break;
+ case STUN_MID:
+ {
+ int iLayer = me->AddGesture( ACT_MP_STUN_END );
+ float flDuration = me->GetLayerDuration( iLayer );
+ m_stunAnimationTimer.Start( flDuration );
+
+ m_nStunStage = STUN_END;
+ m_stunFinishTimer.Start( flDuration + 0.5f );
+ }
+ break;
+ }
+ }
+
+ return Continue();
+}
+
+
+void CMerasmusStunned::OnEnd( CMerasmus *me, Action< CMerasmus > *nextAction )
+{
+ me->OnEndStun();
+}
diff --git a/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_stunned.h b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_stunned.h
new file mode 100644
index 0000000..4f02145
--- /dev/null
+++ b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_stunned.h
@@ -0,0 +1,29 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//
+//
+//=============================================================================
+#ifndef TF_MERASMUS_STUNNED_H
+#define TF_MERASMUS_STUNNED_H
+
+class CMerasmusStunned : public Action< CMerasmus >
+{
+public:
+ virtual ActionResult< CMerasmus > OnStart( CMerasmus *me, Action< CMerasmus > *priorAction );
+ virtual ActionResult< CMerasmus > Update( CMerasmus *me, float interval );
+ virtual void OnEnd( CMerasmus *me, Action< CMerasmus > *nextAction );
+
+ virtual const char *GetName( void ) const { return "Stunned!"; } // return name of this action
+private:
+ enum StunStage_t
+ {
+ STUN_BEGIN,
+ STUN_MID,
+ STUN_END
+ };
+ StunStage_t m_nStunStage;
+ CountdownTimer m_stunAnimationTimer;
+ CountdownTimer m_stunFinishTimer;
+};
+
+#endif //TF_MERASMUS_STUNNED_H
diff --git a/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_teleport.cpp b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_teleport.cpp
new file mode 100644
index 0000000..538b77f
--- /dev/null
+++ b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_teleport.cpp
@@ -0,0 +1,189 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//
+//
+//=============================================================================
+#include "cbase.h"
+
+#include "particle_parse.h"
+#include "tf/halloween/eyeball_boss/teleport_vortex.h"
+#include "player_vs_environment/monster_resource.h"
+#include "tf_gamerules.h"
+#include "nav_mesh/tf_nav_area.h"
+
+#include "../merasmus.h"
+#include "merasmus_teleport.h"
+#include "merasmus_aoe_attack.h"
+
+
+CMerasmusTeleport::CMerasmusTeleport( bool bShouldAOE, bool bGoToCap )
+ : m_bShouldAOE( bShouldAOE ), m_bShouldGoToCap( bGoToCap )
+{
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CMerasmus > CMerasmusTeleport::OnStart( CMerasmus *me, Action< CMerasmus > *priorAction )
+{
+ // teleport out
+ m_state = TELEPORTING_OUT;
+ me->GetBodyInterface()->StartActivity( ACT_SHIELD_DOWN );
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CMerasmus > CMerasmusTeleport::Update( CMerasmus *me, float interval )
+{
+ if ( me->IsActivityFinished() )
+ {
+ switch( m_state )
+ {
+ case TELEPORTING_OUT:
+ {
+ DispatchParticleEffect( "merasmus_tp", me->WorldSpaceCenter(), me->GetAbsAngles() );
+
+ me->AddEffects( EF_NOINTERP | EF_NODRAW );
+
+ me->SetAbsOrigin( GetTeleportPosition( me ) );
+
+ // wait on the other side for a moment
+ m_state = TELEPORTING_IN;
+ }
+ break;
+
+ case TELEPORTING_IN:
+ {
+ me->RemoveEffects( EF_NOINTERP | EF_NODRAW );
+
+ DispatchParticleEffect( "merasmus_tp", me->WorldSpaceCenter(), me->GetAbsAngles() );
+
+ me->GetBodyInterface()->StartActivity( ACT_SHIELD_UP );
+
+ m_state = DONE;
+ }
+ break;
+
+ case DONE:
+ {
+ if ( m_bShouldAOE )
+ {
+ m_bShouldAOE = false;
+ return SuspendFor( new CMerasmusAOEAttack, "AOE Attack!" );
+ }
+ }
+ return Done();
+ }
+ }
+
+ return Continue();
+}
+
+
+Vector CMerasmusTeleport::GetTeleportPosition( CMerasmus *me ) const
+{
+ Vector vGroundOffset( 0, 0, 75.0f );
+ if ( m_bShouldGoToCap )
+ {
+ return me->GetHomePosition() + vGroundOffset;
+ }
+ else
+ {
+ // pick a random spot
+ const float goodSize = 100.f;
+ CUtlVector< CTFNavArea * > spawnAreaVector;
+ for( int i=0; i<TheNavAreas.Count(); ++i )
+ {
+ CTFNavArea *area = (CTFNavArea *)TheNavAreas[i];
+
+ if ( !area->HasFuncNavPrefer() )
+ {
+ // don't spawn outside nav prefer
+ continue;
+ }
+
+ // don't use small nav areas
+ if ( area->GetSizeX() < goodSize || area->GetSizeY() < goodSize )
+ {
+ continue;
+ }
+
+ // don't use area containing player
+ if ( area->GetPlayerCount( TF_TEAM_BLUE ) || area->GetPlayerCount( TF_TEAM_RED ) )
+ {
+ continue;
+ }
+
+ spawnAreaVector.AddToTail( area );
+ }
+
+ if ( spawnAreaVector.Count() )
+ {
+ int which = RandomInt( 0, spawnAreaVector.Count() - 1 );
+ return spawnAreaVector[ which ]->GetCenter();
+ }
+ else
+ {
+ return me->GetHomePosition() + vGroundOffset;
+ }
+ }
+}
+
+
+//---------------------------------------------------------------------------------------------
+//---------------------------------------------------------------------------------------------
+ActionResult< CMerasmus > CMerasmusEscape::OnStart( CMerasmus *me, Action< CMerasmus > *priorAction )
+{
+ me->GetBodyInterface()->StartActivity( ACT_FLY );
+
+ if (RandomInt(0,10) == 0)
+ {
+ me->PlayHighPrioritySound( "Halloween.MerasmusDepartRare" );
+ }
+ else
+ {
+ me->PlayHighPrioritySound( "Halloween.MerasmusDepart" );
+ }
+
+ UTIL_LogPrintf( "HALLOWEEN: merasmus_escaped (max_dps %3.2f) (health %d) (level %d)\n", me->GetMaxInjuryRate(), me->GetHealth(), me->GetLevel() );
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CMerasmus > CMerasmusEscape::Update( CMerasmus *me, float interval )
+{
+ if ( me->IsActivityFinished() )
+ {
+ Vector vPos;
+ QAngle qAngles;
+ me->GetAttachment( "effect_robe", vPos, qAngles );
+ DispatchParticleEffect( "merasmus_tp", vPos, qAngles );
+
+ if ( g_pMonsterResource )
+ {
+ g_pMonsterResource->HideBossHealthMeter();
+ }
+
+ IGameEvent *event = gameeventmanager->CreateEvent( "merasmus_escaped" );
+ if ( event )
+ {
+ event->SetInt( "level", me->GetLevel() );
+ gameeventmanager->FireEvent( event );
+ }
+ me->TriggerLogicRelay( "boss_exit_relay" );
+
+ // reset back to normal level
+ me->ResetLevel();
+
+ me->StartRespawnTimer();
+
+ UTIL_Remove( me );
+
+ return Done();
+ }
+
+ return Continue();
+}
diff --git a/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_teleport.h b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_teleport.h
new file mode 100644
index 0000000..1bd5d67
--- /dev/null
+++ b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_teleport.h
@@ -0,0 +1,49 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//
+//
+//=============================================================================
+#ifndef MERASMUS_TELEPORT_H
+#define MERASMUS_TELEPORT_H
+
+//---------------------------------------------------------------------------------------------
+//---------------------------------------------------------------------------------------------
+class CMerasmusTeleport : public Action< CMerasmus >
+{
+public:
+ CMerasmusTeleport( bool bShouldAOE, bool bGoToCap );
+
+ virtual ActionResult< CMerasmus > OnStart( CMerasmus *me, Action< CMerasmus > *priorAction );
+ virtual ActionResult< CMerasmus > Update( CMerasmus *me, float interval );
+
+ virtual const char *GetName( void ) const { return "Teleport"; } // return name of this action
+
+private:
+ enum TeleportState
+ {
+ TELEPORTING_OUT,
+ TELEPORTING_IN,
+ DONE
+ };
+ TeleportState m_state;
+
+ bool m_bShouldAOE;
+ bool m_bShouldGoToCap;
+
+ Vector GetTeleportPosition( CMerasmus *me ) const;
+};
+
+
+//---------------------------------------------------------------------------------------------
+//---------------------------------------------------------------------------------------------
+class CMerasmusEscape : public Action< CMerasmus >
+{
+public:
+ virtual ActionResult< CMerasmus > OnStart( CMerasmus *me, Action< CMerasmus > *priorAction );
+ virtual ActionResult< CMerasmus > Update( CMerasmus *me, float interval );
+
+ virtual const char *GetName( void ) const { return "Escape"; } // return name of this action
+};
+
+
+#endif // MERASMUS_TELEPORT_H
diff --git a/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_throwing_grenade.cpp b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_throwing_grenade.cpp
new file mode 100644
index 0000000..f82fdc7
--- /dev/null
+++ b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_throwing_grenade.cpp
@@ -0,0 +1,159 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//
+//
+//=============================================================================
+#include "cbase.h"
+
+#include "tf_player.h"
+#include "tf_gamerules.h"
+#include "particle_parse.h"
+
+#include "../merasmus.h"
+#include "merasmus_throwing_grenade.h"
+#include "merasmus_stunned.h"
+
+CMerasmusThrowingGrenade::CMerasmusThrowingGrenade( CTFPlayer* pTarget )
+{
+ m_hTarget = pTarget;
+}
+
+
+ActionResult< CMerasmus > CMerasmusThrowingGrenade::OnStart( CMerasmus *me, Action< CMerasmus > *priorAction )
+{
+ if ( m_hTarget == NULL )
+ {
+ return Done( "No Target" );
+ }
+
+ if ( !me->IsLineOfSightClear( m_hTarget ) )
+ {
+ CUtlVector< CTFPlayer * > playerVector;
+
+ // collect everyone
+ CollectPlayers( &playerVector, TF_TEAM_RED, COLLECT_ONLY_LIVING_PLAYERS );
+ CollectPlayers( &playerVector, TF_TEAM_BLUE, COLLECT_ONLY_LIVING_PLAYERS, APPEND_PLAYERS );
+
+ CUtlVector< CTFPlayer * > newTargetVector;
+ for ( int i=0; i<playerVector.Count(); ++i )
+ {
+ if ( playerVector[i] == m_hTarget )
+ {
+ continue;
+ }
+
+ if ( !me->IsLineOfSightClear( playerVector[i] ) )
+ {
+ continue;
+ }
+
+ newTargetVector.AddToTail( playerVector[i] );
+ }
+
+ if ( newTargetVector.Count() == 0 )
+ {
+ m_hTarget = NULL;
+ }
+ else
+ {
+ int which = RandomInt( 0, newTargetVector.Count() - 1 );
+ m_hTarget = newTargetVector[ which ];
+ }
+ }
+
+ if ( m_hTarget == NULL )
+ {
+ return Done( "No Target" );
+ }
+
+ me->GetLocomotionInterface()->FaceTowards( m_hTarget->WorldSpaceCenter() );
+ int iLayer = me->AddGesture( ACT_MP_ATTACK_STAND_ITEM1 );
+ float flDuration = me->GetLayerDuration( iLayer );
+ m_throwTimer.Start( flDuration );
+
+ // we want to release the grenade mid-animation
+ m_releaseGrenadeTimer.Start( 0.25f );
+
+ // hide his staff
+ int staffBodyGroup = me->FindBodygroupByName( "staff" );
+ me->SetBodygroup( staffBodyGroup, 2 );
+
+ // smooth out the bot's path following by moving toward a point farther down the path
+ m_path.SetMinLookAheadDistance( 100.0f );
+
+ return Continue();
+}
+
+
+ActionResult< CMerasmus > CMerasmusThrowingGrenade::Update( CMerasmus *me, float interval )
+{
+ // Interupt if stunned
+ if ( me->HasStunTimer() )
+ {
+ return ChangeTo( new CMerasmusStunned, "Stun Interupt!" );
+ }
+
+ if ( m_releaseGrenadeTimer.HasStarted() && m_releaseGrenadeTimer.IsElapsed() )
+ {
+ m_releaseGrenadeTimer.Invalidate();
+
+ DispatchParticleEffect( "merasmus_shoot", PATTACH_ABSORIGIN_FOLLOW, me, "effect_hand_R" );
+
+ Vector vPos;
+ QAngle qAngles;
+ me->GetAttachment( "effect_hand_R", vPos, qAngles );
+
+ Vector vForward, vRight, vUp;
+ AngleVectors( me->EyeAngles(), &vForward, &vRight, &vUp );
+ float flLaunchSpeed = RandomFloat( 1500.f, 2000.f );
+ Vector vecVelocity = ( vForward * flLaunchSpeed ) + ( vUp * 200.0f ) + ( RandomFloat( -10.0f, 10.0f ) * vRight ) + ( RandomFloat( -10.0f, 10.0f ) * vUp );
+ CTFWeaponBaseGrenadeProj* pGrenade = CMerasmus::CreateMerasmusGrenade( vPos, vecVelocity, me );
+ if ( pGrenade )
+ {
+ if ( RandomInt( 0, 6 ) == 0 )
+ {
+ CPVSFilter filter( me->WorldSpaceCenter() );
+ if ( RandomInt(1,10) == 1 )
+ {
+ me->PlayLowPrioritySound( filter, "Halloween.MerasmusGrenadeThrowRare" );
+ }
+ else
+ {
+ me->PlayLowPrioritySound( filter, "Halloween.MerasmusGrenadeThrow" );
+ }
+ }
+ }
+ }
+
+ if ( m_hTarget )
+ {
+ if ( me->IsRangeGreaterThan( m_hTarget, 100.f ) || !me->IsLineOfSightClear( m_hTarget ) )
+ {
+ if ( m_path.GetAge() > 1.0f )
+ {
+ CMerasmusPathCost cost( me );
+ m_path.Compute( me, m_hTarget, cost );
+ }
+
+ m_path.Update( me );
+ }
+
+ me->GetLocomotionInterface()->FaceTowards( m_hTarget->WorldSpaceCenter() );
+ }
+
+
+ if ( m_throwTimer.IsElapsed() )
+ {
+ return Done( "Fire in the hole!" );
+ }
+
+ return Continue();
+}
+
+
+void CMerasmusThrowingGrenade::OnEnd( CMerasmus *me, Action< CMerasmus > *nextAction )
+{
+ // turn his staff back on
+ int staffBodyGroup = me->FindBodygroupByName( "staff" );
+ me->SetBodygroup( staffBodyGroup, 0 );
+}
diff --git a/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_throwing_grenade.h b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_throwing_grenade.h
new file mode 100644
index 0000000..7a508cf
--- /dev/null
+++ b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_throwing_grenade.h
@@ -0,0 +1,27 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//
+//
+//=============================================================================
+#ifndef MERASMUS_ROCKET_H
+#define MERASMUS_ROCKET_H
+
+class CMerasmusThrowingGrenade : public Action< CMerasmus >
+{
+public:
+ CMerasmusThrowingGrenade( CTFPlayer* pTarget );
+
+ virtual ActionResult< CMerasmus > OnStart( CMerasmus *me, Action< CMerasmus > *priorAction );
+ virtual ActionResult< CMerasmus > Update( CMerasmus *me, float interval );
+ virtual void OnEnd( CMerasmus *me, Action< CMerasmus > *nextAction );
+
+ virtual const char *GetName( void ) const { return "Rocket"; } // return name of this action
+private:
+ CHandle< CTFPlayer > m_hTarget;
+ CountdownTimer m_throwTimer;
+ CountdownTimer m_releaseGrenadeTimer;
+
+ PathFollower m_path;
+};
+
+#endif // MERASMUS_ROCKET_H
diff --git a/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_zap.cpp b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_zap.cpp
new file mode 100644
index 0000000..1be6822
--- /dev/null
+++ b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_zap.cpp
@@ -0,0 +1,73 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//
+//
+//=============================================================================
+#include "cbase.h"
+
+#include "../merasmus.h"
+#include "merasmus_zap.h"
+#include "merasmus_stunned.h"
+
+ActionResult< CMerasmus > CMerasmusZap::OnStart( CMerasmus *me, Action< CMerasmus > *priorAction )
+{
+ me->GetBodyInterface()->StartActivity( ACT_RANGE_ATTACK2 );
+ m_zapTimer.Start( 1.3f );
+
+ m_spellType = SpellType_t( RandomInt( 0, SPELL_COUNT - 1 ) );
+ PlayCastSound( me );
+
+ return Continue();
+}
+
+
+ActionResult< CMerasmus > CMerasmusZap::Update( CMerasmus *me, float interval )
+{
+ // Interupt if stunned
+ if ( me->HasStunTimer() )
+ {
+ return ChangeTo( new CMerasmusStunned, "Stun Interupt!" );
+ }
+
+ if ( m_zapTimer.HasStarted() && m_zapTimer.IsElapsed() )
+ {
+ m_zapTimer.Invalidate();
+
+ const float flSpellRange = 600.f + 50.f * ( me->GetLevel() - 1 );
+ const int nTargetCount = 6 + ( me->GetLevel() - 1 );
+ const float flMaxDamage = 50.f + ( 5 * (me->GetLevel() - 1) );
+ const float flMinDamage = 20.f + ( 5 * (me->GetLevel() - 1) );
+
+ if ( CMerasmus::Zap( me, "effect_staff", flSpellRange, flMinDamage, flMaxDamage, nTargetCount ) )
+ {
+ me->EmitSound( "Halloween.Merasmus_Spell" );
+ }
+ }
+
+ if ( me->IsActivityFinished() )
+ {
+ return Done( "Zapped!" );
+ }
+
+ return Continue();
+}
+
+
+void CMerasmusZap::PlayCastSound( CMerasmus* me ) const
+{
+ CPVSFilter filter( me->WorldSpaceCenter() );
+ switch ( m_spellType )
+ {
+ case SPELL_FIRE:
+ {
+ me->PlayLowPrioritySound( filter, "Halloween.MerasmusCastFireSpell" );
+ }
+ break;
+ case SPELL_LAUNCH:
+ {
+ me->PlayLowPrioritySound( filter, "Halloween.MerasmusLaunchSpell" );
+ }
+ break;
+ }
+}
+
diff --git a/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_zap.h b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_zap.h
new file mode 100644
index 0000000..559d136
--- /dev/null
+++ b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_zap.h
@@ -0,0 +1,32 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//
+//
+//=============================================================================
+#ifndef TF_MERASMUS_ZAP_H
+#define TF_MERASMUS_ZAP_H
+
+#include "tf_gamerules.h"
+
+class CMerasmusZap : public Action< CMerasmus >
+{
+public:
+ virtual ActionResult< CMerasmus > OnStart( CMerasmus *me, Action< CMerasmus > *priorAction );
+ virtual ActionResult< CMerasmus > Update( CMerasmus *me, float interval );
+
+ virtual const char *GetName( void ) const { return "Zap!"; } // return name of this action
+private:
+ enum SpellType_t
+ {
+ SPELL_FIRE,
+ SPELL_LAUNCH,
+
+ SPELL_COUNT
+ };
+ SpellType_t m_spellType;
+ void PlayCastSound( CMerasmus* me ) const;
+
+ CountdownTimer m_zapTimer;
+};
+
+#endif //TF_MERASMUS_ZAP_H
diff --git a/game/server/tf/halloween/merasmus/merasmus_body.cpp b/game/server/tf/halloween/merasmus/merasmus_body.cpp
new file mode 100644
index 0000000..a7ec353
--- /dev/null
+++ b/game/server/tf/halloween/merasmus/merasmus_body.cpp
@@ -0,0 +1,118 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//
+//
+//=============================================================================
+#include "cbase.h"
+
+#include "NextBot.h"
+#include "merasmus.h"
+#include "merasmus_body.h"
+
+
+//-------------------------------------------------------------------------------------------
+CMerasmusBody::CMerasmusBody( INextBot *bot ) : IBody( bot )
+{
+ m_moveXPoseParameter = -1;
+ m_moveYPoseParameter = -1;
+ m_currentActivity = -1;
+}
+
+
+//-------------------------------------------------------------------------------------------
+bool CMerasmusBody::StartActivity( Activity act, unsigned int flags )
+{
+ CMerasmus *me = (CMerasmus *)GetBot()->GetEntity();
+
+ int animSequence = ::SelectWeightedSequence( me->GetModelPtr(), act, me->GetSequence() );
+
+ if ( animSequence )
+ {
+ m_currentActivity = act;
+ me->SetSequence( animSequence );
+ me->SetPlaybackRate( 1.0f );
+ me->SetCycle( 0 );
+ me->ResetSequenceInfo();
+
+ return true;
+ }
+
+ return false;
+}
+
+
+//-------------------------------------------------------------------------------------------
+void CMerasmusBody::Update( void )
+{
+ CMerasmus *me = (CMerasmus *)GetBot()->GetEntity();
+
+ if ( m_moveXPoseParameter < 0 )
+ {
+ m_moveXPoseParameter = me->LookupPoseParameter( "move_x" );
+ }
+
+ if ( m_moveYPoseParameter < 0 )
+ {
+ m_moveYPoseParameter = me->LookupPoseParameter( "move_y" );
+ }
+
+
+ // Update the pose parameters
+ float speed = me->GetLocomotionInterface()->GetGroundSpeed(); // me->GetAbsVelocity().Length();
+
+ if ( speed < 0.01f )
+ {
+ // stopped
+ if ( m_moveXPoseParameter >= 0 )
+ {
+ me->SetPoseParameter( m_moveXPoseParameter, 0.0f );
+ }
+
+ if ( m_moveYPoseParameter >= 0 )
+ {
+ me->SetPoseParameter( m_moveYPoseParameter, 0.0f );
+ }
+ }
+ else
+ {
+ Vector forward, right, up;
+ me->GetVectors( &forward, &right, &up );
+
+ const Vector &motionVector = me->GetLocomotionInterface()->GetGroundMotionVector();
+
+ // move_x == 1.0 at full forward motion and -1.0 in full reverse
+ if ( m_moveXPoseParameter >= 0 )
+ {
+ float forwardVel = DotProduct( motionVector, forward );
+
+ me->SetPoseParameter( m_moveXPoseParameter, forwardVel );
+ }
+
+ if ( m_moveYPoseParameter >= 0 )
+ {
+ float sideVel = DotProduct( motionVector, right );
+
+ me->SetPoseParameter( m_moveYPoseParameter, sideVel );
+ }
+ }
+
+ // adjust animation speed to actual movement speed
+ if ( me->m_flGroundSpeed > 0.0f )
+ {
+ // Clamp playback rate to avoid datatable warnings. Anything faster would look silly, anyway.
+ float playbackRate = clamp( speed / me->m_flGroundSpeed, -4.f, 12.f );
+ me->SetPlaybackRate( playbackRate );
+ }
+
+ // move the animation ahead in time
+ me->StudioFrameAdvance();
+ me->DispatchAnimEvents( me );
+}
+
+
+//---------------------------------------------------------------------------------------------
+// return the bot's collision mask (hack until we get a general hull trace abstraction here or in the locomotion interface)
+unsigned int CMerasmusBody::GetSolidMask( void ) const
+{
+ return MASK_NPCSOLID | CONTENTS_PLAYERCLIP;
+}
diff --git a/game/server/tf/halloween/merasmus/merasmus_body.h b/game/server/tf/halloween/merasmus/merasmus_body.h
new file mode 100644
index 0000000..b542b19
--- /dev/null
+++ b/game/server/tf/halloween/merasmus/merasmus_body.h
@@ -0,0 +1,51 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//
+//
+//=============================================================================
+#ifndef MERASMUS_BODY_H
+#define MERASMUS_BODY_H
+
+#include "animation.h"
+#include "NextBotBodyInterface.h"
+
+class INextBot;
+
+
+//----------------------------------------------------------------------------------------------------------------
+/**
+ * The interface for control and information about the bot's body state (posture, animation state, etc)
+ */
+class CMerasmusBody : public IBody
+{
+public:
+ CMerasmusBody( INextBot *bot );
+ virtual ~CMerasmusBody() { }
+
+ virtual void Update( void );
+
+ virtual bool StartActivity( Activity act, unsigned int flags = 0 );
+ virtual Activity GetActivity( void ) const; // return currently animating activity
+ virtual bool IsActivity( Activity act ) const; // return true if currently animating activity matches the given one
+
+ virtual unsigned int GetSolidMask( void ) const; // return the bot's collision mask (hack until we get a general hull trace abstraction here or in the locomotion interface)
+
+private:
+ int m_currentActivity;
+ int m_moveXPoseParameter;
+ int m_moveYPoseParameter;
+};
+
+
+inline Activity CMerasmusBody::GetActivity( void ) const
+{
+ return (Activity)m_currentActivity;
+}
+
+inline bool CMerasmusBody::IsActivity( Activity act ) const
+{
+ return act == m_currentActivity ? true : false;
+}
+
+
+#endif // MERASMUS_BODY_H
diff --git a/game/server/tf/halloween/merasmus/merasmus_dancer.cpp b/game/server/tf/halloween/merasmus/merasmus_dancer.cpp
new file mode 100644
index 0000000..5e9b7f4
--- /dev/null
+++ b/game/server/tf/halloween/merasmus/merasmus_dancer.cpp
@@ -0,0 +1,172 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include "tf_gamerules.h"
+#include "merasmus_dancer.h"
+#include "animation.h"
+
+//-----------------------------------------------------------------------------
+
+#include "tf_fx.h"
+
+//-----------------------------------------------------------------------------
+
+#define POOF_SOUND "Halloween.Merasmus_Hiding_Explode"
+
+//-----------------------------------------------------------------------------
+
+LINK_ENTITY_TO_CLASS( merasmus_dancer, CMerasmusDancer );
+
+IMPLEMENT_SERVERCLASS_ST( CMerasmusDancer, DT_MerasmusDancer )
+END_SEND_TABLE()
+
+//-----------------------------------------------------------------------------
+
+#define MERASMUS_MODEL_NAME "models/bots/merasmus/merasmus.mdl"
+
+//-----------------------------------------------------------------------------
+
+CMerasmusDancer::CMerasmusDancer()
+: m_bEmitParticleEffect( false )
+{
+}
+
+//-----------------------------------------------------------------------------
+
+CMerasmusDancer::~CMerasmusDancer()
+{
+}
+
+//-----------------------------------------------------------------------------
+
+void CMerasmusDancer::Spawn()
+{
+ Precache();
+
+ m_DieCountdownTimer.Invalidate();
+
+ BaseClass::Spawn();
+
+ SetModel( MERASMUS_MODEL_NAME );
+ UseClientSideAnimation();
+
+ SetThink( &CMerasmusDancer::DanceThink );
+ SetNextThink( gpGlobals->curtime );
+
+ m_bEmitParticleEffect = true;
+}
+
+//-----------------------------------------------------------------------------
+
+void CMerasmusDancer::PlaySequence( const char *pSeqName )
+{
+ int iAnimSequence = LookupSequence( pSeqName ); // dance animation
+ if ( iAnimSequence )
+ {
+ SetSequence( iAnimSequence );
+ SetPlaybackRate( 1.0f );
+ SetCycle( 0 );
+ ResetSequenceInfo();
+
+ HideStaff();
+ }
+}
+
+//-----------------------------------------------------------------------------
+
+void CMerasmusDancer::PlayActivity( int iActivity )
+{
+ int iAnimSequence = ::SelectWeightedSequence( GetModelPtr(), iActivity, GetSequence() );
+ if ( iAnimSequence )
+ {
+ SetSequence( iAnimSequence );
+ SetPlaybackRate( 1.0f );
+ SetCycle( 0 );
+ ResetSequenceInfo();
+
+ HideStaff();
+ }
+}
+
+//-----------------------------------------------------------------------------
+
+void CMerasmusDancer::HideStaff()
+{
+ int nStaffBodyGroup = FindBodygroupByName( "staff" );
+ SetBodygroup( nStaffBodyGroup, 2 );
+}
+
+//-----------------------------------------------------------------------------
+
+void CMerasmusDancer::Dance()
+{
+ PlaySequence( "taunt06" );
+}
+
+//-----------------------------------------------------------------------------
+
+void CMerasmusDancer::Vanish()
+{
+ m_bEmitParticleEffect = true;
+ m_DieCountdownTimer.Start( 0.0f );
+}
+
+//-----------------------------------------------------------------------------
+
+void CMerasmusDancer::BlastOff()
+{
+ m_bEmitParticleEffect = true;
+ m_DieCountdownTimer.Start( 0.3f );
+
+ PlayActivity( ACT_FLY );
+}
+
+//-----------------------------------------------------------------------------
+
+void CMerasmusDancer::Precache()
+{
+ BaseClass::Precache();
+
+ int model = PrecacheModel( MERASMUS_MODEL_NAME );
+ PrecacheGibsForModel( model );
+ PrecacheParticleSystem( "merasmus_tp" ); // puff effect
+
+ PrecacheScriptSound( POOF_SOUND );
+
+ // We deliberately allow late precaches here.
+ bool bAllowPrecache = CBaseAnimating::IsPrecacheAllowed();
+ CBaseAnimating::SetAllowPrecache( bAllowPrecache );
+}
+
+//-----------------------------------------------------------------------------
+
+bool CMerasmusDancer::ShouldDelete() const
+{
+ return m_DieCountdownTimer.HasStarted() && m_DieCountdownTimer.IsElapsed();
+}
+
+//-----------------------------------------------------------------------------
+
+void CMerasmusDancer::DanceThink()
+{
+ // Emit the initial effect here, rather than in Spawn(), since GetAbsOrigin() and GetAbsAngles() don't return useful values then.
+ if ( m_bEmitParticleEffect )
+ {
+ DispatchParticleEffect( "merasmus_tp", GetAbsOrigin(), GetAbsAngles() );
+ m_bEmitParticleEffect = false;
+ EmitSound( POOF_SOUND );
+ }
+
+ if ( ShouldDelete() )
+ {
+ EmitSound( POOF_SOUND );
+ UTIL_Remove( this );
+ return;
+ }
+
+ SetNextThink( gpGlobals->curtime );
+}
diff --git a/game/server/tf/halloween/merasmus/merasmus_dancer.h b/game/server/tf/halloween/merasmus/merasmus_dancer.h
new file mode 100644
index 0000000..305f589
--- /dev/null
+++ b/game/server/tf/halloween/merasmus/merasmus_dancer.h
@@ -0,0 +1,49 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Teleport vortex for the Eyeball Boss
+//
+//=============================================================================//
+#ifndef MERASMUS_DANCE_H
+#define MERASMUS_DANCE_H
+
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "baseanimating.h"
+
+//=============================================================================
+//
+// Non-AI version of Merasmus that can be spawned during the dance spell.
+//
+class CMerasmusDancer : public CBaseAnimating
+{
+ DECLARE_CLASS( CMerasmusDancer, CBaseAnimating );
+ DECLARE_SERVERCLASS();
+
+public:
+ CMerasmusDancer();
+ virtual ~CMerasmusDancer();
+
+ void Dance();
+ void Vanish();
+ void BlastOff();
+
+private:
+ virtual void Spawn();
+ virtual void Precache();
+
+ void HideStaff();
+ void PlaySequence( const char *pSeqName );
+ void PlayActivity( int iActivity );
+ void DanceThink();
+
+ bool ShouldDelete() const;
+
+ bool m_bEmitParticleEffect;
+ CountdownTimer m_DieCountdownTimer;
+};
+
+#endif // MERASMUS_DANCE_H
+
+
diff --git a/game/server/tf/halloween/merasmus/merasmus_trick_or_treat_prop.cpp b/game/server/tf/halloween/merasmus/merasmus_trick_or_treat_prop.cpp
new file mode 100644
index 0000000..f343d92
--- /dev/null
+++ b/game/server/tf/halloween/merasmus/merasmus_trick_or_treat_prop.cpp
@@ -0,0 +1,178 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//
+//
+//=============================================================================
+#include "cbase.h"
+
+#include "tf_ammo_pack.h"
+#include "particle_parse.h"
+#include "tf/halloween/ghost/ghost.h"
+#include "tf_player.h"
+#include "tf_gamerules.h"
+
+#include "merasmus.h"
+#include "merasmus_trick_or_treat_prop.h"
+
+LINK_ENTITY_TO_CLASS( tf_merasmus_trick_or_treat_prop, CTFMerasmusTrickOrTreatProp );
+
+IMPLEMENT_AUTO_LIST( ITFMerasmusTrickOrTreatProp );
+
+ConVar tf_merasmus_prop_health( "tf_merasmus_prop_health", "150", FCVAR_CHEAT | FCVAR_GAMEDLL );
+
+CTFMerasmusTrickOrTreatProp::CTFMerasmusTrickOrTreatProp()
+{
+}
+
+
+void CTFMerasmusTrickOrTreatProp::Spawn()
+{
+ Precache();
+
+ SetModel( CMerasmus::GetRandomPropModelName() );
+
+ BaseClass::Spawn();
+
+ SetMoveType( MOVETYPE_NONE );
+ SetSolid( SOLID_VPHYSICS );
+ m_takedamage = DAMAGE_YES;
+ SetHealth( tf_merasmus_prop_health.GetInt() );
+
+ DispatchParticleEffect( "merasmus_object_spawn", WorldSpaceCenter(), GetAbsAngles() );
+}
+
+
+void CTFMerasmusTrickOrTreatProp::Event_Killed( const CTakeDamageInfo &info )
+{
+ SpawnTrickOrTreatItem();
+
+ DispatchParticleEffect( "merasmus_object_spawn", WorldSpaceCenter(), GetAbsAngles() );
+ EmitSound( "Halloween.Merasmus_Hiding_Explode" );
+
+ if ( TFGameRules()->GetActiveBoss() )
+ {
+ CMerasmus* pMerasmus = assert_cast< CMerasmus* >( TFGameRules()->GetActiveBoss() );
+ if ( pMerasmus )
+ {
+ if ( pMerasmus->IsNextKilledPropMerasmus() )
+ {
+ // move merasmus to the destroyed prop before we reveal him
+ pMerasmus->SetAbsOrigin( GetAbsOrigin() );
+
+ if ( info.GetAttacker() && info.GetAttacker()->IsPlayer() )
+ {
+ CTFPlayer* pTFPlayer = assert_cast< CTFPlayer* >( info.GetAttacker() );
+ if ( pTFPlayer )
+ {
+ pMerasmus->SetRevealer( pTFPlayer );
+
+ IGameEvent *pEvent = gameeventmanager->CreateEvent( "merasmus_prop_found" );
+ if ( pEvent )
+ {
+ pEvent->SetInt( "player", pTFPlayer->GetUserID() );
+ gameeventmanager->FireEvent( pEvent, true );
+ }
+ }
+ }
+ }
+ else
+ {
+ CPVSFilter filter( pMerasmus->WorldSpaceCenter() );
+ if (RandomInt(1,3) == 1)
+ {
+ pMerasmus->PlayLowPrioritySound( filter, "Halloween.MerasmusTauntFakeProp" );
+ }
+ }
+ }
+ }
+
+ BaseClass::Event_Killed( info );
+}
+
+
+int CTFMerasmusTrickOrTreatProp::OnTakeDamage( const CTakeDamageInfo &info )
+{
+ DispatchParticleEffect( "merasmus_blood", info.GetDamagePosition(), GetAbsAngles() );
+
+ CTakeDamageInfo newinfo = info;
+
+ CTFPlayer *pTFPlayer = ToTFPlayer( newinfo.GetAttacker() );
+ if ( pTFPlayer && ( pTFPlayer->IsPlayerClass( TF_CLASS_SOLDIER ) || pTFPlayer->IsPlayerClass( TF_CLASS_DEMOMAN ) ) && ( newinfo.GetDamageType() & DMG_BLAST ) )
+ {
+ newinfo.SetDamage( GetHealth() * 2.f );
+ }
+
+ return BaseClass::OnTakeDamage( newinfo );
+}
+
+
+void CTFMerasmusTrickOrTreatProp::Touch( CBaseEntity *pOther )
+{
+ BaseClass::Touch( pOther );
+
+ if ( pOther && pOther->IsPlayer() )
+ {
+ CTFPlayer *pPlayer = ToTFPlayer( pOther );
+ if ( pPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_BOMB_HEAD ) )
+ {
+ pPlayer->m_Shared.RemoveCond( TF_COND_HALLOWEEN_BOMB_HEAD );
+ pPlayer->m_Shared.RemoveCond( TF_COND_STUNNED );
+ pPlayer->MerasmusPlayerBombExplode( false );
+
+ // force kill
+ CTakeDamageInfo info( pPlayer, pPlayer, 99999, DMG_BLAST, TF_DMG_CUSTOM_NONE );
+ Event_Killed( info );
+ }
+ }
+}
+
+
+CTFMerasmusTrickOrTreatProp* CTFMerasmusTrickOrTreatProp::Create( const Vector& vPosition, const QAngle& qAngles )
+{
+ CTFMerasmusTrickOrTreatProp *pTrickOrTreatProp = static_cast<CTFMerasmusTrickOrTreatProp*>( CBaseEntity::Create( "tf_merasmus_trick_or_treat_prop", vPosition, qAngles, NULL ) );
+
+ // must be on a team different from player(s) in order for some
+ // weapons to hit (ie: pipe bombs)
+ if ( pTrickOrTreatProp )
+ {
+ pTrickOrTreatProp->ChangeTeam( TF_TEAM_HALLOWEEN );
+ }
+
+ return pTrickOrTreatProp;
+}
+
+
+void CTFMerasmusTrickOrTreatProp::SpawnTrickOrTreatItem()
+{
+ int nNumAmmo = 1/*RandomInt( 1, 3 )*/;
+ for ( int i=0; i<nNumAmmo; ++i )
+ {
+ // Create the ammo pack.
+ CTFAmmoPack *pAmmoPack = CTFAmmoPack::Create( WorldSpaceCenter(), vec3_angle, this, "models/items/ammopack_medium.mdl" );
+ Assert( pAmmoPack );
+ if ( pAmmoPack )
+ {
+ pAmmoPack->MakeHolidayPack();
+ pAmmoPack->SetBonusScale( 2.f );
+ pAmmoPack->SetModelScale( 1.4f );
+
+ Vector vecRight, vecUp;
+ AngleVectors( EyeAngles(), NULL, &vecRight, &vecUp );
+
+ // Calculate the initial impulse on the weapon.
+ Vector vecImpulse = RandomVector( 40.f, 80.f );
+ vecImpulse.z *= Sign( vecImpulse.z ); // always go up
+
+ pAmmoPack->SetInitialVelocity( vecImpulse );
+ pAmmoPack->ApplyAbsVelocityImpulse( vecImpulse );
+
+
+ // Give the ammo pack some health, so that trains can destroy it.
+ pAmmoPack->SetCollisionGroup( COLLISION_GROUP_DEBRIS );
+ pAmmoPack->m_takedamage = DAMAGE_YES;
+ pAmmoPack->SetHealth( 900 );
+
+ pAmmoPack->SetBodygroup( 1, 1 );
+ }
+ }
+}
diff --git a/game/server/tf/halloween/merasmus/merasmus_trick_or_treat_prop.h b/game/server/tf/halloween/merasmus/merasmus_trick_or_treat_prop.h
new file mode 100644
index 0000000..b9d48c4
--- /dev/null
+++ b/game/server/tf/halloween/merasmus/merasmus_trick_or_treat_prop.h
@@ -0,0 +1,30 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//
+//
+//=============================================================================
+#ifndef MERASMUS_TRICK_OR_TREAT_PROP_H
+#define MERASMUS_TRICK_OR_TREAT_PROP_H
+
+DECLARE_AUTO_LIST( ITFMerasmusTrickOrTreatProp );
+
+class CTFMerasmusTrickOrTreatProp : public CBaseAnimating, public ITFMerasmusTrickOrTreatProp
+{
+ DECLARE_CLASS( CTFMerasmusTrickOrTreatProp, CBaseAnimating );
+
+public:
+ CTFMerasmusTrickOrTreatProp();
+ ~CTFMerasmusTrickOrTreatProp() {}
+
+ virtual void Spawn( void );
+ virtual void Event_Killed( const CTakeDamageInfo &info );
+ virtual int OnTakeDamage( const CTakeDamageInfo &info );
+ virtual void Touch( CBaseEntity *pOther );
+
+ static CTFMerasmusTrickOrTreatProp* Create( const Vector& vPosition, const QAngle& qAngles );
+
+private:
+ void SpawnTrickOrTreatItem();
+};
+
+#endif // MERASMUS_TRICK_OR_TREAT_PROP_H