summaryrefslogtreecommitdiff
path: root/game/server/tf/halloween/eyeball_boss/eyeball_boss.cpp
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /game/server/tf/halloween/eyeball_boss/eyeball_boss.cpp
downloadarchived-source-engine-2018-hl2-src-master.tar.xz
archived-source-engine-2018-hl2-src-master.zip
Diffstat (limited to 'game/server/tf/halloween/eyeball_boss/eyeball_boss.cpp')
-rw-r--r--game/server/tf/halloween/eyeball_boss/eyeball_boss.cpp1050
1 files changed, 1050 insertions, 0 deletions
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();
+}