summaryrefslogtreecommitdiff
path: root/game/server/tf/halloween/eyeball_boss
diff options
context:
space:
mode:
Diffstat (limited to 'game/server/tf/halloween/eyeball_boss')
-rw-r--r--game/server/tf/halloween/eyeball_boss/eyeball_behavior/eyeball_boss_approach_target.cpp81
-rw-r--r--game/server/tf/halloween/eyeball_boss/eyeball_behavior/eyeball_boss_approach_target.h26
-rw-r--r--game/server/tf/halloween/eyeball_boss/eyeball_behavior/eyeball_boss_behavior.cpp201
-rw-r--r--game/server/tf/halloween/eyeball_boss/eyeball_behavior/eyeball_boss_behavior.h46
-rw-r--r--game/server/tf/halloween/eyeball_boss/eyeball_behavior/eyeball_boss_emerge.cpp172
-rw-r--r--game/server/tf/halloween/eyeball_boss/eyeball_behavior/eyeball_boss_emerge.h28
-rw-r--r--game/server/tf/halloween/eyeball_boss/eyeball_behavior/eyeball_boss_emote.cpp56
-rw-r--r--game/server/tf/halloween/eyeball_boss/eyeball_behavior/eyeball_boss_emote.h28
-rw-r--r--game/server/tf/halloween/eyeball_boss/eyeball_behavior/eyeball_boss_idle.cpp242
-rw-r--r--game/server/tf/halloween/eyeball_boss/eyeball_behavior/eyeball_boss_idle.h37
-rw-r--r--game/server/tf/halloween/eyeball_boss/eyeball_behavior/eyeball_boss_launch_rockets.cpp182
-rw-r--r--game/server/tf/halloween/eyeball_boss/eyeball_behavior/eyeball_boss_launch_rockets.h30
-rw-r--r--game/server/tf/halloween/eyeball_boss/eyeball_behavior/eyeball_boss_notice.cpp43
-rw-r--r--game/server/tf/halloween/eyeball_boss/eyeball_behavior/eyeball_boss_notice.h23
-rw-r--r--game/server/tf/halloween/eyeball_boss/eyeball_behavior/eyeball_boss_stunned.cpp60
-rw-r--r--game/server/tf/halloween/eyeball_boss/eyeball_behavior/eyeball_boss_stunned.h31
-rw-r--r--game/server/tf/halloween/eyeball_boss/eyeball_behavior/eyeball_boss_teleport.cpp148
-rw-r--r--game/server/tf/halloween/eyeball_boss/eyeball_behavior/eyeball_boss_teleport.h45
-rw-r--r--game/server/tf/halloween/eyeball_boss/eyeball_boss.cpp1050
-rw-r--r--game/server/tf/halloween/eyeball_boss/eyeball_boss.h338
20 files changed, 2867 insertions, 0 deletions
diff --git a/game/server/tf/halloween/eyeball_boss/eyeball_behavior/eyeball_boss_approach_target.cpp b/game/server/tf/halloween/eyeball_boss/eyeball_behavior/eyeball_boss_approach_target.cpp
new file mode 100644
index 0000000..a9bfd2f
--- /dev/null
+++ b/game/server/tf/halloween/eyeball_boss/eyeball_behavior/eyeball_boss_approach_target.cpp
@@ -0,0 +1,81 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// eyeball_boss_approach_target.cpp
+// The 2011 Halloween Boss
+// Michael Booth, October 2011
+
+#include "cbase.h"
+
+#include "../eyeball_boss.h"
+#include "eyeball_boss_approach_target.h"
+#include "eyeball_boss_launch_rockets.h"
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CEyeballBoss > CEyeballBossApproachTarget::OnStart( CEyeballBoss *me, Action< CEyeballBoss > *priorAction )
+{
+ m_giveUpTimer.Start( 5.0f );
+ m_minChaseTimer.Start( 0.5f );
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CEyeballBoss > CEyeballBossApproachTarget::Update( CEyeballBoss *me, float interval )
+{
+ CBaseCombatCharacter *victim = me->GetVictim();
+ CBaseCombatCharacter *closestVictim = me->FindClosestVisibleVictim();
+
+ if ( victim != closestVictim && m_minChaseTimer.IsElapsed() )
+ {
+ return Done( "Noticed better victim" );
+ }
+
+ if ( !victim || !victim->IsAlive() )
+ {
+ return Done( "Victim gone" );
+ }
+
+ if ( m_giveUpTimer.IsElapsed() )
+ {
+ return Done( "Giving up" );
+ }
+
+ bool isVictimVisible = me->IsLineOfSightClear( victim, CBaseCombatCharacter::IGNORE_ACTORS );
+
+ if ( !isVictimVisible )
+ {
+ if ( m_lingerTimer.IsElapsed() )
+ {
+ return Done( "Lost victim" );
+ }
+
+ // wait a bit to see if we catch a glimpse of our victim again
+ return Continue();
+ }
+
+ m_lingerTimer.Start( 1.0f );
+
+ float attackRange = tf_eyeball_boss_attack_range.GetFloat();
+ if ( me->IsEnraged() )
+ {
+ attackRange *= 2.0f;
+ }
+
+ if ( me->IsRangeLessThan( victim, attackRange ) )
+ {
+ return ChangeTo( new CEyeballBossLaunchRockets, "Rocket attack!" );
+ }
+
+ // approach victim
+ me->GetLocomotionInterface()->SetDesiredSpeed( tf_eyeball_boss_speed.GetFloat() );
+ me->GetLocomotionInterface()->Approach( victim->WorldSpaceCenter() );
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+void CEyeballBossApproachTarget::OnEnd( CEyeballBoss *me, Action< CEyeballBoss > *nextAction )
+{
+}
diff --git a/game/server/tf/halloween/eyeball_boss/eyeball_behavior/eyeball_boss_approach_target.h b/game/server/tf/halloween/eyeball_boss/eyeball_behavior/eyeball_boss_approach_target.h
new file mode 100644
index 0000000..5c09a7b
--- /dev/null
+++ b/game/server/tf/halloween/eyeball_boss/eyeball_behavior/eyeball_boss_approach_target.h
@@ -0,0 +1,26 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// eyeball_boss_approach_target.h
+// The 2011 Halloween Boss
+// Michael Booth, October 2011
+
+#ifndef EYEBALL_BOSS_APPROACH_TARGET_H
+#define EYEBALL_BOSS_APPROACH_TARGET_H
+
+//---------------------------------------------------------------------------------------------
+//---------------------------------------------------------------------------------------------
+class CEyeballBossApproachTarget : public Action< CEyeballBoss >
+{
+public:
+ virtual ActionResult< CEyeballBoss > OnStart( CEyeballBoss *me, Action< CEyeballBoss > *priorAction );
+ virtual ActionResult< CEyeballBoss > Update( CEyeballBoss *me, float interval );
+ virtual void OnEnd( CEyeballBoss *me, Action< CEyeballBoss > *nextAction );
+
+ virtual const char *GetName( void ) const { return "ApproachTarget"; } // return name of this action
+
+private:
+ CountdownTimer m_lingerTimer;
+ CountdownTimer m_giveUpTimer;
+ CountdownTimer m_minChaseTimer;
+};
+
+#endif // EYEBALL_BOSS_APPROACH_TARGET_H
diff --git a/game/server/tf/halloween/eyeball_boss/eyeball_behavior/eyeball_boss_behavior.cpp b/game/server/tf/halloween/eyeball_boss/eyeball_behavior/eyeball_boss_behavior.cpp
new file mode 100644
index 0000000..1875526
--- /dev/null
+++ b/game/server/tf/halloween/eyeball_boss/eyeball_behavior/eyeball_boss_behavior.cpp
@@ -0,0 +1,201 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// eyeball_boss_behavior.cpp
+// The 2011 Halloween Boss' top level behavior, containing all other actions as children
+// Michael Booth, October 2011
+
+#include "cbase.h"
+
+#include "tf_player.h"
+#include "tf_gamerules.h"
+#include "particle_parse.h"
+#include "tf/halloween/eyeball_boss/teleport_vortex.h"
+
+#include "../eyeball_boss.h"
+#include "eyeball_boss_behavior.h"
+#include "eyeball_boss_emerge.h"
+#include "eyeball_boss_stunned.h"
+
+
+//---------------------------------------------------------------------------------------------
+Action< CEyeballBoss > *CEyeballBossBehavior::InitialContainedAction( CEyeballBoss *me )
+{
+ return new CEyeballBossEmerge;
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CEyeballBoss > CEyeballBossBehavior::OnStart( CEyeballBoss *me, Action< CEyeballBoss > *priorAction )
+{
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CEyeballBoss > CEyeballBossBehavior::Update( CEyeballBoss *me, float interval )
+{
+ if ( tf_eyeball_boss_debug.GetBool() )
+ {
+ DevMsg( "%3.2f: DPS = %3.2f\n", gpGlobals->curtime, me->GetInjuryRate() );
+ }
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+EventDesiredResult< CEyeballBoss > CEyeballBossBehavior::OnInjured( CEyeballBoss *me, const CTakeDamageInfo &info )
+{
+ CTFPlayer *attacker = ToTFPlayer( info.GetAttacker() );
+ if ( attacker )
+ {
+ if ( attacker->HasPurgatoryBuff() && m_stunCooldownTimer.IsElapsed() )
+ {
+ IGameEvent *event = gameeventmanager->CreateEvent( "eyeball_boss_stunned" );
+ if ( event )
+ {
+ event->SetInt( "level", me->GetLevel() );
+ event->SetInt( "player_entindex", attacker->entindex() );
+ gameeventmanager->FireEvent( event );
+ }
+
+ me->LogPlayerInteraction( "eyeball_stunned", attacker );
+
+ m_stunCooldownTimer.Start( 10.0f );
+
+ return TrySuspendFor( new CEyeballBossStunned, RESULT_IMPORTANT, "Hurt by Purgatory Buff!" );
+ }
+
+ // critz piss me off
+ if ( info.GetDamageType() & DMG_CRITICAL )
+ {
+ me->BecomeEnraged( 5.0f );
+ }
+
+ // heavy DPS pisses me off
+ if ( me->GetInjuryRate() > 300.0f )
+ {
+ me->BecomeEnraged( 5.0f );
+ }
+ }
+
+ return TryContinue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+EventDesiredResult< CEyeballBoss > CEyeballBossBehavior::OnKilled( CEyeballBoss *me, const CTakeDamageInfo &info )
+{
+ // award achievement to everyone who injured me within the last few seconds
+ const float deathTime = 5.0f;
+ const CUtlVector< CEyeballBoss::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 )
+ {
+ if ( !me->IsSpell() )
+ {
+ IGameEvent *event = gameeventmanager->CreateEvent( "eyeball_boss_killer" );
+ if ( event )
+ {
+ event->SetInt( "level", me->GetLevel() );
+ event->SetInt( "player_entindex", attackerVector[i].m_attacker->entindex() );
+ gameeventmanager->FireEvent( event );
+ }
+ }
+
+ if ( TFGameRules() && TFGameRules()->IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_VIADUCT ) )
+ {
+ if ( !me->WasSpawnedByCheats() )
+ {
+ attackerVector[i].m_attacker->AwardAchievement( ACHIEVEMENT_TF_HALLOWEEN_EYEBOSS_KILL );
+ }
+ }
+
+ me->LogPlayerInteraction( "eyeball_killer", attackerVector[i].m_attacker );
+ }
+ }
+
+ CUtlVector< CTFPlayer * > playerVector;
+ CollectPlayers( &playerVector, TF_TEAM_RED );
+ CollectPlayers( &playerVector, TF_TEAM_BLUE, false, APPEND_PLAYERS );
+
+ UTIL_LogPrintf( "HALLOWEEN: eyeball_death (max_dps %3.2f) (max_health %d) (player_count %d) (level %d)\n", me->GetMaxInjuryRate(), me->GetMaxHealth(), playerVector.Count(), me->GetLevel() );
+
+ return TryChangeTo( new CEyeballBossDead, RESULT_CRITICAL, "I died!" );
+}
+
+
+//---------------------------------------------------------------------------------------------
+//---------------------------------------------------------------------------------------------
+ActionResult< CEyeballBoss > CEyeballBossDead::OnStart( CEyeballBoss *me, Action< CEyeballBoss > *priorAction )
+{
+ int animSequence = me->LookupSequence( "death" );
+ if ( animSequence )
+ {
+ me->SetSequence( animSequence );
+ me->SetPlaybackRate( 1.0f );
+ me->SetCycle( 0 );
+ me->ResetSequenceInfo();
+ }
+
+ me->EmitSound( "Halloween.EyeballBossStunned" );
+
+ m_giveUpTimer.Start( 10.0f );
+
+ if ( tf_eyeball_boss_debug.GetBool() )
+ {
+ DevMsg( "Max Eyeball DPS taken = %3.2f\n", me->GetMaxInjuryRate() );
+ }
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CEyeballBoss > CEyeballBossDead::Update( CEyeballBoss *me, float interval )
+{
+ float ground;
+ TheNavMesh->GetSimpleGroundHeight( me->WorldSpaceCenter(), &ground );
+
+ if ( m_giveUpTimer.IsElapsed() || ( me->WorldSpaceCenter().z - ground ) < 100.0f )
+ {
+ // we're on the ground - pop
+ DispatchParticleEffect( "eyeboss_death", me->GetAbsOrigin(), me->GetAbsAngles() );
+
+ me->EmitSound( "Cart.Explode" );
+ me->EmitSound( "Halloween.EyeballBossDie" );
+
+ UTIL_ScreenShake( me->GetAbsOrigin(), 25.0f, 5.0f, 5.0f, 1000.0f, SHAKE_START );
+
+ UTIL_Remove( me );
+
+ me->SetVictim( NULL );
+
+ if ( !me->IsSpell() )
+ {
+ IGameEvent *event = gameeventmanager->CreateEvent( "eyeball_boss_killed" );
+ if ( event )
+ {
+ event->SetInt( "level", me->GetLevel() );
+ gameeventmanager->FireEvent( event );
+ }
+
+ // next time, I'll be tougher!
+ me->GainLevel();
+ }
+
+ // coat nearby players with goo
+ const float gooRange = 750.0f;
+ me->JarateNearbyPlayers( gooRange );
+
+ // create vortex to loot
+ CTeleportVortex *vortex = (CTeleportVortex *)CBaseEntity::Create( "teleport_vortex", me->GetAbsOrigin(), vec3_angle );
+ if ( vortex )
+ {
+ vortex->SetupVortex( true );
+ }
+ }
+
+ return Continue();
+}
diff --git a/game/server/tf/halloween/eyeball_boss/eyeball_behavior/eyeball_boss_behavior.h b/game/server/tf/halloween/eyeball_boss/eyeball_behavior/eyeball_boss_behavior.h
new file mode 100644
index 0000000..87f5649
--- /dev/null
+++ b/game/server/tf/halloween/eyeball_boss/eyeball_behavior/eyeball_boss_behavior.h
@@ -0,0 +1,46 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// eyeball_boss_behavior.h
+// The 2011 Halloween Boss
+// Michael Booth, October 2011
+
+#ifndef EYEBALL_BOSS_BEHAVIOR_H
+#define EYEBALL_BOSS_BEHAVIOR_H
+
+
+//---------------------------------------------------------------------------------------------
+//---------------------------------------------------------------------------------------------
+class CEyeballBossBehavior : public Action< CEyeballBoss >
+{
+public:
+ virtual Action< CEyeballBoss > *InitialContainedAction( CEyeballBoss *me );
+
+ virtual ActionResult< CEyeballBoss > OnStart( CEyeballBoss *me, Action< CEyeballBoss > *priorAction );
+ virtual ActionResult< CEyeballBoss > Update( CEyeballBoss *me, float interval );
+
+ virtual EventDesiredResult< CEyeballBoss > OnInjured( CEyeballBoss *me, const CTakeDamageInfo &info );
+ virtual EventDesiredResult< CEyeballBoss > OnKilled( CEyeballBoss *me, const CTakeDamageInfo &info );
+
+ virtual const char *GetName( void ) const { return "Behavior"; } // return name of this action
+
+private:
+ CountdownTimer m_stunCooldownTimer;
+};
+
+
+//---------------------------------------------------------------------------------------------
+//---------------------------------------------------------------------------------------------
+class CEyeballBossDead : public Action< CEyeballBoss >
+{
+public:
+ virtual ActionResult< CEyeballBoss > OnStart( CEyeballBoss *me, Action< CEyeballBoss > *priorAction );
+ virtual ActionResult< CEyeballBoss > Update( CEyeballBoss *me, float interval );
+
+ virtual const char *GetName( void ) const { return "Dead"; } // return name of this action
+
+private:
+ CountdownTimer m_giveUpTimer;
+};
+
+
+
+#endif // EYEBALL_BOSS_BEHAVIOR_H
diff --git a/game/server/tf/halloween/eyeball_boss/eyeball_behavior/eyeball_boss_emerge.cpp b/game/server/tf/halloween/eyeball_boss/eyeball_behavior/eyeball_boss_emerge.cpp
new file mode 100644
index 0000000..1578b81
--- /dev/null
+++ b/game/server/tf/halloween/eyeball_boss/eyeball_behavior/eyeball_boss_emerge.cpp
@@ -0,0 +1,172 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// eyeball_boss_emerge.cpp
+// The Halloween Boss emerging from the ground
+// Michael Booth, October 2011
+
+#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 "../eyeball_boss.h"
+#include "eyeball_boss_emerge.h"
+#include "eyeball_boss_idle.h"
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CEyeballBoss > CEyeballBossEmerge::OnStart( CEyeballBoss *me, Action< CEyeballBoss > *priorAction )
+{
+ if ( me->IsSpell() )
+ {
+ // just do teleport in code
+ me->RemoveEffects( EF_NOINTERP | EF_NODRAW );
+
+ DispatchParticleEffect( "eyeboss_tp_normal", me->GetAbsOrigin(), me->GetAbsAngles() );
+
+ int animSequence = me->LookupSequence( "teleport_in" );
+ if ( animSequence )
+ {
+ me->SetSequence( animSequence );
+ me->SetPlaybackRate( 1.0f );
+ me->SetCycle( 0 );
+ me->ResetSequenceInfo();
+ }
+ }
+ else
+ {
+ int animSequence = me->LookupSequence( "arrives" );
+ if ( animSequence )
+ {
+ me->SetSequence( animSequence );
+ me->SetPlaybackRate( 1.0f );
+ me->SetCycle( 0 );
+ me->ResetSequenceInfo();
+ }
+
+ DispatchParticleEffect( "halloween_boss_summon", me->GetAbsOrigin(), me->GetAbsAngles() );
+
+ m_groundPos = me->GetAbsOrigin();
+ m_riseTimer.Start( 3.0f );
+ m_emergePos = me->GetAbsOrigin() + Vector( 0, 0, 100.0f );
+
+ m_height = 150.0f;
+ me->SetAbsOrigin( m_emergePos + Vector( 0, 0, -m_height ) );
+ me->EmitSound( "Halloween.HeadlessBossSpawnRumble" );
+
+ IGameEvent *event = gameeventmanager->CreateEvent( "eyeball_boss_summoned" );
+ if ( event )
+ {
+ event->SetInt( "level", me->GetLevel() );
+ gameeventmanager->FireEvent( event );
+ }
+
+ m_killTimer.Start( 2.0f );
+
+ CUtlVector< CTFPlayer * > playerVector;
+ CollectPlayers( &playerVector, TF_TEAM_RED );
+ CollectPlayers( &playerVector, TF_TEAM_BLUE, false, APPEND_PLAYERS );
+
+ UTIL_LogPrintf( "HALLOWEEN: eyeball_spawn (max_health %d) (player_count %d) (level %d)\n", me->GetMaxHealth(), playerVector.Count(), me->GetLevel() );
+ }
+
+ return Continue();
+}
+
+
+//----------------------------------------------------------------------------------
+ActionResult< CEyeballBoss > CEyeballBossEmerge::Update( CEyeballBoss *me, float interval )
+{
+ if ( !m_riseTimer.IsElapsed() )
+ {
+ me->SetAbsOrigin( m_emergePos + Vector( 0, 0, -m_height * m_riseTimer.GetRemainingTime() / m_riseTimer.GetCountdownDuration() ) );
+
+ if ( m_rumbleTimer.IsElapsed() )
+ {
+ m_rumbleTimer.Start( 0.25f );
+
+ // shake nearby players' screens.
+ UTIL_ScreenShake( me->GetAbsOrigin(), 15.0f, 5.0f, 1.0f, 1000.0f, SHAKE_START );
+ }
+ }
+
+ if ( me->IsActivityFinished() )
+ {
+ return ChangeTo( new CEyeballBossIdle, "Here I am!" );
+ }
+
+ // don't do any kill players or remove pipe bomb code below if I'm a spell
+ if ( me->IsSpell() )
+ {
+ return Continue();
+ }
+
+ // push players away to avoid penetration issues
+ CUtlVector< CTFPlayer * > playerVector;
+ CollectPlayers( &playerVector, TF_TEAM_RED, COLLECT_ONLY_LIVING_PLAYERS );
+ CollectPlayers( &playerVector, TF_TEAM_BLUE, COLLECT_ONLY_LIVING_PLAYERS, APPEND_PLAYERS );
+
+ const float pushRange = 250.0f;
+ const float pushForce = 200.0f;
+
+ const float deathRange = me->GetLevel() > 1 ? 200.0f : 100.0f;
+
+ for( int i=0; i<playerVector.Count(); ++i )
+ {
+ CTFPlayer *player = playerVector[i];
+
+ Vector toPlayer = player->EyePosition() - m_groundPos;
+ float range = toPlayer.NormalizeInPlace();
+
+ if ( range < pushRange )
+ {
+ // make sure we push players up and away
+ toPlayer.z = 0.0f;
+ toPlayer.NormalizeInPlace();
+ toPlayer.z = 1.0f;
+
+ Vector push = pushForce * toPlayer;
+
+ player->RemoveFlag( FL_ONGROUND );
+ player->ApplyAbsVelocityImpulse( push );
+ }
+
+ // kill anyone touching the summon portal
+ if ( !m_killTimer.IsElapsed() )
+ {
+ if ( range < deathRange )
+ {
+ CTakeDamageInfo info( me, me, 2.0f * player->GetMaxHealth(), DMG_BLAST, TF_DMG_CUSTOM_PLASMA );
+ player->TakeDamage( info );
+ }
+ }
+ }
+
+ // leveled-up boss fizzles any grenades/etc near his summon portal
+ if ( !m_killTimer.IsElapsed() && me->GetLevel() > 1 )
+ {
+ Vector vecSize = Vector( 256, 256, 256 );
+
+ const int maxCollectedEntities = 1024;
+ CBaseEntity *pObjects[ maxCollectedEntities ];
+ int count = UTIL_EntitiesInBox( pObjects, maxCollectedEntities, m_groundPos - vecSize, m_groundPos + vecSize, 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 );
+ }
+ }
+
+ return Continue();
+}
diff --git a/game/server/tf/halloween/eyeball_boss/eyeball_behavior/eyeball_boss_emerge.h b/game/server/tf/halloween/eyeball_boss/eyeball_behavior/eyeball_boss_emerge.h
new file mode 100644
index 0000000..765e96c
--- /dev/null
+++ b/game/server/tf/halloween/eyeball_boss/eyeball_behavior/eyeball_boss_emerge.h
@@ -0,0 +1,28 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// eyeball_boss_emerge.h
+// The Halloween Boss emerging from the ground
+// Michael Booth, October 2011
+
+#ifndef EYEBALL_BOSS_EMERGE_H
+#define EYEBALL_BOSS_EMERGE_H
+
+//---------------------------------------------------------------------------------------------
+//---------------------------------------------------------------------------------------------
+class CEyeballBossEmerge : public Action< CEyeballBoss >
+{
+public:
+ virtual ActionResult< CEyeballBoss > OnStart( CEyeballBoss *me, Action< CEyeballBoss > *priorAction );
+ virtual ActionResult< CEyeballBoss > Update( CEyeballBoss *me, float interval );
+ virtual const char *GetName( void ) const { return "Emerge"; } // return name of this action
+
+private:
+ CountdownTimer m_riseTimer;
+ CountdownTimer m_rumbleTimer;
+ CountdownTimer m_killTimer;
+ Vector m_emergePos;
+ Vector m_groundPos;
+ float m_height;
+};
+
+
+#endif // EYEBALL_BOSS_EMERGE_H
diff --git a/game/server/tf/halloween/eyeball_boss/eyeball_behavior/eyeball_boss_emote.cpp b/game/server/tf/halloween/eyeball_boss/eyeball_behavior/eyeball_boss_emote.cpp
new file mode 100644
index 0000000..ebd9825
--- /dev/null
+++ b/game/server/tf/halloween/eyeball_boss/eyeball_behavior/eyeball_boss_emote.cpp
@@ -0,0 +1,56 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// eyeball_boss_emote.cpp
+// The 2011 Halloween Boss - play an animation
+// Michael Booth, October 2011
+
+#include "cbase.h"
+
+#include "../eyeball_boss.h"
+#include "eyeball_boss_emote.h"
+
+
+//---------------------------------------------------------------------------------------------
+CEyeballBossEmote::CEyeballBossEmote( int animationSequence, const char *soundName, Action< CEyeballBoss > *nextAction )
+{
+ m_animationSequence = animationSequence;
+ m_soundName = soundName;
+ m_nextAction = nextAction;
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CEyeballBoss > CEyeballBossEmote::OnStart( CEyeballBoss *me, Action< CEyeballBoss > *priorAction )
+{
+ if ( m_animationSequence )
+ {
+ me->SetSequence( m_animationSequence );
+ me->SetPlaybackRate( 1.0f );
+ me->SetCycle( 0 );
+ me->ResetSequenceInfo();
+ }
+
+ if ( m_soundName )
+ {
+ me->EmitSound( m_soundName );
+ }
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CEyeballBoss > CEyeballBossEmote::Update( CEyeballBoss *me, float interval )
+{
+ if ( me->IsSequenceFinished() )
+ {
+ if ( m_nextAction )
+ {
+ return ChangeTo( m_nextAction );
+ }
+
+ return Done();
+ }
+
+ return Continue();
+}
+
diff --git a/game/server/tf/halloween/eyeball_boss/eyeball_behavior/eyeball_boss_emote.h b/game/server/tf/halloween/eyeball_boss/eyeball_behavior/eyeball_boss_emote.h
new file mode 100644
index 0000000..889b3b9
--- /dev/null
+++ b/game/server/tf/halloween/eyeball_boss/eyeball_behavior/eyeball_boss_emote.h
@@ -0,0 +1,28 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// eyeball_boss_emote.h
+// The 2011 Halloween Boss - play an animation
+// Michael Booth, October 2011
+
+#ifndef EYEBALL_BOSS_EMOTE_H
+#define EYEBALL_BOSS_EMOTE_H
+
+//---------------------------------------------------------------------------------------------
+//---------------------------------------------------------------------------------------------
+class CEyeballBossEmote : public Action< CEyeballBoss >
+{
+public:
+ CEyeballBossEmote( int animationSequence, const char *soundName, Action< CEyeballBoss > *nextAction = NULL );
+ virtual ~CEyeballBossEmote() { }
+
+ virtual ActionResult< CEyeballBoss > OnStart( CEyeballBoss *me, Action< CEyeballBoss > *priorAction );
+ virtual ActionResult< CEyeballBoss > Update( CEyeballBoss *me, float interval );
+
+ virtual const char *GetName( void ) const { return "Emote"; } // return name of this action
+
+private:
+ int m_animationSequence;
+ const char *m_soundName;
+ Action< CEyeballBoss > *m_nextAction;
+};
+
+#endif // EYEBALL_BOSS_EMOTE_H
diff --git a/game/server/tf/halloween/eyeball_boss/eyeball_behavior/eyeball_boss_idle.cpp b/game/server/tf/halloween/eyeball_boss/eyeball_behavior/eyeball_boss_idle.cpp
new file mode 100644
index 0000000..6bae53e
--- /dev/null
+++ b/game/server/tf/halloween/eyeball_boss/eyeball_behavior/eyeball_boss_idle.cpp
@@ -0,0 +1,242 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// eyeball_boss_idle.cpp
+// The 2011 Halloween Boss
+// Michael Booth, October 2011
+
+#include "cbase.h"
+
+#include "particle_parse.h"
+#include "tf_gamerules.h"
+
+#include "../eyeball_boss.h"
+#include "eyeball_boss_idle.h"
+#include "eyeball_boss_teleport.h"
+#include "eyeball_boss_notice.h"
+#include "eyeball_boss_emote.h"
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CEyeballBoss > CEyeballBossIdle::OnStart( CEyeballBoss *me, Action< CEyeballBoss > *priorAction )
+{
+ m_attacker = NULL;
+
+ m_talkTimer.Start( RandomFloat( 3.0f, 5.0f ) );
+
+ m_lifeTimer.Start( me->IsSpell() ? tf_eyeball_boss_lifetime_spell.GetFloat() : tf_eyeball_boss_lifetime.GetFloat() );
+
+ m_moveTimer.Start( RandomFloat( 10.0f, 15.0f ) );
+
+ m_lastWarnTime = 0.0f;
+ m_isLaughReady = false;
+ m_lastHealth = me->GetHealth();
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CEyeballBoss > CEyeballBossIdle::Update( CEyeballBoss *me, float interval )
+{
+ if ( tf_eyeball_boss_debug_orientation.GetBool() )
+ {
+ CBaseCombatCharacter *target = me->FindClosestVisibleVictim();
+
+ if ( target )
+ {
+ me->GetBodyInterface()->AimHeadTowards( target );
+ }
+
+ if ( me->GetInjuryRate() > 0.0001f )
+ {
+ DevMsg( "%3.2f: DPS = %3.2f, Max DPS = %3.2f\n", gpGlobals->curtime, me->GetInjuryRate(), me->GetMaxInjuryRate() );
+ }
+
+ return Continue();
+ }
+
+ if ( !me->IsSpell() )
+ {
+ if ( m_lifeTimer.GetRemainingTime() < 10.0f && m_lastWarnTime > 10.0f )
+ {
+ IGameEvent *event = gameeventmanager->CreateEvent( "eyeball_boss_escape_imminent" );
+ if ( event )
+ {
+ event->SetInt( "level", me->GetLevel() );
+ event->SetInt( "time_remaining", 10 );
+ gameeventmanager->FireEvent( event );
+ }
+ }
+ else if ( m_lifeTimer.GetRemainingTime() < 30.0f && m_lastWarnTime > 30.0f )
+ {
+ IGameEvent *event = gameeventmanager->CreateEvent( "eyeball_boss_escape_imminent" );
+ if ( event )
+ {
+ event->SetInt( "level", me->GetLevel() );
+ event->SetInt( "time_remaining", 30 );
+ gameeventmanager->FireEvent( event );
+ }
+ }
+ else if ( m_lifeTimer.GetRemainingTime() < 60.0f && m_lastWarnTime > 60.0f )
+ {
+ IGameEvent *event = gameeventmanager->CreateEvent( "eyeball_boss_escape_imminent" );
+ if ( event )
+ {
+ event->SetInt( "level", me->GetLevel() );
+ event->SetInt( "time_remaining", 60 );
+ gameeventmanager->FireEvent( event );
+ }
+ }
+ m_lastWarnTime = m_lifeTimer.GetRemainingTime();
+ }
+
+ // just leave
+ if ( me->IsSpell() && m_lifeTimer.IsElapsed() )
+ {
+ return ChangeTo( new CEyeballBossEscape, "Escaping..." );
+ }
+
+ // teleport to a new place
+ if ( m_moveTimer.IsElapsed() && !me->IsSpell() )
+ {
+ // get low
+ me->GetLocomotionInterface()->SetDesiredAltitude( 0.0f );
+
+ // only teleport if we're low enough for players to reach the vortex we leave behind
+ Vector ground = me->WorldSpaceCenter();
+ if ( TheNavMesh->GetSimpleGroundHeight( me->WorldSpaceCenter(), &ground.z ) )
+ {
+ const float maxTeleportHeight = 300.0f;
+
+ // make sure the ground is actually reachable by the players
+ CNavArea *area = TheNavMesh->GetNearestNavArea( ground, true, maxTeleportHeight * 1.5f );
+
+ if ( area )
+ {
+ if ( me->WorldSpaceCenter().z - ground.z < maxTeleportHeight )
+ {
+ if ( m_lifeTimer.IsElapsed() )
+ {
+ return ChangeTo( new CEyeballBossEscape, "Escaping..." );
+ }
+
+ m_moveTimer.Start( RandomFloat( 10.0f, 15.0f ) );
+
+ return SuspendFor( new CEyeballBossTeleport, "Moving..." );
+ }
+ }
+ }
+ }
+ else
+ {
+ // resume hovering
+ me->GetLocomotionInterface()->SetDesiredAltitude( tf_eyeball_boss_hover_height.GetFloat() );
+ }
+
+ CBaseCombatCharacter *victim = me->FindClosestVisibleVictim();
+ if ( victim )
+ {
+ me->SetVictim( victim );
+
+ return SuspendFor( new CEyeballBossNotice, "Target found..." );
+ }
+
+ if ( m_attacker != NULL )
+ {
+ // look at attacker that just injured us
+ me->GetBodyInterface()->AimHeadTowards( m_attacker );
+
+ m_attacker = NULL;
+ m_lookAroundTimer.Start( RandomFloat( 0.5f, 2.0f ) );
+ }
+ else
+ {
+ if ( m_lookAroundTimer.IsElapsed() )
+ {
+ // look around
+ m_lookAroundTimer.Start( RandomFloat( 2.0f, 4.0f ) );
+
+ Vector target;
+
+ SinCos( RandomFloat( -3.141592f, 3.141592f ), &target.y, &target.x );
+ target.z = 0.0f;
+
+ me->GetBodyInterface()->AimHeadTowards( me->GetAbsOrigin() + 100.0f * target );
+ }
+
+ // we have no target - if someone died recently, laugh
+ if ( m_isLaughReady )
+ {
+ m_isLaughReady = false;
+ return SuspendFor( new CEyeballBossEmote( me->LookupSequence( "laugh" ), "Halloween.EyeballBossLaugh" ), "Taunt our victim" );
+ }
+ }
+
+ int animSequence = 0;
+
+ if ( me->IsEnraged() )
+ {
+ animSequence = me->LookupSequence( "lookaround3" );
+ }
+ else if ( me->IsGrumpy() )
+ {
+ animSequence = me->LookupSequence( "lookaround2" );
+ }
+ else
+ {
+ animSequence = me->LookupSequence( "lookaround1" );
+ }
+
+ if ( animSequence )
+ {
+ if ( me->GetSequence() != animSequence || me->IsSequenceFinished() )
+ {
+ me->SetSequence( animSequence );
+ me->SetPlaybackRate( 1.0f );
+ me->SetCycle( 0 );
+ me->ResetSequenceInfo();
+ }
+ }
+
+ if ( m_talkTimer.IsElapsed() )
+ {
+ if ( me->IsEnraged() )
+ {
+ me->EmitSound( "Halloween.EyeballBossRage" );
+ m_talkTimer.Start( RandomFloat( 1.0f, 2.0f ) );
+ }
+ else
+ {
+ me->EmitSound( "Halloween.EyeballBossIdle" );
+ m_talkTimer.Start( RandomFloat( 3.0f, 5.0f ) );
+ }
+ }
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CEyeballBoss > CEyeballBossIdle::OnResume( CEyeballBoss *me, Action< CEyeballBoss > *interruptingAction )
+{
+ m_talkTimer.Start( RandomFloat( 3.0f, 5.0f ) );
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+EventDesiredResult< CEyeballBoss > CEyeballBossIdle::OnInjured( CEyeballBoss *me, const CTakeDamageInfo &info )
+{
+ m_attacker = info.GetAttacker();
+
+ return TryContinue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+EventDesiredResult< CEyeballBoss > CEyeballBossIdle::OnOtherKilled( CEyeballBoss *me, CBaseCombatCharacter *victim, const CTakeDamageInfo &info )
+{
+ m_isLaughReady = true;
+ return TryContinue();
+}
+
diff --git a/game/server/tf/halloween/eyeball_boss/eyeball_behavior/eyeball_boss_idle.h b/game/server/tf/halloween/eyeball_boss/eyeball_behavior/eyeball_boss_idle.h
new file mode 100644
index 0000000..99f853e
--- /dev/null
+++ b/game/server/tf/halloween/eyeball_boss/eyeball_behavior/eyeball_boss_idle.h
@@ -0,0 +1,37 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// eyeball_boss_idle.h
+// The 2011 Halloween Boss
+// Michael Booth, October 2011
+
+#ifndef EYEBALL_BOSS_IDLE_H
+#define EYEBALL_BOSS_IDLE_H
+
+//---------------------------------------------------------------------------------------------
+//---------------------------------------------------------------------------------------------
+class CEyeballBossIdle : public Action< CEyeballBoss >
+{
+public:
+ virtual ActionResult< CEyeballBoss > OnStart( CEyeballBoss *me, Action< CEyeballBoss > *priorAction );
+ virtual ActionResult< CEyeballBoss > Update( CEyeballBoss *me, float interval );
+
+ virtual ActionResult< CEyeballBoss > OnResume( CEyeballBoss *me, Action< CEyeballBoss > *interruptingAction );
+
+ virtual EventDesiredResult< CEyeballBoss > OnInjured( CEyeballBoss *me, const CTakeDamageInfo &info );
+ virtual EventDesiredResult< CEyeballBoss > OnOtherKilled( CEyeballBoss *me, CBaseCombatCharacter *victim, const CTakeDamageInfo &info );
+
+ virtual const char *GetName( void ) const { return "Idle"; } // return name of this action
+
+private:
+ CountdownTimer m_lookAroundTimer;
+ PathFollower m_path;
+ CountdownTimer m_repathTimer;
+ CHandle< CBaseEntity > m_attacker;
+ CountdownTimer m_talkTimer;
+ CountdownTimer m_lifeTimer;
+ CountdownTimer m_moveTimer;
+ float m_lastWarnTime;
+ int m_lastHealth;
+ bool m_isLaughReady;
+};
+
+#endif // EYEBALL_BOSS_IDLE_H \ No newline at end of file
diff --git a/game/server/tf/halloween/eyeball_boss/eyeball_behavior/eyeball_boss_launch_rockets.cpp b/game/server/tf/halloween/eyeball_boss/eyeball_behavior/eyeball_boss_launch_rockets.cpp
new file mode 100644
index 0000000..1f41288
--- /dev/null
+++ b/game/server/tf/halloween/eyeball_boss/eyeball_behavior/eyeball_boss_launch_rockets.cpp
@@ -0,0 +1,182 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// eyeball_boss_launch_rockets.cpp
+// The 2011 Halloween Boss
+// Michael Booth, October 2011
+
+#include "cbase.h"
+
+#include "tf_projectile_rocket.h"
+#include "tf_obj_sentrygun.h"
+
+#include "../eyeball_boss.h"
+#include "eyeball_boss_launch_rockets.h"
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CEyeballBoss > CEyeballBossLaunchRockets::OnStart( CEyeballBoss *me, Action< CEyeballBoss > *priorAction )
+{
+ if ( me->GetVictim() == NULL )
+ {
+ return Done( "No target" );
+ }
+
+ m_lastTargetPosition = me->GetVictim()->GetAbsOrigin();
+
+ const char *firingAnimation = NULL;
+
+ if ( me->IsEnraged() )
+ {
+ m_rocketsLeft = 3;
+ m_initialDelayTimer.Start( 0.25f );
+ firingAnimation = "firing3";
+
+ me->EmitSound( "Halloween.EyeballBossRage" );
+ }
+ else if ( me->IsGrumpy() )
+ {
+ m_rocketsLeft = 3;
+ m_initialDelayTimer.Start( 0.25f );
+ firingAnimation = "firing2";
+ }
+ else
+ {
+ m_rocketsLeft = 1;
+ m_initialDelayTimer.Start( 0.5f );
+ firingAnimation = "firing1";
+ }
+
+ int animSequence = me->LookupSequence( firingAnimation );
+ if ( animSequence )
+ {
+ me->SetSequence( animSequence );
+ me->SetPlaybackRate( 1.0f );
+ me->SetCycle( 0 );
+ me->ResetSequenceInfo();
+ }
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CEyeballBoss > CEyeballBossLaunchRockets::Update( CEyeballBoss *me, float interval )
+{
+ if ( me->GetVictim() && me->GetVictim()->IsAlive() )
+ {
+ m_lastTargetPosition = me->GetVictim()->GetAbsOrigin();
+
+ // target upper part of sentry guns
+ if ( me->GetVictim()->IsBaseObject() )
+ {
+ CObjectSentrygun *sentry = dynamic_cast< CObjectSentrygun * >( const_cast< CBaseCombatCharacter * >( me->GetVictim() ) );
+ if ( sentry )
+ {
+ m_lastTargetPosition = sentry->EyePosition();
+ }
+ }
+
+ // circle around them
+ Vector forward, right;
+ me->GetVectors( &forward, &right, NULL );
+
+ me->GetLocomotionInterface()->SetDesiredSpeed( tf_eyeball_boss_speed.GetFloat() );
+
+ if ( !me->IsSpell() )
+ {
+ if ( me->GetVictim()->entindex() & 0x1 )
+ {
+ me->GetLocomotionInterface()->Approach( 100.0f * right + me->WorldSpaceCenter() );
+ }
+ else
+ {
+ me->GetLocomotionInterface()->Approach( -100.0f * right + me->WorldSpaceCenter() );
+ }
+ }
+
+ me->GetBodyInterface()->AimHeadTowards( me->GetVictim() );
+ }
+ else
+ {
+ me->GetBodyInterface()->AimHeadTowards( m_lastTargetPosition );
+ }
+
+ // fire rockets
+ if ( m_initialDelayTimer.IsElapsed() && m_launchTimer.IsElapsed() )
+ {
+ --m_rocketsLeft;
+ m_launchTimer.Start( 0.3f );
+
+ const float rocketDamage = 50.0f;
+
+ // if I'm enraged, shoot full speed rockets, otherwise shoot slow ones
+ float speedFactor = me->IsEnraged() ? 1.0f : 0.3f;
+ float rocketSpeed = 1100.0f * speedFactor;
+
+ Vector targetSpot = m_lastTargetPosition;
+
+ if ( me->IsEnraged() && me->GetVictim() )
+ {
+ // lead our target
+ float rangeBetween = me->GetRangeTo( targetSpot );
+
+ const float veryCloseRange = 150.0f;
+ if ( rangeBetween > veryCloseRange )
+ {
+ float timeToTravel = rangeBetween / rocketSpeed;
+
+ Vector leadOffset = timeToTravel * me->GetVictim()->GetAbsVelocity();
+
+ CTraceFilterNoNPCsOrPlayer filter( me, COLLISION_GROUP_NONE );
+ trace_t result;
+
+ UTIL_TraceLine( me->WorldSpaceCenter(), me->GetVictim()->GetAbsOrigin() + leadOffset, MASK_SOLID_BRUSHONLY, &filter, &result );
+
+ if ( result.DidHit() )
+ {
+ const float errorTolerance = 300.0f;
+ if ( ( result.endpos - targetSpot ).IsLengthGreaterThan( errorTolerance ) )
+ {
+ // rocket is going to hit an obstruction not near our victim - just aim right for them and hope for splash
+ leadOffset = vec3_origin;
+ }
+ }
+
+ targetSpot = me->GetVictim()->GetAbsOrigin() + leadOffset;
+ }
+ }
+
+ QAngle launchAngles;
+ Vector toTarget = targetSpot - me->WorldSpaceCenter();
+ VectorAngles( toTarget, launchAngles );
+
+ CBaseEntity *pScorer = me->GetOwnerEntity() ? me->GetOwnerEntity() : me;
+
+ CTFProjectile_Rocket *pRocket = CTFProjectile_Rocket::Create( me, me->WorldSpaceCenter(), launchAngles, me, pScorer );
+ if ( pRocket )
+ {
+ pRocket->SetModel( "models/props_halloween/eyeball_projectile.mdl" );
+
+ Vector forward;
+ AngleVectors( launchAngles, &forward, NULL, NULL );
+
+ Vector vecVelocity = forward * rocketSpeed;
+ pRocket->SetAbsVelocity( vecVelocity );
+ pRocket->SetupInitialTransmittedGrenadeVelocity( vecVelocity );
+
+ pRocket->EmitSound( "Weapon_RPG.SingleCrit" );
+ pRocket->SetDamage( rocketDamage );
+ pRocket->SetCritical( !me->IsSpell() );
+ pRocket->SetEyeBallRocket( !me->IsSpell() );
+ pRocket->SetSpell( me->IsSpell() );
+ pRocket->ChangeTeam( me->GetTeamNumber() );
+ }
+
+ if ( !m_rocketsLeft )
+ {
+ return Done();
+ }
+ }
+
+ return Continue();
+}
+
diff --git a/game/server/tf/halloween/eyeball_boss/eyeball_behavior/eyeball_boss_launch_rockets.h b/game/server/tf/halloween/eyeball_boss/eyeball_behavior/eyeball_boss_launch_rockets.h
new file mode 100644
index 0000000..f2b2772
--- /dev/null
+++ b/game/server/tf/halloween/eyeball_boss/eyeball_behavior/eyeball_boss_launch_rockets.h
@@ -0,0 +1,30 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// eyeball_boss_launch_rockets.h
+// The 2011 Halloween Boss
+// Michael Booth, October 2011
+
+#ifndef EYEBALL_BOSS_LAUNCH_ROCKETS_H
+#define EYEBALL_BOSS_LAUNCH_ROCKETS_H
+
+
+//---------------------------------------------------------------------------------------------
+//---------------------------------------------------------------------------------------------
+class CEyeballBossLaunchRockets : public Action< CEyeballBoss >
+{
+public:
+ virtual ActionResult< CEyeballBoss > OnStart( CEyeballBoss *me, Action< CEyeballBoss > *priorAction );
+ virtual ActionResult< CEyeballBoss > Update( CEyeballBoss *me, float interval );
+
+ virtual const char *GetName( void ) const { return "LaunchRockets"; } // return name of this action
+
+private:
+ CountdownTimer m_initialDelayTimer;
+
+ CountdownTimer m_launchTimer;
+ int m_rocketsLeft;
+
+ Vector m_lastTargetPosition;
+};
+
+
+#endif // EYEBALL_BOSS_LAUNCH_ROCKETS_H
diff --git a/game/server/tf/halloween/eyeball_boss/eyeball_behavior/eyeball_boss_notice.cpp b/game/server/tf/halloween/eyeball_boss/eyeball_behavior/eyeball_boss_notice.cpp
new file mode 100644
index 0000000..85e284d
--- /dev/null
+++ b/game/server/tf/halloween/eyeball_boss/eyeball_behavior/eyeball_boss_notice.cpp
@@ -0,0 +1,43 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// eyeball_boss_notice.cpp
+// The 2011 Halloween Boss
+// Michael Booth, October 2011
+
+#include "cbase.h"
+
+#include "../eyeball_boss.h"
+#include "eyeball_boss_notice.h"
+#include "eyeball_boss_approach_target.h"
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CEyeballBoss > CEyeballBossNotice::OnStart( CEyeballBoss *me, Action< CEyeballBoss > *priorAction )
+{
+ m_timer.Start( 0.25f );
+
+ me->EmitSound( "Halloween.EyeballBossBecomeAlert" );
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CEyeballBoss > CEyeballBossNotice::Update( CEyeballBoss *me, float interval )
+{
+ CBaseEntity *victim = me->GetVictim();
+
+ if ( !victim )
+ {
+ return Done( "Victim gone" );
+ }
+
+ me->GetBodyInterface()->AimHeadTowards( victim );
+
+ if ( m_timer.IsElapsed() )
+ {
+ return ChangeTo( new CEyeballBossApproachTarget, "Chasing victim" );
+ }
+
+ return Continue();
+}
+
diff --git a/game/server/tf/halloween/eyeball_boss/eyeball_behavior/eyeball_boss_notice.h b/game/server/tf/halloween/eyeball_boss/eyeball_behavior/eyeball_boss_notice.h
new file mode 100644
index 0000000..658cbf2
--- /dev/null
+++ b/game/server/tf/halloween/eyeball_boss/eyeball_behavior/eyeball_boss_notice.h
@@ -0,0 +1,23 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// eyeball_boss_notice.h
+// The 2011 Halloween Boss
+// Michael Booth, October 2011
+
+#ifndef EYEBALL_BOSS_NOTICE_H
+#define EYEBALL_BOSS_NOTICE_H
+
+//---------------------------------------------------------------------------------------------
+//---------------------------------------------------------------------------------------------
+class CEyeballBossNotice : public Action< CEyeballBoss >
+{
+public:
+ virtual ActionResult< CEyeballBoss > OnStart( CEyeballBoss *me, Action< CEyeballBoss > *priorAction );
+ virtual ActionResult< CEyeballBoss > Update( CEyeballBoss *me, float interval );
+
+ virtual const char *GetName( void ) const { return "Notice"; } // return name of this action
+
+private:
+ CountdownTimer m_timer;
+};
+
+#endif // EYEBALL_BOSS_NOTICE_H
diff --git a/game/server/tf/halloween/eyeball_boss/eyeball_behavior/eyeball_boss_stunned.cpp b/game/server/tf/halloween/eyeball_boss/eyeball_behavior/eyeball_boss_stunned.cpp
new file mode 100644
index 0000000..8cfc342
--- /dev/null
+++ b/game/server/tf/halloween/eyeball_boss/eyeball_behavior/eyeball_boss_stunned.cpp
@@ -0,0 +1,60 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// eyeball_boss_stunned.cpp
+// The 2011 Halloween Boss
+// Michael Booth, October 2011
+
+#include "cbase.h"
+
+#include "../eyeball_boss.h"
+#include "eyeball_boss_stunned.h"
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CEyeballBoss > CEyeballBossStunned::OnStart( CEyeballBoss *me, Action< CEyeballBoss > *priorAction )
+{
+ m_stunTimer.Start( 5.0f );
+
+ int animSequence = me->LookupSequence( "stunned" );
+ if ( animSequence )
+ {
+ me->SetSequence( animSequence );
+ me->SetPlaybackRate( 1.0f );
+ me->SetCycle( 0 );
+ me->ResetSequenceInfo();
+ }
+
+ me->EmitSound( "Halloween.EyeballBossStunned" );
+
+ // limit the total amount of damage we can take while stunned
+ me->SetDamageLimit( me->GetMaxHealth() / 3 );
+
+ // sink
+ me->GetLocomotionInterface()->SetDesiredAltitude( 0.0f );
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CEyeballBoss > CEyeballBossStunned::Update( CEyeballBoss *me, float interval )
+{
+ if ( m_stunTimer.IsElapsed() )
+ {
+ // get mad and retaliate
+ me->BecomeEnraged( 20.0f );
+
+ return Done();
+ }
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+void CEyeballBossStunned::OnEnd( CEyeballBoss *me, Action< CEyeballBoss > *nextAction )
+{
+ me->RemoveDamageLimit();
+
+ // resume hovering
+ me->GetLocomotionInterface()->SetDesiredAltitude( tf_eyeball_boss_hover_height.GetFloat() );
+}
diff --git a/game/server/tf/halloween/eyeball_boss/eyeball_behavior/eyeball_boss_stunned.h b/game/server/tf/halloween/eyeball_boss/eyeball_behavior/eyeball_boss_stunned.h
new file mode 100644
index 0000000..9bf26f2
--- /dev/null
+++ b/game/server/tf/halloween/eyeball_boss/eyeball_behavior/eyeball_boss_stunned.h
@@ -0,0 +1,31 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// eyeball_boss_stunned.h
+// The 2011 Halloween Boss
+// Michael Booth, October 2011
+
+#ifndef EYEBALL_BOSS_STUNNED_H
+#define EYEBALL_BOSS_STUNNED_H
+
+//---------------------------------------------------------------------------------------------
+//---------------------------------------------------------------------------------------------
+class CEyeballBossStunned : public Action< CEyeballBoss >
+{
+public:
+ virtual ActionResult< CEyeballBoss > OnStart( CEyeballBoss *me, Action< CEyeballBoss > *priorAction );
+ virtual ActionResult< CEyeballBoss > Update( CEyeballBoss *me, float interval );
+ virtual void OnEnd( CEyeballBoss *me, Action< CEyeballBoss > *nextAction );
+
+ virtual EventDesiredResult< CEyeballBoss > OnInjured( CEyeballBoss *me, const CTakeDamageInfo &info )
+ {
+ // don't get stunned while stunned
+ return TryToSustain( RESULT_CRITICAL );
+ }
+
+ virtual const char *GetName( void ) const { return "Stunned"; } // return name of this action
+
+private:
+ CountdownTimer m_stunTimer;
+ float m_spinRate;
+};
+
+#endif // EYEBALL_BOSS_STUNNED_H
diff --git a/game/server/tf/halloween/eyeball_boss/eyeball_behavior/eyeball_boss_teleport.cpp b/game/server/tf/halloween/eyeball_boss/eyeball_behavior/eyeball_boss_teleport.cpp
new file mode 100644
index 0000000..859e5f5
--- /dev/null
+++ b/game/server/tf/halloween/eyeball_boss/eyeball_behavior/eyeball_boss_teleport.cpp
@@ -0,0 +1,148 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// eyeball_boss_teleport.cpp
+// The 2011 Halloween Boss
+// Michael Booth, October 2011
+
+#include "cbase.h"
+
+#include "particle_parse.h"
+#include "tf/halloween/eyeball_boss/teleport_vortex.h"
+#include "../eyeball_boss.h"
+#include "eyeball_boss_teleport.h"
+#include "player_vs_environment/monster_resource.h"
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CEyeballBoss > CEyeballBossTeleport::OnStart( CEyeballBoss *me, Action< CEyeballBoss > *priorAction )
+{
+ m_state = TELEPORTING_OUT;
+
+ int animSequence = me->LookupSequence( "teleport_out" );
+ if ( animSequence )
+ {
+ me->SetSequence( animSequence );
+ me->SetPlaybackRate( 1.0f );
+ me->SetCycle( 0 );
+ me->ResetSequenceInfo();
+ }
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CEyeballBoss > CEyeballBossTeleport::Update( CEyeballBoss *me, float interval )
+{
+ if ( me->IsSequenceFinished() )
+ {
+ switch( m_state )
+ {
+ case TELEPORTING_OUT:
+ {
+ CTeleportVortex *vortex = (CTeleportVortex *)CBaseEntity::Create( "teleport_vortex", me->GetAbsOrigin(), vec3_angle );
+ if ( vortex )
+ {
+ vortex->SetupVortex( false );
+ }
+
+ DispatchParticleEffect( "eyeboss_tp_normal", me->GetAbsOrigin(), me->GetAbsAngles() );
+
+ me->EmitSound( "Halloween.EyeballBossTeleport" );
+
+ me->AddEffects( EF_NOINTERP | EF_NODRAW );
+
+ me->SetAbsOrigin( me->PickNewSpawnSpot() + Vector( 0, 0, 75.0f ) );
+
+ // wait on the other side for a moment
+ m_state = TELEPORTING_IN;
+ }
+ break;
+
+ case TELEPORTING_IN:
+ {
+ me->RemoveEffects( EF_NOINTERP | EF_NODRAW );
+
+ DispatchParticleEffect( "eyeboss_tp_normal", me->GetAbsOrigin(), me->GetAbsAngles() );
+
+ int animSequence = me->LookupSequence( "teleport_in" );
+ if ( animSequence )
+ {
+ me->SetSequence( animSequence );
+ me->SetPlaybackRate( 1.0f );
+ me->SetCycle( 0 );
+ me->ResetSequenceInfo();
+ }
+
+ m_state = DONE;
+ }
+ break;
+
+ case DONE:
+ return Done();
+ }
+ }
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+//---------------------------------------------------------------------------------------------
+ActionResult< CEyeballBoss > CEyeballBossEscape::OnStart( CEyeballBoss *me, Action< CEyeballBoss > *priorAction )
+{
+ int animSequence = me->LookupSequence( "escape" );
+ if ( animSequence )
+ {
+ me->SetSequence( animSequence );
+ me->SetPlaybackRate( 1.0f );
+ me->SetCycle( 0 );
+ me->ResetSequenceInfo();
+ }
+
+ me->EmitSound( "Halloween.EyeballBossLaugh" );
+
+ UTIL_LogPrintf( "HALLOWEEN: eyeball_escaped (max_dps %3.2f) (health %d) (level %d)\n", me->GetMaxInjuryRate(), me->GetHealth(), me->GetLevel() );
+
+ return Continue();
+}
+
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CEyeballBoss > CEyeballBossEscape::Update( CEyeballBoss *me, float interval )
+{
+ if ( me->IsSequenceFinished() )
+ {
+ if ( me->IsSpell() )
+ {
+ me->EmitSound( "Halloween.spell_spawn_boss_disappear" );
+ }
+
+ DispatchParticleEffect( "eyeboss_tp_escape", me->GetAbsOrigin(), me->GetAbsAngles() );
+
+ if ( g_pMonsterResource )
+ {
+ g_pMonsterResource->HideBossHealthMeter();
+ }
+
+ UTIL_Remove( me );
+
+ me->SetVictim( NULL );
+
+ if ( !me->IsSpell() )
+ {
+ IGameEvent *event = gameeventmanager->CreateEvent( "eyeball_boss_escaped" );
+ if ( event )
+ {
+ event->SetInt( "level", me->GetLevel() );
+ gameeventmanager->FireEvent( event );
+ }
+ }
+
+ // reset back to normal level
+ me->ResetLevel();
+
+ return Done();
+ }
+
+ return Continue();
+}
diff --git a/game/server/tf/halloween/eyeball_boss/eyeball_behavior/eyeball_boss_teleport.h b/game/server/tf/halloween/eyeball_boss/eyeball_behavior/eyeball_boss_teleport.h
new file mode 100644
index 0000000..03cd088
--- /dev/null
+++ b/game/server/tf/halloween/eyeball_boss/eyeball_behavior/eyeball_boss_teleport.h
@@ -0,0 +1,45 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// eyeball_boss_teleport.h
+// The 2011 Halloween Boss
+// Michael Booth, October 2011
+
+#ifndef EYEBALL_BOSS_TELEPORT_H
+#define EYEBALL_BOSS_TELEPORT_H
+
+//---------------------------------------------------------------------------------------------
+//---------------------------------------------------------------------------------------------
+class CEyeballBossTeleport : public Action< CEyeballBoss >
+{
+public:
+ virtual ActionResult< CEyeballBoss > OnStart( CEyeballBoss *me, Action< CEyeballBoss > *priorAction );
+ virtual ActionResult< CEyeballBoss > Update( CEyeballBoss *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;
+};
+
+
+//---------------------------------------------------------------------------------------------
+//---------------------------------------------------------------------------------------------
+class CEyeballBossEscape : public Action< CEyeballBoss >
+{
+public:
+ virtual ActionResult< CEyeballBoss > OnStart( CEyeballBoss *me, Action< CEyeballBoss > *priorAction );
+ virtual ActionResult< CEyeballBoss > Update( CEyeballBoss *me, float interval );
+
+ virtual const char *GetName( void ) const { return "Escape"; } // return name of this action
+
+private:
+ CountdownTimer m_timer;
+};
+
+
+#endif // EYEBALL_BOSS_TELEPORT_H
diff --git a/game/server/tf/halloween/eyeball_boss/eyeball_boss.cpp b/game/server/tf/halloween/eyeball_boss/eyeball_boss.cpp
new file mode 100644
index 0000000..1aff63b
--- /dev/null
+++ b/game/server/tf/halloween/eyeball_boss/eyeball_boss.cpp
@@ -0,0 +1,1050 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// eyeball_boss.cpp
+// The 2011 Halloween Boss
+// Michael Booth, October 2011
+
+#include "cbase.h"
+
+#include "tf_player.h"
+#include "tf_gamerules.h"
+#include "tf_team.h"
+#include "tf_projectile_arrow.h"
+#include "tf_weapon_grenade_pipebomb.h"
+#include "tf_ammo_pack.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 "nav_mesh/tf_path_follower.h"
+#include "tf_obj_sentrygun.h"
+#include "bot/map_entities/tf_spawner.h"
+#include "tf_fx.h"
+#include "player_vs_environment/monster_resource.h"
+
+#include "eyeball_boss.h"
+#include "eyeball_behavior/eyeball_boss_behavior.h"
+#include "halloween/zombie/zombie.h"
+
+
+ConVar tf_eyeball_boss_debug( "tf_eyeball_boss_debug", "0", FCVAR_CHEAT );
+ConVar tf_eyeball_boss_debug_orientation( "tf_eyeball_boss_debug_orientation", "0", FCVAR_CHEAT );
+
+ConVar tf_eyeball_boss_lifetime( "tf_eyeball_boss_lifetime", "120", FCVAR_CHEAT );
+ConVar tf_eyeball_boss_lifetime_spell( "tf_eyeball_boss_lifetime_spell", "8", FCVAR_CHEAT );
+
+ConVar tf_eyeball_boss_speed( "tf_eyeball_boss_speed", "250", FCVAR_CHEAT );
+
+ConVar tf_eyeball_boss_hover_height( "tf_eyeball_boss_hover_height", "200", FCVAR_CHEAT );
+
+ConVar tf_eyeball_boss_acceleration( "tf_eyeball_boss_acceleration", "500", FCVAR_CHEAT );
+ConVar tf_eyeball_boss_horiz_damping( "tf_eyeball_boss_horiz_damping", "2", FCVAR_CHEAT );
+ConVar tf_eyeball_boss_vert_damping( "tf_eyeball_boss_vert_damping", "1", FCVAR_CHEAT );
+
+ConVar tf_eyeball_boss_attack_range( "tf_eyeball_boss_attack_range", "750", FCVAR_CHEAT );
+
+ConVar tf_eyeball_boss_health_base( "tf_eyeball_boss_health_base", "8000", FCVAR_CHEAT );
+ConVar tf_eyeball_boss_health_per_player( "tf_eyeball_boss_health_per_player", "400", FCVAR_CHEAT );
+extern ConVar tf_halloween_bot_min_player_count;
+
+ConVar tf_eyeball_boss_health_at_level_2( "tf_eyeball_boss_health_at_level_2", "17000", FCVAR_CHEAT );
+ConVar tf_eyeball_boss_health_per_level( "tf_eyeball_boss_health_per_level", "3000", FCVAR_CHEAT );
+
+
+LINK_ENTITY_TO_CLASS( eyeball_boss, CEyeballBoss );
+
+IMPLEMENT_SERVERCLASS_ST( CEyeballBoss, DT_EyeballBoss )
+
+ SendPropExclude( "DT_BaseEntity", "m_angRotation" ), // client has its own orientation logic
+ SendPropExclude( "DT_BaseEntity", "m_angAbsRotation" ), // client has its own orientation logic
+ SendPropVector( SENDINFO( m_lookAtSpot ), 0, SPROP_COORD ),
+ SendPropInt( SENDINFO( m_attitude ) ),
+
+END_SEND_TABLE()
+
+
+int CEyeballBoss::m_level = 1;
+
+IMPLEMENT_AUTO_LIST( IEyeballBossAutoList );
+
+//-----------------------------------------------------------------------------------------------------
+//-----------------------------------------------------------------------------------------------------
+CEyeballBoss::CEyeballBoss()
+{
+ ALLOCATE_INTENTION_INTERFACE( CEyeballBoss );
+
+ m_locomotor = new CEyeballBossLocomotion( this );
+ m_body = new CEyeballBossBody( this );
+ m_vision = new CDisableVision( this );
+
+ m_eyeOffset = vec3_origin;
+ m_target = NULL;
+ m_rageTimer.Invalidate();
+ m_victim = NULL;
+ m_lookAtSpot = vec3_origin;
+ m_attitude = EYEBALL_CALM;
+ m_damageLimit = -1;
+}
+
+
+//-----------------------------------------------------------------------------------------------------
+CEyeballBoss::~CEyeballBoss()
+{
+ DEALLOCATE_INTENTION_INTERFACE;
+
+ if ( m_vision )
+ delete m_vision;
+
+ if ( m_body )
+ delete m_body;
+
+ if ( m_locomotor )
+ delete m_locomotor;
+
+ IGameEvent *event = gameeventmanager->CreateEvent( "recalculate_truce" );
+ if ( event )
+ {
+ gameeventmanager->FireEvent( event, true );
+ }
+}
+
+void CEyeballBoss::PrecacheEyeballBoss()
+{
+ PrecacheModel( "models/props_halloween/halloween_demoeye.mdl" );
+ PrecacheModel( "models/props_halloween/eyeball_projectile.mdl" );
+
+ PrecacheScriptSound( "Halloween.EyeballBossIdle" );
+ PrecacheScriptSound( "Halloween.EyeballBossBecomeAlert" );
+ PrecacheScriptSound( "Halloween.EyeballBossAcquiredVictim" );
+ PrecacheScriptSound( "Halloween.EyeballBossStunned" );
+ PrecacheScriptSound( "Halloween.EyeballBossStunRecover" );
+ PrecacheScriptSound( "Halloween.EyeballBossLaugh" );
+ PrecacheScriptSound( "Halloween.EyeballBossBigLaugh" );
+ PrecacheScriptSound( "Halloween.EyeballBossDie" );
+ PrecacheScriptSound( "Halloween.EyeballBossEscapeSoon" );
+ PrecacheScriptSound( "Halloween.EyeballBossEscapeImminent" );
+ PrecacheScriptSound( "Halloween.EyeballBossEscaped" );
+ PrecacheScriptSound( "Halloween.EyeballBossDie" );
+ PrecacheScriptSound( "Halloween.EyeballBossTeleport" );
+ PrecacheScriptSound( "Halloween.HeadlessBossSpawnRumble" );
+
+ PrecacheScriptSound( "Halloween.EyeballBossBecomeEnraged" );
+ PrecacheScriptSound( "Halloween.EyeballBossRage" );
+ PrecacheScriptSound( "Halloween.EyeballBossCalmDown" );
+ PrecacheScriptSound( "Halloween.spell_spawn_boss_disappear" );
+
+ PrecacheScriptSound( "Halloween.MonoculusBossSpawn" );
+ PrecacheScriptSound( "Halloween.MonoculusBossDeath" );
+
+ PrecacheParticleSystem( "eyeboss_death" );
+ PrecacheParticleSystem( "eyeboss_aura_angry" );
+ PrecacheParticleSystem( "eyeboss_aura_grumpy" );
+ PrecacheParticleSystem( "eyeboss_aura_calm" );
+ PrecacheParticleSystem( "eyeboss_aura_stunned" );
+ PrecacheParticleSystem( "eyeboss_tp_normal" );
+ PrecacheParticleSystem( "eyeboss_tp_escape" );
+ PrecacheParticleSystem( "eyeboss_team_red" );
+ PrecacheParticleSystem( "eyeboss_team_blue" );
+}
+
+//-----------------------------------------------------------------------------------------------------
+void CEyeballBoss::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 );
+
+ PrecacheEyeballBoss();
+
+ CBaseEntity::SetAllowPrecache( bAllowPrecache );
+}
+
+
+//-----------------------------------------------------------------------------------------------------
+void CEyeballBoss::Spawn( void )
+{
+ Precache();
+
+ BaseClass::Spawn();
+
+ SetModel( "models/props_halloween/halloween_demoeye.mdl" );
+
+ int health = tf_eyeball_boss_health_base.GetInt();
+
+ if ( m_level > 1 )
+ {
+ // the Boss was defeated last time - he's tougher this time
+ health = tf_eyeball_boss_health_at_level_2.GetInt();
+
+ health += tf_eyeball_boss_health_per_level.GetInt() * ( m_level - 2 );
+ }
+ else
+ {
+ // scale the boss' health with the player count
+ int totalPlayers = GetGlobalTFTeam( TF_TEAM_BLUE )->GetNumPlayers() + GetGlobalTFTeam( TF_TEAM_RED )->GetNumPlayers();
+
+ if ( totalPlayers > tf_halloween_bot_min_player_count.GetInt() )
+ {
+ health += ( totalPlayers - tf_halloween_bot_min_player_count.GetInt() ) * tf_eyeball_boss_health_per_player.GetInt();
+ }
+ }
+
+ SetHealth( health );
+ SetMaxHealth( health );
+
+ m_homePos = GetAbsOrigin();
+
+ Vector mins( -50, -50, -50 );
+ Vector maxs( 50, 50, 50 );
+ CollisionProp()->SetSurroundingBoundsType( USE_SPECIFIED_BOUNDS, &mins, &maxs );
+ CollisionProp()->SetCollisionBounds( mins, maxs );
+
+ m_lookAtSpot = vec3_origin;
+
+ CBaseEntity *spawnPoint = NULL;
+ while( ( spawnPoint = gEntList.FindEntityByClassname( spawnPoint, "info_target" ) ) != NULL )
+ {
+ if ( FStrEq( STRING( spawnPoint->GetEntityName() ), "spawn_boss_alt" ) )
+ {
+ m_spawnSpotVector.AddToTail( spawnPoint );
+ }
+ }
+
+ if ( m_spawnSpotVector.Count() == 0 )
+ {
+ Warning( "No info_target entities named 'spawn_boss_alt' found!\n" );
+ }
+
+ // show Boss' health meter on HUD
+ if ( IsSpell() )
+ {
+ // this will force particle effect on the boss
+ m_attitude = GetTeamNumber() == TF_TEAM_RED ? EYEBALL_ANGRY : EYEBALL_CALM;
+ }
+ else
+ {
+ if ( g_pMonsterResource )
+ {
+ g_pMonsterResource->SetBossHealthPercentage( 1.0f );
+ }
+
+ m_attitude = EYEBALL_CALM;
+ }
+
+ IGameEvent *event = gameeventmanager->CreateEvent( "recalculate_truce" );
+ if ( event )
+ {
+ gameeventmanager->FireEvent( event, true );
+ }
+}
+
+
+//---------------------------------------------------------------------------------------------
+void CEyeballBoss::Update( void )
+{
+ BaseClass::Update();
+
+ m_attitude = EYEBALL_CALM;
+
+ if ( IsEnraged() )
+ {
+ if ( IsSpell() )
+ {
+ m_attitude = GetTeamNumber() == TF_TEAM_RED ? EYEBALL_ANGRY : EYEBALL_CALM;
+ m_nSkin = GetTeamNumber() == TF_TEAM_RED ? EYEBALL_TEAM_RED : EYEBALL_TEAM_BLUE;
+ }
+ else
+ {
+ m_nSkin = EYEBALL_RED_SKIN;
+ }
+
+ int angryPoseParameter = LookupPoseParameter( "anger" );
+ if ( angryPoseParameter >= 0 )
+ {
+ SetPoseParameter( angryPoseParameter, 1 );
+ }
+ }
+ else if ( IsGrumpy() )
+ {
+ m_nSkin = EYEBALL_NORMAL_SKIN;
+
+ int angryPoseParameter = LookupPoseParameter( "anger" );
+ if ( angryPoseParameter >= 0 )
+ {
+ SetPoseParameter( angryPoseParameter, 0.4f );
+ }
+ }
+ else
+ {
+ m_nSkin = EYEBALL_NORMAL_SKIN;
+
+ int angryPoseParameter = LookupPoseParameter( "anger" );
+ if ( angryPoseParameter >= 0 )
+ {
+ SetPoseParameter( angryPoseParameter, 0 );
+ }
+ }
+}
+
+
+//---------------------------------------------------------------------------------------------
+void CEyeballBoss::UpdateOnRemove( void )
+{
+ // In regular TF gameplay, g_pMonsterResource should always be non-null. The null check helps some server plugins though.
+ Assert( g_pMonsterResource != NULL );
+ if ( g_pMonsterResource )
+ {
+ g_pMonsterResource->HideBossHealthMeter();
+ }
+
+ BaseClass::UpdateOnRemove();
+}
+
+
+//---------------------------------------------------------------------------------------------
+void CEyeballBoss::JarateNearbyPlayers( float range )
+{
+ 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 ( IsRangeLessThan( playerVector[i], range ) &&
+ IsLineOfSightClear( playerVector[i], CBaseCombatCharacter::IGNORE_ACTORS ) )
+ {
+ playerVector[i]->m_Shared.AddCond( TF_COND_URINE, 10.0f );
+ }
+ }
+}
+
+
+//---------------------------------------------------------------------------------------------
+float EyeballBossModifyDamage( 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.25f;
+ }
+ else if ( pWeapon )
+ {
+ switch( pWeapon->GetWeaponID() )
+ {
+ case TF_WEAPON_FLAMETHROWER:
+ return info.GetDamage() * 0.5f;
+
+ case TF_WEAPON_MINIGUN:
+ return info.GetDamage() * 0.25f;
+ }
+ }
+
+ // unmodified
+ return info.GetDamage();
+}
+
+
+//---------------------------------------------------------------------------------------------
+int CEyeballBoss::OnTakeDamage_Alive( const CTakeDamageInfo &rawInfo )
+{
+ CTakeDamageInfo info = rawInfo;
+
+ if ( IsSelf( info.GetAttacker() ) )
+ {
+ // don't injure myself
+ return 0;
+ }
+
+ // have we reached our damage limit?
+ if ( m_damageLimit == 0 )
+ {
+ return 0;
+ }
+
+ if ( IsSpell() )
+ {
+ return 0;
+ }
+
+ int beforeHealth = GetHealth();
+
+ info.SetDamage( EyeballBossModifyDamage( info ) );
+
+ int result = BaseClass::OnTakeDamage_Alive( info );
+
+ // update boss health meter
+ float healthPercentage = (float)GetHealth() / (float)GetMaxHealth();
+
+ if ( g_pMonsterResource )
+ {
+ if ( healthPercentage <= 0.0f )
+ {
+ g_pMonsterResource->HideBossHealthMeter();
+ }
+ else
+ {
+ g_pMonsterResource->SetBossHealthPercentage( healthPercentage );
+ }
+ }
+
+ // do we have a damage limit?
+ if ( m_damageLimit >= 0 )
+ {
+ int actualDamage = beforeHealth - GetHealth();
+
+ m_damageLimit -= actualDamage;
+
+ if ( m_damageLimit < 0 )
+ {
+ m_damageLimit = 0;
+ }
+ }
+
+ return result;
+}
+
+
+//---------------------------------------------------------------------------------------------
+bool CEyeballBoss::ShouldCollide( int collisionGroup, int contentsMask ) const
+{
+ return BaseClass::ShouldCollide( collisionGroup, contentsMask );
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Update our last known nav area directly underneath us (since we fly)
+//-----------------------------------------------------------------------------
+void CEyeballBoss::UpdateLastKnownArea( void )
+{
+ if ( TheNavMesh->IsGenerating() )
+ {
+ ClearLastKnownArea();
+ return;
+ }
+
+ // find the area we are directly standing in
+ CNavArea *area = TheNavMesh->GetNearestNavArea( this, GETNAVAREA_CHECK_LOS, 500.0f );
+ if ( !area )
+ return;
+
+ // make sure we can actually use this area - if not, consider ourselves off the mesh
+ if ( !IsAreaTraversable( area ) )
+ return;
+
+ if ( area != m_lastNavArea )
+ {
+ // player entered a new nav area
+ if ( m_lastNavArea )
+ {
+ m_lastNavArea->DecrementPlayerCount( m_registeredNavTeam, entindex() );
+ m_lastNavArea->OnExit( this, area );
+ }
+
+ m_registeredNavTeam = GetTeamNumber();
+ area->IncrementPlayerCount( m_registeredNavTeam, entindex() );
+ area->OnEnter( this, m_lastNavArea );
+
+ OnNavAreaChanged( area, m_lastNavArea );
+
+ m_lastNavArea = area;
+ }
+}
+
+
+//---------------------------------------------------------------------------------------------
+CBaseCombatCharacter *CEyeballBoss::GetVictim( void ) const
+{
+ if ( m_victim == NULL )
+ return NULL;
+
+ if ( !m_victim->IsAlive() )
+ return NULL;
+
+ if ( IsInPurgatory( m_victim ) )
+ return NULL;
+
+ return m_victim;
+}
+
+
+//---------------------------------------------------------------------------------------------
+CBaseCombatCharacter *CEyeballBoss::FindClosestVisibleVictim( void )
+{
+ CBaseCombatCharacter *victim = NULL;
+ float victimRangeSq = FLT_MAX;
+
+ CUtlVector< CTFPlayer * > playerVector;
+ int nTargetTeam = TEAM_ANY;
+ if ( IsSpell() )
+ {
+ nTargetTeam = GetTeamNumber() == TF_TEAM_RED ? TF_TEAM_BLUE : TF_TEAM_RED;
+
+ for ( int i=0; i<TFGameRules()->GetBossCount(); ++i )
+ {
+ CBaseCombatCharacter *pBoss = TFGameRules()->GetActiveBoss( i );
+ if ( pBoss && !IsSelf( pBoss ) && pBoss->GetTeamNumber() != GetTeamNumber() )
+ {
+ float rangeSq = ( pBoss->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr();
+ if ( rangeSq < victimRangeSq )
+ {
+ if ( IsLineOfSightClear( pBoss ) )
+ {
+ victim = pBoss;
+ victimRangeSq = rangeSq;
+ }
+ }
+ }
+ }
+ }
+ CollectPlayers( &playerVector, nTargetTeam, COLLECT_ONLY_LIVING_PLAYERS );
+
+ for( int i=0; i<playerVector.Count(); ++i )
+ {
+ CTFPlayer *player = playerVector[i];
+
+ if ( IsInPurgatory( player ) )
+ continue;
+
+ if ( player->m_Shared.IsStealthed() )
+ {
+ if ( !player->m_Shared.InCond( TF_COND_BURNING ) &&
+ !player->m_Shared.InCond( TF_COND_URINE ) &&
+ !player->m_Shared.InCond( TF_COND_STEALTHED_BLINK ) &&
+ !player->m_Shared.InCond( TF_COND_BLEEDING ) )
+ {
+ // cloaked spies are invisible to us
+ continue;
+ }
+ }
+
+ // ignore player who disguises as my team
+ if ( player->m_Shared.InCond( TF_COND_DISGUISED ) && player->m_Shared.GetDisguiseTeam() == GetTeamNumber() )
+ {
+ continue;
+ }
+
+ // ignore ghost players
+ if ( player->m_Shared.InCond( TF_COND_HALLOWEEN_GHOST_MODE ) )
+ {
+ continue;
+ }
+
+ float rangeSq = ( player->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr();
+ if ( rangeSq < victimRangeSq )
+ {
+ if ( IsLineOfSightClear( player ) )
+ {
+ victim = player;
+ victimRangeSq = rangeSq;
+ }
+ }
+ }
+
+ for ( int i=0; i<IBaseObjectAutoList::AutoList().Count(); ++i )
+ {
+ CBaseObject* pObj = static_cast< CBaseObject* >( IBaseObjectAutoList::AutoList()[i] );
+ if ( pObj->GetTeamNumber() == GetTeamNumber() )
+ {
+ continue;
+ }
+
+ if ( pObj->ObjectType() == OBJ_SENTRYGUN )
+ {
+ float rangeSq = ( pObj->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr();
+ if ( rangeSq < victimRangeSq )
+ {
+ if ( IsLineOfSightClear( pObj ) )
+ {
+ victim = pObj;
+ victimRangeSq = rangeSq;
+ }
+ }
+ }
+ }
+
+ // find closest zombie
+ for ( int i=0; i<IZombieAutoList::AutoList().Count(); ++i )
+ {
+ CZombie* pZombie = static_cast< CZombie* >( IZombieAutoList::AutoList()[i] );
+ if ( pZombie->GetTeamNumber() == GetTeamNumber() )
+ {
+ continue;
+ }
+
+ float rangeSq = GetRangeSquaredTo( pZombie );
+ if ( rangeSq < victimRangeSq )
+ {
+ if ( IsLineOfSightClear( pZombie ) )
+ {
+ victim = pZombie;
+ victimRangeSq = rangeSq;
+ }
+ }
+ }
+
+ return victim;
+}
+
+
+//---------------------------------------------------------------------------------------------
+const Vector &CEyeballBoss::PickNewSpawnSpot( void ) const
+{
+ static Vector spot;
+
+ if ( m_spawnSpotVector.Count() == 0 )
+ {
+ spot = GetAbsOrigin();
+ }
+ else
+ {
+ spot = m_spawnSpotVector[ RandomInt( 0, m_spawnSpotVector.Count()-1 ) ]->GetAbsOrigin();
+ }
+
+ return spot;
+}
+
+
+//---------------------------------------------------------------------------------------------
+void CEyeballBoss::BecomeEnraged( float duration )
+{
+ if ( !IsEnraged() )
+ {
+ EmitSound( "Halloween.EyeballBossBecomeEnraged" );
+ }
+
+ m_rageTimer.Start( duration );
+}
+
+
+//---------------------------------------------------------------------------------------------
+void CEyeballBoss::LogPlayerInteraction( const char *verb, CTFPlayer *player )
+{
+ if ( !player || !verb )
+ return;
+
+ if ( !player->GetTeam() )
+ return;
+
+ CTFWeaponBase *weapon = player->GetActiveTFWeapon();
+ const char *weaponLogName = NULL;
+
+ if ( weapon )
+ {
+ weaponLogName = WeaponIdToAlias( weapon->GetWeaponID() );
+
+ CEconItemView *pItem = weapon->GetAttributeContainer()->GetItem();
+
+ if ( pItem && pItem->GetStaticData() )
+ {
+ if ( pItem->GetStaticData()->GetLogClassname() )
+ {
+ weaponLogName = pItem->GetStaticData()->GetLogClassname();
+ }
+ }
+ }
+
+ UTIL_LogPrintf( "HALLOWEEN: \"%s<%i><%s><%s>\" %s with \"%s\" (attacker_position \"%d %d %d\")\n",
+ player->GetPlayerName(),
+ player->GetUserID(),
+ player->GetNetworkIDString(),
+ player->GetTeam()->GetName(),
+ verb,
+ weaponLogName ? weaponLogName : "NoWeapon",
+ (int)player->GetAbsOrigin().x,
+ (int)player->GetAbsOrigin().y,
+ (int)player->GetAbsOrigin().z );
+}
+
+
+//---------------------------------------------------------------------------------------------
+//---------------------------------------------------------------------------------------------
+IMPLEMENT_INTENTION_INTERFACE( CEyeballBoss, CEyeballBossBehavior );
+
+
+//---------------------------------------------------------------------------------------------
+//---------------------------------------------------------------------------------------------
+CEyeballBossLocomotion::CEyeballBossLocomotion( INextBot *bot ) : ILocomotion( bot )
+{
+ Reset();
+}
+
+
+//---------------------------------------------------------------------------------------------
+CEyeballBossLocomotion::~CEyeballBossLocomotion()
+{
+}
+
+
+//---------------------------------------------------------------------------------------------
+// (EXTEND) reset to initial state
+void CEyeballBossLocomotion::Reset( void )
+{
+ m_velocity = vec3_origin;
+ m_acceleration = vec3_origin;
+ m_desiredSpeed = 0.0f;
+ m_currentSpeed = 0.0f;
+ m_forward = vec3_origin;
+ m_desiredAltitude = tf_eyeball_boss_hover_height.GetFloat();
+}
+
+
+#ifdef LOW_FLOAT_BUT_HANGS_UP_ON_LEDGES
+//---------------------------------------------------------------------------------------------
+void CEyeballBossLocomotion::MaintainAltitude( void )
+{
+ CBaseCombatCharacter *me = GetBot()->GetEntity();
+
+ trace_t result;
+ CTraceFilterSimpleClassnameList filter( me, COLLISION_GROUP_NONE );
+ filter.AddClassnameToIgnore( "eyeball_boss" );
+
+ UTIL_TraceLine( me->GetAbsOrigin(), me->GetAbsOrigin() + Vector( 0, 0, -2000.0f ), MASK_PLAYERSOLID_BRUSHONLY, &filter, &result );
+
+ float groundZ = result.endpos.z;
+
+ float currentAltitude = me->GetAbsOrigin().z - groundZ;
+
+ float desiredAltitude = GetDesiredAltitude();
+
+ float error = desiredAltitude - currentAltitude;
+
+ float accelZ = clamp( error, -tf_eyeball_boss_acceleration.GetFloat(), tf_eyeball_boss_acceleration.GetFloat() );
+
+ m_acceleration.z += accelZ;
+}
+#endif
+
+#define HI_FLOATING
+#ifdef HI_FLOATING
+//---------------------------------------------------------------------------------------------
+void CEyeballBossLocomotion::MaintainAltitude( void )
+{
+ CBaseCombatCharacter *me = GetBot()->GetEntity();
+
+ if ( !me->IsAlive() )
+ {
+ m_acceleration.x = 0.0f;
+ m_acceleration.y = 0.0f;
+ m_acceleration.z = -300.0f;
+
+ return;
+ }
+
+ trace_t result;
+ CTraceFilterSimpleClassnameList filter( me, COLLISION_GROUP_NONE );
+ filter.AddClassnameToIgnore( "eyeball_boss" );
+
+ // find ceiling
+ TraceHull( me->GetAbsOrigin(), me->GetAbsOrigin() + Vector( 0, 0, 1000.0f ),
+ me->WorldAlignMins(), me->WorldAlignMaxs(),
+ GetBot()->GetBodyInterface()->GetSolidMask(), &filter, &result );
+
+ float ceiling = result.endpos.z - me->GetAbsOrigin().z;
+
+ Vector aheadXY;
+
+ if ( IsAttemptingToMove() )
+ {
+ aheadXY.x = m_forward.x;
+ aheadXY.y = m_forward.y;
+ aheadXY.z = 0.0f;
+ aheadXY.NormalizeInPlace();
+ }
+ else
+ {
+ aheadXY = vec3_origin;
+ }
+
+ TraceHull( me->GetAbsOrigin() + Vector( 0, 0, ceiling ) + aheadXY * 50.0f,
+ me->GetAbsOrigin() + Vector( 0, 0, -2000.0f ) + aheadXY * 50.0f,
+ Vector( 1.25f * me->WorldAlignMins().x, 1.25f * me->WorldAlignMins().y, me->WorldAlignMins().z ),
+ Vector( 1.25f * me->WorldAlignMaxs().x, 1.25f * me->WorldAlignMaxs().y, me->WorldAlignMaxs().z ),
+ GetBot()->GetBodyInterface()->GetSolidMask(), &filter, &result );
+
+ float groundZ = result.endpos.z;
+
+ float currentAltitude = me->GetAbsOrigin().z - groundZ;
+
+ float desiredAltitude = GetDesiredAltitude();
+
+ float error = desiredAltitude - currentAltitude;
+
+ float accelZ = clamp( error, -tf_eyeball_boss_acceleration.GetFloat(), tf_eyeball_boss_acceleration.GetFloat() );
+
+ m_acceleration.z += accelZ;
+}
+#endif
+
+//---------------------------------------------------------------------------------------------
+// (EXTEND) update internal state
+void CEyeballBossLocomotion::Update( void )
+{
+ CBaseCombatCharacter *me = GetBot()->GetEntity();
+ const float deltaT = GetUpdateInterval();
+
+ 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( tf_eyeball_boss_horiz_damping.GetFloat(), tf_eyeball_boss_horiz_damping.GetFloat(), tf_eyeball_boss_vert_damping.GetFloat() );
+ Vector totalAccel = m_acceleration - m_velocity * damping;
+
+ m_velocity += totalAccel * deltaT;
+ me->SetAbsVelocity( m_velocity );
+
+ pos += m_velocity * deltaT;
+
+ // check for collisions along move
+ trace_t result;
+ CTraceFilterSkipClassname filter( me, "eyeball_boss", COLLISION_GROUP_NONE );
+ Vector from = me->GetAbsOrigin();
+ Vector to = pos;
+ Vector desiredGoal = to;
+ Vector resolvedGoal;
+ int recursionLimit = 3;
+
+ int hitCount = 0;
+ Vector surfaceNormal = vec3_origin;
+
+ bool didHitWorld = false;
+
+ while( true )
+ {
+ TraceHull( from, desiredGoal, me->WorldAlignMins(), me->WorldAlignMaxs(), GetBot()->GetBodyInterface()->GetSolidMask(), &filter, &result );
+
+ if ( !result.DidHit() )
+ {
+ resolvedGoal = pos;
+ break;
+ }
+
+ if ( result.DidHitWorld() )
+ {
+ didHitWorld = true;
+ }
+
+ ++hitCount;
+ surfaceNormal += result.plane.normal;
+
+ // If we hit really close to our target, then stop
+ if ( !result.startsolid && desiredGoal.DistToSqr( result.endpos ) < 1.0f )
+ {
+ resolvedGoal = result.endpos;
+ break;
+ }
+
+ if ( result.startsolid )
+ {
+ // stuck inside solid; don't move
+ resolvedGoal = me->GetAbsOrigin();
+ break;
+ }
+
+ if ( --recursionLimit <= 0 )
+ {
+ // reached recursion limit, no more adjusting allowed
+ resolvedGoal = result.endpos;
+ break;
+ }
+
+ // slide off of surface we hit
+ Vector fullMove = desiredGoal - from;
+ Vector leftToMove = fullMove * ( 1.0f - result.fraction );
+
+ float blocked = DotProduct( result.plane.normal, leftToMove );
+
+ Vector unconstrained = fullMove - blocked * result.plane.normal;
+
+ // check for collisions along remainder of move
+ // But don't bother if we're not going to deflect much
+ Vector remainingMove = from + unconstrained;
+ if ( remainingMove.DistToSqr( result.endpos ) < 1.0f )
+ {
+ resolvedGoal = result.endpos;
+ break;
+ }
+
+ desiredGoal = remainingMove;
+ }
+
+ if ( hitCount > 0 )
+ {
+ surfaceNormal.NormalizeInPlace();
+
+ // bounce
+ m_velocity = m_velocity - 2.0f * DotProduct( m_velocity, surfaceNormal ) * surfaceNormal;
+
+ if ( didHitWorld )
+ {
+ //me->EmitSound( "Minion.Bounce" );
+ }
+ }
+
+ GetBot()->GetEntity()->SetAbsOrigin( result.endpos );
+
+ m_acceleration = vec3_origin;
+}
+
+
+//---------------------------------------------------------------------------------------------
+// (EXTEND) move directly towards the given position
+void CEyeballBossLocomotion::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 += tf_eyeball_boss_acceleration.GetFloat() * toGoal;
+}
+
+
+//---------------------------------------------------------------------------------------------
+void CEyeballBossLocomotion::SetDesiredSpeed( float speed )
+{
+ m_desiredSpeed = speed;
+}
+
+
+//---------------------------------------------------------------------------------------------
+float CEyeballBossLocomotion::GetDesiredSpeed( void ) const
+{
+ return m_desiredSpeed;
+}
+
+
+//---------------------------------------------------------------------------------------------
+void CEyeballBossLocomotion::SetDesiredAltitude( float height )
+{
+ m_desiredAltitude = height;
+}
+
+
+//---------------------------------------------------------------------------------------------
+float CEyeballBossLocomotion::GetDesiredAltitude( void ) const
+{
+ return m_desiredAltitude;
+}
+
+
+//---------------------------------------------------------------------------------------------
+// Face along path. Since we float, only face horizontally.
+void CEyeballBossLocomotion::FaceTowards( const Vector &target )
+{
+ CBaseCombatCharacter *me = GetBot()->GetEntity();
+
+ Vector toTarget = target - me->WorldSpaceCenter();
+ toTarget.z = 0.0f;
+
+ QAngle angles;
+ VectorAngles( toTarget, angles );
+
+ me->SetAbsAngles( angles );
+}
+
+
+//---------------------------------------------------------------------------------------------
+// return position of "feet" - the driving point where the bot contacts the ground
+// for this floating boss, "feet" refers to the ground directly underneath him
+const Vector &CEyeballBossLocomotion::GetFeet( void ) const
+{
+ static Vector feet;
+ CBaseCombatCharacter *me = GetBot()->GetEntity();
+
+ trace_t result;
+ CTraceFilterSimpleClassnameList filter( me, COLLISION_GROUP_NONE );
+ filter.AddClassnameToIgnore( "eyeball_boss" );
+
+ feet = me->GetAbsOrigin();
+
+ UTIL_TraceLine( feet, feet + Vector( 0, 0, -2000.0f ), MASK_PLAYERSOLID_BRUSHONLY, &filter, &result );
+
+ feet.z = result.endpos.z;
+
+ return feet;
+}
+
+
+
+//---------------------------------------------------------------------------------------------
+//---------------------------------------------------------------------------------------------
+CEyeballBossBody::CEyeballBossBody( INextBot *bot ) : CBotNPCBody( bot )
+{
+ m_leftRightPoseParameter = -1;
+ m_upDownPoseParameter = -1;
+ m_lookAtSpot = vec3_origin;
+}
+
+
+//---------------------------------------------------------------------------------------------
+void CEyeballBossBody::Update( void )
+{
+ CBaseCombatCharacter *me = GetBot()->GetEntity();
+
+ // track client-side rotation
+ Vector myForward;
+ me->GetVectors( &myForward, NULL, NULL );
+
+ const float myApproachRate = 3.0f; // 1.0f;
+
+ Vector toTarget = m_lookAtSpot - me->WorldSpaceCenter();
+ toTarget.NormalizeInPlace();
+
+ myForward += toTarget * myApproachRate * GetUpdateInterval();
+ myForward.NormalizeInPlace();
+
+ QAngle myNewAngles;
+ VectorAngles( myForward, myNewAngles );
+
+ me->SetAbsAngles( myNewAngles );
+
+ if ( tf_eyeball_boss_debug.GetBool() )
+ {
+ NDebugOverlay::Line( me->WorldSpaceCenter(), me->WorldSpaceCenter() + 150.0f * myForward, 255, 255, 0, true, 0.1f );
+ }
+
+ // move the animation ahead in time
+ me->StudioFrameAdvance();
+ me->DispatchAnimEvents( me );
+}
+
+
+//---------------------------------------------------------------------------------------------
+// Aim the bot's head towards the given goal
+void CEyeballBossBody::AimHeadTowards( const Vector &lookAtPos, LookAtPriorityType priority, float duration, INextBotReply *replyWhenAimed, const char *reason )
+{
+ CEyeballBoss *me = (CEyeballBoss *)GetBot()->GetEntity();
+
+ m_lookAtSpot = lookAtPos;
+ me->SetLookAtTarget( lookAtPos );
+}
+
+
+//---------------------------------------------------------------------------------------------
+// Continually aim the bot's head towards the given subject
+void CEyeballBossBody::AimHeadTowards( CBaseEntity *subject, LookAtPriorityType priority, float duration, INextBotReply *replyWhenAimed, const char *reason )
+{
+ CEyeballBoss *me = (CEyeballBoss *)GetBot()->GetEntity();
+
+ me->SetLookAtTarget( subject->EyePosition() );
+
+ if ( !subject )
+ return;
+
+ m_lookAtSpot = subject->EyePosition();
+}
diff --git a/game/server/tf/halloween/eyeball_boss/eyeball_boss.h b/game/server/tf/halloween/eyeball_boss/eyeball_boss.h
new file mode 100644
index 0000000..3ef481b
--- /dev/null
+++ b/game/server/tf/halloween/eyeball_boss/eyeball_boss.h
@@ -0,0 +1,338 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+// eyeball_boss.h
+// The 2011 Halloween Boss
+// Michael Booth, October 2011
+
+#ifndef EYEBALL_BOSS_H
+#define EYEBALL_BOSS_H
+
+#include "NextBot.h"
+#include "NextBotBehavior.h"
+#include "Path/NextBotPathFollow.h"
+#include "bot_npc/bot_npc_body.h"
+#include "../halloween_base_boss.h"
+
+#define EYEBALL_RADIUS 100.0f
+
+#define EYEBALL_NORMAL_SKIN 0
+#define EYEBALL_RED_SKIN 1
+#define EYEBALL_TEAM_RED 2
+#define EYEBALL_TEAM_BLUE 3
+
+#define PURGATORY_Z -1152
+
+#define EYEBALL_ANGRY 2
+#define EYEBALL_GRUMPY 1
+#define EYEBALL_CALM 0
+
+extern ConVar tf_eyeball_boss_debug;
+extern ConVar tf_eyeball_boss_debug_orientation;
+extern ConVar tf_eyeball_boss_lifetime;
+extern ConVar tf_eyeball_boss_lifetime_spell;
+extern ConVar tf_eyeball_boss_speed;
+extern ConVar tf_eyeball_boss_hover_height;
+extern ConVar tf_eyeball_boss_acceleration;
+extern ConVar tf_eyeball_boss_horiz_damping;
+extern ConVar tf_eyeball_boss_vert_damping;
+extern ConVar tf_eyeball_boss_attack_range;
+extern ConVar tf_eyeball_boss_health_base;
+extern ConVar tf_eyeball_boss_health_per_player;
+extern ConVar tf_halloween_bot_min_player_count;
+
+
+//----------------------------------------------------------------------------
+class CEyeballBossBody : public CBotNPCBody
+{
+public:
+ CEyeballBossBody( INextBot *bot );
+ virtual ~CEyeballBossBody() { }
+
+ virtual void Update( void );
+
+ virtual void AimHeadTowards( const Vector &lookAtPos,
+ LookAtPriorityType priority = BORING,
+ float duration = 0.0f,
+ INextBotReply *replyWhenAimed = NULL,
+ const char *reason = NULL ); // aim the bot's head towards the given goal
+ virtual void AimHeadTowards( CBaseEntity *subject,
+ LookAtPriorityType priority = BORING,
+ float duration = 0.0f,
+ INextBotReply *replyWhenAimed = NULL,
+ const char *reason = NULL ); // continually aim the bot's head towards the given subject
+
+ virtual float GetMaxHeadAngularVelocity( void ) const // return max turn rate of head in degrees/second
+ {
+ return 3000.0f;
+ }
+
+private:
+ int m_leftRightPoseParameter;
+ int m_upDownPoseParameter;
+
+ Vector m_lookAtSpot;
+};
+
+
+//----------------------------------------------------------------------------
+// Bypass vision system
+class CDisableVision : public IVision
+{
+public:
+ CDisableVision( INextBot *bot ) : IVision( bot ) { }
+ virtual ~CDisableVision() { }
+
+ virtual void Reset( void ) { }
+ virtual void Update( void ) { }
+};
+
+
+//----------------------------------------------------------------------------
+class CEyeballBossLocomotion : public ILocomotion
+{
+public:
+ CEyeballBossLocomotion( INextBot *bot );
+ virtual ~CEyeballBossLocomotion();
+
+ 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 void SetDesiredSpeed( float speed ); // set desired speed for locomotor movement
+ virtual float GetDesiredSpeed( void ) const; // returns the current desired 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
+ virtual float GetDeathDropHeight( void ) const; // distance at which we will die if we fall
+
+ 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 void FaceTowards( const Vector &target ); // rotate body to face towards "target"
+
+ // return position of "feet" - the driving point where the bot contacts the ground
+ // for this floating boss, "feet" refers to the ground directly underneath him
+ virtual const Vector &GetFeet( void ) const;
+
+protected:
+ float m_desiredSpeed;
+ float m_currentSpeed;
+ Vector m_forward;
+
+ float m_desiredAltitude;
+ void MaintainAltitude( void );
+
+ Vector m_velocity;
+ Vector m_acceleration;
+};
+
+
+inline float CEyeballBossLocomotion::GetStepHeight( void ) const
+{
+ return 50.0f;
+}
+
+inline float CEyeballBossLocomotion::GetMaxJumpHeight( void ) const
+{
+ return 100.0f;
+}
+
+inline float CEyeballBossLocomotion::GetDeathDropHeight( void ) const
+{
+ return 999.9f;
+}
+
+inline const Vector &CEyeballBossLocomotion::GetGroundNormal( void ) const
+{
+ static Vector up( 0, 0, 1.0f );
+
+ return up;
+}
+
+inline const Vector &CEyeballBossLocomotion::GetVelocity( void ) const
+{
+ return m_velocity;
+}
+
+inline void CEyeballBossLocomotion::SetVelocity( const Vector &velocity )
+{
+ m_velocity = velocity;
+}
+
+DECLARE_AUTO_LIST( IEyeballBossAutoList );
+
+//----------------------------------------------------------------------------
+class CEyeballBoss : public CHalloweenBaseBoss, public IEyeballBossAutoList
+{
+public:
+ DECLARE_CLASS( CEyeballBoss, CHalloweenBaseBoss );
+ DECLARE_SERVERCLASS();
+
+ CEyeballBoss();
+ virtual ~CEyeballBoss();
+
+ static void PrecacheEyeballBoss();
+ virtual void Precache();
+ virtual void Spawn( void );
+ virtual void UpdateOnRemove( void );
+
+ virtual int OnTakeDamage_Alive( const CTakeDamageInfo &info );
+
+ virtual void UpdateLastKnownArea( void ); // invoke this to update our last known nav area (since there is no think method chained to CBaseCombatCharacter)
+
+ // INextBot
+ DECLARE_INTENTION_INTERFACE( CEyeballBoss );
+ virtual CEyeballBossLocomotion *GetLocomotionInterface( void ) const { return m_locomotor; }
+ virtual CEyeballBossBody *GetBodyInterface( void ) const { return m_body; }
+ virtual CDisableVision *GetVisionInterface( void ) const { return m_vision; }
+
+ virtual void Update( void );
+
+ virtual Vector EyePosition( void );
+
+ virtual bool ShouldCollide( int collisionGroup, int contentsMask ) const;
+
+ virtual float GetCritInjuryMultiplier( void ) const; // when we are hit by a crit, damage is mutiplied by this
+
+ const Vector &GetHomePosition( void ) const;
+
+ void BecomeEnraged( float duration );
+ bool IsEnraged( void ) const;
+
+ bool IsGrumpy( void ) const;
+
+ void SetLookAtTarget( const Vector &spot );
+
+ void SetVictim( CBaseCombatCharacter *victim );
+ CBaseCombatCharacter *GetVictim( void ) const;
+
+ bool IsInPurgatory( CBaseEntity *entity ) const;
+
+ CBaseCombatCharacter *FindClosestVisibleVictim( void );
+
+ const Vector &PickNewSpawnSpot( void ) const;
+
+ void JarateNearbyPlayers( float range );
+
+ void SetDamageLimit( int limit );
+ void RemoveDamageLimit( void );
+
+ void LogPlayerInteraction( const char *verb, CTFPlayer *player );
+
+ void GainLevel( void );
+ void ResetLevel( void );
+ virtual int GetLevel( void ) const OVERRIDE;
+
+ virtual HalloweenBossType GetBossType() const { return HALLOWEEN_BOSS_MONOCULUS; }
+
+private:
+ CEyeballBossLocomotion *m_locomotor;
+ CEyeballBossBody *m_body;
+ CDisableVision *m_vision;
+
+ Vector m_eyeOffset;
+ Vector m_homePos;
+
+ CTFPlayer *m_target;
+
+ CountdownTimer m_invulnTimer;
+
+ CNetworkVector( m_lookAtSpot );
+ CNetworkVar( int, m_attitude );
+
+ CountdownTimer m_rageTimer;
+
+ CHandle< CBaseCombatCharacter > m_victim;
+
+ CUtlVector< CHandle< CBaseEntity > > m_spawnSpotVector;
+
+ int m_damageLimit;
+
+ static int m_level;
+};
+
+inline int CEyeballBoss::GetLevel( void ) const
+{
+ return m_level;
+}
+
+inline void CEyeballBoss::GainLevel( void )
+{
+ ++m_level;
+}
+
+inline void CEyeballBoss::ResetLevel( void )
+{
+ m_level = 1;
+}
+
+inline void CEyeballBoss::SetDamageLimit( int limit )
+{
+ m_damageLimit = limit;
+}
+
+inline void CEyeballBoss::RemoveDamageLimit( void )
+{
+ m_damageLimit = -1;
+}
+
+inline float CEyeballBoss::GetCritInjuryMultiplier( void ) const
+{
+ return 2.0f;
+}
+
+inline bool CEyeballBoss::IsInPurgatory( CBaseEntity *entity ) const
+{
+ if ( IsSpell() )
+ return false;
+
+ return ( entity->GetAbsOrigin().z < PURGATORY_Z );
+}
+
+inline void CEyeballBoss::SetVictim( CBaseCombatCharacter *victim )
+{
+ m_victim = victim;
+}
+
+inline bool CEyeballBoss::IsEnraged( void ) const
+{
+ // always enrage if I'm a spell
+ if ( IsSpell() )
+ return true;
+
+ // being near death always makes me mad
+ if ( GetHealth() < GetMaxHealth()/3 )
+ return true;
+
+ return m_rageTimer.HasStarted() && !m_rageTimer.IsElapsed();
+}
+
+inline bool CEyeballBoss::IsGrumpy( void ) const
+{
+ if ( IsEnraged() )
+ return false;
+
+ return ( GetHealth() < 2*GetMaxHealth()/3 );
+}
+
+inline const Vector &CEyeballBoss::GetHomePosition( void ) const
+{
+ return m_homePos;
+}
+
+inline Vector CEyeballBoss::EyePosition( void )
+{
+ return GetAbsOrigin() + m_eyeOffset;
+}
+
+inline void CEyeballBoss::SetLookAtTarget( const Vector &spot )
+{
+ m_lookAtSpot = spot;
+}
+
+#endif // EYEBALL_BOSS_H