diff options
Diffstat (limited to 'game/server/tf/halloween/eyeball_boss')
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 |